Java反射机制的定义
Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。俗称”超级工具箱“。
反射都用于哪些场景
用于代码编辑工具中,如Eclipse或者idea,我们在代码编写的时候,是不是经常自动的给我们各种提示呢,这就是用到了反射的原因。
一些框架的开发,为了程序更加的优雅和便携,就会用到反射机制。比如,spring的bean的自动注入,看如下代码:
1
| <bean id="person" class="com.study.beans.User" init-method="initUser">
|
还有Mybatis 在 Mapper 使用外部类的 Sql 构建查询时,也是用到反射:
1 2 3 4 5 6 7 8 9 10 11 12
| @SelectProvider(type = UserSql.class, method = "getListSql") List<User> getList();
public class UserSql { public String getListSql() { String sql = new SQL() { SELECT("*"); FROM("user"); }.toString(); return sql; } }
|
- 数据库连接池。使用反射调用不同数据库驱动
1 2 3 4 5
| String url = "jdbc:mysql://127.0.0.1:3306/database"; String username = "root"; String password = "root"; Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection(url, username, password);
|
反射机制的优缺点
为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念
- 静态编译:在编译时确定类型,绑定对象,即通过。
- 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。
优点
可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。
比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。
采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
缺点
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。
理解Class类和类类型
想要了解反射首先理解一下Class类,它是反射实现的基础。
类是java.lang.Class类的实例对象,而Class是所有类的类(There is a class named Class)
对于普通的对象,我们一般都会这样创建和表示:
上面说了,所有的类都是Class的对象,那么如何表示呢,可不可以通过如下方式呢:
但是我们查看Class的源码时,是这样写的:
1 2 3
| private Class(ClassLoader loader) { classLoader = loader; }
|
可以看到构造器是私有的,只有JVM可以创建Class的对象,因此不可以像普通类一样new一个Class对象,虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Class对象,共有三种方式,如下:
1 2 3 4 5 6 7 8 9 10
|
Class c1 = Code.class;
Class c2 = code1.getClass();
Class c3 = Class.forName("com.jelly.reflect.Code");
|
这里,c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Code的类类型(class type)。
这里就让人奇怪了,前面不是说Code是Class的对象吗,而c1、c2、c3也是Class的对象,那么Code和c1、c2、c3不就一样了吗?为什么还叫Code什么类类型?
这里不要纠结于它们是否相同,只要理解类类型是干什么的就好了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。
举个简单例子代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class ReflectDemo { public static void main(String[] args) throws ClassNotFoundException { Class class1=ReflectDemo.class; System.out.println(class1.getName());
ReflectDemo demo2= new ReflectDemo(); Class c2 = demo2.getClass(); System.out.println(c2.getName());
Class class3 = Class.forName("com.jelly.reflect.ReflectDemo"); System.out.println(class3.getName()); } }
com.jelly.reflect.ReflectDemo com.jelly.reflect.ReflectDemo com.jelly.reflect.ReflectDemo
|
Java反射相关操作
前面我们知道了怎么获取Class,那么我们可以通过这个Class干什么呢?
总结如下:
- 获取成员方法Method
- 获取成员变量Field
- 获取构造函数Constructor
- 获取对象
- 获取Class类
获取成员方法
单独获取某一个方法是通过Class类的以下方法获得的:
1 2 3 4 5
| public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
public Method getMethod(String name, Class<?>... parameterTypes)
|
两个参数分别是方法名和方法参数类的类类型列表。
举个栗子
例如类A有如下一个方法:
1 2 3
| public void fun(String name,int age) { System.out.println("我叫"+name+",今年"+age+"岁"); }
|
现在知道A有一个对象a,那么就可以通过:
1 2 3 4 5 6 7 8 9 10 11
| Class c = Class.forName("com.jelly.reflect.Person");
Object o = c.newInstance();
Method method = c.getMethod("fun", String.class, int.class);
method.invoke(o, "jelly", 10);
|
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class Person { private String name; private int age; private String msg="hello wrold"; public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Person() { }
private Person(String name) { this.name = name; System.out.println(name); }
public void fun() { System.out.println("fun"); }
public void fun(String name,int age) { System.out.println("我叫"+name+",今年"+age+"岁"); } }
public class ReflectDemo { public static void main(String[] args){ try { Class c = Class.forName("com.jelly.reflect.Person"); Object o = c.newInstance(); Method method = c.getMethod("fun", String.class, int.class); method.invoke(o, "jelly", 10); } catch (Exception e) { e.printStackTrace(); } } }
|
执行结果:
怎样,是不是感觉很厉害,我们只要知道这个类的路径全称就能玩弄它于鼓掌之间。
获取所有成员方法
有时候我们想获取类中所有成员方法的信息,要怎么办。可以通过以下几步来实现:
1.获取所有方法的数组:
1 2 3 4 5 6
| Class c = Class.forName("com.jelly.reflect.Person");
Method[] methods = c.getDeclaredMethods();
Method[] methods = c.getMethods();
|
2.然后循环这个数组就得到每个方法了:
完整代码如下:
person类跟上面一样,这里以及后面就不贴出来了,只贴关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class ReflectDemo { public static void main(String[] args){ try { Class c = Class.forName("com.jelly.reflect.Person"); Method[] methods = c.getDeclaredMethods(); for(Method m:methods){ String methodName= m.getName(); System.out.println(methodName); } } catch (Exception e) { e.printStackTrace(); } } }
执行结果: getName setName setAge fun fun getAge
|
这里如果把c.getDeclaredMethods();改成c.getMethods();执行结果如下,多了很多方法,因为把Object里面的方法也打印出来了,因为Object是所有类的父类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| getName setName getAge setAge fun fun wait wait wait equals toString hashCode getClass notify notifyAll
|
获取成员变量
想一想成员变量中都包括什么:成员变量类型+成员变量名
类的成员变量也是一个对象,它是java.lang.reflect.Field的一个对象,所以我们通过java.lang.reflect.Field里面封装的方法来获取这些信息。
单独获取某个成员变量,通过Class类的以下方法实现:
1 2 3 4 5
| public Field getDeclaredField(String name)
public Field getField(String name)
|
参数是成员变量的名字。
举个栗子
例如一个类A有如下成员变量:
如果A有一个对象a,那么就可以这样得到其成员变量:
1 2
| Class c = a.getClass(); Field field = c.getDeclaredField("n");
|
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class ReflectDemo { public static void main(String[] args){ try { Class c = Class.forName("com.jelly.reflect.Person"); Field field = c.getDeclaredField("msg"); Object o = c.newInstance(); field.setAccessible(true); Object msg = field.get(o); System.out.println(msg); } catch (Exception e) { e.printStackTrace(); } } }
执行结果: hello wrold
|
获取所有成员变量
1.获取所有成员变量的数组:
1
| Field[] fields = c.getDeclaredFields();
|
2.遍历变量数组,获得某个成员变量field
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class ReflectDemo { public static void main(String[] args){ try { Class c = Class.forName("com.jelly.reflect.Person"); Field[] fields = c.getDeclaredFields(); for(Field field :fields){ System.out.println(field.getName()); } } catch (Exception e) { e.printStackTrace(); } } }
执行结果: name age msg
|
获取构造函数
最后再想一想构造函数中都包括什么:构造函数参数
同上,类的成构造函数也是一个对象,它是java.lang.reflect.Constructor的一个对象,所以我们通过java.lang.reflect.Constructor里面封装的方法来获取这些信息。
单独获取某个构造函数,通过Class类的以下方法实现:
1 2 3 4 5
| public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
public Constructor<T> getConstructor(Class<?>... parameterTypes)
|
这个参数为构造函数参数类的类类型列表。
举个栗子
例如类A有如下一个构造函数:
1 2 3
| public A(String a, int b) { }
|
那么就可以通过:
1
| Constructor constructor = a.getDeclaredConstructor(String.class, int.class);
|
来获取这个构造函数。
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class ReflectDemo { public static void main(String[] args){ try { Class c = Class.forName("com.jelly.reflect.Person"); Constructor constructor = c.getDeclaredConstructor(String.class); constructor.setAccessible(true); constructor.newInstance("jelly"); } catch (Exception e) { e.printStackTrace(); } } }
jelly
|
注意:Class的newInstance方法,只能创建只包含无参数的构造函数的类,如果某类只有带参数的构造函数,那么就要使用另外一种方式:
1
| fromClass.getDeclaredConstructor(String.class).newInstance(“jelly”);
|
获取所有的构造函数
1.获取该类的所有构造函数,放在一个数组中:
1
| Constructor[] constructors = c.getDeclaredConstructors();
|
2.遍历构造函数数组,获得某个构造函数constructor:
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class ReflectDemo { public static void main(String[] args){ Constructor[] constructors = c.getDeclaredConstructors(); for(Constructor constructor:constructors){ System.out.println(constructor); } } catch (Exception e) { e.printStackTrace(); } } }
public com.jelly.reflect.Person() public com.jelly.reflect.Person(java.lang.String)
|
通过反射了解集合泛型的本质
Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
public class GenericEssence { public static void main(String[] args) { List list1 = new ArrayList(); List<String> list2 = new ArrayList<String>();
list2.add("hello"); System.out.println("list2的长度是:" + list2.size());
Class c1 = list1.getClass(); Class c2 = list2.getClass(); System.out.println(c1 == c2);
try { Method m = c2.getMethod("add", Object.class); m.invoke(list2, 20); System.out.println("list2的长度是:" + list2.size()); } catch (Exception e) { e.printStackTrace(); } } }
list2的长度是:1 true list2的长度是:2
|
综上可以看出,在编译器的时候,泛型会限制集合内元素类型保持一致,但是编译器结束进入运行期以后,泛型就不再起作用了,即使是不同类型的元素也可以插入集合。
通过反射取得并修改数组的信息
1 2 3 4 5 6 7 8 9 10 11 12 13
| package net.xsoftlab.baike; import java.lang.reflect.Array; public class TestReflect { public static void main(String[] args) throws Exception { int[] temp = { 1, 2, 3, 4, 5 }; Class<?> demo = temp.getClass().getComponentType(); System.out.println("数组类型: " + demo.getName()); System.out.println("数组长度 " + Array.getLength(temp)); System.out.println("数组的第一个元素: " + Array.get(temp, 0)); Array.set(temp, 0, 100); System.out.println("修改之后数组第一个元素为: " + Array.get(temp, 0)); } }
|
通过反射机制修改数组的大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package net.xsoftlab.baike; import java.lang.reflect.Array; public class TestReflect { public static void main(String[] args) throws Exception { int[] temp = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int[] newTemp = (int[]) arrayInc(temp, 15); print(newTemp); String[] atr = { "a", "b", "c" }; String[] str1 = (String[]) arrayInc(atr, 8); print(str1); } public static Object arrayInc(Object obj, int len) { Class<?> arr = obj.getClass().getComponentType(); Object newArr = Array.newInstance(arr, len); int co = Array.getLength(obj); System.arraycopy(obj, 0, newArr, 0, co); return newArr; } public static void print(Object obj) { Class<?> c = obj.getClass(); if (!c.isArray()) { return; } System.out.println("数组长度为: " + Array.getLength(obj)); for (int i = 0; i < Array.getLength(obj); i++) { System.out.print(Array.get(obj, i) + " "); } System.out.println(); } }
|
将反射机制应用于工厂模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package net.xsoftlab.baike; interface fruit { public abstract void eat(); } class Apple implements fruit { public void eat() { System.out.println("Apple"); } } class Orange implements fruit { public void eat() { System.out.println("Orange"); } } class Factory { public static fruit getInstance(String ClassName) { fruit f = null; try { f = (fruit) Class.forName(ClassName).newInstance(); } catch (Exception e) { e.printStackTrace(); } return f; } }
public class TestReflect { public static void main(String[] args) throws Exception { fruit f = Factory.getInstance("net.xsoftlab.baike.Apple"); if (f != null) { f.eat(); } } }
|