java回忆录—带你破封装瞧个遍(反射)
Posted minigeek
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java回忆录—带你破封装瞧个遍(反射)相关的知识,希望对你有一定的参考价值。
今天来看看我们经常能用到的一个知识点,但又不怎么会用的知识点——反射。那我们在什么时候会用到这个知识点呢,以前我们在学习SSH框架的时候都在和反射打交道,还记得我们学习SSH框架的时候一直需要配置文件(当然注解就更简单了),这就是频繁的用到了反射。现在很多开源框架都用到反射机制。
还有就是设计模式中的动态代理模式,我们需要在运行的时候才能确定要代理的对象是啥,要代理干什么,这就需要用到反射,在运行期才能够确定下来。
当然还有一些应用就要是在安卓中,可能为了安全起见,有些API方法是被隐藏起来了(有@hide标记的),按正常流程是访问不到的,这时我们也需要用到反射来访问到这个方法。
一、什么是反射机制
简单来说就是,反射机制指的是程序在运行时能够获取自身的信息。
在java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。
二、反射机制的优点与缺点
静态编译:在编译时确定类型,绑定对象,即通过。
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。
优点:
可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中,它的灵活性就表现的十分明显。
比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了。
当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。
采用静态的话,需要把整个程序重新编译一次才可以实现功能 的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
缺点:
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。
三、利用反射机制能获得什么信息
一句话,类中有什么信息,它就可以获得什么信息,不过前提是得知道类的名字,要不就没有后文了,首先得根据传入的类的全名来创建Class对象。
获得构造器的方法
Constructor getConstructor(Class[] params)//根据指定参数获得public构造器
Constructor[] getConstructors()//获得public的所有构造器
Constructor getDeclaredConstructor(Class[] params)//根据指定参数获得public和非public的构造器
Constructor[] getDeclaredConstructors()//获得所有构造器
获得类方法的方法
Method getMethod(String name, Class[] params),根据方法名,参数类型获得public方法
Method[] getMethods()//获得所有的public方法
Method getDeclaredMethod(String name, Class[] params)//根据方法名和参数类型,获得public和非public的方法
Method[] getDeclaredMethods()//获得所有的方法
获得类中属性的方法
Field getField(String name)//根据变量名得到相应的public成员变量
Field[] getFields()//获得类中所有public的成员变量
Field getDeclaredField(String name)//根据方法名获得public和非public成员变量
Field[] getDeclaredFields()//获得类中所有的成员变量
常用的就这些,知道这些,其他的都好办……
首先能调用这些方法,先得获得 Class 对象,那么获取 Class 对象的方式有三种:
①Object 的 getClass()方法
②数据类型的静态属性 class
③Class 中的静态方法
public static void forName(String className)
那么一般使用哪种方式来创建呢?
有两种可能,如果是自己玩,那么可以任选一种,第二种比较方便。
如果是开发,会选择第三种,为什么呢?因为第三种是一个字符串,而不是一个具体的类名,这样的话我们可以把这样的字符串配置在配置文件中去了。
我个人始终认为案例驱动是最好的,要不然只看理论的话,看了也不懂,不过建议大家在看完文章之后,在回过头去看看理论,会有更好的理解。
这里新建一个 Student 类,在整个案例中,我们都用到这个 Student 类来讲解
Student.java
public class Student
private String name;
int age;
public String address;
public Student()
private Student(String name)
this.name = name;
Student(String name, int age)
this.name = name;
this.age = age;
private Student(String name, int age, String address)
this.name = name;
this.age = age;
this.address = address;
public void show()
System.out.println("show");
public void method(String s)
System.out.println("method:"+s);
private String getString(String s,int i)
return "getString:"+s+i;
private void funcation()
System.out.println("funcation");
@Override
public String toString()
return "Student [name=" + name + ", age=" + age + ", address="
+ address + "]";
简单分析一下这个类,这个Student类有三个成员变量 name 、age、address ,权限修饰符分别为 private 、default、public;有四个构造器,无参、一参、两参、三参,权限修饰符分别为 public 、private 、defalut、private;还有四个成员方法,在参数、返回值、权限修饰符各有不同;当然还重写了一个 toString()方法。
案例一:获得 Class 类的对象(三种方式)
public class StudentTestDemo1
public static void main(String[] args) throws Exception
//第一种方法
Student s = new Student();
Class c1 = s.getClass();
//第二种方法
Class c2 = Student.class;
//第三种方式
Class c3 = Class.forName("com.briup.reflect.Student");
System.out.println(c1 == c2);//true
System.out.println(c1 == c3);//true
结果是 c1 、c2、c3 为同一个对象,这里请记住一句话:所有的类都是 Class 类的对象。即类也是一个对象,它是Class的对象,它的类型称为类类型。
案例二:通过反射去获取构造方法并使用
这里就获取无参的和三参的,因为权限修饰符为 public 和 private ,这两种会了其他的构造方法也就同理了。
public class StudentTestDemo2
public static void main(String[] args) throws Exception
//获取Class对象,字符串为包名+类名
Class c = Class.forName("com.briup.reflect.Student");
//获取无参构造方法,参数为空,因为无参构造方法没有参数
Constructor constructor = c.getConstructor();
//根据构造方法创建对象,参数为空,因为是无参构造方法
Object obj = constructor.newInstance();
System.out.println(obj);
//获取三参的构造方法,参数是个可变参数,表示构造方法的参数的类型的类类型
Constructor con = c.getDeclaredConstructor(String.class,int.class,String.class);
//设置取消访问检查,因为权限修饰符为private
con.setAccessible(true);
//创建对象,参数为构造方法的实参
Object object = con.newInstance("周杰伦",30,"台湾");
System.out.println(object);
结果:
从结果我们可以看出,我们获取无参构造方法和三参构造方法并创建出了对象。不过在使用三参的构造方法创建对象之前添加这句代码:con.setAccessible(true);
那么这句代码是表示什么意思呢?
在注释中我们提到这是取消访问检查(我这里把它叫做暴力访问,因为它不管别人的权限修饰符),因为它是 private 的,如果我们不加这句代码去使用的话报异常:java.lang.IllegalAccessException(非法访问异常)
将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。
让我们来看看这个方法是在哪呢?是在Constructor中吗?
答:并不是,在 AccessibleObject 类中,AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。
案例三:通过反射获取成员变量并使用
public class StudentTestDemo4
public static void main(String[] args) throws Exception
Class c = Class.forName("com.briup.reflect.Student");
//获取指定的成员变量(name属性)
Field nameField = c.getDeclaredField("name");
//获取指定的成员变量(address属性)
Field addressField = c.getField("address");
//通过无参构造函数创建对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//在使用之前要取消访问检查,因为name的权限修饰符为private
nameField.setAccessible(true);
//给obj对象的nameField即name成员变量赋值为TF
nameField.set(obj, "TF");
//给obj对象的addressField即address成员变量赋值为中国
addressField.set(obj, "中国");
System.out.println(obj);
结果:
同理,成员变量和构造方法一样,使用的时候(比如赋值)如果是 private 修饰的话,需要取消访问检查。不然会报异常。
nameField.set(obj, “TF”);这句话的含义为 调用 obj 对象的set() 方法给 nameFiled 成员变量赋值为 TF。
案例四:通过反射获取成员方法并使用
public class StudentTestDemo5
public static void main(String[] args) throws Exception
Class c = Class.forName("com.briup.reflect.Student");
//获取指定参数的成员方法
//第一个参数表示方法名,第二个参数表示形参的类类型
Method method = c.getDeclaredMethod("getString",String.class,int.class);
// 通过无参构造方法创建对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//取消访问检查
method.setAccessible(true);
//调用obj对象的method方法,后面跟着的实参,返回值为方法的返回值
Object object = method.invoke(obj,"zhou",15);
System.out.println(object);
结果:
同理,使用方法之前也需要取消访问检查,因为它的权限修饰符为 private,不然会报异常。
Object object = method.invoke(obj,”zhou”,15); 这句代码表示 调用 obj 对象的method 方法,并传入“zhou”、15这两个实参,object 为方法的返回值。
温馨提示:
①我们每次获取构造函数、成员变量、成员方法的时候要根据它的权限来选择相应的方法。其实我们需要获取的时候只要调用相应的 getDeclaredXXX()即可。因为
getDeclaredXXX()方法获取的是所有的构造函数/成员变量/成员方法(包括public 和 非public 的)
②大家可以看到每次使用构造函数、成员变量、成员方法之前要判断是否要添加取消访问检查,其实我们不用管他们的权限修饰符,每次要使用这些之前都加上取消访问检查不就行了嘛。 是的。
反射的基本方法的使用就介绍到这,当然它不止上面这些方法,我相信,学会上面的方法的使用,你去学习它的其他方法的使用应该是同理的。
总的来说,java反射机制是一个很好用的东西,用它可以解决很多死的东西,因为反射机制的灵活行很大。
前面我们提到了运用反射+配置文件可以非常方便。现在就来模拟一下这中场景:
需求:通过配置文件运行运行类中的方法
前提:
需要有配置文件配合使用
用class.txt代替使用
并且你知道有两个键
className
methodName
有三个简单的类:BasketBall、FootBall、PingPang
BasketBall.java
public class BasketBall
public void method()
System.out.println("篮球: 科比");
FootBall.java
public class FootBall
public void method()
System.out.println("足球: C罗");
PingPang.java
public class PingPang
public void method()
System.out.println("乒乓: 张继科");
如果现在现在需要调用BasketBall的方法,那么我们会创建BasketBall的对象,然后再去调用它里面的方法;然后现在需求又改成需要调用FootBall的方法,那么我们需要创建FootBall的对象,再去调用它里面的方法;再然后需求又变成需要调用BasketBall的方法,那么我们又写回去吗?。。。。。。这样周而复始,是不是觉得很繁琐,而且每次都得改动源码,你觉得在客户手中有源码吗?没有,只有.class文件。这样改的话每次都得重新编译,再交到客户手中。
所以我们想到了运用反射,因为反射是在运行中动态获取信息。
配置文件用class.txt代替,内容为:
className=com.briup.reflect.BasketBall
methodName=method
public class TestDemo
public static void main(String[] args) throws Exception
Properties prop = new Properties();
FileReader fr = new FileReader("class.txt"); //将配置文件加载进来
prop.load(fr);
//根据键来获取值
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
//创建Class对象
Class c = Class.forName(className);
//获取无参构造方法
Constructor constructor = c.getDeclaredConstructor();
//不管三七二十一,暴力访问
constructor.setAccessible(true);
Object obj = constructor.newInstance();
//获取成员方法
Method method = c.getDeclaredMethod(methodName);
//不管三七二十一,暴力访问
method.setAccessible(true);
method.invoke(obj);
这样的话,你需求变了的话,你只需要改一下配置文件的相应的值。重新运行下即可。
如:现在需要调用FootBall的method方法。只需将配置文件改成这样:
className=com.briup.reflect.FootBall
methodName=method
如果又变成需要调用PingPang的method方法。只需将配置文件改成这样:
className=com.briup.reflect.PingPang
methodName=method
这样是不是很简单呢?以前封装就是为了不让访问,现在有了反射,还是可以访问了。赶紧试试吧,小伙伴们。
以上是关于java回忆录—带你破封装瞧个遍(反射)的主要内容,如果未能解决你的问题,请参考以下文章