第5章 继承
本章内容:
* 类、超类和子类
* Object:所有类的超类
* 泛型数组列表
* 对象包装器和自动装箱
* 参数数量可变的方法
* 枚举类
* 反射
* 继承设计的技巧
- 利用继承,人们可以基于已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域,以满足新的需求。
- 反射是指在程序运行期间发现更多的类及其属性的能力。
5.1 类、超类和子类
- “is-a”关系是继承的一个明显特征。
- 在Java中,所有的继承都是公有继承,而没有C++中的私有继承和保护继承。
- 关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)、基类(base class)或父类(parent class);新类称为子类(subclass)、派生类(derived class)或者孩子类(child class)。
- 关键字this有两个用途:一是引用隐式参数,而是调用该类其他的构造器。同样,super关键字也有两个用途:一是调用超类的方法,而是调用超类的构造器。调用构造器的语句只能作为另一个构造器的第一条语句出现。构造参数既可以传递给本类(this)的其他构造器,也可以传递给超类(super)的构造器。
- 一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)。
- 在Java中,不需要将方法声明为虚拟方法。动态绑定是默认的处理方式。如果不希望让一个方法具有虚拟特征,可以将它标记为final。
5.1.1 继承层次
- 由一个公共超类派生出来的所有类的集合被称为继承层次(inheritance hierarchy)。在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链(inheritance chain)。
5.1.2 多态
- 有一个用来判断是否应该设计为继承关系的简单规则,这就是“is-a”规则,它表明子类的每个对象也是超类的对象。
- “is-a”规则的另一种表述法是置换法则。它表明程序中出现超类对象的任何地方都可以用子类对象置换。
- 在Java程序设计语言中,对象变量是多态的。不能将一个超类的引用赋给子类变量。
5.1.3 动态绑定
-
调用对象方法的执行过程:
(1) 编译器查看对象的声明类型和方法名。假设调用x.f(param),且隐式参数x声明为C类的对象。需要注意的是:有可能存在多个名字为f,但参数类型不一样的方法。例如,可能存在方法f(int)和方法f(String)。编译器将会一一列举所有C类中名为f的方法和其超类中访问属性为public且名为f的方法(超类的私有方法不可访问)。
至此,编译器已获得所有可能被调用的候选方法。
(2) 接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析(overloading resolutioin)。例如,对于调用x.f(“hello”)来说,编译器将会挑选f(String),而不是f(int)。由于允许类型转换(int可以转换成double,等等),所以这个过程可能很复杂。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。
至此,编译器已获得需要调用的方法名字和参数类型。方法的名字和参数列表称为方法的签名。例如,f(int)和f(String)是两个具有相同名字,不同签名的方法。如果在子类中定义了一个与超类签名相同的方法,那么子类中的这个方法就覆盖了超类中的这个相同签名的方法。
不过,返回类型不是签名的一部分,因此,在覆盖方法时,一定要保证返回类型的兼容性。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。(3)如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,将这种调用方式称为静态绑定(static binding)。与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。在示例中,编译器采用动态绑定的方式生成一条调用f(String)的指令。
(4)当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。假设x的实际类型是D,它是C类的子类。如果D类定义了方法f(String),就直接调用它;否则,将在D类的超类中寻找f(String),以此类推。
每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候,虚拟机仅查找这个表就行了。在例子中,虚拟机搜索D类的方法表,以便寻找与调用f(String)相匹配的方法。这个方法既有可能是D.f(String),也有可能是X.f(String),这里的X是D的超类。如果调用super.f(param),编译器将对隐式参数超类的方法表进行搜索。 动态绑定有一个非常重要的特性:无需对现存在代码进行修改,就可以对程序进行扩展。
- 在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。特别是,如果超类方法是public,子类方法一定要声明为public。经常会发生这类错误:在声明子类方法的时候,遗漏了public修饰符。此时,编译器将会把他解释为试图降低访问权限。
5.1.4 阻止继承:final类和方法
- 有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。
- 类中的特定方法也可以声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地成为final方法)。
- 域也可以被声明为final。对于final域来说,构造对象之后就不允许改变它们的值了。不过,如果将一个类声明为final,只有其中的方法自动地成为final,而不包括域。
- 将方法或域声明为final主要的目的是:确保它们不会在子类中改变语义。
- 在早期的Java中,有些程序员为了避免动态绑定带来的系统开销而使用final关键字。如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程称为内联(inlining)。例如,内联调用e.getName()将被替换为访问e.name域。这是一项很有意义的改进,这是由于CPU在处理调用方法的指令时,使用的分支转移会扰乱预取指令的策略,所以,这被视为不受欢迎的。然而,如果getName在另外一个类中被覆盖,那么编译器就无法知道覆盖的代码将会做什么操作,因此也就不能对它进行内联处理了。
- 幸运的是,虚拟机中的即时编译器比传统编译器的处理能力强得多。这种编译器可以准确地知道类之间的继承关系,并能够检测出类中是否真正地存在覆盖给定的方法。如果方法很简短、被频繁调用且没有真正地被覆盖,那么即时编译器就会将这个方法进行内联处理。如果虚拟机加载了另外一个子类,而在这个子类中包含了对内联方法的覆盖,那么将会发生什么情况呢?优化器将取消对覆盖方法的内联。这个过程很慢,但却很少发生。
5.1.5 强制类型转换
- 将一个类型强制转换成另外一个类型的过程被称为类型转换。
- 进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后,使用对象的全部功能。
- 在Java中,每个对象变量都属于一个类型。类型描述了这个变量所引用的以及能够引用的对象类型。
- 将一个值存入变量时,编译器将检查是否允许该操作。将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量,必须进行类型转换,这样才能够通过运行时的检查。
- 只有在继承层次内进行类型转换。在将超类转换成子类之前,应该使用instanceof进行检查。
5.1.6 抽象类
- 为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。
- 除了抽象方法之外,抽象类还可以包含具体数据和具体方法。
- 扩展抽象类可以有两种选择。一种是在子类中定义部分抽象方法或抽象方法也不定义,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。
- 类即使不含抽象方法,也可以将类声明为抽象类。抽象类不能被实例化。
- 需要注意,可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。例如
Person p = new Student("Vince Vu","Econonices");
5.1.7 受保护访问
- Java中的受保护部分对所有子类及同一个包中的所有其他类都可见。
- Java用于控制可见性的4个访问修饰符:
- 仅对本类可见—-private。
- 对所有类可见—-public。
- 对本包和所有子类可见—-protected。
- 对本包可见—-默认,不需要修饰符。
5.2 Object:所有类的超类
- Object是Java中所有类的始祖,在Java中每个类都是由它扩展而来的。如果没有明确地指出超类,Object就被认为是这个类的超类。
- Object类型的变量只能用于作为各种值的通用持有者。要想对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的类型转换。
- 在Java中,只有基本类型(pimitive types)不是对象,例如,数组、字符和布尔类型的值都不是对象。所有的数组类型,不管是对象数组还是基本类型的数组都扩展于Object类。
5.2.1 equals方法
- Object类中的equals方法用于检测一个对象是佛福等于另一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,他们一定是相等的。
- getClass方法将返回一个对象所属的类。在检测中,只有两个对象属于同一个类时,才有可能相等。
5.2.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,如果为null,返回false。这种检测是很必要的。if(otherObject==null) return false;
。
(4)比较this与otherObject是否属于同一个类。如果equals的语义在每个子类中有所变化,就使用getClass检测:if(getClass() != otherObject.getClass()) return false;
,如果所有的子类都拥有统一的语义,就使用instanceof检测:if(!(otherObject instanceof ClassName)) return false;
。
(5)将otherObject转换为相应的类类型变量:ClassName other = (ClassName)otherObject
。
(6)现在开始对所有需要比较的域进行比较了。使用 比较基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回true;否则返回false。`return field1 other.field1 && Objects.equals(fiels2,other.field2)&&…;`
如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。对于数组类型的域,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等。
-
java.utils.Arrays 1.2
- static Boolean equals(type[] a, type[] b) 5.0
如果两个数组长度相同,并且在对应的位置上数据元素有均相同,将返回true。数组的元素类型可以是Object、int、long、short、char、byte、boolean、float或double。
- static Boolean equals(type[] a, type[] b) 5.0
-
java.util.Object 7
- static boolean equals(Object a,Object b)
如果a和b都为null,
返回true;如果只有其中之一为null,则返回false;否则返回a.equals(b)。
- static boolean equals(Object a,Object b)
5.2.3 hashCode方法
- 散列码(hash code)是由对象导出的一个整型值。散列码是没有规律的。如果x和y是两个不同的对象。x.hashCode()与y.hashCode()基本上不会相同。
- 由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列值,其值为对象的存储地址。
- 字符串的散列值是由内容导出的,s与t的散列值是一样的。字符串缓存sb与tb是不同的。
- 如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。
- hashCode方法应该返回一个整型数值(也可以是负数),并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。
- 需要组合多个散列值时,可以调用Objects.hash并提供多个参数。这个方法会对各个参数调用Objects.hashCode,并组合这些散列值。
- Equals与hashCode的定义必须一致:如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。
- 如果存在数组类型的域,那么可以使用静态的Ayyas.hashCode方法计算一个散列码,这个散列码由数组元素的散列码组成。
-
java.lang.Object 1.0
- int hashCode()
返回对象的散列码。散列码可以是任意的整数,包括正数或负数。两个想等的对象要求返回相等的散列码。
- int hashCode()
-
java.lang.Objects 7
- int hash(Object… objects)
返回一个散列码,由提供的所有对象的散列码组合而得到。 - static int hashCode(Object a)
如果a为null返回0,否则返回a.hashCode()。
- int hash(Object… objects)
-
java.util.Arrays 1.2
- static int hashCode(type[] a) 5.0
计算数组a的散列码。组成这个数组的元素类型可以是object,int,long,short,char,byte,boolean,float或double。
- static int hashCode(type[] a) 5.0
5.2.4 toString方法
- toString方法用于返回表示对象值得字符串。
-
java.lang.Object 1.0
- Class getClass()
返回包含对象信息的类对象。 - boolean equals(Object otherObject)
比较两个对象是否相等,如果两个对象指向同一块存储区域,方法返回true;否则方法返回false。在自定义的类中,应该覆盖这个方法。 - String toString()
返回描述该对象的字符串。在自定义的类中,应该覆盖这个方法。
- Class getClass()
-
java.lang.Class 1.0
- String getName()
返回这个类的名字。 - Class getSuperclass()
以Class对象的形式返回这个类的超类信息。
- String getName()
5.3 泛型数组列表
- ArrayList是一个采用类型参数(type parameter)的泛型类(generic class)。为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来加在后面。
- 如果调用add且内部数组已经满了,数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。如果已经清楚或能够估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法。还可以把初始容量传递给ArrayList构造器,
ArrayList<Employee> staff = new ArrayList<>(100)
。 - 一旦能够确认数组列表的大小不再发生变化,就可以调用trimToSize方法。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间。
- 一旦整理了数组列表的大小,添加新元素就需要花时间再次移动存储块,所以应该在确认不会添加任何元素时,再调用trimToSize。
- java.util.ArrayList<T> 1.2
- ArrayList<T>()
构造一个空数组列表。 - ArrayList<T>(int initialCapacity)
用指定容量构造一个空数组列表。
参数:initalCapacity 数组列表的最初容量 - boolean add(T obj)
在数组列表的尾端添加一个元素。永远返回true。
参数:obj 添加的元素 - int size()
返回存储在数组列表中的当前元素数量。(这个值将小于或等于数组列表的容量。) - void ensureCapacity(int capacity)
确保数组列表在不重新分配存储空间的情况下就能够保存给定数量的元素。 - void trimToSize()
将数组列表的存储容量消减到当前尺寸。
- ArrayList<T>()
5.3.1 访问数组列表元素
- 数组列表自动扩展容量的便利增加了访问元素语法的复杂程度。其原因是ArrarList类并不是Java程序设计语言的一部分;它只是一个由某些人编写且被放在标准库中的一个实用类。
- 使用get和set方法实现访问或改变数组元素的操作,而不使用[]语法格式。
- 只有i小于或等于数组列表的大小时,才能够调用list.set(i,x)。使用add方法为数组添加新元素,而不要使用set方法,它只能替换数组中已经存在的元素内容。
- java.util.ArrayList<T> 1.2
- void set(int index,T obj)
设置数组列表指定位置的元素值,这个操作将覆盖这个位置的原有内容。
参数:index 位置(必须介于0~size()-1之间); obj 新的值 - T get(int index)
获得指定位置的元素值。
参数:index 获得的元素位置(必须介于0~size()-1之间) - void add(int index,T obj)
向后移动元素,以便插入元素。 - T remove(int index)
删除一个元素,并将后面的元素向前移动。被删除的元素由返回值返回。
参数:index 被删除的元素位置(必须介于0~size()-1之间)
- void set(int index,T obj)
5.3.2 类型化与原始数组列表
- 在程序运行时,所有的数组列表都是一样的,即没有虚拟机中的类型参数。因此,类型转换(ArrayList)和(ArrayList<Employee>)将执行相同的运行时检查。
5.4 对象包装器与自动装箱
- 所有的基本雷士都有一个与之对应的类。这些类称为包装器(wrapper)。这些对象包装器类拥有很鲜明的名字:Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前6个类派生于公共的超类Number)。对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final,因此不能定义它们的子类。
- 由于每个值分别包装在对象中,所以ArrayList<Integer>的效率远远低于int[]数组。因此,应该用它构造小型集合,其原因是此时程序员操作的方便性要比执行效率更加重要。
- Java SE 5.0的另一个改进之处是更加便于添加或获取数组云阿苏。下面这个调用
list.add(3)
将自动地变换成list.add(Integer.valueOf(3));
这种变换被称为自动装箱(autoboxing)。 - 相反地,当将一个Integer对象赋给一个int值时,将会自动地拆箱。也就是说,编译器将下列语句:
int n=list.get(i)
翻译成int n = list.get(n).intValue();
。 - 在两个比较器对象比较时调用equals方法。
- 自动装箱规范要求boolean、byte、char<=127,介于-128~127之间的short和int被包装到固定的对象中。
- 装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些自己吗。
-
java.lang.Integer 1.0
- int intValue()
以int的形式返回Integer对象的值(在Number类中覆盖了intValue方法)。 - static String toString(int i)
以一个新String对象的形式返回给定数值i的十进制表示。 - static String toString(int i,int radix)
返回数值i的基于给定radix参数进制的表示。 - static int parseInt(String s)
- static int parseInt(String s,int radix)
返回字符串s表示的整型数值,给定字符串表示的是十进制的整数(第一种方法),或者是radix参数进制的整数(第二种方法)。 - static Integer valueOf(String s)
- static Integer valueOf(String s,int radix)
返回用s表示的整型数值进行初始化后的一个新Integer对象,给定字符串表示的是十进制的整数(第一种方法),或者是radix参数进制的整数(第二种方法)。
- int intValue()
-
java.text.NumberFormat 1.1
- Number parse(String s)
返回数字值,假设给定的String表示了一个数值。
- Number parse(String s)
5.5 参数数量可变的方法
- 在Java SE 5.0以前的版本中,每个Java方法都有固定数量的参数。然而,现在的版本提供了可以用可变的参数数量调用的方法。
5.6 枚举类
- 在比较两个枚举类型的值时,永远不需要调用equals,而直接使用“==”就可以了。
- 如果需要的话,可以在枚举类型中添加一些构造器、方法和域。当然,构造器只是在构造枚举常量的时候被调用。
- 所有的枚举类型都是Enum类的子类。
- 每个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举值的数组。
- java.lang.Enum <E> 5.0
- static Enum valueOf(Class enumClass,String name)
返回指定名字、给定类的枚举常量。 - String toString()
返回枚举常量名。 - int ordinal()
返回枚举常量在enum声明中的位置,位置从0开始计数。 - int compareTo(E other)
如果枚举常量出现在other之前,则返回一个负值;如果this==other,则返回0;否则,返回正值。枚举常量的出现次序在enum声明中给出。
- static Enum valueOf(Class enumClass,String name)
5.7 反射
- 反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。特别是在设计或运行中添加新类时,能够快速地应用开发工具动态地查询新添加类的能力。
- 能够分析类能力的程序称为反射(reflaction)。反射机制的功能及其强大,在下面可以看到,反射机制可以用来:
- 在运行中分析类的能力。
- 在运行中查看对象。
- 实现通用的数组操作代码。
- 利用Method对象,这个对象很像C++中的函数指针。
5.7.1 Class类
- 在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。然而,可以通过专门的Java类访问这些信息。保存这些信息的类被称为Class。Object类中的getClass()方法将会返回一个Class类型的实例。
- 一个Class对象将表示一个特定类的属性。
- 可以调用Class类的静态方法forName获得类名对应的Class对象。如果类名保存在字符串中,并可在运行中改变,就可以使用这个方法。当然,这个方法只有在className是类名或接口名时才能执行。否则,forName方法将抛出一个checkedexception(已检查异常)。无论何时使用这个方法,都应该提供一个异常处理器(exception handler)。·
- 一个Class对象实际上表示的是一个类型,而这个类型未必是一种类。
- 虚拟机为每个类型管理一个Class对象。因此,可以利用==运算符实现两个类对象比较的操作。
- Class类的方法newInstance(),可以用来快速地创建一个类的实例。newInstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。
5.7.2 捕获异常
- 异常有两种类型:未检查异常和已检查异常。对于已检查异常,编译器将会检查是否提供了处理器。未检查异常编译器不会查看是否为这些错误提供了处理器。
- java.lang.Class 1.0
- static Class forName(String className)
返回描述类名为className的Class对象。 - Object newInstance()
返回这个类的一个新实例。
- static Class forName(String className)
- java.lang.reflect.Constructor 1.1
- Object newInstance(Object[] args)
构造一个这个构造器所属类的新实例。
参数:args 这是提供给构造器的参数。
- Object newInstance(Object[] args)
- java.lang.Throwable 1.0
- void printStackTrace()
将Throwable对象和栈的轨迹输出到标准错误流。
- void printStackTrace()
5.7.3 利用反射分析类的能力
- 在java.lang.reflact包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器、这三个类都有一各叫做getName的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属类型的Class对象。Method和Constructor类有能够报告参数类型的方法,它将返回一个整型数值,用不同的位开关描述public和static这样修饰符使用状况。另外,还可以利用java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的整型数值。还可以利用Modifier.toString方法将修饰符打印出来。
- Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class类的getDelareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
- java.lang.Class 1.0
- Field[] getFields() 1.1
- Field[] getDeclaredFields() 1.1
getFields方法将返回一个包含Field对象的数组,这些对象记录了这个类或其超类的公有域。getDeclaredField方法也将返回包含Filed对象的数组,这些对象记录了这个类的全部域。如果类中没有域,或者Class对象描述的是基本类型或数组类型,这些方法将返回一个长度为0的数组。 - Method[] getMethods() 1.1
- Method[] getDeclaredMethods() 1.1
返回包含Methos对象的数组:getMethods将返回所有的公有方法,包括从超类继承来的公有方法;getDeclaredMethods返回这个类或接口的全部方法,但不包括超类继承了的方法。 - Constructor[] getConstructors() 1.1
- Constructor[] getDeclaredConstructors() 1.1
返回包含Constructor对象的数组,其中包含了Class对象所描述的类的所有公有构造器(getConstructors)或所有构造器(getDeclaredConstructors)。
- java.lang.reflact.Field 1.1 java.lang.reflect.Method 1.1 java.lang.reflact.Constructor 1.1
- Class getDeclaringClass()
返回一个用于描述类中定义的构造器、方法或域的Class对象。 - Class[] getExceptionTypes()(在Constructor和Method类中)
返回一个用于描述方法抛出的异常类型的Class对象数组。 - int getModifiers()
返回一个用于描述构造器、方法或域的修饰符的整型数值。使用Modifier类总的这个方法可以分析这个返回值。 - String getName()
返回一个用于描述构造器、方法或域名的字符串。 - Class[] getParameterTypes()(在Constructor和Methos类中)
返回一个用于描述参数类型的Class对象数组。 - Class getReturnType()(在Method类中)
返回一个用于描述返回类型的Class对象。
- Class getDeclaringClass()
- java.lang.reflact.Modifier 1.1
- static String toString(int modifiers)
返回对应modifiers中位设置的修饰符的字符串表示。 - static boolean isAbstract(int modifiers)
- static boolean isFinal(int modifiers)
- static boolean isInterface(int modifiers)
- static boolean isNative(int modifiers)
- static boolean isPrivate(int modifiers)
- static boolean isProtected(int modifiers)
- static boolean isPublic(int modifiers)
- static boolean isStatic(int modifiers)
- static boolean isStrict(int modifiers)
- static boolean isSynchronized(int modifiers)
- static boolean isVolatile(int modifiers)
这些方法将检测方法名中对象的修饰符在modifiers值中的位。
- static String toString(int modifiers)
5.7.4 在运行时使用反射分析对象
- 查看对象域的关键方法是Field类中的get方法。
- 除非拥有访问权限,否则Java安全机制只允许查看任意对象有哪些域,而不允许读取他们的值。
- 反射机制的默认行为受限于Java的访问机制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。
- setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的公共超类。这个特性是为调试、持久存储和相似机制提供的。
- 调用Field的f.set(obj,value)可以将obj对象的f域设置成新值。
- 使用Class的getDeclaredFields获取所有的数据域,然后使用setAccessible将所有的域设置为可访问的。对于每个域,获得了名字和值。
- java.lang.reflect.AccessibleObject 1.2
- void setAccessible(boolean flag)
为反射对象设置可访问标志。flag为true表明屏蔽Java语言的访问检查,使得对象的私有属性也可以被查询和设置。 - boolean isAccessible()
返回反射对象的可访问标志的值。 - static void setAccessible(AccessibleObject[] array,boolean flag)
是一种设置对象数组可访问标志的快捷方法。
- void setAccessible(boolean flag)
- java.lang.Class 1.1
- Field getField(String name)
- Field[] getField()
返回指定名称的公有域,或返回所有域的数组。 - Field getDeclaredField(String name)
- Field[] getDeclaredFields()
返回类中声明的给定名称的域,或者包含声明的全部域的数组。
- java.lang.reflect.Field 1.1
- Object get(Object obj)
返回obj对象中用Field对象表示的域值。 - void set(Object obj,Object newValue)
用一个新值设置Obj对象中Field对象表示的域。
- Object get(Object obj)
5.7.5 使用反射编写泛型数组代码
- java.lang.reflect包中的Array类允许动态地创建数组。
- 可以通过调用Array.getLength(a)获得数组的长度,也可以通过Array类的静态getLength方法的返回值得到任意数组的长度。而要获得新数组元素类型,就需要进行以下工作:
1)首先获得数组的类对象。
2)确认它是一个数组。
3)使用Class类(只能定义表示数组的类对象)的getComponentType方法确定数组对应的类型。 - java.lang.reflect.Array 1.1
- static Object get(Object array,int index)
- static xxx getxxx(Object array,int index)
(xxx是boolean、byte、char、double、float、int、long、short之中的一种基本类型。)
这些方法将返回存储在给定位置上的给定数组的内容。 - static void set(Object array,int index,Object value)
- static setxxx(Object array,int index,xxx newValue)
(xxx是boolean、byte、char、double、float、int、long、short之中的一种基本类型。)
这些方法将一个新值存储到给定位置上的给定数组中。 - static int getLength(Object array)
返回数组的长度。 - static Object newInstance(Class componentType, int length)
- static Object newInstance(Class componentType, int[] lengths)
返回一个具有给定类型、给定维数的新数组。
5.7.6 调用任意方法
- 在Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:
Object invoke(Object obj,Object... args)
,第一个参数是隐式参数,其余的对象提供了显式参数。对于静态方法,第一个参数可以被忽略,即可以将它设置为null。 - 如何得到Method对象呢?当然,可以通过调用getDeclareMethods方法,然后对返回的Method对象数组进行查找,直到发现想要的方法为止。也可以调用Class类中的getMethod方法得到想要的方法。它与getField方法类似。getField方法根据表示域名的字符串,返回一个Field对象。然而,有可能存在若干个相同名字的方法,因此要格外小心,以确保能够准确地得到想要的那个方法。有鉴于此,还必须提供想要的方法的参数类型。getMethod的签名是:
Method getMethod(String name,Class... parameterTypes)
。 - 如果在调用方法的时候提供了一个错误的参数,那么invoke方法将会抛出一个异常。
- invoke的参数和返回值必须是Object类型的。这就意味着必须进行多次的类型转换。这样做将会使编译器错过检查代码的机会。因此,等到测试阶段才会发现这些错误,找到并改正它们将会更加困难。不仅如此,使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些。有鉴于此,建议仅在必要的时候才使用Method对象,而最好使用接口和内部类。特别要重申:建议Java开发者不要使用Method对象的回调功能。使用接口进行回调会使得代码的执行速度更快,更易于维护。
- java.lang.reflect.Method 1.1
- public Object invoke(Object implicitParameter,Object[] explicitParamenters)
调用这个对象所描述的方法,传递给定参数,并返回方法的返回值。对于静态方法,把null作为隐式参数传递。在使用包装器传递基本类型的值时,基本类型的返回值必须是未包装的。
5.8 继承设计的技巧
- 将公共操作和域放在超类。
- 不要使用受保护的域。
protected机制并不能够带来更好的保护,其原因主要有两点。第一,子类集合是无限制的,任何一个人都能够由某个类派生一个子类,并编写代码以直接访问protected的示例域,从而破坏了封装性。第二,在Java程序设计语言中,在同一个包中的所有类都可以访问protected域,而不管它是否为这个类的子类。
protected方法对于指示那些不提供一般用途而应在子类中重新定义的方法很有用。 - 使用继承实现“is-a”关系。
- 除非所有继承的方法都有意义,否则不要使用继承。
- 在覆盖方法时,不要改变预期的方法。
- 使用多态,而非类型信息。
使用多态方法或接口编写的代码比使用对多种类型进行检测的代码更加易于维护和扩展。 - 不要过多地使用反射。