Java 反射机制

  • 反射的概念与反射的基础Class类
  • 构造方法的反射
  • 成员变量的反射
  • 成员方法的反射
  • 对接收数组参数的成员方法进行反射
  • ArrayList、HashSet的比较及hashCode方法分析
  • 框架的概念及用反射技术开发框架的原理
  • 用类加载器的方式管理资源和配置文件
  • JavaBean与内省
  • 使用BeanUtils工具包操作JavaBean

反射的概念与反射的基础Class类

Class,是反射的基石。

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

Java程序中的各个Java类,他们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的的名字就是Class,要与小写的class关键字区别。

Class描述了那些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表,等等。学习反射,首先就要明白Class这个类。

Class类代表Java类,它的各个实例对象又分别对应什么呢?

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

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

Class————代表字节码,每使用一个类的时候,这个类的字节码就需要加载到内存中。

Class对象的初始化,得到字节码的三种方法:

Class clazz  = Person.class
Class clazz2 = person.getClass();
Class clazz3 = Class.forName("java.lang.String");//反射时使用最多

九个预定义的Class实例对象  isPrimitive原始类型
八个基本类型和void共九个
int.class == Integer.TYPE  //true
void.class == Void.TYPE

数组类型的Class实例对象
Class.isArray()
int[].class.isArray()

反射就是把java类的各个成分映射成为相应的java类。

例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示, 就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。 表示Java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示, 它们是Field, Method, Constructor, Package等等。

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


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 StringBuilder("abc"));

反射方式:

String str = (String)constructor.newInstance(new StringBuffer("abc")); //返回Object类型对象

两种方法的实参是一样的。

Class.newInstance()方法:

Class.forName("java.lang.String").newInstance();

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

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

class-->constructor-->newInstance

class-..............->newInstance//对于用无参的构造方法可以直接创建对象,不用再通过获得无参构造方法后再创建对象

一个类有多个构造方法,用什么方式可以区分清楚想得到其中的哪个方法呢?

根据参数的个数和类型,例如,Class.getMethod(name, Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。 重点:参数类型用什么方式表示?用Class实例对象。例如:int.class, int[].class,

int[] ints = new int[0]; ints.getClass()

Constructor代表一个构造方法,它拥有的方法有得到名字,得到所属于的类,产生实例对象。


Field类

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

Field实例代表的是字段的定义,而不是具体的变量。

Point p = new Point(1,2);
Field fieldY = p.getClass().getField("y");
//fieldY不是对象上的值,而是用它来取某个对象的字段值。
int y = fieldY.get(p);

Field  fieldX = p.getClass().getDeclaredField("x");
fieldX.setAccessible(true);//暴力访问私有成员
int x = fieldX.get(p);

反射的一个小应用

//字节码比较用==表示同一个字节码,不用equals
public class Example{
  public String str1 = "abc";
  public String str2 = "bcd";
  public String str2 = "bbb";
}
//将以上类的所有字符串属性中的‘b’-->'M'
Example example = new Example();
Field[] fields = example.getClass().getFields();
for(Field field: fields)
{
  if(field.getType() == String.class)
  {
    String oldValue = (String)field.get(example);
     String newValue = oldValue.replace('b','M');
     field.set(example,newValue);
  }
}

成员方法的反射

Method类

Method类代表某个类中的一个成员方法(可以用这个Method对象调用各个对象)

得到类中的某一个方法,Method methodCharAt = String.class.getMethod("charAt",int.class); 调用方法:

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

jdk1.4和jdk1.5的invoke方法的区别: jdk1.5 invoke(obj,Object...params) jdk1.4 invoke(obj,Object[] params) 需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数, 所以,调用charAt方法的代码也可以用jdk1.4改写为 methodCharAt.invoke("abc", new Object[]{1})

面向对象的设计

人在黑板上画圆,有人、黑板,圆三个类,画圆需要圆心和半径,它们是圆的私有变量,所以这个方法应该属于圆这个类,circle.draw();

列车员刹车,列车员没有刹车的功能,它只是给列车发一个信号,刹车是由列车完成的。

我们只能推门,关门的功能是门本身。

谁用于数据,谁就是干这件事的专家,专家模式。

谁拥有数据,方法就属于谁。

对接收数组参数的成员方法进行反射(注意可变参数) 用反射方式调用一个类的main方法: 调用的类名作为参数传递给当前类的main方法

 String startingClassName = args[0];  // 可以动态的指定,非常灵活
 Method mainMethod = Class.forName(startingClassName).getMethod("main",String[].class);
 mainMethod.invoke(null,new Object[]{String[]{"111","222","333"}});  // 为了兼容可变参数类型,需要对参数拆包
 //mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
 //mainMethod.invoke(null,new String[]{"111","222","333"}); ERROR

启动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[]{String[]{"111","222","333"}});
mainMethod.invoke(null,(Object)new String[]{"111","222","333"});

数组与Object的关系及其反射类型

数组的反射 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。 代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class。 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用,非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。 实际上 Object obj 可以引用任意维度的引用类型的变量。

Arrays.asList()方法处理int[] 和 String[]时的差异。 ArrayUtil工具类用于完成对数据的反射操作。 思考题:怎么得到数组中的元素类型?没有办法!

int[] arr1 = new int[3];
int[] arr2 = new int[4];
arr1.getClass() == arr2.getClass(); // true,两者的类型和维度都是相同的

int[] dim1 = new int[3];
int[][] dim2 = new int[3][];
Object obj = dim1; // ok
Object[] ints = dim1; // error
Object[] objs = dim2; // ok

String[] strs = new String[5];
Object[] objs = strs; // ok

List<T> Arrays.asList(T... a);
Arrays.asList(1,2,3); // int[]{1,2,4},是一个对象,Object类型
Arrays.asList("aa", "bb", "cc"); // String[]{"aa", "bb", "cc"},是一个对象数组,Object[]类型
// 可变参数学问大啊

数组的反射应用

ArrayUtil类的使用--打印一个对象

int[] arr = {1,2,3};
PrintObject(arr);
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);
  }
}

反射缺点,消耗性能。


ArrayList、HashSet的比较及Hashcode分析

集合、hashCode方法和equals方法

ArrayList对象保存的是对象的引用,size方法的结果并不是对象的个数,而是对象的引用个数,可能多个引用共同引用同一个对象

内存泄漏,对于一个对象存放到集合中,又改变了该对象的参与了hashCode运算的属性值,会导致内存泄露,对象无法删除。

内存泄漏与内存溢出的区别,内存泄漏,一个不再使用的对象,它一直占用着内存空间,垃圾回收器无法收回它。内存溢出,是内存不够用了。

我只吃鸡蛋,不关心是哪只鸡下的蛋。(今天,我们关心是哪只鸡下的蛋也非常重要,毕竟并不是每只鸡都是好鸡)


框架的概念及用反射技术开发框架的原理

反射的作用————实现框架功能

工具与框架

工具,是自己来调用别人的代码。

框架,是别人来调用你的代码。

框架与工具类有区别,工具类被用户的类使用,而框架则是调用用户提供的类。

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

框架要解决的核心问题,我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢,我写的框架程序怎样能调用你以后写的类(门窗)呢? 因为在写框架程序时无法知道要被调用的类名,所以在程序中无法new某各类的实例对象了,而要用反射方式来做。

反射结合配置文件来开发框架

InputStream ips = new FileInputStream("config.properties");//在工程目录下的配置文件  className=java.util.HashSet
Properties props = new Properties();
props.load(ips);
ips.close();
String className = props.getProperty("className");
Collection collection = (Collection)Class.forName(className).newInstance();

用类加载器的方式管理资源和配置文件

加载文件一般不采用相对路径,通常用绝对路径或者在classpath下用类加载器加载配置文件

// (把配置文件放置在跟源代码同一个目录下) InputStream ips = CurrentClass.class.getClassLoader().getResourceAsStream("edu/ccu/se/config.properties");

简写形式如下:

// (把配置文件放置在跟源代码同一个目录下) InputStream ips = CurrentClass.class.getResourceAsStream("config.properties");//用相对路径,可以不写包名

框架都是使用类加载器加载配置文件,所以要把配置文件放置在classpath下(也就是源文件夹下面)。 类加载器只能读配置文件,而FileInputStream和FileOutputStream可以更新配置文件。

Class类也提供getResourceAsStream方法的比喻:如果你每次都找我给你买可乐,那我不如直接向你卖可乐,即直接提供一个买可乐的方法给你。


JavaBean与内省

内省 introspection,支持对JavaBean操作。

JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。

如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。

JavaBean一般作为值对象(value-Object, VO),用来传递数据。

这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?

JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。

如果方法名为setId,就是设置id,至于你把它存到哪个变量上,用管吗?

如果方法名为getId,就是获取id,至于你从哪个变量上取,用管吗?

去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小写的。

setId的属性名,id
isLast的属性名,last
getID-->ID
setCPU的属性名,CPU
getUPS的属性名,UPS

总之,一个类被当作JavaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到Java内部的成员变量。

public class Person{
    private int x;
    public int getAge(){ return x; }
    public void setAge(int age) { this.x = age; }
}

一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean。

在JavaEE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就遵守大家的约定。

JDK中提供了对JavaBean进行操作的API,这套API称为内省。如果要你自己去通过getX方法来访问私有的x,则怎么做,有一定难度吧? 用这套内省api操作JavaBean比用普通类的方式更方便。

IntroSpector内省,内部检查

Java内省主要的类包括,PropertyDescriptor, IntroSpector, BeanInfo 等。

获得对象的属性值

private static Object getProperty(Object obj,String propertyName)
{
  PropertyDescriptor pd = new PropertyDescriptor(propertyName,obj.getClass());
  Method methodGetX = pd.getReadMethod();
  Object retVal = methodGetX.invoke(obj);
  return retVal;
}

设置对象的属性值

private static void setProperty(Object obj,String propertyName,Object value)
{
  PropertyDescriptor pd = new PropertyDescriptor(propertyName,obj.getClass());
  Method methodSetX = pd.getWriteMethod();
  methodSetX.invoke(obj,value);
}

获得对象的属性值,另一种复杂方式:

private static Object getProperty(Object obj,String propertyName)
{
  //PropertyDescriptor pd = new PropertyDescriptor(propertyName,obj.getClass());
  //Method methodGetX = pd.getReadMethod();
  //Object retVal = methodGetX.invoke(obj);
  BeanInfo beanInfo = IntroSpector.getBeanInfo(obj.getClass());
  PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
  Object retVal = null;
  for(PropertyDescriptor pd : pds)
  {
     if(pd.getName().equals(propertyName))
      {
        Method methodGetX = pd.getReadMethod();
        retVal = methodGetX.invoke(obj);
        break;
      }
  }
  return retVal;
}

使用BeanUtils工具包操作JavaBean

BeanUtils工具,需要导入 commons-beanutils.jar 和其依赖的 commons-logging.jar。

BeanUtils 和 PropertyUtils 工具类。

BeanUtils以字符串的形式对属性进操作,PropertyUtils以属性本身的类型进行操作

/**
 * 第三方的Bean操作工具 BeanUtils
 * @throws Exception
 */
@Test
public void test_BeanUtils() throws Exception{
    ReflectPoint obj = new ReflectPoint(4,6);
    // getProperty方法返回值是String类型的
    String retval = BeanUtils.getProperty(obj, "x");

    // setProperty方法设置属性的值是String类型
    String value = "12";
    BeanUtils.setProperty(obj, "x", value);

    // PropertyUtils.getProperty返回值类型是其真实类型,基本类型对应为包装类
    // 因为是运行时才知道返回类型,所以需要强制类型装换
    int intRetVal = (int)PropertyUtils.getProperty(obj, "x");
    // 设置值时也是使用该属性的真实类型
    PropertyUtils.setProperty(obj, "x", 100);
}

上述的类型转换是BeanUtils工具自动完成的。

如果ReflectPoint类中有属性private java.util.Date birthday,BeanUtils也可以对其操作:

BeanUtils.setProperty(obj, "birthday.time", "1000");
String result = BeanUtils.getProperty(obj, "birthday.time");  // 属性链

可以使用属性链。

BeanUtils也可以实现对象和Map集合之间的相互转换。

results matching ""

    No results matching ""