读后笔记 -- Java核心技术(第10版 卷I ) Chapter5 继承


Chapter5 继承

5.1 类、超类和子类

  • 继承:基于已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。
  • 反射:在程序运行期间发现更多的类及其属性的能力

1. 定义子类

public class Manager extends Employee
{
    添加的方法和域    // Java 所有的继承都是公有继承
}

Employee:超类(superclass)、基类(base class)、       父类(parent class)

Manager:  子类(subclass)、   派生类(derived class)、孩子类(child class) 

2. 子类覆盖方法

public class Manager extends Employee
{
    private double bonus;
    public double getSalary()
    {
         double baseSalary = super.getSalary();     // 调用超类 Employee 的 getSalary()方法
         return baseSalary + bonus;
    }
}

 在子类中可以 增加域、 增加方法 或 覆盖超类 的方法,但绝不能删除继承的任何域和方法。

3. 子类构造器

public class Manager extends Employee {
    private double bonus;

    public Manager(String name, double salary, int year, int month, int day) {
        // 调用超类 Employee 中含有 n、s、year、month 和 day 参数的构造器” 的简写形式
        // super 实现对超类构造器的调用。使用 super 调用构造器的语句必须是子类构造器的第一条语句
        super(name, salary, year, month, day);
        bonus = 0;
    }

    @Override
    public double getSalary() {
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }

    public void setBonus(double aBonus){
        bonus = aBonus;
    }
}
  • 如果子类的构造器没有显式地调用超类的构造器, 则将自动地调用超类默认(没有参数 )的构造器。
  • 如果超类没有不带参数的构造器, 并且在子类的构造器中又没有显式地调用超类的其他构造器, 则 Java 编译器将报告错误。
this 关键字的用途

1)引用 隐式参数;

2)调用该类其他的构造器

super 关键字的用途

1)调用超类的方法;

2)调用超类的构造器

4. 继承层次

   由一个公共超类派生出来的所有类的集合被称为继承层次

        

5. 多态

  1)是否应该设计为继承关系:“is-a”规则,它表明子类的每个对象也是超类的对象

  2)Java中,对象变量是多态的:一个 Employee 变量既可以引用一个Employee 类对象, 也可以引用一个 Employee 类的任何一个子类的对象

Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);

Employee[] staff = new Employee[3];

// 变量 staff[0] 与 boss 引用同一个对象
staff[0] = boss;

// 这将意味着(1):
boss.setBonus(5000);       // OK
staff[0].setBonus(5000);   // Error,因为 staff[0] 是 Employee 类,而 setBonus 不是 Employee 的方法

// (2)不能将一个超类的引用赋给子类变量
Manager m = staff[i];      // Error,因为 不是所有的雇员都是经理

6. 方法调用

  在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。特别是, 如果超类方法是 public, 子类方法一定要声明为 public。 7. 阻止继承:final 类和方法   将方法或类声明为 final 主要目的是: 确保它们不会在子类中改变语义
  • a)类被定义为 final,表示阻止 定义其子类
  • b)类中的方法被定义为 final,表示 子类不能覆盖这个方法。(final 类中的所有方法自动地成为 final 方法,!!不包含域)
8. 强制类型转换   1)数据类型转换: double x = 3.405;   int nx = (int) x ;
  2)类转换:     类的类型转换的唯一原因:在暂时忽视对象的实际类型之后,使用对象的全部功能。   将一个子类的引用赋给一个超类变量, 编译器是允许的。但将一个超类的引用赋给一个子类变量, 必须进行类型转换, 才能通过运行时的检査。   综上所述:
  • 只能在继承层次内进行类型转换。
  • 在将超类转换成子类之前,应该使用 instanceof 进行检查。
 在一般情况下,应该尽量少用类型转换和 instanceof 运算符。
public class ManagerTest {

    public static void main(String[] args) {
        Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
        boss.setBonus(5000);

        Employee[] staff = new Employee[3];

        staff[0] = boss;
        staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
        staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

        for (Employee e : staff) {
            System.out.println("name= " + e.getName() + ", salary= " + e.getSalary() + ", hireDay=" + e.getHireDay());
        }

        Manager boss2 = (Manager) staff[1];    // Error [java.lang.ClassCastException: inheritance.Employee cannot be cast to inheritance.Manager],可以改成 staff[0]

        if (staff[1] instanceof Manager) {
            Manager boss3 = (Manager) staff[1];
            System.out.println(boss3);
        }
    }
}

9. 抽象类

OOP中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。

在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

1) 抽象方法充当着占位的角色, 它们的具体实现在子类中。

2)扩展抽象类可以有两种选择:

  • (1)在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类;
  • (2)定义全部的抽象方法,这样子类就不是抽象的了。
3)抽象类不能被实例化

4)可以定义一个抽象类的对象变量, 但是它只能引用非抽象子类的对象。 Person 类是抽象类,Student 类是他的派生类。可以 Person p = new Student("Vince Wu", "Economics);

public class PersonTest {
    public static void main(String[] args) {
        Person[] people = new Person[2];

        people[0] = new Employee("Harry Hacker", 50000, 1982, 7, 9);
        people[1] = new Student("Maria Morris", "computer science");

        for (Person p : people) {
            System.out.println("name: " + p.getName() + ", description: " + p.getDescription());
        }
    }
}

10. 受保护访问 protected

1)超类的方法或域 设置 protected 的目的:超类中的某些方法允许被子类访问, 或允许子类的方法访问超类的某个域。

2)protected 的实际意义是:限制某个方法的使用

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

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

5.2 Object:所有类的超类

1)可以使用 Object 类型的变量引用任何类型的对象 2)在 Java 中,只有基本类型 ( primitive types) 不是对象, 如,数值、字符和布尔类型的值都不是对象。所有的数组类型(包括:对象数组、基本类型的数组)都扩展了 Object 类。
// 1) 可以使用 Object 类型的变量引用任何类型的对象
Object obj = new Employee("Harry Hacker", 35000);

// 1.1)对其中的内容进行具体的操作, 要清楚对象的原始类型, 并进行相应的类型转换
Employee e = (Employee) obj;

// 2)
Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK

2. Object 类的几个主要方法

1)equals:判断两个对象是否具有相同的引用

什么是==?
   == 等于比较运算符,
   1)如果进行比较的两个操作数都是数值类型,即使他们的数据类型不相同,只要他们的值相等,也都将返回true.
   2)如果两个操作数都是引用类型,那么只有当两个引用变量的类型具有父子关系时才可以比较,而且这两个引用必须指向同一个对象,才会返回true.(在这里我们可以理解成 == 比较的是两个变量的内存地址)

什么是equals()?
   equals()方法是 Object 类的方法,在 Object 类中的 equals() 方法体内,实际上返回的就是使用 == 进行比较的结果.但是我们知道所有的类都继承 Object,而且 Object 中的 equals() 方法没有使用 final 关键字修饰,
那么当我们使用 equal() 方法进行比较的时候,我们需要关注的就是这个类有没有重写 Object 中的 equals() 方法. 区别
== 是java提供的等于比较运算符,用来比较两个变量指向的内存地址是否相同.而 equals() 是 Object 提供的一个方法.Object 中 equals() 方法的默认实现就是返回两个对象==的比较结果.
但是equals()可以被重写,所以我们在具体使用的时候需要关注equals()方法有没有被重写.

2)相等测试与继承:

  Java 要求 equals 方法的下列特性:

  • 1 ) 自反性:对于任何非空引用 x, x.equals(x) 返回 true;
  • 2 ) 对称性:对于任何引用 x 和 y, 当且仅当 y.equals(x) 返回 true , x.equals(y) 也应该返回 true;
  • 3 ) 传递性:对于任何引用 x、y 和 z, 如果 x.equals(y) 返回 true, y.equals(z) 返回 true,x.equals(z) 也应该返回 true;
  • 4 ) 一致性:如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果;
  • 5 ) 对于任意非空引用 x, x.equals(null) 返回 false;

  完美的 equals 方法

1)// 显示参数命名为 otherObject,稍后需要转换为另一个 other 的变量;

2)// 检测 this 与 otherObject 是否引用同一个对象;
     if (this == otherObject) { return true; }

3)// 检测 otherObject 是否为 null,如是返回 false;
     if (otherObject == null) { return false; }

4)// 比较 this 与 otherObject 是否属于同一个类
     // a) 如 equals 的语义在每个子类中有所改变,需使用 getClass
     if (getClass() != otherObject.getClass()) { return false; }
     // b) 如所有的子类都拥有统一的语义,使用 instanceof
     if (!(otherObject instanceof ClassName)) { return false; }

5)// 将 otherObject 转换为相应的类类型变量;
    ClassName other = (ClassName) otherObject;

6)// 比较域。 使用 == 比较类型域,使用 equals 比较对象域
    return field1 == other.field1 && Objects.equals(field2, other.field2) && ...;
---- 实例 ----
@Override
public boolean equals(Object otherObject)
{
    // 1) a quick test to see if the objects are identical
    if (this == otherObject) { return true; }

    // 2) must return false if the explicit parameter is null
    if (otherObject == null) { return false; }

    // 3) if the classes don't match, they can't be equal
    if (getClass() != otherObject.getClass()) { return false; }

    // 4) now we know otherObject is a non-null Employee
    Employee other = (Employee) otherObject;

    // 5) test whether the fields have identical values
    return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
}

3)hashCode:

  • 对象导出的一个整型值,如果x, y 是两个不同的对象,则 x.hashCode() 和 y.hashCode() 基本不同;
  • 如果重新定义 equals 方法,必须重新定义 hashCode 方法;
  • equals 与 hashCode 的定义必须一致;
String s = "Ok";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode() + " " + sb.hashCode());

String t = "Ok";
StringBuilder tb = new StringBuilder(t);
System.out.println(t.hashCode() + " " + tb.hashCode());

--------- Output ---------
2556 225534817
2556 1878246837

// 字符串的 hashCode 是由内容导出的,所以 s 和 t 拥有相同的 hashCode
// StringBuilder 类中没有定义 hashCode 方法,所以由默认的 hashCode 导出 对象的存储地址
/**
 * hashCode 的几种方法“”
 */
// 第一种:
public int hashCode() {
    return 7 * name.hashCode() + 11 * new Double(salary).hashCode() + 13 * hireDay.hashCode(); }

// 第二种: better
public int hashCode() {
    return 7 * Objects.hashCode(name) + 11 * Double.hashCode(salary) + 13 * Objects.hashCode(hireDay); }

// 第三种: best
public int hashCode() {
    return Objects.hash(name, salary, hireDay);}

4)toString:

  • 只要对象与一个字符串通过 “ +”  连接,Java 编译就会自动地调用 toString 方法
  • 如果是数组,则要用 Arrrays.toString,如  int[] luckyNumbers = { 2, 3, 5, 7 , 11, 13 } ;    String s = Arrays.toString(luckyNumbers);
  • 多维数组,则使用 Arrays.deepToString 方法;

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

5.3 泛型数组列表

1. 泛型数组 的2种方式: 1) 稍后定义。缺点:一旦确定数组大小,不容易改变
int actualSize = ...;
Employee[] staff = new Employee[actualSize];

2)ArrayList 类:采用 类型参数 的泛型类;

ArrayList staff = new ArrayList<>(); 

 ** 如果赋值给一个变量,或传递到某个方法,或从某个方法返回,编译器会检査这个变量、参数或方法的泛型类型,然后将类型放在 <> 中。

    在这个例子中,new ArrayList<>() 将赋至一个类型为 ArrayList 的变量, 所以泛型类型为 Employee。

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

// 数组列表 与 数组的区别:

new ArrayList<>(100);  // capacity is 100
=> 只是拥有 100 个元素的能力(重新分配,将超过 100),但最初甚至初始化构造之后,数组列表根本不含任何元素

new Employee[100];    // size is 100
=> 为数组分配 100 个元素的存储空间,可以使用。超过时“java.lang.ArrayIndexOutOfBoundsException”,不能扩展
 
方法 参数 作用
-- java.util.ArrayList 1.2    
  ArrayList()   构造一个空数组列表
  ArrayList(int initialCapacity) initalCapacity: 数组列表的最初容量 用指定容量构造一个空数组列表
  boolean add(E obj) obj: 添加的元素 在数组列表的尾端添加一个元素。 永远返回 true。
  int size()   返回存储在数组列表中的当前元素数量。( <= 数组列表的容量。) 
  void ensureCapacity(int capacity) capacity:需要的存储容量 确保数组列表在不重新分配存储空间的情况下,就能保存给定数量的元素
  void trimToSize()   将数组列表的存储容量削减到当前尺寸
-- java.util.ArrayList 1.2    
  void set(int index, E obj)

index:位置(介于 0 ~ size()-1 之间)

obj:新的值

  设置数组列表指定位置的元素值, 这个操作将覆盖这个位置的原有内容  空数组不能用该方法
  void get(int index) index:位置(介于 0 ~ size()-1 之间)  获得指定位置的元素值
  void add(int index, E obj)

index:位置(介于 0 ~ size()-1 之间)

obj:新元素

向后移动元素,以便插入元素
  void remove(int index)  index:被删的元素位置(介于 0 ~ size()-1 之间) 删除一个元素,并将后面的元素向前移动。被删除的元素由返回值返回
 
**** ArrayList 的注意事项:
1)原始的 ArrayList 存在一定的危险性,如
ArrayList staff = new ArrayLis<>(100);
Employee harry = new Employee(.....);

staff.set(i, harry);    // Ok

staff.set(i, "Harry Hacker");   // 1)编译不会报警,仅在检索对象并试图进行类型转换时,才发现问题。   2)如使用 ArrayList,编辑器就会报错

2)灵活的操作方式:
// step1:创建数组,并添加所有的元素
ArrayList list = new ArrayList<>();
while (...)
{
    x=...;
    list.add(x);
}

// step2:使用 toArray 方法 copy 到另一个数组中
X[] a = new X[list.size()];
list.toArray(a);

5.4 对象包装器与自动装箱

1. 对象包装器
  • 对象包装器:所有的基本类型都有一个与之对应的类(如 Integer 对应基本类型 int)。包含:Integer、Long、Float、Double、Short、Byte (这 6 个类派生于公共的超类 Number)、Character 、Void 和 Boolean 。
  • 对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时, 对象包装器类还是 final , 因此不能定义它们的子类。
// 错误定义,尖括号中的类型参数不允许是基本类型
ArrayList<int> list

// 可以定义
ArrayList list = new ArrayList<>;

2. 自动装箱与拆箱

1)自动装箱,如 添加 int 类型的元素到 ArrayList
list.add(3);            => 自动变换成:list.add(Integer.valueOf(3));

2)自动拆箱,如 将一个 Integer 对象赋给一个 intint n = list.get(i);   => 自动转换成: int n = list.get(i).intValue();
 ** 自动装箱规范要求 booleanbytechar <= 127, -128~127 之间的 shortint 被包装到固定的对象中。

Integer a = 1000;
Integer b = 1000;

// 所以,下面一定返回 true
if (a == b) {
    return true;
}

3. 参数数量可变的方法

// "..." 表示若干参数
public static double max (double... values) {
    double largest = Double.NEGATIVE_INFINITY;
    for (double v : values) if (v > largest) largest = v;
    return largest; 
}

// 调用
double m = max(3.1, 40.4, -5);

5.6 枚举类

  • toString:返回枚举常量名。如, Size.SMALL.toString( ) 将返回字符串“ SMALL”。
  • toString 的逆方法是静态方法 valueOf。如,Size s = Enum.valueOf(Size,class, "SMALL");    // 将 s 设置成 Size.SMALL
public class EnumTest {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        System.out.println("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) ");
        String input = in.next().toUpperCase();
        Size size = Enum.valueOf(Size.class, input);
        System.out.println("size=" + size);
        System.out.println("abbreviation=" + size.getAbbreviation());
        if (size == Size.EXTRA_LARGE) {
            System.out.println("Good job -- you paid attention to the _.");
        }
    }
}

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; }
}

5.7 反射

反射库:提供了一个工具集,以便编写能够动态操纵 Java 代码的程序。被大量地应用于 JavaBeans 中,是 Java 组件的体系结构;

反射:能够分析类能力的程序(一般工具构造者使用);

反射机制可以用来:

  • 在运行时分析类的能力。
  • 在运行时查看对象, 如, 编写一个 toString 方法供所有类使用。
  • 实现通用的数组操作代码。
  • 利用 Method 对象, 这个对象很像中的函数指针。

1. Class 类

public class ReflectTest {
    public static void main(String[] args) throws ClassNotFoundException {

        // method1: getClass() 返回一个 Class 类型的实例
        Employee e = new Employee("Hack", 50000, 1986, 2, 1);

        Class c1 = e.getClass();
        System.out.println(e.getClass().getName() + ", " + e.getName());
        System.out.println(c1);

        // method2:forName():获得类名对应的 Class 对象 【需要增加一个 异常处理】
        String className = "java.util.Random";
        Class c2= Class.forName(className);
        System.out.println(c2);

        // method3: 如果 T 是任意的 Java 类(或 void 关键字), T.class 将代表匹配的类对象
        Class cl1 = Random.class;
        Class cl2 = int.class;
        Class cl3 = Double[].class;

        System.out.println(cl1 + ", " + cl2 + ", " + cl3);
    }
}

-------- Output ----------
v1ch05.reflecttest.Employee, Hack
class v1ch05.reflecttest.Employee
class java.util.Random
class java.util.Random, int, class [Ljava.lang.Double;

两个有用的方法:

  • 1) == 运算符:类对比,如 if (e.getClass() == Employee.class) ...
  • 2)newInstance:创建一个相同类型的实例。newInstance 方法调用默认的构造器(无参数的构造器)初始化新建的对象。
// forName + newInstance 结合使用
String s = "java.util.Random()";
Object m = Class.forName(s).newInstance();

2. 捕获异常

异常的两个分类:

  • 1)未检查异常,如访问 null 引用(编译器不会检查是否提供了处理器)
  • 2)已检查异常                             (编译器会检查是否提供了处理器)
try
{
    statements that might throw exceptions
}
catch (Exception e) {
    handler action
}

 ** 如果调用了一个抛出已检查异常的方法,而又没有提供处理器,编译器就会给出错误

3. 利用反射分析类的能力

    java.lang.reflect 包中有三个类 Field、Method 和 Constructor 分别描述 类的 域、方法 和 构造器。

Method Function
Field[] getFields() 返回一个包含 Field 对象的数组, 这些对象记录了这个类或其超类的公有域
Field[] getDeclaredFields() 返回包含 Field 对象的数组, 这些对象记录了这个类的全部域。如果类中没有域, 或者 Class 对象描述的是基本类型或数组类型, 这些方法将返回一个长度为 0 的数组。
Method[] getMethods() 返回包含 Method 对象的数组,返回所有的公有方法, 包括从超类继承来的公有方法
Method[] getDeclaredMethods() 返回包含 Method 对象的数组,返回这个类或接口的全部方法, 但不包括由超类继承了的方法
Constructor[] getConstructors() 返回包含 Constructor 对象的数组, 其中包含了 Class 对象所描述的类的所有公有构造器(getConstructors)
Constructor[] getDeclaredConstructors() 返回包含 Constructor 对象的数组, 其中包含了 Class 对象所描述的类的所有构造器(getDeclaredConstructors)
Class getDeclaringClass() 返冋一个用于描述类中定义的 构造器、 方法 或 域 的 Class 对象
Class[] getExceptionTypes() (在 Constructor 和 Method 类中) 返回一个用于描述方法抛出的异常类型的 Class 对象数组
int getModifiers() 

返回一个用于描述 构造器、 方法 或 域 的修饰符的整型数值。使用 Modifier 类中的这个方法可以分析这个返回值。

用不同的位开关描述 public 和 static 这样的修饰符使用状况
String getName() 返冋一个用于描述 构造器、 方法 或 域 名的字符串
Class[] getParameterTypes() (在 Constructor 和 Method 类中) 返回一个用于描述参数类型的 Class 对象数组
Class getReturnType()  ( 在 Method 类中)  返回一个用于描述返回类型的 Class 对象
package reflection;

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

/**
 * This program uses reflection to print all features of a class
 * @version 1.0 2022/5/10
 * @author Google_he
 */
public class ReflectionTest {
    public static void main(String[] args) {
        // read class name from command args or users 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.println("\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[] parameterTypes = c.getParameterTypes();
            for (int j = 0; j < parameterTypes.length; j++) {
                if (j > 0)
                {
                    System.out.print(", ");
                }
                System.out.print(parameterTypes[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[] parameterTypes = m.getParameterTypes();
            for (int j = 0; j < parameterTypes.length; j++) {
                if (j > 0)
                {
                    System.out.print(", ");
                }
                System.out.print(parameterTypes[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 + ";");
        }
    }
}

-------- Output --------
Enter class name (e.g. java.util.Date): 
java.lang.Double
public final  class java.lang.Double  extends java.lang.Number
{
  public java.lang.Double(double);
  public java.lang.Double(java.lang.String);

  public boolean equals(java.lang.Object)
  public static java.lang.String toString(double)
  public java.lang.String toString()
  public int hashCode()
  public static int hashCode(double)
  public static double min(double, double)
  public static double max(double, double)
  public static native long doubleToRawLongBits(double)
  public static long doubleToLongBits(double)
  public static native double longBitsToDouble(long)
  public volatile int compareTo(java.lang.Object)
  public int compareTo(java.lang.Double)
  public byte byteValue()
  public short shortValue()
  public int intValue()
  public long longValue()
  public float floatValue()
  public double doubleValue()
  public static java.lang.Double valueOf(java.lang.String)
  public static java.lang.Double valueOf(double)
  public static java.lang.String toHexString(double)
  public static int compare(double, double)
  public static boolean isNaN(double)
  public boolean isNaN()
  public static boolean isFinite(double)
  public static boolean isInfinite(double)
  public boolean isInfinite()
  public static double sum(double, double)
  public static double parseDouble(java.lang.String)

  public static final double POSITIVE_INFINITY;
  public static final double NEGATIVE_INFINITY;
  public static final double NaN;
  public static final double MAX_VALUE;
  public static final double MIN_NORMAL;
  public static final double MIN_VALUE;
  public static final int MAX_EXPONENT;
  public static final int MIN_EXPONENT;
  public static final int SIZE;
  public static final int BYTES;
  public static final java.lang.Class TYPE;
  private final double value;
  private static final long serialVersionUID;
}

4. 在运行时使用反射分析对象

查看任意对象的数据域名称和类型:

  • 获得对应的 Class 对象;
  • 通过 Class 对象调用 getDeclaredFields;
import java.lang.reflect.Field;

public class OATest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Employee harry = new Employee("Harry Hacker", 50000, 1982, 7, 9);
        Class cl = harry.getClass();

        Field f = cl.getDeclaredField("name");
        f.setAccessible(true);
        Object v = f.get(harry);
    }
}

5. (Skip 5.7.5 使用反射编写泛型数组代码,5.7.6 调用任意方法)

5.8 继承的设计技巧

1. 将 公共操作 和 域 放在超类; 2. 不要使用 protected 域,原因是:
  • 1)子类集合是无限制的,任何人都可由某个类派生一个子类,并直接访问 protected 的实例域,从而破坏封装性;
  • 2)同一包中的任何类都可访问 protected 域,不管是否为这个类的子类;
3. 使用继续实现 “is-a” 关系; 4. 除非所有继承的方法都有意义,否则不要使用继承; 5. 在覆盖方法时,不要改变预期的行为; 6. 使用多态,而非 类型信息; 7. 不要过多地使用 反射;