反射
Java的反射是指程序在运行期可以拿到一个对象的所有信息。
Class类
除了int等基础类型,Java的其他类型都是class(包括interface)。而class是在JVM中动态加载的,当JVM第一次读取到某个数据类型时,会在内存中创建一个与之关联的Class类的实例。
Class类:
public final class Class { private Class() {} }构造方法是private,这表明只有JVM才能调用此构造方法,java程序是无法创建Class实例的。
JVM内部加载String类:
Class cls = new Class(String);JVM持有的每个Class实例都指向一个数据类型(class或interface),并且每个实例都保存了该class(即数据类型)的所有信息,包括类名、包名、父类、方法、域等等。
获取一个class的Class实例
通过一个class的静态变量获取:
Class cls = String.class;通过一个class方法的getClass方法获取:
String s = "Hello"; Class cls = s.getClass();在forName方法中,指定完整类名获取:
Class cls = Class.forName("java.lang.String");
注意:这三种方式获取的Class实例都是同一个实例,因为JVM对每个加载的Class只创建一个Class实例来表示它的类型。
比较两个class实例
用instanceof不但匹配指定类型,还匹配指定类型的子类。而用==判断class实例可以精确地判断数据类型,但不能作子类型比较。
通过Class实例来创建对应数据类型的实例
// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();动态加载
// Main.java
public class Main {
public static void main(String[] args) {
if (args.length > 0) {
create(args[0]);
}
}
static void create(String name) {
Person p = new Person(name);
}
}当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。
利用JVM动态加载class的特性,我们才能在运行期根据条件加载不同的实现类。例如,Commons Logging总是优先使用Log4j,只有当Log4j不存在时,才使用JDK的logging。
访问字段
Class类提供了以下几个方法来获取字段:
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(包括private的field,但不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(包括private的field,但不包括父类)
一个Field对象包含了一个字段的所有信息:
- getName():返回字段名称,例如,”name”;
- getType():返回字段类型,也是一个Class实例,例如,String.class;
- getModifiers():返回字段的修饰符,它是一个int值,不同的值表示不同的含义。
public class Hello {
private final String gesture = "hi";
public static void main(String[] args) {
Class cl = Hello.class;
Field f;
try {
f = cl.getDeclaredField("gesture");
System.out.println(f.getName());
//wil print gesture
System.out.println(f.getType());
//will print class java.lang.String
int n = f.getModifiers();
System.out.println(n);
//will print 18
System.out.println(Modifier.isFinal(n));
//will print true
System.out.println(Modifier.isStatic(n));
//will print false
System.out.println(Modifier.isProtected(n));
//will print false
System.out.println(Modifier.isPublic(n));
//will print false
System.out.println(Modifier.isPrivate(n));
//will print true
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}获取字段值
用Field.get(Object)获取指定实例的指定字段的值。
当需要获取的字段为private时,可以写一句:
f.setAccessible(true);这样就可以访问了。
public class Hello {
public static void main(String[] args) {
Class cl = Person.class;
try {
Field f = cl.getDeclaredField("name");
Person aperson = new Person("秦锋");
f.setAccessible(true);
System.out.println(f.get(aperson));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
}问题:如果使用反射可以获取private字段的值,那么封装还有意义吗?
答案是正常情况下,我们总是通过p.name来访问Person的name字段,编译器会根据private等修饰符决定是否允许访问字段,这样就达到了数据封装的目的。
而反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。
而且setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。(廖老师yyds)
设置字段值
设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。
public class Hello {
public static void main(String[] args) {
Class cl = Person.class;
try {
Field f = cl.getDeclaredField("name");
Person aperson1 = new Person("秦锋");
f.setAccessible(true);
f.set(aperson1, "周玉豪");
System.out.println(f.get(aperson1));
//will print 周玉豪
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
}调用方法
- Class类提供了以下几个方法来获取Method:
- Method getMethod(name, Class…):获取某个public的Method(包括父类)
- Method getDeclaredMethod(name, Class…):获取当前类的某个Method(包括private,但不包括父类)
- Method[] getMethods():获取所有public的Method(包括父类)
- Method[] getDeclaredMethods():获取当前类的所有Method(包括private,但不包括父类)
- 一个Method对象包含一个方法的所有信息:
- getName():返回方法名称,例如:”getScore”;
- getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
- getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
- getModifiers():返回方法的修饰符,它是一个int值,不同的值表示不同的含义。
- 对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
- 使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。
public class Hello {
public static void main(String[] args) {
Class cl = Person.class;
try {
Method m = cl.getMethod("print", null);
//没有参数,写null
Person aperson = new Person("秦锋");
m.invoke(aperson, null);
//will print 秦锋
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void print() {
System.out.println(this.name);
}
}调用静态方法
invoke方法传入的第一个参数为null。
public class Hello {
public static void main(String[] args) {
Class cl = Math.class;
try {
Method m = cl.getMethod("sqrt", double.class);
System.out.println(m.invoke(null, 3));
// will print 1.7320508075688772
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}调用非public方法
通过Method.setAccessible(true)允许其调用。
public class Hello {
public static void main(String[] args) {
Class cl = Person.class;
Person aperson = new Person("秦锋");
try {
Method m = cl.getDeclaredMethod("print", null);
m.setAccessible(true);
m.invoke(aperson, null);
//will print 秦锋
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
private void print() {
System.out.println(this.name);
}
}调用构造方法
通过调用Class.newInstance()方法来创建实例:
public class Hello { private String s = "hello"; public static void main(String[] args) { try { Hello p = Hello.class.newInstance(); System.out.println(p.s); //will print hello } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }局限:它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
通过获取Constructor对象来创建实例
通过Class实例获取Constructor的方法如下:
- getConstructor(Class…):获取某个public的Constructor;
- getDeclaredConstructor(Class…):获取某个Constructor;
- getConstructors():获取所有public的Constructor;
- getDeclaredConstructors():获取所有Constructor。
注意:以上方法均和父类无关。
public class Hello { public static void main(String[] args) { Class cl = Person.class; try { Constructor cons = cl.getDeclaredConstructor(String.class); cons.setAccessible(true); Person p = (Person) cons.newInstance("秦锋"); p.print(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class Person { private String name; private Person(String name) { this.name = name; } public void print() { System.out.println(this.name); } }**注意:**和Field、Method一样,当要访问非public的构造方法时,需要设置cons.setAccessible(true)。
获取继承关系
获取父类的Class
有了Class实例,我们还可以获取它的父类的Class:
public class Hello {
public static void main(String[] args) {
Class cl = Integer.class;
while (cl.getSuperclass() != null) {
System.out.println(cl.getSuperclass());
cl = cl.getSuperclass();
}
//will print
//class java.lang.Number
//class java.lang.Object
}
}获取interface
通过Class我们就可以查询到实现的接口:
public class Hello {
public static void main(String[] args) {
Class cl = Integer.class;
Class[] cls = cl.getInterfaces();
for (Class c : cls) {
System.out.println(c);
}
//will print
//interface java.lang.Comparable
//interface java.lang.constant.Constable
//interface java.lang.constant.ConstantDesc
}
}判断向上转型是否成立
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。
public class Hello {
public static void main(String[] args) {
System.out.println(Integer.class.isAssignableFrom(Integer.class));
// will print true
System.out.println(Number.class.isAssignableFrom(Integer.class));
//will print true
System.out.println(Integer.class.isAssignableFrom(Number.class));
//will print false
System.out.println(Object.class.isAssignableFrom(String.class));
//will print true
}
}动态代理
Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。
- 在运行期动态创建一个interface实例的方法如下:
定义一个InvocationHandler实例,它负责实现接口的方法调用;
通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
使用的ClassLoader,通常就是接口或者被代理的对象的ClassLoader;
需要实现的接口数组,至少需要传入一个接口进去;
用来处理接口方法调用的InvocationHandler实例。
将返回的Object强制转型为接口。
- 当使用已创建的代理实例去调用方法时,会转而去调用InvocatonHandler中的invoke方法。
public class Hello {
public static void main(String[] args) {
Person st = new Student();
InvocationHandler ih = new InvocationHandler() {
private Object target = st;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
method.invoke(st, args);
//调用st中的method
//will print 已调用speak方法
return null;
}
};
//创建代理接口的实例
Person pr = (Person) Proxy.newProxyInstance(st.getClass().getClassLoader(), st.getClass().getInterfaces(), ih);
pr.speak();
}
}
interface Person {
void speak();
}
class Student implements Person {
public void speak() {
System.out.println("已调用speak方法");
}
}