在上一篇中讲了类的加载机制,在加载过后类的class文件会被JVM加载成为一个class对象,而对这个对象的使用,就是java的反射机制。我们在平常的使用中,我们要使用一个类的时候,一般会进行创建对象,利用对象来进行调用成员方法,调用成员变量等,是必须先进行得到一个完整的类体系,再对其中的一部分进行使用。秉着“万物皆对象”的原则,我们发现连类本身都是一个类的对象,那么类中的构造方法,成员变量,成员方法不也应该是类么?答案是肯定的,来看一下这些类的使用。
反射
反射相当于对一个类的解剖过程,解剖的结果一般分为三个部分:Constructor类(构造方法类),Field类(成员变量类),Method(成员方法类)。他们的主人还有一个Class类,那么一共就是使用这四个类来进行编写代码执行我们想要的操作。
事先准备好的Person类,主要是了里面要有三种组成结构(成员变量,成员方法,构造方法)
public class Person {
public String name;
public int age;
private boolean sex;
public Person() {
super();
System.out.println("空参执行了");
}
private Person(String name, int age, boolean sex) {
super();
System.out.println("三个参数的执行了");
this.name = name;
this.age = age;
this.sex = sex;
}
public Person(String name, int age) {
super();
System.out.println("两个参数的执行了");
this.name = name;
this.age = age;
}
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 boolean isSex() {
return sex;
}
public void setSex(boolean sex) {
this.sex = sex;
}
public void run() {
System.out.println("跑步啊啊啊啊");
}
public void sleep() {
System.out.println(name+"睡觉啦啦啦啦啦");
}
public void sleep(int a) {
System.out.println(name+"是"+a+"个a");
}
}
获取类的字节码对象(Class类型对象)的三种方式
要想获取和操作类中的内容,我们首先要获取这个类的字节码对象(.class对象)
- 对象名.getName():这个方法是从Object类中继承下来的,每个类中都有这个方法。
- 类名.class:已知类名的情况下可以直接这样调用来获取这个对象
- Class.forName(String className):Class类中的一个静态方法,可以根据一个类的全类名,动态的加载某个类型。传入一个类的全类名,将类名描述的字节码文件,加载到内存中,形成一个字节码对象,并且把这个对象作为该方法的返回值。这个方法相对来说虽然麻烦,但是比前两个方法的应用广泛的多,因为这个可以从字符串获取这个类,而字符串的获取渠道多种多样,而前两种必须需要类和对象的前提
里面参数的格式为:包名.类名,比如上面的Person类所属的包为:反射,那么传入的就是:反射.Person
代码示例:
public class Demo {
public static void main(String[] args) throws Exception {
//第一种方法:通过对象调用getClass方法来获得类
Person p = new Person();
Class class1 = p.getClass();
//第二种方法:通过类名来调用
Class class2 = Person.class;
//第三种方法:通过Class类的forName(String s)方法来获得,需要抛出找不到文件的异常,传入的s是类的全路径(包名.类名)
Class class3 = Class.forName("反射.Person");
System.out.println(class1==class2);
System.out.println(class2==class3);
}
}
反射成员变量
获取多个
- public Field[] getFields() 获取所有public 修饰的变量
- public Field[] getDeclaredFields() 获取所有的变量 (包含私有)
获取单个
- public Field getField(String name) 获取指定的public修饰的变量
- public Field getDeclaredField(String name) 获取指定的任意变量(包含私有)
通过方法,给指定对象的指定成员变量赋值或者获取值
- public void set(Object obj, Object value)在指定对象obj中,将此Field设置为指定值
- public Object get(Object obj)返回指定对象obj中,此 Field的值
成员变量的反射使用Field类创建的对象进行接收反射出来的变量,这里常用的方法有四个,分别作用为获取某个变量,获取非公共变量(这里是只要不是public修饰的变量都是非公共变量),获取所有公共变量,获取所有变量(包括非公共和公共)。这里方法会放在代码里进行演示,得到变量后,我们可以调用,set()方法对变量进行赋值,但是私有变量需要通过setAccessible()方法来获取修改的权限,才可以进行修改。我们之前一直说私有变量只能在本类进行访问,但是利用了反射,就不是了
Field类的方法
getModifiers(); //修饰符 getType(); //类型 getName(); //变量名 get(Object obj); // 获取值 |
代码示例:
import java.io.File;
import java.lang.reflect.Field;
public class Demo01 {
public static void main(String[] args) throws Exception {
//创建一个对象
Person p = new Person();
//通过forName()方法获取到类对象
Class class1 = Class.forName("反射.Person");
System.out.println("\n--------单个public权限成员变量-----------");
//通过getField("成员变量名"):只能获取用public修饰的成员变量
Field f = class1.getField("age");
System.out.println(f);
System.out.println("\n--------所有public权限成员变量-----------");
//通过getFields():获取全部成员变量,返回值为一个成员变量数组,只能获取public修饰的成员变量
Field[] fields = class1.getFields();
for(Field ff:fields) {
System.out.println(ff);
}
System.out.println("\n--------单个任何权限成员变量-----------");
//通过getDeclaerdField("成员变量名"):暴力获取任何权限的成员变量,传入内容为变量名
Field f1 = class1.getDeclaredField("sex");
System.out.println(f1);
System.out.println("\n--------所有任何权限成员变量-----------");
//通过getDeclaerdField():获取任何权限全部的成员变量
Field[] fields2 = class1.getDeclaredFields();
for(Field ff:fields2) {
System.out.println(ff);
}
System.out.println("--------单个public权限成员变量赋值-----------");
//set()方法:把20装到对象p中的上一步获取的对象中
f.set(p, 20);
System.out.println(p.age);
System.out.println("--------单个所有权限成员变量赋值-----------");
//获取权限
f1.setAccessible(true);
//给私有变量赋值
f1.set(p, true);
}
}
反射构造方法
构造方法的反射和上面对变量的反射其实差不多,也是四个方法分别进行单个获取或者一起获取,但是还有一个问题就是构造方法的重载,对于这个问题我们会利用参数来解决。构造方法有了,我们就可以对对象进行创建了,利用构造方法对象调用newInstance()方法就可以创建对象了。这里的代码看起来会有点乱,方法名比较长,但是其实原理都很简单,都在代码里了,走一个!
import java.lang.reflect.Constructor;
public class Demo02 {
public static void main(String[] args) throws Exception {
//获取一个类
Class class1 = Person.class;
//getConstructors()方法:获取所有构造方法
Constructor[] constructors = class1.getConstructors();
for(Constructor c:constructors) {
System.out.println(c);
}
//getConstructor():获取单个构造方法,根据里面参数的个数选择重载的构造方法,参数格式为参数类型.class,如果空参可以不写
Constructor constructor = class1.getConstructor(String.class,int.class);
System.out.println(constructor);
//getDeclaredConstructor():暴力获取单个构造方法,参数类型和单个构造方法一样
Constructor declaredConstructor = class1.getDeclaredConstructor(String.class,int.class,boolean.class);
System.out.println(declaredConstructor);
//获取空参构造方法
Constructor constructor3 = class1.getConstructor();
System.out.println(constructor3);
//调用构造方法来创建对象,创建一个引用,然后后面先加强转,通过构造方法对象调用newInstance()方法,括号内根据这个构造方法对象的参数个数进行赋值,和正常构造赋值一样
Person p = (Person)constructor3.newInstance();
p.run();
Person p1 =(Person)constructor.newInstance("明",12);
p1.sleep();
//暴力破解过来的不是Public权限的构造方法把修改权限设置为true,就可以调用构造方法进行创建。
declaredConstructor.setAccessible(true);
Person p2 = (Person)declaredConstructor.newInstance("ning",20,true);
p2.sleep();
Person p3=(Person)class1.newInstance();
}
}
反射成员方法:
反射成员方法也是四种方法,会造成四种现象:
- 1、获取所有public方法,包括继承的
- 2、获取所有方法,包括private,不包括继承的
- 3、获取指定方法,包括继承的
- 4、获取指定方法,包括private,不包括继承的
这里获得了方法对象之后,最重要的方法是用方法对象调用invoke()方法,参数第一个是你要调用方法的对象,参数后面是方法对象本来需要的参数,如果是无参可以不写,也可以写个null。
Method类下的方法:
getModifiers() 获得访问控制符 getReturnType() 获得返回值类型 getName() 获得方法名 getParameterTypes() 获得参数列表 |
都在代码里了,走一个:
反射调用静态方法和数组参数方法
使用反射调用静态方法:
静态方法不属于任何对象,静态方法属于类本身。
此时把invoke方法的第一个参数设置为null即可。
使用反射调用数组参数方法(可变参数):
调用方法的时候把实际参数统统作为Object数组的元素即可。
Method对象.invoke(方法所属对象,new Object[]{所有实参 });
反射其他的API
String getName():获取全限定名 String getSimpleName():获取简单类名,不包含包名 Package getPackage():获取该类的包 Class getSuperclass():获取父类的Class getGenericSuperclass():获取父类 boolean isArray():是否为数组类型 boolean isEnum():是否为枚举类型 boolean isInterface():是否为接口类型 boolean isPrimitive():是否为基本类型 boolean isSynthetic():是否为引用类型 boolean isAnnotation():是否为注解类型 boolean isAnnotationPresent(Class annotationClass):当前类是否加了指定类型注解 |
反射应用①泛型擦除
//有如下集合
ArrayList<Integer> list = new ArrayList<>();
list.add(666);
//设计代码,将字符串类型的"abc",添加到上述带有Integer泛型的集合list中
public class Demo11_反射练习_泛型擦除 {
public static void main(String[] args) throws Exception{
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(666);
// 要把 "abc" 也加入到list中
//list.add("abc"); // 编译期检查泛型——"abc" 不能加入到list中。可以在运行期加入
/**
* 在编译阶段,检查add方法的实际参数,如果在编译阶段,不要调用add方法,
* 就会避免掉在编译阶段,对实际参数数据类型的检查
* 在运行阶段,调用add方法
* 使用反射的方式,调用某个方法,在写代码的阶段,根本不知道将来调用哪个方法
* 编译器也就没有办法在编译阶段对代码进行检查
*
* 这种方式叫做“泛型擦除”
* 在java中,只会在编译阶段,对泛型进行检查,到了运行阶段,对泛型不检查
* 称这种泛型为:伪泛型
*/
Class clazz = list.getClass();
Method method = clazz.getMethod("add",Object.class);
method.invoke(list, "abc");
System.out.println(list);
}
}
反射应用①
我们在之前做过一个例子,是利用多态来实现一个榨汁机的功能,但是我们站在一个使用者的角度上看,我们不能一直按照程序规定的流程来进行榨汁,我们想要榨什么水果就榨什么水果,而且后台代码不能够进行改变。这个时候,反射和获取类对象的第三种方法的灵活性就展现出来了
import java.util.Scanner;
public class Demo03 {
public static void main(String[] args) throws Exception {
while(true) {
Scanner sc = new Scanner(System.in);
System.out.println("输入你想要的水果:");
String s = sc.next();
Class class1 = Class.forName("反射."+s);
Fruit f = (Fruit)class1.newInstance();
JuiceMachine j = new JuiceMachine();
j.zhaZhi(f);
}
}
}
class JuiceMachine{
public void zhaZhi(Fruit f) {
f.flow();
}
}
interface Fruit{
void flow();
}
class Apple implements Fruit{
@Override
public void flow() {
System.out.println("苹果榨汁成功");
}
}
class Orange implements Fruit{
@Override
public void flow() {
System.out.println("橘子榨汁成功");
}
}