zoukankan      html  css  js  c++  java
  • Java 面向对象:类的继承

    禁止码迷,布布扣,豌豆代理,码农教程,爱码网等第三方爬虫网站爬取!

    继承

    继承(inheritance)是面向对象编程的重要手法,思想是基于已有的类创建新的类。继承已存在的类时可以复用原类的方法,同时增加一些新的方法和字段,使得新的类可以对新的情况进行处理。
    例如虚竹误打误撞破了玲珑棋局,继承了无崖子七十余年的功力,而后面他在童姥和李秋水互斗时获取了她们的九成功力。如果把无崖子当做是原类,无崖子的内力当做类的状态,招式当做类的方法,虚竹就当做是新类。我们发现虚竹不仅继承了无涯子内力和招式,还获得了其他的内力和新招式,也就是说类可以新类不仅能继承原类的字段和方法,也能自己拥有些独特的字段和方法。

    超类、子类

    一般将已存在的类称为超类、基类或父类,新的类被称之为子类或派生类。虽然原类被称之为超类,但是它并不见得比子类拥有更多的功能,实际上是“青出于蓝而胜于蓝”,子类将具有更多的特色。超类和子类的概念来源于数学的集合,例如员工是一个集合,经理虽然属于员工,但是并不等同于员工,因此可以称经理是员工的子集,或员工是经理的超集。
    当使用继承对超类进行拓展时,只需要指出子类和超类的不同之处。这个操作将使用到 extends 关键字,表示正在构造的新类派生于一个已存在的类。例如我有一个 Employee 类,现在我要派生出 Manager 类:

    public class Manager extends Employee{
       private double bonus;
    
       public void setBonus(double b){
          bonus = b;
       }
    }
    

    覆盖

    继承时我们没必要把原有的类全套照搬,也要“取其精华去其糟粕”。当遇到不使用的方法时,可以写一个新的方法来覆盖之。例如 Employee 类有个方法为 getSalary():

    private String name;
    private double salary;
    private LocalDate hireDay;
    
    public double getSalary(){
          return salary;
    }
    

    当需要重写 getSalary() 方法时,就可以在新的类中重新定义一次,这样就能够覆盖掉原方法了。

    private double bonus;
    
    public double getSalary(){
          return salary + bonus;
    }
    

    不过很可惜这种写法是错的,因为在原类中,salary 字段是私有字段,也就是说该方法只能被 Employee 类的方法使用。

    新的 Manager 类不能访问 Employee 类的字段,那就得使用 Employee 类的字段访问器 getSalary() 来访问。

    public double getSalary(){
          return salary;
    }
    

    但是这个方法已经被我们重写了,而且调用这个方法将会出现自己调用自己的递归调用情况。因此我们需要用关键字 super,这个关键字可以指示调用的是超类中的方法。

    private double bonus;
    
    public double getSalary(){
          double baseSalary = super.getSalary();
          return baseSalary + bonus;
    }
    

    子类构造器

    由于子类的构造器不能访问超类的私有字段,因此如果要初始化超类中的字段,就需要调用超类的构造器。此时还是可以使用 super 关键字来表示对超类构造器的调用:

    private double bonus;
    
    public Manager(String name, double salary, int year, int month, int day){
          super(name, salary, year, month, day);
          bonus = 0;
    }
    

    如果子类构造器没有显式地调用超类的构造器,那么会自动调用超类的无参构造器

    多态

    继承并不仅限于一个层次,而从某个特定的类到其祖先的路径被称为继承链。通常一个祖先类可以有多个子孙链,而且可以不断地继承下去。

    根据替换原则,程序中出现超类对象的任何地方都可以用子类对象来替换,也就是说子类的每个对象也会是超类对象。Java 类的对象是多态的,也就是说一个类变量不仅可以引用一个它本身的类对象,也可以引用它的子类的对象。例如:

    Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
    Employee staff = new Employee[3];
    staff[0] = boss;
    

    虽然 staff[0] 和 boss 引用的是一个相同的对象,但是 staff[0] 认为 boss 是个 Employee 对象。也就是说,staff[0] 只能使用 Employee 的方法,而 Manager 的方法只能由 boss 对象来用。同时还有一点,不能讲超类的引用赋给子类,也就是多态不能反过来。
    子类引用的数组可以转换成超类引用的数组,这个过程不需要转换。例如:

    Manager[] managers = new Manager[10];
    Employee[] staff = managers;
    

    这种写法是可以的,但是在实际中很可能就会混用,从而出现问题。所以对于数组我们要注意在创建时是什么类,并且仅将相同类对象的引用存储到数组中。

    禁止继承

    有的类被定义之后,可能不希望被新的类继承,这种不可扩展的类称之为 final 类。在类定义时使用 final 关键字就可以表示为 final 类,String 类就是一个知名的 final 类。例如:

    public final class Manager extends Employee
    

    其类中的方法同样也不能被覆盖,当然也可以给超类中的某几个方法加上 final。

    public final String getName(){
          return name;
    }
    

    将方法或类声明为 final 可以确保其不会在子类中改变语义,例如 String 类就要注意避免这种事发生。在设计类的层次时,需要好好考虑哪些类和方法要被定义为 final。

    强制类型转换

    需要暂时忽略对象的实例类型,使用对象的全部功能时,就需要对对象进行强制类型转换。对象类型转换的语法和数值表达式的语法蕾西,用圆括号把目标类名括起来,卸载需要转换的对象引用前。例如:

    Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
    Employee staff = new Employee[3];
    staff[0] = boss;
    Manager boss = (Manager) staff[0];
    

    当一个超类引用赋予一个子类变量时,必须要进行强制类型转换。不过强制类型转换并不是个很好的做法,实际中应当少用。

    抽象类

    对于一个继承层次,越上层的类于具有一般性,同时也会显得更为抽象。例如对于员工和铠甲勇士,他们都是人类,那么他们都具有一些相同的特征,例如名字。

    可以将这种类抽象出来,使用 abstract 关键字抽象出来,同时也可以用该关键字将方法定义为抽象方法。抽象类中可以包括字段、抽象方法和具体方法,通常是用来给子类提供模板,子类可以保留抽象类中的方法,也可以进行酬谢覆盖。

    public abstract class Person{
       public abstract String getDescription();
       private String name;
    
       public Person(String name){
          this.name = name;
       }
    
       public String getName(){
          return name;
       }
    }
    

    如果一个类被声明为抽象类,就不能创建这个类的对象。当定义一个抽象类的对象变量时,该变量只能引用非抽象子类的对象。

    受保护访问

    当希望某个超类的方法只允许子类访问,或者希望子类的方法可以访问超类的字段时,可以将类方法或者字段声明为受保护 protected。受保护字段可以限制该字段只能来同一个包中的类访问,但是这个字段需要慎用,因为其他程序员可能会从这个类派生出新的类,从而破坏封装性。

    访问控制修饰符 作用
    private 仅对本类可见
    public 对外部可见
    protected 对本包和所有子类可见
    不加修饰符 对本包可见

    包装器和装箱

    基本类型的数据都不是对象,但是我们有时候需要将一个基本类型变量转换为对象。Java 为每个基本类型都对应了一个类,这些类就被称之为包装器,例如 Int 类型对应的类为 Integer。包装器类是不可变的,一旦构造了包装器就不能修改其中的值,同时包装器类是 final,也就是说包装器不能被继承。
    例如我需要一个 int 类型的数组列表,但是 <> 中的类型不能是基本类型,这是就可以使用 Integer 包装器类来声明。

    var list = new ArrayList<Integer>();
    

    当使用 ArrayList 的 add() 方法添加元素时,由于 int 类型变量不是对象,因此类似 “list.add(1)” 这种写法会自动转换为下面的代码,这种变换称为自动装箱

    list.add(Integer.valueOf(1));
    

    当一个 Integer 对象赋值给 int 变量时,例如 “int n = list.get(i)” 也会自动转换,这种变换称为自动拆箱

    int n = list.get(i).intValue();
    

    Integer 对象提供了很多有用的方法,例如 parseInt(String) 方法就可以把一个字符串转换为 int 类型变量。Integer 也支持进制转换,分别使用 toBinaryString、toOctalString 和 toHexString 方法就可以得到。

    int num;
    String str = "123";
    		
    num = Integer.parseInt(str);
    System.out.println(Integer.toBinaryString(num));
    System.out.println(Integer.toOctalString(num));
    System.out.println(Integer.toHexString(num));
    

    包装器对象可以引用 null,同时如果混用了 Integer 和 Double 类,Integer 也会自动拆箱升级为 double,在自动装箱为 Double。

    继承的设计技巧

    1. 将公共操作和字段放在超类中。
    2. 不要使用 protected 字段,虽然它适合知识不提供一般用途,而是应该在子类覆盖的方法。这是因为子类集合是无限制的,任何人都能由你的类派生出子类。而且 Java 中同一个包的所有类,都可以随意访问 protected 字段。
    3. 使用继承实现 “is-a” 关系,也就是所谓的类的父子继承关系。
    4. 除非所有的继承方法都很有意义,否则不要使用继承。
    5. 覆盖方法时,不要改变方法预期的行为。
    6. 使用多态,不要使用类型信息。使用多态方法或接口实现的代码,比使用多个类型检查的代码更易于维护和扩展。

    参考资料

    《Java 核心技术 卷Ⅰ》,[美]Cay S.Horstmann 著,林琪 苏钰涵 等译,机械工业出版社

  • 相关阅读:
    Mysql(11)_Mysql权限与安全
    Mysql(10)_存储过程与流程控制
    Java(43)_AWT事件处理挂关闭生效
    6.实现合同测试用例
    6.测试库优化
    5.案例回顾及编写测试用例
    4.测试案例实现代码库与测试用例V2.0
    3.测试案例实现代码库与测试用例
    markdown语法学习
    1.faker批量随机造数据
  • 原文地址:https://www.cnblogs.com/linfangnan/p/13415848.html
Copyright © 2011-2022 走看看