0%

面向对象编程(中级部分)-2

  • 封装、继承

★★★封装

  • 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。

    封装可以隐藏实现细节,用户只需调用方法即可;封装也可以对数据进行验证,保证安全合理。

★封装的实现步骤
  1. 属性私有化,即用户不能直接修改属性(用 private 关键字);

  2. 提供一个公共的 set 方法,用于对私有属性进行判断并赋值

    1
    2
    3
    4
    public void setXxx(类型 参数){
    //加入数据验证的业务逻辑
    属性 = 参数;
    }
  3. 提供一个公共的 get 方法,用于获取私有属性的值

    1
    2
    3
    4
    public 数据类型 getXxx(){
    //进行权限判断
    return xx;
    }
封装与构造器结合
  • 可以在构造器中使用已有的 setXxx 方法来保证对输入的数据进行业务逻辑判断,保证了封装。
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
class Person {
String name;
private int age;
private double salary;

public Person() {
}

public Person(String name, int age, double salary) {
this.setName(name);
this.setAge(age);
this.setSalary(salary);
}

public void setName(String name) {
if (name.length() > 6 || name.length() < 2) {
System.out.println("名字长度不合理,请重新设置");
} else {
this.name = name;
}
}

public void setAge(int age) {
if (age >= 121 || age <= 0) {
System.out.println("年龄不合理,请重新设置");
} else {
this.age = age;
}
}

public void setSalary(double salary) {
this.salary = salary;
}
...
}

★★★继承

  • 继承可以解决代码复用,让我们的编程更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。

    继承提高了代码的复用性,也提高了代码的扩展性和维护性。

  • 继承的基本语法:

    1
    2
    class 子类 extends 父类{
    }
    • 子类会自动拥有父类定义的属性和方法;
    • 父类又叫超类、基类,子类又叫派生类。
继承图

![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C8-7.jpg)

★继承使用细节
  1. 子类继承了父类的所有属性和方法,非私有的属性和方法可以在子类中直接访问,但是私有的属性和方法不能在子类中直接访问,要通过父类提供的公共的方法去访问

  2. 子类必须调用父类的构造器,完成父类的初始化。当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无 参构造器(super();),如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作(**super(参数列表)**),否则编译不会通过。

    注意super() 在使用时,必须放在构造器的第一行super() 只能在构造器中使用)。

  3. super()this() 都只能放在构造器的第一行,因此这两个方法不能共存于一个构造器中(即当在子类的构造器中使用 this() 时,就不会再调用 super() 了)

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C8-11.jpg)

    A(){...}中也是有 super() 的,但是是 Object 类的构造器,所以没有输出。

  4. Java 所有类都是 Object 类的子类,Object 类是所有类的父类。

  5. 父类构造器的调用不限于子类的直接父类,将一直往上追溯直到 Object 类(顶级父类),例如假设 C 是 B 的子类,B 是 A 的子类,A 是 Object 的子类,则在创建 C 类的对象时,构造器的调用顺序为:Object -> A -> B -> C。(全部都会调用

  6. 子类最多只能继承一个父类,即 Java 中是单继承机制。若要 C 类既继承 A 类又继承 B 类,则可以让 C 类继承 B 类,然后让 B 类继承 A类。

  7. 不能滥用继承,子类和父类必须满足 is-a 的逻辑关系。

★★★继承本质详解
  • 如下图所示,有 GrandPaFatherSon 三个类,当在 main 函数中创建 Son 对象时:

    1. 首先会在方法区中加载其父类的信息,但由于 Father 类也有父类 GrandPaGrandPa 类也有父类 Object,因此会最先加载 Object 类的信息,然后加载 GrandPa 类的信息,再加载 Father 类的信息,最后加载 Son 类的信息,然后这几个类的信息是相关联的。

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C8-8.jpg)

    2. 然后在堆中会分配一段内存空间,用于存储成员变量,由于父类的存在,所以在 Son 对象的内存空间中,有如下图所示的几个存储区。

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C8-9.jpg)

    3. 然后在栈的 main栈 中,变量 son 指向 Son 对象。

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-notebook/img/Java/C8-10.jpg)

  • 由上图可以看到,在 GrandPaFatherSon 类中,都有成员变量 name,那在使用 son.name 时,到底是用哪个 name 呢?这就要按照查找关系来返回信息

    ★★★查找关系

    1. 首先看子类本身是否有该属性;
    2. 如果子类有这个属性,则返回信息;
    3. 如果子类没有这个属性,就看父类有没有这个属性(如果父类有这个属性,并且可以访问,就返回信息);
    4. 如果父类没有这个属性,就按照 3 的规则,继续找上级父类,直到 Object。

    注意:假设 Father 类的 age 属性是 private 修饰的,那 son.age 是无法访问的;此时如果在 GrandPa 类中增加一个可以访问的 age 属性,son.age 仍然无法访问,因为 son 对象已经向上找到了 Father 类中的 age 属性,只是该属性是 private ,无法访问,并不会跳过这个无法访问的属性继续向上找

---------------The End---------------