5 继承

5.1 类、超类和子类

5.1.1 定义子类

超类(superclass)和子类(subclass),

基类(base class)和派生类(derived class),

父类(parent class)和孩子类(child class)

在 Java 中,所有的继承都是公有继承。

5.1.2 覆盖方法(略)

5.1.3 子类构造器

this是当前对象的引用

super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。

我们可以通过 super 实现对超类构造器的调用。使用 super 调用构造器的语句必须是子类构造器的第一条语句。

如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,则 Java 编译器将报告错误。

5.1.4 继承层次

继承层次继承链

Java 不支持多继承。

5.1.5 多态

一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)

在 Java 中,不需要将方法声明为虚函数(C++)。动态绑定是默认的处理方式。如果不希望让一个方法具有虚拟特征,可以将它标记为 final 。

5.1.6 理解方法调用

覆盖方法时,一定要保证返回类型的兼容性。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。(协变返回类型)

重载可以有不同的返回类型。

在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。

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

private 方法、static 方法、final 方法或者构造器,是静态绑定

详见P155

5.1.7 阻止继承:final类和方法

final 类不允许extends,final 方法不允许Override;

final 类中的所有方法自动称为 final 方法,而域不会。

如果方法很简短、被频繁调用且没有被覆盖,那么即时编译器就会将这个方法进行内联处理。

例如,内联调用 e.getName() 将被替换为访问 e.name域。

如果虚拟机加载了另外一个子类,而在这个子类中包含了对内联方法的覆盖,那么优化器将取消对覆盖方法的内联。这个过程很慢,不过很少发生。

5.1.8 强制类型转换

如果试图在继承链上进行向下的类型转换,并且“谎报”有关对象包含的内容,运行这个程序时,Java runtime system 将报告这个错误,并产生一个 ClassCastException 异常。

Manager boss = (Manager)staff[1]; // Error
// 在进行类型转换之前,先查看一下是否能够成功地转换
if (staff[1] instanceof Manager)
{
boss = (Manager)staff[1];
}
// 将会产生编译错误,这是因为 String 不是 Employee 的子类
String c = (String)staff[1];
  • 只能在继承层次内进行类型转换。
  • 在将超类转换成子类之前,应该使用 instanceof 进行检查

5.1.9 抽象类

  • 包含一个或多个抽象方法的类本身必须被声明为抽象的;
  • 除了抽象方法之外,抽象类还可以包含具体数据和具体方法;
  • 类即使不含抽象方法,也可以将类声明为抽象类。
  • 抽象类不能被实例化。
  • 可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。

5.1.10 受保护访问

人们希望超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域。

为此,需要将这些方法或域声明为 protected。

例如,如果将超类 Employee 中的 hireDay 声明为proteced,而不是私有的,Manager 中的方法就可以直接地访问它。

不过,Manager 类中的方法只能够访问 Manager 对象中的 hireDay 域,而不能访问其他 Employee 对象中的这个域 。

事实上,Java 中的受保护部分对所有子类及同一个包中的所有其他类都可见。

包访问级别(package-private):没有访问修饰符的类的访问级别是包级,其public方法也会变成包级。

Java 用于控制可见性的 4 个访问修饰符:

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

5.2 Object:所有类的超类

在 Java 中,只有基本类型不是对象。

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

5.2.1 equals方法

在 Object 类中,这个方法将判断两个对象是否具有相同的引用。

如果两个参数都为 null,Objects.equals(a, b) 调用将返回 true;

如果其中一个参数为 null,则返回 false;

否则,如果两个参数都不为 null,则调用 a.equals(b)。

在子类中定义 equals 方法时,首先调用超类的 equals。如果检测失败,对象就不可能相等。

如果超类中的域都相等,就需要比较子类中的实例域。

5.2.2 相等测试与继承

getClass() or instanceOf ?

  1. 显式参数命名为 otherObject , 稍后需要将它转换成另一个叫做 other 的变量 。

  2. 检测 this 与 otherObject 是否引用同一个对象:

    if (this == otherObject) return true;

  3. 检测 otherObject 是否为 null , 如果为 null , 返回 false:

    if (otherObject == null) return false;

  4. 比较 this 与 otherObject 是否属于同一个类

    1. 如果 equals 的语义在每个子类中有所改变,就使用 getClass 检测:

      if (getClass() != otherObject.getClass()) return false;

    2. 如果所有的子类都拥有统一的语义,就使用 instanceof 检测:

      if (!(otherObject instanceof ClassName)) return false;

  5. 将 otherObject 转换为相应的类类型变量:

    ClassName other = (ClassName) otherObject;

  6. 现在开始对所有需要比较的域进行比较了。使用 == 比较基本类型域,使用 equals 比较对象域。

    如果所有的域都匹配,就返回true;否则返回 false。

    return field1 == other.field1

    && Objects.equals(field2, other.field2)

    && . . .;

    如果在子类中重新定义 equals,就要在其中包含调用 super.equals(other)。

  • 对于数组类型的域,可以使用静态的 Arrays.equals 方法检测相应的数组元素是否相等。
  • 覆盖 Object 类中的equals方法时,显示参数类型应该是 Object。否则其结果并没有覆盖 Object 类的 equals 方法,而是定义了一个完全无关的方法。

5.2.3 hashCode方法

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

如果重新定义 equals 方法,就应该重新定义 hashCode 方法;

equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true,那么 x.hashCode() 就必须与 y.hashCode() 具有相同的值。

Objects.hashCode,如果其参数为 null,这个方法会返回 0,否则返回对参数调用 hashCode 的结果;

需要组合多个散列值时,可以调用Objects.hash并提供多个参数。这个方法会对各个参数调用 Objects.hashCode,并组合这些散列值。

public int hashCode()
{
return 7 * Objects.hashCode(name)
+ 11 * Double.hashCode(salary)
+ 13 * Objects.hashCode(hireDay);
}
public int hashCode()
{
return Objects.hash(name, salary, hireDay);
}

可以使用静态方法 Double.hashCode 来避免创建 Double 对象;

如果存在数组类型的域,那么可以使用静态的 Arrays.hashCode 方法计算一个散列码,这个散列码由数组元素的散列码组成。

5.2.4 toString方法

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

int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 };
String s = "" + luckyNumbers; // s = "[I@1a46e30", [I 表示类型为整型数组
String s = Arrays.toString(luckyNumbers); // s = "[2, 3, 5, 7, 11, 13]"

① class A extends B;

② B.toString(){ getClass().getName(); }

③ A.toString(){ super.toString() + "[this is A]"; }

调用 A.toString 时调用的 “getClass.getName” 会是 A 而不是 B

5.3 泛型数组列表(略)

5.4 对象包装器与自动装箱

对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。

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

Integer 对象是不可变的:包含在包装器中的内容不会改变。这些包装器类不能用来实现修改数值参数的方法(像 C++ 的引用那样)。

如果想编写一个修改数值参数值的方法,就需要使用在 org.omg.CORBA 包中定义的持有者(holder)类型,

包括 IntHolder、 BooleanHolder 等。每个持有者类型都包含一个公有域值,通过它可以访问存储在其中的值。

public static void triple(IntHolder x)
{
x.value = 3 * x.value;
}

java.lang.Integer

/// API
int value();
static String toString(int i);
static String toString(int i, int radix);
static int parseInt(String s);
static int parseInt(String s, int radix);
static Integer valueOf(String s);
static Integer valueOf(String s, int radix);

java.text.NumberFormat

///API
Number parse(String s);

5.5 参数数量可变的方法

参数里Object[] argsObject... args等价。

System.out.printf("%d %s", new Object[] { new Integer(n), "widgets" } );
public static double max(double... values){}
public static void main(String... args){}

例如调用double m = max(3.1, 40.4, -5);,编译器会传递new double[] { 3.1, 40.4, -5 }进去。

5.6 枚举类

public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
/// 添加构造器、方法和域
public enum Size {
SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL"); private Size(String abbreviation) {
this.abbreviation = abbreviation;
} public String getAbbreviation() {
return abbreviation;
} private String abbreviation;
}

5.7 反射

主要是一些类及其API

java.lang.Class

/// API
static Class forName(String className);
Object newInstance();

java.lang.reflect.Field

java.lang.reflect.Method

java.lang.reflect.Constructor

/// API
Object newInstance(Object[] args);

java.lang.reflect.Modifier

  • 使用java.lang.reflect.Array来实现通用的数组拷贝
public static Object goodCopyOf(Object a, int newLength)
{
Class cl = a.getClass();
if (!cl.isArray()) return null;
Class componentType = cl.getComponentType();
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
  • 使用java.lang.reflect.Method调用任意方法:

    public Object invoke(Object implicitParameter, Object[] explicitParameters)

    建议仅在必要的时候才使用 Method 对象。

    特别要重申:建议 Java 开发者不要使用 Method 对象的回调功能。使用接口进行回调会使得代码的执行速度更快,更易于维护。

5.8 继承的设计技巧(略)

【阅读笔记】Java核心技术卷一 #3.Chapter5的更多相关文章

  1. 【阅读笔记】Java核心技术卷一 #0

    这是一篇备忘性质的读书笔记,仅记录个人觉得有用的知识点 本文作为一个目录索引,部分章节跳过 吐槽:此书中文翻译有不少地方不太通顺,这种情况我要把英文版对应的部分也读一遍才能明白(说实话,英文里的从句表 ...

  2. java核心技术卷一

    java核心技术卷一 java基础类型 整型 数据类型 字节数 取值范围 int 4 +_2^4*8-1 short 2 +_2^2*8-1 long 8 +_2^8*8-1 byte 1 -128- ...

  3. 对《Java核心技术卷一》读者的一些建议

    <Java核心技术卷一>是唯一可以和<Java编程思想>媲美的一本 Java 入门书.单从技术的角度来看,前者更好一些.但上升到思想层面嘛,自然后者更好,两者的偏重点不同. 思 ...

  4. 读《java核心技术卷一》有感

    过去一个多月了吧.才囫囵吞枣地把这书过了一遍.话说这书也够长的,一共706页.我从来不是个喜欢记录的人,一直以来看什么书都是看完了就扔一边去,可能有时候有那么一点想记录下来的冲动,但算算时间太紧,很多 ...

  5. 【阅读笔记】Java核心技术卷一 #6.Chapter8

    8 泛型程序设计 8.1 为什么要使用泛型程序设计 类型参数(type parameters)(E.T.S...) 通配符类型(wildcard type)(?) 注意这两者用法用处并不同. 8.2 ...

  6. 【阅读笔记】Java核心技术卷一 #5.Chapter7

    7 异常.断言和日志 在 Java 中,如果某个方法不能够采用正常的途径完整它的任务,就可以通过另外一个路径退出方法. 在这种情况下,将会立刻退出,并不返回任何值,而是抛出(throw)一个封装了错误 ...

  7. 【阅读笔记】Java核心技术卷一 #4.Chapter6

    6 接口.lambda 表达式与内部类 6.1 接口 6.1.1 接口概念 接口绝不能含有实例域:但在接口中可以定义常量,被自动设为 public static final 接口中的所有方法自动地属于 ...

  8. 【阅读笔记】Java核心技术卷一 #2.Chapter4

    4 对象和类 4.1 面向对象程序设计概述(略) 4.2 使用预定义类 java.time.LocalDate static LocalDate now(); static LocalDate of( ...

  9. 【阅读笔记】Java核心技术卷一 #1.Chapter3

    3 Java的基本程序设计结构 3.1 一个简单的 Java 应用程序(略) 3.2 注释(略) 3.3 数据类型 8种基本类型 byte,short,int,long float,double ch ...

随机推荐

  1. Redis 面试题 - 收藏版 (持续更新、吐血推荐)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  2. 【题解】Luogu p3478 [POI2008]STA-Station 动态规划

    题目描述 给出一个$N$个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大 输入输出格式 输入格式 第一行一个数$n$,表示树上共有$n$个点接下来$n-1$行,表示$n-1$条边;每行 ...

  3. 【codeforces841A】Generous Kefa

    原题 A. Generous Kefatime limit per test:2 secondsmemory limit per test:256 megabytes input:standard i ...

  4. 【数论】8.30题解-prime素数密度 洛谷p1835

    prime 洛谷p1835 题目描述 给定区间[L, R](L <= R <= 2147483647, R-L <= 1000000),请计算区间中 素数的个数. 输入输出 输入 两 ...

  5. kafka简单介绍

    Kafka是分布式发布-订阅消息系统.它最初由LinkedIn公司开发,之后成为Apache项目的一部分.Kafka是一个分布式的,可划分的,冗余备份的持久性的日志服务.它主要用于处理活跃的流式数据. ...

  6. 关于Ubuntu的超级管理员Root的切换及初始密码设置

    背景介绍 总有一些操作,可能需要更高的超级管理员权限才能进行,甚至才可见有些文件,所以在Linux中我们需要切换到Root用户,也就是对应的Windows的Administrator账户. 从当前用户 ...

  7. 41、shell编程基础

    bash的变量默认都是全局变量,脚本内都可以调用,无论在什么位置(函数体中也一样),即函数体外可以调用函数体内的变量: local一般用于局部变量声明,多在函数体内使用: 如果要变为局部变量,则要使用 ...

  8. AcWing 1143. 联络员

    Tyvj已经一岁了,网站也由最初的几个用户增加到了上万个用户,随着Tyvj网站的逐步壮大,管理员的数目也越来越多,现在你身为Tyvj管理层的联络员,希望你找到一些通信渠道,使得管理员两两都可以联络(直 ...

  9. 『无为则无心』Python函数 — 27、Python函数的返回值

    目录 1.返回值概念 2.return关键字的作用 3.返回值可以返回的数据类型 4.函数如何返回多个值 5.fn5 和 fn5()的区别 6.总结: 1.返回值概念 例如:我们去超市购物,比如买饮料 ...

  10. php加密压缩文件

    前言 近几日,用爬虫采集的了一些数据,存放到硬盘中,随着数据量越来越多,所以想上传到网盘当中,可是不加下密又觉得不放心, 所以开始用PHP的zip模块进行压缩加密. 开始 $zipArc = new ...