Java 核心技术卷一基础知识第 10 版

#继承

#类、超类和子类

  • 关键字extends表示继承。

  • 在通过扩展超类定义子类的时候,仅需要指出子类与超类的不同之处。

  • 在设计类的时候,应该将通用的方法放在超类中,而将具有特殊用途的方法放在子类中。

  • 子类继承的方法不能直接地访问超类的私有域,可以使用 super 调用超类的方法来重写子类的方法。

    1
    2
    3
    4
    5
    public double getSalary()
    {
    double baseSalary = super.getSalary();
    return baseSalary + bonus;
    }
  • 使用 super 调用构造器的语句必须是子类构造器的第一条语句。

    1
    2
    3
    4
    5
    6
    7
    8
    public Manager(String name, double salary, int year,
    int month, int day)
    {
    super(name, salary, year, month, day);
    // 调用超类 Employee 中含有 n、s、year month
    // 和 day 参数的构造器
    bonus = 0;
    }
  • 如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,则 Java 编译器将报告错误。

  • super 关键字的两个用途:

    1. 调用超类的方法。
    2. 调用超类的构造器。
  • 多态(polymorphism):一个对象变量可以指示多种实际类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Manager boss = new Manager(. . .);
    Employee[] staff = new Employee[3];
    staff[0] = boss;

    // 在这个例子中,变量 staff[0] 与 boss 引用同一个对象。
    // 但编译器将 staff[0] 看成 Employee 对象。 这意味着,
    // 可以这样调用
    boss.setBonus(5000); // OK

    // 但不能这样调用
    staff[0].setBonus(5000); // Error

    // 这是因为 staff[0] 声明的类型是 Employee,
    // 而 setBonus 不是 Employee 类的方法。
  • 动态绑定(dynamic binding):在运行时能够自动地选择调用哪个方法。

  • 可以将一个子类的对象赋给超类变量,但不能将一个超类的引用赋给子类变量。不是所有的水果都是西瓜[1]

  • 假设要调用 x.f(args),隐式参数 x 声明为类 C 的一个对象。

    1. 编译器査看对象的声明类型和方法名。编译器将会一一列举所有 C 类中名为 f 的方法和其超类中访问属性为 public 且名为 f 的方法(超类的私有方法不可访问)。
    2. 编译器将査看调用方法时提供的参数类型。如果在所有名为 f 的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析(overloading resolution)。返回类型不是签名的一部分,因此,在覆盖方法时,一定要保证返回类型的兼容性。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。[1:1]
    3. 如果是 private 方法、static 方法、final 方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式称 为静态绑定(static binding)
    4. 当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与 x 所引用对象的实际类型最合适的那个类的方法。
  • 每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。

  • 动态绑定有一个非常重要的特性:无需对现存的代码进行修改,就可以对程序进行扩展。[2]多了一个新的类之后,比如Executive类也是Employee的子类,那Employee e如果刚好引用一个 Executive 类的对象,就可以自动的调用而不需要修改代码。[1:2]

  • 在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。[1:3]

  • 不允许扩展的类被称为 final 类。

  • final 类中的所有方法自动地成为 final 方法。

  • 如果将一个类声明为 final,只有其中的方法自动地成为 final,而不包括域。

  • 如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程为称为内联(inlining)。例如,内联调用e.getName()将被替换为访问 e.name 域。[1:4]


  • 进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后,使用对象的全部功能。
  • 将一个值存人变量时,编译器将检查是否允许该操作。将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量,必须进行类型转换,这样才能够通过运行时的检査。[1:5]
  • 运行这个程序时,Java 运行时系统将报告这个错误,并产生一个ClassCastException异常。[1:6]
    1
    2
    3
    4
    Manager boss = new Manager(. . .);
    Employee[] staff = new Employee[3];
    staff[0] = boss;
    Manager boss = (Manager) staff[1]; // Error
  • 在进行类型转换之前,先查看一下是否能够成功地转换。
    1
    2
    3
    4
    5
    if (staff[1] instanceof Manager)
    {
    boss = (Manager) staff[1];
    ...
    }
  • 只能在继承层次内进行类型转换。
  • 在将超类转换成子类之前,应该使用 instanceof 进行检查。
  • 在一般情况下,应该尽量少用类型转换和 instanceof 运算符。

  • 包含一个或多个抽象方法的类本身必须被声明为抽象的。
  • 抽象方法充当着占位的角色,它们的具体实现在子类中。
    1
    2
    3
    4
    5
    public abstract class Person
    {
    . . .
    public abstract String getDescription();
    }
  • 抽象类可以包含具体数据和具体方法。

    许多程序员认为,在抽象类中不能包含具体方法。建议尽量将通用的域和方法(不管是否是抽象的)放在超类(不管是否是抽象类)中。

  • 类即使不含抽象方法,也可以将类声明为抽象类。
  • 抽象类不能被实例化。
    1
    new Person("Vinee Vu") // 错误
  • 可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。
    1
    Person p = new Student("Vinee Vu", "Economics");
  • Java 用于控制可见性的 4 个访问修饰符:
    1. 仅对本类可见: private。
    2. 对所有类可见: public。
    3. 对本包和所有子类可见: protected。
    4. 对本包可见: 不需要修饰符。

#Object:所有类的超类

  • Object 类是 Java 中所有类的始祖,在 Java 中每个类都是由它扩展而来的。
  • 可以使用 Object 类型的变量引用任何类型的对象。

编写一个完美的 equals 方法的建议:

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

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

    1
    if (this = otherObject) return true;

    这条语句只是一个优化。实际上,这是一种经常采用的形式。因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多。

  3. 检测 otherObject 是否为 null ,如果为 null ,返回 false。这项检测是很必要的。

    1
    if (otherObject = null) return false;
  4. 比较 this 与 otherObject 是否属于同一个类。如果 equals 的语义在每个子类中有所改变,就使用 getClass 检测:

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

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

    1
    if (!(otherObject instanceof ClassName)) return false;
  5. 将 otherObject 转换为相应的类类型变量:

    1
    ClassName other = (ClassName) otherObject
  6. 现在开始对所有需要比较的域进行比较了。使用 == 比较基本类型域,使用 equals 比较对象域。如果所有的域都匹配,就返回 true;否则返回 false。

    1
    2
    3
    return field1 == other.field1
    && Objects.equals(field2, other.field2)
    && . . . ;

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


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

#范性数组列表

  • 范型数组列表
    1
    ArrayList<Integer> list = new ArrayList<>();
  • 不必指出数组的大小。
  • 使用 add 将任意多的元素添加到数组中。
  • 使用 size() 替代 length 计算元素的数目。
  • 使用 a.get(i) 替代 a[i] 访问元素。
  • 一旦能够确认数组列表的大小不再发生变化,就可以调用 trimToSize 方法。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间。
  • 一旦整理了数组列表的大小,添加新元素就需要花时间再次移动存储块, 所以应该在确认不会添加任何元素时,再调用 trimToSize

  • printf 方法是这样定义的:[1:7]
    1
    2
    3
    4
    public class PrintStream
    {
    public PrintStream printf(String fmt, Object... args) { return format(fmt, args); }
    }
    ...是 Java 代码的一部分,它表明这个方法可以接收任意数量的对象(除 fmt 参数之外)。

  • 在比较两个枚举类型的值时,永远不需要调用 equals, 而直接使用 == 就可以了。

    1
    public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
  • 如果需要的话,可以在枚举类型中添加一些构造器、方法和域。构造器只是在构造枚举常量的时候被调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public enum Size
    {
    SMALL("S"), MEDIUM("M") , LARGE("L") , EXTRA_LARGE("XL");

    private String abbreviation;

    private Size(String abbreviation) { this.abbreviation = abbreviation; }
    public String getAbbreviation() { return abbreviation; }
    }
  • 所有的枚举类型都是 Enum 类的子类。

  • 每个枚举类型都有一个静态的 values 方法,它将返回一个包含全部枚举值的数组。

  • ordinal 方法返冋 enum 声明中枚举常量的位置,位置从 0 开始计数。例如:Size.MEDIUM.ordinal() 返回 1


#反射

  • 反射(reflective):能够分析类能力的程序。[3]

  • 反射机制可以用来:

    • 在运行时分析类的能力。
    • 在运行时查看对象。例如,编写一个 toString 方法供所有类使用。
    • 实现通用的数组操作代码。
    • 利用 Method 对象。
  • 在程序运行期间,Java 运行时系统始终为所有的对象维护一个被称为运行时的类型标识。 这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。

  • Object 类中的 getClass() 方法将会返回一个 Class 类型的实例。

    1
    2
    3
    Employee e;
    ...
    Class cl = e.getClass();
  • 一个 Class 对象将表示一个特定类的属性。最常用的 Class 方法是getName。这个方法将返回类的名字。

  • 可以调用静态方法 forName 获得类名对应的 Class 对象。

    1
    2
    3
    4
    String className = "java.util.Random";
    Class cl = Class.forName(className);
    // 如果类名保存在字符串中,并可在运行中改变,
    // 就可以使用这个方法。
  • 如果 T 是任意的 Java 类型(或 void 关键字),T.class 将代表匹配的类对象。

    1
    2
    3
    Class cl1 = Random.class; // if you import java.util.*;
    Class cl2 = int.class;
    Class cl3 = Double[].class;
  • 一个 Class 对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int不是类,但 int.class 是一个 Class 类型的对象。

  • 虚拟机为每个类型管理一个 Class 对象。因此,可以利用 == 运算符实现两个类对象比较的操作。

    1
    if (e.getClass() == Employee.class) ...
  • newInstance()可以用来动态地创建一个类的实例:

    1
    e.getClass().newInstance();
  • newInstance 方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。

  • java.lang.reflect包中有三个类FieldMethodConstructor分别用于描述类的方法构造器

  • 共有方法:

    • getName方法,用来返回项目的名称。
    • getModifiers方法,返回一个整型数值,用不同的位开关描述publicstatic这样的修饰符使用状况。
  • Field类有一个getType方法,用来返回描述域所属类型的 Class 对象。

  • Class 类中的getFieldsgetMethodsgetConstructors方法将分别返回类提供的 public 域、方法和构造器数组,其中包括超类的公有成员。

  • Class 类的getDeclareFieldsgetDeclareMethodsgetDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    package reflection;

    import java.util.*;
    import java.lang.reflect.*;

    /**
    * This program uses reflection to print all features of a class.
    * @version 1.1 2004-02-21
    * @author Cay Horstmann
    */
    public class ReflectionTest
    {
    public static void main(String[] args)
    {
    // read class name from command line args or user input
    String name;
    if (args.length > 0) name = args[0];
    else
    {
    Scanner in = new Scanner(System.in);
    System.out.println("Enter class name (e.g. java.util.Date): ");
    name = in.next();
    }

    try
    {
    // print class name and superclass name (if != Object)
    Class cl = Class.forName(name);
    Class supercl = cl.getSuperclass();
    String modifiers = Modifier.toString(cl.getModifiers());
    if (modifiers.length() > 0) System.out.print(modifiers + " ");
    System.out.print("class " + name);
    if (supercl != null && supercl != Object.class) System.out.print(" extends "
    + supercl.getName());

    System.out.print("\n{\n");
    printConstructors(cl);
    System.out.println();
    printMethods(cl);
    System.out.println();
    printFields(cl);
    System.out.println("}");
    }
    catch (ClassNotFoundException e)
    {
    e.printStackTrace();
    }
    System.exit(0);
    }

    /**
    * Prints all constructors of a class
    * @param cl a class
    */
    public static void printConstructors(Class cl)
    {
    Constructor[] constructors = cl.getDeclaredConstructors();

    for (Constructor c : constructors)
    {
    String name = c.getName();
    System.out.print(" ");
    String modifiers = Modifier.toString(c.getModifiers());
    if (modifiers.length() > 0) System.out.print(modifiers + " ");
    System.out.print(name + "(");

    // print parameter types
    Class[] paramTypes = c.getParameterTypes();
    for (int j = 0; j < paramTypes.length; j++)
    {
    if (j > 0) System.out.print(", ");
    System.out.print(paramTypes[j].getName());
    }
    System.out.println(");");
    }
    }

    /**
    * Prints all methods of a class
    * @param cl a class
    */
    public static void printMethods(Class cl)
    {
    Method[] methods = cl.getDeclaredMethods();

    for (Method m : methods)
    {
    Class retType = m.getReturnType();
    String name = m.getName();

    System.out.print(" ");
    // print modifiers, return type and method name
    String modifiers = Modifier.toString(m.getModifiers());
    if (modifiers.length() > 0) System.out.print(modifiers + " ");
    System.out.print(retType.getName() + " " + name + "(");

    // print parameter types
    Class[] paramTypes = m.getParameterTypes();
    for (int j = 0; j < paramTypes.length; j++)
    {
    if (j > 0) System.out.print(", ");
    System.out.print(paramTypes[j].getName());
    }
    System.out.println(");");
    }
    }

    /**
    * Prints all fields of a class
    * @param cl a class
    */
    public static void printFields(Class cl)
    {
    Field[] fields = cl.getDeclaredFields();

    for (Field f : fields)
    {
    Class type = f.getType();
    String name = f.getName();
    System.out.print(" ");
    String modifiers = Modifier.toString(f.getModifiers());
    if (modifiers.length() > 0) System.out.print(modifiers + " ");
    System.out.println(type.getName() + " " + name + ";");
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    $ java reflection.ReflectionTest
    Enter class name (e.g. java.util.Date):
    java.util.List
    public abstract interface class java.util.List
    {

    public abstract void add(int, java.lang.Object);
    public abstract boolean add(java.lang.Object);
    public abstract boolean remove(java.lang.Object);
    public abstract java.lang.Object remove(int);
    public abstract java.lang.Object get(int);
    public abstract boolean equals(java.lang.Object);
    public abstract int hashCode();
    public static java.util.List copyOf(java.util.Collection);
    public abstract int indexOf(java.lang.Object);
    public abstract void clear();
    public abstract int lastIndexOf(java.lang.Object);
    public abstract boolean isEmpty();
    public void replaceAll(java.util.function.UnaryOperator);
    public abstract int size();
    public abstract java.util.List subList(int, int);
    public abstract [Ljava.lang.Object; toArray([Ljava.lang.Object;);
    public abstract [Ljava.lang.Object; toArray();
    public abstract java.util.Iterator iterator();
    public static java.util.List of(java.lang.Object, java.lang.Object);
    public static java.util.List of(java.lang.Object);
    public static java.util.List of();
    public static java.util.List of(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object);
    public static java.util.List of(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object);
    public static java.util.List of(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object);
    public static java.util.List of(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object);
    public static java.util.List of(java.lang.Object, java.lang.Object, java.lang.Object);
    public static java.util.List of(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object);
    public static java.util.List of(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object);
    public static transient java.util.List of([Ljava.lang.Object;);
    public static java.util.List of(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object);
    public abstract boolean contains(java.lang.Object);
    public java.util.Spliterator spliterator();
    public abstract boolean addAll(int, java.util.Collection);
    public abstract boolean addAll(java.util.Collection);
    public abstract java.lang.Object set(int, java.lang.Object);
    public abstract boolean containsAll(java.util.Collection);
    public abstract boolean retainAll(java.util.Collection);
    public abstract boolean removeAll(java.util.Collection);
    public void sort(java.util.Comparator);
    public abstract java.util.ListIterator listIterator();
    public abstract java.util.ListIterator listIterator(int);

    }
  • 反射机制的默认行为受限于 Java 的访问控制。

  • 将一个Employee[]临时地转换成Object[]数组,然后再把它转换回来是可以的,但一从开始就是Object[]的数组却永远不能转换成Employe[]数组。

  • Array 类中的静态方法newInstance,它能够构造新数组。在调用它时必须提供两个参数,一个是数组的元素类型,一个是数组的长度。

    1
    Object newArray = Array.newInstance(componentType, newLength);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * This method grows an array by allocating a new array of the same type and
    * copying all elements.
    * @param a the array to grow. This can be an object array or a primitive
    * type array
    * @return a larger array that contains all elements of a.
    */
    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;
    }
  • 在 Method 类中有一个invoke方法,它允许调用包装在当前 Method 对象中的方法。

  • invoke方法的签名是:

    1
    Object invoke(Object obj, Object... args)
  • 第一个参数是隐式参数,其余的对象提供了显式参数。对于静态方法,第一个参数可以被忽略,即可以将它设置为null

  • invoke的参数和返回值必须是Object类型的。


  • 继承的设计技巧
    1. 将公共操作和域放在超类
    2. 不要使用受保护的域
    3. 使用继承实现 is-a 关系
    4. 除非所有继承的方法都有意义,否则不要使用继承
    5. 在覆盖方法时,不要改变预期的行为
    6. 使用多态,而非类型信息
    7. 不要过多地使用反射

  1. 20200815 更新,看【狂神说 Java】Java 零基础学习视频通俗易懂再回来看看。 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. 其实没太懂。 ↩︎

  3. 回来学反射 ↩︎