反射
为什么需要反射?
背景知识: 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