Java反射

Posted gzhjj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java反射相关的知识,希望对你有一定的参考价值。

在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。

反射主要有两个重要功能:

  1. 可以通过反射机制发现对象的类型,发现类型的方法、属性、构造器。
  2. 可以创建对象并访问任意对象方法和属性等。

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 main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After 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 Toy
implements 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: FancyToy
Canonical name : typeinfo.toys.FancyToy
Class name: typeinfo.toys.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name : typeinfo.toys.HasBatteries
Class name: typeinfo.toys.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name : typeinfo.toys.Waterproof
Class name: typeinfo.toys.Shoots is interface? [true]
Simple name: Shoots
Canonical name : typeinfo.toys.Shoots
Class name: typeinfo.toys.Toy is interface? [false]
Simple name: Toy
Canonical name : typeinfo.toys.Toy

下面的代码片段就是通过getClass()来获取Class引用的。

Class的newInstance()方法表明:“我不知道你的确切类型,但是无论如何要正确地创建你自己”。
使用newInstance()来创建的类,必须带有默认的构造器。

2.3 使用类字面常量来生成对Class对象的引用(例如 Example.class)

类字面常量不仅可以用于普通的类,也可以应用于接口、数组以及基本数据类型。
对于基本数据类型的包装类,还有一个标准字段TYPE。
TYPE字段是一个引用,指向对应的基本数据类型的Class对象。

当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象。
为了使用类而做的准备工作实际包含三个步骤:

  1. 加载。这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个Class对象。
  2. 链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
  3. 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。

初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行。

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 ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74

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.Date
public 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.Date
private static final sun.util.calendar.BaseCalendar gcal
private static sun.util.calendar.BaseCalendar jcal
private transient long fastTime
private transient sun.util.calendar.BaseCalendar$Date cdate
private static int defaultCenturyStart
private static final long serialVersionUID
private static final [Ljava.lang.String; wtb
private 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()));
  }
}

输出结果:

doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse 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);
  }
}

输出结果:

doSomething
somethingElse bonobo
**** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null
doSomething
**** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@53bd815b
  bonobo
somethingElse bonobo

通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个类加载器。
动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递给一个“实际”对象的引用,从而使得调用处理器在执行其中任务时,可以将请求转发。

7.空对象

当你使用内置的null表示缺少对象时,在每次使用引用时都必须测试其是否为null,这显得枯燥,而且势必产生相当乏味的代码。问题在于null除了在你试图用它执行任何操作来产生NullPointerException之外,它自己没有其他任何行为。有时引入空对象的思想将会很有用,它可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何“真实”对象的值。通过这种方式,你可以假设所有的对象都是有效的,而不必浪费编程精力去检查null
推荐阅读:java设计模式之空对象模式

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.C
public C.g()
package C.u()
protected C.v()
private C.w()

如果你知道方法名,你就可以在其Method对象上调用setAccessible(true)
没有任何方式可以阻止反射到达并调用那些非公共访问权限的方法。对于域来说,的确如此,即便是private域。

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): 1
i = 47, I\'m totally safe, Am I safe?
f.get(pf): I\'m totally safe
i = 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域实际上在遭遇修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改尝试,但是实际上不会发生任何修改。

以上是关于Java反射的主要内容,如果未能解决你的问题,请参考以下文章

反射机制入门

反射机制入门

反射机制入门

使用反射在外部JAR / CLASS上调用包含Hibernate事务的方法(Java EE)

为啥我的 Ray March 片段着色器反射纹理查找会减慢我的帧速率?

OpenGL片段着色器不照亮场景