Java中的反射
反射
今天我来分享下, 我关于Java中反射的理解。如果做过iOS开发的同学应该很清楚iOS里Runtime的黑魔法, 而Java中的反射其实就是iOS中的Runtime.
Java为什么要引入反射
- java是一门强类型,静态语言,需要在编译期间检查引用类型。而动态语言, 在编译期间不用检查引用类型。
说java是强类型是因为java的每种数据都需要声明类型。并且java会在编译期间进行引用类型检查。
实际开发中,不是所有对象都可以在编译期间确定类型。Java希望学习动态语言的特点,在运行期间去确定对象的类型,获取对象的信息,Java引入反射机制就是为了实现这个目标,所以Java又被称为准动态语言。
一点思考
一、静态语言和反射概念冲突吗
可能你看到这里会有疑惑, 竟然说java是静态语言, 那么它在编译期间就确定了变量的类型, 那么不是和反射的概念相互冲突?
其实这并不冲突, java中有编译期间类型和运行时类型的机制, 也就说java是静态语言说的是其在编译期间的数据类型。反射可以再运行期间获得对象在运行时的真实类型。因此,Java中所有的方法都是通过动态绑定来实现多态的,但如果直接访问某个域,则这个访问就会在编译其进行解析;
二、反射是可在编译期间确定类的类型, 多态也是在编译期间才确定类型, 那么多态的实现是否和反射有关?
多态的技术上的实现是方法后期的动态绑定。而反射技术上的实现是因为Java中的类都是有类对象创建的,我们可以通过类对象来管理我们创建的所有对象。多态的实现本质上和反射并没有关系。他们之前的实现的技术细节我也不是很清楚,留作后续研究
反射机制的作用
我们先了解下反射究竟能做哪些事情, 可以分为以下四类.
- 在运行期间可以动态的创建任意类型的对象
- 在运行期间可以获取任意类型的任意信息
- 在运行期间可以获取某个对象某个属性的值或设置某个属性的值
- 在运行期间可以调用某个对象的任意方法
反射机制的根源
所谓的反射的根源, 就是反射机制实现的依赖和原理。反射主要依赖于java的类对象实现。Java在使用任何类型的时候都会使用ClassLoader将这个类型加载到内存。并且在内存中创建一个唯一一个Class对象来代表该类型。
Class对象
要学习反射, 首先要学习Class对象, 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, 传入实例对象