《Java核心技术 卷Ⅰ》第五章 继承

235 阅读11分钟

继承

  • 基于已存在的类构造一个新类,继承已存在的类就是复用(继承)这些类的方法和域 通用的方法在超类中,特殊用途的方法放在子类中

  • 继承层次

  1. 由一个公共超类派生出来的所有类的集合被称为 继承层次
  2. 从某一个特定的类到其祖先的路径被称为该类的 继承链
  • 判断是否应该设计为继承关系的简单规则
  1. is - a规则 子类的每个对象也是超类的对象
  2. 置换法则 程序中出现超类对象的任何地方都可以用子类的对象置换
  • 将方法或类声明为 final 主要目的是:确保他们不会在子类中改变语义

  • String 类也是 final

  • 方法没有被覆盖并且很短,编译器能够对它进行优化处理,这被称为 内联(inlining)

  • 即时编译器可以知道类之间的继承关系,并能够检测出类中是否真正地存在覆盖给定的方法。

多态

  • 一个特定类型的对象引用可以指向不同类型的对象实例

  • 动态绑定 :在运行时依赖于隐式参数的实际类型,(在对程序进行扩展时,无须对现存的代码进行修改)

  • 静态绑定 :有 final static private修饰的方法,在编译期间就可以准确地知道应该调用哪个方法

  • ArrayStoreException 数组存放错误对象异常

  • 方法调用步骤

  1. 编译器检测调用方法的 声明类型 和 方法名 编译器将会列举出C类中所有名为 f 的方法和其超类中访问属性为 public 且名为 f 的方法 (超类的私有方法不可访问)
  2. 查看调用方法时提供的参数类型,这被称为 重载解析(参数类型匹配)
  3. 如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,会报错。

方法的名字和参数列表被称为方法签名

  • 方法表 虚拟机预先为每个类创建了一个 方法表(method table),列出了所有方法的签名和实际调用的方法。

  • 动态绑定的解析过程

  1. 提取类型的方法表(也可能是当前类的其他子类的方法表)
  2. 搜索要调用的方法签名的类(在方法表中搜索要调用的的方法签名的类,此时虚拟机已经知道应该调用哪个方法)
  3. 调用方法
  • 强制类型转换
  1. 超类引用赋给子类变量(暂时忽视对象的实际类型之后,使用对象的全部功能)
  2. 使用子类中特有的方法
  • instanceof 查看对象之间是否存在继承关系引用关系

  • 强制类型转换 须知

  1. 在继承层次内进行类型转换
  2. 转换之前,应该使用 instanceof 进行检查

注:应该尽量少用 类型转换 和 instanceof运算符

抽象

  • 包含一个或多个抽象方法的类本身必须被声明为抽象的。

  • 上层的类更具有通用性,更加抽象,祖先类更加通用。

  1. 抽象方法的具体实现在子类中。(子类若不实现父类的抽象方法,也必须被定义为抽象类)
  2. 抽象类不能被实例化
  3. 定义一个抽象类的对象变量,引用非抽象子类的对象

Object : 所有类的超类

equals方法

四大特性

  1. 自反性 :对于任何非空引用 x ,x.equals(x)应该返回 true.
  2. 传递性 :对于任何引用 x 、y、z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true, x.equals(z) 也应该返回 true.
  3. 对称性 :对于任何引用 x 和 y,当且仅当 y.equals(x) 返回 true,x.equals(y) 也应该返回 ture.
  4. 一致性 :如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果.
  5. 对于任意非空引用 x ,x.equals(null) 应该返回 false.
  • 编写 equals 方法的建议
  1. 是否引用同一个对象
if(this == otherObject)return true; //要比一个一个地比较类中的域所付出的代价小得多
  1. 是否为 null
if(otherObject == null)return false;
  1. 比较是否属于同一个类
if(getClass() != otherObject.getClass())return false;
  1. 使用 instanceof 检测 (保证要比较的类型都拥有统一的语义)
if(!(otherObject instanceof ClassName))return false;
  1. 转换为相同的类类型变量 ClassName other = (ClassName) otherObject
  2. 开始对需要比较的域进行比较(使用 == 比较基本类型域,使用 equals 比较对象域)
return field1 == other.field1 && Objects.equals(field2,other.field2) && ...;
  • 数组类型的域,使用静态的 Arrays.equals 方法

  • Java7 以后提供了 Objects 类 提供了 静态equals方法 进行比较,如果 a 和 b 都为 null ,返回 true ; 其中一个为 null ,则返回 false; 否则返回 a.equals(b), 使用此种方式进行比较,可防止空指针异常

hashCode方法

  1. 整数值(包括整数和负数)
  2. 没有规律
  3. 对象的存储地址
  4. 重写 equals 方法的同时,也必须重写 hashCode 方法(将对象插入到散列表中)
  5. 定义必须一致(eg: equals 比较ID , hashCode 就需要散列 ID)
  6. equals 相同 hashCode 一定相同 ;hashCode 相同,equals 则不一定相同
  • Objects.hashCode方法 防止空指针异常。

  • 调用 JDK1.7 中的 Objects.hash方法 ,并提供多个参数,可组合多个散列值。

  • 数组类型的域,使用静态的 Arrays.hashCode方法 来计算散列码。

toString方法

  • 用于返回表示对象值的字符串。

  • getClass().getName() 获得类名的字符串。

  • 只要对象与一个字符串通过操作符 “+” 连接起来,Java编译器就会自动地调用 toString 方法,以便获得这个对象的字符串描述。

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

  • 数组的字符串打印

Arrays.toString 方法 打印多维数组 Arrays.deepToString 方法

  • 建议为自定义的每一个类增加 toString 方法。

泛型数组列表 ArrayList

  • 动态列表

  • ArrayList 是一个采用 类型参数(type parameter)泛型类(generic class)

  • Java SE 7 中,可以省去右边的类型参数,这被称为 "菱形" 语法,编译器会检查这个变量、参数或方法的泛型类型,然后将这个类型放在 <> 中。 ArrayList<User> list = new ArrayList<>();

  • 如果调用 add 且内部数组已经满了,数组列表就将自动创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。

  • ensureCapacity方法直接指定数量 的方式可有效降低开销。

  • trimToSize方法:垃圾收集器将 回收多余的存储空间 ,应该在确认 不会添加任何元素时,再调用 trimToSize方法

  • 访问数组元素

  • 使用 add方法 为数组添加新元素,而不要使用 set方法 ,它只能替换数组中已经存在的元素内容。
  • 原始的 ArrayList类 只能是 Object 类,必须对返回值进行类型转换,存在一定的危险性。
  • 使用ArrayList<>泛型类 会对内部的数组类型进行检测 ,当加入集合的元素类型不一致时,编译器会出现一个警告。
  • 在数组列表的中间插入元素,使用带索引参数的 add方法int n = staff.size()/2; staff.add(n,e);

  • 插入一个新元素,位于 n 之后的所有元素都要向后移动一个位置。此时,若数组列表的大小超过了容量,数组列表就会重新分配存储空间。

  • 与之类似,当从数组列表中间删除一个元素,位于这个位置之后的所有元素都向前移动一个位置,并且数组的大小减 1。

  • 泛型设计是 Java语言糖,在程序运行时,所有的数组列表都是一样的,即没有虚拟机中的类型参数。因此,类型转换(ArrayList)和(ArrayList)将执行相同的运行时检查。所以,在使用原始的列表类型时,出现警告,可以不必担心,这并不会造成严重的后果。

对象包装器与自动装箱

  • 所有的基本类型都有一个与之对应的类。这些类称为 包装器(wrapper)

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

    Void : 表示空类

  • 对象包装器都是 final,因此不能定义它们的子类。

  • 泛型类的尖括号<>中的类型参数不允许是基本类型

  • ArrayList<Integer> 的效率远远低于 int[] 数组,应该用它构建 小型集合,此时程序员操作的方便性要比执行效率更加重要。

自动装箱 自动拆箱
基本类型==》包装类 包装类==》基本类型
  • 两个包装器对象比较时调用 equals 方法。

  • 包装器注意事项

  • 包装器类引用可以为 null ,所以自动装箱有可能会抛出一个 NullPointerException异常
  • 如果在一个条件表达式中混合使用 IntegerDouble 类型,Integer 值就会拆箱,提升为 double,再装箱为Double
  • 装箱和拆箱是编译器认可的,而不是虚拟机

  • parseInt 是一个Integer包装类的静态方法。

  • 包装类是 final 不可变类,包装在包装器中的内容不会改变。

  • 修改数值参数值的方法,使用 org.omg.CORBA 包中定义的 (持有者)holder 类型,包括 IntHolder BooleanHolder

  • Number parse(String s) 字符串转数值

不定长参数的方法

  • printf方法 接收两个参数,一个是 格式字符串,另一个是 Object[]数组,数组内存放着提供的参数,若是其内部为整型数组或者其他基本类型的值,将自动装箱为包装类型,扫描 fmt ,并将第 i 个格式说明符与 args[i] 的值匹配。

  • Object... 参数类型与 Object[] 完全一样。

  • 最后一个参数是数组的方法可重新定义为可变参数的方法,并不会破坏任何已经存在的代码。

枚举类

  • 所有的枚举类都是 Enum类 的子类。
  • 静态的 values 方法,返回一个包含全部枚举值的数组。
  • Enum 类省略了一个类型参数,实际上 ,枚举类型 应该 是 Enum<T> 这种形式。

反射

  • 能够分析类能力的程序称为反射(reflective)
  1. 在运行时分析类的能力
  2. 在运行时查看对象
  3. 实现通用的数组操作代码
  4. 利用 Method对象,这个对象很像 C++ 中的函数指针。
  • 获得Class类的 3种方式
  1. Object 类中的 getClass 方法
  2. 对象类的 class 属性
  3. Class 类的静态方法 forName方法 ,获得类名字符串,从而得到对应的 Class 对象
  • 调用 forName方法 时,只有在 className 是类名或者接口名时才能够执行。否则,forName 方法将抛出一个 checked exception (检查异常),在使用 forName 方法是,需要提供一个异常处理器。

  • newInstance 方法 ,实现动态地创建一个类的实例。 调用的是默认的构造器,若此时没有默认的构造器,就会抛出一个异常。

  • 异常分为 运行时异常检查时异常检查时异常 强制规定在程序中必须被捕获。

  • Modifier类 获取 修饰符返回类型Modifier类 的静态方法 isPublic isPrivate isFinal 等方法可判断 方法或者构造器是否 为相应的 访问级别。

  • 在运行时改变对象的状态

  1. 使用反射获取到类的 数据域 后 ,针对 访问级别不够的数据域,要执行 setAccessible方法 ,指定参数为 ture ,方可对相应的数据域进行处理。f.setAccessible(true) setAccessible方法AccessibleObject类 中的一个方法。它是 Field Method Constructor 类的公共超类。
  2. 基本数据类型 使用 getDouble 此形式的方法进行获取;对象类型 使用 get 方式获取。
  3. 设置:调用 f.set(obj,value) 可以将 obj 对象的 f 域设置成新值 ,相对应的 基本数据类型 ,使用 setXXX 方法进行设置。
  • 编写泛型数组代码
  1. Array 类允许动态地创建数组
  2. Array 类中的静态方法 newInstance方法能够构造新数组,必须提供两个参数,一个是数组的 元素类型,一个是数组的长度

Object newArray = Array.newInstance(componentType,newLength);

  • 获得数组的长度

Array.getLength(a) 获得数组的长度,Array 类的静态 getLength方法 的返回值得到任意数组的长度。

  • 获得数组的元素类型
  1. 首先获得 a 数组的类对象 Class clazz = arr.getClass();
  2. 确认它是一个数组 if (clazz.isArray()) {System.out.println("arr是数组");}
  3. 使用 Class类getComponentType方法 确定数组对应的类型
  • System.arraycopy方法 数组拷贝,常用于扩容。

  • public Object invoke(Object implicitparameter,Object[] explicitParamenters) 反射中 Method类的 执行方法,参数为 对象方法参数,返回方法的返回值,方法若没有参数,第二个参数可以使用 null 作为隐式参数传递,在使用包装器传递基本类型的值时,基本类型的返回值必须是未包装的。