反射的作用:反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
反射的目的:获取一个class对应的Class实例。
反射的实现:
- 通过一个class的静态变量class获取:
Class cls = String.class;
- 若有实例变量,可用实例变量的.getClass()方法获取:
String s = "Hello"; Class cls = s.getClass();
- 若知道class的完整类名,通过Class.forName()获取:
Class cls = Class.forName("java.lang.String");
JVM的动态加载:JVM在执行java程序时,不是一次性将用到的class全部加载到内存,而是第一次使用才加载。
如何使用反射获取字段:
需要注意的时,获取字段的field会经常出现错误,因此需要在方法之前抛出异常。
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
返回的Field类似:
public int Student.score
public java.lang.String Person.name
private int Student.grade
一个Field对象包含了字段的所有信息,包括:
- getName():返回字段名称,例如,“name”;
- getType():返回字段类型,也是一个Class实例,例如,String.class;
- getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
获取字段的值:
字段值的获取是需要在有值时才能执行。
因此使用反射的第二种方法,根据实例值获取字段值。
public class Main {
public static void main(String[] args) throws Exception {
Object p = new Person("Xiao Ming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
f.setAccessible(true);//设置该字段在此类中可访问
Object value = f.get(p);
System.out.println(value); // "Xiao Ming"
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
根据以上demo,步骤为:
- 创建Obj实例对象p。
- 用.getClass()方法获取到实例的Class。
- 根据Class的.getField()方法获取到field。
- 根据获取到的field的get()方法获取到字段的值。
注意:在获取private字段时用到getDeclaredField()方法,但即便获取到,在改类中也用不了,因此为了解决这个问题,将该字段设置为可访问即:f.setAccessible(true);,否则会出现异常。
这是一个获取字段值的方法,体现了反射中不知道该类名并且能够获取到字段值的特点:
public static Object getFieldValue(Object obj,String name) throws Exception {
//设置为静态方法,可以不通过任何实例调用此方法。
//方法参数有两个,第一个obj是传递的类,第二个是传递的类的属性。
Class cla = obj.getClass();//获取到Class
Field field = cla.getDeclaredField(name);//获取到field
field.setAccessible(true);//设置field可访问
return field.get(obj);//返回获取到的字段值
}
设置字段值
有获取就有设置:
设置是通过Field.set(Object, Object)方法实现的。其中第一个是参数指定的实例,第二个是待修改的参数。
public static void setFieldValue(Object obj,String name,String setName) throws Exception {
//方法参数有三个,第一个为传递的类,第二个为传递类的字段名,第三个为设置的字段的值。
Class cla = obj.getClass();
Field field = cla.getDeclaredField(name);
field.setAccessible(true);
field.set(obj, setName);
}
反射中的方法
和字段一样,Class类也提供了获取Method的方法:
- Method getMethod(name, Class…):获取某个public的Method(包括父类)
- Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
- Method[] getMethods():获取所有public的Method(包括父类)
- Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
与字段不同的是,getMethod()的参数可以有两个,第一个是方法名,第二个是参数类型,可以无参数。
public class Main {
public static void main(String[] args) throws Exception {
Class stdClass = Student.class;
// 获取public方法getScore,参数为String:
System.out.println(stdClass.getMethod("getScore", String.class));
// 获取继承的public方法getName,无参数:
System.out.println(stdClass.getMethod("getName"));
// 获取private方法getGrade,参数为int:
System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
}
}
class Student extends Person {
public int getScore(String type) {
return 99;
}
private int getGrade(int year) {
return 1;
}
}
class Person {
public String getName() {
return "Person";
}
}
一个Method的对象包含的方法有:
- getName():返回方法名称,例如:“getScore”;
- getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
- getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
- getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
获取到的方法调用:
使用Method.invoke(实例对象,参数);
public class Main {
public static void main(String[] args) throws Exception {
// String对象:
String s = "Hello world";
// 获取String substring(int)方法,参数为int:
Method m = String.class.getMethod("substring", int.class);
// 在s对象上调用该方法并获取结果:
String r = (String) m.invoke(s, 6);
// 打印调用结果:
System.out.println(r);
}
}
如果调用的是一个静态方法,则无需传入对象,invoke()方法的第一个参数填null
如果设置方法属性为public,则用.setAccessible()方法,参数为true,但注意有时候会报错。
接下来是一个例子,用于演示反射调用方法:
package test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class testMethod {
public static void main(String[] args) throws Exception {
testMethod t = new testMethod();
Method method1 = getMethod(t, "print", int.class);
method1.invoke(t, 1);
Method method = getMethod(t, "print", int.class,String.class);
method.invoke(t, 1,"这是一首简单的小情歌");
}
//使用java中的可变参数,注意可变参数一定要放在最后,因为当你可变参数之后还有参数的话,那么最后一个参数一定不能成功赋值
public static Method getMethod(Object obj,String methodname,Class... classes) throws Exception {
//参数:第一个实例对象,第二个获取的方法名,第三个参数类型,第四个参数值。
Class class1 = obj.getClass();//获取实例的类
//根据方法名和参数类型获取方法
Method method = class1.getMethod(methodname, classes);
//调用方法
return method;
}
//重载,一个参数
public void print(int a) {
// TODO Auto-generated method stub
System.out.println(a);
}
//重载,两个参数
public void print(int a,String text) {
// TODO Auto-generated method stub
System.out.println(a+text);
}
}
结果
1
1这是一首简单的小情歌
反射如何调用构造方法
反射中调用无参构造方法:
Class提供了一个方法是newInstance():供其创建一个新的实例,仅限于无参构造方法
Person p = Person.class.newInstance();
调用有参数的构造方法:
java反射的API提供了Constructor对象,它包含了构造方法的所有信息,能够去创建一个实例。
Constructor与Method非常相似,都是可以创建一个方法,不同的是Constructor创建的是一个构造方法。
使用Method和Constructor返回的都是该对象方法的一个实例,获得实例之后,调用的invoke和newInstance方法是Method和Constructor的方法(这里的newInstance和Class提供的newInstance方法不同。)
通过Class实例获取Constructor的方法:
- getConstructor(Class…):获取某个public的Constructor;
- getDeclaredConstructor(Class…):获取某个Constructor;
- getConstructors():获取所有public的Constructor;
- getDeclaredConstructors():获取所有Constructor。
public class TestConstructor {
public static void main(String[] args) throws Exception {
Class class1 = Person.class;
//参数和Method一样,是该构造方法传递的参数类型
Constructor constructor = class1.getConstructor(String.class);
//创建实例对象
Object object = constructor.newInstance("张三");
//创建getName方法对象
Method m = getMethod(object, "getName");
//调用getName方法
System.out.println(m.invoke(object));
}
public static Method getMethod(Object obj,String methodname,Class... classes) throws Exception {
//参数:第一个实例对象,第二个获取的方法名,第三个参数类型,第四个参数值。
Class class1 = obj.getClass();//获取实例的类
//根据方法名和参数类型获取方法
Method method = class1.getMethod(methodname, classes);
//调用方法
return method;
}
}
向上获取类(父类或接口)
获取父类:用Class的getSuperclass()方法
获取接口:用Class的getInterface()方法
继承关系:instanceof判断的是该实例是否属于该类型
isAssignableForm()判断的是前者是否能够向上转型为后者
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
代理
借鉴链接
静态代理:
一个接口,一个被代理类,一个代理类,代理和被代理类均继承这个接口,被代理类不做实际操作,一切由代理类来完成,在代理类中创建被代理类的对象进行操作。
接口:
public interface HelloInterface {
void sayHello();
}
被代理类:
public class Hello implements HelloInterface{
@Override
public void sayHello() {
System.out.println("Hello zhanghao!");
}
}
代理类:
public class HelloProxy implements HelloInterface{
private HelloInterface helloInterface = new Hello();
@Override
public void sayHello() {
System.out.println("Before invoke sayHello" );
helloInterface.sayHello();
System.out.println("After invoke sayHello");
}
}
调用代理类:
public static void main(String[] args) {
HelloProxy helloProxy = new HelloProxy();
helloProxy.sayHello();
}
输出:
Before invoke sayHello
Hello zhanghao!
After invoke sayHello
使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。
动态代理:
动态创建接口对象的方式叫做动态代理。
在运行期动态创建一个interface实例的方法如下:
- 定义一个InvocationHandler实例,它负责实现接口的方法调用;
- 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
- 使用的ClassLoader,通常就是接口类的ClassLoader;
- 需要实现的接口数组,至少需要传入一个接口进去;
- 用来处理接口方法调用的InvocationHandler实例。 - 将返回的Object强制转型为接口。
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};//创建接口的对象
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
//一个接口
interface Hello {
void morning(String name);
}