Java编程思想学习笔记(14)
类型信息
这章学习如何在运行时识别对象和类的信息的.
主要有两种方式:
-
一种是传统的RTTI它假定我们在编译时已经知道了所有的类型.
-
另一种是反射机制它允许我们在运行时发现和使用类的信息.
为什么需要RTTI(Run-Time Type Information)
例子:
abstract class Shape {
void draw() { System.out.println(this + ".draw()"); }
abstract public String toString();
}
class Circle extends Shape {
public String toString() { return "Circle"; }
}
class Square extends Shape {
public String toString() { return "Square"; }
}
class Triangle extends Shape {
public String toString() { return "Triangle"; }
}
public class Shapes {
public static void main(String[] args) {
List<Shape> shapeList = Arrays.asList(
new Circle(), new Square(), new Triangle()
);
for(Shape shape : shapeList)
shape.draw();
}
}
上面,Shape是基类,其它是它的派生类。
接下来就是创建一个具体对象,把它向上转型为Shape(忽略对象的具体类型)。并在后面的程序中使用匿名(不知道具体类型)的Shape引用。
在main函数中,
-
当把Shape对象放人List< Shape >的数组时会向上转型。但在向上转型为 Shape的时候也丢失了Shape对象的具体类型。对干数组而言,它们只是Shape类的对象。
-
当从数组中取出元素时,这种容器一实际上它将所有的事物都当作Object持有----会自动将结果转型回Shape。这是RITI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTII名字的含义:在运行时,识别一个对象的类型。
-
在这个例子中,RTTI类型转换并不彻底:Object被转型为Shape,而不是转型为Circle、 Square或者Triangle。这是因为目前我们只知道这个List< Shape >保存的都是Shape。在编译时,将由容器和Java的泛型系统来强制确保这一点;而在运行时,由类型转换操作来确保这一点。
-
接下来就是多态机制的事情了,Shape对象实际执行什么样的代码,是由引用所指向的具体 对象Circle、Square或者Triangle而决定的。通常,也正是这样要求的,你希望大部分代码尽可能少地了解对象的具体类型,而是只与对象家族中的一个通用表示打交道(在这个例子中是Shape)。这样代码会更容易写,更容易读,且更便千维护,设计也更容易实现、理解和改变。 所以“多态”是面向对象编程的基本目标。
Class对象
在java世界里,一切皆对象。从某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的。
每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。
Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:
-
加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。
-
链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。
-
初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。
所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。
在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。
一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。
class Candy {
static { print("Loading Candy");}
}
class Cookie {
static { print("Loading Cookie"); }
}
class Gum {
static { print("Loading Gum"); }
}
public class SweetShop {
// 每个类Candy、 Gum和Cookie, 都有一个static子句,
// 该子句在类第一次被加载时执行。这时会有相应的信息打印出来,
// 告诉我们这个类什么时候被加载了。在main()中, 创建对象的代码被置于打印语句之间,
// 以帮助我们判断加载的时间点。
// Class对象仅在需要的时候才被加载, static初始化是在类加载时进行的。
public static void main(String[] args) {
print("inside main");
new Candy();
print("After creating Candy");
try {
// 这个方法是Class类(所有Class对象都属于这个类) 的一个static成员。
// forName()是取得Class 对象的引用的一种方法。
// 它是用一个包含目标类的文本名(注意拼写和大小写)的String作输人参数,
// 返回的是一个Class对象的引用, 上面的代码忽略了返回值。
// 对forNameO的调用是为了它产生的 "副作用”:如果类Gum还没有被加载就加载它。
// 在加载的过程中, Gum的static子旬被执行。
// 无论何时, 只要你想在运行时使用类型信息, 就必须首先获得对恰当的Class对象的引用。
// Class.forN ame()就是实现此功能的便捷途径,
// 因为你不需要为了获得Class引用而持有该类型的对象。
Class.forName("Gum");
} catch(ClassNotFoundException e) {
print("Couldn’t find Gum");
}
print("After Class.forName(\"Gum\")");
new Cookie();
print("After creating Cookie");
}
}
如果你已经拥有了一个感兴趣的类型的对象, 那就可以通过调用getClassO方法来 获取Class引用了, 这个方法属千根类Object的一部分, 它将返回表示该对象的实际类型的Class 引用。Class包含很多有用的方法, 下面是其中的一部分:
interface HasBatteries {
}
interface Waterproof {
}
interface Shoots {
}
class Toy {
// Comment out the following default constructor
// to see NoSuchMethodError from (*1*)
Toy() {}
Toy(int i) {}
}
public class FancyToy extends Toy implements HasBatteries,Waterproof,Shoots{
FancyToy() { super(1); }
}
public class ToyTest {
// printInfo使用getName()来产生全限定的类名,
// 并分别使用getSiinpleName()和 getCanonicalName()
// (在Ja俨vaSES中引入的)来产茬习不含包名的类名和全限定的类名。
// islnterfaceO方法如同其名,可以告诉你这个Class对象是否表示某个接口。
// 因此,通过Class对 象,你可以发现你想要了解的类型的所有信息。
static void printInfo(Class cc) {
print("Class name: " + cc.getName() +
" is interface? [" + cc.isInterface() + "]");
print("Simple name: " + cc.getSimpleName());
print("Canonical name : " + cc.getCanonicalName());
}
public static void main(String[] args) {
// forName方法在适当的try语句块中,创建了一个Class引用,
// 井将其初始化为指向FancyToy Class。
// 注意,在传递给forNameO的字符串中,你必须使用全限定名(包含包名)。
Class c = null;
try {
c = Class.forName("chapter_14.c_14_2.c1.FancyToy");
} catch(ClassNotFoundException e) {
print("Can’t find FancyToy");
System.exit(1);
}
printInfo(c);
System.out.println("_________________________________________");
// 在main()中调用的Class.getlnterfaces()方法返回的是Class对象,
// 它们表示在感兴趣的Class 对象中所包含的接口。
for(Class face : c.getInterfaces())
printInfo(face);
System.out.println("_________________________________________");
// 如果你有一个Class对象,还可以使用getSuperclassO方法查询其直接基类,
// 这将返回你可以用来进一步查询的Class对象。
// 因此,你可以在运行时发现一个对象完整的类继承结构。
Class up = c.getSuperclass();
Object obj = null;
try {
// Requires default constructor:
// Class的newInstance()方法是实现“虚拟构造器”的一种途径,
// 虚拟构造器允许你声明: “我不知道你的确切类型,但是无论如何要正确地创建你自己。”
// 在前面的示例中,up仅仅只是 一个Class引用,在编译期不具备任何更进一步的类型信息。
// 当你创建新实例时,会得到Object 引用,但是这个引用指向的是Toy对象。
obj = up.newInstance();
} catch(InstantiationException e) {
print("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
print("Cannot access");
System.exit(1);
}
printInfo(obj.getClass());
}
}
类字面常量
在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量。
如:
FancyToy.class;
这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要置千t盯语句块中)。 并且它根除了对forName()方法的调用,所以也更高效。因为通过字面量的方法获取Class对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型.
为了使用类而做的准备工作包含三个步骤:
-
加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象;
-
链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用;
-
初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用
才执行
class Initable {
static final int staticFinal = 47;
static final int staticFinal2 =
ClassInitialization.rand.nextInt(1000);
static {
System.out.println("Initializing Initable");
}
}
class Initable2 {
static int staticNonFinal = 147;
static {
System.out.println("Initializing Initable2");
}
}
class Initable3 {
static int staticNonFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws Exception {
// 初始化有效地实现了尽可能的"惰性”。从对initable引用的创建中可以看到,
// 仅使用.class语法来获得对类的引用不会引发初始化。但是,为了产生Class引用,
// Ciass.forNameO立即就进
// 行了初始化,就像在对initabie3引用的创建中所看到的.
Class initable = Initable.class;
System.out.println("After creating Initable ref");
//初始化,就像在对initabie3引用的创建中所看到的.
//如果-个staticfinal值是“编译期常盐",
// 就像Initable.s.taticFinal那样,那么这个值不需要 对initable类进行初始化就可以被读取。
// 但是,如果只是将一个域设置为static和final的,还不足
// 以确保这种行为,例如,对lnitable.staticFinal2的访问将强制进行类的初始化
// ,因为它不是一个编译期常量。
// Does not trigger initialization:
System.out.println(Initable.staticFinal);
// Does trigger initialization:
System.out.println(Initable.staticFinal2);
// 如果一个static域不是final的,那么在对它访间时,总是要求在它被读取之前,
// 要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间),
// 就像在对Initable2.staticNonFinal 的访间中所看到的那样。
// Does trigger initialization:
System.out.println(Initable2.staticNonFinal);
Class initable3 = Class.forName("Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}
-
获取Class对象引用的方式3种,通过继承自Object类的getClass()方法,Class类的静态方法forName()以及字面常量的方式”.class”;
-
其中实例类的getClass()方法和Class类的静态方法forName()都将会触发类的初始化阶段,而字面常量获取Class对象的方式则不会触发初始化;
-
初始化是类加载的最后一个阶段,也就是说完成这个阶段后类也就加载到内存中(Class对象在加载阶段已被创建),此时可以对类进行各种必要的操作了(如new对象,调用静态成员等),注意在这个阶段,才真正开始执行类中定义的Java程序代码或者字节码。
关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化:
-
使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段);
-
使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要;
-
当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化;
-
当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类;
-
当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化(这点看不懂就算了,这是1.7的新增的动态语言支持,其关键特征是它的类型检查的主体过程是在运行期而不是编译期进行的,这是一个比较大点的话题,这里暂且打住)。
泛华的Class引用
Class的引用总是指向某个类的Class对象,利用Class对象可以创建实例类,这也就足以说明Class对象的引用指向的对象确切的类型。
在Java SE5引入泛型后,我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,使得编译期足以确保我们使用正确的对象类型
public class GenericClassReferences {
public static void main(String[] args) {
Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
genericIntClass = Integer.class; // Same thing
//没有泛型的约束,可以随意赋值
intClass = double.class;
// genericIntClass = double.class; // Illegal
// 普通的类引用不会产生警告信息,你可以看到,尽管泛型类引用只能赋值为指向其声明的 类型,
// 但是普通的类引用可以被重新赋值为指向任何其他的qas对象。通过使用泛型语法,
// 可以让编译器强制执行额外的类型检查。
}
}
但是下面语句无法通过编译
Class<Number> numberClass=Integer.class;
因为Integer Class对象不是Number Class对象的子类
我们可以利用通配符“?”来解决问题:
public class WildcardClassReferences {
public static void main(String[] args) {
Class<?> intClass = int.class;
intClass = double.class;
}
}
这样的语句并没有什么问题,毕竟通配符指明所有类型都适用,那么为什么不直接使用Class还要使用Class<?>呢?这样做的好处是告诉编译器,我们是确实是采用任意类型的泛型,而非忘记使用泛型约束,因此Class<?>总是优于直接使用Class,至少前者在编译器检查时不会产生警告信息。当然我们还可以使用extends关键字告诉编译器接收某个类型的子类,如解决前面Number与Integer的问题:
public class BoundedClassReferences {
public static void main(String[] args) {
Class<? extends Number> bounded = int.class;
bounded = double.class;
bounded = Number.class;
// Or anything else derived from Number.
}
}
extends关键字的作用是告诉编译器,只要是Number的子类都可以赋值。这点与前面直接使用Class< Number >是不一样的。实际上,应该时刻记住向Class引用添加泛型约束仅仅是为了提供编译期类型的检查从而避免将错误延续到运行时期。
下面的示例使用了泛型类语法。 它存储了一个类引用,稍候又产生了一个List, 填充这个 List的对象是使用newlnstanceO方法,通过该引用生成的:
class CountedInteger {
private static long counter;
//每次创建一个实例,都会执行一次初始化id=counter++
private final long id = counter++;
public String toString() { return Long.toString(id); }
}
public class FilledList<T> {
private Class<T> type;
public FilledList(Class<T> type) { this.type = type; }
public List<T> create(int nElements) {
List<T> result = new ArrayList<T>();
try {for(int i = 0; i < nElements; i++)
//CountedInteger必须提供默认的构造函数,通过CountedInteger类的Class对象创建一个实例对象
result.add(type.newInstance());
} catch(Exception e) {
throw new RuntimeException(e);
}
return result;
}
public static void main(String[] args) {
FilledList<CountedInteger> fl =
new FilledList<CountedInteger>(CountedInteger.class);
System.out.println(fl.create(15));
}
}
当你将泛型语法用千Class对象是,会发生一件很有趣的事情:newlnstanceO将返回该对象的确切类型,而不仅仅只是在ToyTest.java中看到的基本的Object。这在某种程度上有些受限:
public class GenericToyTest {
public static void main(String[] args) throws Exception {
Class<FancyToy> ftClass = FancyToy.class;
// Produces exact type:
FancyToy fancyToy = ftClass.newInstance();
Class<? super FancyToy> up = ftClass.getSuperclass();
// This won’t compile:
// Class<Toy> up2 = ftClass.getSuperclass();
// Only produces Object:
Object obj = up.newInstance();
}
}
新的转型语法
在Java SE5中新增一种使用Class对象进行类型转换的方式,即cast()方式:
public class Building {
}
public class House extends Building{
}
public class ClassCasts {
public static void main(String[] args) {
Building b = new House();
Class<House> houseType = House.class;
House h = houseType.cast(b);
h = (House)b; // ... or just do this.
}
}
利用Class对象的cast()方法,其参数接收一个参数对象并将其转换为Class引用的类型。当然,如果仔细观察上面的代码,则会发现,与实现了相同功能的main()中最后一行相比,这种转型好像做了很多额外的工作。新的转型语法对于无法使用普通类型的情况显得非常有用,在编写泛型代码时,如果存储了Class引用,并希望以后通过这个引用来执行转型,这种情况就会时有发生。
instanceof 关键字与isInstance方法
instanceof 关键字,它返回一个boolean类型的值,意在告诉我们对象是不是某个特定的类型实例
Man m1 = new Man();
if (m1 instanceof Man) {
System.out.println("---");
}
isInstance方法则是Class类中的一个Native方法,也是用于判断对象类型的
Man m1 = new Man();
if (Man.class.isInstance(m1)) {
System.out.println("+++++++++++");
}
-
事实上instanceOf 与isInstance方法产生的结果是相同的。对于instanceof是关键字只被用于对象引用变量,检查左边对象是不是右边类或接口的实例化。如果被测对象是null值,则测试结果总是false。
-
isInstance方法则是Class类的Native方法,其中obj是被测试的对象或者变量,如果obj是调用这个方法的class或接口的实例,则返回true。如果被检测的对象是null或者基本类型,那么返回值是false
验证isInstance方法与instanceof等价性
public class Base {
}
public class Derived extends Base{
}
public class FamilyVsExactType {
static void test(Object x) {
print("Testing x of type " + x.getClass());
System.out.println("");
print("x instanceof Base " + (x instanceof Base));
System.out.println("");
print("x instanceof Derived "+ (x instanceof Derived));
System.out.println("");
print("Base.isInstance(x) "+ Base.class.isInstance(x));
System.out.println("");
print("Derived.isInstance(x) " +
Derived.class.isInstance(x));
System.out.println("");
System.out.println("___________________________________________");
print("x.getClass() == Base.class " +
(x.getClass() == Base.class));
System.out.println("");
print("x.getClass() == Derived.class " +
(x.getClass() == Derived.class));
System.out.println("");
print("x.getClass().equals(Base.class)) "+
(x.getClass().equals(Base.class)));
System.out.println("");
print("x.getClass().equals(Derived.class)) " +
(x.getClass().equals(Derived.class)));
System.out.println("");
}
public static void main(String[] args) {
test(new Base());
test(new Derived());
}
}
反射:运行时的类信息
如果不知道某个对象的确切类型,RTII可以告诉你。
但是有一个限制:这个类型在编译时必须已知, 这样才能使用RTII识别它,并利用这些信息做一些有用的事。 换句话说,在编译时,编译器必须知道所有要通过RTII来处理的类。
所以这个时候就需要使用反射技术。
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Class类与java.Iang.reflect类库一起对反射的概念进行了支持,该类库包含了Field
Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行 时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用getO和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组(在JDK文档中,通过查找Class类可了解更多相关资料)。
RTTI和反射之间真正的区别只在千, 对RTTI来说,编译器在编译时打开和检查.class文件。(换句话说,我们可以用“普通”方式调 用对象的所有方法。)而对千反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查class文件。
类方法提取器
public class ShowMethods {
private static String usage ="usage:\n" +
"ShowMethods qualified.class.name\n" +
"To show all methods in class or:\n" +
"ShowMethods qualified.class.name word\n" +
"To search for methods involving ‘word’";
private static Pattern p = Pattern.compile("\\w+\\.");
public static void main(String[] args) {
if(args.length < 1) {
print(usage);
System.exit(0);
}
int lines = 0;
try {
Class<?> c = Class.forName(args[0]);
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();
if(args.length == 1) {
for(Method method : methods)
print(
p.matcher(method.toString()).replaceAll(""));
for(Constructor ctor : ctors)
print(p.matcher(ctor.toString()).replaceAll(""));
lines = methods.length + ctors.length;
} else {
for(Method method : methods)
if(method.toString().indexOf(args[1]) != -1) {
print(
p.matcher(method.toString()).replaceAll(""));
lines++;
}
for(Constructor ctor : ctors)
if(ctor.toString().indexOf(args[1]) != -1) {
print(p.matcher(
ctor.toString()).replaceAll(""));
lines++;
}
}
} catch(ClassNotFoundException e) {
print("No such class: " + e);
}
}
}
Class的getMethods()和getConstructors{)方法分别返回Method对象的数组和Constructor对象的数组。这两个类都提供了深层方法,用以解析其对象所代表的方法,并获取其名字输入参数以及返回值。
Constructor类及其用法
public class Person {
private int age;
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
private Person(int age, String name) {
this.age = age;
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "我的基本信息" + age + " " + name;
}
}
public class ConstructorDemo {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> clazz = null;
// 获取Person类对应的Class对象的引用
clazz = Class.forName("chapter_14.c_14_6.demo.Person");
//第一种方法,实例化默认构造方法,Person必须有无参构造函数,否则将抛异常
Person p1 = (Person) clazz.newInstance();
p1.setAge(100);
p1.setName("P1");
System.out.println(p1);
System.out.println("_____________________________________________________________");
//获取带String参数的public构造函数
Constructor cs1 = clazz.getConstructor(String.class);
// 创建Person
Person p2 = (Person) cs1.newInstance("P2");
p2.setAge(90);
System.out.println(p2);
System.out.println("_____________________________________________________________");
//取得指定带int和String参数构造函数,该方法是私有构造private
Constructor cs2 = clazz.getDeclaredConstructor(int.class,String.class);
//由于是private必须设置可访问
cs2.setAccessible(true);
Person p3 = (Person) cs2.newInstance(80,"P3");
System.out.println(p3);
System.out.println("_____________________________________________________________");
//获取所有构造包含private
Constructor<?>[] cons = clazz.getDeclaredConstructors();
//查看每个构造方法需要的参数
for (int i = 0; i < cons.length; i++) {
//获取构造函数参数类型
Class<?> clazzs[] = cons[i].getParameterTypes();
System.out.println("构造函数["+i+"]:"+cons[i].toString() );
System.out.print("参数类型["+i+"]:(");
for (int j = 0; j < clazzs.length; j++) {
if (j == clazzs.length - 1)
System.out.print(clazzs[j].getName());
else
System.out.print(clazzs[j].getName() + ",");
}
System.out.println(")");
}
}
}
Field类及其用法
public class Car {
public float price;
public String name;
private String tech;
}
public class Audi extends Car{
private String desc;
}
public class FieldDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> clazz = Class.forName("chapter_14.c_14_6.demo_1.Audi");
//获取指定name名称、具有public修饰的字段,包含继承字段
Field field = clazz.getField("name");
System.out.println("field:" + field);
System.out.println("-----------------------");
//获取修饰符为public的字段,包含继承字段
Field[] fields = clazz.getFields();
for(Field f:fields) {
System.out.println("field:" + f);
}
System.out.println("-----------------------");
//获取指定name名称的(包含private修饰的)字段,不包括继承的字段
Field field2 = clazz.getDeclaredField("desc");
System.out.println("field2:" + field2);
System.out.println("-----------------------");
//获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
Field[] fields2 = clazz.getDeclaredFields();
for(Field f:fields2) {
System.out.println("field:" + f);
}
System.out.println("-----------------------");
}
Method类及其用法
public class Shape {
public void draw() {
System.out.println("draw");
}
public void draw(int count,String name) {
System.out.println("draw" + name + ",count=" + count);
}
}
public class Circle extends Shape{
private void drawCircle() {
System.out.println("drawCircle");
}
public int getAllCount() {
return 100;
}
}
public class MethodDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class clazz = Class.forName("chapter_14.c_14_6.demo2.Circle");
//返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的public方法,包括继承的方法
Method method = clazz.getMethod("draw", int.class,String.class);
System.out.println("method:" + method);
System.out.println("-----------------------------------------");
//返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的所有public方法
Method[] methods = clazz.getMethods();
for(Method m:methods) {
System.out.println("method:" + m);
}
System.out.println("-----------------------------------------");
//返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的方法,该方法可以是公共、保护、默认(包)访问或者私有方法,但不可以是继承的方法
Method method1 = clazz.getDeclaredMethod("drawCircle");
System.out.println("method:" + method1);
System.out.println("-----------------------------------------");
//返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
Method[] methods1 = clazz.getDeclaredMethods();
for(Method m:methods1) {
System.out.println("method:" + m);
}
System.out.println("-----------------------------------------");
}
}
动态代理
代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替 “实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的 角色。下面是一个用来展示代理结构的简单示例:
public interface Interface {
void doSomething();
void somethingElse(String arg);
}
public class RealObject implements Interface{
public void doSomething() { print(" RealObject doSomething"); }
public void somethingElse(String arg) {
print(" RealObject somethingElse " + arg);
}
}
public class SimpleProxy implements Interface{
private Interface proxied;
public SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
public void doSomething() {
print("SimpleProxy doSomething");
proxied.doSomething();
}
public void somethingElse(String arg) {
print("SimpleProxy somethingElse " + arg);
proxied.somethingElse(arg);
}
}
public class SimpleProxyDemo {
// 因为consumer()接受的Interface,所以它无法知道正在获得的到底是Rea.lObject还是
// SimpleProxy, 因为这二者都实现了Interface。但是SimpleProxy已经被插人到了客户端和
// RealObject之间, 因此它会执行操作, 然后调用RealObject上相同的方法。
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
consumer(new RealObject());
System.out.println("___________________________________________");
consumer(new SimpleProxy(new RealObject()));
}
}
Java的动态代理比代理的思想更向前迈进了一步, 因为它可以动态地创建代理并动态地处理回 对所代理方法的调用。 在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。下面是用动态代理重写的
public class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
public Object
invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("**** proxy: " + proxy.getClass() +
", method: " + method + ", args: " + args);
if(args != null)
for(Object arg : args)
System.out.println(" " + arg);
return method.invoke(proxied, args);
}
}
public class SimpleDynamicProxy {
// 通过调用静态方法Proxy.newProxylnstanceO可以创建动态代理,这个方法蒂要得到一个类加载器
// (你通常可以从已经被加载的对象中获取其类加载器,然后传递给它),
// 一个你希望该代理实现的接口列表(不是类或抽象类), 以及InvocationHandler接口的一个实现。
// 动态代理可以将所有调用重定向到调用处理器,
// 因此通常会向调用处理器的构造器传递给一个 “实际” 对象的引用,
// 从而使得调用处理器在执行其中介任务时, 可以将请求转发。
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
// Insert a proxy and call again:
Interface proxy = (Interface) Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{ Interface.class },
new DynamicProxyHandler(real));
consumer(proxy);
}
}
空对象
有时引人空对象e的思想将会很有用,它可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何“真实”对象的值。通过这种方式,你可以假设所有的对象都是有效的,而不必浪费编程精力去检查null(并阅读所产生的代码)。
有一个简单的例子,许多系统都有一个Person类, 而在代码中,有很多情况是你没有一个实际的人(或者你有,但是你还没有这个人的全部信息),因此,通常你会使用一个null引用并测试它。与此不同的是,我们可以使用空对象。但是即使空 对象可以响应 ”实际” 对象可以响应的所有消息,你仍需要某种方式去测试其是否为空。要达到此目的,最简单的方式是创建一个标记接口:
public interface Null {
}
public class Person {
public final String first;
public final String last;
public final String address;
// etc.
public Person(String first, String last, String address){
this.first = first;
this.last = last;
this.address = address;
}
public String toString() {
return "Person: " + first + " " + last + " " + address;
}
public static class NullPerson
extends Person implements Null {
private NullPerson() { super("None", "None", "None"); }
public String toString() { return "NullPerson"; }
}
public static final Person NULL = new NullPerson();
}
public class Position {
private String title;
private Person person;
public Position(String jobTitle, Person employee) {
title = jobTitle;
person = employee;
if(person == null)
person = Person.NULL;
}
public Position(String jobTitle) {
title = jobTitle;
person = Person.NULL;
}
public String getTitle() { return title; }
public void setTitle(String newTitle) {
title = newTitle;
}
public Person getPerson() { return person; }
public void setPerson(Person newPerson) {
person = newPerson;
if(person == null)
person = Person.NULL;
}
public String toString() {
return "Position: " + title + " " + person;
}
}
public class Staff extends ArrayList<Position> {
public void add(String title, Person person) {
add(new Position(title, person));
}
public void add(String... titles) {
for(String title : titles)
add(new Position(title));
}
public Staff(String... titles) { add(titles); }
public boolean positionAvailable(String title) {
for(Position position : this)
if(position.getTitle().equals(title) &&
position.getPerson() == Person.NULL)
return true;
return false;
}
public void fillPosition(String title, Person hire) {
for(Position position : this)
if(position.getTitle().equals(title) &&
position.getPerson() == Person.NULL) {
position.setPerson(hire);
return;
}
throw new RuntimeException(
"Position " + title + " not available");
}
public static void main(String[] args) {
Staff staff = new Staff("President", "CTO",
"Marketing Manager", "Product Manager",
"Project Lead", "Software Engineer",
"Software Engineer", "Software Engineer",
"Software Engineer", "Test Engineer",
"Technical Writer");
staff.fillPosition("President",
new Person("Me", "Last", "The Top, Lonely At"));
staff.fillPosition("Project Lead",new Person("Janet", "Planner", "The Burbs"));
if(staff.positionAvailable("Software Engineer"))
staff.fillPosition("Software Engineer",
new Person("Bob", "Coder", "Bright Light City"));
System.out.println(staff);
}
}
如果你用接口取代具体类,那么就可以使用DynamicProxy来自动地创建空对象。
public interface Operation {
String description();
void command();
}
public interface Robot {
String name();
String model();
List<Operation> operations();
class Test {
public static void test(Robot r) {
if(r instanceof Null)
System.out.println("[Null Robot]");
System.out.println("Robot name: " + r.name());
System.out.println("Robot model: " + r.model());
for(Operation operation : r.operations()) {
System.out.println(operation.description());
operation.command();
}
}
}
}
public class SnowRemovalRobot implements Robot{
private String name;
public SnowRemovalRobot(String name) {this.name = name;}
public String name() { return name; }
public String model() { return "SnowBot Series 11"; }
public List<Operation> operations() {
return Arrays.asList(
new Operation() {
public String description() {
return name + " can shovel snow";
}
public void command() {
System.out.println(name + " shoveling snow");
}
},
new Operation() {
public String description() {
return name + " can chip ice";
}
public void command() {
System.out.println(name + " chipping ice");
}
},
new Operation() {
public String description() {
return name + " can clear the roof";
}
public void command() {
System.out.println(name + " clearing roof");
}
}
);
}
public static void main(String[] args) {
Robot.Test.test(new SnowRemovalRobot("Slusher"));
}
}