反射

为什么需要反射?

背景知识: Java引用变量有两种类型,一种编译时类型,一种运行时类型。编译时类型由声明变量使用的类型决定。运行时类型由实际赋给他的变量决定。两个类型不一致的话,就可能出现“多态”的情况。

 

package Test01;
class Base{
	final static int book=6;
	public void base(){
		System.out.println("老爸有的函数.......");
	} 
	public void test() {
		System.out.println("普通有的函数.......");
	}
}
public class SubClass extends Base{
	final static String book="哈哈,我修改了book";
	public void test() {
		System.out.println("子类重写的函数.......");
	}
	public void sub(){
		System.out.println("子类才有的函数.......");
	}
	public static void main(String[] args) {
		
		Base base =new Base();
		base.base();
		base.test();
		System.out.println("base里的book是"+base.book); 
		SubClass sub =new SubClass();
		sub.base();
		sub.test();
		System.out.println("sub里的book是"+sub.book);
		//编译和引用类型不一样的
		Base b =new SubClass();
		b.base();
		b.test();
		System.out.println("b里的book是"+b.book);
		//b编译时是Base类型,没提供sub()方法
	/*	b.sub();*/
		
	}
}

 

  以上代码说明:

1.对象的方法具有多态性。最后一行注释的代码不能通过编译,虽然b的引用变量确实包含了sub方法,但是编译时它仍是Base类型,不能执行 SubClass里的方法(提示:可使用反射来执行该方法)。

2.对象的实例变量不具有多态性。b里的book不是输出子类的book,而是父类的。

运行结果如下:

 

 

       总结下可能出现的问题:程序运行会出现编译和运行的类型不一致的情况,比如Person p =new Student(); 该行代码产生一个p变量,编译时 是Person类型,运行时是Student类型。更不好的例子是程序如果接收外部的一个对象,这个对象编译时是Object,但是运行时却调用对象运行类型时的方法。

解决 程序需要知道 运行时对象和类的信息 情况  这个问题的办法有:

1.假设 运行和编译就完全已经知道对象和类的信息,则强制类型转换,将编译时类型强转为运行时类型。强转时注意:基本数据类型只能在数值类型之间转换(整型,字符型,浮点型),数值型不能喝布尔型转。存在继承关系才父子类之间才能进行强转,否则会CastClassException。因此,在引用强转时候加上instanceof(instanceof用法不清楚的可以搜一下)判断下再转,能增加代码的健壮性。

2假设 只能运行 才能发现对象和类的信息,就得靠 反射解决问题。

下面进入重点啦:

如何获得Class对象呢?

使用Class类的静态方法foreName(String clazzName)方法传入的字符串必须是类的全限定名,包括包名。

1   Class a= Class.forName("Test01.Classloader");
2    System.out.println(a);

 

类的class属性。Person.class.

1  Class a= Base.class;

 

调用某对象的getClass()方法。该方法是java.lang.object类里的方法,所以所有的对象都能使用。

Base b =new Base();
Class a= b.getClass();

 

对比下,前两种方法都是根据类取得类指定Class对象,但是第二个更好,因为不需要调用方法,而且代码更安全,编译阶段就检查了要访问的该Class对象是否存在。

获得Class对象了还没结束,还需要调用里面的方法来获得Class对象和该类的详细信息。

  获得Class对象里的构造器,Constructor (在获得类里必须有对应的构造器)

   Class a= Class.forName("Test01.Classloader");
   //无参数
   Constructor  c = a.getConstructor(null);
   //有参数
   Constructor  m = a.getConstructor(String.class,int.class); //数据类型在前,class后面,具体看构造参数
   System.out.println(m);

     获得Class对象里的方法,Method

Class a= Class.forName("Test01.Classloader");
Method  m = a.getMethod("test", String.class); //第一个是方法名字,后面的是参数类型
   System.out.println(m);

 

      获得Class对象里的实例变量,Field(注:如果同一个包下的两个类,一个被访问类里的成员变量省略修饰符,会报找不到成员变量的错)

 Class a= Class.forName("Test01.Classloader");
  
   Field  m = a.getField("name"); //第一个是方法名字,后面的是参数的类型
   System.out.println(m);

拿到了Class对象里的方法,如果不想到去调用它,拿到了也没用啊。所以,Method对象里有invoke方法。该例子适用适用于默认的构造器。 如果是有参数的构造器,则应该先拿到Constructor 对象再调用这个对象的newInstance()(注意:此方法带参数)构造该Class对象的实例。

package Test01; 
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
public class SubClass{
 
	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, NoSuchFieldException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Class a= Class.forName("Test01.Classloader");
  
       Object object    = a.newInstance(); //一个实例
       Method test =a.getMethod("test", String.class);
       test.invoke(object, "zyz");
 	}
}

 修改公共的成员变量,引用举例

  Class a= Class.forName("Test01.Classloader");
	   Object object    = a.newInstance(); //一个实例
	   Field f =a.getField("name");
	   f.set(object, "zyz");

 修改公共的成员变量,基本类型 setXxx(Object obj,Xxx val),getXxx(Object obj) Xxx是基本类型举例

 Class a= Class.forName("Test01.Classloader");
       Object object    = a.newInstance(); //一个实例
       Field f =a.getField("name");
       f.setXxx(object, "zyz");

 

小注意:上述讨论的都是和公共的构造器,方法,成员变量。私有的(private)就不能同样处理

原理是通过setAccessible方法,避开了对类的加载器对类的检查。

反例:

 正确的:(对比运行结果)

Classloader类 长这样

package Test01;
public class Classloader{
    public String name;
    public int i;
     private   Classloader(String name) {
         this.name =name;
        System.out.println(name);
    }

}

 

以上讨论了引用类型,基本类型,没涉及,如果想反射创建数组怎么办呢?

对于数组java.lang.reflect包里有个Array类,它能代表所有的数组,利用该类能动态创建数组,操作数组元素。

Array类里有

静态方法 Object newInstance(Class.class,int length)创建有指定实例的数组

静态方法 void setXxx(Object arr,int index,int val)  为arr 数组的第index设值

静态方法 xxx getXxx(Object arr,int index) 返回array数组的第Index个元素

下面代码举例说明

package Test01;


import java.lang.reflect.Array;


public class ArrayTest {
 public static void main(String[] args) {
     //创建一个有指定元素的数组(有点疑问为嘛数组也 是Object,某个返回元素类型元素Object)
    Object object = Array.newInstance(String.class, 3);
    Array.set(object, 2, "小张");
    Object name =Array.get(object, 2);
    System.out.println(name);
}
}

 

 

下面是对反射的一个实际应用举例

准备 1.txt 文件,里面是 

 

a= java.util.Date
b=javax.swing.JFrame

 

package Test01;
//实现了从txt文件中读取key-value,并根据value创建对象,放入hashmap里,即一个简单的对象池功能

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Properties;


public class ObjectPoolFactory {
     HashMap<String, Object> objectPool =new HashMap<>();
     //创建对象
     public  Object createObject(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class<?> object = Class.forName(className);
         return object.newInstance();
    }
     public void initPool(String fileName) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
         FileInputStream inputStream =new FileInputStream(fileName);
        Properties properties =new Properties();
        properties.load(inputStream);
        for(String name:properties.stringPropertyNames()) {
            objectPool.put(name,createObject(properties.getProperty(name)));
        }
    }
     //从对象池里读取对象
     public Object getObject(String name) {
        return objectPool.get(name);
    }
     public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
         ObjectPoolFactory factory =new ObjectPoolFactory();
         factory.initPool("D://1.txt");
         System.out.println(factory.getObject("a"));
         System.out.println(factory.getObject("b"));
    }
}

 注意 1.txt是这样的 

 

不要画蛇添足 ,变成这样,否则会找不到文件的。(低级错误)

可得到如下 的运行结果输出两个对象。

上面的例子就是Spring框架采用的方式,简化Javaee的开发。但是不同的是从属性文件读的信息太少,spring 是读xml格式的文件。


给之前的对象池工厂增加 配置对象的成员变量 的值 (待续.....)

准备1.txt 文件,里面是 

a= java.util.Date
b=javax.swing.JFrame
#set the title of a
a%title=Test Title