反射
类加载
类加载原理
当程序要使用某一个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤对类进行初始化,如果不出现意外的情况,JVM虚拟机会连续的完成这三个步骤,所以有时也把这三个步骤统称为类加载,或类初始化
类的加载:
-
就是将class文件读入内存,并为之创建一个java.lang.Class对象
-
任何类被使用时,系统都会为之建立一个java.lang.Class对象
类的链接:
- 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
- 准备阶段:负责为类的类变量(static)分配内存,并设置默认初始值
- 解析阶段:将类的二进制数据中的符号引用替换为直接引用(虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程)
类的初始化:
主要是对类变量进行初始化(类构造器是构造类信息的,不是构造该类对象的构造器)
类的初始化步骤:
-
假如类还未被加载和链接,则程序先加载再链接该类
-
假如类的直接父类还未被初始化,则先初始化该类的直接父类
-
假如类中有初始化语句,则系统依次执行这些初始化语句(保证一个类的()方法在多线程环境中被正确加锁和同步)
-
类的初始化时机(类的主动引用):
-
- 创建类的实例
- 调用类的类方法
- 访问类或接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某类的子类
- 直接使用java.exe 命令来运行某个主类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l5Qa5Lp5-1657251461048)(…/…/…/Users/86182/AppData/Roaming/Typora/typora-user-images/image-20220707234706580.png)]
类的被动引用(不会发生类的初始化):
-
当访问一个静态域时,只有真正声明这个域的类才会被初始化(父)。如当通过子类引用父类的静态变量,不会导致子类初始化
-
通过数组定义类的引用,不会触发此类的初始化
-
引用常量不会触发此类的初始化,static final 常量在链接阶段就存入类的常量池中
类加载器
ClassLoader:是负责加载类的对象
Java运行时具有以下内置类加载器
- 启动类加载器(Bootstrap ClassLoader):它是虚拟机的内置类加载器,通常表示为NULL,负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
- 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
- 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。(System Class Loader)
package yu;
public class Demo1 {
public static void main(String[] args) {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();//返回用于委派的系统类加载器
System.out.println(systemClassLoader);
ClassLoader parent = systemClassLoader.getParent();//返回父类加载器进行委派
System.out.println(parent);
ClassLoader parentParent = parent.getParent();
System.out.println(parentParent);// BootstrapClassLoader
// 是虚拟机的内置类加载器通常为null
}
}
反射
反射机制:
Java反射机制:是指在运行期去获取一个类的变量和方法信息。然后通过获取到的信息类创建对象,调用方法的一种机制。由于这种动态性,可以极大的提高程序的灵活性。程序不用在编译期就完成确定,在运行期仍可以扩展
-
Class 类是所有.class文件对象所对应的类型
-
Class 类中有成员变量,构造方法,成员方法…是对具体类的映射
1.反射获取Class对象
我们要想通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是Class类型的对象,下面有三种获取Class 类型的方式
- 使用类的class属性来获取该类对应的Class对象,举例:Student.class将会返回Student类对应的Class对象
- 调用对象的getClass()方法,返回该对象所属类的Class对象,该方法是Object类中的方法,所有的Java对象都可以调用该方法
- 使用Class类中的静态方法forName(String className),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径(完整包名的路径)
public class Demo2 {
public static void main(String ...args) throws ClassNotFoundException {
//方式1通过调用类的getClass()方法获取Class字节码文件对象
Class<? extends Student> aClass = new Student("nb", 5, "马牛皮").getClass();
System.out.println(aClass);
//方式2通过类的class属性获取Class的字节码文件对象
Class<Student> studentClass = Student.class;
System.out.println(studentClass);
//方式3 使用Class类中的静态方法forName获取Class的字节码文件对象
Class<?> aClass1 = Class.forName("lff.xm.Student");
System.out.println(aClass1);
}
}
2.反射获取构造器方法并使用
Class 类中用于获取构造方法的方法
-
Constructor<?>[] getConstructors(): 返回所有public 构造方法对象的数组
-
Constructors<?>[] getDeclaredConstructors():返回所有构造方法对象的数组
-
Constructor getConstructor(Class<?>…parameterTypes):返回单个public 构造方法对象
-
ConstructorgetDeclaredConstructor(Class<?>… parameterTypes):返回单个构造方法对象
-
基本数据类型也可以通过.class得到对应的Class类型
-
- Constructor 类中用于创建对象的方法
- T newInstance(Object…initargs):根据指定的构造器方法去创建对象
package lff.xm;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Demo3 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Student> c = Student.class;
//拿到public 修饰的构造方法数组
Constructor<?>[] constructors = c.getConstructors();
for (Constructor con : constructors) {
System.out.println(con);
}
//拿到类声明的所有 构造方法数组
Constructor<?>[] declaredConstructors = c.getDeclaredConstructors();
for (Constructor con : declaredConstructors) {
System.out.println(con);
}
System.out.println("----------------");
//通过字节码文件对象得到了单个的构造方法对象con;
Constructor<Student> con = c.getConstructor();
Constructor<Student> con2 = c.getConstructor(String.class, int.class, String.class);//基本数据类型也可以通过.class得到对应的Class类型
// 通过构造方法对象调用newInstance 创建一个该类(Student)的对象
Student student1 = con.newInstance();
Student student2 = con2.newInstance("李肥肥",5,"山东");
System.out.println(student1);
System.out.println(student2);
}
}
注意点:
- public void setAccessable(true) 表示反射对象应该在使用Java语言访问控制时抑制检查。
//正常情况不能使用私有构造方法创建对象,在反射中可以暴力反射 setAccessible(true)
Class<Student> c3 = Student.class;
Constructor<Student> con3 = c3.getDeclaredConstructor(int.class);
con3.setAccessible(true);
Student student3 = con3.newInstance(18);
System.out.println(student3);
3.反射获取成员变量并使用
成员变量字段field
- public void setAccessable(true) 表示反射对象应该在使用Java语言访问控制时抑制检查。
- Field [] getFields():返回所有公共成员变量对象的数组
- Field [] getDeclareFields():返回所有成员变量的对象的数组
- Field [] getField(String name):返回单个公共成员变量对象
- Field [] getDeclareField(String name) 返回单个成员变量对象
package yu;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* 字段用法
*/
public class Demo4 {
/**
* @param args String类型的可变参数
* @throws NoSuchFieldException 抛出异常
*/
public static void main(String[] args) throws NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Student> studentClass = Student.class;
System.out.println("public修饰的字段:");
Field[] fields = studentClass.getFields();
for (Field field : fields)
System.out.println(field);
System.out.println("类中所有声明的字段:");
Field[] fields1 = studentClass.getDeclaredFields();
for (Field field : fields1)
System.out.println(field);
System.out.println("获取单个public修饰的字段:");
Field field = studentClass.getField("address");
System.out.println(field);
System.out.println("获取单个任意修饰的字段:");
Field field1 = studentClass.getDeclaredField("name");
System.out.println(field1);
Class<?> aClass = Class.forName("yu.Student");
Constructor<?> c = aClass.getDeclaredConstructor(String.class, int.class, String.class);
Object o = c.newInstance("肖猛", 18, "东明");
//正常情况不能使用私有属性赋值,在反射中可以暴力反射 setAccessible(true)
//public void setAccessable(true) 表示反射对象应该在使用Java语言访问控制时抑制检查。
field1.setAccessible(true);
field1.set(o,"李振宇");
Student s=(Student)o;
System.out.println(s);
}
}
4.反射获取成员方法并使用
Class类中用于获取成员方法的类
- Method[] getMethods() : 返回所有公共成员方法对象的数组,包括继承的
- Method[] getDeclareMethods() : 返回所有成员方法对象的数组,不包括继承的
- Method[] getMethod() : 返回单个公共成员方法的对象
- Method[] getDeclaremethods() : 返回单个成员方法对象
Mehod类中用于调用成员方法的方法
- Object invoke (Object obj,Object…args): 调用obj 对象的成员方法,参数args 例如:(int.class),返回值是Object类型
package yu;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 反射获取成员方法
*/
public class Demo5 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Student> cs = Student.class;
Method[] methods = cs.getMethods();
for(Method method:methods)
System.out.println(method);
System.out.println("------------");
Method[] declaredMethods = cs.getDeclaredMethods();
for(Method method:declaredMethods)
System.out.println(method);
System.out.println("------------");
Constructor<Student> constructor = cs.getDeclaredConstructor();
Student student = constructor.newInstance();
Method m1 = cs.getDeclaredMethod("method1",int.class);
Object o = m1.invoke(student, 10);
System.out.println((boolean)o);
System.out.println("--------------");
Method method2 = cs.getDeclaredMethod("method2");
//暴力反射,忽略检查
//public void setAccessable(true) 表示反射对象应该在使用Java语言访问控制时抑制检查。
method2.setAccessible(true);
method2.invoke(student);
}
}
5.反射操作泛型
Java采用泛型擦除的机制类引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的雷响全部擦除(Class对象中有所保留)
为了通过反射操作这些类型,Java新增了ParameterizedType(参数化类型),GenericArrayType(泛型数组型),TypeVariable(类型变量),WildcardType (通配符类型) 这几种类型来代表不能被归一到Class类中的类型,但是又和原始类型齐名的类型
- ParameterizedType(参数化类型) ,表示一种参数化类型,例如:List(Integer)
- GenericArrayType(泛型数组型),表示一种元素类型是参数化类型或类型变量的数组类型
- TypeVariable(类型变量),是各种类型变量的公共接口
- WildcardType (通配符类型), 代表一种通配符类型表达式
package com.xm;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
/**
* 反射获取泛型
*/
public class Demo2 {
public static void main(String[] args) throws NoSuchMethodException {
Class<Demo2> demo2Class = Demo2.class;
//参数类型是泛型
Method t1 = demo2Class.getMethod("test01", Map.class);
Type[] genericParameterTypes = t1.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);//打印Map
//泛型的参数类型属于参数化类型
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);//打印Map中的类型
}
}
}
//返回值类型是泛型
System.out.println("----------------------");
Method t2 = demo2Class.getMethod("test02", null);
Type returnType = t2.getGenericReturnType();
if (returnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
public void test01(Map<Integer, Student> map) {
System.out.println("test");
}
public List<String> test02() {
System.out.println("test2");
return null;
}
}
6.反射越过泛型检查
向ArrayList 集合中添加字符串数据
package yu;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class TestDemo {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
ArrayList<Integer> array=new ArrayList<>();
array.add(12);
array.add(23);
array.add(34);
Class<? extends ArrayList> aClass = array.getClass();
/*
反射越过泛型检查
*/
Method method = aClass.getMethod("add", Object.class);
method.invoke(array,"你好");
method.invoke(array,"世界");
method.invoke(array,45);
for (Object obj:
array) {
System.out.println(obj);
}
}
}
7.运行配置文件指定内容
package yu;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* 通过配置文件运行类中的方法
*/
public class TestDemo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
/*
加载数据
*/
Properties prop = new Properties();
FileReader fr = new FileReader("test.txt");
prop.load(fr);
fr.close();
/*
由键获取值
*/
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
/*
通过反射建造对象,调用方法
*/
Class<?> aClass = Class.forName(className);
Constructor<?> constructor = aClass.getDeclaredConstructor();
Object o = constructor.newInstance();
Method declaredMethod = aClass.getDeclaredMethod(methodName);
declaredMethod.invoke(o);
}
}
class Student1 {
public void study() {
System.out.println("好好学习");
}
}
class Teacher1 {
public void teach() {
System.out.println("教授知识");
}
}
test.txt
className=yu.Teacher1
methodName=teach
模块化
模块化的概述
即使程序只需要使用Java的部分核心功能,JVM 也要加载整个JRE环境
为了给java “瘦身”,让Java实现轻量化,Java9正式的推出了模块化系统。Java被拆分为N个模块,并允许Java程序可以根据需要选择加载程序必须的Java模块,这样就可以让Java以轻量化的方式来运行
其实,Java7 已经提出了模块化的概念,但是由于过于复杂,Java7 Java8 都没有真正推出,直到Java9 才真正的成熟起来,对于Java语言来说,模块化系统是一次真正的自我革新,使得Java语言重新焕发活力
项目中包含多个模块,每个模块包含多个包,运行程序往往只需要部分模块功能,不用加载整个模块
模块的基本使用
- 在模块的src目录下新建一个module-info.java的描述性文件,该文件专门定义模块名,访问权限,模块依赖等信息 ;描述性文件中使用模块导出和模块依赖来进行配置和使用;
- 模块中所有未导出的包都是模块私有的,他们时不能在模块之外被访问的,在A模块下的描述性文件中配置模块导出 ;模块导出格式:exports 包名
- 模块B要访问其他模块,则必须指定依赖哪些模块,未明确指定依赖关系的模块不能访问,在B模块下的描述性文件中配置模块依赖;模块依赖格式:requires 模块名;
- 这里有点坑:写requires 模块名时,会报错,需要按下CTRL +1 或者 Alt+ Enter,根据改进信息,选择模块依赖
- 然后模块B中就可以使用模块A中的内容了(导入类之类的操作不可少)
Student
package com.xm;
public class Student {
public void study(){
System.out.println("好好学习");
}
}
module-info.java (A) 和 module-info.java(B)
module A {
exports com.xm;
}
module B {
requires A;
}
测试类
package meng.test;
import com.xm.Student;
/**
* 模块的基本使用
*/
public class Demo1 {
public static void main(String[] args) {
new Student().study();
}
}
模块服务的使用
服务:从Java 6开始,Java提供了一种服务机制,允许服务提供者和服务使用者之间完成解耦简单的说,就是服务使用者只面向接口编程,但不清楚服务提供者的实现类
Java 9 的模块化系统则进一步的简化了Java的服务机制。Java9 允许服务接口定义在一个模块中,并使用uses语句来声明该服务接口,然后针对该服务接口提供不同的服务实现类,这些服务实现类可以分布在不同的模块中,服务实现模块则使用provides语句为服务接口指定实现类 ,服务使用者只需要面向接口编程即可
模块使用步骤:
- 在A模块中创建一个包 com.server,在该包下提供一个接口,接口中定义一个抽象方法public interface MyServer{ void server(); }
- 在com.server包下创建一个包impl ,在该包中创建两个实现类Game1和Game2实现MyServer
- 在A模块下的描述性文件中添加如下配置
-
- 模块导出 exprots com.server;
- 服务提供:provides MyServer with Game1; 指定MyServer的服务实现类是Game1
- 在B模块下的描述文件中添加如下配置: 声明服务接口:use MyService;
- 在B模块的类中使用MyServer接口提供的服务: ServiceLoader : 一种加载服务实现类的工具
MyServer接口
package com.server;
public interface MyServer {
void service();
}
实现类game1和game2
package com.server.impl;
import com.server.MyServer;
public class Game1 implements MyServer {
@Override
public void service() {
System.out.println("实现Game1");
}
}
package com.server.impl;
import com.server.MyServer;
public class Game2 implements MyServer {
@Override
public void service() {
System.out.println("实现game2");
}
}
module-info.java(A)和module-info.java(B)
import com.server.MyServer;
import com.server.impl.Game1;
import com.server.impl.Game2;
module A {
exports com.xm;
exports com.server;
provides MyServer with Game2;
//provides MyServer with Game1;
}
import com.server.MyServer;
module B {
requires A;
uses MyServer;
}
测试类
package meng.test;
import com.server.MyServer;
import java.util.ServiceLoader;
public class Demo2 {
public static void main(String[] args) {
//加载服务
ServiceLoader<MyServer> myServers = ServiceLoader.load(MyServer.class);
//遍历服务
for (MyServer ms :
myServers) {
ms.service();
}
}
}
扩充知识
动态语言:
是一类在运行时可以改变其结构的语言:例如新的函数,对象,甚至代码可以被引进,已有的函数可以被删除或是其他的结构上的变化。通俗点说就是运行时代码可以根据某些条件改变自身的结构
动态语言:Object-C ,C#,JavaScript , PHP, Python等
静态语言:
与动态语言相对应,运行时结构不可变的语言就是静态语言。 静态语言: Java,C,C++;
注意:Java 不是动态语言,但是Java被称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活
反射优点:
可以实现动态的创建对象和编译,非常灵活
反射缺点:
对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且,它可以满足我们的要求。这类操作总是慢于直接执行相同的操作(正常new)。
获取Class 对象:
基本内置类型的包装类都有一个Type 属性
Class c1=Integer.TYPE;
获取父类Class对象:
Class c2=c2.getSuperclass();
所有类型的Class对象
package com.xm;
import java.lang.annotation.ElementType;
public class Demo1 {
public static void main(String[] args) {
Class<Object> c1 = Object.class;//类
Class<Comparable> c2 = Comparable.class;//接口
Class<String[]> c3 = String[].class;//一维数组
Class<int[][]> c4 = int[][].class;//二维数组
Class<Override> c5 = Override.class;//注解
Class<ElementType> c6 = ElementType.class;//枚举
Class<Integer> c7 = Integer.class;//基本数据类型
Class<Void> c8 = void.class;//void
Class<Class> c9 = Class.class;//Class本身
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
}
}
下篇文章 Java注解