--------------android培训、java培训、学习型技术博客、期待与您交流! --------------
一、反射(Reflect)
1、概述
反射就是分析类的具体能力,就是将Java类的各种成分映射成相应的java类。
在学习反射时,一定要记住,一切的操作都将使用Object完成,类、数组的引用都可以使用Object进行接收。
2、Class类
Class类是所有Java类的类,是反射的基石。
在正常情况下,需要先有一个类的完整路径引入后,才能按照固定的格式产生实例化对象。但是在java中也允许通过一个实例化对象,来找到一个类的完整信息,这就是Class的功能。其实按照面向对象来说,类也是对象,所以Java中提供了一个Class类来封装这些对象。如有一个Person类,我们可以通过new运算符新建一个对象,Person p = new Person();我们也可以通过p.getClass()方法来获取该对象的类。
一个Java类经过编译后由.java文件变成了.class文件,也叫字节码文件,在我们需要创建对象的时候,就是通过类加载器加载这些字节码,然后经过一系列操作,产生对象。
获取字节码的三种方式:
第一种:运用对象直接获取字节码,对象.getClass()
如:Person p = new Person();
Class clazz = p.getClass();
第二种:直接用类名获取,类名.class
如:Class clazz = Person.class;
第三种:用类名作为字符串获取,Class.forName(类名)
如:Class clazz = Class.forName("java.lang.String");
第三种是获取字节码的最常用方式,仅传入字符串就行,便于扩展
Class.forName(类名)的作用:当内存中没有该类时,就用类加载器将类加载进内存并缓存,然后返回该类字节码;若内存中有,则直接返回该类字节码。
万物皆对象,java中的基本类型数据也是对象,而物以类聚,则基本数据类型也有其对应的类,java中有九种预定义的Class
对象,表示八个基本类型和 void。这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,即boolean
、byte
、char
、short
、int
、long
、float
和double
。 当然数组也有。
一个类的构成元素有很多,所以Class类中的方法有很多。类中有父类,有接口,有构造函数,有成员函数,有成员变量。Class类中常用的操作有:
static Class forName(String className)
返回与给定字符串名的类或接口的相关联的Class对象。
Constructor getConstructor(Class<?>... parameterTypes)
返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
Constructor getConstructors()
返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
Field getField(String name)
返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。
Field[] getFields()
返回包含某些Field对象的数组,表示所代表类中的成员字段。
Method getMethod(String name, Class<?>... parameterTypes)
返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。
Method[] getMehtods()
返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
String getName()
以String形式返回此Class对象所表示的实体名称。
String getSuperclass()
返回此Class所表示的类的超类的名称
boolean isArray()
判定此Class对象是否表示一个数组
boolean isPrimitive()
判断指定的Class对象是否是一个基本类型。
T newInstance()
创建此Class对象所表示的类的一个新实例。
注意:通过newInstance()方法来新建对象时,一定要保证该类中有无参构造方法,如果不存在,是无法实例化的。因此,写类时最好编写无参构造方法。
Class应用示例
package cn.itheima.blog8;
public class Person {
private String name;
private int age;
public String gender;
public Person() {
}
public Person(String name, int age, String gender) {
super();
this.name = name;
this.age = age;
this.gender = gender;
}
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 String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String toString(){
return name+ ":" + age + gender;
}
}
package cn.itheima.blog8;
public class ReflectPersonClass {
/**
* 获取字节码及常用操作演示
*/
public static void main(String[] args) {
Person p = new Person("mike", 22, "male");
//类名.getClass()获取字节码
Class clazz1 = p.getClass();
//类名.class获取字节码
Class clazz2 = Person.class;
Class clazz3 = null;
try {
//Class.forName()获取字节码,可能会类不存在异常
clazz3 = Class.forName("cn.itheima.blog8.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//字节码只有一份,全部相等
System.out.println(clazz1 == clazz2);
System.out.println(clazz2 == clazz3);
//判断是否为数组
System.out.println(clazz1.isArray());
//判断是否为八种基本数据类型
System.out.println(clazz1.isPrimitive());
//获取父类
System.out.println(clazz1.getSuperclass());
//获取类的名称
System.out.println(clazz1.getName());
}
}
3、Constructor(构造函数)
反射是将java类中各种成分映射为java类,因此需要java类去接收。Java类中的构造函数的类为Constructor。
作为构造函数,有自己的方法签名,修饰符,还可以通过构造函数来初始化对象等等操作。
运用构造函数初始化对象的步骤:
(1)取得字节码,用字节码的getConstructors()方法获取所有构造方法,或者根据参数类型来取得指定构造方法。
(2)通过构造函数的newInstance()初始化实例,若有参数需要传入参数,得到的对象为Object类型,需要进行强转。
Constrctor用法示例:
package cn.itheima.blog8;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class ReflectPersonCon {
/**
* 运用反射获取构造函数并进行实例化
*/
public static void main(String[] args) throws Exception {
//获取Person的字节码
Class clazz = Class.forName("cn.itheima.blog8.Person");
//获取Person所有的构造函数,并打印
Constructor[] constructors = clazz.getConstructors();
for(Constructor con : constructors){
System.out.println(con);
}
//获取参数为(String, int, String)的构造函数
Constructor constructor = clazz.getConstructor(String.class, int.class, String.class);
//通过构造函数初始化对象,构造方法得到的是Object,需要强转
Person p = (Person) constructor.newInstance("mike", 22, "male");
System.out.println(p);
//获取构造函数的访问修饰符
/*
* 访问修饰符是以数字形式作为返回值,所以需要用Modifier的静态方法toString转成public
*/
System.out.println(constructor + "的访问修饰符为:" + Modifier.toString(constructor.getModifiers()));
}
}
4、Field(成员变量)
Field类用来表示的是java类中的成员变量部分。变量有公有,也有私有,若得到私有成员,是无法访问的,需要暴力破解。代码演示如下
package cn.itheima.blog8;
import java.lang.reflect.Field;
public class ReflectPersonField {
/**
* 通过反射获取对象的属性
* @throws SecurityException
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//new Person对象
Person p = new Person("mike", 22, "male");
//获取字节码
Class clazz = p.getClass();
//获取公共属性
Field[] fields = clazz.getFields();
for(Field field : fields){
System.out.println(field);
}
System.out.println("-----------------------");
//获取所有属性
Field[] fields2 = clazz.getDeclaredFields();
for(Field field : fields2){
System.out.println(field);
}
//获取指定私有属性name
Field field = clazz.getDeclaredField("name");
//由于属性是私有的,所以需要强行获取,也称暴力破解
field.setAccessible(true);
System.out.println(field.get(p));
//获取指定公有属性
Field field2 = clazz.getField("gender");
System.out.println(field2.get(p));
}
}
5、Method(成员方法)
Method类用来表示的是java类中的成员变量部分。方法存在重载形式,因此当要确定某一个方法的时候,仅仅通过函数名是无法确定的,还需要参数来确定。代码演示如下
package cn.itheima.blog8;
import java.lang.reflect.Method;
public class ReflectPersonMethod {
/**
* 反射得到方法,并调用方法演示
*/
public static void main(String[] args) throws Exception {
//new Person对象
Person p = new Person("mike", 22, "male");
//获取字节码
Class clazz = p.getClass();
//获取所有public修饰的方法
Method[] methods = clazz.getMethods();
for(Method method : methods){
System.out.println(method);
}
//获取所有方法
Method[] methods2 = clazz.getDeclaredMethods();
//获取方法名为getName,无参数的方法
/*
* 由于方法存在重载,所以唯一确定一个函数除了需要函数名,还需要参数
*/
Method method = clazz.getMethod("getName", null);
System.out.println(method);
//调用方法,
System.out.println(method.invoke(p, null));
}
}
6、数组的反射
反射包中使用Array类表示一个数组,通过此类可以取得数组长度,取得数组内容的操作。Class类中有getComponentType()方法,取得数组的数据类型的字节码文件。Array中有newInstance(Class<?> componentType, int length)方法,根据已有的数组类型开辟新的数组对象。现修改数组大小示例代码如下:
package cn.itheima.blog8;
import java.lang.reflect.Array;
public class ReflectArray {
/**
* 通过反射修改数组的大小
*/
public static void main(String[] args) {
int[] temp = {1,2,3};
System.out.println("temp数组的长度为:" + temp.length);
int[] newTemp = (int[])changArrLen(temp, 5);
System.out.println("变动后的数组长度为:" + newTemp.length);
}
public static Object changArrLen(Object temp, int len) {
Class clazz = temp.getClass().getComponentType();
Object newObj = Array.newInstance(clazz, len);
int length = Array.getLength(temp);
System.arraycopy(temp, 0, newObj, 0, length);
return newObj;
}
}
7、反射的作用--实现框架的功能
反射式一种功能强大而且复杂的机制,使用它的主要对象是工具构造者,而不是应用程序员(摘自java核心技术卷1)。
工具类:java.util包中有许多工具类,如ArrayList,需要时直接new,然后用,非常方便。这是因为工具类是开发者事先写好的。
框架:框架也是开发者写好的,但是我们用的不是框架的代码,而是框架用我们的代码,框架写出来的时候,框架作者是不知道框架谁会用他的框架,这就是矛盾所在。因此框架开发者会提供一些借口规范,然后通过反射来用我们所写的内容。
二、内省(IntroSpector)
内省是Java 语言对JavaBean类属性、事件的一种缺省处理方法。
1、JavaBean
JavaBean是一种特殊的类,主要用于数据的传递,也称为值对象,这种Java类中的方法主要用于访问私有的字段,且方法符合某种特殊的命名规则。若一个类当做JavaBean来使用时,JavaBean的属性可以根据方法名推断出来的,它根本看不到java类内部的成员变量。
JavaBean中方法的命名规范:
若有方法getName()/setName()则表明该JavaBean中有name属性,且该属性不是boolean型;方法名将get/set去掉后,若仅有第一个字母为大写,则属性名为将大写字母改成小写后得到的名称
若有方法isFlag/setFlag()则表明该JavaBean中有f属性,且属性为boolean型;若有is开头的方法,表明为布尔型属性,去掉is后将第一个字母大小改小写。
若有方法getCPU/setCPU则表明该JavaBean中有属性,为CPU;方法名将get/set去掉后,若仅第一个与第二个字母均为大写,则仅需去掉get/set
JavaBean的用处很广泛,如JEE开发中有三层结构,将Jsp+servlet+JavaBean想结合,用JavaBean封装数据;Java提供了一套对JavaBean操作的Api,叫做内省,通过这套api可以比用普通方法操作JavaBean更方便。
2、JavaBean的两种内省方式
(1)通过PropertyDescriptor类,获取名称对应的PropertyDescriptor对象,再用getReadMethod()或者getWriteMethod()方法获取get,set方法,最后用Method中的invoke()进行操作。
(2)通过 Introspector.getBeanInfo(Class<?> beanClass)获取BeanInfo对象,然后获得所有的PropertyDescriptor,迭代该数组,依次判断名称获取指定方法,最后进行invoke()操作。
具体代码演示如下
package cn.itheima.blog8;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class IntroSpectorDemo {
/**
* 设有Person类,与前面的相同
* 通过内省操作getName()方法,获取name属性的值,并打印
*
*/
public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException {
Person p = new Person("mike", 20, "male");
String propertyName = "name";
getNameProperty_2(propertyName, p);
}
//第一种方式:获取name属性的值
public static <T> void getNameProperty_1(String propertyName, T t) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
//通过属性名与类名获得PropertyDescriptor对象
PropertyDescriptor prop = new PropertyDescriptor(propertyName, t.getClass());
//获取get方法
Method getPropName = prop.getReadMethod();
//调用get方法
System.out.println(getPropName.invoke(t));
}
//第二种方式:获取name属性的值
public static <T> void getNameProperty_2(String propertyName, T t) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
//通过类获取BeanInfo对象
BeanInfo bean = Introspector.getBeanInfo(t.getClass());
//获取所有的PropertyDescriptor对象
PropertyDescriptor[] pds = bean.getPropertyDescriptors();
//遍历数组,寻找与name属性对应的方法
for(PropertyDescriptor pd : pds){
if(propertyName.equals(pd.getName())){
//获取属性的get方法
Method getPropName = pd.getReadMethod();
//调用get方法
System.out.println(getPropName.invoke(t));
}
}
}
}
3、BeanUtils工具包
由于JavaBean的用途广泛,为了方便操作,所以Apach就提供了一套工具类,BeanUtils工具包,beanutils工具包的操作中运用了另一个jar--commons-logging,所以要用该工具类,需要在工程下拷入两个jar包。
Eclipse中添加jar包,在工程写简历一个lib文件夹,将beanutils与commons-logging包复制到该jar包中,然后鼠标右键点击lib目录,选择build path,最后选择add build path。该种添加jar包的方式比较常用,因为其中的jar包跟着工程移动,不以开发环境为转移。
BeanUtils包的功能比较强大,可以对八种数据类型进行自动类型转换,设置值时,可设置为字符串,取出来时仍是字符串,但是存入javabean中却是以基本数据类型存储,当有非基本数据类型存入是,也可以进行转换,但是需要先注册转换器Converter;同时,BeanUtils还支持属性级联操作,如将Date中有setTime方法,我们在设置Date型值是就可以birthday.time。 BeanUtils还提供JavaBean与Map进行转换等等一系列方法。运用示例如下
package cn.itheima.blog8;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.beanutils.BeanUtils;
public class BeanutilsDemo {
/**
* BeanUtils运用演示
*
*/
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Person p = new Person("mike", 22, "male");
String propertyName = "age";
//运用BeanUtils
System.out.println(BeanUtils.getProperty(p, propertyName));
System.out.println(BeanUtils.getProperty(p, propertyName).getClass());
}
}
三、类加载器
1、概述
类加载器就是加载类的工具,就是将.class文件加载到内存,并转成Class类的一个实例。
系统默认有三个类加载器:BootStrap,ExtClassLoader,AppClassLoader。类加载器也是一个类,然后最源头的类加载器(BootStrap)却不是,它潜在虚拟机内核中,由C++语言编写,因此打印他的名称为null。类加载器存在父子树状结构,不同的类加载器有不同加载范围,如下图所示
2、委托机制
为了更好地集中管理字节码,让内存中的字节码只有一份,类的加载就用到了委托机制。当一个类需要被一个类加载器加载时,先找其父类,让其父类加载,然后其父类又先不加载,先给父类的父类加载,依次类推,若上层的类加载不到,再由子类加载,直到加载结束。