概念
得到类的元数据的过程,即在运行时期动态地获取某个类的成员信息(构造类、方法、字段、内部类、接口、父类等),反射功能十分强大,但也十分损耗性能,可以理解成动态的new
。
本部分内容所用到的类基本都在java.lang.reflect
下
使用场景
什么时候会用到反射呢?这里举一个场景:假如你一个类里有一堆私有属性,如果要给他们赋值,最简单的办法就是一行行的调用setXxx
方法完成,比如下面这样:
o.setName("aaa");
o.setSex("man");
o.setAge(10);
...
但是这样的话可能有一些问题:太麻烦了、而且一旦属性名更改,后面一堆代码需要跟着改,维护起来十分不方便。因此我们可以这样做:获取该类中所有指定的set
方法,然后通过方法名动态地循环调用他们,这样我们就不需要再自己手动去完成这一项繁琐的工作了,而获取这些指定的方法名和通过方法名调用这些方法就需要用到反射,下面是一个代码示例(具体的方法后面都有介绍):
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
// 对象传对象
People a = new People("aaa", 100);
People b = new People();
System.out.println(b);
// 对象b属性皆为初始值
ClassUtil.setValue(b, a);
// 将对象a的内容通过调用get方法获取,并通过对象b的set方法赋值
System.out.println(b);
// 可以看到对象b发现改变
// map传对象
Map<String, Object> m = new HashMap<>();
People c = new People();
m.put("name", "xxx");
m.put("age", 100);
System.out.println(c);
ClassUtil.setValue(c, m);
// 将map中的值赋值到对应属性上
System.out.println(c);
}
}
class People {
private String name;
private int age;
People() {}
People(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public String toString() {
return this.name + "-" + this.age;
}
}
class ClassUtil {
// 通过对象之间传值
static void setValue(Object o, Object oSource){
Method[] methods = o.getClass().getMethods();
// 获取所有方法
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// 遍历获取方法名
if (methodName.indexOf("set") != -1) {
// 方法名为set开头的
String methodGetName = methodName.replace("set", "get");
// 找到对应的get方法
try {
methods[i].invoke(o, oSource.getClass().getMethod(methodGetName).invoke(oSource));
// 反射执行set方法,其中值通过反射执行get方法获取
} catch (Exception e) {
System.out.println(e);
}
}
}
}
// 通过map给对象传值
static void setValue(Object o, Map m){
Method[] methods = o.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
if (methodName.indexOf("set") != -1) {
String attrName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
// 获取对应属性名
try {
methods[i].invoke(o, m.get(attrName));
// 在map中找到对应属性名,获取value传值
} catch (Exception e) {
System.out.println(e);
}
}
}
}
}
Class
表示所有类
获取类的字节码对象三种方式
1.使用class属性
该方法用得比较少,举例:
Class<java.util.Date> class1 = java.util.Date.class;
但是有一种情况下比较常用,即基本数据类型,他们没有类,但是有class
属性,因此可以这样用:
Class class1 = int.class;
System.out.println(class1); //int
因为Integer
和int
是不同的类型(只是装箱后一样,其他包装类也同理),所以两者的class
属性并不等,但是这些包装类中都有个TYPE
常量用于返回其对应基本类的字节码对象,举例:
System.out.println(Integer.TYPE == int.class); //true
2.通过getClass方法获取
举例:
java.util.Date d = new java.util.Date();
Class<?> class1 = d.getClass();
3.通过Class类中的静态方法forName
在框架最常用的方法,需要抛出异常,举例:
public static void main(String[] args) throws ClassNotFoundException {
Class<?> class1 = Class.forName("java.util.Date");
}
注:
同一个类在JVM中只有一份字节码对象,因此上面三个都是一样的,举例:
public static void main(String[] args) throws ClassNotFoundException {
Class<java.util.Date> class1 = java.util.Date.class;
java.util.Date d = new java.util.Date();
Class<?> class2 = d.getClass();
Class<?> class3 = Class.forName("java.util.Date");
System.out.println(class1 == class2); //true
System.out.println(class1 == class3); //true
System.out.println(class2 == class3); //true
}
可以看到三个互相都是相等的
获取类名
当用前面的方法获取类以后,可以通过getName()
/getSimpleName()
来获取类名,前面会获取包名+类名,后者则只有类名,如果只要获取报名则用getPackage()
。
Constructor
表示所有构造器
获取所有构造器
通过java.lang.reflect
下的getConstructors()
方法获取,但这种方法只能获取到public
修饰符的构造器,举例:
import java.lang.reflect.Constructor;
public class test {
public static void main(String[] args) {
Class<Stu> s = Stu.class;
Constructor<?>[] c = s.getConstructors(); //构造器数组装所有构造器
System.out.println(c.length);
for(Constructor<?> i: c)
System.out.println(i);
}
}
class Stu {
public Stu() {}
public Stu(int i) {}
public Stu(String s, int i) {}
}
结果:
3
public Stu()
public Stu(int)
public Stu(java.lang.String,int)
获取指定构造器
通过getConstructor()
获取,需要抛出异常,并且需要根据形参的执行类型传入,举例:
import java.lang.reflect.Constructor;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Constructor<?> c1 = s.getConstructor(); //无参数就什么都没
Constructor<?> c2 = s.getConstructor(int.class); //要的是类型,所以要.class
Constructor<?> c3 = s.getConstructor(String.class, int.class); //顺序不要出错
System.out.println(c1); //public Stu()
System.out.println(c2); //public Stu(int)
System.out.println(c3); //public Stu(java.lang.String,int)
}
}
class Stu {
public Stu() {}
public Stu(int i) {}
public Stu(String s, int i) {}
}
获取指定私有构造器
上面的方法只能获取public
的构造器,如果要获取private
的构造器,则需要用到getDeclaredConstructor()
,举例:
Class<Stu> s = Stu.class;
Constructor<?> c1 = s.getDeclaredConstructor(String.class, int.class); //能够获取private的构造器
// Constructor<?> c2 = s.getConstructor(String.class, int.class); //报错
System.out.println(c1); //private Stu(java.lang.String,int)
同理,如果要获取所有到private
权限的构造器,则用:getDeclaredConstructors()
Method
表示所有方法
获取所有公共方法
获取Method
和获取构造器原理差不多,对于公共的方法,可以通过getMethods()
获取,并且会获取包括其父类和父接口的所有方法,举例:
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
for(Method m: s.getMethods()) //获取所有公共方法
System.out.println(m);
}
}
class Stu {
public Stu() {}
public void method1(){System.out.println(1);}
private void method2(){System.out.println(2);}
}
结果:
public void Stu.method1()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
...
public final native void java.lang.Object.notifyAll()
可以看出其获取的method1
和所有的父类继承下来的公共方法
获取指定方法
通过getMethod(方法名, 参数类型)
获取,举例:
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Method m = s.getMethod("method1");
System.out.println(m); //public void Stu.method1()
System.out.println(s.getMethod("method2", String.class, int.class)); //public void Stu.method2(java.lang.String,int)
}
}
class Stu {
public Stu() {}
public void method1() {System.out.println(1);}
public void method2(String s, int i) {System.out.println(2);}
}
如果要获取私有权限的则使用getDeclaredMethods(方法名, 参数类型)
注:
对于参数是泛型时,因为泛型只有在调用时才能知道基本类型,所以这个时候需要提高成最高类型,即使用Object.class
来代替,举例:
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Method m = s.getDeclaredMethod("method", Object.class); //泛型则用Object
System.out.println(m);
}
}
class Stu <T> {
public Stu() {}
public void method (T t) {
System.out.println(t);
}
}
获取私有方法
使用getDeclaredMethods()
可以获取自身类所有的包括私有的方法,但是不获取父类和父接口继承的方法,举例:
Class<Stu> s = Stu.class;
for(Method m: s.getDeclaredMethods())
System.out.println(m);
结果:
public void Stu.method1()
private void Stu.method2()
Field
表示所有字段,即成员变量,使用方法同理也是使用getFields()
等,举例:
import java.lang.reflect.Field;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
for(Field f:s.getFields())
System.out.println(f);
}
}
class Stu <T> {
public int i;
public String string;
public Stu() {}
}
举例:
public int Stu.i
public java.lang.String Stu.string
使用反射实例化对象
平常实例化对象都是采用new
的方式,但是这个在框架应用当中却会造成耦合的问题,所以才需要反射机制来实例化, 从而降低耦合
步骤
1.找到构造器所在类的字节码对象
2.获取构造器对象
3.使用反射创建对象
前两步参考上面即可,第三步创建对象通过newInstance(实参)
来实现,举例:
import java.lang.reflect.Constructor;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Constructor<?> c = s.getConstructor(int.class);
c.newInstance(1); //调用构造函数输出1
}
}
class Stu {
public Stu() {}
public Stu(int i) {System.out.println(i);}
}
上面的也就相当于:
Stu s = new Stu(1);
私有构造器创建对象
前面的对于public
构造器则是可以获取并调用,从而实例化对象的。但是如果是像private
这样的,首先要获取构造器需要用getDeclaredConstructor()
来获取,但此时默认还是不能够创建对象的,如果想要能够创建,需要设置setAccessible()
为true
才行,举例:
import java.lang.reflect.Constructor;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Constructor<?> c = s.getDeclaredConstructor(int.class);
c.setAccessible(true); // 如果没设置这句,下面的实例化语句则会报错
c.newInstance(1); //执行上面一条后实例化成功,输出1
}
}
class Stu {
public Stu() {}
private Stu(int i) {System.out.println(1);}
}
特殊无参构造器的实例化
如果一个无参构造器是外接可访问的,那么可以直接使用class
下的newInstance()
方法来直接实例化,即省略了步骤里的第二步,举例:
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
s.newInstance(); //无参构造器实例化,输出0
}
}
class Stu {
public Stu() {System.out.println(0);} //外接可访问、无参
private Stu(int i) {System.out.println(1);}
}
使用反射调用方法
步骤
1.找到构造器所在类的字节码对象
2.获取方法对象
3.使用反射调用方法
前两步参考上面即可,第三步创建对象通过invoke(对象, 参数)
来实现,举例:
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Method m = s.getMethod("method2", String.class, int.class);
m.invoke(s.newInstance(), "aaa", 1); //传入实例化对象,参数,输出aaa1
}
}
class Stu {
public Stu() {}
public void method2(String s, int i) {System.out.println(s+i);}
}
私有方法调用
和私有构造器创建对象同理,也是使用getDelcaredMethod()
,然后需要设置setAccessible()
为true
。如果函数无返回值则返回的是null
,有则可直接用Object
对象获取,举例:
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Method m = s.getDeclaredMethod("method2", String.class, int.class);
m.setAccessible(true);
Object o = m.invoke(s.newInstance(), "aaa", 1); //传入实例化对象,参数,输出aaa1
System.out.println(o); //输出aaa
}
}
class Stu {
public Stu() {}
private String method2(String s, int i) {System.out.println(s+i); return s;}
}
静态方法调用
也是使用invoke()
调用,但是因为静态方法不属于对象,所以其会自动忽略第一个参数(即原来传对象的参数),为了节省,可以直接传null
进去,举例:
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Method m = s.getDeclaredMethod("method2", String.class, int.class);
m.setAccessible(true);
Object o = m.invoke(null, "aaa", 1); //调用静态方法,第一个参数会被忽略,输出aaa1
System.out.println(o); //输出aaa
}
}
class Stu {
public Stu() {}
static private String method2(String s, int i) {System.out.println(s+i); return s;}
}
形参为基本类型数组方法的调用
当方法的形参为int[]
/char[]
等基本数据类型的数组时,因为数组本身也是一种类,所以获取的类型直接通过基本类型 [].class
即可,而传入参数时直接实例化一个数组对象传入即可,即new 基本类型[]{}
,举例:
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Method m = s.getDeclaredMethod("method", int[].class); //数组本身也是个类,所以直接取类属性
m.invoke(s.newInstance(), new int[]{1,2,3}); //传入基本类型数组参数时,直接实例化一个传入
}
}
class Stu {
public Stu() {}
public void method(int[] is) {
for(int i:is)
System.out.println(i);
}
}
形参为引用类型数组方法的调用
当方法的形参为String[]
/Integer[]
等这样的引用类型数组时,获取类型还是跟基本数据类型的数组一样即可,但是传参时其会自动进行拆包,所以需要在外面再包一层Object的数组,即new Object[]{new 引用类型[]{}}
,举例:
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Method m = s.getDeclaredMethod("method", String[].class); //跟基本数据类型数组一样
m.invoke(s.newInstance(), new Object[]{new String[]{"aaa", "bbb", "ccc"}}); //用Object[]再包一层,防止String被拆包
}
}
class Stu {
public Stu() {}
public void method(String[] ss) {
for(String s:ss)
System.out.println(s);
}
}
注:
invoke()
的底层调用方法时都可以把参数传入Object []
数组当中,其会自动拆包,举例:
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
Method m = s.getDeclaredMethod("method", String.class, int.class);
m.invoke(s.newInstance(), new Object[]{"aaa", 1}); //这样也可以,其底层都会对Object[]数组进行拆包
m.invoke(s.newInstance(), "aaa", 1); //两种是一样的
}
}
class Stu {
public Stu() {}
public void method(String s, int i) {System.out.println(s+i);}
}
获取修饰符
在构造器/方法/字段等底下都有一个getModifiers()
方法,能够获取该构造器/方法/字段等的修饰符,但返回的是修饰符的int编号,举例:
public class test {
public static void main(String[] args) throws Exception {
Class<Stu> s = Stu.class;
System.out.println(s.getDeclaredMethod("method0").getModifiers()); //0
System.out.println(s.getDeclaredMethod("method1").getModifiers()); //1
System.out.println(s.getDeclaredMethod("method2").getModifiers()); //2
System.out.println(s.getDeclaredMethod("method4").getModifiers()); //4
}
}
class Stu <T> {
public Stu() {}
void method0 () {}
public void method1 () {}
private void method2 () {}
protected void method4 () {}
}
修饰符对应编号
直接在源代码中就可以看到,这里进行稍微的整理:
default 0
public 1
private 2
protected 4
abstract 1024
static 8
final 10
synchronized 20
volatile 40
transient 80
native 100
interface 200
abstract 400
strict 800
bridge 40
varargs 80
synthetic 1000
annotation 2000
enum 4000
多个修饰符的情况
对于有多个修饰符的情况,其返回的编号是多个修饰符之和,比如abstract
+public
结果为1025
将修饰符转回字符
通过java.lang.reflect.Modifier
下的toString()
方法,举例:
System.out.println(Modifier.toString(1026)); //private abstract
附:new
引造成耦合原因参考
https://jingyan.baidu.com/article/08b6a5919baca114a80922a3.html
网友评论