【Java SE】反射 Reflect
反射,它就像是一种魔法,引入运行时自省能力,赋予了 Java 语言令人意外的活力,通过运行时操作元数据或对象,Java 可以灵活地操作运行时才能确定的信息。而动态代理,则是延伸出来的一种广泛应用于产品开发中的技术,很多繁琐的重复编程,都可以被动态代理机制优雅地解决。
反射一般很少使用,基本都是在封装框架的时候会用到,我们可以根据反射获取对象内容信息(属性,方法),从而生成API文档,既然可以获取所有对象的内容,我们可以做插件功能,获取里面的方法,随意调用,当然也有缺点,本该有的封装性会被破坏。
-
提问:什么是反射?
反射就是【动态加载对象】,并对对象进行剖析。
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法;
这种动态获取信息以及动态调用对象方法的功能成为Java反射机制。 -
提问:反射的优缺点?
优点:
反射提高了程序的灵活性和扩展性,在底层框架中用的比较多,业务层面的开发过程中尽量少用。
缺点:
性能不好
反射是一种解释操作,用于字段和方法接入时要远慢于直接代码;
程序逻辑有影响
使用反射操作会模糊化程序的内部逻辑,从代码的维护角度来讲,我们更希望在源码中看到程序的逻辑,反射相当于绕过了源码的方式,因此会带来维护难度比较大的问题。 -
提问:反射的使用场景有哪些?
实现RPC框架;
实现ORM框架;
拷贝属性值(BeanUtils.copyProperties);
——————————————————————
例子:
public class Student {
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
获取类中的所有方法
public static void main(String[] args) {
try {
Class<?> clz = Class.forName("fs.Student");
Method[] methods = clz.getMethods();
for (Method method : methods) {
System.out.println("方法名:" + method.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
如果只需要获取加载类中的方法,不要父类的方法,可以使用下面的代码:Method[] methods = clz.getDeclaredMethods();
通过反射来【调用方法】:
try {
Class<?> clz = Class.forName("fs.Student");
Student stu = (Student) clz.newInstance();
System.out.println(stu.getName());
Method method = clz.getMethod("setName", String.class);
method.invoke(stu, "名字");
System.out.println(stu.getName());
} catch (Exception e) {
e.printStackTrace();
}
获取类中的所有属性
Class<?> clz = Class.forName("fs.Student");
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println("属性名:" + field.getName());
}
Class类的使用
- 1.在面向对象的世界里,万事万物皆对象
但在Java中,有两项事物不是对象,他们是:
(1).普通数据类型;
比如int i = 5
不是对象;但是他有对应包装类Integer
来弥补这个缺陷;
(2).静态成员;
提问 - 类是不是对象?如果是,是谁的对象?
类是对象!是java.lang.Class
类的实例对象;
These is a class named Class.
【重要】
比如说有个类叫Foo.class
:
他有【实例对象】foo1
:Foo foo1 = new Foo();
他也有【类类型】c1
:Class c1 = Foo.class;
【类类型】:
因为万事万物皆为对象;
所以类也是对象,是java.lang.Class
的对象(类类型);
类类型有三种创建方法:
1.Class c1 = Foo.class;
2.Class c2 = foo1.getClass();
3.需要加上try catch语句块;
Class c3 = null;
c3 = Class.forName("com.xxx.xxx.Foo")
——————————————————————————
例子:
class Foo{
void print(){
System.out.println("foo...");
}
}
public class Test {
public static void main(String[] args) {
Foo foo1 = new Foo();
// 方法1 -> 任何一个类都有一个隐含的静态成员变量class
Class c1 = Foo.class;
// 方法2 -> 应知道该类的对象通过getClass方法
Class c2 = foo1.getClass();
System.out.println(c1 == c2);
// 一个类只有一个类类型,类类型的实例对象都是相等的;返true
// 方法3 ->
Class c3 = null;
try {
c3 = Class.forName("com.example.springtest.Controller.Foo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c2 == c3);
// 返true
}
}
class Foo{}
另外,我们完全可以通过类的类类型创建该类的实例对象!
即,通过c1 c2 c3
来创建Foo的实例对象;
【前提:必须要有无参数的构造方法;】
public class Test {
public static void main(String[] args) {
Class c1 = Foo.class;
try {
Foo foo = (Foo)c1.newInstance();
foo.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
输出:
foo...
动态类加载
-
提问:什么是动态加载?静态加载?
Class,forName("类的全程")
不及表示了类的类类型,还代表了动态加载类; -
提问:编译、运行的区别?
(高级工具帮我们区分了,导致我们可能忽视了这一块,但这很重要)
【编译时刻】:加载类是【静态加载类】;
【运行时刻】Test:加载类是【动态加载类】;
——————————————————————————————
我们用记事本和Terminal操作以下步骤(不用IDE)
class Office{
public static void main(String[] args){
if("Word".equals(args[0])){
Word w = new Word();
w.start();
}
if("Excel".equals(args[0])){
Excel e = new Excel();
e.start();
}
}
}
编译:javac Office.java
报错:
找不到Word和Excel这两个类!
两个start()找不到!
我们试一试,把Word类创建出来;
class Word{
public static void main(String[] args){
System.out.println("Word...starts...");
}
}
编译:javac Word.java
javac Office.java
只报两个错了:Excel找不到和start()
我们可能理所当然觉得这很正常;
但是!只有Excel不存在,而Word已经存在的!
假设我只想用Word,程序用不了;
因为程序现在new对象是【静态加载】;
在【编译时刻】需要加载所有可能使用到的类,不管你用不用;
然后实际中我们希望如果Word存在,我们就能用;
假设程序有100个功能,只要有一个功能,其他99个都用不了,这是不希望发生的;
我们希望用哪个就加载哪个,不用就不加载!!!
———————————————
通过【动态加载类】可以解决以上问题;
public class OfficeBetter{
public static void main(String[] args){
try{
Class c = Class.forName(args[0]);
}catch(Exception e){
e.printStackTrace();
}
}
}
javac OfficeBetter.java
OK
运行里面的Excel类(不存在Excel类时)java OfficeBetter Excel
报错:ClassNotFoundException
说明运行动态加载,不会管编译,只管运行;
往里面用类类型创建Excel对象就可以了;
但是如果运行了Word怎么办?
public class OfficeBetter{
public static void main(String[] args){
try{
Class c = Class.forName(args[0]);
OfficeAble oa = (OfficeAble)c.newInstance();
oa.start();
}catch(Exception e){
e.printStackTrace();
}
}
}
interface OfficeAble{
public void start();
}
制定一个接口标准;
若要运行Word,让Word类implements OfficeAble
。。。如下;
class Word implements OfficeAble{
public void start(){
System.out.println("Word...starts...");
}
}
javac Word.java
javac OfficeBetter.java
java OfficeBetter Word
返回:Word…starts…
动态加载;
比如升级QQ新版本,老版本不需要重新编译;
短期升级用动态加载;
功能性的类尽量使用动态加载;
——————————————————————————————
获取 方法信息
基本的数据类型、void关键字都存在类类型;
注意:double
Double
这样的两种数据类型代表的意义完全不一样;
public class Test {
public static void main(String[] args) {
Class c1 = int.class;
System.out.println(c1.getName());
Class c2 = String.class;
System.out.println(c2.getName());
System.out.println(c2.getSimpleName());
Class c3 = double.class;
Class c4 = Double.class;
Class c5 = void.class;
System.out.println(c5.getName());
}
}
输出:
int
java.lang.String
String
void
public class Test {
public static void printClassMessage(Object obj){
Class c = obj.getClass();
System.out.println("类的名称是:"+c.getName());
Method[] ms = c.getMethods();
// .getDeclaredMethods()获取的是自己声明的方法
for(int i = 0;i<ms.length;i++){
// 得到方法的返回值的类类型
Class returnType = ms[i].getReturnType();
System.out.print(returnType.getName()+" ");
// 得到方法名称
System.out.print(ms[i].getName());
// 获取参数类型 得到的是参数列表的类型的类类型
Class[] paramTypes = ms[i].getParameterTypes();
for (Class class1:paramTypes){
System.out.print("("+class1.getName()+",");
}
System.out.println(")");
}
}
}
public class Test2 {
public static void main(String[] args) {
// 拿到String的所有方法
String s = "hello";
Test.printClassMessage(s); // 调用Test类的printClassMessage()方法
}
}
输出:
类的名称是:java.lang.String
boolean equals(java.lang.Object,)
java.lang.String toString)
int hashCode)
int compareTo(java.lang.Object,)
int compareTo(java.lang.String,)
int indexOf(java.lang.String,(int,)
int indexOf(int,)
int indexOf(java.lang.String,)
int indexOf(int,(int,)
。。。。。。
获取 成员变量构造函数信息
成员变量也是对象;java.lang.reflect.Field
类封装了关于成员变量的操作;
getFields()
方法获取的是所有public的成员变量的信息;getDeclaredFields()
获取的是自己声明的成员变量信息(包括私有、公有);
Test类中:
public static void printFieldMessage(Object obj) {
Class c = obj.getClass();
Field[] fs = c.getDeclaredFields();
for(Field field:fs){
Class fieldType = field.getType();
String typeName = fieldType.getName();
String fieldName = field.getName();
System.out.println(typeName+","+fieldName);
}
}
public class Test3 {
public static void main(String[] args) {
Test.printFieldMessage("hello"); // 或者写 (new Integer(1));
}
}
输出:
[C,value // [C 是数组反射的内容
int,hash
long,serialVersionUID
[Ljava.io.ObjectStreamField;,serialPersistentFields
java.util.Comparator,CASE_INSENSITIVE_ORDER
——————————————————————————————
——————————————————————————————
方法反射 的基本操作
1.如何获取某个方法
方法的名称和方法的参数列表才能为一决定某个方法;
2.方法反射的操作method.invoke(对象,参数列表)
——————————————————————————————
public class MethodDemo1 {
public static void main(String[] args) {
// 1.获取print方法;
// 获取方法就是获取类的信息;获取类的信息首先要获取类的类类型
A a1 = new A();
Class c = a1.getClass();
// 2.获取方法
// 要先获取:名称+参数列表
// getMethod 获取public方法;getDeclaredMethod 获取公共和私有方法
try {
Method m = c.getMethod("print",new Class[]{int.class,int.class});
// 因为第二个参数有...所以是可变的,也可以写成:int.class,int.class
// 3.方法的反射操作
// 如果不用反射,可以这么写:a1.print(10,20);
// 反射操作:用m对象来进行方法调用;和a1.print调用效果完全相同;
Object o = m.invoke(a1,new Object[]{10,20});
// 也可以写:Object o = m.invoke(a1,10,20);
System.out.println("========================");
Method m1 = c.getMethod("print",String.class,String.class);
o = m1.invoke(a1,"hello","WORLD");
System.out.println("========================");
Method m2 = c.getMethod("print");
o = m2.invoke(a1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class A{
public void print(int a,int b){
System.out.println(a+b);
}
public void print(String a,String b){
System.out.println(a.toUpperCase()+b.toLowerCase());
}
public void print(){
System.out.println("helloworld");
}
}
——————————————————————————————
语法上会更困难和麻烦,但是需要先记下来,才能发现它的好处;
通过反射了解 集合范型 的本质
通过Class、Method认识范型的本质;
public class MethodDemo4 {
public static void main(String[] args) {
ArrayList list1 = new ArrayList();
ArrayList<String> list2 = new ArrayList<>();
list2.add("hello");
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2); // 返true
// 反射操作都是编译后的操作
// 返true 说明:编译后集合的范型是【去范型化】的
// 结论:Java中集合的范型 是防止错误输入的,只在编译阶段有效;绕过编译就无效了
// 验证:通过方法的反射来操作,绕过编译
// 本来c2是有范型的,不能添加int参数
// 之前添加了一个String参数,现在试试反射后添加一个int:20,看能否添加成功
try{
Method m = c2.getMethod("add",Object.class);
m.invoke(list2,20); // 绕过了范型
System.out.println(list2.size());
System.out.println(list2);
}catch(Exception e){
e.printStackTrace();
}
}
}
true
2
[hello, 20]
上一篇: Java SE Lesson 5