java核心技术第11版 继承

类、超类和子类

例如员工只有标准工资, 而经理有工资加奖金,
Manager和Employee之间存在明显的“is-a”关系

定义子类

使用关键字extends表示继承

public class Manager extends Employee
{
	add methods and fields
}

构造的新类诞生与一个已存在的域(超类(superclass), 基类(base class)或父类(parents class)), 新类称为子类(subclass), 派生类(derived class)或孩子类(child class)。

public class Manager entends Employee
{
	private double bonus;
	...
	public void setBonus(double bonus)
	{
		this.bonus = bonus;
	}
}

覆盖方法

超类中有些方法对子类并不一定适用, 需要提供新的方法来覆盖(override)

public class Manager extends Empolyee
{
	...
	public double getSalary()
	{
		...
	}
	...
}

若希望在覆盖方法之中调用超类的同名方法, 使用关键字super

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

子类构造器

public Manager(String name, double salary, int year, int month, int day)
{
	super(name, salary, year, month, day);
	bonus = 0;
}

如果子类没有显式调用超类的构造器, 将自动调用超类的无参数构造器
一个对象变量可以指示多种实际类型的现象称为多态(polymorphism), 运行时自动选择适当的方法, 称为动态绑定(dynamic binding)。
inheritance/ManagerTest.java

package inheritance;

/**
 * This program demonstrates inheritance
 * @author Cay Horstmann
 */

public class ManagerTest {
    public static void main(String[] args)
    {
        //construct a Manager object
        var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
        boss.setBonus(5000);
        var staff = new Employee[3];

        //fill the staff array with Manager and Employee objects

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

        //print out information about all Employee objects
        for (Employee e: staff)
            System.out.println("name=" + e.getName() + ", salary=" + e.getSalary());
    }
}

inheritance/Employee.java

package inheritance;

import java.time.*;
public class Employee {
    private String name;
    private double salary;
    public Employee(String name, double salary, LocalDate hireDay) {
        this.name = name;
        this.salary = salary;
        this.hireDay = hireDay;
    }
    private LocalDate hireDay;

    public Employee(String name, double salary, int year, int month, int day)
    {
        this.name = name;
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    public String getName()
    {
        return name;
    }

    public double getSalary()
    {
        return salary;
    }
    public LocalDate getHireDay()
    {
        return hireDay;
    }
    public void raiseSalary(double byPercent)
    {
        double raise = salary * byPercent / 100;
        salary  += raise;
    }
}

inheritance/Manager.java

package inheritance;

public class Manager extends Employee
{
    private double bonus;
    /**
     * @param name the employee's name
     * @param salary the salary
     * @param year the hire year
     * @param month the hire month
     * @param day the hire day
     */
    public Manager(String name, double salary, int year, int month, int day)
    {
        super(name, salary, year, month, day);
        bonus = 0;
    }
    public double getSalary()
    {
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }
    public void setBonus(double b)
    {
        bonus = b;
    }
    
    
}

继承层次

由一个公共超类派生出的所有类的集合称为** 继承层次(inheritance hierarchy)**, 某个子类指向其祖先的路径称为继承链(inheritance chain)

多态

“is-a”规则的另一种表述是替换原则(substitution principle) 指出程序中出现超类对象的任何地方都可以用子类方法替换。

Employee e;
e = new Employee(...);	//OK
e = new Manager(...);			//OK

在java中, 对象变量是多态的(polymorphic.)

方法调用

调用过程如下

  1. 编译器查看对象的声明类型和方法名
  2. 编译器确定方法调用中提供的参数类型, 这个过程称为重载解析(overloading resolution)
  3. 对于private方法, static方法 , final方法, 编译器将进行静态绑定(static binding), 如果调用的方法依赖于隐式参数的实际类型, 则必须在运行时使用动态绑定
  4. 程序运行时且采用动态绑定方法时, 虚拟机必须调用与x所引用对象的实际类型对应的方法, 虚拟机会为每个类计算一个方法表(method table)

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

阻止继承: final类和方法

如下, 可阻止派生Executive类的子类

public final class Executive extends Manager
{
	...
}

同样可用来类中的某个特定方法, 使子类不能覆盖

public class Employee
{
	...
	public final String getName()
	{
		return name;
	}
}

强制类型转换

java中每个对象变量都有一个类型
如果试图在继承链上进行向下的强制类型转换

Manager boss = (Manager) staff[1];

会产生ClassCastException的异常, 可以使用instanceof操作符进行转换

if (staff[1] instanceof Manager)
{
	boss = (Manager) staff[1];
}

将超类强制转换为子类之前, 应该使用instanceof进行检查。
只有在使用Manager中特有的方法时才进行强制类型转换

抽象类

继承层次位于上层的类更具有一般性
使用abstract关键字可以完全不需要去实现该方法, 使其返回默认值

public abstract class Person
{
	...
	public abstract String getDescription();
}

这种类称为抽象类
抽象类不能实例化

public class Student extends Person
{
	private String major;
	public Student (String name, String major)
	{
		super(name);
		this.major = major;
	}
	public String getDescription()
	{
		return "a student majoring in " + major;
	}
	
} 

abstractClasses/PersonTest.java


package abstractClasses;

/**
 * This program demonstrates abstract classes.
 * @author Cay Horstmann
 */
public class PersonTest
{
    public static void main(String[] args)
    {
        var people = new Person[2];
        //fill the people array with Student and Employee objects
        people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
        people[1] = new Student("Maria Morris", "computer science");

        //print out names and descriptions of all Person objects
        for (Person p : people)
            System.out.println(p.getName() + ", " + p.getDescription());
    }
}

abstractClasses/Person.java

package abstractClasses;

public abstract class Person 
{
    public abstract String getDescription();
    private String name;
    public Person(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }
}

abstractClasses/Employee.java

package abstractClasses;

import java.time.*;
public class Employee extends Person{
    private double salary;
    private LocalDate hireDay;
    

    public Employee(String name, double salary, int year, int month, int day)
    {
        super(name);
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }

    public double getSalary()
    {
        return salary;
    }
    public LocalDate getHireDay()
    {
        return hireDay;
    }
    public String getDescription()
    {
        return String.format("an employee with a salary of %.2f", salary);
    }
    public void raiseSalary(double byPercent)
    {
        double raise = salary * byPercent / 100;
        salary  += raise;
    }
}

abstractClasses/Student.java

package abstractClasses;

public class Student extends Person
{
    private String major;

    /**
     * @param name the student's name
     * @param major the student's major
     */
    public Student(String name, String major)
    {
        //pass name to superclass constructor
        super(name);
        this.major = major;
        
    }
    public String getDescription()
    {
        return "a student majoring in " + major;
    }

}

受访问保护

若希望限制超类中某个方法 只允许子类访问, 或访问某个字段, 使用关键字protected
java中保护字段只能由同一个包的类访问

Object: 所有类的超类

java中每个类都扩展了超类

Object类型变量

可以使用Object类型的变量引用任何类型的变量

Object obj = new Employee("Harry Hacker", 35000 );
Employee e = (Employee) obj;

java中只有基本类型不是对象

equals方法

Object类中的equals方法将确定两个对象引用是否相等

public class employee
{
	...
	public boolean equals (Object otherObject)
	{
		//a quick test to see if the Objects and identical
		if(this == otherObject) return true;

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

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

		//now we know otherObject is a non-null Employee
		Employee other = (Employee) otherObject;
		// test whether the fields have identical values
		return name.equals(other.name)
			&& salary == other.salary
			&& hireDay.equals(other.hireDay);
	}
}

getClass方法返回一个对象所属的类.

相等测试和继承

if (!(otherObject instanceof Employee))  return false

允许 otherObject属于一个子类
但这种方法不推荐
java要求equals方法具有如下特质:

  1. 自反性
  2. 对称性
  3. 传递性
  4. 一致性
  5. 对非空引用x, x.equals(null)返回null

如果子类可以有自己的相等性概念, 对称性需求将强制使用getClass检测
如果由超类决定相等性概念, 那么可以使用instanceof来进行检测

对于equals方法的建议 :

  1. 显式参数命名为otherObject, 稍后将其强制转换为另一个名为other的方法
  2. 检测this和otherObject是否相等
  3. 检测otherObject是否为null, 若null返回false
  4. 比较this与otherObject的类, 如果equals的语义可以在子类中改变, 使用getClass检测; 如果所有子类都有相同的相等性语义, 使用instanceof检测
  5. 将otherObect强制转换为相应类类型的变量
  6. 根据相等性概念进行字段比较; 如果在子类中重新定义equals, 就要在其中包含一个super.equals(other)调用

equals方法必须覆盖Object的equals方法

API

java.util.Arrays
static boolean equals​(Type[] a, int aFromIndex, int aToIndex, Type[] b, int bFromIndex, int bToIndex)
Returns true if the two specified arrays of booleans are equal to one another.
the Type coule be boolean, short, int, long, byte, char, float, double, Object
FromIndex and ToIndex are optional
java.util.Objects
static boolean equals​(Object a, Object b)
Returns true if the arguments are equal to each other and false otherwise. Consequently, if both arguments are null, true is returned. Otherwise, if the first argument is not null, equality is determined by calling the equals method of the first argument with the second argument of this method. Otherwise, false is returned.

hashcode方法

散列码(hash code)是由对象导出的一个整型值。
String类使用如下算法计算hash code:

int hash = 0;
for (int i = 0; i < length(); i++)
	hash = 31 * hash + charAt(i);

hashCode方法定义在Object类中, 其值由对象的存储地址得出

public class Employee
{
	public int hashCode()
	{
		return 7 * name.hashCode()
			+ 11 * new Double(salary).hashCode()
			+ 13 * hireDay.hashCode();
	}
	...
}

可以使用null安全的Objects.hashCode, 使用静态方法Double.hashCode来避免创建Double对象

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

需要组合多个散列值时, 可以使用Objects.hash并提供所有参数, 这个方法可以做到自动计算并组合

public int hashCode()
{
	return Objects.hash(name, salary, hireDay);
}

API

java.lang.Object
int hashCode()
Returns a hash code value for the object.
java.util.Objects
static int hash​(Object… values)
Generates a hash code for a sequence of input values.
static int hashCode​(Object o)
Returns the hash code of a non-null argument and 0 for a null argument.
java.lang.(Integer | Long | Short | Byte | Double | Float | Chatacter | Boolean)
static int hashCode​(Type value)
Returns a hash code for an int value; compatible with Integer.hashCode().
java.util.Arrays
static int hashCode​(Type[] a)
Returns a hash code based on the contents of the specified array.
The Type could be object, int, long, short, char, byte, boolean, float, double

toString方法

toString方法返回表示对象值的一个字符串

public String toString()
{
	return "Employee[name=]" + name
		+  ", salary=" + salary
		+ ", hireDay=" + hireDay
		+ "]";
}

可以通过getClass().getName()获得类名的字符串

public String toString()
{
	return getClass().getName()
	+ "[name=" + name
	+ "salary=" + salary
	+ "hireDay=" + hireDay
	+ "]";
}

这样toString方法同样子类可以调用

public class Manager extends Employee
{
 	...
 	public String toString()
 	{
 		return super.toString()
 			+ "[bonus=" + bonus
 			+ "]";
 	}
}

如果x是一个任意对象

System.out.println(x);

println方法就会简单调用toString方法
Object类定义的toString方法返回对象的类名和散列码

System.out.println(System.out);

equals/EqualsTest.java

package equals;


/**
 * This program demonstrates the equals method.
 * @author Cay Horstmann
 */
public class EqualsTest 
{
    public static void main(String[] args)
    {
        var alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
        var alice2 = alice1;
        var alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15);
        var bob = new Employee("Bob Brandson", 50000, 1989, 10, 1);

        System.out.println("alice1 == alice2: " + (alice1 == alice2));
        System.out.println("alice1 == alice3: " + (alice1 == alice3));
        System.out.println("alice1.equals(alice3): " + alice1.equals(alice3));
        System.out.println("alice1.equals(bob): " + alice1.equals(bob));
        System.out.println("bob.toString(): " + bob);

        var carl  = new Manager("Carl Cracker", 80000, 1987, 12, 15);
        var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
        boss.setBonus(5000);

        System.out.println("boss.toString(): " + boss);
        System.out.println("carl.equals(boss): " + carl.equals(boss));
        System.out.println("alice1.hashCode(): " + alice1.hashCode());
        System.out.println("alice3.hashCode(): " + alice3.hashCode());
        System.out.println("bob.hashCode(): " + bob.hashCode());
        System.out.println("carl.hashCode(): " + carl.hashCode());
    }
}

equals/Employee.java

package equals;

import java.time.*;
import java.util.Objects;


public class Employee {
    private String name;
    private double salary;
    private LocalDate hireDay;

    public Employee(String name, double salary, LocalDate hireDay) {
        this.name = name;
        this.salary = salary;
        this.hireDay = hireDay;
    }

    public Employee(String name, double salary, int year, int month, int day)
    {
        this.name = name;
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    public String getName()
    {
        return name;
    }

    public double getSalary()
    {
        return salary;
    }
    public LocalDate getHireDay()
    {
        return hireDay;
    }
    public void raiseSalary(double byPercent)
    {
        double raise = salary * byPercent / 100;
        salary  += raise;
    }

    public boolean equals(Object otherObject)
    {
        //a quick test to see if the Objeccts are identical
        if(this == otherObject) return true;

        //must return false if the explicit parameter is null
        if(otherObject == null) return false;
        // if the classes do not match, they can not be equal
        if(getClass() != otherObject.getClass()) return false;
        
        //now we know otherObject is a non-null Employee
        var other = (Employee) otherObject;
        //test whether the fields have identical values
        return Objects.equals(name, other.name)
            && salary == other.salary && Objects.equals(hireDay, other.hireDay);
    }

    public int hashCode()
    {
        return Objects.hash(name, salary, hireDay);
    }
    public String toString()
    {
        return getClass().getName() + "[name=" + name + ", salary=" + salary + ", hireDay="
            + hireDay + "]"; 
    }
}

equals/Manager.java

package equals;

public class Manager extends Employee
{
    private double bonus;
    /**
     * @param name the employee's name
     * @param salary the salary
     * @param year the hire year
     * @param month the hire month
     * @param day the hire day
     */
    public Manager(String name, double salary, int year, int month, int day)
    {
        super(name, salary, year, month, day);
        bonus = 0;
    }
    public double getSalary()
    {
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }
    public void setBonus(double b)
    {
        bonus = b;
    }
    

    public boolean equals(Object otherObject)
    {
        if (!super.equals(otherObject)) return false;
        var other = (Manager) otherObject;
        //super.equals checked that this and other belong to the same class
        return bonus == other.bonus;
    }

    public int hashCode()
    {
        return java.util.Objects.hash(super.hashCode(), bonus);
    }

    public String toString()
    {
        return super.toString() + "[bonus=" + bonus + "]";
    }
    
}

泛型数组列表

ArrayList类类似于数组, 但可以自动调整数组容量
ArrayList是一个有** 参数类型(type parameter)** 的** 泛型类(generic class) **用一堆尖括号<>将类名追加到ArrayList之后来指定数组列表保存的元素对象类型

声明数组列表

ArrayList<Employee> staff = new ArrayList<Employee>();
var staff = new ArrayList<Employee>();
ArrayList<Employee> staff = new ArrayList<>();

这称为"菱形"语法
使用var进行声明时菱形语法不可用
使用add方法进行元素添加

staff.add(new Employee("Harry Hacker", ... ));
staff.add(new Employee("Tony Tester", ....));

如果可能存储单位已知, 可以在填充数组列表之前调用ensureCapacity方法:

staff.ensureCapacity(100);
ArrayList<Employee> staff = new ArrayList<>(100);

size方法返回数组列表中包含的实际元素个数

staff.size()

等价于数组的a.length.
确定数组列表的大小将保持恒定, 调用trimToSize方法

API

java.util.ArrayList
ArrayList()
Constructs an empty list with an initial capacity of ten.
ArrayList​(int initialCapacity)
Constructs an empty list with the specified initial capacity.
void add​(int index, E element)
Inserts the specified element at the specified position in this list.
boolean add​(E e)
Appends the specified element to the end of this list.
int size()
Returns the number of elements in this list.
void ensureCapacity​(int minCapacity)
Increases the capacity of this ArrayList instance, if necessary, to ensure that it can hold at least the number of elements specified by the minimum capacity argument.
void trimToSize()
Trims the capacity of this ArrayList instance to be the list’s current size.

访问数组列表元素

不能使用数组下标法进行数组列表的访问与元素设置

staff.set(i, harry);
Employee e = staff.get(i);

以下技巧既可以灵活扩展数组, 又可以方便的访问数组元素

var list = new ArrayList<x>();
while (...)
{
	x= ...;
	list.add(x);
}

随后使用toArray方法拷贝进一个数组

var a = new X[list.size()];
list.toArray(a);

使用add方法在数组列表中间插入元素

int n = staff.size() / 2;
staff.add(n, e);

arrayList/ArrayListTest.java

package arraylist;

import java.util.*;
/**
 * This program demonstrates the ArrayList class.
 * @author Cay Horstmann
 */

public class ArrayListTest 
{
    public static void main(String[] args)
    {
        //fill the staff array list with three Employee objects
        var staff = new ArrayList<Employee>();
        
        staff.add(new Employee("Carl Cracker", 75000, 1987, 12, 15));
        staff.add(new Employee("Harry Hacker", 50000, 1989, 10, 1));
        staff.add(new Employee("Tony Tester", 40000, 1990, 3, 15));

        //raise everyone's salary by 5%
        for (Employee e: staff)
            e.raiseSalary(5);

        //print out information about all Employee objects
        for (Employee e: staff)
            System.out.println("name=" + e.getName() + ", salary=" + e.getSalary() + " ,hireDay=" + e.getHireDay());
    }
    
}

API

java.util.ArrayList
E set​(int index, E element)
Replaces the element at the specified position in this list with the specified element.
E get​(int index)
Returns the element at the specified position in this list.
void add​(int index, E element)
Inserts the specified element at the specified position in this list.
E remove​(int index)
Removes the element at the specified position in this list.

类型化与原始数组列表的兼容性

假设有下面这个遗留下来的类

public class EmployeeDB
{
	public void update(ArrayList list){...}
	public ArrayList find (String query){...}
}

可以将一个类型化数组传递给update方法不需要进行强制转化

ArrayList<Employee> staff = ...;
employeeDB.update(staff);

反之将一个原始ArrayList赋给一个类型化ArrayList会得到一个警告

ArrayList<Employee> result = employeeDB.find(query);		//yields warning

使用强制转换将会得到另一个警告消息, 指出类型转换有误。
在于这些遗留代码交互后确定警告不太严重后, 可以用@SuppressWarnings(“unchecked”)注解来标记接受强制类型转换的变量

@SuppressWarnings("unchecked") ArrayList<Employee> result
	= (ArrayList<Employee>) employeeDB.find(query); 		//yield another warning

对象包装器与自动装箱

所有基本类型都有一个与之对应的类
Integer, Long, Float, Double, Short, Byte, Chatacter和Boolean(前六个类诞生于超类Number)
若要定义一个整型数组列表, 类型参数不允许是基本类型

var list = new ArrayList<Integer>();
list.add(3);

将自动变换为

list.add(Integer.valueOf(3));

这种变换称为自动装箱(autoboxing)
将一个Integer对象赋给一个int值时, 也将会自动的拆箱
其同样适用于算术表达式
但其与基本类型在同一性有区别, == 可以应用于包装器对象, 但检测的是是否有相同的内存位置。
在比较两个包装器对象时还是使用equals方法比较好
如果在一个条件表达式中混用integer和Double, Integer值就会拆箱, 提升为Double, 再装箱为Integer:

   Integer n = 1;
      Double x = 2.0;
      out.println(true? n : x);

装箱和拆箱为编译器执行的工作, 而非虚拟机
要想将字符串转换为整型, 可以使用parseInt(s);

int x = Integer.parseInt(s);

API

java.lang.Integer
int intValue()
Returns the value of this Integer as an int.
static String toString​(int i)
Returns a String object representing the specified integer.
static String toString​(int i, int radix)
Returns a string representation of the first argument in the radix specified by the second argument.
static int parseInt​(String s)
Parses the string argument as a signed decimal integer.
static int parseInt​(String s, int radix)
Parses the string argument as a signed integer in the radix specified by the second argument.
static Integer valueOf​(int i)
Returns an Integer instance representing the specified int value.
static Integer valueOf​(String s)
Returns an Integer object holding the value of the specified String.
static Integer valueOf​(String s, int radix)
Returns an Integer object holding the value extracted from the specified String when parsed with the radix given by the second argument.
java.text.NumberFormat
Number parse​(String source)
Parses text from the beginning of the given string to produce a number

参数数量可变的方法

有时这些方法称为变参(varargs)方法。
printf方法定义如下:

public class PrintStream
{
	public PrintStream printf(String fmt, Object... args){return format(fmt, args);}
}

省略号…表明这个方法可以接受任意数量参数。
Object…参数类型与Object[]完全一致

System.out.printf("%d %s", new Object[] {new Integer(n), "widgets"});

如下函数可以计算若干个数值的最大值

public static double max(double... values)
{
	double largest = Double.NEGATIVE_INFINITY;
	for (double v: values) if(v > largest)  largest = v;
	return largest;
}

枚举类

public enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE}

实际上这个声明定义的类型是一个类, 在比较两个枚举类型的值时, 直接使用==即可
可以为枚举类型增加构造器, 方法和字段

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

toString方法返回枚举类型的枚举常量名,其逆方法为静态方法valueOf。
枚举类型的静态values方法将返回一个包含全部枚举值的数组

Size[] values = Size.values();

ordinal方法返回enum声明中枚举常量的位置, 从零开始计数

Size.MEDIUM.ordinal()

返回1
enums/EnumTest.java

package enums;

import java.util.*;


/**
 * This program demonstrates enumerated types.
 * @author Cay Horstmann
 */
public class EnumTest 
{
    
    public static void main(String[] args)
    {
        var in = new Scanner(System.in);
        System.out.print("Enter a size:(SMALL, MEDIUM, LARGE, EXTRA_LARGE)");
        String input = in.next().toUpperCase();
        Size size = Enum.valueOf(Size.class, input);
        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 Size(String abbreviation) {this.abbreviation = abbreviation;}
    public String getAbbreviation() {return abbreviation;}

    private String abbreviation;
}

API

java.lang.Enum
static <T extends Enum< T >>T valueOf​(Class< T > enumClass, String name)
Returns the enum constant of the specified enum class with the specified name.
String toString()
Returns the name of this enum constant, as contained in the declaration.
int ordinal()
Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero).
int compareTo​(E o)
Compares this enum with the specified object for order.

反射

反射库(reflection library)提供了一个工具集用来编写可以动态操纵java代码的程序, 使用反射可完成很多需要动态查询类能力的开发工具。
能够分析类能力的程序称之为反射(reflective)
反射机制可以

  1. 运行时分析类的能力
  2. 运行时检查对象
  3. 实现泛型数组操作代码
  4. 利用method对象, 类似于函数指针

Class类

程序运行期间java始终为所有对象维护一个运行时类型标识, 该信息跟踪每个对象所属的类
可以使用Class类访问这些信息。

Employee e;
...
Class cl = e.getClass();

Class对象会描述一个特定类的属性, 最常用的Class方法通常是getName。
如果类在一个包里, 包的名字也会是类名的一部分

var generator = new Random();
Class cl = generator.getClass();
String name = cl.getName();		name is set to "java.util.Random'

forName静态方法可以获取类名对应的Class对象

String className = "java.util.Random";
Class cl = Class.forName(className);

如果className不是一个类名或接口名, forName方法会抛出一个检查型异常(checked exception), 使用该方法时应该提供一个** 异常处理器(exception handler)**

如果T是任意的java类型(或void), T.class将代表匹配的类对象

Class cl1 = Random.class;
Class cl2 = int.class;
Class cl3 = Double[].class

可以利用==实现两个类对象的比较, 与条件e instanceof Employee不同, 若e为子类的实例, 仍然不会通过

if (e.getClass() == Employee.class)...

getConstructor方法将得到一个Constructor类型对象, 随后可以使用newInstance方法创造一个实例

var className = "java.util.Random";
Class cl = Class.forName(className);
Object obj = cl.getConstructor().newInstance();

API

java.lang.Class
static Class<?> forName​(String className)
Returns the Class object associated with the class or interface with the given string name.
Constructor getConstructor​(Class<?>… parameterTypes)
Returns a Constructor object that reflects the specified public constructor of the class represented by this Class object.
Constructor<?>[] getConstructors()
Returns an array containing Constructor objects reflecting all the public constructors of the class represented by this Class object.
java.lang.reflect.Constructor
T newInstance​(Object… initargs)
Uses the constructor represented by this Constructor object to create and initialize a new instance of the constructor’s declaring class, with the specified initialization parameters.
java.lang.Throwable
void printStackTrace()
Prints this throwable and its backtrace to the standard error stream.
void printStackTrace​(PrintStream s)
Prints this throwable and its backtrace to the specified print stream.

声明异常

抛出异常时可以提供一个处理器(handler) 捕获该异常并处理
异常有两种类型:** 非检查型(unchecked)检查型(checked)** 异常
通常越界错误或访问null引用, 都属于非检查型异常, 这类异常应该尽力避免, 而不是去编写异常处理器
类似Class.forName方法, 很多方法都会抛出一个检查型异常

增加throws子句

public static void doSomethingWithClass(String name)
	throws ReflectiveOperationException
	{
		Class cl = class.forName(name);		//might throw exception
	}

资源

Class类提供了一个服务可以查找资源文件

  1. 获得拥有资源的的类的class对象, 例如ResourceTest.class
  2. 有些方法接受描述资源位置的URL,, 则要调用
URL url = cl.getResource("about.gif");
  1. 否则使用getResourceAsStream方法得到一个输入流来读取文件中的数据

文件的自动装载是利用资源加载特性完成, 没有标准方法来解释资源文件内容
resource/ResourceTest.java

package resources;

import java.io.*;
import java.net.*;
import java.nio.charset.*;
import javax.swing.*;

/**
 * @author Cat Horstmann
 */


public class ResourceTest 
{
    public static void main(String[] args) throws IOException
    {
        Class cl = ResourceTest.class;
        URL aboutURL = cl.getResource("about.gif");
        var icon = new ImageIcon(aboutURL);

        InputStream stream = cl.getResourceAsStream("data/about.txt");
        var about = new String(stream.readAllBytes(), "UTF-8");

        InputStream stream2 = cl.getResourceAsStream("title.txt");
        var title = new String(stream2.readAllBytes(), StandardCharsets.UTF_8).trim();

        JOptionPane.showMessageDialog(null, about, title, JOptionPane.INFORMATION_MESSAGE, icon);
    }
}

API

java.lang.Class
static Class<?> forName​(String className)
Returns the Class object associated with the class or interface with the given string name.
URL getResource​(String name)
Finds a resource with a given name.
InputStream getResourceAsStream​(String name)
Finds a resource with a given name.

利用反射分析类的能力

java.lang.reflect包中有三个类Field, Method和Constructor分别描述类的字段, 方法和构造器
这三个类都有一个叫做getName的方法, 用来返回字段, 方法或构造器的名称。·
Field类有一个getType方法返回描述字段类型的一个对象; 这三个类都有getModifiers方法返回描述修饰符的对象, Modifier类中isPublic, isPrivate, isFinal可以进行判断, 可以用Modifier.toString方法打印修饰符。
Class类中getFields, getMothods, getConstructors方法将分别返回这个类支持的公共字段, 方法, 构造器的数组, 包括超类的公共成员
Class类getDeclaredFields, getDeclaredMethods和getDeclaredConstructors将返回
所有成员(其中getDeclaredMethods不包括超类成员)

reflection/ReflectionTest.java

package reflection;

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

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

        //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("}");



    }
    /**
    * Print all constorctors of 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(");");
        }
    }

    /**
    * Print all methods of 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
    */
    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 + ";");
        }
    }
}

API

java.lang.Class
Field[] getFields()
Returns an array containing Field objects reflecting all the accessible public fields of the class or interface represented by this Class object.
Field[] getDeclaredFields()
Returns an array of Field objects reflecting all the fields declared by the class or interface represented by this Class object.
Method[] getMethods()
Returns an array containing Method objects reflecting all the public methods of the class or interface represented by this Class object, including those declared by the class or interface and those inherited from superclasses and superinterfaces.
Method[] getDeclaredMethods()
Returns an array containing Method objects reflecting all the declared methods of the class or interface represented by this Class object, including public, protected, default (package) access, and private methods, but excluding inherited methods.
Constructor<?>[] getConstructors()
Returns an array containing Constructor objects reflecting all the public constructors of the class represented by this Class object.
Constructor<?>[] getDeclaredConstructors()
Returns an array of Constructor objects reflecting all the constructors declared by the class represented by this Class object.
String getPackageName()
Returns the fully qualified package name.
java.lang.reflect.Field | Method | Constructor
Class<?> getDeclaringClass()
Returns the Class object representing the class or interface that declares the field represented by this Field object.
int getModifiers()
Returns the Java language modifiers for the field represented by this Field object, as an integer.
String getName()
Returns the name of the field represented by this Field object.
Class<?>[] getParameterTypes() (defined in Constructor and Moethod class)
Returns an array of Class objects that represent the formal parameter types, in declaration order, of the executable represented by this object.
Class<?> getReturnType() (defined in Method class)
Returns a Class object that represents the formal return type of the method represented by this Method object.
java.lang.reflect.Modifier
static String toString​(int mod)
Return a string describing the access modifier flags in the specified modifier.
static boolean isAbstract​(int mod)
Return true if the integer argument includes the abstract modifier, false otherwise.
static boolean isFinal​(int mod)
Return true if the integer argument includes the final modifier, false otherwise.
static boolean isInterface​(int mod)
Return true if the integer argument includes the interface modifier, false otherwise.
static boolean isNative​(int mod)
Return true if the integer argument includes the native modifier, false otherwise.
static boolean isPrivate​(int mod)
Return true if the integer argument includes the private modifier, false otherwise.
static boolean isProtected​(int mod)
Return true if the integer argument includes the protected modifier, false otherwise.
static boolean isPublic​(int mod)
Return true if the integer argument includes the public modifier, false otherwise.
static boolean isStatic​(int mod)
Return true if the integer argument includes the static modifier, false otherwise.
static boolean isStrict​(int mod)
Return true if the integer argument includes the strictfp modifier, false otherwise.
static boolean isSynchronized​(int mod)
Return true if the integer argument includes the synchronized modifier, false otherwise.
static boolean isVolatile​(int mod)
Return true if the integer argument includes the volatile modifier, false otherwise.

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

利用反射可以进一步查看编译时还不知道的对象字段
关键方法: Field类中的get方法, f.get(obj)将返回obj的当前f字段值(f是一个Field类型对象, obj是某个包含f字段的对象)
可以使用f.set(obj, value)为obj的f表示的字段设置为新值。
反射机制受限于java的访问限制, 可以调用Field, Method 和 Constructor类中的setAccessible来覆盖访问限制

f.setAccessible(true);

如果仍然不允许访问, 会抛出一个异常。访问会被模块系统或安全管理器拒绝。

当使用反射访问一个模块的非公共特性时, java抛出警告
可以把模块的包打开为无名的模块

java -add-opens java.base/java.util=ALL-UNNAMED

objectAnalyzer/ObjectAnalyzerTest.java

package objectAnalyzer;

import java.util.*;


/**
 * This program uses reflection to spy on objects.
 * @author Cay Horstmann
 */
public class ObjectAnalyzerTest 
{
    public static void main(String[] args)
    throws ReflectiveOperationException
    {
        var squares = new ArrayList<Integer>();
        for (int i = 1; i <= 5; i++)
            squares.add(i * i);
            String str = new ObjectAnalyzer().toString(squares);
        System.out.println(str);
    }
    
}

objectAnalyzer/ObjectAnalyzer.java

package objectAnalyzer;


import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

public class ObjectAnalyzer 
{
    private ArrayList<Object> visited = new ArrayList<>();
    /**
     * Converts an object to a string representation that lists all fields.
     * @param obj an object;
     * @return a string with the object's class name and all field names and values 
     */
    public String toString(Object obj)
    throws ReflectiveOperationException
    {
        if(obj == null) return "null";
        if(visited.contains(obj)) return "...";
        visited.add(obj);
        Class cl = obj.getClass();
        if(cl.isArray())
        {
            String r = cl.getComponentType() + "[]{";
            for (int i = 0; i < Array.getLength(obj); i++)
            {
        //     if(i > 0) r += ",";
                Object val = Array.get(obj, i);
                if(cl.getComponentType().isPrimitive()) r+=val;
                else r += toString(val);

            }
            return r + "}";
        }
        String r = cl.getName();
        //inspect the fields of this class and all superclassws
        do
        {
            r += "[";
            Field[] fields = cl.getDeclaredFields();
            AccessibleObject.setAccessible(fields, true);
            //get the name and values of all fields 
            for (Field f : fields)
            {
                if(!Modifier.isStatic(f.getModifiers()))
                {
                    if(!r.endsWith("[")) r += ",";
                    r += f.getName() + "=";
                    Class t = f.getType();

                    Object val = f.get(obj);
                    if(t.isPrimitive()) 
                        r += val;
                    
                    else r += toString(val);
                    System.out.println(r);
                }
            }
            r += "]";
            cl = cl.getSuperclass();
        }
        while(cl != null);
        return r;
    }
}

API

java.lang.reflect.AccessibleObject
void setAccessible​(boolean flag)
Set the accessible flag for this reflected object to the indicated boolean value.
static void setAccessible​(AccessibleObject[] array, boolean flag)
Convenience method to set the accessible flag for an array of reflected objects with a single security check (for efficiency).
boolean trySetAccessible()
Set the accessible flag for this reflected object to true if possible
java.lang.Class
Field[] getFields()
Returns an array containing Field objects reflecting all the accessible public fields of the class or interface represented by this Class object.
Field getField​(String name)
Returns a Field object that reflects the specified public member field of the class or interface represented by this Class object.
Field getDeclaredField​(String name)
Returns a Field object that reflects the specified declared field of the class or interface represented by this Class object.
Field[] getDeclaredFields()
Returns an array of Field objects reflecting all the fields declared by the class or interface represented by this Class object.
java.lang.reflect.Field
Object get​(Object obj)
Returns the value of the field represented by this Field, on the specified object.
void set​(Object obj, Object value)
Sets the field represented by this Field object on the specified object argument to the specified new value.

使用反射编写泛型数组代码

java.lang.Array类允许动态创建数组
将Employee[]转换为Object[], 再转换回Employee可行,
反之则会生成一个ClassCastException异常。
Array.getLength方法可以获得数组的长度
Array.newInstance方法可以构造一个新数组
Class类的getComponentType方法获得数组元素类型
arrays/CopyOfTest.java

package arrays;

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

/**
 * This program demostrates the use of reflection for manipulation arrays.
 * @author Cay Horstmann
 */
public class CopyOfTest 
{
    public static void main(String[] args)
    {
        int[] a = {1, 2, 3};
        a = (int[]) goodCopyOf(a, 10);
        System.out.println(Arrays.toString(a));
        
        String [] b = {"Tom", "Dick", "Harry"};
        b = (String[]) badCopyOf(b, 10);
        System.out.println(Arrays.toString(b));

        System.out.println("The following call will generate an exception.");
        b = (String[])badCopyOf(b, 10);
    }
    
    /**
     * This method attempts to grow an array by allocating a new array and copying all elements.
     * @param a the array to grow
     * @param newLength the new length
     * @return a larger array that contains all elements of a. However, the returned array has type Object[], not the same type as a
     */
    public static Object[] badCopyOf(Object[] a, int newLength)     //not useful
    {
        var newArray = new Object[newLength];
        System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
        return newArray;
    }

    /**
     * This method grows an array by allocation a new array of the same type and copying all elements.
     * @param a the array to grow.This can be an object array or 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;
     }
}

API

java.ang.reflect.Array
static Object get​(Object array, int index)
Returns the value of the indexed component in the specified array object.
static boolean getBoolean​(Object array, int index)
Returns the value of the indexed component in the specified array object, as a boolean.
static byte getByte​(Object array, int index)
Returns the value of the indexed component in the specified array object, as a byte.
static char getChar​(Object array, int index)
Returns the value of the indexed component in the specified array object, as a char.
static double getDouble​(Object array, int index)
Returns the value of the indexed component in the specified array object, as a double.
static float getFloat​(Object array, int index)
Returns the value of the indexed component in the specified array object, as a float.
static int getInt​(Object array, int index)
Returns the value of the indexed component in the specified array object, as an int.
static long getLong​(Object array, int index)
Returns the value of the indexed component in the specified array object, as a long.
static short getShort​(Object array, int index)
Returns the value of the indexed component in the specified array object, as a short.
static void set​(Object array, int index, Object value)
Sets the value of the indexed component of the specified array object to the specified new value.
static void setBoolean​(Object array, int index, boolean z)
Sets the value of the indexed component of the specified array object to the specified boolean value.
static void setByte​(Object array, int index, byte b)
Sets the value of the indexed component of the specified array object to the specified byte value.
static void setChar​(Object array, int index, char c)
Sets the value of the indexed component of the specified array object to the specified char value.
static void setDouble​(Object array, int index, double d)
Sets the value of the indexed component of the specified array object to the specified double value.
static void setFloat​(Object array, int index, float f)
Sets the value of the indexed component of the specified array object to the specified float value.
static void setInt​(Object array, int index, int i)
Sets the value of the indexed component of the specified array object to the specified int value.
static void setLong​(Object array, int index, long l)
Sets the value of the indexed component of the specified array object to the specified long value.
static void setShort​(Object array, int index, short s)
Sets the value of the indexed component of the specified array object to the specified short value.
static int getLength​(Object array)
Returns the length of the specified array object, as an int.
static Object newInstance​(Class<?> componentType, int length)
Creates a new array with the specified component type and length.
static Object newInstance​(Class<?> componentType, int… dimensions)
Creates a new array with the specified component type and dimensions.

调用任意方法和构造器

Method类的invoke方法允许调用Method对象中的方法
其第一个参数是隐式参数, 其余对象为显示参数
静态方法的第一个参数设置为null
Class类的getMethod方法根据方法的签名返回一个方法
methods/MethodTableTest.java

package methods;

import java.lang.reflect.*;

/**
 * This program shows how to invoke methods through reflection.
 * @author Cay Horstmann
 */

public class MethodTableTest 
{
    public static void main(String[] args)
    throws ReflectiveOperationException, IllegalArgumentException
    {
        //get method pointer to the square and sqrt methods.
        Method square = MethodTableTest.class.getMethod("square", double.class);
        Method sqrt = Math.class.getMethod("sqrt", double.class);
        
        //print tables of x- and y-values
        printTable(1,10, 10, square);
        printTable(1, 10, 10, sqrt);
    }
    /**
     * Return the square of a number
     * @param x a number
     * @return x squared
     */
    public static double square(double x)
    {
        return x * x;
    }

    /**
     * Prints a table with x- and y-values for a method
     * @param from the lower bound for the x-values
     * @param to the upper bound for the x-values
     * @param n the number of rows in the table
     * @param f a method with a double parameter and double return value
     */
    public static void printTable(double from, double to, int n, Method f)
        throws ReflectiveOperationException, IllegalArgumentException
    {
        //print out the method as table header
        System.out.println(f);

        double dx = (to - from) / (n - 1);

        for (double x = from; x <= to; x += dx)
        {
            double y = (Double) f.invoke(null, x);
            System.out.printf("%10.4f | %10.4f%n", x, y);
        }
    }  
}

invoke方法参数和返回值必须是Object类型, 即必须多次来回进行强制类型转换
更好的做法还是使用接口与Iambda表达式

API

java.lang.reflect.Method
Object invoke​(Object obj, Object… args)
Invokes the underlying method represented by this Method object, on the specified object with the specified parameters.

继承的设计技巧

  1. 将公共操作和字段放在超类里
  2. 不要使用受保护的字段
  3. 使用继承实现“is-a”关系
  4. 除非所有继承的方法都有意义, 否则不要使用继承
  5. 在覆盖方法时, 不要改变预期行为
  6. 使用多态, 而不是类型信息
  7. 不要滥用反射
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值