一、分类
类大致可以分为普通类、final类、抽象类和接口类,另外还有一种特殊的类叫内部类。这里我们先不讨论内部类。
- 类都不可以用static修饰(内部类可以)。
- 类可以用访问修饰符public修饰,也可以啥都不写(内部类可以用所有访问修饰符修饰)。
二、类与文件
一个类文件中可以同级定义多个类(包括普通类、final类、抽象类和接口类)。
- 这些类可以用public修饰,也可以不用。
- public只能修饰其中一个类,且该类必须与类文件同名;如果没有类用public修饰,不必有一个类与类文件同名。
- 不用public修饰的类只能被同文件或者同一个包下的类访问。
- 同级的类不能同名,即便是不同类别的类;且内部类不能与自己的外部类同名,但可以与外部类的同级类同名。
下面的讨论都是一个文件一个类(除了内部类)。
三、类的关系
父类普通类 | 父类final类 | 父类abstract类 | 父类接口类 | 被实现的接口类 | |
---|---|---|---|---|---|
子类普通类 | √ | × | √ | × | √ |
子类final类 | √ | × | √ | × | √ |
子类abstract类 | √ | × | √ | × | √ |
子类接口类 | × | × | × | √ | × |
四、类的成员
4.1 标准的普通类
1. private 修饰的成员变量
2. 所有成员变量的Getter/Setter方法(对于 boolean 型成员变量,Getter方法用isXxx的形式,而setXxx规则不变。)
3. 一个无参的 public 构造方法
4. 一个全参的 public 构造方法
4.2 各种类的成员
说明:
- 接口类interface前其实有一个abstract,只不过可以省略。
- 不存在final抽象类和final接口类,也不存在final抽象方法,因为final与abstract不能同时存在。
- 不存在static抽象方法,因为static与abstract不能同时存在。
- private与abstract不能同时存在。
- static、final、abstract都不能与default同时存在。
- 用访问修饰符、static、final、abstract、default修饰时,位置可以任意调换 | | 普通类 | final类 | 抽象类 | 接口类 | | —- | —- | —- | —- | —- | | 普通变量 | √ | √ | √ | × | | static变量 | √ | √ | √ | × | | final常量 | √ | √ | √ | × | | static final常量 | √ | √ | √ | √( 常量 用public static final,三个都可以省略) | | 普通方法 | √ | √ | √ | √(有方法体,只能为private,此时为 普通私有方法 ) | | static方法 | √ | √ | √ | √(有方法体,包括静态普通方法和静态私有方法: 静态普通方法 用public static,只有public可省略; 静态私有方法 用private static) | | final方法 | √ | √ | √ | × | | static final方法 | √ | √ | √ | × | | static代码块 | √ | √ | √ | × | | 构造方法 | √ | √ | √ | × | | 抽象方法 | × | × | √(不能为private) | √( 抽象方法 用public abstract,两个都可省略,无方法体) | | default方法 | × | × | × | √( 默认方法 用public default,只可省略public,有方法体) | | 任何类别的内部类 | √ | √ | √ | √ |
注意:
- 任何类中都可以定义main方法(包括内部类),同一个类中不能定义多个main方法,但是如果外部类定义了main方法,内部类仍然可以定义main方法,即把main方法当成普通的static方法来看就可以(main方法可被其他类调用)。
- 内部类可以无限嵌套内部类,内部类不能和外部类同名。
- 关于方法:方法中可以包含普通变量、final变量和局部内部类,不能包含static变量、static final变量、static代码块和任何方法的定义。
五、构造方法
访问修饰符 类名称(参数列表){
方法体
}
构造方法的访问修饰符可以是任何一种,以下是访问权限:
public | protected | (default) | private | |
---|---|---|---|---|
同一个类 | √ | √ | √ | √ |
同一个包 | √ | √ | √ | |
不同包子类 | √ | √(只是在子类构造方法使用super()可以调用,其他任何时候都不可以) | ||
不同包非子类 | √ |
建议通常状况下构造方法直接用public即可。
六、static与final
6.1 static变量与方法
使用static修饰的变量与方法属于整个类,它们可以用对象名来调用,也可以直接通过类名来调用,推荐使用类名来调用。
① 静态方法只能调用静态变量和静态方法,而非静态方法可以调用静态也可以调用非静态。
② 静态方法中不能用this关键字。
6.2 static代码块
static代码块不可以被任何其他修饰符修饰!
public class 类名称{
static{
...
}
}
- 当第一次使用本类时,静态代码块执行唯一的一次。
- 静态代码块比非静态代码块优先执行,所以它先于构造方法执行。
一般用于给静态变量赋值。
可以把static代码块当做一个方法,只不过这个方法没有名称和返回值,且当new这个类的对象时,他就会被调用。
6.3 fianl
修饰 | 作用 |
---|---|
修饰类 | 该类不能被继承 |
修饰方法 | 该方法不能被重写 |
修饰局部变量 | 该局部变量不可更改 (基本类型:数据不可变;引用类型:地址值不可变,但其内容可变) |
修饰成员变量 | 该成员变量不可更改(基本类型:数据不可变;引用类型:地址值不可变,但其内容可变) |
- 对于final局部变量,可以声明的同时赋值,也可以先声明后赋值
对于final成员变量,由于用final修饰了,所以不再有默认值,所以必须要赋值,赋值方式如下选其一(不能选两个):
- 要么声明时赋值;要么通过构造方法赋值(所有的构造方法都要对final成员变量赋值,否则报错)。
6.4 static final常量
一旦赋值,不可修改!且定义时必须赋值!
可以用对象名来调用,也可以直接通过类名来调用,推荐使用类名来调用。
6.5 static final方法
只不过是不能被重写的static方法,具体看static方法。
七、抽象类
① 无法直接创建抽象类对象,必须用一个子类来继承抽象父类。
② 子类必须重写抽象父类中的所有抽象方法(要去掉abstract关键字,并写上方法体)。
③ 如果子类也是抽象类,就可以不重写抽象方法,或者重写部分抽象方法。
八、接口类
8.1 说明
接口类的成员:
- jdk7 接口中包含 1.常量 2.抽象方法
- jdk8 接口中增加了 3.默认方法 4.静态方法
- jdk9 接口中增加了 5.私有方法
① 无法直接创建接口类对象,必须用一个“实现类”来“实现”该接口。
//一般接口的实现类命名为接口名称末尾添加上Impl
public class 实现类名称 implements 接口名称{
//...
}
② 实现类必须重写接口的所有抽象方法(除非这个实现类是抽象类)。
③ 接口和其他类一样,都可以作为成员变量类型、方法的参数类型和返回值类型,但实参必须是其实现类。
8.2 默认方法
用于解决接口升级问题:原接口需要增加方法,如果用抽象方法,需要修改实现类,这样工作量太大,所以用默认方法。
- 用实现类对象调用。
- 可以被重写。
8.3 静态方法
- 用接口名来调用,不能被实现类的对象调用。
- 不能被重写。
8.4 私有方法
- 只能被接口自己调用,不能被实现类的对象调用
- 由于是私有方法,所以不能在其他类中被调用
- 不能被重写。
① 普通私有方法,解决多个默认方法之间重复代码问题
② 静态私有方法,解决多个静态方法之间重复代码问题
8.5 常量
//这三个关键字可以随意省略
//常量名称完全用大写字母,多个单词用下划线隔开
public static final 数据类型 常量名称= 数据值;
- 用接口名来调用,不能被实现类的对象调用。
九、多态
9.1 向上转型
//父类引用指向子类对象
父类名称 对象名 = new 子类名称();
接口名称 对象名 = new 实现类名称();
向上转型一定是安全的。但是有个弊端,就是没办法访问子类的专有的方法。
9.2 向下转型
其实是一个还原的动作,还原成本来的子类对象(如果还原成其他子类,编译没有错误,运行会报错)。
//类似于强制类型转换
子类名称 对象名 = (子类名称)父类对象;
实现类名称 对象名 = (实现类名称)接口类对象;
Animal animal = new Cat();//本来是猫,向上转型成为动物
Cat cat = (Cat) animal;//还原成为本来的猫
9.3 instanceof
对象 instanceof 类名称;//返回一个布尔值,返回前面的对象是不是后面类型的实例
子类对象 instanceof 子类/父类;//返回true
向上转型后的对象 instanceof 子类/父类;//返回true
向下转型后的对象 instanceof 子类/父类;//返回true
//实现关系中也有以上规律
十、子类与实现类
10.1 访问修饰符
public > protected > (default) > private
public | protected | (default) | private | |
---|---|---|---|---|
同一个类 | √ | √ | √ | √ |
同一个包 | √ | √ | √ | |
不同包子类 | √ | √ | ||
不同包非子类 | √ |
10.2 重写
10.2.1 规则
一个方法只要方法名和参数相同(不管返回值与修饰符是否相同),即认定为 相同方法 ,不可出现在同一个类当中,但是子类可以有父类的相同方法,即为 重写 。
① 子类方法的返回值必须【小于等于】父类方法的返回值范围(小于即为其子类)。
java.lang.Object
类是所有类的公共最高父类
② 子类方法的权限必须【大于等于】父类方法的权限修饰符。
- public > protected > (default) > private
③ 子类方法可以被final修饰,但不可以被static和static final修饰。
10.2.2 规律
构造方法不能被重写。
普通private方法不能被重写,但是可以在子类中有相同方法。
static方法不可以被重写,但是可以在子类中有相同方法。
final方法和static final都不可以被重写,但是只有private final和private static final方法可以有相同方法。
普通非private方法可以被重写,且如果子类存在此类型的相同方法时,必须是重写方法(即要遵守重写规则),否则报错,即子类不存在 普通非private类型 的 非重写相同方法 。
① 父类普通类
子类普通类 | 子类final类 | 子类抽象类 | |
---|---|---|---|
普通方法 | 非private方法可以重写,重写成普通方法或final方法;private方法不能(可以有非重写相同方法) | 非private方法可以重写,重写成普通方法或final方法;private方法不能(可以有非重写相同方法) | 非private方法可以重写,重写成普通方法、final方法或抽象方法;private方法不能(可以有非重写相同方法) |
static方法 | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) |
final方法 | 不能重写(只有private方法可以有非重写相同方法) | 不能重写(只有private方法可以有非重写相同方法) | 不能重写(只有private方法可以有非重写相同方法) |
static final方法 | 不能重写(只有private方法可以有非重写相同方法) | 不能重写(只有private方法可以有非重写相同方法) | 不能重写(只有private方法可以有非重写相同方法) |
② 父类抽象类
子类普通类 | 子类final类 | 子类抽象类 | |
---|---|---|---|
普通方法 | 非private方法可以重写,重写成普通方法或final方法;private方法不能(可以有非重写相同方法) | 非private方法可以重写,重写成普通方法或final方法;private方法不能(可以有非重写相同方法) | 非private方法可以重写,重写成普通方法、final方法或抽象方法;private方法不能(可以有非重写相同方法) |
static方法 | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) |
final方法 | 不能重写(只有private方法可以有非重写相同方法) | 不能重写(只有private方法可以有非重写相同方法) | 不能重写(只有private方法可以有非重写相同方法) |
static final方法 | 不能重写(只有private方法可以有非重写相同方法) | 不能重写(只有private方法可以有非重写相同方法) | 不能重写(只有private方法可以有非重写相同方法) |
抽象方法 | 可以重写,重写成普通方法或final方法 | 可以重写,重写成普通方法或final方法 | 可以重写,重写成普通方法、final方法或抽象方法本身 |
③ 父类接口类
子类接口类 | |
---|---|
private普通方法 | 不能重写(可以有非重写相同方法) |
普通static方法 | 不能重写(可以有非重写相同方法) |
private static方法 | 不能重写(可以有非重写相同方法) |
抽象方法 | 可以重写,重写成default方法或抽象方法本身 |
default方法 | 可以重写,重写成default方法或抽象方法 |
④ 被实现接口类
实现类普通类 | 实现类final类 | 实现类抽象类 | |
---|---|---|---|
private普通方法 | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) |
普通static方法 | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) |
private static方法 | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) | 不能重写(可以有非重写相同方法) |
抽象方法 | 可以重写,重写成普通方法或final方法 | 可以重写,重写成普通方法或final方法 | 可以重写,重写成普通方法、final方法或抽象方法本身 |
default方法 | 可以重写,重写成普通方法或final方法 | 可以重写,重写成普通方法或final方法 | 可以重写,重写成普通方法、final方法或抽象方法 |
⑤ 总结
1. static 、 final 、 private 修饰的方法都不能被重写。
2. 构造方法不能被重写。
3. 子类或实现类中的重写方法不可以用 static 和 static final 修饰。
4. 能被重写的方法只有 普通非 private 方法、 抽象方法 和 default 方法。
5. 将以上关系分成两类,接口继承和非接口继承。
6. 在非接口继承关系中,普通非 private 方法 、 抽象方法 和 default 方法可以重写成普通方法或 final 方法;如果子类或实现类为抽象类,他们还可以重写成抽象方法。
7. 在接口继承关系中,抽象方法重写成 default 方法 或 抽象方法本身, default 方法重写成 default 方法 或 抽象方法。
10.3 子类与实现类的成员
- 父类/抽象类的所有成员
- 子类/实现类的非同名内容
- 子类/实现类的同名变量、子类/实现类的相同方法(包括重写方法与非重写方法)
10.4 重名
① 通过子类对象访问成员变量
等号左边是哪个类(即定义的是哪个类的对象),就优先用哪个类中的那个同名变量,没有则向上找。
② 访问相同方法
创建的是谁(即new的是谁)就优先用谁,如果没有则向上找。
③ 通过成员方法访问成员变量
先根据 ② 判断调用的是子类还是父类的方法,该方法属于哪个类,就优先用哪个类中的同名变量,没有则向上找 。
- 编译看左边,运行看左边。(访问成员变量时)
- 编译看左边,运行看右边。(访问成员方法时)
//Animal类
public class Animal {
public void eat(){
System.out.println("吃东西");
}
}
//Dog类
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃屎");
}
public void watchHouse(){
System.out.println("狗会看家");
}
}
//主类
public class MainClass {
public static void main(String[] args) {
Animal animal = new Dog();
animal.watchHouse();//会编译报错(编译看左边)
animal.eat();
}
}
10.5 super和this总结
10.5.1 super
① 在子类的成员方法中,访问父类的成员变量。
② 在子类的成员方法中,访问父类的成员方法。
③ 在子类的构造方法中,访问父类的构造方法。
1. 子类构造方法当中有一个默认隐含的 super()(父类的无参构造方法)调用,所以一定是先调用父类构造方法,再调用子类构造方法。
2. 子类构造方法可以自己传参调用父类的重载构造方法。
3. 只有子类构造方法可以调用父类构造方法,子类的其他方法不可以调用。
4. super()必须写在子类构造方法的第一个语句,因此不能写多个父类构造语句。
5. 如果父类没有无参构造方法(当定义了有参构造方法时,默认的无参构造方法会消失),且子类构造方法没有调用父类的有参构造方法,则会报错。
10.5.2 this
① 在本类的成员方法中,访问本类的成员变量。
② 在本类的成员方法中,访问本类的另一个成员方法(可以强调该方法来自本类)。
③ 在本类的构造方法中,访问本类的另一个构造方法。
1. this() 必须是第一个语句,只能有一个。
2. super() 和 this() 不能同时显式使用,但是如果使用了 this() ,仍会默认调用无参的 super() 。
3. 不可以在多个构造方法中循环调用,程序会报错。
//不可以在多个构造方法中循环调用,程序会报错。
public Zi() {
this(123);
}
public Zi(int n) {
this(1, 2);
}
public Zi(int n, int m) {
this();
}
十一、多实现与多继承
11.1 多实现
① 如果实现类实现的多个接口中,存在返回值相同的相同抽象方法,那只需要重写一次即可。
② 如果实现类实现的多个接口中,存在返回值相同的相同默认方法,为了解决冲突,实现类一定要对默认方法进行重写(去掉default关键字)。
③ 如果实现类实现的多个接口中,存在返回值不同的相同抽象方法或相同默认方法,实现类无法全部实现这些抽象方法,也无法解决默认方法的冲突,会报错;所以定义接口时要注意。
11.2 继承与实现
一个子类继承了一个父类,并实现了一个接口类。
① 继承优先于实现,父类的成员会先进入子类。
② 如果该父类中的方法与接口类中的抽象方法为相同方法,那么该子类不需要重写该抽象方法,因为系统直接把该子类继承父类的这个相同方法视为对该抽象方法的重写。
③ 如果该父类中的方法与接口类中的默认方法为相同返回值的相同方法,子类不用重写该方法,调用该方法时会使用父类的这个方法;而若不采用其他手段,无法访问接口类的这个方法。
④ 如果该父类中的方法与接口类中的默认方法为不同返回值的相同方法,由于父类的方法会先进入子类,系统把他当成重写的方法,所以如果不符合重写规则时,会报错。
11.3 接口的多继承
① 接口之间可以进行多继承
② 子接口的多个父接口的抽象方法为返回值相同的相同方法,子接口只保留一个。
③ 子接口的多个父接口的抽象方法为返回值不同的相同方法,无法重写,会报错。
④ 子接口的多个父接口的默认方法为返回值相同的相同方法,则要在子接口中进行默认方法的重写(在子接口中仍然是默认方法,即要带着default关键字)。
⑤ 子接口的多个父接口的默认方法为返回值不同的相同方法,无法重写,会报错。
十二、匿名对象与内部类
12.1 匿名对象
匿名对象就是只有右边的对象,没有左边的名字和赋值运算符:
new 类名().方法名();//调用
new 类名().变量名;//调用
12.2 修饰符
① 成员内部类:public / protected / (defaul) / private / static / final /static final / abstract / Interface
② 局部内部类:final / abstract (不允许为interface)
12.3 成员内部类
修饰符 class 外部类名称{
修饰符 class 内部类名称{
//...
}
//...
}
注意: 内用外,随意访问;外用内,需要内部类对象。
调用:
① 间接方式:在外部类的方法中,创建内部类对象,然后main只是调用外部类的方法。
② 直接方式:
外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
12.4 局部内部类
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
修饰符 class 外部类名称{
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称{
//...
}
}
}
12.5 局部内部类的final
如果希望在局部内部类访问所在方法的局部变量,那么这个局部变量必须定义final,但也可以省略final(但不能修改值)。以下为原因:
① new出来的对象在堆内存中。
② 局部变量是跟着方法走的,在栈内存当中。
③ 方法运行结束之后,立刻出栈,局部变量立刻消失。
④ 但是new出来的对象会在堆内存当中持续存在,直到垃圾回收消失。(此时局部内部类仍然想要使用局部变量,但是局部变量已经消失,所以解决办法是:拷贝之前的局部变量,同时保证之前的值不可更改)
public class MyOuter {
public void methodOuter() {
int num = 10; // 所在方法的局部变量
num = 20;//报错,值不可更改
class MyInner {
public void methodInner() {
System.out.println(num);
}
}
}
}
12.6 匿名内部类
匿名内部类是局部内部类的一种。
如果接口的实现类(或者父类的子类)只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义,而改为使用匿名内部类。
接口名/父类名 对象名 = new 接口名/父类名(){
//覆盖重写所有 抽象方法
};//末尾有个分号
12.7 重名问题
内部类成员变量、内部类局部变量和外部类成员变量重名:
//在内部类的方法中
变量名//内部类局部变量
this.变量名//内部类成员变量
外部类名.this.变量名//外部类成员变量
//局部内部类
public class Outer {
int num = 10;
public void methodOuter(){
int num = 20;
class Inner {
int num = 30;
public void methodInner() {
int num = 40;
System.out.println(Outer.this.num);//10
System.out.println(this.num);//30
System.out.println(num);//40
}
}
new Inner().methodInner();
}
}
这里无法访问值为20的num,有一解释为,由于局部内部类会复制一份外部类成员方法的局部变量。而这里内部类方法中的局部变量与其同名,所以直接把外部类的局部变量覆盖掉了。(该说法有待考证)