反射

反射的概述

反射(Reflection)是被视为动态语言的关键,反射机制允许程序在执行期借助 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性和方法。

加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个

类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。可

以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看

到类的结构,所以,我们形象的称之为反射

Java 不是动态语言(在运行时代码可以根据某些条件改变自身结构),但J ava 可以称之为“准动态语言”。即 Java 有一定的动态性,可以利用反射机制字节码操作获得类似动态语言的特性。

类的加载与 ClassLoader 的理解

在 Object 类中定义了 public final Class getClass() 方法,此方法

将被所有子类继承。它的返回值的类型是一个 Class 类,此类是 Java 反射的源头

通俗的将,Class 对应着加载到内存中(通过类加载器)的一个运行时类

Class 类的常用方法:

  • static Class forName(String name) 返回指定类名 name 的 Class 对象
  • Object newInstance() 调用缺省构造函数,返回该 Class 对象的一个实例
  • getName() 返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称
  • Class getSuperClass() 返回当前 Class 对象的父类的 Class 对象
  • Class [] getInterfaces() 获取当前 Class 对象的接口
  • ClassLoader getClassLoader() 返回该类的类加载器
  • Class getSuperclass() 返回表示此 Class 所表示的实体的超类的 Class
  • Constructor[] getConstructors() 返回一个包含某些 Constructor 对象的数组
  • Field[] getDeclaredFields() 返回 Field 对象的一个数组
  • Method getMethod(String name,Class … paramTypes) 返回一个Method 对象,此对象的形参类型为 paramType
package parzulpan.com.java;

import org.junit.Test;

import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method; /**
* @Author : parzulpan
* @Time : 2020-11-28
* @Desc : 反射初体验
*/ public class ReflectionTest { // 反射之前,对 Person 的操作
@Test
public void test1() {
// 创建 Person 对象
Person p1 = new Person("Tom", 22); // 调用属性
p1.age = 10;
System.out.println(p1.toString()); // 调用方法
p1.show(); // 不能调用私有的构造器、属性、方法等
} // 反射之后,对 Person 的操作
@Test
public void test2() {
try {
// 通过反射,创建 Person 对象
Class<Person> clazz = Person.class;
Constructor<Person> cons = clazz.getConstructor(String.class, int.class);
Person p1 = cons.newInstance("Tom", 22); // 通过反射,调用属性
Field age = clazz.getDeclaredField("age");
age.set(p1, 10);
System.out.println(p1.toString()); // 通过反射,调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p1); System.out.println(); // 通过反射,能调用私有的构造器、属性、方法等 // 调用私有的构造器
Constructor<Person> cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p2 = cons1.newInstance("Jerry");
System.out.println(p2.toString()); // 调用私有的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p2, "JerryCat");
System.out.println(p2); // 调用私有的方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
Object nation = showNation.invoke(p2, "China");
System.out.println(nation);
} catch (Exception e) {
e.printStackTrace();
}
} // 获取 Class 类的实例
@Test
public void test3() {
// 方式一:通过运行时类的属性 .class
Class<Person> clazz1 = Person.class;
System.out.println(clazz1); // 方式二:通过运行时类的对象,调用 getClass()
Person p1 = new Person();
Class<? extends Person> clazz2 = p1.getClass();
System.out.println(clazz2); Class<?> clazz3 = null;
Class<?> clazz4 = null;
try {
// 方式三:通过 Class 的静态方法,forName(String classPath),这个方式使用频率最高
clazz3 = Class.forName("parzulpan.com.java.Person");
System.out.println(clazz3);
clazz4 = Class.forName("java.lang.String");
System.out.println(clazz4);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} // 这三种方式获得的类的实例均相同
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true // 方式四:使用类的加载器,ClassLoader
try {
ClassLoader clazzL = this.getClass().getClassLoader();
Class<?> clazz5 = clazzL.loadClass("parzulpan.com.java.Person");
System.out.println(clazz5);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} // 哪些类型可以有 Class 对象
@Test
public void test4() {
Class<Object> c1 = Object.class;
Class<Comparable> c2 = Comparable.class;
Class<String[]> c3 = String[].class;
Class<int[][]> c4 = int[][].class;
Class<ElementType> c5 = ElementType.class;
Class<Override> c6 = Override.class;
Class<Integer> c7 = int.class;
Class<Void> c8 = void.class;
Class<Class> c9 = Class.class; int[] a = new int[10];
int[] b = new int[100];
Class<? extends int[]> c10 = a.getClass();
Class<? extends int[]> c11 = b.getClass();
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11); // true
}
}

着重掌握获取 Class 类的实例的四种方式,特别是第三种。

类的加载过程,当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:

  • 类的加载(Load):将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象,此过程由类加载器完成;
  • 类的链接(Link):将类的二进制数据合并到 JRE 中;
  • 类的初始化(Initialize):JVM 负责对类进行初始化。

什么时候会发生类的初始化?

  • 当虚拟机启动,先初始化 main 方法所在的类;
  • new 一个类的对象;
  • 调用类的静态成员(除了 final 常量)和静态方法;
  • 使用 java.lang.reflect 包的方法对类进行反射调用;
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类;
  • 注意,通过数组定义类引用,不会触发此类的初始;
  • 注意,引用常量也不会触发此类的初始化,因为常量在链接阶段就存入调用类的常量池中了。

类加载器作用是用来把类装载进内存的,JVM 规范定义了如下类型的类的加载器:

  • 引导类加载器:用 C++ 编写的,是 JVM 自带的类加载器,负责 Java 平台核心库,用来装载核心类库。该加载器无法直接获取
  • 扩展类加载器:负责 jre/lib/ext 目录下的 jar 包或 – D java.ext.dirs 指定目录下的 jar 包装入工作库。
  • 系统类加载器:负责 java –classpath–D java.class.path 所指的目录下的类与 jar 包装入工作,是最常用的加载器。
package parzulpan.com.java;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties; /**
* @Author : parzulpan
* @Time : 2020-11-28
* @Desc : 了解类的加载器
*/ public class ClassLoaderTest { // 类加载器
@Test
public void test1() {
// 对于自定义类,获取一个系统类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader); // 获取系统类加载器的父类加载器,即扩展类加载器
ClassLoader parent = classLoader.getParent();
System.out.println(parent); // 获取扩展类加载器的父类加载器,即引导类加载器
// 无法获取引导类加载器,它主要负责加载 Java 的核心类库,无法加载自定义类
ClassLoader parent1 = parent.getParent();
System.out.println(parent1); // null ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1); // null ClassLoader classLoader2 = null;
try {
classLoader2 = Class.forName("java.lang.Object").getClassLoader(); // null,说明也是引导类加载器
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(classLoader2);
} @Test
// 读取配置文件
public void test2() {
Properties properties = new Properties(); try {
// 读取配置文件方式一:使用 IO 流
// 此时文件默认在当前 module 下
FileInputStream fis = new FileInputStream("jdbc.properties");
properties.load(fis); // 读取配置文件方式二:使用类加载器
// 此时文件默认在当前 module 的 src 下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream rss = classLoader.getResourceAsStream("jdbc1.properties");
properties.load(rss);
} catch (IOException e) {
e.printStackTrace();
} String user = properties.getProperty("user");
String password = properties.getProperty("password");
System.out.println("user = " + user + ", password = " + password);
}
}

创建运行时类的对象

package parzulpan.com.java;

import org.junit.Test;

import java.util.Random;

/**
* @Author : parzulpan
* @Time : 2020-11-28
* @Desc : 通过反射 创建对应的运行时类的对象
*/ public class NewInstanceTest { // 创建运行时类的对象
@Test
public void test1() {
Class<Person> clazz = Person.class; try {
/* 调用了运行时类的空参构造器,需要满足两个条件
1. 类必须有一个无参数的构造器
2. 类的构造器的访问权限需要足够,通常设置为 public 所以,在 JavaBean 中要求提供一个 public 的空参构造器,原因:
1. 便于通过反射,创建运行时类的对象;
2. 便于子类继承此运行时类的时候,默认调用 super() 保证父类有此构造器
*/
Person p1 = clazz.newInstance();
System.out.println(p1); } catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
} // 体会反射的动态性
@Test
public void test2() {
for (int i = 0; i < 10; i++) {
int num = new Random().nextInt(3);// 0 1 2
String classPath;
switch (num) {
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "parzulpan.com.java.Person";
break;
default:
classPath = "";
} try {
Object instance = getInstance(classPath);
System.out.println(instance);
} catch (Exception e) {
e.printStackTrace();
} } } /**
* 创建一个指定类的对象
* @param classPath 指定类的全类名
* @return 指定类的对象
*/
public Object getInstance(String classPath) throws Exception {
Class<?> clazz = Class.forName(classPath);
return clazz.newInstance();
} }

获取运行时类的完整结构

完整结构包括:实现的全部接口、所继承的父类、全部的构造器、全部的方法、全部的属性等。

获取运行时类的完整结构 - 类

获取运行时类的完整结构 - 父类

获取运行时类的完整结构 - 接口

获取运行时类的完整结构 - 注解

获取运行时类的完整结构

调用运行时类的指定结构

package parzulpan.com.java;

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method; /**
* @Author : parzulpan
* @Time : 2020-11-28
* @Desc : 调用运行时类的指定结构
*/ public class ReflectionTest1 { // 调用运行时类的指定结构 - 指定属性
@Test
public void test1() throws Exception{
// 获取类的实例
Class<Person> clazz = Person.class; // 创建运行时类的对象
Person person = clazz.newInstance(); // 调用空参构造器 // public Field getField(String name)
// 返回此 Class 对象表示的类或接口的指定的 public 的 Field
// 通常不用,因为很少用 public 修饰属性
Field age = clazz.getField("age");
age.set(person, 99);
Object o = age.get(person);
System.out.println(o); System.out.println(); // public Field getDeclaredField(String name)
// 返回此 Class 对象表示的类或接口的指定的 Field
Field name = clazz.getDeclaredField("name");
name.setAccessible(true); // 禁用访问安全检查
name.set(person, "Tom");
Object o1 = name.get(person);
System.out.println(o1);
} // 调用运行时类的指定结构 - 指定方法
@Test
public void test2() throws Exception{
// 获取类的实例
Class<Person> clazz = Person.class; // 创建运行时类的对象
Person person = clazz.newInstance(); // 1. 通过 Class 类的 getMethod(String name, Class…parameterTypes) 方法
// 取得一个 Method 对象,并设置此方法操作时所需要的参数类型
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
// 2. 使用后使用 Object invoke(Object obj, Object[] args)进行调用,
// 并向方法中传递要设置的 obj 对象的参数信息
showNation.setAccessible(true); // 禁用访问安全检查
Object china = showNation.invoke(person, "China");
System.out.println(china); // 返回值 System.out.println(); // 对于原方法若为静态方法,此时形参 Object obj 可为 null
Method showNationStatic = clazz.getDeclaredMethod("showNationStatic");
showNationStatic.setAccessible(true);
Object invoke = showNationStatic.invoke(null);
System.out.println(invoke);
} // 调用运行时类的指定结构 - 指定构造器
// 用的比较少,一般会直接调用空参构造器 clazz.newInstance();
@Test
public void test3() throws Exception{
Class<Person> clazz = Person.class; // 参数指明构造器的参数列表
Constructor<Person> declaredConstructor = clazz.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(true); // 调用此构造器创建运行时类的对象
Person tom = declaredConstructor.newInstance("Tom");
System.out.println(tom); // Person{name='Tom', age=0}
}
}

反射的应用:动态代理

代理设计模式:使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

代理一般分为两种:

  • 静态代理;
  • 动态代理。

静态代理的缺点:

  • 代理类和被代理类在编译期间就被确定了,不利于程序的扩展。
  • 同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
package parzulpan.com.java1;

/**
* @Author : parzulpan
* @Time : 2020-11-28
* @Desc : 静态代理的举例
* 特点:代理类和被代理类在编译期间就被确定了,不利于程序的扩展。
* 同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
*/ public class StaticProxyTest {
public static void main(String[] args) {
// 创建被代理类的对象
NikeClothFactory nikeClothFactory = new NikeClothFactory();
// 创建代理类的对象
ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nikeClothFactory); proxyClothFactory.produceCloth();
}
} // 工厂接口
interface ClothFactory {
void produceCloth();
} // 代理类
class ProxyClothFactory implements ClothFactory {
private ClothFactory factory; // 用被代理类对象进行实例化 public ProxyClothFactory(ClothFactory factory) {
this.factory = factory;
} @Override
public void produceCloth() {
System.out.println("代理工厂进行准备工作!"); factory.produceCloth(); System.out.println("代理工厂进行收尾工作!");
}
} // 被代理类
class NikeClothFactory implements ClothFactory { @Override
public void produceCloth() {
System.out.println("Nike 工厂生产一批球鞋!");
}
}

动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时

根据需要动态创建目标类的代理对象。动态代理使用场合有调试远程方法调用等。

动态代理实现步骤

  • 创建一个实现接口 InvocationHandler 的类,它必须实现 invoke 方法,以完成代理的具体操作。
  • 创建被代理的类以及接口。
  • 通过 Proxy 的静态方法 newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个接收被代理类接口的代理工厂类。
  • 通过代理工厂类代理调用实现类的方法。
package parzulpan.com.java1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* @Author : parzulpan
* @Time : 2020-11-28
* @Desc : 动态代理的举例
* 要想实现动态代理,需要解决两个问题:
* 1. 如果根据加载到内存中的被代理类,动态的创建一个代理类及其对象?
* 2. 当通过代理类的对象调用方法时,如果动态的去调用被代理类中的同名方法?
*/ public class ProxyTest {
public static void main(String[] args) {
// 被代理类
SuperMan superMan = new SuperMan();
// Object proxyInstance = ProxyFactory.getProxyInstance(superMan);
// 动态代理类
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("回锅肉"); System.out.println(); // 被代理类
NikeClothFactory nikeClothFactory = new NikeClothFactory();
// 动态代理类
ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyInstance1.produceCloth();
}
} // 被代理接口
interface Human {
String getBelief();
void eat(String food);
} // AOP
class HumanUtil {
public void method1() {
System.out.println("我是方法一!");
} public void method2() {
System.out.println("我是方法二!");
}
} // 被代理类
class SuperMan implements Human { @Override
public String getBelief() {
return "我相信我可以飞!";
} @Override
public void eat(String food) {
System.out.println("我喜欢吃 " + food);
}
} // 代理工厂
class ProxyFactory {
/**
* 解决问题1
* @param obj 被代理类的对象
* @return 代理类的对象
*/
public static Object getProxyInstance(Object obj) {
MyInvocationHandler handler = new MyInvocationHandler(); handler.bind(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
} // 实现接口 InvocationHandler 的类
class MyInvocationHandler implements InvocationHandler {
private Object obj; // 需要使用被代理类的对象进行赋值 // 绑定被代理类
public void bind(Object obj) {
this.obj = obj;
} // 当通过代理类的对象调用方法 A 时,就会调用这个 invoke()
// 解决问题2,将被代理类要执行的方法 A 的功能就声明在 invoke() 中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 使用 AOP
HumanUtil humanUtil = new HumanUtil(); humanUtil.method1(); // method 作为被代理类对象要调用的方法
// obj 被代理类对象
// returnValue 作为当前类的 invoke() 的返回值
Object returnValue = method.invoke(obj, args); humanUtil.method2(); return returnValue;
}
}

动态代理和 AOP(Aspect Orient Programming,面向切面编程),即 AOP 代理里的方法可以在执行目标方法之前、之后插入一些通用处理。如下图:

实现如上代码的 HumanUtil 类。

总结和练习


反射机制与面向对象中的封装性是不是矛盾的?如何看待这两个技术?

不矛盾。封装性解决的是建议你使用什么的问题,而反射是我能不能使用的问题。


反射机制的使用时机?应用举例?

动态代理。

框架 = 注解 + 反射 + 设计模式。


【Java基础】反射的更多相关文章

  1. Java基础-反射(reflect)技术详解

    Java基础-反射(reflect)技术详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.类加载器 1>.JVM 类加载机制  如下图所示,JVM类加载机制分为五个部分 ...

  2. Java基础——反射

    今天学到Java基础中的反反射.依照我学习后的个人理解呢,反射就是一套获取类.属性.方法等的工具吧.(其实,感觉学完反射后,有点像喝凉水,解渴但确实我也没体会出它有什么味道,我可能没有学到精髓吧.自己 ...

  3. Java基础反射(二)

    原文地址http://blog.csdn.net/sinat_38259539/article/details/71799078 反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Cla ...

  4. java基础(反射,注解,多线程,juc)

    JAVA基础 java反射 class对象 三种方式获取class加载时对象 1.class.forName("全类名"):将字节码文件加载进内存,返回class对象 2.类名.c ...

  5. [java 基础]反射入门

    原文 概况 使用java的反射,可以让我们检查(或者修改)类,接口,字段,方法的特性.当你在编译期不知道他们的名字的时候非常有用. 除此之外,可以使用反射来创建实例,调用方法或者get/set 字段值 ...

  6. JAVA基础-反射机制

    什么是JAVA的反射机制 Java反射是Java被视为动态(或准动态)语言的一个关键性质.这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其 ...

  7. java基础-反射(细节)

    java面试题--java反射机制? Java反射机制的作用:1)在运行时判断任意一个对象所属的类.2)在运行时判断任意一个类所具有的成员变量和方法.3)在运行时任意调用一个对象的方法4)在运行时构造 ...

  8. Java基础—反射(转载)

    转载自: JAVA反射与注解 JAVA反射 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射机制是什么 反射 ...

  9. java基础——反射机制

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

  10. Java基础—反射与代理(新手向)

    第1章 反射与代理 1.1 反射定义 一般情况下,需要一个功能的前提是遇到了某个问题,这里先列举一些问题,然后再通过反射是如何解决了这些问题,来引出反射的定义. 普通开发人员工作中最常见的问题:需要生 ...

随机推荐

  1. 原生js之事件解绑

    #removeEventListener ##html <button id='btn'>click</button> ##js ###第一种方式(错误方式) var btn ...

  2. 一些vue项目

    https://segmentfault.com/a/1190000010330905

  3. Navicat操作Oracle

    1.出现connection to server failed, probable Oracle Net admin error 更换oci.dll,Navicat有自己默认的oci.dll,更换成你 ...

  4. QQ电话能定位

    QQ电话是网络电话,可以定位吗??? 下面介绍利用wireshark获取QQ好友IP实施精准定位 超详!! Wireshark Wireshark是一个使用WinPcap作为接口,直接与网卡进行数据报 ...

  5. Spring Boot 启动事件和监听器,太强大了!

    大家都知道,在 Spring 框架中事件和监听无处不在,打通了 Spring 框架的任督二脉,事件和监听也是 Spring 框架必学的核心知识之一. 一般来说,我们很少会使用到应用程序事件,但我们也不 ...

  6. 查看kafka topic的消息offset范围

    查看各个patition消息的最大位移 sh kafka-run-class.sh kafka.tools.GetOffsetShell --topic mytopic --time -1 --bro ...

  7. db2密码中有感叹号时,连接方法

    在脚本文件中,可以将密码用单引号括起来 db2 "connect  to testdb user test_user using 'Gb2Zk1!R'"

  8. AD PCB模块复用

    该文档为原创,转发需注明出处!https://www.cnblogs.com/brianblog/ 在画图的时候如果遇到PCB中有多个模块原理图是一模一样的时候,我们便会想能不能偷点懒,只画一个模块, ...

  9. $$ PHP 的含义

    php中两个$也就bai是$$用来定义可变变量. 所谓可变变量,就是一个变量的名,又是一个变量.

  10. Mysql实现定时清空一张表的旧数据并保留几条数据

    要达到如下目的: Mysql数据库会每隔一段时间(可以是2小时,也可以是一天,这个可以自定义),定时对一张库中的表做一个判断,如果这张表的数据超过了20条(这个数据也是自定义的,也可以是200条),就 ...