高新技术_反射

 

一,反射的基石(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());
		
	}
}


 



 

posted on 2014-11-23 16:39  grkbeyonf  阅读(173)  评论(0编辑  收藏  举报

导航