Java核心技术-继承

一、类,超类和子类

1.覆盖方法

  1. 由于超类中的有些方法对子类不一定适用,因此需要提供一个新的方法覆盖override
  2. 我们可以使用super关键字调用超类的方法或构造器
  3. s u p e r 不是一个对象的引用,只是一个指示编译器调用超类方法的关键字。 \textcolor{CarnationPink}{super不是一个对象的引用,只是一个指示编译器调用超类方法的关键字。} super不是一个对象的引用,只是一个指示编译器调用超类方法的关键字。

2.子类构造器

  1. 使用super/this调用的构造器语句必须是子类构造器的第一条语句
  2. 由于子类的构造器不能访问超类的私有字段,所以必须通过一个构造器来初始化这些私有字段
  3. 如果子类构造器没有显示的调用超类的构造器,将自动的调用超类的无参构造器
  4. 如果超类没有无参构造器,并且子类的构造器中又没有显示的调用超类的其它构造器,Java编译器会报错

3.多态

  1. 一个对象变量可以指示多种实际类型的现象称为 多态 \textcolor{CarnationPink}{多态} 多态

  2. 在运行时能够自动的选择适当的方法称为 动态绑定 \textcolor{CarnationPink}{动态绑定} 动态绑定,使用动态绑定可以无需对现有代码进行修改就可以对程序进行扩展。

  3. 判断数据是否应该设计为继承关系:"is-a"规则

    • 子类的每个对象也是超类的对象
    • 程序中出现超类对象的任何地方都可以使用子类的对象替换
  4. 警告⚠: \textcolor{CarnationPink}{警告⚠:} 警告子类引用的数组可以转换成超类引用的数组,而不需要使用强制类型转换

    1. 所有数组都要牢记创建时的元素类型,仅将类型兼容的引用存储到数组中
            Manager[] managers=new Manager[10];
            Employee[] staff=managers;
            staff[0]=new Employee("mraz",1,2,3,4);//ArrayStoreException
    

4.理解方法调用

假设要调用x.f(args),隐式参数x声明为类C的一个对象,下面是详细调用过程:

  1. 编译器查看对象的声明类型和方法名。编译器会一一列举C类中所有名为f的方法及其超类中所有名为f且可访问的方法

  2. 编译器要确定方法调用中提供的参数类型。

    1. 如果存在一个与所提供参数类型完全匹配的方法,就选择这个方法。这个过程称为 重载解析。 \textcolor{CarnationPink}{重载解析。} 重载解析。

    2. 由于允许类型转换,如果编译器没有找到与参数类型匹配的方法,或发现经过类型转换后有多个方法与之匹配,编译器就会报错。

    3. 方法的名字和参数列表称为 方法的签名, \textcolor{CarnationPink}{方法的签名,} 方法的签名,返回类型不是签名的一部分。

    4. 覆盖一个方法时,要保证返回类型的兼容性,子类方法不能低于超类方法的可见性。 允许子类将覆盖方法的返回类型改为原返回类型的子类型。 \textcolor{CarnationPink}{允许子类将覆盖方法的返回类型改为原返回类型的子类型。} 允许子类将覆盖方法的返回类型改为原返回类型的子类型。我们说这两个getBuddy()方法有可协变的返回类型。

      //Employee类
      public Employee getBuddy() {
              return this;
          }
         
      //Manager类
      @Override
      public Manager getBuddy() {
          return this;
      }
      

      5.至此,编译器已经知道需要调用的方法的名字和参数类型了

  3. 如果是private方法、static方法、final方法或构造器,那么编译器可以准确知道要调用哪个方法,这称为静态绑定。

  4. 如果要调用的方法依赖于隐式参数的实际类型,那么必须在运行时使用动态绑定。

  5. 程序运行并且采用动态绑定调用方法时,虚拟机必须调用与x所引用对象的实际类型对应的那个方法。

  6. 虚拟机预先为每个类计算了一个方法表,其中列出了所有的方法签名和要调用的实际方法

    真正调用方法的时候,虚拟机只要查找这个表就行了。

    注意:如果调用是super.f(param),那么编译器将对隐式参数超类的方法表进行搜索。

5.阻止继承:final类和方法

  1. 不允许被扩展的类称为final类
  2. final类中的所有方法自动成为final方法,不包括字段,子类不能覆盖final方法
  3. 将方法或类声明为final的主要原因是:确保它们不会在子类中改变语义
  4. 如果一个方法没有被覆盖并且很短,编译器能够对它进行优化处理,这个过程称为 内联 \textcolor{CarnationPink}{内联} 内联
  5. 虚拟机中的 即时编译器 \textcolor{CarnationPink}{即时编译器} 即时编译器可以准确的知道类之间了继承关系,并能够检测出是否有类确实覆盖了给定的方法
  6. 如果虚拟机加载了另一个子类,而这个子类覆盖了一个内联方法,优化器将取消对这个方法的内联。

6.强制类型转换

  1. 只能在继承层次内进行强制类型转换
  2. 在将超类转换成子类之前,应当使用instanceof进行检查
  3. 尽量少用强制类型转换和instanceof运算符

7.抽象类

  1. 扩展抽象类有两种选择:
    • 在子类中保留抽象类中的部分或所有抽象方法仍未定义,这样就必须将子类也标记为抽象类
    • 定义全部方法,这样子类就不是抽象的了
  2. 抽象类不能实例化,可以定义一个抽象类的对象变量,这个变量只能引用非抽象子类的对象

二、Object类:所有类的超类

1.Object类型的变量

  1. Object类型的变量只能用于作为各种值的一个泛型容器,要想对其中内容进行具体操作,还需要清楚对象的原始类型,并进行相应的强制类型转换

  2. 所有的数组类型,不管是对象数组还是基本类型数组都扩展了object类

    Employee[] staff=new Employee[10];
    Object obj=staff;
    Object o=new int[10];
    

2.equals方法

  1. Onject类中的equals方法用于检测一个对象是否等于另外一个对象

    为了防备name和hireDay可能为空的情况,需要使用使用Objects.equals方法

    //Employee类
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;//先判断是不是同一个对象
        if (o == null || getClass() != o.getClass()) return false;//如果o为空或者它们类型不一致,返回false
        Employee employee = (Employee) o;//将o强制类型转换
        //防备name和hireDay可能为null的情况
        return Double.compare(employee.salary, salary) == 0
                && Objects.equals(name, employee.name)
                && Objects.equals(hireDay, employee.hireDay);
    }
    //Manager类
        @Override
        public boolean equals(Object o) {
            //首先通过super关键字调用父类的方法,检测父类字段是否相等
            if (!super.equals(o)) return false;
            //强制类型转换为子类对象,然后检测子类字段的值是否相等
            Manager manager = (Manager) o;
            return Double.compare(manager.bonus, bonus) == 0;
        }
    

3.相等测试与继承

  1. 如果显示参数和隐式参数不属于同一个类,equals方法如果发现类不匹配,就会返回false

  2. 许多程序员使用instanceof进行检测:

    //Employee  
    public boolean equals(Object o) {
            if (this == o) return true;//先判断是不是同一个对象
    //        if (o == null || getClass() != o.getClass()) return false;//如果o为空或者它们类型不一致,返回false
            if(!(o instanceof Employee)) return false;
            Employee employee = (Employee) o;//将o强制类型转换
            //防备name和hireDay可能为null的情况
            return Double.compare(employee.salary, salary) == 0
                    && Objects.equals(name, employee.name)
                    && Objects.equals(hireDay, employee.hireDay);
        }
    

    这样就允许o属于一个子类,但是这种方法可能会招致一些麻烦,不建议采用这种方式处理

  3. Java语言规范要求equals方法具有以下特性:

  • 自反性

    对于任意非空引用x,x.equals(x)应该返回true

  • 对称性

    对于任何应用x和y,y.equals(x)返回true时,x.equals(y)也返回true

  • 传递性

    对于任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true

  • 一致性

    如果x和y的引用对象没有发生变化,反复调用x.equals(y)应该返回同样的结果

  • 对于任意非空引用x,x.equals(null)应该返回false

  1. 比较this和o类时:

    如果 e q u a l s 的语义在子类中可以改变时 \textcolor{ForestGreen}{如果equals的语义在子类中可以改变时} 如果equals的语义在子类中可以改变时 就使用 g e t C l a s s 检测 \textcolor{CarnationPink}{就使用getClass检测} 就使用getClass检测

    如果所有的子类都有相同的相等性语义 \textcolor{ForestGreen}{如果所有的子类都有相同的相等性语义} 如果所有的子类都有相同的相等性语义 可以使用 i n s t a n c e o f 检测 \textcolor{CarnationPink}{可以使用instanceof检测} 可以使用instanceof检测

  2. 使用==比较基本类型字段,使用Objects.equals比较对象字段

  3. 对于数组类型的字段,可以使用静态的 A r r a y s . e q u a l s 方法检测相应的数组元素是否相等 \textcolor{CarnationPink}{对于数组类型的字段,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等} 对于数组类型的字段,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等

  4. 常见错误:

    这个方法声明的显示参数类型是Employee,它没有覆盖Object类的equals方法,而是定义了一个完全无关的方法

    要避免这种错误,可以添加@Override注解

    
    public boolean equals(Employee other) {
        return true;
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值