反射

在上一篇中讲了类的加载机制,在加载过后类的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("橘子榨汁成功");
	}
	
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值