12.[Java 面向对象] 封装与继承


1. 封装

1.1 封装介绍

封装是面向对象编程的三个基本特征之一,其余两个特征是继承与多态。

  • 封装(encapsulation) 就是将对象的 属性方法 藏在对象的内部被保护起来
  • 程序的其他部分只能通过被授权的方法才能对封装后的 属性方法 进行操作
  • 封装可以隐藏实现细节
  • 把属性封装之后, 只能通过指定方法访问属性, 可以在方法中对数据进行验证

1.2 封装的实现方式

  1. 使用 private 修饰符修饰属性或方法
  2. 提供一个 public 修饰的 set 方法, 赋予属性值,可在赋予属性值的过程中加入判断逻辑
  3. 提供一个 public 修饰的 get 方法, 获取属性值,可在获取属性值的过程中对属性值进行处理
// 使用 private 修饰符,定义属性
private 数据类型 属性名称;

// set 方法
public void setXXX(类型 参数名){
  // 数据验证逻辑
  属性 = 参数名;
}
// get 方法
public void getXXX(类型 参数名){
  return 属性;
}

1.3 封装示例

给人设置年龄, 年龄要在一个合理的范围

public class PrivatePerson {
    public String name;
    private int age;

    @Override
    public String toString() {
        return "PrivatePerson{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(age <0 || age > 150){
            System.out.println("年龄设置错误");
            this.age = 18;
        }else{
            this.age = age;
        }
    }
}

class PrivatePersonTest{
    public static void main(String[] args) {
        PrivatePerson person = new PrivatePerson();
        person.name = "阿花";
        person.setAge(1000);
        System.out.println(person.toString());
    }
}

1.4 封装与构造器

public class Private02 {
    public static void main(String args[]) {
        PrivatePerson02 p1 = new PrivatePerson02("小花", 2000);
        System.out.println(p1.toString());
    }
}

class PrivatePerson02{
    String name;
    int age;

    private PrivatePerson02() { }

    public PrivatePerson02(String name, int age) {
        // 在构造器中使用 set 方法设置属性
        setName(name);
        setAge(age);
    }


    @Override
    public String toString() {
        return "PrivatePerson{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(age <0 || age > 150){
            System.out.println("年龄设置错误,设置默认 age 为 18");
            this.age = 18;
        }else{
            this.age = age;
        }
    }
}

2. 继承

2.1 继承的基本介绍

  • 继承可以提高代码的复用性和扩展性,可以很好的解决代码重复问题,让编程更加接近人类思维

  • 当多个类存在相同的属性和方法时, 可以从这些类中的公共属性和方法抽出来,定义成一个父类

  • 所有的子类只需要通过 extends 来声明继承父类即可,不需要重新定义父类中存在的公共的属性和方法

  • 在继承关系中, 父类又叫做 超类或基类,子类又叫做派生类

  • Java 中的继承是单继承制,即:子类最多只能直接继承一个父类

  • Java 中所有的类都是 java.lang.Object 类的子类

  • 继承语法

class 子类 extends 父类{
  // 逻辑代码
    // 定义成员属性。。。
    // 定义成员方法。。。
    // 定义构造方法。。。
}
// 继承父类后,子类自动拥有父类定义的属性和方法

类的继承关系示意图

  • 测试代码
public class Extends01 {
    public static void main(String[] args) {
        Pupil pupil = new Pupil();
        pupil.name = "小红";
        pupil.testing();
        pupil.score = 90;
        pupil.getInfo();

        Graduate graduate = new Graduate();
        graduate.name = "大黄";
        graduate.testing();
        graduate.score = 100;
        graduate.getInfo();
    }
}

// 小学生
class Pupil extends Student{
    public void testing() {
        System.out.println("小学生 "+name+" 正在考试...");
    }
}
// 大学生
class Graduate extends Student{
    public void testing() {
        System.out.println("大学生 "+name+" 正在考试...");
    }
}

// 学生类, 是 Pupil  和 Graduate 的父类
class Student{
    // 定义属性
    String name;
    int age;
    double score;
    // 定义方法

    // 设置分数
    public void setScore(double score) {
        this.score = score;
    }

    // 考试方法
    public void testing() {
        System.out.println("正在考试ing...");
    }

    // 获得信息方法
    public void getInfo() {
        System.out.println("Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}');
    }
}

2.2 继承的细节

  1. 子类对象实例化时
  • 不管使用子类的哪个构造器,默认情况下,总会去调用父类的无参构造器
  • 如果父类没有无参构造器,则必须在子类中使用 super 去显式的调用父类的有参构造器完成对父类的初始化工作
  • 如果父类没有无参构造器,也不显式调用父类有参构造器,则编译不会通过
  • 父类构造器的调用不限于直接父类, 将一直往上调用到 Object 类的构造方法
public class Extends03 {
    public static void main(String[] args) {
        Sub03 sub = new Sub03();
    }
}
// 父类
class Base03 {
    String name;
    private Base03(){
        System.out.println("Base03 的无参构造方法被调用");
    }
    public Base03(String name){
        System.out.println("Base03 的有参构造方法被调用");
        this.name = name;
    }
}
// 子类
class Sub03 extends Base03 {
    public Sub03() {
        // 显示的调用父类的有参构造器
        super("123");
        System.out.println("Sub03 的构造方法被调用");
    }
}
  1. 子类继承了父类所有的属性和方法,
  • 父类 非私有 属性,子类 能够 直接访问
  • 父类的 私有 属性,子类 不能够 直接访问, 需要通过父类中的方法进行访问
public class Extends02 {
    public static void main(String[] args) {
        Sub02 sub = new Sub02();
        // 父类中非 private 的属性都可以访问
        System.out.println(sub.public_);
        System.out.println(sub.protected_);
        System.out.println(sub.default_);
        // 无法访问到父类的 private 方法
        // 提示信息: 'private_' has private access in 'com.hnbian.part1.oop.inheritance.Base'
        // System.out.println(sub.private_);

        // 父类中非 private 的方法都可以访问
        sub.publicMethod();
        sub.protectedMethod();
        sub.defaultMethod();

        // 通过父类中的公共方法访问 私有属性和方法
        sub.getPrivate();
    }
}

class Base02 {
    public Base02(){
        System.out.println("Base02 的无参构造方法被调用");
    }

    // 用四种修饰符分别修饰四个属性;
    public String public_ = "public_";
    protected String protected_ = "protected_";
    String  default_= "default_";
    private String private_ = "private_";

    // 用四种修饰符分别修饰四个方法;
    public void publicMethod(){
        System.out.println("public method 被调用");
    }

    protected void protectedMethod(){
        System.out.println("protected method 被调用");
    }

    void defaultMethod(){
        System.out.println("default method 被调用");
    }

    private void privateMethod(){
        System.out.println("private method 被调用");
    }

    // 在父类中提供 public方法, 访问父类中的私有方法和属性
    public void getPrivate(){
        System.out.println("getPrivate 中访问私有方法, private_ = "+private_);
        privateMethod();
    }
}

class Sub02 extends Base02 {
    public Sub02(){
        System.out.println("Sub02 的构造方法被调用");
    }
}

2.3 继承的内存形式

我们看一个案例分析, 当子类继承父类, 创建子类对象时, 内存中发生了什么,

当子类对象创建好以后,建立一种查找关系

class Grandpa {
    String name = "Grandpa";
    String hobby="钓鱼";
}

class Father extends Grandpa {
    String name = "Father";
    int age = 30;
}

public class Son extends Father {
    String name = "Son";
    public static void main(String args[]) {
        Son son = new Son();
        // 属性查找顺序
        // 1. 先看本类是否有该属性,
        // 2. 如果本类有该属性则返回, 如果没有则去父类找该属性
        // 3. 如果父类没有再去父类的父类找, 一直向上一直追溯到 object 类
        System.out.println(son.name);
        System.out.println(son.age);
        System.out.println(son.hobby);
    }
}
  • 修改图
    继承的内存模型

2.4 继承中的属性查找

属性查找顺序

  1. 先看本类是否有该属性,并且可以访问则返回该属性信息
  2. 如果子类没有该属性,则去父类找该属性,如果父类有并且可以访问就返回该属性信息
  3. 如果父类没有再去父类的父类找, 按照此规则向上一直追溯到 object 类
  4. 如果找到父类有该属性,但没有访问权限, 则直接报错,不会再向上查找

3. super

super 代表父类的引用, 可用于访问父类的成员属性、成员方法和构造方法

3.1 super访问构造器

  1. 如果希望指定去调用父类的某个构造器, 则显式的调用一下, 调用方式 super(参数列表)
  2. 使用 super 调用父类的构造器只能在本类构造器中使用
  3. 在使用 super 调用父类的构造器时, 必须放在构造器的第一行
  4. 使用 super()this() 调用构造器时都只能放在构造器第一行, 因此这两个方法不能共存在一个构造器中
  • 代码示例
public class Super01 {
    public static void main(String[] args) {
        Sub01 sub01 = new Sub01("小明");
    }
}

class Base01 {
    String name;
    public Base01(){
        System.out.println("Base01 父类 的无参构造方法被调用");
    }
    public Base01(String name){
        this.name = name;
        System.out.println("Base01 父类 的有参构造方法被调用");
    }
}

class Sub01 extends Base01 {
    public Sub01(String name) {
        // 显式的调用父类的有参构造器
        super(name);
        System.out.println("Sub01 子类 的构造方法被调用");
    }

    public Sub01() {
        // 显式的调用父类的无参构造器
        super();
        System.out.println("Sub01 的构造方法被调用");
    }
}

3.2 super访问属性和成员方法

使用 super 可以访问父类中 非私有 的属性和方法,如果想访问父类的私有方法可以借助父类的公有方法

  • 访问父类属性:super.属性名;
  • 访问父类的方法:super.方法名(参数列表);
class Super01_A{
    // 用四种修饰符分别修饰四个属性;
    public String public_ = "public_";
    protected String protected_ = "protected_";
    String  default_= "default_";
    private String private_ = "private_";

    // 用四种修饰符分别修饰四个方法;
    public void publicMethod(){
        System.out.println("public method");
    }
    protected void protectedMethod(){
        System.out.println("protected method");
    }
      void defaultMethod(){
        System.out.println("default method");
    }
    private void privateMethod(){
        System.out.println("private method");
    }
}

class Super01_B extends Super01_A{
    public void testSuper(){
        System.out.println(super.default_);
        System.out.println(super.protected_);
        System.out.println(super.default_);
        // 无法使用super访问 父类的 private 属性
        //System.out.println(super.private_);

        super.publicMethod();
        super.protectedMethod();
        super.defaultMethod();
        // 无法使用super访问 父类的 private 方法
        // super.privateMethod();
    }
}

3.3 super 属性访问顺序

  • 使用 this 访问成员属性或成员方法时:
  1. 先查找本类, 如果本类有则调用, 如果本类没有则查找父类,

  2. 如果父类有则调用, 如果父类没有继续查找父类的父类, 一直找到object类

  3. 如果在编码过程中, 尝试调用父类没有访问权限的方法, 则无法通过编译

  • 使用 super 访问成员属性或成员方法时:
  1. super访问属性或方法会跳过本类直接去父类查找,如果父类有则直接调用

  2. 如果父类没有则去父类的父类继续查找,

  3. 如果子类重写了父类的方法,一直找到object类

  4. 如果在编码过程中, 尝试调用父类没有访问权限的方法, 则无法通过编译

  5. super 的访问不限于直接父类, 如果爷爷类和本类中有同名的成员,也可以使用,

  6. 如果多个基类中都有同名的成员, 遵循就近访问的原则

class Son extends Father {
    public  void call() {
        m1(); // Son 类 - m1 方法 被调用
        this.m1(); // Son 类 - m1 方法 被调用
        super.m1(); // Father 类 - m1 方法 被调用

        System.out.println("---");

        // 如果 调用父类特有的方法 ,使用 this 或super效果一样
        m2(); // Father 类 - m2 方法 被调用
        this.m2(); // Father 类 - m2 方法 被调用
        super.m2(); // Father 类 - m2 方法 被调用

        System.out.println("---");

        // 如果 调用子类特有的方法 ,只能使用 this
        m3(); // Son 类 - m3 方法 被调用
        this.m3(); // Son 类 - m3 方法 被调用
        // super.m3(); 无法使用super 调用子类特有的方法

        System.out.println("---");

        m4(); // GrandFather 类 - m4 方法 被调用
        this.m4(); // GrandFather 类 - m4 方法 被调用
        super.m4(); // GrandFather 类 - m4 方法 被调用
    }

    // 这里重写了父类的 m1 方法, 这里设置方法的权限不能低于父类的访问权限 public
    @Override
    public void m1(){ System.out.println("Son类 -m1 方法被调用"); }
    public void m3(){ System.out.println("Son类 -m3 方法被调用"); }
}

class Father extends GrandFather {
    public void m1(){ System.out.println("Father类- m1 方法被调用"); }
    public void m2(){ System.out.println("Father类- m2 方法被调用"); }
}

class GrandFather {
    public void m4(){ System.out.println("GrandFather类- m4 方法被调用"); }
    public void m1(){ System.out.println("GrandFather类 - m1 方法 被调用"); }
}
默认访问 使用 this 访问 使用 super 访问
m1() son.m1 被调用 son.m1 被调用 father.m1 被调用
m2() father.m2 被调用 father.m2 被调用 father.m2 被调用
m3() son.m3 被调用 son.m3 被调用 报错,father 没 m3方法
m4() GrandFather.m4 被调用 GrandFather.m4 被调用 GrandFather.m4 被调用

3.4 Super 与 this 的比较

区别 this super
访问
属性
访问本类中的属性, 如果本类没有此属性或方法,继续到父类查找, 一直到 object 类 跳过子类, 直接从父类的查找,如果父类没有继续查找父类的父类, 一直到 object 类
调用
方法
访问本类中的属性, 如果本类没有此属性或方法,继续到父类查找, 一直到 object 类 跳过子类, 直接从父类的查找,如果父类没有继续查找父类的父类, 一直到 object 类
调用
构造器
调用本类构造器,必须放在构造器的首行 调用父类构造器,必须放在子类构造器的首行
特殊 表示当前对象 在子类中表示父类对象

4. 方法重写

  • 方法重写(Override)也叫方法覆盖

  • 子类的方法和父类的某个方法的 名称返回类型形参 都一样,即子类个方法覆盖了父类的方法

  • 子类的方法与其任意一个基类中的方法满足上面的条件都认为是方法重写

class Animal {
    public void say() {
        System.out.println("动物会叫");
    }
}

class Dog extends Animal {
    // 方法重写, 子类的方法重写了父类的方法
    public void say() {
        System.out.println("小狗汪汪叫");
    }
}

4.1 方法重写细节

  1. 子类方法的返回类型和父类方法返回类型一致 或是 父类返回类型的子类
    • 如 父类 方法返回类型是 Object,子类 方法返回类型是 String
  2. 子类方法不能缩小父类方法的访问权限
// 子类
class Sub02 extends Base02{
    // 重写父类方法,返回类型为 父类方法的返回类型的子类
    @Override
    public String m1(){
        return null;
    }

    //Method does not override method from its superclass
    // 当子类的形参 为父类 参数的形参, 则不会构成 方法重写
    //@Override // 加上 Override 注解则编译不通过
    public Object m2(String str){
        return null;
    }

    // 当重写父类的方法 并 试图缩小访问范围时,会抛出异常,编译不通过
    private void m3(){ }
}
// 父类
class Base02{
    public Object m1(){
        return null;
    }

    public Object m2(Object obj){
        return null;
    }

    public void m3(){ }
}

4.2 方法重载与重写的比较

名称 发生范围 方法名 形参列表 返回类型 修饰符
重载
Overload
本类 必须一样 类型,个数,顺序
至少有一个不同
无要求 无要求
重写
Override
父类,子类 必须一样 必须一样 子类方法返回类型

父类方法相同
或者是
父类返回类型的子类
子类方法的访问范围
必须大于等于
父类方法的访问范围

文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
 上一篇
14.[Java 面向对象] Object 类介绍 14.[Java 面向对象] Object 类介绍
1. Object 类介绍1.1 介绍 Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。 Object 类位于 java.lang 包中,编译时会自动导
2018-02-02
下一篇 
11.[Java 面向对象] 包与访问修饰符 11.[Java 面向对象] 包与访问修饰符
1. 包1.1 介绍包 包的本质就是创建不同的文件夹,来保存类文件 同一个包内不允许有重名的类,但是可以在不同包中相同名字的类 通过包可以进行访问权限的控制 当系统比较复杂时, 类会比较多, 使用包可以方便对类进行管理 声明包的基本语法
2018-01-30
  目录