Java的高级特性反射

首先简单说说什么反射,其实就是动态的加载类,我们在写JDBC的时候加载驱动Class.forName("xxxx"),这句话就涉及到了反射。


反射是自JAVA诞生就具备的高级特性,其强大的扩展能力使JAVA严谨死板的语法变得灵活。


但是能够超越一些JAVA对普通类的限定,有关反射主要相关的包在java.lang.reflect中。


但是反射也有缺点,就是结构代码较为复杂,使人难以理解,在编程中应当注视上普通的实现方式。


由于反射将一个类的各种成分都映射成了相应的类和对象,所以相对普通方法来说,比较消耗资源。


为什么要学习反射呢?


因为其强大的扩展性,在java开发中框架(Spring)大量应用了反射,反射是Java开发者非常有必要掌握的一门技能。


 


Java的反射机制主要提供了:


在运行时判断任意一个对象的所属的类。


在运行时判断构造任意一个类的对象。


在运行时判断任意一个类所具有的成员变量和方法。


在运行时调用任意一个对象的方法。


 


Class类(反射的基石)


Java中每个类都代表了一类事物,如Person类可以有张三李四的具体对象


Class是用于描述JAVA类的一个类,代表类的字节码实例对象


一个类被加载器加载到内存中,占用一片储存空间,在这个空间里的内容就是类的字节码


这样一个个空间可以用一类对象来表示,这些对象具备相同的类型,这个类型就是CLASS


//不同类的字节码是不同的,所以他们在内存中的内容也是不同的


 


java类的用于描述一类事物的共性,该类的属性是由该类的实例对象来决定的,不同的实例对象具备不同的属性


java程序中的各个JAVA类也属于同一类事物,而CLASS类就是用于描述这类事物的


 Class 类描述了类的属性信息,如类名、访问权限、包名、字段名称列表、方法名称列表等  


学习反射就要先搞清楚 Class 这个类  


获取各个字节码对应实例对象(class类型)的方法


1.类名.class ;如:Class c1= Person.class;


2.对象.getClass();如:new Date().getClass();


3.class.forName("类名");如:Class.forName("java.lang.String");


反射时主要用第三种,因为可以将表示“类名”的字符串定义一个变量


得到类的字节方式


                1.该类的字节码已经加载到了内存中,直接找到类的字节码返回(如上1,2)


                2.虚拟机中没有该字节码,用类加载器加载,将该字节码缓存起来同时返回(如3)


 


Class类的实例表示正在运行的JAVA应用程序中的类和接口。枚举是一种类,注释是一种接口。


每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。


基本的 Java 类型(


Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。    


  九个预定义Class实例对象 


   (八个基本数据类型+ void )每个类型都对应了一个 Class 实例对象   


 Class c1 = void.class;  


Class 类方法  


 isPrimitive();//判断该字节码是否是基本数据类型


 包装类不属于基本数据类型,但是可以通过 TYPE 常量获取其基本数据类型字节码;


 如:Integer.TYPE 与 int.class 是同一个字节码对象  


数组类型的Class实例对象判断方法: Class.isArray()


只要是在源程序中出现的类型,都有各自 Class 实例对象,如:int[],void 


----------------------------------------------------------------------  


"反射->Refelection"  


 反射并不是1.5版本才出现的,始于1.0,是java最重要的高级特性之一


概念:反射就是把Java类中的各种成分映射成相应的java类


 例如:Java中一个类可以用Class类的一个对象来表示


一个类的组成部分:成员变量、方法、构造方法、包等信息也用一个个的Java类来表示


//就像汽车是一个类,汽车中的发动机、变速箱等组成也是一个个类 


 表示Java类的Class类显然要提供一系列的方法,来获得其中的变量、方法、构造方法、修饰符、包等信息


这些信息就是用相应类的实例对象来表示,这些类是: Field,Method,Constructor,Package 等


 一个类中的每一个成员都可以用相应的反射API类的实例对象来表示,通过调用 Class 类的方法可以得到这些实例对象


 '得到这些实例对象后该怎么用,是学习应用反射的要点' 


 例如;  


 System.exit


System.getProperties() 


 Method -->   nethodObj1  可以使用两个 Method 对象来代表 System 类的两个方法


 methodOjb2  


----------------------------------------------- 


Constructor 类   代表某个类中的构造方法 


获取某个类所有的构造方法    


 示例: Constructor[] constructor = Class.forName(


  


 示例: Constructor constructor = Class.forName(


//获得构造方法时要用到类型  


 2,创建实例对象 


 通常方式: String str = 


 反射方式: String str = (String)constructor.newInstance(


 


Class.newInstance()方法:  


 ep: String obj = (String)Class.forName("java.lanng.String").newInstance();  //空参 


该方法首先得到默认的无参构造方法,然后使用该构造方法创建实例对象  


 该方法在内部使用了缓存机制来保存默认构造方法的实例对象,所以会影响性能


 只有两个类拥有newInstance()方法,分别是 Class 类和 Constructor 类


 Class类中的newInstance()方法是不带参数的,而Constructro类中的newInstance()方法是带参数的(Object),


 需要提供必要的参数。  在编程时要择需使用  


构造方法的反射: class-->constructor-->new object 通过两个步骤获取该类的对象 






-----------------------------------------------------------------------------------------------


Field 类  


   Field类代表某个类中的一个成员变量, 设有一个obj对象


   Field对象不是obj具体的变量值,而是指代的是obj所属类的哪一个变量,可以通过Field(对象).get(obj)获取相应的变量值


    示例: Field field = obj.getClass().getField("变量名")


   field.get(obj) //通过反射获取对象的变量值,参数是对象,意思就是获得那个对象的那个变量的值


   步骤: 1)获取class字节码  2)获取指定的Field对象  3)获取变量值  


 "暴力反射"  


    get方法只能获取声明为 public 的变量,对于私有变量,可以通过getDeclaredField()方法获取 private 变量


    获取对象后要通过 setAccessible(true)方法将该域设置为可访问


    示例: Field field = obj.getClass().getDeclaredField();  //1)2)获取私有file对象  


   field.setAccessible(true);  //3)将private变量设置为可访问;继承自父类 AccessibleObject 的方法


 field.get(obj);     //4)获取变量值 


反射替换 


 getField()方法获取一个.


 获取变量后就可以通过 field.set(obj,newValue) 将指定对象变量上此Field对象表示的值替换为新的值


 一个问题,我把自己的变量定义成private,就是不想让人家访问,可是,现在人家用暴力反射还是能够访问我,这说不通啊,


 能不能让人家用暴力反射也访问不了我。首先,private主要是给javac编译器看的,希望在写程序的时候,在源代码中不要访问我,


 是帮组程序员实现高内聚、低耦合的一种策略。你这个程序员不领情,非要去访问,那我拦不住你,由你去吧。


同样的道理,泛型集合在编译时可以帮助我们限定元素的内容,这是人家提供的好处,而你非不想要这个好处,怎么办?  


绕过编译器,就可以往集合中存入另外类型了。  


---------------------------------------------------------------------- 


Method 类  


 Method类代表某个类中的成员方法 


Method对象不是具体的方法,而是来代表类中哪一个方法,与对象无关 


示例:得到类中某一个方法:  


 Method methodCharAt = Class.forName("java.lang.String").getMethod("charAt",int.class) //方法名,int为参数


Class.getMethod方法用于得到一个方法对象,该方法接受的参数首先要有该方法名,


 然后通过参数列表来区分重载那个方法,参数类型用 Class 对象来表示(如为 int 就用 int.class)


调用方法:普通方式:str.charAt(1)  


反射方式:methodCharAt.invoke(str,1)  //参数1:调用哪个字符串。 参数2:实参int


//如果invoke方法接收的第一个方法是null,说明该方法是一个静态方法(不需要对象,如main方法)


jdk1.4 和jdk1.5 的invoke方法的区别: 


  1.5: public Object invoke(Object obj, Object...args)  


1.4: public Object invoke(Object obj, Object[] args)


//按照1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中每个元素分别对应被调用方法中的一个参数


//所以调用charAt方法可以用1.4的写法改为 methodCharAt.invoke("str", new Object[]{1}) 的形式  


用反射方式执行某个类中的"main"方法 


  学习目的:在不知道类名的情况下调用其main方法。  


 一般可以通过 类名.main(new String[]{"..."})来调用;如果不知道类名,而只是在程序中接收某一


代表此main方法所属类的名称的参数,就需要用到反射


 要处理的问题:


  main方法的参数(String[] args)是一个字符串数组,通过反射调用该方法需要为invoke方法参数


 按照1.5的语法,整个数组是一个参数,而在1.4中数组的每个元素对应一个参数(会自动拆包)


1.5 为了兼容1.4,保留了该设定,所以在给main方法传递参数时,不能使用Method.invoke(null, String[]{...}) 


 因为编译器会将其按照1.4的语法进行编译,所以会出现"参数个数异常",这是1.4版本遗留的兼容性问题  


 处理方法: 


 Method.invoke(null, new Object[]{new String[]{"..."}});//1,相当于加一层皮,拆分一次  


 Method.invoke(null, (Object)new String[]{"..."});//2,相当于声明为一个对象,不让编译器拆分


-----------------------------------------------------------------------------------------  


数组的反射


 具有相同维数和元素类型的数组属于同一个类型,即具有相同的 Class 实例对象 


 代表数组的Class实例对象的getSuperClass方法返回父类为Object类对应的Class


  基本类型的一维数组可以被当做Object类型使用不能当做Object[]类型使用


 非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用  //引用类型可以当Object[]类型使用


  String不是基本类型 


   Arrays.asList()方法处理 int[] 和 String[]时的差异:int[](Object); String[](Object 或 Object[]) 


   Array 工具类用于完成对数组的反射操作,专门用于处理数组的Object对象 


细节: Array 类与 Arrays 类的区分 


   java.util包中 Arrays 工具类对数组元素进行操作,接收参数为(Array[] array),无法处理数组的Object形态


    java.lang.reflect包中的 Array 工具类对数组对象进行操作,接收参数为(Object array)


  如何得到数组中的元素类型? 


   int[] a = new int[];    //即获取a前面数组的数据类型


    Object[] a = new Object{"String",1,true,'a'}  


     不能通过引用a获取来获取数组的数据类型,如Object示例


    只能获取该数组中某个具体的元素的数据类型;如: a[0].getClass().getName();


    【个人总结】 反射就是将java类中各种成员映射成java类,反射具有极其强大的扩展性,每个成员都是class,所以说JAVA中万物皆是对象


    可以通过一个对象获取该对象所属的字节码(运行时类),然后通过相应的方法获取其


    构造器、成员变量、成员方法等,然后将这些成分反作用给该对象,对该对象数据进行获取、修改等操作


    如:一个app没有源代码,只有打包的.class 字节码,如果想要对其进行修改升级等操作,无法通过修改源代码来实现


    这时就可以用到反射,通过改变运行时类来实现  






--------------------------------代码部分---------------------------------


package reflect;




import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;




public class reflectTest {
public static void main(String[] args) throws ClassNotFoundException,
SecurityException, NoSuchMethodException, IllegalArgumentException,
InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchFieldException {
String str = "abc";
Class clazz = str.getClass();
Class clazz2 = String.class;
Class clazz3 = Class.forName("java.lang.String");
System.out.println(clazz == clazz2);// true
System.out.println(clazz2 == clazz3);// true
System.out.println(clazz.isPrimitive());// 判断是否为基本类型
System.out.println(int.class.isPrimitive());// true
System.out.println(int.class == Integer.class);// false
System.out.println(int.class == Integer.TYPE);// true
System.out.println(int[].class.isPrimitive());// false
System.out.println(int[].class.isArray());// 判断是否为数组




// 构造方法的反射
// 1.获取构造器
Constructor<String> constructor1 = String.class
.getConstructor(StringBuffer.class);
// 2,使用Constructor的有参newInstance(obj) 方法创建对象,该参数是一个对象
String str2 = (String) constructor1
.newInstance(new StringBuffer("abc"));
System.out.println(str2);




// Field 类的反射
// 1.获取public变量
reflectBean re = new reflectBean("10", "11");
Field field = re.getClass().getField("y");// getField获取指定public 变量
System.out.println(field.get(re));// 获取re对象Y的值
// 2, 获取私有变量 <-- 暴力反射 -->
Field field2 = re.getClass().getDeclaredField("x");// 获取私有变量
field2.setAccessible(true);// 设置访问权限
System.out.println(field2.get(re));




// 修改值
changeStringValue(re);




// Method 类的反射
Method method = String.class.getMethod("charAt", int.class);
// 反射方法
System.out.println(method.invoke("str", 1));// str字符串对象调用此方法
// 静态方法不需要对象,如果不是str而是null,说明该方法是静态方法
System.out.println(method.invoke(str, new Object[] { 2 }));// jdk1.4的写法,一个元素代表一个参数




// 用反射方式执行某个类中的main方法
// (1)普通方式直接调用静态方法
reflectBean.main(new String[] { "aa", "bb", "cc" });
// (2)反射方式
// String startingClassName = args[0]; //要在控制台传入参数,否则会抛出角标越界异常
// Method mainMethod =
// Class.forName(startingClassName).getMethod("main", String[].class);
// jdk1.4遗留的兼容性问题:数组中每个元素分别对应被调用方法中的一个参数,所以会抛出参数个数异常
// 解决方法:将该数组封装进一个Object对象
// mainMethod.invoke(null, new Object[]{new String[]
// {"aaa","bbb","ccc"}});//相当于包一层皮
// mainMethod.invoke(null, (Object)new
// String[]{"xxx","ooo","yyy"});//相当于声明不让编译器拆包




// 数组的反射
int[] a1 = new int[] { 1, 2, 3 };
int[] a2 = new int[4];
int[][] a3 = new int[3][4];
String a4[] = new String[] { "a", "b", "c" };
System.out.println(a1.getClass() == a2.getClass());
System.out.println(a2.getClass().getName());
System.out.println(a1.getClass().getSuperclass().getName());// 父类为Object




Object obj1 = a1;
// Object[] obj2 = a1; //基本数据类型的一维数组不能作为Object[]使用
Object[] obj3 = a3; // 基本数据类型的多维数组可以作为Object或Object[]使用
Object obj4 = a4;
Object[] obj5 = a4; // 非基本元素类型一维数组可以作为Object或Object[]使用
System.out.println(a1);
System.out.println(a4);
System.out.println(Arrays.asList(a1)); // a1是int基本类型,不能作为Object[]使用,只能作为一个Object
System.out.println(Arrays.asList(a4)); // a4可以作为Object[]使用,符合jdk1.4的语法,自动拆分打印
// Arrays工具类的静态方法:1.4版本: asList(Object[] a);将数组转成List集合,List集合可以直接打印
// 1.5版本: asList(T...a) ; 向下兼容1.4 ,新增功能可以将多个参数转成List集合
System.out.println(Arrays.asList(1, 2, 3, 4, 5));// asList方法1.5特性,可变参数
printObject(a4);
printObject("aaaaa"); 
}




// 细节:Array类与Arrays类
// java.util包中 Arrays工具类对数组元素进行操作,接收参数为(Array[] array),无法处理数组的Object形态
// java.lang.reflect包中的Array工具类对数组对象进行操作,接收参数为(Object array)
private static void printObject(Object obj) {
Class clazz = obj.getClass();
if (clazz.isArray()) {
for (int i = 0; i < Array.getLength(obj); i++) {
System.out.println(Array.get(obj, i));
}
}
}




private static void changeStringValue(Object obj)
throws IllegalArgumentException, IllegalAccessException {
Field[] fields = obj.getClass().getFields();
for (Field field : fields) {
if (field.getType() == String.class) {
String oldValue = (String) field.get(obj);
String newValue = oldValue.replace("11", "20");
field.set(obj, newValue);




}
}








}
}

                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值