JavaSE进阶--反射

这篇具有很好参考价值的文章主要介绍了JavaSE进阶--反射。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

📢 大家好,我是程序员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修饰的东西,这样其实就破坏了封装性,但一般不建议调用文章来源地址https://www.toymoban.com/news/detail-496971.html

到了这里,关于JavaSE进阶--反射的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JAVASE进阶(设计模式、设计原则)(更新中...)

    目录 一、注解 内置注解:JAVA中已经定义好的注解。 元注解:修饰注解的注解。 自定义注解。 二、克隆  JAVA中对clone的实现? 浅克隆 深克隆  那么该如何做到深克隆呢? 三、常用设计模式         1、创建型模式         单例模式         工厂模式        

    2024年01月23日
    浏览(41)
  • JavaSE进阶 | IDEA工具快捷键的使用

    ✅作者简介:大家好我是@每天都要敲代码,希望一起努力,一起进步!本博文是初学时所写,后面有详细的IDEA讲解IDEA全家桶讲解 📃个人主页:@每天都要敲代码的个人主页 目录 🏀关于IDEA工具的快捷键以及一些简单的设置 🥅project---Module---class三板斧 🥅IDEA常用快捷键 🥅

    2023年04月18日
    浏览(44)
  • JavaSE学习进阶day03_02 内部类

    2.1.1 什么是内部类 将一个类A定义在另一个类B里面,里面的那个类A就称为 内部类 ,B则称为 外部类 。可以把内部类理解成寄生,外部类理解成宿主。 2.1.2 什么时候使用内部类 一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用 人里面有一颗心脏。

    2023年04月15日
    浏览(36)
  • php 进阶 - 反射的讲解

    (1)反射有什么用:分析类,属性,方法等,帮助我们构建复杂可扩展的应用. php内置提供了一些类和函数让我们实现这些功能 如:判断某个类是否存在一个方法,动态执行一个方法等等 (2)具体用到哪些类或函数方法 具体查看php文档的介绍http://www.php.net/manual/zh/reflectionclass.construct.php

    2024年02月09日
    浏览(42)
  • 【C#进阶】C# 反射

    序号 系列文章 11 【C#基础】C# 预处理器指令 12 【C#基础】C# 文件与IO 13 【C#进阶】C# 特性 ✋ 大家好,我是writer桑,本章为大家介绍 C# 中的 反射 。 反射 指的是程序可以 访问,检测和修改 它本身状态或行为的一种行为。 其中访问的目标包括程序集 1 、模块和类型对象等。可

    2024年03月15日
    浏览(49)
  • GO基础进阶篇 (十二)、反射

    Go语言中的反射是指在程序运行时检查程序的结构,比如变量的类型、方法的签名等。Go语言的反射包是reflect。通过反射,你可以动态地检查类型信息、获取字段和方法、调用方法等。 反射可以在运行时动态获取变量的各种信息,比如变量的类型、值等 如果是结构体,还可以

    2024年02月02日
    浏览(49)
  • JavaSE进阶 | Map集合、HashMap集合、TreeMap集合

    目录 🏀Map集合概述  🥅Map接口常用的方法 🥅哈希表(散列表)数据结构 🥅同时重写HashCode和equals 🥅HashMap和Hashtable的区别 🥅Properties类 🥅TreeSet(TreeMap)集合 🥅自平衡二叉树数据结构 🥅实现比较器接口 🥅集合工具类Collections (1) Map和Collection没有继承关系,是一个平级的

    2023年04月09日
    浏览(44)
  • C#进阶-反射的详解与应用

    反射是.NET框架提供的一个功能强大的机制,它允许程序在运行时检查和操作对象的类型信息。通过使用反射,程序可以动态地创建对象、调用方法、访问字段和属性,无需在编译时显式知道类型信息。在.NET中,所有类型的信息最终都是存储在元数据中的。反射就是.NET提供的

    2024年03月28日
    浏览(80)
  • JavaSE进阶 | 二维数组的定义和使用、查找和排序算法

    目录 🥅二维数组 ❤️二维数组的遍历 ❤️动态初始化二维数组 🥅数组知识点总结 🥅习题练习 ❤️用数组模拟栈 ❤️模拟酒店的订房退房功能 ❤️杨辉三角 ❤️把数据存入数组,保证值各不相同 ❤️数组元素的赋值与数组复制 ❤️数组元素的反转 ❤️数组的扩容与缩

    2024年02月14日
    浏览(41)
  • JavaSE进阶 | 深入理解Java IO流(文件专属流)

    目录 🥅IO流理论概述 1.什么是IO 2.IO流的分类 3.流的四大家族 4.需要掌握的十六个流 🥅字节输入流FileInputStream 1.FileInputStream初步理解 2.FileInputStream常用方法 🥅字节输出流FileOutputStream 🥅任意文件拷贝 🥅FileReader FileWriter 普通文件拷贝 1.字符输入流FileReader 2.字符输出流FileW

    2023年04月12日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包