Java 从入门到放弃09 - 《封装、继承、多态》

封装

  • 该露的露,该藏得藏
  • 高内聚,低耦合
  • 一句话概括封装: 属性私有,get/set

也就是把属性变成private,然后通过使用用public的方法来允许其他人访问类的属性

  • 快捷键 alt+insert 可以快速地插入方法
  • 封装还可以通过 set/get 来进行提前的安全性检查,避免不合理数据的注入或不合理成员的获取

方法的重载

  • 在同一个类名字里加入许多同名函数,让他们传入的参数不同,叫做方法的重载

继承

  • 继承的本质是对某一种类的抽象,从而实现更好的建模

  • extends 的意思是拓展,子类是对父类的拓展

  • Java中只有单继承,没有多继承!

    • 继承是类与类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
    • 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用extends表示
    • 子类与父类之间,存在“is a”的关系
    • 继承之间注意修饰符,比如 public protected default private 之间的继承是由顺序要求的
  • 子类无法继承父类的 private 字段或者 private 方法。可以通过protected修饰符来修饰,使得字段和方法的访问权限被控制在继承树内部

  • 子类不会继承父类的任何构造方法,子类的构造方法是编译器自动生成的,不是继承的

组织继承

  • 正常情况下,如果一个类没有 final 修饰符,就是可以被继承的,但是还有permits关键字,可以指定可以继承该类的类的名称。前提是用 sealed 修饰class
public sealed class Shape permits Rect, Circle, Triangle {
    ...
}

object类

  • object类是所有类的父类,所有的类都是从object拓展来的,所以才会有“toString”等方法

super 与 this

  • this 指向自身,super指向父类

方法的重写

  • 重写都是方法的重写,与属性无关

  1. 如图,我们建立了两个类,A类是B类的拓展,并且各赋予一个方法

image

  1. 接下来在main函数里创建一个A类的对象a,调用a的方法test()
    image

  2. 程序也是很自然的跑出了结果

A test

  1. 我们此时用B类创建一个A类的对象b,调用方法test(),查看结果
    image

A test
B test

  • 如何理解这里呢?定义是什么类,就调用什么类的静态方法
  • 父类的引用指向了子类,调用了子类的方法
  • 用B类创建了A类的对象,把A的值给了B,这时候B是A,而A又继承了B类,向上转型,所以调用的是B类的方法,也就是 B test

这就是多态

  • 多态的关键:父类的引用指向了子类的对象
  1. 如果此时我们把static去掉呢?
    image
    可以看到这里两边都多出来了两个圈圈
    6.使用快捷键 alt+insert 之后
    image
  • 这里的override是 注解 注解是有功能的注释,
  1. 这时候再执行程序,可以看到结果

A test
A test

  • 重写只和非静态方法有关,只和非static方法有关
  • 静态方法只能被继承,不能被重写
  • 这是因为静态方法,是和类同时被加载的,所以只和类有关
  • 重写的范围可以扩大不能缩小
  • 抛出异常的范围可以缩小不能扩大

向上转型

  • 上述的过程涉及到向上转型。
  • 上述过程中,A是从B继承下来的,那么一个引用类型为 B 的变量,能够指向子类 A 的实例
  • 向上转型实际上是把一个子类型安全地变为更加抽象的父类型

向下转型

Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
  • Person 类型 p1 作为父类,指向了子类 Student 的实例,但 Person类型 p2 指向 Person 的实例。在向下转型的过程中,p1 的转型会成功,因为 p1 确实指向 Student 的实例,但把 p2 转型成为 Student 会失败,因为 p2 的实例,实际上就是 Person ,不能把父类变成子类,因为子类的功能比父类更多,多的功能无法凭空编出来。
  • 转型失败报错会报错 ClassCastException
  • 可以使用 instanceof 操作符 来判断一个实例究竟是不是某种类型
  • 所以可以使用 instanceof 进行安全地向下转型
Object obj = "hello";
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
}

从java 14 开始,可以更简洁的使用下面这种写法

public class Main {
    public static void main(String[] args) {
        Object obj = "hello";
        if (obj instanceof String s) {
            // 可以直接使用变量s:
            System.out.println(s.toUpperCase());
        }
    }
}

区分继承和组合

假设Person类拥有属性name ,Student类继承自Person类,现在有一个新类 Book,Book类也拥有属性name。那么我们能不能让Student继承自Book呢?

显然从逻辑上讲,这是不合理的,因为Student 与 Book 之间是 has 的关系 而不是 is a 的关系

所以这里我们使用组合,而不是继承,即让Student可以持有一个Book的实例

class Student extends Person {
    protected Book book;
    protected int score;
}

多态

  • 多态是指,针对某个类型的方法调用,其真正的执行方法取决于运行时期实际类型的方法
Person p = new Student();
p.run();

上面的代码,可以看出来调用的是 Student 的 run() 方法
但是假如有如下的方法

public void runTwice(Person p) {
    p.run();
    p.run();
}

传入的参数类型是 Person 但是我们并不知道传入参数的实际类型到底是 Person 还是 Student 抑或是 Person 的其他子类,因此也就无法确定调用的到底是不是 Person 类的 run() 方法

所以,多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类override之后的方法。

public class Main {
    public static void main(String[] args) {
        // 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
        Income[] incomes = new Income[] {
            new Income(3000),
            new Salary(7500),
            new StateCouncilSpecialAllowance(15000)
        };
        System.out.println(totalTax(incomes));
    }

    public static double totalTax(Income... incomes) {
        double total = 0;
        for (Income income: incomes) {
            total = total + income.getTax();
        }
        return total;
    }
}

class Income {
    protected double income;

    public Income(double income) {
        this.income = income;
    }

    public double getTax() {
        return income * 0.1; // 税率10%
    }
}

class Salary extends Income {
    public Salary(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        if (income <= 5000) {
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}

class StateCouncilSpecialAllowance extends Income {
    public StateCouncilSpecialAllowance(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        return 0;
    }
}

观察totalTax()方法,利用多态,totalTax() 方法只需要和 Income 打交道,它完全不需要知道Slery等类的存在,就可以计算出总的税,如果要新增一种稿费收入,只需要从Income中派生,然后正确的覆写(override) getTax()方法就可以。把新的类型传入 totalTax(),剩下的部分都不需要修改

posted @   ZzTzZ  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示