第5章 继承

1. 父类和子类

  扩展父类时,仅需要指出子类与父类的不同之处,也可以通过重写(动态(单)分派:根据调用者的实际类型决定调用的方法)覆盖父类方法。

  子类可以继承父类的私有域,但是只能通过父类方法访问此私有域,若本身覆盖了父类的访问方法,则通过关键字 super 表示调用的是父类方法。 super 两个用途:1、调用父类方法 2、调用父类构造器

  

public class father {
    private int age = 40;
    String name = "jj"; 
    
    public int getAge() {
        return age;
    }
}

class son extends father{
    public int getAge() {
       return age; // error 字段 father.age 不可视
        // return super.getAge();
    }
        
        public String getName(){
             return name;  
        }
}   

  在子类中可以增加域,增加/覆盖方法,但绝对不能删除继承的域和方法。

2. 多态

  静态类型为父类的对象引用既可以引用本身的对象,也可以引用子类对象。反之不成立。

class Father{};
class Son extends Father{};

Father father = new Son();      // Yes
Son son = new Father();     // No

  (JVM)在确定方法调用的版本时:

  非虚方法(invokespecial, invokestatic指令调用的方法,例如静态方法,私有方法,父类方法,实例初始化方法 以及 final修饰的方法)编译期可知,运行期不可变,所以编译器确定类型,在类加载的解析阶段就会把符号引用转化为直接引用,称为静态调用(解析)。

  虚方法(invokevirtual等指令)在运行期才会确定调用方法的版本(根据接受者的实际类型)此时称为(分派)。

  分派分为静态分派和动态分派:

  • 静态分派:依据接受者方法参数静态类型确定调用方法的版本,属于多分派。静态分派发生在编译期  
  • 动态分派:依据接受者实际类型确定调用方法的版本,属于单分派。动态分派发生在执行期

  invokevirtual指令执行过程:

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
  2. 如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。
  3. 否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
  4. 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

3. 方法表

  每次调用方法都要进行搜索,为此,虚拟机预先为每个类创建了方法表,列出了方法的签名和实际调用的方法。  

4. 阻止继承

  不允许扩展的类称为 final 类,类中方法声明为 final 表示子类不能覆盖这个方法。若将一个类声明为 final ,类中方法自动成为 final 方法, 但不包括域。

5. 抽象类

  包含一个或多个抽象方法的类本身必须声明是抽象的,当然,抽象类也可以包含具体的方法和具体的数据。子类继承抽象类后,除非定义全部的抽象方法,否则,依然需要声明为抽象类。

  类即使不包含抽象方法,也可以声明为抽象类。

  抽象类不能实例化。但可以定义一个抽象类的对象引用,引用非抽象的子类实例。

public abstract class person{
    public int age;
    public abstract int getAge(){}
}

public student extends person{
    public int getAge(){
        return age;
    }
}


person p = new person(); // error
person p = new student(); // true

6. 访问修饰符

  1. private 仅对本类可见
  2. protect 对本包和所有子类可见
  3. public 对所有类可见
  4. default (默认)对包可见,不需要修饰符

7. equals

  Object 类的equals()方法判断两个对象引用是否指向同一个对象。

public boolean equals (Object x){
    return this == x;
}
// java 中 == 判断两个引用是否指向同一对象

8. hash code

  hashCode 方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。

  此外,equals() 与 hashCode() /*必须结果统一(有误)*/,如果 x.equals(y) 返回 true, 则 x.hashCode() 和 y.hashCode() 必须相同。 (2018-03-30) 

  1. 如果两个对象equals比较返回true,则两个对象的hashCode必须相同
  2. 如果equals返回false,两个对象的hashCode可以相同,例如 HashMap

9. toString

  Object类定义了 toString 方法, 用来打印输出对象所属的类名和散列码。

10. 对象包装器与自动装箱

  Integer、Long、Float、Double、Short、Byte、Character、Void、Boolean

  包装器类是不可变的,同时,包装器类修饰关键字为final。

  ArrayList<Integer> list not ArrayList<int> list ,尖括号中的参数类型不允许是基本类型。

  自动装箱:list.add(3); ==> list.add(Integer.valueOf(3));

  自动拆箱:int a = list.get(i);  ==> int a = list.get(i).intValue();   

Integer a = 1000;
Integer b = 1000;
System.out.printlen(a==b);  // return false

Integer a = 100;
Integer b = 100;
System.out.printlen(a==b);  // return true

  介于 -128~127 之间的 short 和 int 被包装到固定的对象中,即指向同一个对象。

11. Class类

  在程序运行期间,java运行时系统始终为所有对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。保存这些信息的类称为Class。

12. 继承的技巧

  1. 将公共操作和域放在超类
  2. 不要使用受保护的域
  3. 除非所有继承的方法都有意义,否则不要使用继承

13. 多继承

  Java不允许类的多继承,是为了避免菱形继承的问题。设想两个类定义了签名(方法名和入参及入参顺序)相同的方法,那么同时继承这两个类,调用这个方法的时候,编译期犯迷糊了,到底想要哪个父类的方法呢。在C++中,使用虚继承解决这个问题,Java秉持简单的原则,放弃了类的多继承。

  那Java中有多继承吗?

  有的,Java中接口是可以多继承的。

  那会产生菱形问题吗?

  会也不会,在Java8之前,接口只能定义方法,具体的实现交给实现类完成,如果多个接口定义了签名相同的方法,没有关系,反正只是定义而已,实现类只需要实现一次即可。而在Java8中,接口可以定义default方法,即在接口中定义方法实现。这种情况就跟类的多继承一样,会出现菱形继承问题了。

  我们引申一下,为什么Java8需要引入default方法机制呢?

  试想一下,一个接口有千万的实现类,或者打成jar包,提供给别人使用。突然有一天,接口添加了一个新方法,所有的实现类都需要实现这个方法,这将是灾难性的。而default方法正如其名,是一个默认方法,接口添加后,实现类可以不实现。不实现则使用接口的默认实现。

人生就像蒲公英,看似自由,其实身不由己。
原文地址:https://www.cnblogs.com/walker993/p/8666094.html