动态编程
程序运行过程中,可以改变程序的结构或变量类型。
虽然java不是动态语言,但是java具有一定的动态性,可以利用反射机制,字节码操作获得类似动态语言的操作。
反射是很重要的,他是java里的大哥级别人物,好多不能做的他都可以做。
反射机制
反射机制是指可以在运行时加载,探知,使用编译期间完全未知的类。
也就是说,利用反射机制,可以实例化一个在编译时只知道名字的类,不用new 创建对象。
Class c = Class.forName("包名.类名");
这就是一个常用的利用反射获取对象class的方法,对象的class里面包含了完整的类的结构信息,我们可以通过这个class,实例化一个对象。
同一个类只会被加载一次,只对应一个Class对象,多次获得Class对象,都是同一个Class对象(地址相同)
对象是表示或封装一些数据。一个类被加载之后,jvm会创建一个对应该类的Class对象,类的整个结构信息会被放到对应的Class对象中
获取Class对象的方法
- Class.forName(“包名.类名”);
- 类名.class; (基本数据类型,数组都可以,数组Class对象是否相同看维度,不看大小)
- 对象名.getClass();
获得Class对象之后的操作
首先我们来看一下我们获得Class对象的名字:
System.out.println(clazz.getName());//包名+类名
System.out.println(clazz.getSimpleName());//类名
getName()获得类的包名+类名,getSimpleName()见名知意,返回简单的类名,即只返回类名。
接着我们可以看一下类里面的属性
Field[] fields = clazz.getFields();//返回public的属性
System.out.println(fields.length);
fields = clazz.getDeclaredFields();//返回所有的属性
System.out.println(fields.length);
Field field = clazz.getField("name");//必须是public的属性
System.out.println(field);
getFields()返回类中所有的public属性,如果想要看到所有的属性,可以用getDeclaredFields()方法。如果只想看到某一指定的方法怎么办,getFields(String name),getDeclaredFields(String name)能实现这一想法。
接下来是类的方法,方法可以看作广义的属性,所以方法差不多,只需要把Field改成Method就可以了
Method method = clazz.getMethod("getId", null);//第二个是参数,如果有,则写参数对应的Class对象
Method mm = clazz.getMethod("setId", int.class);
System.out.println(method);
方法是有参数的,所以getMethod()和vgetDeclaredMethod()方法多了个参数,表明你想看的那个方法的参数,如果没有,就写null,如果用,参数是方法参数的Class对象,有几个写几个。
然后是构造器,构造器也是方法,所以方法相同
Constructor con = clazz.getConstructor(int.class,String.class,int.class);
System.out.println(con);
获取到了构造器,当然要用它实例化一个对象啦,实例化对象用newInstance()方法,Class对象自带一个newInstance()方法,调用类的无参构造器,我们也可以用geiConstructor()获得了一个构造器之后再调用构造器的newInstance()方法。
user user = (user)clazz.newInstance();//调用无参构造器
user = (user)clazz.getConstructor(int.class,String.class,int.class).newInstance(3969,"王二狗",21);
System.out.println(user);
获得了构造器能构造对象,那么获得了方法和属性能干什么呢,答:可以给操作对象的方法和属性。Method类有invoke(Object obj, Object… args)方法,obj是要操作的对象,args是这个方法的参数,用这个方法可以操作对象中对应的方法;Field类中用get和set方法,能操作对象中对应的属性。但是操作的都是共有方法和属性,怎么能操作私有的呢,反射是大哥,得给他找个方法—setAccessible(boolean),是否关闭安全检查,参数为true,就可以无差别的操作共有和私有方法属性了。
Method mm = clazz.getDeclaredMethod("setName", String.class);
mm.invoke(user, "王星宇");
System.out.println(user);
Field ff = clazz.getDeclaredField("age");
ff.setAccessible(true);//不用做安全检查,直接访问,忽略private
ff.set(user, 18);
System.out.println(ff.get(user));
反射机制的性能问题
使用反射会使程序有性能问题(一般30倍的普通方法效率【用反射调用:使用对象调用】)
可以使用setAccessiable提高性能(提高4倍)
这也可以理解,禁用了安全检查,当然会提高速度。
下面的一般用不到
反射操作泛型*
java采用的是泛型擦除机制来引入的泛型——java中的泛型仅仅是给编译器使用,确保了数据的安全性和免去强制类型转换的麻烦,但是,一旦编译完成,所有跟泛型有关的数据全部擦除。
泛型和反射可谓两个次元的人。可是,反射想要操作泛型怎么办呢,别急,为了能通过反射操作这类类型(以迎合实际开发需要),Java就新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类型中的类型但是又和原始类型起名的类型,也即是说,给反射大哥一下子来了四种“泛型”。
测试ParameterizedType:
public class testType {
public void test01(Map<String,user>map,List<user> list) {
System.out.println("testType.test01");
}
public Map<Integer,user> test02(){
System.out.println("testType.test02");
return null;
}
public static void main(String[] args) {
try {
//获得test01方法
Method m = testType.class.getMethod("test01", Map.class,List.class);
//获得参数并用泛型存起来 type是另外四个泛型的接口
Type[] t = m.getGenericParameterTypes();
for(Type paramType : t) {
//输出参数
System.out.println("#" + paramType);
//如果是容器类型的泛型
if(paramType instanceof ParameterizedType) {
//获得容器的泛型<String,user>
Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
//循环输出
for(Type genericType : genericTypes) {
System.out.println("泛型类型" + genericType);
}
}
}
}catch (Exception e) {
}
}
}
反射操作注解
既然反射这个大哥想要操作注解,那么注解必须用上全力呀,让自己的有效时间达到Runtime才行。
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface table {
String value(); // 修饰的类跟哪个表对应
}
@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Field {
//类每个属性对应的列名
String columnName();
String type();
int length();
}
被注解的类
@table("tb_student")
public class student {
@Field(columnName = "id",type = "int",length = 10)
private int id;
@Field(columnName = "sname",type = "varchar",length = 10)
private String name;
@Field(columnName = "age",type = "int",length = 3)
private int age;
}
使用反射操作注解:
public class testAnnotation {
public static void main(String[] args) {
try {
Class clazz = Class.forName("TestAnnotation.student");
//获得类的所有注解
Annotation[] annotations = clazz.getAnnotations();
for(Annotation a : annotations) {
System.out.println(a);
}
//获得指定注解的值
table table = (TestAnnotation.table) clazz.getAnnotation(table.class);
System.out.println(table.value());
//获得属性的值
java.lang.reflect.Field field = clazz.getDeclaredField("name");
Field f = field.getAnnotation(Field.class);
System.out.println(f.columnName() + "," + f.type() + "," + f.length());
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结
反射归根结底就是在操作Class对象,这在我们之前一直是JVM在做。Class对象相当于类的模子,尽管反射降低了运行效率,但是提高了开发效率,就像活字印刷术,可能制作起来麻烦,但是用起来很方便,而且快。