黑马程序员—java基础学习--反射

日后的框架开发工作中,反射的用处是非常大的,如何通过Class对象获取类的字节码文件,以及用配置文件的形式将数据运用框架得以发挥,以便高效的开发和熟练的掌握?今天,我们同时来掌握java的重点:反射技术。

*Class

Class的定义:

java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值,java程序中的各个java类,它们是否属于同一个事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与小写的class关键字区别,Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属的包名,字段名称的列表,方法名称的列表,等等。学习反射,首先要明白Class这个类

java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class


得到类的字节码的三种方法:

Date                                                 第一种:Class cls1 = Date.class 类名.class

Person p1 = new Person ( ) ;    第二种: p1.getClass ( ) ;对象.getClass( );

java.lang.String                                 第三种:Class.forName ( " java.lang.String " ) ;Class.forName( “ 类名 ” );


九个预定义的Class示例对象:

int.class == Integer.TYPE

void.class


数组类型的Class实例对象 :

Class.isArray ( )


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

Class理解代码如下:

package cn.itheima.day1;

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		String str1 = "abc";
		//获取字节码文件的三种方式
		Class cls1 = str1.getClass();
		Class cls2 = String.class;
		Class cls3 = Class.forName("java.lang.String");
		
		System.out.println(cls1 == cls2);	//打印结果为true,证明三个字节码文件为同一个对象
		System.out.println(cls2 == cls3);
		
		System.out.println(cls1.isPrimitive());			//是否为基本类型,结果为false
		System.out.println(int.class.isPrimitive());	//结果为true,是基本类型	
		System.out.println(int.class == Integer.class);	//结果为false,Integer类并非基本类型
		System.out.println(int.class == Integer.TYPE);	//Integer.TYPE 代表所包装的基本类型的字节码  结果为true
		System.out.println(int[].class.isPrimitive());	//结果为false,数组也是一种类型,非基本类型
		System.out.println(int[].class.isArray()); 		//结果为true,是数组类型
	}
}


*反射

反射的概念:

反射就是把java类中各种成分映射成相应的java类。

反射就是把java类中的各种成分映射成相应的java类,例如,一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个小小的java类来表示。表示java类的Class类显然要提供一系列的方法,来获取其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的示例对象来表示,他们是Field、Method、Contructor、Package等等

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。


-Constructor类

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

得到某个类的所有构造方法:

例子:Constructor [ ]   cons= Class.forName( " java.lang.String " ).getConstructors();

得到某个构造方法:

例子:Constructor  cons = Class.forName( " StringBuffer " ).getConstructor();


创建实例对象:

通常方式:

Stringstr = new String( new StringBuffer( " abc " ) ) ;

反射方式:

Constructor con = String.class.getConstructor ( StringBuffer.class ) ;

String str = ( String ) Constructor.newInstance ( new StringBuffer( " abc " ) )

Class.newInstance( ) 方法:

例子:String obj = (String)Class.forName( " java.lang.String " ).new Instance(  );

该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。

该方法内部的具体代码怎么写的呢?用到了缓存机制来保存默认方法的实例对象。


-Field类

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

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?

类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段fieldX代表的是x的定义,而不是具体的x变量


Field类和Constructor类的理解代码如下:

package cn.itheima.day1;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		//new String(new StringBuffer("abc"))
		//得到构造方法
		Constructor con1 = String.class.getConstructor(StringBuffer.class);
		//通过构造方法创建新实例
		String str2 = (String)con1.newInstance(/*"abc"*/new StringBuffer("abc"));
		System.out.println(str2.charAt(2));
		
		
		ReflectPoint pt1 = new ReflectPoint(3,5);	//创建对象
		Field fieldY = pt1.getClass().getField("y");	//通过反射获取对象
//		fieldY的值是多少?	是5?错!fieldY不是对象身上的变量,而是类上的,要用它去取某个对象对应的值
		System.out.println(fieldY.get(pt1));	//将fieldY与pt1相关联,打印结果为5
		Field fieldX = pt1.getClass().getDeclaredField("x");	//获取不可见(私有)的变量
		fieldX.setAccessible(true);		//设置可见,称为暴力反射
		System.out.println(fieldX.get(pt1));	//打印结果为3
		
		//练习:将ReflectPoint类中的字符串变量中含有"b"的字母改为"a"
		changeStringValue(pt1);
		System.out.println(pt1);
	}

	public static void changeStringValue(Object obj) throws IllegalArgumentException, IllegalAccessException {
		
		Field[] fields = obj.getClass().getFields();
		for(Field field:fields){
			//field.getType().equals(String.class)
			if(field.getType() == String.class){	//同一份字节码,可以用==表示
				String oldValue = (String) field.get(obj);
				String newValue = oldValue.replace("b", "a");
				field.set(obj, newValue);//设置对象修改为新字符
			}
		}
	}
}


-Method类

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

得到类中的某一个方法:

例子:Method charAt = Class.forName( " java.lang.String " ).getMethod( "charAt " , int.class );

调用方法:

通常方式:System.out.println(str.charAt(1));

反射方式:System.out.println( charAt.invoke( str , 1 ) );
如果传递给Method对象的invoke( ) 方法的一个参数为null,这有这什么样的意义呢?说明该Method对象对应的是一个静态方法!静态方法不需要对象调用


目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,要明白为什么要用反射方式去调用。

问题:启动java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照那种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数据打散成若干个单独的参数,所以,在给main方法传递参数时,不能使用代码。

mainMethod.invoke( null , new String[ ] { " xxx " } ) javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释。因此会出现参数类型不对的问题。

解决办法:

mainMethod.invoke( null , new Object [ ] {new String[ ] { " xxx " }});

mainMethod.invoke(null , (Object)new String [ ] { " xxx " } ) ;编译器会做特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干参数了。

Method理解代码以及问题答案如下:

package cn.itheima.day1;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectTest {
	public static void main(String[] args) throws Exception {
		String str1 = "abc";

		//str1.charAt(1);
		Method methodCharAt = String.class.getMethod("charAt",int.class );
		System.out.println(methodCharAt.invoke(str1, 1));	//调用此方法
		System.out.println(methodCharAt.invoke(str1, new Object[](2)));	//使用数组,表示Object数组中只有一个对象2,用于1.4版本
		//普通方式调用已有类中的main方法
		TestArguments.main(new String[]("111","222","333"));
		//反射方法调用已有类中的main方法
		String staringClassName = args[0];
		Method mainMethod = Class.forName(staringClassName).getMethod("main", String[].class);
		mainMethod.invoke(null, new Object[](new String[]("111","222","333")));	//调用main方法,数组的父类是Object
		
	}

	class TestArguments{
		public static void main(String[] args) {
			for(String arg:args){
				System.out.println(arg);
			}
		}
	}
}

数组的反射

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

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

基本类型的以为数组可以被当作Object类型使用,不能当作Object[ ] 类型使用,非基本类型的一位数组,既可以当做Object类型使用,又可以当作Object[ ] 类型使用。

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

数组反射的理解代码如下:

package cn.itheima.day1;

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 ReflectTest {
	public static void main(String[] args) throws Exception {
		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());	//false
		System.out.println(a1.getClass() == a3.getClass());	//false
		System.out.println(a1.getClass().getName());		//[I	表示int类型数组
		System.out.println(a1.getClass().getSuperclass().getName());	//java.lang.Object
		System.out.println(a4.getClass().getSuperclass().getName()); 	//java.lang.Object
		Object aObj1 = a1;
		Object aObj2 = a4;
//		Object[] aObj3 = a1;	//基本数组类型的数组父类不是Object,编译报错
		Object[] aObj4 = a3;
		Object[] aObj5 = a4;
		
		System.out.println(a1);		//打印结果为int数组类型哈希码
		System.out.println(a4);		//打印结果为String数组类型的哈希码
		System.out.println(Arrays.asList(a1));		//打印结果为int数组类型哈希码,int为基本数据类型
		System.out.println(Arrays.asList(a4));		//打印结果为[a,b,c]
		
		PrintObject(a4);
		PrintObject("xyz");
	}

	public 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);
		}
	}
}

HashSet的注意事项

先直接用new语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成ReflectPoint类的equals和hashcode方法,比较两个运行结果的差异。

理解代码如下:

package cn.itheima.day1;

import java.util.Collection;
import java.util.HashSet;

public class ReflectTest2 {
	public static void main(String[] args) {
		Collection coll = new HashSet();
		ReflectPoint pt1 = new ReflectPoint(3,3);	
		ReflectPoint pt2 = new ReflectPoint(5,5);
		ReflectPoint pt3 = new ReflectPoint(3,3);
		coll.add(pt1);	
		coll.add(pt2);
		coll.add(pt3);//需要将HashSet的哈希值进行覆盖,pt3对象才无法存入
		coll.add(pt1);
		
		pt1.y = 7;		//修改完pt1的值之后,哈希码就变化了
		coll.remove(pt1);	//所以此动作未完成,打印结果为2,专业术语称为内存泄漏
		
		System.out.println(coll.size());
	}
}

class ReflectPoint {
	private int x;
	public int y;
	public String str1 = "ball";
	public String str2 = "basketball";
	public String str3 = "itheima";
	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}
	
	@Override
	public int hashCode() {		//对于有关哈希的集合,必须覆盖此方法,用来判断元素是否重复
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return str1+":"+str2+":"+str3;
	}
}
本示例中也讲解了内存泄漏的原因,因为pt1使用过后进行了修改,哈希码也同样变化,再进行移除操作的时候便没有完成此动作。


反射的作用->实现框架的功能

框架与框架要解决的核心问题:

我要做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中,框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。

框架要解决的核心问题:

我在写框架(房子)时,用户可能还不会写程序,我写的框架程序怎么样调用到以后写出来的类(门窗)呢?

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

利用反射方法制作小小的框架,理解代码如下:

package cn.itheima.day1;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;

public class ReflectTest2 {
	public static void main(String[] args) throws Exception{
		//加载properties文件
		InputStream ips = new FileInputStream("config.properties");
		Properties props = new Properties();
		//将Properties对象和文件相关联
		props.load(ips);
		//关闭流资源,防止系统内存泄漏
		ips.close();
		
		//获取配置文件的键所对应的类名
		String className = props.getProperty("className");
		//通过反射的方法创建实例对象
		Collection coll = (Collection)Class.forName(className).newInstance();
		
		//通过一般的方法创建对象
//		Collection coll = new HashSet();
		ReflectPoint pt1 = new ReflectPoint(3,3);	
		ReflectPoint pt2 = new ReflectPoint(5,5);
		ReflectPoint pt3 = new ReflectPoint(3,3);
		coll.add(pt1);	
		coll.add(pt2);
		coll.add(pt3);
		coll.add(pt1);
		
		System.out.println(coll.size());
	}
}

class ReflectPoint {
	private int x;
	public int y;
	public String str1 = "ball";
	public String str2 = "basketball";
	public String str3 = "itheima";
	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}
	
	@Override
	public int hashCode() {		//对于有关哈希的集合,必须覆盖此方法,用来判断元素是否重复
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return str1+":"+str2+":"+str3;
	}
}


你可能感兴趣的:(黑马程序员—java基础学习--反射)