黑马程序员———反射


------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

反射的基石:Class类

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

对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?

人----->Person

Java类------->Class

对比提问:Person类代表人,它的实例对象就是张三、李四这样的一个个具体的人,Class类代表Java类,它的各个实例对象又分别对应什么呢?

对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。

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

的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?

Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性。至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不

同的属性值。Java程序中的各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与小写的class

关键字区别。Class类描述了哪方面的信息呢?类的名字,类的访问属性,类所属的包名,字段名称列表,方法名称的列表等等。学习放射,首先要明白Class

这个类。

反射

反射就是把Java类中的各种成分映射成相应的Java类。例如,一个Java类中用一个Class类的对象来表示一个类中的组成部分:成员变量,方法,构造方法,包等等信息页用一

个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示Java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方

法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Constructor、Package等等。

一个类中的每个成员都可以用相应的反射API的一个实例对象来表。

反射获取字节码的三种方式:

1,  类名.Class,例如:System.class

2,  对象.getClass(),例如,newDate().getClass(

3,  Class.forName(“类名”),例如,Class.forName(“java.util.Date”);反射主要用这种,可以不用知道类型的名字

九个预定义的Class实例对象:八个基本类型加上void

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

IsPrimitive判断指定的Class对象是否表示一个基本类型

IsArray判定此Class对象是否表示一个数组类

练习:

package Reflect;

public class RefDemo {

	public static void main(String[] args) throws ClassNotFoundException {
		String str = "dfjfl";
		//获得String字节码的三种方式
		Class cls1 = str.getClass();
		Class cls2 = String.class;
		Class cls3 = Class.forName("java.lang.String");//参数为完整的类名 Class的静态方法
		//三个字节码对应的是同一个字节码 两个true
		System.out.println(cls1 == cls2);
		System.out.println(cls1 == cls3);
		//判断是否为基本类型
		System.out.println(cls1.isPrimitive());
		System.out.println(int.class.isPrimitive());
		System.out.println(int.class == Integer.class);
	    //Integer。TYPE代表它所包装的基本类型的字节码
		System.out.println(int.class == Integer.TYPE);
		//判断是否为一个数组类型
	    System.out.println(int[].class.isArray());
		

	}

}

打印结果:

 

true

true

false

true

false

Constructor

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

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

例子:Constructor[] constructors = Class.forName(“java.lang.String”).getConstructors();

得到某一个构造方法:

Constructor constructor =Class.forName(“java.lang.String”).getConstructor(StringBuffer.class)

获得方法时传递的参数要用类型

创建实例对象:

通常方式:String str = new String(new StringBuffer(“abc”));

反射方式: String str = (String)constructor.newInstance(new StringBuffer(“abc”));

调用获得的方法时要用到上面相同类型的实例对象

Class.newInstance()方法:

例子:String obj =StringClass.forName(“java.lang.String”).newInstance():

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

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

练习:

package Reflect;
import java.lang.reflect.*;
public class ConstructorDemo {

	public static void main(String[] args)throws Exception {
		//获取String接受StringBuffer类型参数的构造函数
		Constructor constructor = String.class.getConstructor(StringBuffer.class);
		//用获取的构造函数创造新的实例对象
		String str = (String)constructor.newInstance(new StringBuffer("ffe"));
		//用Class类的newInstance方法穿件实例对象
		String str1 = (String)Class.forName("java.lang.String").newInstance();
		System.out.println(str);
		System.out.println(str1.length());

	}

}


打印结果:

ffe
0

FIeld

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

常用方法:

      Field getField(String s);  返回一个Filed对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。

        Field getDeclaredField(String s); 返回一个Field对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。

        setAccessible(ture);将此对象的accseeible标志设置为指示的布尔值。值为true指示反射的对象在使用时应该取消 Java语言访问检查。值为 flase 则 指示  反射的对象应该实施 Java 语言访问检查。

        set(Object obj, Object value);//将指定对象变量上此Field对象表示的字段设置为指定的新值。

        Object get(Object obj);//返回指定对象上Field表示的字段的值。

练习:

import java.lang.reflect.Field;

public class RefDemo2 {

	public static void main(String[] args)throws Exception {
		//创建Person对象
		Person person = new Person("li",23);
		//获取name字段
		Field field = person.getClass().getField("name");
		//获取person对象上的name字段值
		String name = (String) field.get(person);
		System.out.println(name);
	    //获取该类的全部字段 包括私有 获取私有的age字段
		Field field1 = person.getClass().getDeclaredField("age");
		//取消访问检查
		field1.setAccessible(true);
		//获取person上的age属性值
		int age = (int)field1.get(person);
		System.out.println(age);

	}

}

class Person {
	public String name;
	private int age;
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	

}


 

打印结果:

li

23

Method

package Reflect;
/*练习: 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的'b'替换为'a'
 * */
import java.lang.reflect.Field;

public class RefDemo3 {

	public static void main(String[] args) throws Exception {
		//创建Person对象
		Person person = new Person();
		//调用方法
		changeStr(person);
		//输出
		System.out.println(person);

	}
	public static void changeStr(Object obj)throws Exception{
		//获得对象的所有字段
		Field[] fileds = obj.getClass().getFields();
		//遍历字段数组
		for(Field field : fileds){
			//判断字段是否为String类型
			if(field.getType() == String.class){
				//获取字段值
				String oldStr = (String) field.get(obj);
				//字符替换
				String newStr = oldStr.replace('b', 'a');
				//将替换后的字段赋值给对象的字段
				field.set(obj, newStr);
			}
		}
		
		
	}

}
class Person {
	public String name = "djl";
	private int age;
	public String str1 = "ball";
	public String str2 = "bascateball";
	public String str3 = "itcast";
	public Person(){}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
    public String toString(){
    	return str1+":"+str2+":"+str3;
    }
<span style="font-size:18px;">}
</span>

打印结果:

aall:aascateaall:itcast

Method

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

相关方法:

Method getMethod(String name, Class<?>... parameterTypes) ; 返回一个 Method 对象,它反映此Class对象所表示的类或接口的指定公共成员方法。

Method[] getMethods();返回一个包含某些Method 对象的数组,这些对象反映此Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那

些的类或接口)的公共 member 方法。

Object invoke(Object obj, Object... args); 对带有指定参数的指定对象调用由此Method 对象表示的底层方法。

Method getDeclaredMethod(String name, Class<?>... parameterTypes);返回一个Method 对象,该对象反映此Class 对象所表示的类或接口的指定已声明方法。

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

练习:

package Reflect;
import java.lang.reflect.*;
public class RefDemo4 {

 public static void main(String[] args)throws Exception {
  //给定字符串str
  String str = "ksfk";
  //通过反射获取charAt方法
  Method method = String.class.getMethod("charAt", int.class);
  //执行charAt方法
  char c  = (char)method.invoke(str, 1);
  System.out.println(c);

 }

}


 打印结果: s

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

JDK1.5: public Object invoke(Object obj, Object... args);

JDK1.4: public Object invoke(Object obj, Object[] args),即按JDK1.4的语法需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应别调用方法中的一个参

数,所以,调用charAt()方法的代码也可以用JDK1.4改写为charAt(str, new Object[]{1})的形式。

写一个程序,这个程序能够根据用户提供的类名,去执行该类的main方法:

问题:启动Java程序的main方法的参数是一个字符串数组,即public static void main(String args[]),通过反射方式来调用这个main方法时如何为invoke方法传递参数呢?按

JDK1.5的语法,整个数组是一个参数,而按JDK1.4的语法,数组中的每个元素都对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底

按照哪种语法进行处理呢?JDK1.5肯定要兼容JDK1.4。所以,在给main方法传递参数时,不能使用代码 mainMethod.invoke(null,new String[](xxx)javac只把它

当做1.4的语法进行理解,而不把它当做jdk1.5的语法理解,因此会出现参数类型不对的问题。

解决办法:

mainMethod.invoke(null,new Object[]{newString[]{djfl}});

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

示例代码:

package Reflect;

import java.lang.reflect.Method;

public class Demo {
	public static void main (String args[])throws Exception{
		//传统方式
		Ref.main(new String[]{"123","jdfl"});
		String startingClassName = args[0];
		Method methodMain = Class.forName(startingClassName).getMethod("main", String[].class);
		//第一种解决方案: 强制转换为超类Object,不用拆包
		methodMain.invoke(null, (Object)new String[]{"jlsdfj","jld"});
		//第二种结局方案:将数组打包,编译器拆包后就是一个String[]类型的整体
		methodMain.invoke(null,new  Object[]{new String[]{"jlsdfj","jld"}});
		
	}

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

打印结果:

123
jdfl
jlsdfj
jld
jlsdfj
jld

数组的反射

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

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

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

Array完成对数组的反射操作。

练习代码:

package Reflect;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Arrays;

public class RedDemo5 {

	public static void main(String[] args) {
		int[] arr1 ={1,2,3,4,5,6,7};
		int[] arr2 = new int[3];
		String [] arr3 = new String[2];
		int[][] arr4 = new int [3][2];
		//相同维度,相同类型的数组具有相同的Class对象 
		System.out.println(arr1.getClass() == arr2.getClass());
		//得到数组父类的类型名字
	    System.out.println(arr1.getClass().getSuperclass().getName());
	    //数组可以赋值给一个Object对象
	    Object obj1 = arr1;
	    //非基本类型的数组可以看做Object数组
	    Object[] obj2 = arr4;
	    System.out.println(Arrays.toString(obj2));
	    System.out.println(Arrays.asList(arr3));
	    print(arr1);
	}
	public static void print(Object obj){
		//判断传入的是否维数组
		if(obj.getClass().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);
		}
		
	}

}

输出结果:

true
java.lang.Object
[[I@3be4d6ef, [I@2cdb03a1, [I@5ecb5608]
[null, null]
1
2
3
4
5
6
7

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

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

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

框架要解决的核心问题

我咋写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢,我写的框架程序要怎样能调用到你以后写的类(门窗)呢?

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

综合案例:先直接用new语句创建ArrayListHashSet的示例对象,演示用Eclipse自动生成ReflectPoint类的equalshashCode()方法,比较两个集合的运行结果差异。

然后改为采用配置文件加反射的方式创建ArrayListHashSet的实例对象,比较观察运行结果的差异。

练习代码:

配置文件名称为 Properties.properties    存入的数据为:className = java.util.HashSet 或者 className = java.util.ArrayList

package Reflect;
import java.util.*;
import java.io.*;
public class RefDemo6 {

	public static void main(String[] args)throws Exception {
		//创建和配置文件相关联的流
		InputStream in = new FileInputStream("Properties.properties");
		//创建Properties对象
		Properties properties = new Properties();
		//加载流
		properties.load(in);
		in.close();
		//获取类名
		String className = properties.getProperty("className");
		//加载并创建类的实例对象
		Collection<RefTest> c =(Collection) Class.forName(className).newInstance();
		
		RefTest re1 = new RefTest(1,2);
		RefTest re2 = new RefTest(1,3);
		RefTest re3 = new RefTest(1,4);
		RefTest re4 = new RefTest(1,2);
		//往集合中添加元素
		c.add(re1);
		c.add(re2);
		c.add(re3);
		c.add(re4);
		//打印集合的长度
		System.out.println(c.size());

	}

}
class RefTest{
	int x ;
	int y;
	//构造函数
	public RefTest(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}
	//复写hashCode
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	//复写equals
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		RefTest other = (RefTest) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}
	
}

打印结果: 当配置文件中的数据为:className = java.util.HashSet 时 打印结果为: 3

                   当配置文件中的数据位: className = java.util.ArrayList 时打印结果为: 4

 

 

 

 

 

 



 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值