反射
今天我来分享下, 我关于Java中反射的理解。如果做过iOS开发的同学应该很清楚iOS里Runtime的黑魔法, 而Java中的反射其实就是iOS中的Runtime.
什么是反射
反射是一种专门为静态语言提供的技术,用于在程序运行时(Runtime)动态的修改程序的结构,改变程序的行为.
Java为什么要引入反射
Java也是静态语言.为了让Java语言也能够在运行时修改类或者对象的状态和改变类或对象的行为因此引入了反射机制.
在静态语言中,使用一个变量时,必须知道它的类型.在Java中,变量的类型信息在编译时都保存到了class文件中,这样在运行时才能保证准确无误.换句话说,程序在运行时的行为都是固定的.如果想在运行时改变,就需要反射这东西了.举个例子:
在Spring中,有这样的java bean配置:
<bean id="someID" class="com.sweetcs.AppleBean">
<property name="someField" value="someValue" />
</bean>
Spring
在处理这个bean标签
时,发现class属性
指定的是com.sweetcs.AppleBean
这个类,就会调用Class.forName(String)
来实例化这个类,再通过反射,可以取到someField
属性的值了.
如果我们想改变这个程序运行时的信息,我们这里直接修改bean
,property的属性
即可,无需重新编译.
在动态语言中,使用变量不需要声明类型
,因而不需要这反射这种机制
。
比如在javascript
中,我们知道有个变量applebean
,不管applebean
有没有sell()
属性,我们都可以这么写:applebean.sell()
因为没有类型检查,这里这么写是允许的。至于在运行时报不报错,就要看运行时applebean的真正值了。
一点思考
一、反射是可在运行期间确定对象的类型, 多态也是在运行期间才确定类型, 那么多态的实现是否和反射有关?
- 多态的技术上的实现是方法后期的动态绑定, 即在运行时才决定方法应该绑定到那块内存中(该内存即对应相应的对象)。
- 反射技术上的实现是基础是Java中的方法区的class,有的语言把它们称为类对象.Java中的类都是有类对象创建的,我们可以通过类对象来管理我们创建的所有对象.
- 说一说多态的实现本质上和反射并没有关系.
二、为什么Java也能够实现反射,加入我们在编译期间就确定了真实对象类型还能使用反射吗?
- Java在自建立以来就有反射技术, 其反射技术和多态主要基于JVM在运行时才动态加载类或调用方法/访问属性的机制.该机制不需要事先(写代码的时候或编译期)知道真实的运行对象是谁.
- 而反射是基于Class对象,我们每编写的一个类,都会被编译成字节码文件,将类信息存储在这个字节码文件中,该文件即Class对象,在我们使用到这个Class的静态成员变量或者成员时它就会加载进JVM.
- 多态主要是基于方法的动态绑定,前提是JVM在运行时才确定对象的真实类型,这时候才去绑定方法到真实的对象上.
反射机制的作用
我们先了解下反射究竟能做哪些事情, 可以分为以下四类.
- 在运行期间可以动态的创建任意类型的对象
- 在运行期间可以获取任意类的类型的任意信息
- 在运行期间可以获取某个对象某个属性的值或设置某个属性的值
- 在运行期间可以调用某个对象的任意方法
Class对象
要学习反射, 首先要学习Class对象,Class对象表示的是 我们类的类信息(我们编写的类,).
比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息.
实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值.
获取Class对象
类名.class
这种类型表明编译期间就能确定类型。仅限于编译期间这个类型是已知的。
@Test
public void test1(){
Class c1 = int[].class;
Class c2 = int[].class;
System.out.println(c1 == c2);
Class c3 = int[][].class;
System.out.println(c1 == c3);
Class c4 = byte[].class;
System.out.println(c1 == c4);
}
对象.getClass()
获取某个对象的运行时类型
@Test
public void test2(){
Object obj = new TestClass();
Class class1 = obj.getClass();
System.out.println(class1);
}
Class.forName(全限定类名)
使用properties配置文件, 动态配置运行时要创建的类.
@Test
public void test3() throws Exception{
Properties pro = new Properties();
//文件在src下,最终在bin等类路径(.class文件)下,可以用ClassLoader加载这个文件
//文件在src外面,只能使用FileInputStream来加载
pro.load(new FileInputStream("type.properties"));
String name = pro.getProperty("typename");
//这句代码可能会发生:ClassNotFoundException,类型找不到异常
Class clazz = Class.forName(name);
System.out.println(clazz);
}
类加载器.loadClass(全限定类名)
@Test
public void test4()throws Exception{
Properties pro = new Properties();
//文件在src下,最终在bin等类路径(.class文件)下,可以用ClassLoader加载这个文件
//文件在src外面,只能使用FileInputStream来加载
pro.load(new FileInputStream("type.properties"));
String name = pro.getProperty("typename");
//获取系统类加载器对象
ClassLoader c = ClassLoader.getSystemClassLoader();
Class loadClass = c.loadClass(name);
System.out.println(loadClass);
}
类加载器应用场景
- 主要应用于.class文件加密, 需要自身的类加载器来解密
- .class文件是特殊路径,系统不知道,只能用类加载器
注意事项
- 提供的类名是全限定类名(Class.forName或者类加载器获取类对象)
- Class.forName可能抛出ClassNotFoundException
使用场景
(一) 创建任意类型对象
方式一 使用Class对象.newInstance(parameters)
步骤
- 获取这个类型的类对象
- 通过这个类对象.newInstance()创建出对应对象.
- 这个类型必须有一个公共的无参构造器.
示例代码
@Test
public void testNewInstanceMethod1_2() {
FileInputStream fis = null;
try {
fis = new FileInputStream("properties.properties");
Properties properties = new Properties();
properties.load(fis);
String className = properties.getProperty("student");
Class classObject = Class.forName(className);
Object stu = classObject.newInstance();
System.out.println(stu);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void testNewInstanceMethod1_1() {
FileInputStream fis = null;
try {
fis = new FileInputStream("properties.properties");
Properties properties = new Properties();
properties.load(fis);
String className = properties.getProperty("typename");
Class classObject = Class.forName(className);
String str = (String) classObject.newInstance();
System.out.println(classObject);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
特点
- 这个类型必须有一个公共的无参构造
- 非公共的:IllegalAccessException
没有无参构造:NoSuchMethodException 类型<init>()
- 非公共的:IllegalAccessException
- 这个类型在运行期间必须存在
- 不存在 ClassNotFoundException
方式二 使用构造器对象.newInstance(parameters)
步骤
得到这个类型的class对象
-
通过class对象, 获取构造器对象。
-
可能有,可能没有
- 如果构造器的访问权限不允许,那么可以使用如下方法,使得它可以被访问
构造器对象.setAccessible(true)
-
-
使用构造器对象创建对应的类型。
- 构造器对象.newInstance(...)
示例代码
@Test
public void testNewInstanceMethod2_1() {
FileInputStream fis = null;
try {
fis = new FileInputStream("properties.properties");
Properties properties = new Properties();
properties.load(fis);
String className = properties.getProperty("student");
Class classObject = Class.forName(className);
Constructor constructor = classObject.getDeclaredConstructor(String.class, int.class);
if (!constructor.isAccessible())
constructor.setAccessible(true);
Object o = constructor.newInstance("SweetCS", 26);
System.out.println(o);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} finally {
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
特点
- 如果够照的访问权限是不允许的, 可以通过调用构造器对象的setAccessible解决
- 如果没有会抛出 IllegalAccessException
(二)获取类的任意信息
通过这个Class对象可以获取这个类型的所有的信息
包、类名、修饰符、父类、接口们、成员们:属性、构造器、方法、注解信息(必须是RetentionPolicy.RUNTIME
- 关于属性:
修饰符、数据类型、属性名、注解信息 - 关于构造器
属性能获取+ 2(形参列表、抛出的异常列表) - 关于方法
属性能获取 + 3(返回值类型、形参列表、抛出的异常列表)
public class TestClassInfo {
@Test
public void testClassInfo() throws ClassNotFoundException {
Class managerClass = Class.forName("bean.Manager");
// 获取属性信息 (访问控制符(Modifilers) 类型 属性名称)
System.out.println("fileds: ");
Field[] fields = managerClass.getDeclaredFields();
System.out.println("modifiler\ttype\tpropertyName");
for (int i= 0; i <fields.length; i++) {
Field field = fields[i];
System.out.print(
"|-"+ Modifier.toString( + field.getModifiers()) + "-|" + "\t" +
"|-"+ field.getType() + "-|" + "\t" +
"|-"+ field.getName() + "-|"+ "\t");
System.out.println();
// 获取注解
String annotations = StringUtils.join(field.getDeclaredAnnotations(), ",");
if (StringUtils.trimToNull(annotations) != null)
System.out.println("方法上的注解们:" + annotations);
}
// 构造器比属性少了一个类型
System.out.println("construtors: ");
Constructor[] constructors = managerClass.getConstructors();
System.out.println("访问控制符\t构造器名称");
for (Constructor c :
constructors) {
System.out.println("|-" + Modifier.toString(c.getModifiers()) + "\t" + "-|" + "|-" + c.getName() + "-|" );
String exceptions = StringUtils.join(c.getExceptionTypes(), ",");
if (StringUtils.trimToNull(exceptions) != null)
System.out.println("构造器上的异常:" + exceptions);
}
// Method 就比属性多了一个返回类型
System.out.println("methods: ");
Method[] methods = managerClass.getDeclaredMethods();
for (Method m :
methods) {
System.out.println("|-" + Modifier.toString(m.getModifiers()) + "-|"+ "\t" +
"|-" + m.getReturnType() + "-|"+"\t" +
"|-" + m.getName() +"-|"+
"("+ StringUtils.join(m.getParameterTypes(), ",") + ")");
// 获取注解
String annotations = StringUtils.join(m.getDeclaredAnnotations(), ",");
if (StringUtils.trimToNull(annotations) != null)
System.out.println("方法上的注解们:" + annotations);
String exceptions = StringUtils.join(m.getExceptionTypes(), ",");
if (StringUtils.trimToNull(exceptions) != null)
System.out.println("构造器上的异常:" + exceptions);
}
// 其他
// 获取包名
Package p = managerClass.getPackage();
System.out.println("包名: " + p.getName());
// 类访问修饰符
String modifierName = Modifier.toString(managerClass.getModifiers());
System.out.println("类访问修饰符:" + modifierName);
// 父类
Class superClass = managerClass.getSuperclass();
System.out.println("父类:" + superClass);
// 获取实现的接口
Class[] interfaces = managerClass.getInterfaces();
for (Class i:
interfaces) {
System.out.println("接口:" + i);
}
}
}
输出
fileds:
modifiler type propertyName
|-private static final-| |-long-| |-serialVersionUID-|
|-private-| |-double-| |-bonus-|
construtors:
访问控制符 构造器名称
|-public -||-day23.bean.Manager-|
|-public -||-day23.bean.Manager-|
methods:
|-public-| |-class java.lang.String-| |-toString-|()
|-public-| |-int-| |-compareTo-|(class day23.bean.Manager)
|-public volatile-| |-int-| |-compareTo-|(class java.lang.Object)
|-public-| |-double-| |-getBonus-|()
|-public-| |-void-| |-setBonus-|(double)
方法上的注解们:@day23.bean.MyAnnotation(value=SweetCS)
包名: day23.bean
类访问修饰符:public final
父类:class day23.bean.Employee
接口:interface java.io.Serializable
接口:interface java.lang.Comparabl
(三) 动态设置和获取属性值
@Test
public void testFiled() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//假设Manager对象是在运行期间创建的
Class mangerClass = Class.forName("day23.bean.Manager");
//obj代表一个经理对象
Object obj = mangerClass.newInstance();
//2、设置obj这个经理的奖金值
//(1)先得到奖金属性对象
Field bounsField = mangerClass.getDeclaredField("bonus"); //"bonus"配置文件中配置
//(2)设置属性可操作,private属性既不能使用反射直接setter也不能直接getter
if (!bounsField.isAccessible()) bounsField.setAccessible(true);
System.out.println("bonus :" + bounsField.get(obj));
//(3)设置奖金值
bounsField.set(obj, 1000.0);
//3、获取obj这个经理的奖金值
System.out.println("bonus :" + bounsField.get(obj));
}
反射进行属性操作终结
- 通过类对象获取属性对象(Field)
- 最好用getDeclaredField来获取属性对象, 该方法私有属性也能获取
- 如果是读属性, 调用
属性对象.get(实例对象)
- 如果是写属性, 调用
属性对象.set(实例对象, 参数)
注意: 如果属性不可访问, 设置属性为accessible, 即可访问
(四) 方法调用
反射技术还可以通过类对象来获取这个对象所有的方法对象。java中方法对象使用Method表示。所以我们可以通过类对象拿到Method对象, 来进行方法的调用。
@Test
public void testMethodInvoke() throws Exception {
// 假设Manager对象是在运行期间创建的
Class clazz = Class.forName("day23.bean.Manager");// 这个字符串可以从配置文件中获取
// obj代表一个经理对象
Object obj = clazz.newInstance();
//调用obj经理对象的setEId,setEName,setSalary等
//(1)得到setName方法对象
//参数一:方法名
//参数二:方法的形参类型列表
//因为方法可能重载,所以需要用“方法名 + 形参列表”
//"setEname", String.class通常也在xml文件中配置
//getMethod可以得到这个类型的公共的方法包括从父类继承的
Method method = clazz.getMethod("setEname", String.class);//
//(2)调用这个方法
//参数一:哪个对象的method方法
//参数二:该method方法需要的实参列表
//invoke方法的返回值,就是method方法调用后的返回值,如果method方法没有返回值,那么为null
Object returnValue = method.invoke(obj, "张三");
System.out.println(returnValue);
// (1)得到getName方法对象
Method method2 = clazz.getMethod("getEname");
//(2)调用
Object value = method2.invoke(obj);
System.out.println(value);
}
反射调用方法总结
- 通过类对象, 拿到对应的方法对象(Method)
- 对于setter, 使用getMethod(方法名, 类对象列表). 这里的类对象列表是方法名的形参类型的类对象
- 对于getter, 使用getMethod(方法名)
- 调用invoke方法
- 对于setter, 传入实例对象(可以用类对象.newInstance()创建) 和 set的参数
- 对于getter, 传入实例对象