zoukankan      html  css  js  c++  java
  • 高新技术_反射

    一,反射的基石(Class类)

    1.1Class概述

    1,java程序中的各个java类属于同一类事物,java提供了一个类来用于描述这类事物,这个类就是Class。

    2,Class类代表java类,它的各个实例对象又分别对应什么呢?

    答:对应各个类在内存中的字节码

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

    所以他们再内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象具有相同的类型,就是Class类型。


    1.2获得Class对象

    1,使用Class类的forName(String className)静态方法。传入的字符串参数值是某个类的权限定类名(必须添加完整包名)

    (该方法可能抛出ClassNotFoundException异常)

    2,调用某个类的class属性来获取该类对应的Class对象。

    3,调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所有的对象都可以调用这个方法。

    会返回该对象所属类对应的Class对象。

    String str1 = "abc";
    //三种得到字节码的方式
    Class cls1 = str1.getClass();
    Class cls2 = String.class;
    Class cls3 = Class.forName("java.lang.String");//抛出异常。


    以上三种得到的String字节码都是同一份

    System.out.println(cls1 == cls2);//true
    System.out.println(cls1 == cls3);//true


    关于字节码的其他相关判断。

    System.out.println(cls1.isPrimitive());// 判定指定的 <code>Class</code> 对象是否表示一个基本类型。、false、
    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());//判断是否是数组

    三种方式的区别:第三种在源程序时不用知道要获得的字节码的类名称,而是临时传进来的。

    对反射的一个总结反射就是把java类的各种成分映射成相应的java类。

    1.3Class的获取信息的常用方法

    1.3.1获取Class对应类所包含的构造器

    Constructor<T>  getConstructor(Class<?>...parameterTypes):
    Constructor<?>[]  getConstructors():  
    Constructor<T>    getDeclaredConstructor(Class<?>...parameterTypes);    与访问权限无关
    Constructor<?>[]  getDeclaredConstructos();

    1.3.2 获取Class对应类所包含的方法 

    Method    getMethod(String name,Class<?>...parameterTypes);
    Method[]  getMethods();
    Method    getDeclaredeMethod(String name,Class<?>...parameterTypes);  与访问权限无关
    Method[]  getDeclaredeMethods();

    1.3.3访问Class对应类所包含的Field

    Field    getField(String name);
    Field[] getFields();
    Field   getDeclaredeField(String name);  与访问权限无关
    Fidle   getDeclaredeFields();

    1.3.4获取方法和构造函数的注意要点

    注意:上面的多个getMethod()方法和getConstructor()方法中,都需要传入多个类型为Class<?>的参数,
    用于指定的方法或指定的构造器,关于这个参数的作用,做以下详述:
    以下是Stirng类的3个三个方法:

    1,public int indexOf(int ch)
    2,public int indexOf(Stirng str)
    3,public int indexOf(Stirng str , int fromIndex)
    以上3个同名方法属于重载,他们的方法名相同,但参数列表不同。在java中要确定一个方法光有方法名是不行的,确定一个方法应该由
    方法名和参数列表来确定,但形参名没有任何意义,所以只能由形参类型来确定.
    例如我们要确定第二个indexOf方法,则必须指定方法名为indexOf,形参列表为String.class,因此要获取此方法的代码为:

    //前一个参数指定方法名,后面是个数可变的Class参数指定形参类型列表

    String.class.getMethod("String",Stirng.class);

    如果需要获取第三个indexOf方法,则使用如下代码:
    //前一个参数指定方法名,后面是个数可变的Class参数指定形参类型列表
    String.class.getMethod("String",Stirng.class,int.class);

    而获取构造器时无须传入构造器名,因为同一个类的所有构造器的名字都是相同的,所以要确定一个构造器只要指定形参列表即可,
    例如要得到的是String(StringBuffer buffer)这个构造函数,则使用如下代码:
    Class.forName("java.lang.String").getConstructor(StringBuffer.class);

    二,创建对象Constructor类


    使用反射来生成对象有如下两种方式。

    2.1第一种创建方式

    1,使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器.
    如:Person p = Person.class.newInstance(); //通过Person的默认构造器创建一个Person对象啊。

    2.2第二种创建方式

    1,先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。
    需要如下三个步骤:
    (1) 获取该类的Class对象
    (2) 利用Class对象的getConstructor()方法来获取指定的构造器
    (3) 调用Constructor的newInstance()方法来创建java对象。

    下面利用反射来创建一个String对象:
    Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
    String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));
    //编译时不知道constructor是String的构造方法,返回的是Object所以上面要强转

    通常没有必要使用反射来创建该对象,毕竟通过反射创建对象时性能要稍低一些,实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,
    通常在开发通用性比较广的框架,基础平台时可能会大量使用反射。


    三,调用方法Method类:(代表类在内存的字节码的一个成员方法)

    1,得到类中的某一个方法

    例子:Method methodCharAt = String.class.getMethod("charAt",int.class);

    获取String类的charAt(int index);

    2,方法的调用

    例子:char s = methodCharAt.invoke(str1,1);

    等价char s = str1.charAt(1)

    注:如果传递给Method对象的invoke()方法的第一个参数为null,说明该method对象对应的是一个静态方法。


    例:用反射方法执行某个类的main方法。

    class TestArguments{
    	public static void main(String[] args){
    		for(String arg : args){
    			System.out.println(arg);
    		}
    	}
    }
    public class ReflectTest {
    	//普通方式调用main方法,
    	//TestArguments.main(new String[]{"111","222","333"});
    	
    	//为什么要用反射的方式调用mian,不知道要调用那个类的main,等外界通过传递参数,告诉我要调用哪个类。
    	String startingClassNmae = args[0];
    	Method mainMethod = Class.forName(startingClassNmae).getMethod("main",String[].class );
    	//mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
    	mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
    
    }

    四,访问属性值(Field类:(代表字节码的某个变量))

    1,Field类代表某个类中的一个成员变量

    public class ReflectPoint {
    	private int x;
    	public int y;	
    	public ReflectPoint(int x, int y) {
    		super();
    		this.x = x;
    		this.y = y;
    	}
    }
    ReflectPoint pt1 = new ReflectPoint(3,5);
    	//getField()只能得到publlic修饰的变量
    Field fieldY = pt1.getClass().getField("y");//对应字节码的变量。没有对应到对象身上。
    System.out.println("fieldY"+fieldY);
    	//field的值是public int cn.itcast.day1.ReflectPoint.y 
    	// fieldY的值是多少?是5 错!fieldY不是对象的变量,而是类上的变量。要用它取某个对象上的Y的值
    System.out.println(fieldY.get(pt1));
    	//private修饰的变量可以使用getDeclareField();,public也可以
    Field fieldX = pt1.getClass().getDeclaredField("x");
    fieldX.setAccessible(true);//暴力反射,给你看到钱,就不让你用,那就只能抢。
    System.out.println(fieldX.get(pt1));


    练习(Field):将任意一个对象的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”;

    private static void changeStringValue(Object obj) throws Exception 
    {
    	Field[] fields = obj.getClass().getFields();
    	for(Field field : fields){
    		if(field.getType() == String.class){
    			String oldValue = (String)field.get(obj);
    			String newValue = oldValue.replace('b','a'); //(old,new)
    			field.set(obj, newValue);
    		}	
    	}		
    }



    五,数组的反射

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

    class ArrayReflect{
    	public static void main(String[] args) 
    	{
    	    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"};
    		//具有相同维数和元素类型的数组属于同一个类型,即具有相同的class实例对象。
    	    System.out.println(a1.getClass() == a2.getClass());//true
    	    System.out.println(a1.getClass() == a3.getClass());//false
    	    System.out.println(a1.getClass() == a4.getClass());//false
    	    System.out.println(a1.getClass().getName());//[I
    	}
    }

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

    //代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
    System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
    System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object


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

    既可以当做Object类型使用,又可以当做Objcet[]类型使用。

    Object aObj1 = a1;
    Object aObj2 = a4;
    	//Object[] aObj3 = a1;//基本类型不是Object,int不属于Object。 Object[] 有一个数组,里面装的是Object
    Object[] aObj4 = a3;
    Object[] aObj5 = a4;

    4,Array.asList()方法处理int[]和String[]时的差异。

    //JDK1.4 asList(Object[] a) 当成一个Object[]处理
    //JDK1.5 asList(T... a) 当成一个Object处理

    System.out.println(a1);//[I@170bea5
    System.out.println(a4);//[Ljava.lang.String;@f47396
    System.out.println(Arrays.asList(a1));//[[I@170bea5] 为什么?jdk1.4和jdk1.5的接收参数的区别
    System.out.println(Arrays.asList(a4));//[a, b, c]

    5,对接收数字参数的成员方法进行反射

    例:在一个类里调用另外一个类的main()方法

    public class Reflect
    {
    	public static void mian(String[] srgs)
    	{
    		//普方式调用
    		 TestArguments.main(new String[]{"111","222","333"});
    		 
    		 /*用反射的方式调用*/
    		 //在运行该类时,传入一个参数作为被调用的类名,用一个变量接收被调用类名
    		 String startingClassNmae = args[0];
    		 //获取被调用类的main()方法
    		 Method mainMethod = Class.forName(startingClassNmae).getMethod("main",String[].class );
    		 
    		 //1,/下面传入的方式不对,JDK1.5兼容JDK1.4的处理方式
    		 //因为String数组属于Object数组,按照jdk1.4的处理方式,所以相当于传了3个参数进去,所以会出现参数个数不对的错误。
    	     //mainMethod.invoke(null,new Stirng[]{"111","222","333"});
    
    		 //2,以下就是创建一个Object数组,将要传的参数作为Object数组的第一个元素,这样就想相当于传进去一个参数
    		 //mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
    	    mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
    	}
    }
    class TestArguments{
    	public static void main(String[] args){
    		for(String arg : args){
    			System.out.println(arg);
    		}
    	}
    }


    6,Array工具类用于完成对数组的反射操作。(反射)

    实例:打印一个Object对象的内容

    private static void printObject(Object obj) {
    		Class clazz = obj.getClass();
    		if(clazz.isArray()){
    			int len = Array.getLength(obj);
    			for(int i=0;i<len;i++){
    				System.out.println(Array.get(obj, i));
    			}
    		}else{
    			System.out.println(obj);
    		}
    	}

    六,反射的作用实现框架的功能

    1,对框架的认识

    如房地产商造房子用户住,门窗和空调等等内部都是由用户自己安装,房地产商造毛呸房就是框架,用户需使用此框架,安好门窗等放入到房地产商提供的毛呸房(框架)。

     框架和工具类的区别:工具类被用户类调用,而框架是调用用户提供的类。

    2,框架要解决的核心问题

    因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象,而要用反射方式来做

    3,实现一个简单框架步骤:

    (1)创建一个配置文件config.properties,然后写入配置信息。如键值对:className=java.util.ArrayList。

    (2)代码实现,加载此文件:

    1将文件读取到读取流中,要写出配置文件的绝对路径。

    2用Properties类的load()方法将流中的数据存入集合。

    3关闭流:关闭的是读取流,因为流中的数据已经加载进内存。

    (3)通过getProperty()方法获取className,即配置的值,也就是某个类名。

    (4)用反射的方式,创建对象newInstance()。

    (5)执行程序主体功能

    实例:

     

    public class ReflectTest2 {
    
    	public static void main(String[] args) throws Exception {
    			
    		InputStream ips = new FileInputStream("config.properties");
    		Properties props = new Properties();
    		props.load(ips);
    		ips.close();
    		
    		String className = props.getProperty("className");
    		//通过上面获取的className类名,创建一个ArrayList对象
    		Collection collections = (Collection)Class.forName(className).newInstance();
    		ReflectPoint pt1 = new ReflectPoint(3,3);
    		ReflectPoint pt2 = new ReflectPoint(5,5);
    		ReflectPoint pt3 = new ReflectPoint(3,3);
    		collections.add(pt1);
    		collections.add(pt2);
    		collections.add(pt3);
    		collections.add(pt1);
    		
    		System.out.println(collections.size());//4
    	
    	}
    }

    4,类加载器加载资源文件

    4.1,类加载器:

    当一个类被使用的时候,会被类加载器加载到内存中,当然,它也可以加载普通文件。

    4.2,eclipse编译,加载功能

    在eclipse中保存一个.java文件后,eclipse会自动将该文件编译成.class文件,并把它放到classpath指定的目录中,

    当然,eclipse也会把源程序目录下的一个非.java文件编译成.class文件,也把它放到classpath指定的目录中。

    所以当classPath指定的目录下需要某个文件,那可以将该文件放在源程序目录下,eclipse会帮你复制过去。

    4.3,使用类加载器加载配置文件

    4.3.1使用类加载器来加载配置文件,需要先通过getClassLoader()获得类加载器,然后使用getResourceAsStream(),获得与配置文件相关的输入流。 

    利用类加载器来加载配置文件,需把配置文件放置的包名一起写上。这种方式只有读取功能!

    InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");

    4.3.2使用类提供的简便方法加载的时候,配置文件路径可以相对也可以是绝对。

    InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
    InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/config.properties");


    实例:

     

    public class ReflectTest2 {
    	public static void main(String[] args) throws Exception {
    		
    		//InputStream ips = new FileInputStream("config.properties");
    		//类加载器,没有OutputStream
    		//一定要记住,用完整的路径,但完整的路径不是硬编码,而是运算出来的
    		//在classpath根目录下面去逐一找这个文件,cn/itcast/day1  在cn前面不要加斜杠
    		//InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
    		//InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
    		InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/config.properties");
    		
    		Properties props = new Properties();
    		props.load(ips);
    		ips.close();
    		
    		String className = props.getProperty("className");
    		Collection collections = (Collection)Class.forName(className).newInstance();
    		
    		//Collection collections = new HashSet();
    		ReflectPoint pt1 = new ReflectPoint(3,3);
    		ReflectPoint pt2 = new ReflectPoint(5,5);
    		ReflectPoint pt3 = new ReflectPoint(3,3);
    		collections.add(pt1);
    		collections.add(pt2);
    		collections.add(pt3);
    		collections.add(pt1);
    		System.out.println(collections.size());
    		
    	}
    }




  • 相关阅读:
    Java消息队列--JMS概述
    Java消息队列--ActiveMq 初体验
    tomcat 日志禁用
    解决Tomcat catalina.out 不断成长导致档案过大的问题
    CentOS防火墙iptables-config的相关配置参数详解
    关于centos7下/etc/sysconfig/目录没有iptables问题
    死磕nginx系列--nginx 限流配置
    Nginx配置之负载均衡、限流、缓存、黑名单和灰度发布
    Android第一个个人APP(帐号助手)
    HDU 2896 病毒侵袭 (AC自己主动机)
  • 原文地址:https://www.cnblogs.com/grkbeyond/p/4147264.html
Copyright © 2011-2022 走看看