1 反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法和属性, 这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象具有相同的类型,这个相同类型的类就是Class类。
反射优点:运行时确定类型绑定对象,发挥了java的灵活性,体现了多态的应用,降低类之间的耦合性。缺点:对性能有影响
2 获取信息
2.1 获取Class对象的方法
每个类被加载后,系统会为该类生成对应的Class 对象,通过Class 对象可以访问到JVM 中的这个类,
①调用某个类的 Class属性来获取该类对应的Class对象,例如:String.class;
②调用 Java.lang.Object 类中的一个方法调用某个对象的getClass()方法 例如:new Date().getClass();
③Class.forName(String className) 传入字符串必须是完整包名 例如: Class.forName("java.lang.String");
注意:基本的Java 类型(boolean、byte、char、short、int、long、float 和double)和关键字void通过class 属性也表示为Class 对象
Class.isPrimitive() :是否基本数据类型
Class.isArray():是否数组类型
(int.class== Integer.TYPE)为true --> Integer.TYPE为包装类的字节码
(int.class== Integer.class)为false -->Integer.class为class java.lang.Integer, int.class为int
2.2 获取Constructor类:构造方法
①获得类中的全部构造函数:
Constructor<?>[] getConstructors() //获得类的所有公共构造函数
Constructor<?>[] getDeclaredConstructors() //获得类的所有构造函数(不限制访问级别)
②得到某个构造方法:
Constructor<T> getConstructor(Class<?>... parameterTypes):表示类的指定的public 构造方法
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):表示类的指定的构造方法,和访问权限无关
public class User {
private int id;
private String name;
private User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
void show(String name){
System.out.println("my name is"+name);
}
public void say(){
System.out.println("say");
}
private void study(){
System.out.println("study");
}
......
}
public class TestConstructor {
public static void main(String[] args) throws SecurityException, NoSuchMethodException {
Class<User> clz=User.class;
//类的所有公共构造方法
Constructor[] cons=clz.getConstructors();
for(Constructor con:cons ){
System.out.println("1:"+con);
}
//获得类的所有构造函数(不限制访问级别)
cons=clz.getDeclaredConstructors();
for(Constructor con:cons ){
System.out.println("2:"+con);
}
//得到指定的构造器,必须public
Constructor<User> c1=clz.getConstructor(int.class,String.class);
System.out.println("3:"+c1);
//私有的报错java.lang.NoSuchMethodException: User.<init>()
//Constructor<User> c2=clz.getConstructor();
//可获取私有的构造器
Constructor<User> c2=clz.getDeclaredConstructor();
System.out.println("4:"+c2);
}
打印输出为
1:public com.User(int,java.lang.String)
2:public com.User(int,java.lang.String)
2:private com.User()
3:public com.User(int,java.lang.String)
4:private com.User()
2.3获取Field:成员变量
①获得类中的所有属性:
getFields()://获取所有的公有属性
getDeclaredFields();//获得所有的属性
②得到某个属性:
getField(String name);//获得指定名字的公有的属性
getDeclaredField(String name);//获得指定名字的公有的属性,不限制访问级别
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println("getFields:" + field);
}
// 得到不受修饰符限定的字段
fields = clz.getDeclaredFields();
for (Field field : fields) {//
System.out.println("getDeclaredFields:" + field);
} // 得到public类型的特定字段
Field fi = clz.getField("sex");//不是对象上的变量,而是类上的,要用它通过get方法取某个对象上对应的值
System.out.println("getField:" + fi);
2.4 获取Method:类中成员方法
①获得类中的所有方法:
Method[] getMethods():返回该Class 对象表示类和其父类的所有public 方法;
Method[] getDeclaredMethods():获得类所有的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法;
②获取类中的某个方法:
Method getMethod(String name, Class<?> ... parameterTypes):返回该Class 对象表示类和其父类的指定的public 方法;
Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回该Class 对象表示类的指定的方法。和访问权限无关,但不包括继承的方法;
Class<User> clz = User.class;
// 获取所有的public修饰的方法,包含父类的方法
Method[] m = clz.getMethods();
for (Method method : m) {
System.out.println("getMethods:" + method);
}
// 访问所有方法,不受访问权限影响
m = clz.getDeclaredMethods();
for (Method method : m) {
System.out.println("getDeclaredMethods:" + method);
}
// 获取指定的方法,只有public方法
Method say = clz.getMethod("say", null);
System.out.println("getMethod: " + say);
// 获取指定的方法,不受访问权限影响
Method show = clz.getDeclaredMethod("show", String.class);
System.out.println("getDeclaredMethod:" + show);
// getDeclaredMethod不能访问父类方法,报错java.lang.NoSuchMethodException
Method toString = clz.getDeclaredMethod("toString");
System.out.println("访问父类方法:" + toString);
输出
getMethods:public void com.User.say()
getMethods:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
getMethods:public final void java.lang.Object.wait() throws java.lang.InterruptedException
getMethods:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
getMethods:public boolean java.lang.Object.equals(java.lang.Object)
getMethods:public java.lang.String java.lang.Object.toString()
getMethods:public native int java.lang.Object.hashCode()
getMethods:public final native java.lang.Class java.lang.Object.getClass()
getMethods:public final native void java.lang.Object.notify()
getMethods:public final native void java.lang.Object.notifyAll()
getDeclaredMethods:public void com.User.say()
getDeclaredMethods:void com.User.show(java.lang.String)
getDeclaredMethods:private void com.User.study()
getMethod: public void com.User.say()
getDeclaredMethod:void com.User.show(java.lang.String)
2.5 获取Interface:接口信息
①getGenericInterfaces()//返回表示某些接口的Type,这些接口由此对象所表示的类或接口直接实现。
②getInterfaces//确定此对象所表示的类或接口实现的接口。
2.6获取SuperClass:父类信息
①getSuperclass// 返回表示此Class
所表示的实体(类、接口、基本类型或 void)的超类的class
。
2.7获取Annotation注解
①对Class、Method、Field以及Constructor对象调用getAnnotation()方法,可以获得与对象关联的特定信息
<A extends Annotation> getAnnotation(Class<A> annoType)
定义注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str();
int val();
}
@Retention(RetentionPolicy.RUNTIME)
@interface What {
String description();
}
@MyAnno(str = "example", val = 200)
public static void myMeth() {
Meta meta = new Meta();
Class clz = meta.getClass();
try {
Method m = clz.getMethod("myMeth");
// MyAnno类型的Class对象,即注解
MyAnno anno = m.getAnnotation(MyAnno.class);
System.out.println(anno.str() + "--" + anno.val());//输出example--200
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
②Annotation[ ] getAnnotations( ) 可以获取与某个条目关联的具有RUNTIME保留策略的所有注解
@MyAnno(str = "example", val = 200)
@What(description = "An annotation test method")
public static void myMeth() {
Meta meta = new Meta();
Class clz = meta.getClass();
try {
Method m = clz.getMethod("myMeth");
// MyAnno类型的Class对象,即注解
Annotation[] anno = m.getAnnotations();
for (Annotation a : anno) {
System.out.println(a);
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
输出 @annotation.MyAnno(str=example, val=200)
@annotation.What(description=An annotation test method)</span>
2.8获取泛型信息
(1)普通Field ,通过指定对应的Class 对象,程序可以获得该类里面所有的Field,再用getType()来获取其类型。
Class<?> type = field.getType();//获得字段的类型
(2)Field 有泛型修饰,如Map<String,Integer>使用;Type gType = f.getGenericType()得到泛型类型
然后将Type 对象强转为ParameterizedType,其表示增加泛型后的类型
Type getRawType()//返回被泛型限制的类型;
Type[] getActualTypeArguments()//返回泛型参数类型;
步骤:
1.获取当前类
2.获取目标字段
3.获取包含泛型类型的类型getGenericType()
4.强转至子类ParameterizedType 因为Type 没有任何对应的方法
5.获得泛型真正的类型getActualTypeArguments()
public class TestField {
Map<Integer, String> map = new HashMap<Integer, String>();
public static void main(String[] args) throws SecurityException,
NoSuchFieldException {
Class clz = TestField.class;
Field field = clz.getDeclaredField("map");
// 返回一个Class 对象,它标识了此Field 对象表示字段的声明类型。
System.out.println("获得其类型:" + field.getType());
Type type = field.getGenericType();// 包含泛型的类型
System.out.println("getGenericType:" + type);
// Type里面没有任何的方法,所以需要调用子类的方法
ParameterizedType pt = (ParameterizedType) type;
System.out.println("parameterizedType:" + type);
// 返回Type 对象,表示此类型是其成员之一的类型。
type = pt.getOwnerType();
System.out.println("getOwnerType:" + type);
// 返回Type 对象,表示声明此类型的类或接口。
type = pt.getRawType();
System.out.println("getRawType:" + type);
// 返回表示此类型实际类型参数的Type对象的数组。
Type[] types = pt.getActualTypeArguments();
for (Type t : types) {
System.out.println("getActualTypeArguments:" + t);
}
}
}
输出
获得其类型:interface java.util.Map
getGenericType:java.util.Map<java.lang.Integer, java.lang.String>
parameterizedType:java.util.Map<java.lang.Integer, java.lang.String>
getOwnerType:null
getRawType:interface java.util.Map
getActualTypeArguments:class java.lang.Integer
getActualTypeArguments:class java.lang.String
3 反射调用
3.1 创建对象
1、使用Class 对象的newInstance()方法创建该Class 对象的实例,此时该Class 对象必须要有无参数的构造方法且不能为私有,否则抛出 java.lang.IllegalAccessException异常
2、使用Class 对象获取指定的Constructor 对象,再调用Constructor 的newInstance()方法创建对象类的实例,此时可以选择使用某个构造方法。如果这个构造方法为私有,那么必须设置setAccessible(true)
java枚举类型不能利用 new、clone()、de-serialization、以及 Reflection API 来产生enum 的 对象
public enum WeekDay {
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday ;
private WeekDay(){};
}
// 报错:java.lang.NoSuchMethodException
Class<WeekDay> c = WeekDay.class;
Constructor<WeekDay> con = c.getDeclaredConstructor();// 编译可以通过,但是运行就通不过了!
WeekDay w = con.newInstance();
System.out.println(w);
3.2调用方法
获得Method 对象后,程序可以使用Method里面的invoke 方法来执行该底层方法
Object invoke(Object obj,Object ... args):obj 表示调用底层方法的对象,后面的args 表示传递的实际参数。
1) 如果底层方法是静态的,那么可以忽略指定的obj 参数。该参数可以为null
2)如果底层方法所需的形参个数为0,则所提供的args 数组长度可以为0 或null。不写,null,或new Object[]{}
Class<User> clz = User.class;
// 调用公有方法
Method method = clz.getMethod("show", String.class);
method.invoke(clz.newInstance(), "张三");
// 调用私有方法
method = clz.getDeclaredMethod("privateshow");
method.setAccessible(true);
method.invoke(clz.newInstance());
// 静态方法的调用
method = clz.getMethod("staticshow");
method.invoke(null);
使用反射调用可变参数方法
要把可变参数都当做是其对应的数组类型参数;
1)若可变参数元素类型是引用类型:
JDK 内部接收到参数之后,会自动拆包取出参数再分配给该底层方法,需要把这个数组实参先包装成一个Object 对象或把实际参数作为一个Object 一维数组的元素再传递。
2)若可变参数元素类型是基本类型:
JDK 内部接收到参数之后,不会拆包,可以不必再封装.
Method m=TestArg.class.getMethod("main", String[].class);
m.invoke(null, new String[]{"111","222"});
报错:java.lang.IllegalArgumentException: wrong number of arguments
可打包成一个数组:m.invoke(null, new Object[]{new String[]{"111","222"}});
或转为object对象:m.invoke(null, (Object)new String[]{"111","222"});
3.3操作字段
Field 提供两组方法操作字段:
①xxx getXxx(Object obj):获取obj 对象该Field 的字段值,此处的xxx 表示8 个基本数据类型。
若该字段的类型是引用数据类型则使用,Object get(Object obj);
②void setXxx(Object obj,xxx val):将obj 对象的该Field 字段设置成val 值,xxx 表示8个基本数据类型。
若该字段的类型是引用数据类型则使用,void set(Object obj, Object value);
Class<User> clz = User.class;
User u = clz.newInstance();
// 引用数据类型
Field fi = clz.getDeclaredField("name");
fi.setAccessible(true);
fi.set(u, "张三");
System.out.println(fi);
System.out.println(fi.getName());
System.out.println(fi.get(u));
// 基本数据类型
Field f2 = clz.getDeclaredField("id");
f2.setAccessible(true);
f2.set(u, 1);
System.out.println(f2.getInt(u));
输出
private java.lang.String com.User.name
name
张三
1
4、数组的反射
(1)具有相同的元素类型和相同的维度表示同一个类型,有相同的class实例对象
在内存中数组用 [ 表示,相应的字符表示类型 通过 数组名.getClass().getName()查看
[I 表示int[]
[J 表示long[]
[S 表示short[]
[Z 表示boolean[]
[B 表示byte[]
[C 表示char[]
[D 表示double[]
[F 表示float[]
(2)通过Class实例 的getSuperClass()方法,返回父类为Object类对应的class
(3)基本类型的一维数组可以当作Object类型使用,但不能当作Object[]使用,非基本类型的都可以
这里可以通过 Arrays.asList()方法处理 int[] 和String[]类型数组看到差异。