继承

继承

  • 继承允许一个类(子类)基于另一个类(父类)来构建,子类可以继承父类的属性和方法。
  • 继承促进了代码的重用,因为子类可以直接使用父类已经定义的方法和属性,而不需要重新实现。
  • 通过继承,可以建立类之间的层次关系,使得代码结构更清晰,易于扩展和维护。

定义:

  • 就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。
  • 子类可以直接访问父类中的非私有的属性和行为。
  • Java只支持单继承,不支持多继承。
img
1
2
3
4
5
6
7
8
9
定义父类的格式:(一个普通的类定义)
public class 父类名称 {
// ...

}
定义子类的格式:
public class 子类名称 extends 父类名称 {
// ...
}

继承的访问规则

在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:

直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上找。

间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Fu {
int numFu = 10;
int num = 100;
public void methodFu() { // 使用的是本类当中的,不会向下找子类的
System.out.println(num);//100
}
}

public class Zi extends Fu {
int numZi = 20;
int num = 200;
public void methodZi() { // 因为本类当中有num,所以这里用的是本类的num
System.out.println(num);//200
}
}


public static void main(String[] args) {
Fu fu = new Fu(); // 创建父类对象
System.out.println(fu.numFu); // 10, 只能使用父类的东西,没有任何子类内容
Zi zi = new Zi();
System.out.println(zi.numFu); // 10
System.out.println(zi.numZi); // 20
// 等号左边是谁,就优先用谁
System.out.println(zi.num); // 优先子类,200
// 这个方法是子类的,优先用子类的,没有再向上找
zi.methodZi(); // 200
// 这个方法是父类的,用父类的
zi.methodFu(); // 100
}

区分子类方法中变量重名

  • 局部变量:直接写成员变量名
  • 本类的成员变量:this.成员变量名
  • 父类的成员变量:super.成员变量名
1
2
3
4
5
6
7
8
9
10
11
12
public class Fu {
int num = 10;
}
public class Zi extends Fu {
int num = 20;
public void method() {
int num = 30;
System.out.println(num); // 30,局部变量
System.out.println(this.num); // 20,本类的成员变量
System.out.println(super.num); // 10,父类的成员变量
}
}

image-20240224001601846

成员方法重名——重写(Override)

如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。

方法覆盖重写的注意事项

  • 必须保证父子类之间方法的名称相同,参数列表也相同。

@Override:写在方法前面,用来检测是不是有效的正确覆盖重写。

这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。

  • 子类方法的返回值必须【小于等于】父类方法的返回值范围。

小扩展提示:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。

  • 子类方法的权限必须【大于等于】父类方法的权限修饰符。

小扩展提示:public > protected > (default) > private

备注:(default)不是关键字default,而是什么都不写,留空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Fu {
public void method() {
System.out.println("父类重名方法执行!");
}
}

public class Zi extends Fu {
public void method() {
System.out.println("子类重名方法执行!");
}

}

public static void main(String[] args) {
Zi zi = new Zi();
// 创建的是new了子类对象,所以优先用子类方法
zi.method();
}

继承中的构造方法

继承关系中,父子类构造方法的访问特点:

  • 子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用的父类构造,后执行的子类构造。
  • 子类构造可以通过super关键字来调用父类重载构造。
  • super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。

总结:子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Fu {
public Fu() {
System.out.println("父类无参构造");
}
public Fu(int num) {
System.out.println("父类有参构造!");
}
}

public class Zi extends Fu {
public Zi() {
super(); // 在调用父类无参构造方法
//super(20); // 在调用父类重载的构造方法
System.out.println("子类构造方法!");
}

// 错误写法!只有子类构造方法,才能调用父类构造方法。
public void method() {
//super();
}
}

super关键字的用法有三种:

  • 在子类的成员方法中,访问父类的成员变量
  • 在子类的成员方法中,访问父类的成员方法
  • 在子类的构造方法中,访问父类的构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Fu {
int num = 10;
public void method() {
System.out.println("父类方法");
}
}

public class Zi extends Fu {
int num = 20;
//在子类的构造方法中,访问父类的构造方法
public Zi() {
super();
}


//在子类的成员方法中,访问父类的成员变量
public void methodZi() {
System.out.println(super.num); // 父类中的num
}

//在子类的成员方法中,访问父类的成员方法
public void method() {
super.method(); // 访问父类中的method
System.out.println("子类方法");
}
}

this关键字用来访问本类内容。用法也有三种:

  • 在本类的成员方法中,访问本类的成员变量。
  • 在本类的成员方法中,访问本类的另一个成员方法。
  • 在本类的构造方法中,访问本类的另一个构造方法。

在第三种用法当中要注意:

  • this调用本类另一个构造方法,也必须是构造方法的第一个语句,唯一一个。
  • super和this两种构造调用,不能同时使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Fu {
int num = 30;
}


public class Zi extends Fu {
int num = 20;
public Zi() {
//super(); // 这一行不再赠送,super和this两种构造不能同时使用
this(123); // 本类的无参构造,调用本类的有参构造
//this(1, 2); // 错误写法,本类构造调构造必须是第一个且是唯一一个
}
public Zi(int n) {
this(1, 2);
}
public Zi(int n, int m) {
super();
}


//在本类的成员方法中,访问本类的成员变量
public void showNum() {
int num = 10;
System.out.println(num); // 局部变量
System.out.println(this.num); // 本类中的成员变量
System.out.println(super.num); // 父类中的成员变量
}


public void methodA() {
System.out.println("AAA");
}

//在本类的成员方法中,访问本类的另一个成员方法
public void methodB() {
this.methodA(); //AAA
System.out.println("BBB");//BBB
}
}

抽象类

抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。

抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class Animal {
// 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
public abstract void eat();

}

public class Cat extends Animal {
@Override
public void eat() { //重写抽象方法
System.out.println("猫吃鱼");
}
}


public class DemoMain {
public static void main(String[] args) {
// Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象
Cat cat = new Cat();
cat.eat();
}
}


注意事项

抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

接口

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法。

备注:换成了关键字interface之后,编译生成的字节码文件仍然是:.java –> .class

如果是Java 7,那么接口中可以包含的内容有:常量抽象方法

如果是Java 8,还可以额外包含有:默认方法,静态方法

如果是Java 9,还可以额外包含有:私有方法

接口除了public 和 abstract 还可以用什么?

从 Java 8 开始,接口可以包含默认方法(default)和静态方法(static)。

protected :在接口中不能使用 protected 修饰符。

原因: 接口的目的是提供公共 API,而 protected 只能被同一包中的类或不同包的子类访问,这限制了不同包的实现类对该接口的访问,这与接口的设计理念相悖。因此,接口中的方法和字段只能是 public 或 default(包私有)。

private:从 Java 9 开始,你可以在接口中使用 private 修饰符。

用途: private 方法可以用于接口内部的辅助方法,主要是为了减少代码重复和提高代码的封装性。这些 private 方法不能被实现类访问。

抽象方法

注意事项:

  • 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract

  • 这两个关键字修饰符,可以选择性地省略

  • 方法的三要素,可以随意定义

1
2
3
4
5
6
7
8
9
10
public interface MyInterfaceAbstract {
// 这是一个抽象方法
public abstract void methodAbs1();
// 这也是抽象方法
abstract void methodAbs2();
// 这也是抽象方法
public void methodAbs3();
// 这也是抽象方法
void methodAbs4();
}

接口实现类

接口不能直接使用,必须有一个实现类来实现该接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {
@Override
public void methodAbs1() {
System.out.println("这是第一个方法!");
}

@Override
public void methodAbs2() {
System.out.println("这是第二个方法!");
}

@Override
public void methodAbs3() {
System.out.println("这是第三个方法!");
}

@Override
public void methodAbs4() {
System.out.println("这是第四个方法!");
}

创建实现类的对象使用

1
2
3
MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
impl.methodAbs1();
impl.methodAbs2();

注意事项:

接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。

如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。

接口中的默认方法

以前创建了一个接口,并且已经被大量的类实现。如果需要再扩充这个接口的功能加新的方法,就会导致所有已经实现的子类需要重写这个方法。如果在接口中使用默认方法就不会有这个问题。所以从 JDK8 开始新加了接口默认方法,便于接口的扩展。

  • 接口的默认方法,可以通过接口实现类对象,直接调用。

  • 接口的默认方法,也可以被接口实现类进行覆盖重写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public interface MyInterfaceDefault { 
// 抽象方法
public abstract void methodAbs();

// 新添加的方法,改成默认方法
public default void methodDefault() {
System.out.println("这是新添加的默认方法");
}
}

public class MyInterfaceDefaultA implements MyInterfaceDefault {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,AAA");
}
}

public class MyInterfaceDefaultB implements MyInterfaceDefault {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,BBB");
}

@Override
public void methodDefault() {
System.out.println("实现类B覆盖重写了接口的默认方法");
}
}


public class Demo02Interface {
public static void main(String[] args) {
// 创建了实现类对象
MyInterfaceDefaultA a = new MyInterfaceDefaultA();
a.methodAbs(); // 调用抽象方法,实际运行的是右侧实现类。

// 调用默认方法,如果实现类当中没有,会向上找接口
a.methodDefault(); // 这是新添加的默认方法

MyInterfaceDefaultB b = new MyInterfaceDefaultB();
b.methodAbs();
b.methodDefault(); // 实现类B覆盖重写了接口的默认方法
}
}

接口中的静态方法

注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。

正确用法:通过接口名称,直接调用其中的静态方法。因为静态跟对象没关系,通过接口调用

1
2
3
4
5
6
7
8
9
10
11
public interface MyInterfaceStatic {
public static void methodStatic() {
System.out.println("这是接口的静态方法!");
}
}
// 创建了实现类对象
MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
// 错误写法,不能通过接口实现类对象直接调用接口当中的静态方法
//impl.methodStatic();
// 直接通过接口名称调用静态方法
MyInterfaceStatic.methodStatic();

接口中的私有方法

如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。

  • 普通私有方法,解决多个默认方法之间重复代码问题
  • 静态私有方法,解决多个静态方法之间重复代码问题

以下以静态私有方法为例,静态私有方法只能通过类内部的静态方法访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface MyInterfacePrivateB {
public static void methodStatic1() {
System.out.println("静态方法1");
methodStaticCommon();
}

public static void methodStatic2() {
System.out.println("静态方法2");
methodStaticCommon();

}


//静态方法的公共部分应该私有化,不能被接口实现类访问
private static void methodStaticCommon() {
System.out.println("AAA");
System.out.println("BBB");
}
}
public static void main(String[] args) {
MyInterfacePrivateB.methodStatic1();//只有接口中的静态方法才能访问私有方法
MyInterfacePrivateB.methodStatic2();
// MyInterfacePrivateB.methodStaticCommon(); // 错误写法!不能直接访问私有静态方法

}

接口常量

接口当中也可以定义成员变量,但是必须使用public static final三个关键字进行修饰,从效果上看,这其实就是接口的常量

备注:一旦使用final关键字进行修饰,说明不可改变。

注意事项:

  • 接口当中的常量,可以省略public static final,注意:不写也照样是这样。
  • 接口当中的常量,必须进行赋值;不能不赋值。
  • 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
1
2
3
4
5
6
7
public interface MyInterfaceConst {
// 这其实就是一个常量,一旦赋值,不可以修改
public static final int NUM_OF_MY_CLASS = 12;
}

// 访问接口当中的常量,直接通过接口名称.常量名
System.out.println(MyInterfaceConst.NUM_OF_MY_CLASS);

接口的多继承

使用接口的时候,需要注意:

  • 接口是没有静态代码块或者构造方法的。
  • 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
1
2
3
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
// 覆盖重写所有抽象方法
}
  • 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
  • 如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
  • 如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
  • 一个类如果其父类当中的方法和所实现接口当中的默认方法产生了冲突,优先用父类当中的方法。

对冲突的默认方法重写

1
2
3
4
5
6
7
8
9
10
11
interface A {
public default void method(){
System.out.println("AAAAAAAAAAAAAAAAAAA");
}
}

interface B {
public default void method(){
System.out.println("BBBBBBBBBBBBBBBBBBB");
}
}

子接口必须重写重复的默认方法,并且需要加上default

1
2
3
4
5
6
interface D extends A,B{
@Override
public default void method() {
System.out.println("DDDDDDDDDDDDDD");
}
}

抽象类和接口的区别

  1. 定义方式:
    • 抽象类使用 abstract 关键字来定义,可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法)。
    • 接口使用 interface 关键字来定义,只能包含抽象方法和常量字段(默认为 public static final)。
  2. 方法实现:
    • 抽象类可以包含抽象方法和具体方法,子类可以继承抽象类并实现其中的抽象方法,也可以覆写具体方法。
    • 接口中的方法默认都是抽象方法,实现接口的类需要实现接口中的所有抽象方法。
  3. 多继承:
    • 抽象类只能单继承,即一个类只能有一个直接父类。
    • 接口可以多继承,即一个类可以实现多个接口。
  4. 构造方法:
    • 抽象类可以有构造方法,并且构造方法可以被子类调用。
    • 接口不能有构造方法,因为接口中只能包含常量字段和抽象方法,无法包含具体实现的方法或者状态。
  5. 成员变量:
    • 抽象类可以包含实例变量、静态变量、常量等。
    • 接口只能包含常量字段(public static final),不能包含实例变量和静态变量。
  6. 默认方法和静态方法:
    • Java 8 引入了接口的默认方法和静态方法,可以在接口中提供默认的方法实现或者静态方法。抽象类没有这种特性。
  7. 用途和设计理念:
    • 抽象类用于定义类族中的共性特征,提供一部分通用实现,适合于代码重用和继承关系。
    • 接口用于定义类的行为规范,强调的是能力和协议,适合于实现类的统一约束和多继承需求。

继承
http://example.com/2023/05/13/Java/Java基础/继承/
作者
PALE13
发布于
2023年5月13日
许可协议