引言: 第二贴,分享下我现在学习的一些内容
个人感觉基础永远是最重要的. 各种框架是一栋栋精美的房子, 而基础就是构成这些房子的一块块材料. 正所谓万丈高楼平地起, 有时候我们不应该好高骛远, 框架是会过时淘汰的, 而基础只会在之前的基础上进行扩展或者修改, 可以说是永不过时的(除非这门语言淘汰了)
本帖将会持续更新, 分享的内容都是我现在学习时做的笔记 (笔记内贴有代码, 做笔记用的Typora[一个 MD编辑器], 所以笔记可以直接贴出来, 论坛的MD编辑器可能导致有些格式会丢失) , 废话不多说---------->开始!
第一章 数据类型
基本数据类型
数值型:
整数类型 <byte , short , int , long>
浮点类型 <float , double>
整数数据类型 |
占用储存空间(1字节=8bit) |
表数范围 |
byte |
1字节 |
-2^7 ~ 2^7-1 (-128~127) |
short |
2字节 |
-2^15 ~ 2^15-1 (-32768 ~ 32767) |
int |
4字节 |
-2^31 ~ 2^31-1 (-2147483648~2147483647)约21亿 |
long |
8字节 |
-2^63 ~ 2^63-1 |
整数型常量的表示形式
- 十进制整数, 如: 99, -500, 0
- 八进制整数, 要求以0开头, 如: 015
- 十六进制整数, 要求以0x或0X开头, 如: 0x15
- 二进制整数, 要求以0b开头, 如: 0b01110011
浮点型数据类型 |
占用储存空间 |
表数范围 |
float(单精度类型,可以精确到7位有效数字) |
4字节 |
-3.403E38 ~ 3.403E38 |
double(双精度类型,精度约是float的两倍) |
8字节 |
-1.798E308 ~ 1.798E308 |
浮点型常量的表示形式
-
十进制数, 例如: 3.14, 314.0, 0.314
-
科学计数法, 例如(e2=10^2, e-2=10^-2): 314E2 314e2 314e-2
-
> java
中java.math
包下面的两个有用的类: BigInteger
和BigDecimal
, 这两个类可以处理任意长度的数值
> BigInteger
实现了任意精度的整数运算, BigDecimal
实现了任意精度的浮点运算
后续展开
字符型: <char>
布尔型: <boolean>
引用数据类型(随着深入会展开)
类 class
接口 interface
数组 []
整数默认 : int
小数默认: double
数据类型的自动提升
- 所有的Byte型,short型和char型的值将被提升到int型
- 如果一个操作数是long型,计算结果就是long型
- 如果一个操作数是float型,计算结果就是float型
- 如果一个操作数是double型,计算结果就是double型
转义字符
\b
(退格);
\n
(换行);
\r
(回车);
\t
(制表符);
"
(双引号);
'
(单引号);
\\
(反斜杠);
第二章 运算符
算术运算符
运算符 |
含义 |
例子 |
结果 |
+ |
加法 |
9+4 |
13 |
- |
减法 |
9-4 |
5 |
* |
乘法 |
9*4 |
36 |
/ |
除法(当两边为整数时,取整数部分,舍余数。当其中一边为浮点型时,按正常规则相除) |
9/4 |
2 |
% |
取余(0不能做除数,否则报错) |
9%4 |
1 |
++ |
自增(++在后,先赋值再运算) |
a=3 ; c=a++ |
先赋值运算c=a=3 ; 再算术运算a=a+1=4 |
++ |
自增(++在前,先运算在赋值) |
a=3 ; c=++a |
先算术运算a=a+1=4 ; 再赋值运算c=a=4 |
-- |
自减(--在后,先赋值再运算) |
a=3 ; c=a-- |
先赋值运算c=a=3 ; 再算术运算a=a-1=2 |
-- |
自减(--在前,先运算在赋值) |
a=3 ; c=--a |
先算术运算a=a-1=2 ; 再赋值运算c=a=2 |
赋值运算符
运算符 |
含义 |
例子 |
结果 |
= |
将运算符右边的值赋给左边的变量<br/>(左边只允许是变量,不能是表达式或其他形式) |
int a ; a=4 |
将4赋值给变量a ,a的值由未知变为4, a=4 |
+= |
将该运算符左边的数值加上右边的数值, 其结果赋值给左边变量本身 |
int a=4 ; a+=4 |
a=a+4=4+4=8 |
-= |
将该运算符左边的数值减去右边的数值, 其结果赋值给左边变量本身 |
int a=4 ; a-=4 |
a=a-4=4-4=0 |
*= |
将该运算符左边的数值乘以右边的数值, 其结果赋值给左边变量本身 |
int a=4 ; a*=4 |
a=a*4=4*4=16 |
/= |
将该运算符左边的数值整除右边的数值, 其结果赋值给左边变量本身 |
int a=4 ; a/=4 |
a=a/4=4/4=1 |
%= |
将该运算符左边的数值除以右边的数值后取余,其结果赋值给左边变量本身 |
int a=4 ; a%=4 |
a=a%4=4%4=0 |
比较(关系)运算符
- (运算结果都是
boolean
型 [ true
(真) , false
(假) ])
运算符 |
含义 |
例子 |
结果 |
> |
大于 |
4>6 |
false |
< |
小于 |
4<6 |
true |
\>= |
大于或等于 |
4>6 |
false |
<= |
小于或等于 |
4<=6 |
true |
== |
两边是否相等(比较对象地址) |
String a="abc"; String b="abc"; System.out.println(a==b); System.out.println(a.equals(b)); |
a==b //内存空间相同 true a.equals(b) //值相同 true |
equals |
是否相等(比较对象内容) |
String a="abc"; String b=new String("abc"); System.out.println(a==b); System.out.println(a.equals(b)); |
a==b //内存空间不同 false a.equals(b) //值相同 true |
!= |
两边是否不等 |
4!=6 |
true |
逻辑运算符
- 特点是操作
boolean
值
- 运算符左右两边必须是
boolean
值 (将两边的结果进行连接,判断真或假 true || false
)
- 其中
&
|
^
这三个也是位运算符,能操作数字
&
|
^
这三个运算符 当两侧操作符是boolean
时作为逻辑运算符,两侧操作符是整数类型,则作为位运算符
或,或-短路
,论坛MD会转义,用文字代替
运算符 |
含义 |
例子 |
结果 |
& |
and(与) <只有两边结果都为true,结果才为true ; 只要有一边结果为false,结果为false> |
false&true; true&true |
false ; true |
或 |
or(或) <只有两边结果都为false,结果才为false; 只要有一边结果为true,结果为true> |
false或true; false或false |
true ; false |
^ |
xor(异或) <两边结果为true,结果为false ; 其余的情况与 (或) 相同 > |
false^true ; false^false ; true^true |
true ; false ; false |
! |
not(非) <输出相反的情况 > |
!true |
false |
&& |
and(短路) < 运算结果与&一致,运算方法不同; 左边的一旦为false,右边的就不再参与计算,直接输出结果为false> |
false&&true ; true&&true |
false;true |
或-短路 |
or(短路)<运算结果与(或)一致,运算方法不同; <br/>左边的一旦为true,右边的就不再计算,直接输出结果为true> |
false 或-短路 true ; false 或-短路 false |
true ; false |
位运算符(非重点, 但是一些加密算法经常用到)
- 对二进制直接进行运算
&
|
^
这三个运算符 当两侧操作符是boolean
时作为逻辑运算符,两侧操作符是整数类型,则作为位运算符
运算符 |
含义 |
运算细节 |
例子 |
结果 |
<< |
左移(左移几位就是该数据乘以2的几次方) |
空位补0 ,被移除的高位丢弃 |
3<<2=12 |
3*2*2=12 |
>> |
右移(右移几位就是该数据除以2的几次方) |
被移除的高位是0就补0 ,是1就补1 |
6>>2=1 |
6/2(2)=1 |
>>> |
无符号右移 |
被移除的高位无论是0还是1 ,空位都补0 |
3>>>1 |
3/2=1 |
& |
与 |
只有1&1时是1 ; 否则是0 |
6&3 |
2 |
或 |
或 |
只有0或0时是0 ; 否则是1 |
6或3 |
7 |
^ |
异或 |
(相同二进制位进行^运算,结果是0 {1^1=0 ;0^0=0} 不同 , 结果是1 .{1^0=1 ; 0^1=1}) |
6^3 |
5 |
~ |
反码 |
对二进位按位求反(1变0 ; 0变1) |
~6 |
-7 |
三元运算符
运算符 |
含义 |
例子 |
结果 |
? : [条件语句]? [表达式1]:[表达式2] |
如果?左边的条件语句为真 ,执行表达式1 ; 如果?左边的条件语句为假 ,执行表达式2 |
int a=1,b=2,c; c=a>b?100:200; |
c=200 |
运算符之间的优先级
优先级 |
运算符 |
类 |
1 |
() |
括号运算符 |
2 |
! , +(正号) , -(负号) |
一元运算符 |
2 |
~ |
位逻辑运算符 |
2 |
++ , -- |
递增与递减运算符 |
3 |
* , / , % |
算术运算符 |
4 |
+ , - |
算术运算符 |
5 |
<< , >> |
位左移,右移运算符 |
6 |
> , >= , < , <= |
关系运算符 |
7 |
== , != |
关系运算符 |
8 |
& |
位运算符,逻辑运算符 |
9 |
^ |
位运算符,逻辑运算符 |
10 |
或 |
位运算符,逻辑运算符 |
11 |
&& |
逻辑运算符 |
12 |
或-短路 |
逻辑运算符 |
13 |
?: |
条件运算符 |
14 |
= ,+= , -= , *= , /= , %= |
赋值运算符,扩展运算符 |
第三章 程序流程控制
1. 判断结构
if-else
- 对具体的值进行判断 a\==b ; a\==6
- 对区间进行判断 a>b ; a<6
- 对运算结果是boolean类型的表达式进行判断 a>b||a<6 ; true ; false
1. if(条件表达式){
执行语句;
}
2. if(条件表达式){
执行语句;
}else{
执行语句;
}
3. if(条件表达式){
执行语句;
}else if(条件表达式){
执行语句;
}else{
执行语句;
}
2. 选择结构
switch-case
- 对具体的值进行判断
- 对于几个固定的值判断,switch会将所有结果加载进内存,然后一一匹配,效率比if高
switch(表达式){
case 取值1:
执行语句;
break;
case 取值2:
执行语句;
break;
.....
default:
执行语句;
break;
}
3. 循环结构
while
while(条件表达式){
执行语句;
}
while中最简单的无限循环 : while(true){}
do while
无论条件是否满足 ,循环体至少执行一次 , 即 do{ } 之中的语句至少执行一次
do{
执行语句;
}while(条件表达式);
while循环使用的变量可以在循环结束后继续使用
for
for(初始化表达式;条件表达式;循环后的操作表达式){
执行语句;
}
for循环为了循环而定义的变量会在循环结束时在内存中释放
for的其它情况
int i=0;
for(System.out.println("a"),System.out.println("b");i<3;
System.out.println("c"),System.out.println("d")){
i++;
}
for循环中,初始化表达式 ; 条件表达式 ; 循环后的操作表达式都==可以定义多个== ; 有多个表达式时可以用 , 号隔开 .
for循环中,初始化表达式 ; 循环后的操作表达式 不仅仅局限于算术和赋值运算 ; 但不能为==逻辑运算==和==比较运算==
for循环的三个表达式都可以不写 ; 不写表达式时 , 条件表达式默认为true , 其余都为空
if中最简单的无限循环 : if(;;){}
给for循环标号
一:for(int i=1;i<=3;i++){
二:for(int x=1;x<=3;x++){
System.out.println("x="+x);
if(x==2){
break 一;
}
}
System.out.println("i="+i);
}
给for循环标号可以指定break跳出的循环 ; 当最外层循环结束时,整个循环就结束了
4. 其它流程控制语句
break : 跳出==当前==循环 ; ==结束==当前循环
break语句只能在选择结构和循环结构中使用
当最外层循环结束时,整个循环就结束了
continue : 跳出==本次==循环 ; ==继续==下次循环
continue语句只能在循环结构中使用
第四章 修饰符
Java语言提供了很多修饰符,主要分为以下两类:
修饰符用来定义类、方法或者变量,通常放在语句的最前端。我们通过下面的例子来说明:
public class ClassName {
// ...
}
private boolean myFlag;
static final double weeks = 9.5;
protected static final int BOXWIDTH = 42;
public static void main(String[] arguments) {
// 方法体
}
访问控制修饰符
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
- default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
- private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
- public : 对所有类可见。使用对象:类、接口、变量、方法
- protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
我们可以通过以下表来说明访问权限:
修饰符 |
当前类 |
同一包内 |
子孙类(同一包) |
子孙类(不同包) |
其他包 |
public |
Y |
Y |
Y |
Y |
Y |
protected |
Y |
Y |
Y |
Y/N(说明) |
N |
default |
Y |
Y |
Y |
N |
N |
private |
Y |
N |
N |
N |
N |
默认访问修饰符-不使用任何关键字
使用默认访问修饰符声明的变量和方法,对同一个包内的类是可见的。接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public。
如下例所示,变量和方法的声明可以不使用任何修饰符。
String version = "1.5.1";
boolean processOrder() {
return true;
}
私有访问修饰符-private
私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private。
声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。
Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。
下面的类使用了私有访问修饰符:
public class Logger {
private String format;
public String getFormat() {
return this.format;
}
public void setFormat(String format) {
this.format = format;
}
}
实例中,Logger 类中的 format 变量为私有变量,所以其他类不能直接得到和设置该变量的值。为了使其他类能够操作该变量,定义了两个 public 方法:getFormat() (返回 format的值)和 setFormat(String)(设置 format 的值)
公有访问修饰符-public
被声明为 public 的类、方法、构造方法和接口能够被任何其他类访问。
如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。
以下函数使用了公有访问控制:
public static void main(String[] arguments) {
// ...
}
Java 程序的 main() 方法必须设置成公有的,否则,Java 解释器将不能运行该类。
受保护的访问修饰符-protected
protected 需要从以下两个点来分析说明:
- 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
- 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。
protected 可以修饰数据成员,构造方法,方法成员,不能修饰类(内部类除外)。
接口及接口的成员变量和成员方法不能声明为 protected。
子类能访问 protected 修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。
下面的父类使用了 protected 访问修饰符,子类重写了父类的 openSpeaker() 方法。
class AudioPlayer {
protected boolean openSpeaker(Speaker sp) {
// 实现细节
}
}
class StreamingAudioPlayer extends AudioPlayer {
protected boolean openSpeaker(Speaker sp) {
// 实现细节
}
}
如果把 openSpeaker() 方法声明为 private,那么除了 AudioPlayer 之外的类将不能访问该方法。
如果把 openSpeaker() 声明为 public,那么所有的类都能够访问该方法。
如果我们只想让该方法对其所在类的子类可见,则将该方法声明为 protected。
protected 是最难理解的一种 Java 类成员访问权限修饰词.
请注意以下方法继承的规则:
- 父类中声明为 public 的方法在子类中也必须为 public。
- 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
- 父类中声明为 private 的方法,不能够被继承。
非访问修饰符
为了实现一些其他的功能,Java 也提供了许多非访问修饰符。
-
static 修饰符,用来修饰类方法和类变量。
-
final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
-
abstract 修饰符,用来创建抽象类和抽象方法。
-
synchronized 和 volatile 修饰符,主要用于线程的编程。
static 修饰符
-
静态变量:
static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
-
静态方法:
static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。
一个static方法内可以调用非static方法吗
非static方法属于某个对象,也叫实例方法,必须创建一个对象后,才可以调用该对象的该非static方法。
> 而static方法(静态方法)也叫类方法,属于某个类,可以直接通过类名调用,不用创建对象。
> 也就是说,当调用一个static方法时,可能还没有创建任?***韵螅耸辈荒艿饔梅莝tatic方法。
对类变量和方法的访问可以直接使用 classname.variablename 和 classname.methodname 的方式访问。
如下例所示,static修饰符用来创建类方法和类变量。
public class InstanceCounter {
private static int numInstances = 0;
protected static int getCount() {
return numInstances;
}
private static void addInstance() {
numInstances++;
}
InstanceCounter() {
InstanceCounter.addInstance();
}
public static void main(String[] arguments) {
System.out.println("Starting with " +
InstanceCounter.getCount() + " instances");
for (int i = 0; i < 500; ++i){
new InstanceCounter();
}
System.out.println("Created " +
InstanceCounter.getCount() + " instances");
}
}
以上实例运行编辑结果如下:
Starting with 0 instances
Created 500 instances
final 修饰符
final 修饰符通常和 static 修饰符一起使用来创建类常量。
public class Test{
final int value = 10;
// 下面是声明常量的实例
public static final int BOXWIDTH = 6;
static final String TITLE = "Manager";
public void changeValue(){
value = 12; //将输出一个错误
}
}
如下所示,使用 final 修饰符声明方法。
public class Test{
public final void changeName(){
// 方法体
}
}
public final class Test {
// 类体
}
abstract 修饰符
抽象类:
抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。
抽象类可以包含抽象方法和非抽象方法。
abstract class Caravan{
private double price;
private String model;
private String year;
public abstract void goFast(); //抽象方法
public abstract void changeColor();
}
抽象方法
抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。
抽象方法不能被声明成 final 和 static。
任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。
抽象方法的声明以分号结尾,例如:public abstract sample();。
public abstract class SuperClass{
abstract void m(); //抽象方法
}
class SubClass extends SuperClass{
//实现抽象方法
void m(){
.........
}
}
synchronized 修饰符
synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。
public synchronized void showDetails(){
.......
}
transient 修饰符
序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。
public transient int limit = 55; // 不会持久化
public int b; // 持久化
volatile 修饰符
volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
一个 volatile 对象引用可能是 null。
public class MyRunnable implements Runnable
{
private volatile boolean active;
public void run()
{
active = true;
while (active) // 第一行
{
// 代码
}
}
public void stop()
{
active = false; // 第二行
}
}
通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法。 如果 第一行 中缓冲区的 active 值被使用,那么在 第二行 的 active 值为 false 时循环不会停止。
但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止。
第五章 函数方法
函数定义格式
修饰符 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,.....){
执行语句;
return 返回值;
}
-
特殊情况
1. 函数没有参数值
>函数不一定需要写参数值,当函数不写参数值时,括号留空
>
>```
>void add(){
> System.out.println("Hello");
> return;//如果返回值类型是void,那么函数中的return返回值可以省略不写,写上也不算错
>}
>```
2. 函数没有具体的返回值
> 这时 return后面直接以分号结束 return;
>
> 应为没有具体的返回值,返回的数据类型是未知的,所以不能写数据类型,此时,返回值类型用关键字void来表示
>
> ```
> void 函数名(参数类型 形式参数1,参数类型 形式参数2,.....){
> System.out.println("Hello");
> return;//如果返回值类型是void,那么函数中的return返回值可以省略不写,写上也不算错
> }
> ```
函数的特点
- 可以将功能代码进行封装 ; 便于对该功能进行重复使用 ; 同时减少重复代码的数量 ; 提高代码的复用性
- 函数只有被调用时才会执行
- 对于没有具体返回值的函数,返回值类型用关键字void表示,那么该函数中的return语句如果在最后一行可以省略不写
-
注意 :
函数的重载
概念 : 在同一个类中,允许存在一个以上的同名函数,只要他们的参数个数==或者==参数类型不同即可
特点 : 与返回值类型无关 , 只看参数列表
public static void main(String[] args){
System.out.println(add(3,4));//通过传递的参数来匹配运行方法
System.out.println(add("年龄",4));
System.out.println(add(3,3,5));
}
static int add(int a,int b){
return a+b;
}
static String add(String a,int b){/*与返回值类型无关,只看参数列表*/
return a+""+b;
}
static boolean add(int a,int b,int c){/*与返回值类型无关,只看参数列表*/
return a==b;
}
提高代码复用性,对重复性功能进行封装
/*打印乘法表*/
static void CFB(int num){
for(int x=1;x<=num;x++){
for(int y=1;y<=x;y++){
System.out.print(y+"*"+x+"="+x*y+"\t");
}
System.out.println();
}
}
/*打印标准乘法表*/
static void CFB(){
/* for(int x=1;x<=9;x++){
for(int y=1;y<=x;y++){
System.out.print(y+"*"+x+"="+x*y+"\t");
}
System.out.println();
} */
CFB(9);//代码复用,不再写重复性功能
}
第六章 数组
数组的定义
- 概念 : 同一种类型数据的集合 , 容器的一种
- 特点 : 可以给数组中的元素自动从0开始编号
数组的格式
格式1:
/*创建一个容器,但不明确容器中的具体数据,只定义数据类型和存储的数据个数+1(索引从0开始)*/
元素类型[] 数组名=new 元素类型[元素个数或者数组长度];
例:int[] a=new int[5]; String[] b=new String[9];
格式2:
/*创建一个容器,存储已知的具体数据*/
元素类型[] 数组名=new 元素类型[]{元素,元素,元素,元素,....};
/*创建数组的同时指定了数组中每一个索引位置上的值*/
例:int[] a=new int[]{1,2,3,4,5}; String[] b=new String[]{"张三","李四","王二麻子",""};
或者省略:new 元素类型[] <静态初始化赋值>
例:int[] arr={1,2,3,4}; String[] b={"张三","李四","王二麻子",""};
数组的遍历
核心思想 : 操作数组的下标
String[] arr=new String[]{"张三","李四","王二"};
//正向遍历
for(int i=0;i<arr.length;i++){//数组的长度 length 从1开始 , 下标从0开始
System.out.println(arr[i]);
};
//反向遍历
for(int i=arr.length-1;i>=0;i--){
System.out.println(arr[i]);
}
//将遍历数组封装,后面遍历数组直接调用此方法,提高代码的复用性
public static void BL(int[] arr){
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
数组的最值
//main函数:
int[] ds=new int[]{23,34,56,87,43,98,12};
int dsmax=getMax(ds);
System.out.println(dsmax);
//取最值的方法:
public static int getMax(int[] arr){
int max=arr[0];//初始化max用数组本身的元素来,更省事
for(int i=1;i<arr.length;i++){//数组长度 length 从1开始
if(arr[i]>max){//数组数据大于max
max=arr[i];//将大的值赋给max,,max的值在比较后永远保留大的值,所有比较完成后max就是最大的那个值
}
}
return max;
}
数组排序
-
选择排序
//main函数:
int[] ds=new int[]{23,34,56,87,43,98,12};
arrPX(ds);//使用函数
for(int i=0;i<ds.length;i++){//遍历排序后的数组
System.out.println(ds[i]);
}
//排序方法 -- 选择排序:
public static void arrPX(int[] arr){
for(int i=0;i<arr.length-1;i++){//length-1 外循环的最后一位不需要参加比较
for(int x=i+1;x<arr.length;x++){
if(arr[i]>arr[x]){
int temp=arr[i];//将比较后大的那个值赋值给temp
arr[i]=arr[x];//将小的值赋值给arr[i],i的值比x的值大1,也就是把小的值向前移一位
arr[x]=temp;//将大的值赋值给arr[x],大的值向后移一位
}
}
}
}
-
冒泡排序
/**
内循环:
-1: 为了避免角标越界.
-x: 为了让外循环增加一次,
内循环参数与比较的元素个数递减
*/
public static void bubbleSort(int[] arr){
for(int x=0;x<arr.length-1;x++){
for(int y=0;y<arr.length-1-x;y++){
if(arr[y]>arr[y+1]){
int temp=arr[y];
arr[y]=arr[y+1];
arr[y+1]=temp;
}
}
}
}
封装思想
将逻辑一样,被重复使用的代码进行封装
/**将排序中调换位置的方法抽取出来 封装成方法*/
public static void swap(int[] arr,int a,int b){
int temp=arr[a];
arr[a]=arr[b];
arr[b]=temp;
}
二维数组
//二维数组
//创建一个二维数组,该数组中有3个一维数组,每个一维数组中有2个元素
int[][] arr=new int[3][2];
//当元素未指定具体值时,其值为引用类型的默认值
//System.out.println(arr);//打印二维数组
//System.out.println(arr[0]);//打印二维数组中角标为0的一维数组
//System.out.println(arr[0][0]);//打印二维数组中角标为0的一维数组中角标为0的元素;
//第二中定义格式
//int[][] arr2=new int[3][];
//System.out.println(arr2);
//System.out.println(arr2[0]);//null
//System.out.println(arr2[0][0]);//NullPointerException
//打印二维数组的长度,其实就是一维数组的个数
System.out.println(arr.length);
//打印二维数组中角标为1的一维数组的长度
System.out.println(arr[1].length);
//定义一个二维数组,里面有三个一维数组
//第一个一维数组有3个元素
//第二个一维数组有4个元素
//第三个一维数组有2个元素
int[][] arr3={{3,1,7},{4,2,6,8},{5,2}};
//遍历二维数组
for(int x=0;x<arr3.length;x++){
for(int y=0;y<arr3[x].length;y++){
System.out.print(arr3[x][y]);
}
}
第七章 面向对象
案例
大象装进冰箱
面向过程:
- [打开] 冰箱
- [存储] 大象
- [关上] 冰箱
对于面向过程思想, 强调的是过程(动作/ 函数), 怎么打开冰箱,关闭冰箱都需要我们去定义, 定义[功能]-->执行[功能]
典型的面向过程语言: C语言
面向对象:
对于面向对象来说, 大象和冰箱都是对象(实体), 而冰箱本身已经具备了打开, 关闭, 存储这些功能. 也就说,冰箱这个对象本身, 封装了这些功能, 我要用到冰箱时, 不需要去考虑怎么打开,关闭冰箱, 直接使用这些已经封装好的功能就可以了
- [冰箱] 打开
- [冰箱] 存储
- [冰箱] 关闭
对于面向对象思想, 强调的是对象(实体), 尽管在真正的编程中,我们需要自己设计封装对象的一些功能, 但是在用到这个对象时, 因为已经封装好了功能, 我们就可以将注意力放在对象身上, 在需要用到对象的某些功能时,我们不需要再去考虑每一步的动作,过程, 我们只需要使用这些已经存在的方法就可以了
典型的面向对象语言: C++ Java C#
面向对象的特点:
- 面向对象就是一种常见的思想, 符合人们的思考习惯
- 面向对象的出现,将复杂的问题简单化
- 面向对象的出现,让曾经在过程中的执行者, 变成了对象中的指挥者
类与对象之间的关系
类: 用Java语言对现实生活中的事物进行描述, 通过类的形式来体现
怎么描述呢?
对于事物描述,通常只关注两方面, 一个是属性, 一个是行为
只要明确该事物的属性和行为, 并定义在类中即可.
对象: 就是该类事物实实在在存在的个体
类与对象之间的关系?
类: 事物的描述
对象: 该类事物的实例, 在Java中通过 new
这个关键字来创建
类与对象的体现
/*描述小汽车*/
//分析:
//1.属性: 轮胎数, 颜色
//2.行为: 运行
class Car{
int num;
//java中所有关键字都是小写的,String比较特殊,不是java的关键字,是一个类
String color;
void run(){
System.out.println(num+".."+color);
}
}
class CarDemo{
public static void main(String[] args){
//在计算机中创建一个car的实例, 通过new关键字来实现
Car c=new Car();//c就是一个类类型的引用变量,指向了该类的对象,括号表示创建car对象时,需要指定一些内容,不指定则留空
c.num=4;//给成员变量赋值叫做显式初始化
c.color="red";
c.run();//要使用对象中的内容, 可以通过 对象.成员 的形式来完成调用
//事物的组成部分就叫成员, 分为两类
//成员变量 成员函数
}
}
定义类其实就是在定义类中的成员
成员: 成员变量<--->属性 成员函数<--->行为
成员变量与局部变量的区别
-
成员变量定义在类中, 整个类都可以访问; 局部变量定义在函数, 语句, 局部代码块中, 只在所属的区域有效
-
成员变量存储在堆内存的对象中; 局部变量存储在栈内存的方法中
-
成员变量随着对象的创建而存在, 随着对象的消失而消失; 局部变量随着所属区域的执行而存在, 随着所属区域的结束而释放
-
成员变量都有默认的初始化值; 局部变量没有默认初始值
-
当局部变量与成员变量重名时, 可以使用关键字this来区分
类类型参数
class CarDemo2{
public static void main(String[] args){
Car2 c1=new Car2();
Car2 c2=new Car2();
show(c1);
show(c2);
}
public static void show(Car2 c){//类类型的变量一定指向对象, 要不就是null
c.num =3;
c.color="red";
System.out.println(c.num+"..."+c.color);
}
}
class Car2{
int num;
String color;
void run(){
System.out.println(num+"..."+color);
}
}
匿名对象
顾名思义, 匿名对象就是没有名字的对象
Car c= new Car();//定义了一个名字叫c的Car类型的对象
new Car();//匿名对象, 对象被创建了,但是没有给他赋予名字
//匿名对象其实就是定义对象的简写格式
new Car().run();
//当对对象 方法 仅进行一次调用的时候, 就可以简化成匿名对象
//匿名对象可以作为实际参数进行传递
shwo(new Car());
参数传递
//基本数据类型参数传递
class Demo{
public static void main(String[] args){
int x=3;
show(x);
System.out.print("x="+x);
}
public static void show(int x){
x = 4;
}
}
//最终输出结果: x=3
//局部变量存储于栈内存中,随着所属作用域的结束而在内存中释放,
//因此,在show方法中,x=4的赋值操作,在main函数中执行show方法后,
//这个为4的x就被释放了.而在main函数中,int x=3的作用域是整个main函数,
//main函数所属的栈的x此时始终是3,并没有被改变,
//x=4只是在show方法所属的栈中存在,且随着show方法执行结束而被释放
//在栈内存中放入变量叫做压栈, 变量被释放叫做弹栈. 可以看做是一个弹夹
//引用数据类型参数传递
class Demo2{
int x= 3;
public static void main(String[] args){
Demo2 d=new Demo2();
d.x=9;
show(d);
System.out.println(d.x);
}
public static void show(Demo2 d){
d.x=4;
}
}
//最终结果: x= 4
//对象储存于堆内存中, Demo2这个类所描述的对象Demo2有一个成员变量x(初始化为0),
//且为x赋了值为3, 在main函数中Demo2 d=new Demo2();
//创建了一个类型是Demo2名称为d的对象,这个对象存储于堆内存中,
//在程序执行到d.x=9时,为对象d的成员变量x(值为3)赋值为9,
//在执行到show(d)时,此时堆内存中对象的成员变量x的值为9,
//show方法引用了Demo2所描述的对象,
//此时对象中的成员变量x经过d.x=9的赋值操作依据变成了9,
//在show方法中执行了d.x=4, 为Demo2所描述的对象的成员变量x重新赋值了4,
//此时,Demo2所描述的对象中的成员变量值变为了4,
//因此,在执行输出语句System.out.println(d.x);时, x为4
面向对象的特征
封装
封装: 是指隐藏对象的属性和实现细节, 仅对外提供公共访问方式
好处:
> 将变化隔离
> 便于使用
> 提高重用性
> 提高安全性
封装原则:
- 将不需要对外提供的内容都隐藏起来
- 把属性都隐藏, 提供公共方法对其访问
//描述一个人
//属性: 姓名, 年龄, 升高
//行为: 奔跑, 吃
class Person{
String name;
int age;
int height;
void run(){
System.out.print(name+"奔跑");
}
void eat(){
System.out.print(name+"吃");
}
}
class personDemo{
public static void main(String[] args){
Person p = new Person();
p.name = "sunwul";
p.run();
p.eat();
}
}
//以上,对人进行了一个简单的封装Person,并在personDemo类中使用了Person,
//但这种封装方式并没有将对象中成员变量的变化隔离,程序直接使用并改变了成员变量的值,
//这样也不安全,所以,在日常的编程中, 我们往往会使用另一种更加安全,更便于使用,
//且能将对象中成员变量的变化隔离的方式来对对象进行封装
class Person{
private String name;
private int age;
private int height;
public String getName(){
return name;
}
public void setName(String name){
this.name = name
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
public int getHeight(){
return height;
}
public void setHeight(int height){
this.height=height;
}
}
//这种封装方式把属性都隐藏起来了, 只提供公共方法对其访问,极大的提高了安全性,
//是大家公认的封装方式
java中最小的封装体就是函数,
封装不一定需要对属性方法进行私有, 不私有一样能进行封装. 私有仅仅是封装的一种体现
私有就是封装,这是对的, 封装就是私有,这是错误的
继承
一个类继承另一个类, 这个类叫做子类, 被继承的类叫做父类/超类/基类
所有的类都直接或间接的继承Object类,Object类也被称为根类
继承的好处:
- 提高了代码的复用性
- 让类与类之间产生了关系, 给第三个特征多态提供了前提
java中 支持单继承, 不直接支持多继承, 但对C++中的多继承机制进行改良
单继承: 一个子类只能有一个直接父类
多继承: 一个子类可以有多个直接父类(java中不直接支持,进行改良, 在java中是通过"多实现"的方式来体现)
class A{}
class B{}
class C extends A{}//单继承
class C extends A,B{}//多继承,类C可以同时拥有A与B的内容--java不直接支持
//java中不直接支持多继承的原因: 当多个父类中有相同成员, 会产生调用的不确定性
class A{
void show(){
System.out.println("A")
}
}
class B{
void show(){
System.out.println("B")
}
}
class C extends A,B{}//多继承
new C().show();//由于C同时继承了A与B,当父类中有相同的方法时,此时调用方法会产生不确定性,程序不知道应该调用哪个父类中的show方法
java支持多重(多层)继承: C继承B, B继承A
使用多重继承会出现继承体系, 当要使用一个继承体系时:
- 查看该体系中的顶层类, 了解该体系的基本功能
- 创建体系中的最子类对象, 完成功能的使用
什么时候定义继承
当类与类之间存在着所属关系的时候, 就定义继承. X是Y中的一种, X extends Y
子类与父类中, 成员特点的体现
- 成员变量
当本类的成员和局部变量同名, 用this区分
当子父类中的成员变量同名用super区分父类
class fu{
int num = 4;
}
class zi extends fu{
int num = 5;
void show(){
//使用this来区分本类的成员, 使用super来区分父类的成员
System.out.println(this.num+"..."+super.num);
}
}
class ExtendsDemo{
public static void main(String[] args){
zi z=new zi();
z.show();
}
}
/*
this: 代表一个本类对象的引用
super: 代表一个父类空间,不代表父类对象,因为在代码中,至始至终都没有出现父类对象
子类为什么能使用父类中的内容: 因为子类持有super引用
在子类继承父类时,持有一个super,这个super立即指向了父类所属空间
*/
子类不能直接访问父类中私有的成员
- 成员函数
当子父类中出现成员函数一模一样的情况, 会运行子类的函数, 这种现象称为==覆盖操作==, 这是函数在子父类中的特性
函数的两个特性:
-
重载 overload 同一个类中, 允许同名函数存在,多个同名函数以参数列表(个数or类型)区分
-
覆盖 override 子类中, 覆盖也称为重写, 覆写. 子类与父类有同名函数,子类中的函数会覆盖父类中的函数,当执行子类中这个同名函数时,会运行子类的函数
> 覆盖操作的注意事项:
>
> 1. 必须体现出子类与父类的函数一模一样(返回值类型,函数名,参数列表)
> 2. 子类方法覆盖父类方法时, 子类权限必须大于等于父类权限
> 3. 当父类函数权限为private时,因为是私有的,所以不能被外部类所发现,因此也不能被覆盖
> 4. 当父类的函数被静态static修饰时,也不能覆盖,只有子类也和父类一模一样被静态修饰才能覆盖(==静态只能覆盖静态或被静态覆盖==)
>
> > class fu{ > //当在父类show方法加上public权限修饰符时,编译报错 > /*报错信息: 正在尝试分配更低的访问权限; 以前为public*/ > /*public*/ void show(){ > System.out.println("fu show run"); > } > } > class zi extends fu{ > void show(){ > System.out.println("zi show run"); > } > } > class OverrideDemo{ > public static void main(String[] args){ > zi z=new zi(); > //子类覆盖父类方法 > z.show(); > } > } > //子类与父类函数一模一样,子类想要覆盖父类方法,要注意子类权限必须大于等于父类权限 > //当父类函数权限为private时,因为是私有的,所以不能被外部类所发现,因此也不能被覆盖 > //当父类的函数被静态static修饰时,也不能覆盖,只有子类函数也是静态的才能覆盖(静态只能覆盖静态或被静态覆盖) >
>
> 什么时候使用覆盖操作:
>
> 1. 当对一个类进行扩展时,子类需要保留父类的功能声明,但是要定义子类中该功能的特有内容时, 就使用覆盖操作完成
- 构造函数
子父类中的构造函数的特点:
在子类构造对象时, 发现访问子类构造函数时,父类也运行了
class fu{
fu(){
System.out.println("fu show run");
}
}
class zi extends fu{
zi(){
System.out.println("zi show run");
}
}
class ExtendsDemo{
public static void main(String[] args){
new zi();
}
}
原因是: 在子类的构造函数中第一行就有一个默认的隐式语句--super(),
与this()代表本类的空参数构造函数一样,super()代表继承的父类的空参数构造函数
子类的实例化过程: 子类中所有的构造函数默认都会访问父类中的空参数的构造函数
为什么子类实例化的时候要访问父类中的构造函数?
因为子类继承了父类, 获取到了父类中的内容(属性), 所以在使用父类内容之前,
要先看父类是如何对自己的内容进行初始化的;
所以子类在构造对象时, 必须访问父类的构造函数,
为了完成这个必须的动作, 就在子类的构造函数中加入了super()语句
如果父类中没有定义空参数构造函数,
那么子类的构造函数必须用super明确要调用父类中哪个构造函数,
同时子类构造函数中如果调用了本类构造函数, 那么super就没有了,
因为super和this都只能定义在第一行, 所以只能有一个,
但是可以保证的是: 子类中肯定会有其他的构造函数访问父类的构造函数
注意: super语句必须要定义在子类构造函数的第一行, 因为父类的初始化动作要先完成
一个对象的实例化过程
Person p=new Person();
- JVM 会先读取指定的路径下的Person.class文件, 并加载进内存, 会先加载Person的父类(如果有直接的父类的情况下)
- 在堆内存中开辟空间,分配地址
- 在对象空间中, 对对象的属性进行默认初始化
- 调用对应的构造函数进行初始化
- 在构造函数中, 第一行会先调用父类中的构造函数进行初始化
- 父类初始化完毕后, 再对子类的属性进行显式初始化
- 再进行子类构造函数的特定初始化
- 初始化完毕后, 将地址值赋值给引用变量
抽象
当一个事物中有不确定的成员时,那么这个事物也不具体,不确定,因此当事物中有不确定成员时,这个事物也是不确定的,这就是抽象类的由来,即当一个类中有抽象的成员变量或函数时,这个类也是抽象的
当一个类描述事物时,没有足够的信息来描述事物,这个类就是抽象类
特点
-
方法只有声明没有实现时, 该方法就是抽象方法, 需要被abstract修饰, 抽象方法必须定义在抽象类中, 该类也必须被abstract修饰
-
抽象类不可以被实例化, 因为调用抽象方法没有意义(抽象方法没有方法体[没有实现,没有大括号])
abstract class demo{
void show1();//没有方法体
void show2(){};//有方法体,只不过方法体没有内容,是一个空方法体
}
-
抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以被实例化, 否则这个子类还是抽象类
细节
-
抽象类中有构造函数吗?
==有, 用于给子类对象进行初始化==
-
抽象类可以不定义抽象方法吗?
==可以, 但是很少见, 目的就是不让该类创建对象.==
==通常这个类中的方法有方法体,但是却没有内容==
-
抽象关键字不可以和哪些关键字共存?
- private(私有): 因为在子父类中,当父类方法被private修饰时, 子类继承不能发现该方法, 而抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以被实例化
- static(静态): 当一个方法被static修饰时,不需要对象,其它类就能通过该方法所在的类名直接调用此静态方法, 而抽象方法没有方法体, 调用后也无意义
- final(最终): 抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以被实例化, 而被final修饰的函数无法被覆盖, 因此会产生冲突
-
抽象类和一般类的异同点
- 抽象类和一般类都是用来描述事物的,都在内部定义了成员,只是一般类有足够的信息来描述事物,而抽象类描述事物的信息有可能不足
- 一般类中不能定义抽象方法, 只能定义非抽象方法, 抽象类中可以定义抽象方法, 同时也可以定义非抽象方法
- 一般类可以被实例化, 抽象类不可以被实例化
-
抽象类一定是一个父类吗?
是的, 抽象类想要被使用,必须有子类覆盖其方法才可以对子类进行实例化
示例
//需求: 公司中程序员有姓名,工号,薪水,工作内容; 项目经理除了有姓名,工号,薪水,工作内容外,还有奖金, 对给出的需求进行数据建模
//分析: 想找出涉及的对象, 通过名词提炼法:
//程序员: 属性(姓名,工号,薪水) 行为(工作)
//项目经理: 属性(姓名,工号,薪水,奖金) 行为(工作)
//程序员和项目经理不存在直接继承关系,但是有着共性内容,因此向上抽取建立体系
/**描述雇员*/
abstract class Employee{
private String name;
private int id;
private double pay;
//初始化雇员
Employee(String name,int id,double pay){
this.name=name;
this.id=id;
this.pay=pay;
}
//工作内容是不具体的, 抽象
public abstract void work();
}
/**描述程序员*/
class Programmer extends Employee{
//初始化程序员
Programmer(String name,int id,double pay){
//父类中已经将功能定义完成,子类只需要将功能直接拿过来使用就可以了
//父类中已经存在对属性的赋值动作,子类直接调用父类的功能
super(name,id,pay);
}
//想要使用父类的功能必须覆盖父类的抽象方法
public void work(){
System.out.println("code...");
}
}
/**描述经理*/
class Manager extends Employee{
private int bonus;
Manager(String name,int id,double pay,int bonus){
super(name,id,pay);
this.bonus=bonus;
}
//想要使用父类的功能必须覆盖父类的抽象方法
public void work(){
System.out.println("manage...");
}
}
接口
interface
当一个抽象类中的方法都是抽象的时候,这时可以将该抽象类用另一种形式定义和表示, 就是接口 interface
==接口类是抽象类, 接口中的方法都是抽象的==
==接口不可以实例化, 只能有实现了接口的子类并覆盖了接口中的所有抽象方法后,该子类才可以实例化, 否则这个子类就是一个抽象类==
定义接口使用的关键字不是class, 是interface
对于接口当中常见的成员(这些成员都有固定的修饰符):
- 全局常量(public static final)
- 抽象方法(public abstract)
由此得出结论: 接口中的成员都是公共的权限
interface Demo{
abstract void show1();
abstract void show1();
}
接口实现
implements
==类与类之间是继承关系, 类与接口之间是实现关系==
class DemoImpl implements Demo{
void show1(){
System.out.println("...");
}
void show2(){}
}
在java中不直接支持多继承, 因为会产生调用的不确定性. 所以java将多继承机制进行改良, 变成了多实现
==一个类可以实现多个接口==, 解决了java中不直接支持多继承的问题
interface A{
public void show();
}
interface B{
public void show();
}
class Test implements A,B{
//接口都是抽象类,子类覆盖了接口中的所有抽象方法后才能实例化
public void show(){//此时Test中的show方法同时覆盖了A和B中的show方法,不存在不确定性
}
}
==一个类在继承另一个类的同时, 还可以实现多个接口==
接口的出现避免了单继承的局限性
class Car{
public void run();
}
interface A{
void show();
}
interface B{
void show();
}
//一个类在继承另一个类的同时, 还可以实现多个接口
class Test extends Car implements A,B{
//接口都是抽象类,子类覆盖了接口中的所有抽象方法后才能实例化
public void show(){//此时Test中的show方法同时覆盖了A和B中的show方法,不存在不确定性
}
}
==接口与接口之间可以存在继承关系, 而且接口可以多继承(原理在于方法体是否存在)==
当一个接口同时继承多个有相同成员的接口时, 因为接口不能被实例化, 想要使用功能就必须要有子类覆盖所有抽象方法, 因此当多个接口存在继承关系时, 实现子类接口的类会覆盖所有接口的相同成员, 不存在不确定性.
java不直接支持多继承的原因就是因为方法体导致的调用不确定性, 而接口中的方法都是抽象方法,没有方法体, 因此接口与接口之间可以多继承
interface A{
void show();
}
interface B{
void method();
}
interface C extends A,B{//接口与接口之间可以存在继承关系, 而且接口可以多继承
void function();
}
class D implements C{
//接口C继承了A和B,当D要实现接口C时,除了要覆盖C的抽象方法外,还要覆盖A和B的抽象方法
//需要覆盖3个方法
public void show();
public void method();
public void function();
}
接口的特点
- 接口是==对外暴露的规则==
- 接口是程序的==功能扩展==
- 接口的出现==降低耦合性==
- 接口可以用来多实现
- 类与接口之间是实现关系,而类可以继承一个类的同时实现多个接口
- 接口与接口之间可以有继承关系,且可以多继承
接口与抽象的异同点
-
共性: 都是不断抽取出来的抽象的概念
-
区别1: 抽象类需要被继承, 且只能单继承
接口需要被实现, 可以多实现
-
区别2: 抽象类的继承, 是 " is a "关系, 在定义该体系的基本共性内容
接口的实现, 是 " like a "关系, 在定义体系的额外功能
-
区别3: 抽象类中可以定义非抽象方法, 供子类直接使用
接口的方法都是抽象的, 接口中的成员独有固定修饰符(全局常量(public static final);
抽象方法(public abstract))
引出多态的概念
interface USB{//对外暴露的规则
public void open();
public void close();
}
class UPan implements USB{//实现规则
public void open(){
System.out.println("UPan...open");
}
public void close(){
System.out.println("UPan...close");
}
}
class BookPC{
public static void main(String[] args){
useUSB(new UPan());
}
//使用规则
public static void useUSB(USB u){//接口类型的引用, 用于接收(指向)接口的子类对象--多态
u.open();
u.close();
}
}
多态
某一类事物的多种存在形态(对象的多态性)
/**对象的多态性*/
class 动物{}
class 猫 extends 动物{}
class 狗 extends 动物{}
猫 x= new 猫();
动物 x= new 猫();//父类引用指向子类对象,对象的多态性
//猫这类事物既具备猫的形态,又具备动物的形态, 这就是对象的多态性
//简单来说: 就是一个对象对应着不同类型
多态在代码中的体现: ==父类或者接口的引用指向其子类的对象==
多态的好处: 提高了代码的扩展性, 前期定义的代码可以使用后期的内容
多态的弊端: 前期定义的内容不能使用(调用)后期子类的特有内容
多态的前提: 必须有关系: 继承,实现 要有覆盖
abstract class Animal{
abstract void eat();
}
class Dog extends Animal{
void eat(){
System.out.println("啃骨头..");
}
void lookHome(){
System.out.println("看家..");
}
}
class Cat extends Animal{
void eat(){
System.out.println("吃鱼..");
}
void catchMouse(){
System.out.println("抓老鼠..");
}
}
class DuoTaiDemo{
public static void main(String[] args){
Cat c=new Cat();
Dog d=new Dog();
method(c);
method(d);
}
public static void method(Animal a){//Animal a= new 子类();
//动物都具备吃的功能,直接在这里指挥动物吃, 此函数的参数传入哪种动物,哪种动物就执行吃的动作
a.eat();
}
}
转型
Animal a=new Cat();//自动类型提升, 猫对象提升为动物类型, 但是猫特有功能无法访问
//作用就是限制对子类特有功能的访问, 专业术语: 向上转型
//如果还想用具体动物猫的特有功能, 可以将该对象进行向下转型
Cat c=(Cat)a;//向下转型的目的是为了使用子类中的特有方法
/** 注意: 对于转型,自始至终都是子类对象在做着类型的变化(一会变成父类型,一会变成本类型) */
Animal a=new Animal();
Cat a=(Cat)a;//非法,此时a是父类对象,不能转型
==对于转型,自始至终都是子类对象在做着类型的变化==
类型判断
instanceof : 用于判断对象的具体类型, 只能用于引用数据类型的判断
abstract class Animal{
abstract void eat();
}
class Dog extends Animal{
void eat(){
System.out.println("啃骨头..");
}
void lookHome(){
System.out.println("看家..");
}
}
class Cat extends Animal{
void eat(){
System.out.println("吃鱼..");
}
void catchMouse(){
System.out.println("抓老鼠..");
}
}
class DuoTaiDemo{
public static void main(String[] args){
Cat c=new Cat();
Dog d=new Dog();
method(c);
method(d);
}
public static void method(Animal a){//Animal a= new 子类();
a.eat();
//instanceof: 通常在向下转型前用于代码健壮性的判断
if(a instanceof Cat){//如果函数接收的对象是Cat
Cat c=(Cat)a;//将此对象向下转型
c.catchMouse();//向下转型后可以使用对象的特有方法
}else if(a instanceof Dog){
Dog d=(Dog)a;
d.lookHome();
}
}
}
多态中的成员特点
1.成员变量
- 编译时: 参考引用型变量所属的类中是否有调用的成员变量, 有: 编译通过; 没有: 编译失败
- 运行时: 参考引用型变量所属的类中是否有调用的成员变量, 并运行该所属类中的成员变量
- 简单来说, 编译和运行都参考创建对象时等号的左边的所属类
- 一般来说,不会出现这种情况, 当父类有成员时,子类不需要在创建同样的成员,直接使用父类的成员即可
class Fu{
int num=3;
}
class Zi extends Fu{
int num=4;
}
class DuoTaiDemo{
public static void main(String[] args){
Fu f=new Zi();//参考创建对象时等号的左边的所属类,Fu中已经有num这个变量了
System.out.println(f.num);//此时输出的是Fu中的num值:3
}
}
2.成员函数(非静态)
- 编译时: 参考引用型变量所属的类中是否有调用的函数, 有: 编译通过; 没有: 编译失败
- 运行时: 参考的是对象所属的类中是否有调用的函数, ==动态绑定==到指定对象中运行
- 简单来说, 编译时看创建对象时等号的左边所属类是否有调用的函数, 运行时看创建对象时等号的右边所属类是否有调用的函数.(编译看左边,运行看右边)
class Fu{
void show(){
System.out.println("fu show");
}
}
class Zi extends Fu{
void show(){
System.out.println("zi show");
}
}
class DuoTaiDemo{
public static void main(String[] args){
Fu f=new Zi();//此时f指向的内存地址中存储的是Zi这个类所描述的对象
//show方法本身是非静态的,依赖于对象,要调用show必须动态绑定到指定对象中运行
//此时f指向的对象是Zi,因此,当运行f.show()时,运行的是Zi对象中的show方法,输出zi show
f.show();//输出: zi show
}
}
3.静态函数
- 编译时: 参考引用型变量所属的类中是否有调用的静态函数
- 运行时: 参考引用型变量所属的类中是否有调用的静态函数
- 简单来说, 编译和运行都参考创建对象时等号的左边的所属类
- 对于静态函数, 是不需要对象的, 直接用类名调用即可
class Fu{
static void show(){
System.out.println("fu show");
}
}
class Zi extends Fu{
static void show(){//重写父类静态方法必须和父类一模一样
System.out.println("zi show");
}
}
class DuoTaiDemo{
public static void main(String[] args){
//show方法是静态的,不依赖对象,依赖于类
//此时,什么类调用这个show方法,这个show绑定到什么类里面
Fu f=new Zi();
//f是Fu类型,此时show方法绑定的是Fu类型,因此会执行Fu中的show方法
f.show();//输出: fu show
//以此来看,静态函数其实不涉及到多态, 因为对象的多态性, 有对象才存在
//静态函数可以直接用类来调用,不需要用对象,因此当使用静态函数时,直接用类来调用就可以了,不需要再浪费内存去创建对象
//如果非要用对象来调用静态函数,则和成员变量一样,编译和运行时都参考引用型变量所属的类中是否有调用的静态函数
}
}
4.总结
成员变量和静态函数编译和运行都参考创建对象时等号的左边的所属类, 只有非静态函数不同, 编译看创建对象时等号的左边的所属类, 运行看创建对象时等号的右边的所属类, 因为非静态函数需要被对象调用,==动态绑定==到指定对象中运行
构造函数
构建创造对象时调用的函数
特点
- 函数名与类名相同
- 不用定义返回值类型, 因为没有具体的返回值
- 一个类可以有多个构造函数
作用
给对象进行初始化, 使对象在一创建时就具备某些内容
注意
- 创建对象都必须要通过构造函数初始化
- 多个构造函数是以重载的形式存在的
- 一个类如果没有定义过构造函数, 那么该类会有一个默认的构造函数; 如果定义了指定的构造函数, 那么类中的构造函数就没有了
- 构造函数也需要进入栈内存(压栈), 执行完毕后被释放(弹栈)
- 构造函数执行到最后也有return;只是一般不写,编译器也会默认加上
class Person{
private String name;
private int age;
public String getName(){
return name;
}
public void setName(String name){
this.name = name
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
//构造函数
Person(){
System.out.print("这是一个构造函数,在创建对象时会被立即调用");
}
}
//创建对象都必须要通过构造函数来初始化
class Car{
//即使一个类中, 没有定义任何属性与方法, 也会有默认构造函数
Car(){}
}
//如果定义了指定的构造函数, 那么类中的构造函数就没有了
class Car{
//编译器在编译时,当检查到类中没有指定构造函数,会生成一个默认构造函数
//当检查到类中指定了构造函数时, 编译器就会处理这个构造函数, 不会再生成默认构造函数
Car(){System.out.print("构造函数");}
}
//多个构造函数是以重载的形式存在的
class Car{
//重载: 函数名相同,参数个数与类型不同
Car(String name,int age){System.out.print(name+"..."+age);}
Car(int age,String name){System.out.print(name+"..."+age);}
}
构造函数与一般函数的区别
-
- >构造函数: 对象创建时, 就会调用与之对应的构造函数, 对对象进行初始化
>
>一般函数: 对象创建后, 需要函数功能时才调用
-
- > 构造函数: 对象创建时, 会被调用, 且只调用一次
>
> 一般函数: 对象创建后, 可以被多次调用
-
- > 构造函数: 不用定义返回值类型
>
> 一般函数: 必须定义返回值类型,
构造代码块
可以给所有对象进行初始化, 对象每被new创建一次就执行一次, 而构造函数是给对应的对象进行针对性的初始化
class StaticCar{
{
System.out.println("构造代码块");
}
StaticCar(String name){
System.out.println("构造函数: "+name);
}
StaticCar(){
System.out.println("构造函数");
}
public static void main(String[] args){
//每创建一次对象构造代码块就会执行一次
new StaticCar();
new StaticCar("哈哈");//构造函数只给对应的对象进行初始化
}
}
内部类
将一个类定义在另一个类中, 这个类就称为内部类(内置类,嵌套类)
访问特点:
- 内部类可以直接访问外部类中的成员, 包括私有成员
- 外部类要访问内部类中的成员必须建立内部类的对象
class Outer{
private int num = 3;
class Inner{//内部类 可以被成员修饰符所修饰
void show(){
System.out.println("show run..."+num);//内部类可以直接访问外部类中的成员
}
}
public void method(){
Inner in=new Inner();//外部类要访问内部类中的成员必须建立内部类的对象
in.show();
}
}
class InnerClassDemo{
public static void main(String[] args){
Outer out=new Outer();
out.method();
//直接访问外部类中的内部类中成员
Outer.Inner in1= new Outer().new Inner();
in1.show();
//如果内部类是静态的, 相当于一个外部类
Outer.Inner in2=new Outer.Inner();
in2.show();
//如果内部类是静态的, 内部类的成员也是静态的
Outer.Inner.show();
}
}
一般用于类的设计, 分析事物时, 发现该事物描述中还有事物, 而且这个事物还在访问被描述事物的内容, 这时就将这个事物定义成内部类来描述
内部类可以被成员修饰符(public,private,static....)所修饰,这是外部类所不具备的
如果内部类中定义了静态成员, 那么该内部类必须被静态修饰
class Outer{
int num=3;
class Inner{
int num =4;//内部类全局变量
void show(){
int num=5;//内部类局部变量
System.out.println("show run..."+num);//此时num是局部变量
//使用内部类的全局变量
System.out.println("show run..."+Inner.this.num);
//内部类使用外部类的成员
System.out.println("show run..."+Outer.this.num);
//为什么内部类能直接访问外部类中的成员?
//因为内部类持有了外部类的引用. 外部类名.this
}
}
void method(){
new Inner().show();
}
}
class InnerClassDemo{
public static void main(String[] args){
new Outer().method();
}
}
局部内部类
内部类可以存放在局部位置上
内部类要想访问外部类的局部变量, 外部类的局部变量要被声明为最终类型final
内部类在局部位置上只能访问局部中被final修饰的局部变量
class Outer{
int num=3;
void method(){
int x=9;
class Inner{//内部类可以存放在局部位置上
void show(){
System.out.println("show run..."+x);//想要访问x,x必须被final修饰
}
}
}
匿名内部类
其实就是内部类的简写格式
匿名内部类的前提条件:
内部类必须继承或者实现一个外部类或者接口, 即必须与外部类有关系
匿名内部类就是一个匿名子类对象( new 父类名称() )
abstract class Demo{
abstract void show();
}
class Outer{
int num=5;
/**
class Inner extends Demo{
void show(){
System.out.println("show run..."+num)
}
}
*/
public void method(){
//new Inner().show();
new Demo(){//匿名内部类就是一个匿名子类对象
void show(){
System.out.println("show run..."+num)
}
}.show();
}
}
class InnerClassDemo{
public static void main(String[] args){
new Outer().method();
}
}
interface Inter{
void show1();
void show2();
}
class Outer{
/**
class Inner implements Inter{
public void show1(){}
public void show2(){}
}
*/
public void method(){
/**
//正常的实现方式
Inner in=new Inner();
in.show1();
in.show2();
*/
//匿名内部类实现
new Inter(){
public void show1(){}
public void show2(){}
}.show1()
new Inter(){
public void show1(){}
public void show2(){}
}.show2()
//匿名内部类实现2 匿名内部类与多态的结合
Inter in=new Inter(){//多态 此时匿名内部类向上转型为Inter
public void show1(){}
public void show2(){}
}
in.show1();
in.show2();
}
}
//通常的使用场景之一:
//当函数参数是接口类型时, 且接口中的方法不超过三个, 可以使用匿名内部类作为实际参数进行传递
class InnerClassDemo{
public static void main(String[] args){
show(new Inter(){
public void show1(){}
public void show2(){}
});
}
public static void show(Inter in){
in.show1();
in.show2();
}
}
class Outer{
/**
class inner extends Object{
public void show(){}
}
*/
void method(){
new Object(){
public void show(){
System.out.println("show run...");
}
}.show();
//会出现异常的情况 匿名内部类与多态的结合
Object obj=new Object(){//多态 此时匿名内部类向上转型为Object
public void show(){
System.out.println("show run...");
}
}
obj.show();//向上转型后, Object中没有show方法, 且不能使用子类中的特有方法
}
}
class InnerClassDemo{
public static void main(String[] args){
new Outer().method();
}
}
第八章 异常
异常的概念
在运行时期发生的不正常情况
在java中, 用类的形式对不正常情况进行了描述和封装
描述了不正常的情况的类, 就称为异常类
以前正常流程代码和问题处理代码相结合, 现在将正常流程代码和问题处理代码分离, 提高阅读性
其实异常就是java通过面向对象的思想将问题封装成了对象, 用异常类对其进行描述
不同的问题用不同的类进行具体描述. (比如: 空指针, 下标越界等等)
异常体系
问题很多, 意味着描述的类也很多, 将其共性进行向上抽取, 就形成了异常体系
最终问题(不正常的情况) 就分成了两大类
Throwable(可抛出): 无论是Error还是Exception, 都是问题, 问题发生就应该可以抛出, 让调用者知道并处理(Throwable
类是 Java 语言中所有错误或异常的超类,只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw
语句抛出)
- 一般不可处理的. Error(错误)
特点: 是由JVM抛出的严重性的问题
这种问题发生一般不针对性处理. 需要直接修改程序
- 可以处理的. Exception(异常)
// 该体系的特点就在于Throwable及其所有的子类都具有可抛性
可抛性到底指什么? 怎么体现可抛性?
通过两个子类关键字来体现: throw throws , 凡是可以被这两个关键字所操作的类和对象都具备可抛性
//命名特点: 子类的后缀名都是用其父类名作为后缀, 阅读性很强
异常的分类
- 编译时被检测异常: 只要是
Exception
和其子类都是, 除了特殊子类RuntimeException
体系
这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式,这样的问题都可以针对性的处理
- 编译时不检测异常(运行时异常): 就是
Exception中
的RuntimeException
和其子类
这种问题的发生, 无法让功能继续,运算无法进行,更多是因为调用的原因导致的,或者引发了内部状态的改变而导致的,这种问题一般不处理,直接编译通过,在运行时,让调用者调用时的程序强制停止,让调用者对代码进行修改
==可能在执行方法期间抛出但未被捕获的 RuntimeException
的任何子类都无需在 throws
子句中进行声明==
声明式处理异常:
public int method(int arr,int index) throws XXXXXException{}
捕捉式处理异常
public int method(int arr,int index){
try{
//需要被检测异常的代码
}catch(异常类 变量){//该变量用于接收发生的异常对象
//处理异常的代码
}finally{
//一定会被执行的代码(无论是否出现异常,catch只要没有执行exit()退出jvm的操作,就一定会执行)
//假设catch中return了,finally代码块后面的内容不会被执行,但是finally中的代码一定会执行
}
}
//多catch情况
try{
//需要被检测异常的代码
}catch(异常类1 变量){//该变量用于接收发生的异常对象
//处理异常的代码
}catch(异常类2 变量){
//处理异常的代码
}catch(Exception 变量){//特别: 多catch情况,异常的父类的catch放在最下面
//处理异常的代码
}
自定义异常
使用自定义异常时,要么继承Exception, 要么继承RuntimeException
throws与throw的区别
- throws使用在函数上, throw使用在函数内
- throws抛出的是异常类, 可以抛出多个, 用逗号隔开, throw抛出的是异常对象
异常处理的原则
- 函数内容如果抛出需要检测的异常, 那么函数上必须要声明, 否则必须要在函数内用try catch捕捉,否则编译失败
- 如果调用到了声明异常的函数, 要么try catch, 要么throws, 否则编译失败
- 什么时候catch, 什么时候throws呢?
- 功能内容可以解决, 用catch
- 解决不了, 用throws告诉调用者, 由调用者解决
- 一个功能如果抛出了多个异常, 那么调用时, 必须有多个对应的catch进行针对性的处理
内部有几个需要检测的异常, 就抛几个异常, 抛出几个, 就catch几个
/**
* 自定义异常
*/
class LanPingException extends Exception{
LanPingException(String msg){
super(msg);
}
}
class KaSiException extends Exception{
KaSiException(String msg){
super(msg);
}
}
class NoPlanException extends Exception{
NoPlanException(String msg){
super(msg);
}
}
class Computer{
private int status=1;//改变status的值触发异常
public void run() throws LanPingException,KaSiException{
if(status == 1){
throw new LanPingException("电脑蓝屏了!");
}
if(status == 2){
throw new KaSiException("电脑卡死了!");
}
System.out.println("电脑运行!");
}
public void restart(){
status=0;
System.out.println("电脑重启!");
}
}
class Teacher{
private String name;
private Computer comp;
Teacher(String name){
this.name=name;
comp=new Computer();
}
public void prelect() throws NoPlanException{
try{
comp.run();
System.out.println(name+"授课!");
}catch(LanPingException e){//捕捉第一个异常
System.out.println(e.toString());//打印异常
comp.restart();//重启
prelect();//继续授课
}catch(KaSiException e){//捕捉第二个异常
System.out.println(e.toString());//打印异常
lianxi();
//throw e;//抛出异常
throw new NoPlanException("课时进度无法完成! 原因:"+e.getMessage());
// 此代码块部分, 涉及到异常转换的情况,catch捕捉到的异常是KaSiException,但是暴露出去的是NoPlanException
//作用是将"我"熟悉的问题进行解决,并将"对方"熟悉的问题进行告知
//这种处理方式也叫做异常的封装,不该暴露的问题内部处理,对外暴露"对方"知道的问题
}
}
public void lianxi(){
System.out.println("大家做练习!");
}
}
class ExceptionTest{
public static void main(String[] args){
Teacher t=new Teacher("sunwul");
try{
t.prelect();
}catch(NoPlanException e){
System.out.println(e.toString()+".....");
System.out.println("换人!");
}
}
}
try catch finally代码块组合特点
- try catch finally --常见组合, 经典案例, 连接数据库(连接数据库,查询等操作(Exception),关闭数据库)
- try catch(多个) --当没有必要资源需要释放时, 可以不用定义finally
- try finally --异常无法直接catch处理, 但是资源需要关闭
异常中的注意事项
- 如果子类在覆盖父类方法时,父类方法抛出了异常,那么子类的方法只能抛出父类方法的异常或者该异常的子类
- 如果父类方法抛出多个异常,那么子类覆盖父类方法时只能抛出父类方法异常的子集
简单来说,就是子类覆盖父类方法只能抛出父类方法的异常或者父类方法异常的子类,或者父类方法异常的子集
即子类方法不能抛出比被覆盖的父类方法多的异常,也不能抛出父类方法不存在的异常
==注意: 如果父类的方法没有抛出异常, 那么子类覆盖父类方法时绝对不能抛出异常==
如果子类方法确实声明了抛出异常(throws Exception),那么此时对于异常的处理, 只能try, 不能抛出
未完待续,持续更新....