zoukankan      html  css  js  c++  java
  • 基础加强____【Java高级特性__反射】


    认识反射

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

    使用反射能够超越一些Java对普通类的限定,有关反射的主要相关类存在于java.lang.reflect包中

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

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

    为什么要学习反射呢?

    因为其强大的扩展性,在Java开发框架中大量应用了反射,反射是Java开发者非常有必要掌握的一部分知识

    正文

    言归正传,为文档保持格式插入到代码

    					反射->Refelection
    
    Class 类(反射的基石)
    
    	Java中每个类都代表一类事物,如Person类可以有张三李四具体的对象
    	Class 是用于描述Java类的一个类,代表类的字节码实例对象
    	一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码
    	这样一个个空间可以用一类对象来表示,这些对象显然具备相同的类型,这个类型就是 Class
    	//不同类的字节码是不同的,所以他们在内存中的内容也是不同的
    
    	java类用于描述一类事物的共性,该类的属性是由该类的实例对象来决定的,不同的实例对象具有不同的属性值
    	Java程序中的各个Java类也属于同一类事物,而 Class 类就是用于描述这类事物的
    	Class 类描述了类的属性信息,如类名、访问权限、包名、字段名称列表、方法名称列表等
    	学习反射就要先搞清楚 Class 这个类
    
    获取各个字节码对应实例对象(Class类型)的方法;(注意 Class class 大小写)
    	1,类名.class;如:Class c1 = Person.class;	//固定写法;String.class 的类型是 Class<!-- <String> -->。
    	2,对象.getClass(); 如:new Date().getClass();//
    	3,Class.forName("类名");如:Class.forName("java.lang.String");//静态方法
    	反射时主要用第三种,因为可以将表示"类名"的字符串定义成一个变量
    得到类的字节方式
    		1,该类的字节码已经加载到了内存中,直接找到该字节码返回(如上1、2)
    		2,虚拟机中没有该字节码,用类加载器加载,将该字节码缓存起来同时返回(如3)
    
    Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。
    每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
    基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。 
    
    Class 没有公共构造方法。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	//Method代表方法这个类型,其对象代表具体是哪个方法
    
    -----------------------------------------------
    
    Constructor 类	代表某个类中的构造方法
    
    	获取某个类所有的构造方法	// []数组,s获取多个
    		示例: Constructor[] constructor = Class.forName("java.lang.String").getConstructors();
    	1,获取某一个构造方法		//可变参数
    		示例: Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
    			//获得构造方法时要用到类型
    	2,创建实例对象
    		通常方式: String str = new String(new StringBuffer("abc"));
    		反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc "));//实参
    			//调用方法时要用到上面相同类型的实例对象
    	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()方法获取一个.class 指定的public变量,getFields()方法获取该字节吗所有的 public 变量
    	获取变量后就可以通过 field.set(obj,newValue) 将指定对象变量上此Field对象表示的值替换为新的值
    
    	一个问题,我把自己的变量定义成private,就是不想让人家访问,可是,现在人家用暴力反射还是能够访问我,这说不通啊,
    	能不能让人家用暴力反射也访问不了我。首先,private主要是给javac编译器看的,希望在写程序的时候,在源代码中不要访问我,
    	是帮组程序员实现高内聚、低耦合的一种策略。你这个程序员不领情,非要去访问,那我拦不住你,由你去吧。
    	同样的道理,泛型集合在编译时可以帮助我们限定元素的内容,这是人家提供的好处,而你非不想要这个好处,怎么办?
    	绕过编译器,就可以往集合中存入另外类型了。
    
    ----------------------------------------------------------------------
    
    Method 类
    	Method类代表某个类中的成员方法
    	Method对象不是具体的方法,而是来代表类中哪一个方法,与对象无关
    	示例:得到类中某一个方法:
    		Method methodCharAt = Class.forName("java.lang.String").getMethod("charAt",int.class)
    		Class.getMethod方法用于得到一个方法对象,该方法接受的参数首先要有该方法名,
    		然后通过参数列表来区分重载那个方法,参数类型用 Class 对象来表示(如为 int 就用 int.class)
    	调用方法:普通方式:str.charAt(1)
    			  反射方式:methodCharAt.invoke(str,1)	
    			  //如果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[]类型使用
    		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类,反射具有极其强大的扩展性,
    	可以通过一个对象获取该对象所属的字节码(运行时类),然后通过相应的方法获取其
    	构造器、成员变量、成员方法等,然后将这些成分反作用给该对象,对该对象数据进行获取、修改等操作
    
    如:一个app没有源代码,只有打包的.class 字节码,如果想要对其进行修改升级等操作,无法通过修改源代码来实现
    	这时就可以用到反射,通过改变运行时类来实现
    

    代码实现示例:

    package itheima.enhance;
    
    import java.lang.reflect.Array;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    public class ReflectionTest {
    
    	/**
    	 * @param args
    	 */
    	public static void main(String[] args) throws Exception{
    		// TODO Auto-generated method stub
    		String str = "abc";
    		Class c1 = str.getClass();
    		Class c2 = String.class;
    		Class c3 = Class.forName("java.lang.String");
    		System.out.println(c1 == c2);
    		System.out.println(c1 == c3);
    		
    		System.out.println(c1.isPrimitive());//String不是基本数据类型
    		System.out.println(int.class.isPrimitive());//int是基本数据类型
    		System.out.println(int.class == Integer.class);//基本类与包装类不是一种类型
    		System.out.println(int.class == Integer.TYPE);//true常量TYPE代表包装类中基本类型的字节码
    		System.out.println(int[].class.isPrimitive());//数组不是基本类型
    		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.charAt(1));
    		
    //Field 类的反射
    		//1,获取public变量
    		ReflectPoint rp1 = new ReflectPoint(6,9);
    		Field fy = rp1.getClass().getField("y");//getField获取指定public 变量
    		//fy 不是对象上的的变量9,而是表示类上的变量“y”(对象),
    		System.out.println(fy.get(rp1));//用来获取该类对象上对应的值
    		//2, 获取私有变量	<-- 暴力反射 -->
    		Field fx = rp1.getClass().getDeclaredField("x");//(1)获取私有属性,该方法返回指定已声明字段
    		fx.setAccessible(true);	//(2)将该域设置为可以访问,是继承父类 AccessibleObject的方法
    		System.out.println(fx.get(rp1));
    		
    		changeStringValue(rp1);	//修改操作
    		System.out.println(rp1);
    		
    //Method 类的反射
    		//str.charAt(1);	//普通方法
    		Method methodCharAt = String.class.getMethod("charAt", int.class);
    		System.out.println(methodCharAt.invoke(str, 1));//反射方法
    		//静态方法不需要对象,如果不是str而是null,说明该方法是静态方法
    		System.out.println(methodCharAt.invoke(str,new Object[]{2}));//jdk1.4的写法,一个元素代表一个参数
    		
    	//用反射方式执行某个类中的main方法
    		//(1)普通方式直接调用静态方法
    		TestArguments.main(new String[] {"aaa","bbb","ccc"});
    		//(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[2][3];
    		String [] a4 = new String[]{"a","b","c"};
    		System.out.println(a1.getClass() == a2.getClass());	//true
    		//System.out.println(a1.getClass() == a4.getClass());
    		//System.out.println(a1.getClass() == a3.getClass());
    		System.out.println(a1.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("abc");
    		
    	}
    	//细节: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 x = 0; x<Array.getLength(obj);x++){
    				System.out.println(Array.get(obj, x));
    			}
    		}
    		else{	//不是数组直接打印
    			System.out.println(obj);
    		}
    		
    	}
    
    	//反射的应用:修改操作
    	private static void changeStringValue(Object obj) throws Exception {
    		Field[] fields = obj.getClass().getFields();	//获取该对象所属class所有public变量
    		//循环遍历,逐一操作
    		for(Field field : fields){
    			if(field.getType() == String.class){	//判断String字段
    				String oldValue = (String)field.get(obj);	//获取对应的值
    				String newValue = oldValue.replace('t','d');//String类操作
    				field.set(obj, newValue);	//将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
    			}		
    		}
    	}
    }
    //打印参数
    class TestArguments{
    	public static void main(String[] args){
    		for(String arg : args){
    			System.out.println(arg);
    		}
    	}
    }
    
    



  • 相关阅读:
    KVM虚拟化学习笔记系列文章列表(转)
    centos 6.5安装docker报错(查看报错详细信息--推荐)
    利用JMX统计远程JAVA进程的CPU和Memory---jVM managerment API
    OpenJDK和JDK区别
    docker sshd image problem, session required pam_loginuid.so, cann't login
    ssh-keygen
    优秀的软件测试人员必需具备的素质
    java基础篇---I/O技术
    jstl long类型数据转换为日期格式
    apache-hadoop-1.2.1、hbase、hive、mahout、nutch、solr安装教程
  • 原文地址:https://www.cnblogs.com/Joure/p/4337192.html
Copyright © 2011-2022 走看看