反射(Reflection)

反射的API介绍:

反射的基石——Class类

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

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

答: 人-->Person;类-->Class

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

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

一个类被加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码。不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示。这些对象虽然具有相同的类型,这个类型是什么呢?Class。

· Java程序中的各个java类,它们属于同一类事物。用一个类来描述这类事物:Class(注意C的大写

· 获取各个字节码对应的实例对象,方法有3种

1. 类名.class

2. new 对象().getClass()

3. Class.forName("类名或者对象名"); ——这种方法中传递的字符串可以是一个变量

· 反射还有9个预定义对象:八个基本数据类型+void

· Class的方法:

//Class类没有提供构造函数,所以用静态方法获取:
//返回与带有给定字符串名的类或接口相关联的 Class 对象
static Class<?> forName(String className);
//使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的Class对象
static Class<?> forName(String name, boolean initialize, ClassLoader loader);
类名.class //获取该类的字节码文件
对象名.getClass(); //获取该对象对应的字节码文件

boolean isArray();//判断该类是否是一个数组
boolean isPrimitive();//判断该类是否是一个基本类型

· Class使用方法的代码示例:

//获取字节码的3种方式
String str = "abc";
Class c1 = str.getClass();
Class c2 = String.class;
Class c3 = Class.forName("java.lang.String");
System.out.println(c1 == c2);//true
System.out.println(c1 == c3);//true
//结果都为true,说明不管什么方法返回的字节码文件都是同一份字节码
		
//判断该字节码文件是否为数组类型
System.out.println(int[].class.isArray());//true
//判断是否为基本类型的字节码
System.out.println(int.class.isPrimitive());//true
System.out.println(String.class.isPrimitive());//false
//9个预定类型
System.out.println(int.class == Integer.TYPE);//true

· 总结:只要在源程序中出现的实例类型,都有各自的Class实例对象

理解反射:

 · 反射就是把java类中的各种成分映射成相应的java类。例如:一个java类的用一个Class类的对象来表示,一个类中的组件部分:成员变量、方法、构造方法、包等信息也用一个个的java类来表示。就像一个汽车,汽车中的发动机,变速箱等等也是一个个的类,表示java类中的Class类显然也要提供一些方法,来获得其中的变量、方法、构造方法、修饰符、包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Constructor、Package等。
·  一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用?怎么用?这才是学习反射的要点

Constructor类:代表类的构造方法

· 获取Constructor类:
//通过Class类身上的方法获取
Constructor<T> getConstructor(Class<?>... parametterTypes);//返回一个Constructor方法
Constructor<T>[] getConstrucrors();//返回该类的所有的构造方法的一个数组
· 代码示例:
//获取String类的所有构造方法
Constructor[] constructors = Class.forName("java.lang.String").getConstructors();
//得到String类,参数为StringBuffer的构造方法
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"));//这里要传递类型的具体对象
System.out.println(str);//abc
· Class.newInstance():
例:String str = (String)Class.forName("java.lang.String").newInstance();
该方法内部默认的构造方法,然后该构造方法创建对象

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

· 获取Field类:
Field getField(String name);//返回一个 Field 对象 
Field[] getFields();//返回一个包含某些 Field 对象的数组 
· Field类的方法:
Object get(Object obj);//返回指定对象上此Field表示的字段值
void set(Object obj,Object value);//将指定对象上此field对象表示的字段值设置为新的值
void setAccessible(boolean flag);//当flag为true时,代表这个字段可以访问
· 代码示例:
//有一个ReflectPoint类,有x,y 2个变量,通过构造方法赋值
//x变量被private修饰,y变量被public修饰
ReflectPoint pt1 = new ReflectPoint(3,5);

Field fieldY = pt1.getClass().getField("y");
//此时,fieldY的值不是5,因为fieldY不是对象身上的变量,而是类上的,要想用它去取某个对象上对应的值
System.out.println(fieldY.get(pt1));

//获取被private修饰的成员变量时,不能用getField方法
Field fieldX = pt1.getClass().getDeclaredField("x");
fieldX.setAccessible(true);//设置可见,暴力反射
System.out.println(fieldX.get(pt1));
· 代码练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容的“b”改成"a"
Test t = new Test();//有一个Test类。其中的String成员变量已经有默认初始化值

//通过对象的Class文件获取字段
Field[] fields = t.getClass().getFields();
for(Field field : fields) {
	//判断Class文件是否相同时使用==连接
	if(field.getType() == String.class) {
		//获取每个String类型的成员变量
		String oldValue = (String)field.get(t);
		String newValue = oldValue.replace('b', 'a');
		field.set(t, newValue);
	}
}

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

· 获得Method类:
 Method getMethod(String name, Class<?>... parameterTypes);//返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 
 Method[] getMethods();// 返回一个包含某些 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方法。 写一个程序,这个程序能够执行用户提供的类名,去执行该类中的main方法
public class MethodForArr {

	public static void main(String[] args) throws Exception{
		Method mainMethod = Class.forName("reflect.Arr").getMethod("main", String[].class);
		//这里强转成Object是为了告诉编译器,我给的是一个对象,并不是一个数组
		mainMethod.invoke(null, (Object)new String[]{"1","2","3"});
	}

}
class Arr {
	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[]时的差异。因为asList接收的参数类型是一个可变参数。传递int[]时,被Java看作一个类,而不是一个数组。

Array工具类:

· 用于完成数组的反射操作
//一个方法,打印传递的对象,如果是数组就把数组的每一个参数打印
public static void print(Object obj) throws Exception{
	//获取这个对象的字节码,对字节码进行判断
	Class c = obj.getClass();
	if(c.isArray()) {
		//Array工具类提供了数组的反射
		int len = Array.getLength(c);
		for(int x=0; x<len; x++) {
			System.out.println(Array.get(obj, x));
		}
	} else {
		System.out.println(obj);
	}
}

反射的作用

实现框架功能

· 框架与框架要解决的核心问题:
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我们的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
· 框架要解决的核心问题:
我在写框架(房子时),用户可能还不会写程序。我写的框架程序怎样能调用用户以后写的类呢?
因为在程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象,而要调用反射方式来做
· 综合案例,使用反射模拟一个框架的应用
//在写程序时,并不知道以后会什么类型的集合存储元素,将类型写在配置文件中
FileInputStream fileIn = new FileInputStream("config.properties");
Properties prop = new Properties();
prop.load(fileIn);
fileIn.close();

String className = prop.getProperty("className");
Collection<String> coll = (Collection<String>) Class.forName(className).newInstance();

coll.add("a");
coll.add("b");
coll.add("b");
coll.add("c");


/*
 * 当className为java.util.HashSet时,size为3.因为HashSet不可以存放重复元素
 */
System.out.println(coll.size());

用类加载器加载文件:

· 使用刚才的综合案例,在刚才的案例中。config.properties的位置是相对于classPath的位置而言的。如果使用FileInputStream,这个路径的值不应该是硬编码,而应该是算出来的
· 还可以使用类加载器的方式加载文件:
1. 使用类的加载器,这种方法写入的文件名不能是/开头,要写完整的包名
FileInputStream fileIn = 类名.class.getClassLoader().getResourceAsStream("com/itheima/config.properties");
2. 使用Class对象直接提供的方法加载,这种方法如果没有以/开头,会默认为相对路径,自己查找
//相对本类的相对路径地址存放的config.properties
FileInputStream fileIn = 类名.class.getResourceAsStream("config.properties");
//当读取的配置文件与本类无关时,需要以/开头,写绝对路径
FileInputStream fileIn = 类名.class.getResourceAsStream("/con/itheima/config.properties");

内省

了解JavaBean

· JavaBean是一种特殊的java类。主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种规则
· 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果是set和get方法,set和get后面跟着的就是属性的名字
setId()的属性名:id
getLast()的属性名:last
setCPU的属性名:CPU
getUSP的属性名:USP
总之:一个类被当作JavaBean来使用时,JavaBean的属性是根据方法名判断出来的,它根本看不到java类内部的成员变量
· 一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当作JavaBean的类来使用肯定会带来额外的好处:
在javaEE开发中,经常要使用到JavaBean。很多环境使用要求就按JavaBean方式进行操作。
  JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你通过getX的方法来访问私有的x,用内省这套API操作JavaBean比用普通类方法更方便

使用内省对JavaBean的简单操作:

代码示例:有一个JavaBean的类:Person。Person有name和age,2个属性值。对这2个属性值进行内省的操作
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

class Person {
	//符合JavaBean规范的Person类
	//有name和age 2个属性
	private String name;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}
public class IntrospectionDemo {

	public static void main(String[] args) throws Exception {
		//创建JavaBean的Person类
		Person p = new Person();
		//创建内省类,设置p对象的name,age值
		PropertyDescriptor pn = new PropertyDescriptor("name", p.getClass());
		PropertyDescriptor pa = new PropertyDescriptor("age", Person.class);
		Method setPersonName = pn.getWriteMethod();
		Method setPersonAge = pa.getWriteMethod();
		setPersonName.invoke(p, "bruce");
		setPersonAge.invoke(p, 20);
		
		//使用内省对象,获取p对象的name值
		Method getPersonName = pn.getReadMethod();
		Method getPersonAge = pa.getReadMethod();
		String personName = (String)getPersonName.invoke(p);
		int personAge = (Integer)getPersonAge.invoke(p);
		System.out.println("personName:"+personName+"...personAge:"+personAge);
	}
}

使用内省对JavaBean的复杂操作:

代码示例:还是对Person类进行的操作
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class IntrospectionDemo2 {

	public static void main(String[] args) throws Exception {
		
		//内省的复杂操作
		Person p = new Person();
		
		String propertyName = "name";
		String personName = null;
		
		//将一个类当作JavaBean的类,获得BeanInfo信息
		BeanInfo info = Introspector.getBeanInfo(Person.class);
		//从信息中获取所有的PropertyDescriptor
		PropertyDescriptor[] pds = info.getPropertyDescriptors();
		for(PropertyDescriptor pd : pds) {
			if(pd.getName().equals(propertyName)) {
				//当遍历要需要的属性之后时,设置这个属性的内容
				Method setName = pd.getWriteMethod();
				setName.invoke(p, "bruce");
				
				//获取这个值的信息
				Method getName = pd.getReadMethod();
				personName = (String)getName.invoke(p);
				break;
			}
		}
		
		System.out.println(personName);
	}

}

BeanUtils:

· 是Apache组织写的操作JavaBean的工具类:需要在apache的官网的进行下载。

· 首先需要2个jar包,一个是commons-logging,另一个是commons-beanutils

· 导入我们需要的jar包,代码示例:

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

public class BeanUtilsDemo {

public static void main(String[] args) throws Exception {
	Person p = new Person();

	BeanUtils.setProperty(p, "name", "bruce");
	BeanUtils.setProperty(p, "age", "19");
	//Person类上有Date属性,并且Date的初始化值要写为new Date()
	//Date类上有setTime的方法。Utils可以设置这个time
	BeanUtils.setProperty(p, "birthday.time", "111");

	System.out.println(BeanUtils.getProperty(p, "name"));
	System.out.println(BeanUtils.getProperty(p, "age"));
	System.out.println(BeanUtils.getProperty(p, "birthday"));

	//BeanUtils操作和返回的对象都是String类型的
	//PropertyUtils操作的具体类型,不需要进行转换
	PropertyUtils.setProperty(p, "name", "lee");
	PropertyUtils.setProperty(p, "age", 17);
	System.out.println(PropertyUtils.getProperty(p, "name"));
	System.out.println(PropertyUtils.getProperty(p, "age"));
	}

}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值