在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
反射主要有两个重要功能:
- 可以通过反射机制发现对象的类型,发现类型的方法、属性、构造器。
- 可以创建对象并访问任意对象方法和属性等。
Class类的实例表示正在运行的Java应用程序的类和接口。
通过Class实例可以获取某个类的属性(Field)、构造器(Constructor)、方法(Method)。 程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建实例,能通过Field对象直接访问并修改对象的属性值。1.反射相关的主要API
- java.lang.Class
- java.lang.reflect.Method
- java.lang.reflect.Field
- java.lang.reflect.Constructor
Class类与java.lang.reflect类库一起对反射的概念进行了支持。该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的。
2.通过反射机制获取类的三种方法
每个类都有一个Class对象。
一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。2.1 Class.forName()
Class.forName()是取得Class对象的引用的一种方法。
只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。 使用Class.forName(),你不需要为了获得Class引用而持有该类型的对象。class Candy { static { System.out.println("Loading Candy"); }}class Gum { static { System.out.println("Loading Gum"); }}class Cookie { static { System.out.println("Loading Cookie"); }}public class SweetShop { public static void main(String[] args) { System.out.println("inside main"); new Candy(); System.out.println("After creating Candy"); try { Class.forName("Gum"); } catch(ClassNotFoundException e) { System.out.println("Couldn't find Gum"); } System.out.println("After Class.forName(\"Gum\")"); new Cookie(); System.out.println("After creating Cookie"); }}
输出结果:
inside mainLoading CandyAfter creating CandyLoading GumAfter Class.forName("Gum")Loading CookieAfter creating Cookie
注意:在传递给Class.forName()的字符串中,你必须使用全限定名(包含包名)。
2.2 getClass()
如果你已经拥有一个类型的对象,那就可以通过调用getClass()方法来获取Class引用了。
package typeinfo.toys;interface HasBatteries {}interface Waterproof {}interface Shoots {}class Toy { // Comment out the following default constructor // to see NoSuchMethodError from (*1*) Toy() {} Toy(int i) {}}class FancyToy extends Toyimplements HasBatteries, Waterproof, Shoots { FancyToy() { super(1); }}public class ToyTest { static void printInfo(Class cc) { System.out.println("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]"); System.out.println("Simple name: " + cc.getSimpleName()); System.out.println("Canonical name : " + cc.getCanonicalName()); } public static void main(String[] args) { Class c = null; try { c = Class.forName("typeinfo.toys.FancyToy"); } catch(ClassNotFoundException e) { System.out.println("Can't find FancyToy"); System.exit(1); } printInfo(c); for(Class face : c.getInterfaces()) printInfo(face); Class up = c.getSuperclass(); Object obj = null; try { // Requires default constructor: obj = up.newInstance(); } catch(InstantiationException e) { System.out.println("Cannot instantiate"); System.exit(1); } catch(IllegalAccessException e) { System.out.println("Cannot access"); System.exit(1); } printInfo(obj.getClass()); }}
输出结果:
Class name: typeinfo.toys.FancyToy is interface? [false]Simple name: FancyToyCanonical name : typeinfo.toys.FancyToyClass name: typeinfo.toys.HasBatteries is interface? [true]Simple name: HasBatteriesCanonical name : typeinfo.toys.HasBatteriesClass name: typeinfo.toys.Waterproof is interface? [true]Simple name: WaterproofCanonical name : typeinfo.toys.WaterproofClass name: typeinfo.toys.Shoots is interface? [true]Simple name: ShootsCanonical name : typeinfo.toys.ShootsClass name: typeinfo.toys.Toy is interface? [false]Simple name: ToyCanonical name : typeinfo.toys.Toy
下面的代码片段就是通过getClass()来获取Class引用的。
Class的newInstance()
方法表明:“我不知道你的确切类型,但是无论如何要正确地创建你自己”。
newInstance()
来创建的类,必须带有默认的构造器。 2.3 使用类字面常量来生成对Class对象的引用(例如 Example.class)
类字面常量不仅可以用于普通的类,也可以应用于接口、数组以及基本数据类型。
对于基本数据类型的包装类,还有一个标准字段TYPE。 TYPE字段是一个引用,指向对应的基本数据类型的Class对象。 当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象。 为了使用类而做的准备工作实际包含三个步骤:- 加载。这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个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 { Class initable = Initable.class; System.out.println("After creating Initable ref"); // Does not trigger initialization: System.out.println(Initable.staticFinal); // Does trigger initialization: System.out.println(Initable.staticFinal2); // 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); }}
输出结果:
After creating Initable ref47Initializing Initable258Initializing Initable2147Initializing Initable3After creating Initable3 ref74
3.获取某个类的所有构造方法
public class ReflectTest1 { public static void main(String[] args) { Date date = new Date(); Class cc = date.getClass(); String className = cc.getName(); System.out.println(className); Constructor[] declaredConstructors = cc.getDeclaredConstructors(); for (Constructor constructor : declaredConstructors) { int modifiers = constructor.getModifiers(); System.out.print(Modifier.toString(modifiers) + " "); System.out.print(constructor.getName() + "("); Class[] paramTypes = constructor.getParameterTypes(); for (Class paramType : paramTypes) { System.out.print(paramType.getName() + " "); } System.out.println(")"); } }}
输出结果:
java.util.Datepublic java.util.Date(java.lang.String )public java.util.Date(int int int int int int )public java.util.Date(int int int int int )public java.util.Date()public java.util.Date(long )public java.util.Date(int int int )
4.获取某个类的所有属性信息
public class ReflectTest2 { public static void main(String[] args) { Date date = new Date(); Class cc = date.getClass(); String className = cc.getName(); System.out.println(className); Field[] fields = cc.getDeclaredFields(); for (Field field : fields) { String modifiers = Modifier.toString(field.getModifiers()); Class type = field.getType(); String name = field.getName(); System.out.println(modifiers + " " + type.getName() + " " + name); } }}
输出结果:
java.util.Dateprivate static final sun.util.calendar.BaseCalendar gcalprivate static sun.util.calendar.BaseCalendar jcalprivate transient long fastTimeprivate transient sun.util.calendar.BaseCalendar$Date cdateprivate static int defaultCenturyStartprivate static final long serialVersionUIDprivate static final [Ljava.lang.String; wtbprivate static final [I ttb
5.获取某个类的所有方法信息
public class ReflectTest3 { public static void main(String[] args) { Date date = new Date(); Class cc = date.getClass(); Method[] methods = cc.getDeclaredMethods(); for (Method method : methods) { String modifiers = Modifier.toString(method.getModifiers()); Class returnType = method.getReturnType(); String name = method.getName(); Class[] parameterTypes = method.getParameterTypes(); Class[] exceptions = method.getExceptionTypes(); System.out.println(modifiers + " " + returnType + " " + name + "(" + Arrays.asList(parameterTypes) + ")throws" + Arrays.asList(exceptions)); } }}
输出结果:
public boolean after([class java.util.Date])throws[]public boolean before([class java.util.Date])throws[]public boolean equals([class java.lang.Object])throws[]public class java.lang.String toString([])throws[]public int hashCode([])throws[]public class java.lang.Object clone([])throws[]public volatile int compareTo([class java.lang.Object])throws[]public int compareTo([class java.util.Date])throws[]······
6.动态代理
代理是基本的设计模式之一。
下面是一个用来展示代理结构的简单示例:interface Interface { void doSomething(); void somethingElse(String arg);}class RealObject implements Interface { public void doSomething() { System.out.println("doSomething"); } public void somethingElse(String arg) { System.out.println("somethingElse " + arg); }} class SimpleProxy implements Interface { private Interface proxied; public SimpleProxy(Interface proxied) { this.proxied = proxied; } public void doSomething() { System.out.println("SimpleProxy doSomething"); proxied.doSomething(); } public void somethingElse(String arg) { System.out.println("SimpleProxy somethingElse " + arg); proxied.somethingElse(arg); }} class SimpleProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { consumer(new RealObject()); consumer(new SimpleProxy(new RealObject())); }}
输出结果:
doSomethingsomethingElse bonoboSimpleProxy doSomethingdoSomethingSimpleProxy somethingElse bonobosomethingElse bonobo
Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理所做的所有调用都会被重定向到单一的调用处理器上。
import java.lang.reflect.*;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); }} class SimpleDynamicProxy { 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); }}
输出结果:
doSomethingsomethingElse bonobo**** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: nulldoSomething**** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@53bd815b bonobosomethingElse bonobo
通过调用静态方法Proxy.newProxyInstance()
可以创建动态代理,这个方法需要得到一个类加载器。
7.空对象
当你使用内置的null
表示缺少对象时,在每次使用引用时都必须测试其是否为null
,这显得枯燥,而且势必产生相当乏味的代码。问题在于null
除了在你试图用它执行任何操作来产生NullPointerException
之外,它自己没有其他任何行为。有时引入空对象的思想将会很有用,它可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何“真实”对象的值。通过这种方式,你可以假设所有的对象都是有效的,而不必浪费编程精力去检查null
。
8.接口与类型信息
interface
关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。如果你编写接口,那么就可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并非是对解耦的一种无懈可击的保障。
package typeinfo.interfacea;public interface A { void f();}
import typeinfo.interfacea.*;class B implements A { public void f() {System.out.println("B.f()");} public void g() {System.out.println("B.g()");}}public class InterfaceViolation { public static void main(String[] args) { A a = new B(); a.f(); // a.g(); // Compile error System.out.println(a.getClass().getName()); if(a instanceof B) { B b = (B)a; b.g(); } }}
通过使用RTTI(Run-Time Type Identification),我们发现a是被当做B实现的。通过将其转型为B,我们可以调用不在A中的方法。
对实现使用包访问权限,这样在包外部的客户端就不能看到它了:
package typeinfo.packageaccess;import typeinfo.interfacea.*;class C implements A { public void f() { System.out.println("public C.f()"); } public void g() { System.out.println("public C.g()"); } void u() { System.out.println("package C.u()"); } protected void v() { System.out.println("protected C.v()"); } private void w() { System.out.println("private C.w()"); }}public class HiddenC { public static A makeA() { return new C(); }}
现在如果你试图将A向下转型为C,则将被禁止,因为在包的外部没有任何C类型可用:
import typeinfo.interfacea.*;import typeinfo.packageaccess.*;import java.lang.reflect.*;public class HiddenImplementation { public static void main(String[] args) throws Exception { A a = HiddenC.makeA(); a.f(); System.out.println(a.getClass().getName()); // Compile error: cannot find symbol 'C': /* if(a instanceof C) { C c = (C)a; c.g(); } */ // Oops! Reflection still allows us to call g(): callHiddenMethod(a, "g"); // And even methods that are less accessible! callHiddenMethod(a, "u"); callHiddenMethod(a, "v"); callHiddenMethod(a, "w"); } static void callHiddenMethod(Object a, String methodName) throws Exception { Method g = a.getClass().getDeclaredMethod(methodName); g.setAccessible(true); g.invoke(a); }}
输出结果:
public C.f()typeinfo.packageaccess.Cpublic C.g()package C.u()protected C.v()private C.w()
如果你知道方法名,你就可以在其Method
对象上调用setAccessible(true)
。
import java.lang.reflect.*;class WithPrivateFinalField { private int i = 1; private final String s = "I'm totally safe"; private String s2 = "Am I safe?"; public String toString() { return "i = " + i + ", " + s + ", " + s2; }}public class ModifyingPrivateFields { public static void main(String[] args) throws Exception { WithPrivateFinalField pf = new WithPrivateFinalField(); System.out.println(pf); Field f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); System.out.println("f.getInt(pf): " + f.getInt(pf)); f.setInt(pf, 47); System.out.println(pf); f = pf.getClass().getDeclaredField("s"); f.setAccessible(true); System.out.println("f.get(pf): " + f.get(pf)); f.set(pf, "No, you're not!"); System.out.println(pf); f = pf.getClass().getDeclaredField("s2"); f.setAccessible(true); System.out.println("f.get(pf): " + f.get(pf)); f.set(pf, "No, you're not!"); System.out.println(pf); }}
输出结果:
i = 1, I'm totally safe, Am I safe?f.getInt(pf): 1i = 47, I'm totally safe, Am I safe?f.get(pf): I'm totally safei = 47, I'm totally safe, Am I safe?f.get(pf): Am I safe?i = 47, I'm totally safe, No, you're not!
但是,final
域实际上在遭遇修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改尝试,但是实际上不会发生任何修改。