JavaSE进阶--反射

文章目录

  • 前言
  • 一、例子引入
  • 二、介绍
    • 1、概念
    • 2、动态语言&静态语言
    • 3、Class类
  • 三、前置代码
    • MyAnnotation 注解
    • Rule 接口
    • Animal 父类
    • Dog 子类
  • 三、获取字节码信息
    • 方式
    • 示例代码
  • 四、动态获取类信息
    • 1、获取构造器&创建对象
      • 1.1 常用方法
      • 1.2 示例代码
    • 2、属性
      • 2.1 常用方法
      • 2.2 示例代码
    • 3、方法
      • 3.1 常用方法
      • 3.2 示例代码
    • 4、类信息
      • 4.1 常用方法
      • 4.2 示例代码
  • 五、面试
    • 1、什么时候需要用反射?
    • 2、反射是否破坏了面向对象的封装性?

前言

大家好,我是程序员Forlan,本篇内容主要分享反射的知识,属于基础内容,在重新学习的过程中,做一个知识补充熟悉,在源码中,随处可见的反射,在我们实际开发中,写一些扩展性强的代码也时常用到,本文主要从例子和概念入手,分享常用的属性和方法~

一、例子引入

实现点餐功能,新增一种食物,就要加一个get方法,很麻烦,代码如下:

public static void main(String[] args) {
	Scanner sc = new Scanner(System.in);
	System.out.print("请输入要吃的东西:");
	String food = sc.next();
	if ("面条".equals(food)) {
		get(new Noodles());
	}
	if ("米饭".equals(food)) {
		get(new Rice());
	}
	if ("汉堡包".equals(food)) {
		get(new Hamburger());
	}
}
public static void get(Noodles noodles) {
	noodles.get();
}
public static void get(Rice rice) {
	rice.get();
}
public static void get(Hamburger hamburger) {
	hamburger.get();
}

引入多态,提高代码的扩展性,直接写一个pay方法,参数是我们的接口

public static void main(String[] args) {
	Scanner sc = new Scanner(System.in);
	System.out.print("请输入要吃的东西:");
	String food = sc.next();
	if ("面条".equals(food)) {
		get(new Noodles());
	}
	if ("米饭".equals(food)) {
		get(new Rice());
	}
	if ("汉堡包".equals(food)) {
		get(new Hamburger());
	}
}

public static void get(Food food) {
	food.get();
}

上面的扩展性其实还没达到最好,为什么?加食物,代码还是需要手动添加和删除,if判断很多

Scanner sc = new Scanner(System.in);
System.out.print("请输入要吃的东西:");
String food = sc.next();
Class cls = Class.forName(food);
Object o = cls.newInstance();
Method method = cls.getMethod("get");
method.invoke(o);

通过反射,我们的代码就变得很简单,可扩展性很好

二、介绍

1、概念

在编译后产生字节码文件的时候,类加载器通过二进制字节流,负责从文件系统加载class文件。
在执行程序(java.exe)时候,将字节码文件读入JVM中,也就是类的加载。然后在内存中对应创建一个java.lang.Class对象,这个对象会被放入字节码信息中,这个对象将被作为程序访问方法区中的这个类的各种数据的外部接口。
所以,我们可以通过这个对象看到类的结构,这个对象就好像是一面镜子,透过镜子看到类的各种信息,我们形象的称之为反射,这种“看透”class的能力(the ability of the program to examine itself)被称为Reflection。

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为反射

2、动态语言&静态语言

动态语言:运行时可以改变其结构的语言,例如:Python、PHP、 C#、JavaScript、 Erlang
静态语言:运行时结构不可变的语言,如Java、C、C++

Java不是动态语言,但反射让Java具备了动态性

3、Class类

Java是面向对象的编程语言,所谓万事万物皆对象
每个对象都有属性、方法、构造器,这些是他们的共同特点,可以向上抽取定义为类,也就是我们说的Class类
通过Class类,就可以得到具体的对象信息,获取属性、方法、构造器…

Class类的实例种类

  1. 类:外部类,内部类
  2. 接口
  3. 注解
  4. 数组
  5. 基本数据类型
  6. String
Class c1 = Money.class;
Class c2 = Serializable.class;
Class c3 = Override.class;
int[] arr = {1, 2, 3};
Class c4 = arr.getClass();
Class c5 = int.class;
Class c6 = String.class;

类的==比较,不看内容,只看字节码是不是同一个,如下:

int[] arr1 = {1};
Class c1 = arr1.getClass();
int[] arr2 = {2};
Class c2 = arr2.getClass();
System.out.println(c1 == c2);// true

三、前置代码

后面演示功能动态获取使用到的代码

MyAnnotation 注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {

}

Rule 接口

public interface Rule {
	void eatLimit();
}

Animal 父类

public class Animal {
	public String name;
	private int age;

	public void eat(){
		System.out.println("吃饭");
	}

	private void sleep(){
		System.out.println("睡觉");
	}
}

Dog 子类

@MyAnnotation
public class Dog extends Animal implements Rule {

	private double height;
	double weight;
	protected static String realname;
	public String nickname;

	public Dog() {
	}

	public Dog(String nickname) {
		this.nickname = nickname;
	}

	private Dog(double weight) {
		this.weight = weight;
	}


	@Override
	@MyAnnotation
	public void eatLimit() throws RuntimeException {
		System.out.println("不能吃屎");
	}

	private void sleepDog() {
		System.out.println("狗睡觉");
	}

	public void setHeight(double height) {
		this.height = height;
	}

	private void setNickname(String nickname) {
		this.nickname = nickname;
	}
}

三、获取字节码信息

方式

  1. 对象名.getClass()
  2. 类.class
  3. Class.forName(类所在包+类名)
  4. 类.class.getClassLoader().loadClass(类所在包+类名)

方法1,2不常用,因为都能拿到对象或类,直接调用就好了呀,干嘛多此一举
方法3用的比较多,推荐
方法4几乎不用,了解即可

示例代码

Animal animal = new Animal();
Class c1 = animal.getClass();
Class c2 = Animal.class;
Class c3 = Class.forName("cn.forlan.reflection.entity.Animal");
ClassLoader loader = Animal.class.getClassLoader();
Class c4 = loader.loadClass("cn.forlan.reflection.entity.Animal");

四种方式获取的字节码都是同一个,用==比较是相等的

四、动态获取类信息

1、获取构造器&创建对象

1.1 常用方法

  • Class对象.getConstructors():获取类中被public修饰的构造器
  • Class对象.getDeclaredConstructors():获取类中全部构造器
  • Class对象.getConstructor(参数…):获取被public修饰的构造器,根据有无传参,来决定是无参还是有参构造器
  • Class对象.getDeclaredConstructor(参数…):获取到全部构造器,根据有无传参,来决定是无参还是有参构造器
  • 构造器对象.newInstance():通过构造器创建对象

思考:创建对象,还可以通过Class对象名.newInstance(),和构造器创建对象的方式有什么区别?

在Java反射中,Class对象和构造函数都可以用来创建对象。Class对象通常用于动态地获取类的信息和调用静态方法,而构造函数则用于实例化这个类的对象。使用Class对象创建对象需要调用其newInstance()方法,而使用构造函数创建对象则需要使用Constructor对象并调用其newInstance()方法。另外,使用构造函数创建对象时可以传入参数来初始化对象的属性,而使用Class对象创建对象则需要保证类具有默认的无参构造函数。

1.2 示例代码

// 获取字节码信息
Class cls = Class.forName("cn.forlan.reflection.entity.Dog");

// 通过字节码得到构造器
Constructor[] constructors1 = cls.getConstructors();
Constructor[] constructors2 = cls.getDeclaredConstructors();
Constructor constructor1 = cls.getConstructor();
Constructor constructor2 = cls.getConstructor(String.class);
Constructor constructor3 = cls.getDeclaredConstructor(double.class);

// 通过构造器创建对象
Object o1 = constructor1.newInstance();
Object o2 = constructor2.newInstance("笑笑");
// Object o3 = constructor3.newInstance(190); // 私有构造器,无法实例化

// 通过类对象创建对象
Object o = cls.newInstance();

2、属性

2.1 常用方法

获取属性:

  • Class对象.getFields():获取类中和父类中被public修饰的属性
  • Class对象.getDeclaredFields():获取类中和父类中全部属性
  • Class对象.getField(xxx):获取被public修饰的指定属性
  • Class对象.getDeclaredField(xxx):获取指定属性

获取属性具体结构:

  • Field对象名getModifiers():获取属性的修饰符,可能有多个
    通过Modifier.toString(modifiers)可以转成的字符串修饰符
  • Field对象.getType():获取属性的数据类型
  • Field对象.getName():获取属性的名字,即字段名

属性赋值:

  • Field对象.set(Class对象,属性值)

2.2 示例代码

// 获取字节码信息
Class cls = Class.forName("cn.forlan.reflection.entity.Dog");
// 获取属性
Field[] fields = cls.getFields();
Field[] declaredFields = cls.getDeclaredFields();
// 获取指定属性
Field nickname = cls.getField("nickname");
Field realname = cls.getDeclaredField("realname");

// 获取属性具体结构
// 获取属性的修饰符
int modifiers = realname.getModifiers();
// System.out.println(modifiers); // 数字来的
// System.out.println(Modifier.toString(modifiers)); // 转为对应枚举值
// 获取属性的数据类型
Class clazz = realname.getType();
// 获取属性的名字
String name = realname.getName();

// 给属性赋值(必须要有对象)
Object obj = cls.newInstance();
nickname.set(obj, "小小");// 给obj对象的nickname属性赋值为"小小"

3、方法

3.1 常用方法

获取方法:

  • Class对象.getMethods():获取类中和父类中被public修饰的方法【当前类和父类被public修饰的方法】
  • Class对象.getDeclaredMethods():获取类中所有方法【当前类所有方法】
  • Class对象.getMethod(方法名,参数…):获取类中和父类中指定方法【被public修饰】
  • Class对象.getDeclaredMethods(方法名,参数…):获取类中指定方法【当前类】

获取方法具体结构:

@注解
修饰符 返回值类型 方法名(参数列表) throws XXException{}

  • Method对象.getName():动态获取方法名字
  • Method对象.getModifiers():动态获取方法修饰符
  • Method对象.getReturnType():动态获取方法返回值
  • Method对象.getParameterTypes():动态获取方法参数列表
  • Method对象.getAnnotations():动态获取方法注解列表,只能是运行时的注解,即RetentionPolicy.RUNTIME
  • Method对象.getExceptionTypes():动态获取方法异常列表

调用方法:

  • Method对象.invoke(Class对象,参数…)

3.2 示例代码

// 获取字节码信息
Class cls = Class.forName("cn.forlan.reflection.entity.Dog");

// 获取方法列表
Method[] methods = cls.getMethods();
Method[] declaredMethods = cls.getDeclaredMethods();
// 获取指定方法
Method method1 = cls.getMethod("eatLimit");
Method method2 = cls.getMethod("setHeight", double.class);
Method method3 = cls.getDeclaredMethod("setNickname", String.class);

// 获取方法的具体结构
// 方法名字
System.out.println(method2.getName());
// 方法修饰符
int modifiers = method2.getModifiers();
System.out.println(Modifier.toString(modifiers));
// 方法返回值
System.out.println(method2.getReturnType());
// 方法参数列表
Class[] parameterTypes = method2.getParameterTypes();
// 方法注解
Annotation[] annotations = method1.getAnnotations();
// 方法异常列表
Class[] exceptionTypes = method1.getExceptionTypes();

// 调用方法
Object o = cls.newInstance();
method1.invoke(o);

4、类信息

4.1 常用方法

  • Class对象.getInterfaces():类的接口列表
  • Class对象.getSuperclass():类的父类
  • Class对象.getPackage():类所在包
  • Class对象.getAnnotations():类的注解列表

4.2 示例代码

// 获取字节码信息
Class cls = Class.forName("cn.forlan.reflection.entity.Dog");
// 获取类的接口列表
Class[] interfaces = cls.getInterfaces();
// 获取类的父类
Class superclass = cls.getSuperclass();
// 获取类所在包
Package aPackage = cls.getPackage();
// 获取类的注解列表
Annotation[] annotations = cls.getAnnotations();

五、面试

1、什么时候需要用反射?

当我们关注程序的动态性,扩展性时,就会使用反射

2、反射是否破坏了面向对象的封装性?

反射关注的是动态性,虽然我们可以调用private修饰的东西,这样其实就破坏了封装性,但一般不建议调用

你可能感兴趣的:(Java,java,开发语言)