JAVA 反射与模块化
类加载
类的初始化步骤
- 假如类还未被加载和连接,则程序现在加载并连接该类
- 假如该类的直接父类还未被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
注意:在执行第二个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3
- 总是最先加载Object类
类的初始化时机
- 创建类的实例
- 调用类的类方法
- 访问类或者接口的类变量,或者为该变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
类加载器
反射
反射概述
- Java反射机制
- 是指在运行时去获取一个类的变量和方法信息,然后通过获取到的信息来创建对象,调用方法的一种机制,由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展
获取Class类的对象
-
我们想要通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class类型的对象
-
这里提供3种方式获取Class类型的对象
- 使用类的class属性来获取该类对应的Class对象,例如 Student.class将会返回Student类对应的class对象
- 调用对象的getClass()方法,返回该对象所属类对应的Class对象,该方法是Object类中的方法,所有的Java对象都可以调用该方法
- 使用Class类中的静态方法forName(String className),该方法需要传入字符串数据,该字符串参数的值是某个类的全路径,也就是完整包名的路径
本节使用的Student类
public class Student {
//成员变量,姓名私有,id公共,年龄默认
public int id;
private String name;
int age;
//构造方法:一个私有,一个默认,两个公共
public Student() {
}
private Student(String name) {
this.name = name;
}
Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
//成员方法:一个私有,四个公共
private void function() {
System.out.println("function");
}
public void method1() {
System.out.println("method");
}
public void method2(String s) {
System.out.println("method" + s);
}
public String method3(String s, int i) {
return s + "," + i;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) throws ClassNotFoundException {
//使用类的class属性来获取该类对应的Class对象
Class<Student> c1 = Student.class;
System.out.println(c1);//class com.fanshe.Student
Class<Student> c2 = Student.class;
System.out.println(c1 == c2);//返回true,说明一个类在内存里只有一个字节码文件对象
System.out.println("==================");
//调用对象的getClass()方法,返回该对象所属类对应的Class对象
Student s = new Student();
Class<? extends Student> c3 = s.getClass();
System.out.println(c3);
System.out.println(c1==c3);//true,结果同上
System.out.println("================");
//使用Class类中的静态方法forName(String className)
Class<?> c4 = Class.forName("com.fanshe.Student");//可以把字符串的类数据配置到配置文件中,可以随时修改配置文件内容
System.out.println(c4);
System.out.println(c1==c4);//同上,这种方法灵活性最高,第一种方法最方便
}
反射获取构造方法并使用
- 反射要做的就是:通过Class类的构造方法四个其中一个获得指定的类的对象,再利用指定类的对象创建类中构造器的对象,最终目的和new Student()类的对象,再获取这个类使用的构造器方法的对象
- 反射机制在以后维护,写代码有着很重要的帮助。开发程序一直强调减少耦合性(是指一程序中,模块及模块之间信息或参数依赖的程度),而如果直接用new创建新的方法那么久减少不了耦合,通过这个反射方法能调用某个包内的方法,这样就能减少耦合性
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//反射获取构造方法并使用
//获取class对象
Class<?> c = Class.forName("com.fanshe.Student");
//Constructor<?>[] getConstructors()
//返回一个Constructor对象的数组,类对象表示的类的公共public构造函数。
Constructor<?>[] cons = c.getConstructors();
for (Constructor<?> con : cons) {
System.out.println(con);//只返回了public方法
}
System.out.println("======================");
//Constructor<?>[] getDeclaredConstructors()
//返回反射所有由该Class对象表示的类声明的所有构造函数的constructor对象数组。
Constructor<?>[] cons1 = c.getDeclaredConstructors();
for (Constructor<?> con : cons1) {
System.out.println(con);//返回了所有构造方法
}
System.out.println("======================");
//Constructor<T> getConstructor(类<?>... parameterTypes)
//返回一个 Constructor对象反映指定的公共构造函数的类对象表示的类。
//Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
//返回一个 Constructor对象反映指定的构造函数的类或接口的类对象表示。
//参数:你要获取的构造方法的参数的个数和数据类型对应的字节码文件对象
Constructor<?> conn = c.getConstructor();
//Constructor提供了一个类的单个构造函数的信息和访问权限
//T newInstance(Object... initargs)
//利用这 Constructor对象创建和初始化的构造函数的声明类的一个新实例构造函数,用指定的初始化参数。
Object obj = conn.newInstance();
System.out.println(obj);
}
}
反射获取构造方法并使用练习
练习1
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
/*
通过反射实现如下的操作
Student s = new Student("001","xuzhiyuan","24");
System.out.println(s);
*/
//获取class对象
Class<?> c = Class.forName("com.fanshe.Student");
//Constructor<T> getConstructor(类<?>... parameterTypes)
//返回一个 Constructor对象反映指定的公共构造函数的类对象表示的类。
Constructor<?> con = c.getConstructor(int.class, String.class, int.class);
//基本数据类型也可以通过.class得到对应的Class类型
Object obj = con.newInstance(001, "林青霞", 30);
System.out.println(obj);
}
- 基本数据类型也可以通过.class得到对应的Class类型
练习2
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取class对象
Class<?> c = Class.forName("com.fanshe.Student");
//private Student(String name)←要调用的目标构造方法
//Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
Constructor<?> con = c.getDeclaredConstructor(String.class);
//由于私有的构造方法虽然获取了对象但是无法访问创建对象,但反射可以做到
//暴力反射
con.setAccessible(true);//输入的值为true则取消访问检查
Object obj = con.newInstance("林青霞");
System.out.println(obj);
}
- public void setAccessible(boolean flag):值为true,取消访问检查(可以访问private)
反射获取成员变量并使用
以前的方法是“现有对象再对其成员变量赋值”,而反射是“先有成员变量再对拥有它的对象赋值”
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取class对象
Class<?> c = Class.forName("com.fanshe.Student");
//Field[] getFields()
//返回一个数组包含 Field物体反射的类或接口的 类对象代表所有可访问的公共领域
//Field[] getDeclaredFields()
//返回 Field物体反射所有字段的类或接口的 类对象表示声明数组。
Field[] f1 = c.getFields();//返回公共成员变量的数组
Field[] f2 = c.getDeclaredFields();//返回所有公共成员变量数组
for (Field field : f2) {
System.out.println(field);
}
System.out.println("===============");
//Field getField(String name)
//返回一个 Field对象反映的类或接口的 类对象表示的指定公共成员。
//Field getDeclaredField(String name)
//返回一个对象,反映了 Field指定声明字段的类或接口的 类对象表示。
Field c3 = c.getField("id");
//获取无参构造方法创造对象
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
//Field提供有关类和接口的单个字段的信息和动态访问
c3.set(obj,155);//给obj的成员变量id赋值为001
//以前的方法是“现有对象再对其成员变量赋值”,而反射是“先有成员变量再对拥有它的对象赋值”
System.out.println(obj);
}
- 私有成员变量需要使用setAccessible(true)暴力反射
反射获取成员方法并使用
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取class对象
Class<?> c = Class.forName("com.fanshe.Student");
//Method[] getMethods()
//返回一个数组包含 方法物体反射所有的类或接口的 类对象表示的公共方法,包括那些由类或接口的超类和超接口继承的声明。
//Method[] getDeclaredMethods()
//返回一个数组包含 方法物体反射所有声明的方法的类或接口的 类对象,代表包括公众、保护,默认(包)的访问,和私有方法,但不包括继承的方法。
// for (Method method : c.getMethods()) {
// System.out.println(method);
// }返回的方法包含父类的公共方法
for (Method declaredMethod : c.getDeclaredMethods()) {
System.out.println(declaredMethod);
}//返回所有成员方法
//Method getMethod(String name, 类<?>... parameterTypes)
//返回一个 方法对象反映的类或接口的 类对象表示的指定公共成员方法。
//Method[] getDeclaredMethods()
//返回一个数组包含 方法物体反射所有声明的方法的类或接口的 类对象,代表包括公众、保护,默认(包)的访问,和私有方法,但不包括继承的方法。
//public void method1()
Method m1 = c.getMethod("method1");
//获取无参构造器对象
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
//Object invoke(Object obj, Object... args)
//调用底层的方法,这方法对象表示,对指定对象的指定参数。
//invoke前面的Object:返回值类型
m1.invoke(obj);
}
练习
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取class对象
Class<?> c = Class.forName("com.fanshe.Student");
//Student s = new Student;
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
//s.method1();
Method m1 = c.getMethod("method1");
m1.invoke(obj);
System.out.println("==========================");
//s.method2("xuzhiyuan");
Method m2 = c.getMethod("method2", String.class);
m2.invoke(obj,"xuzhiyuan");
System.out.println("=========================");
//String ss = s.method3("xuzhiyuan",30);
Method m3 = c.getMethod("method3", String.class, int.class);
Object ss = m3.invoke(obj, "xuzhiyuan", 30);
System.out.println(ss);
System.out.println("=========================");
//s.function();
Method f = c.getDeclaredMethod("function");
f.setAccessible(true);
f.invoke(obj);
}
反射练习之越过泛型检查
- 我有一个ArrayList
集合,想要在这个集合中添加一个字符串数据,如何实现
反射练习之运行配置文件指定内容
- 通过配置文件运行类中的方法
配置文件class.txt
- className=xxx 可以替换xxx
- methodName=xxx 可以替换xxx
public class Aminal {
public void eat(){
System.out.println("吃东西");
}
}
public class People {
public void see(){
System.out.println("看东西");
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
// People p = new People();
// p.see();
//
//
// Aminal a = new Aminal();
// a.eat(); 如果需求更变需要来回修改主方法中的代码
//可以通过反射配置文件就可以不用反复动主方法的代码
/*
class.txt
className=xxx
methodName=xxx
*/
//加载数据
Properties prop = new Properties();
FileReader fr = new FileReader("class.txt");
prop.load(fr);
fr.close();
//根据键获得对应的值
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
//通过反射来使用
Class<?> c = Class.forName(className);//com.fanshe.People
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
Method see = c.getMethod(methodName);
see.invoke(obj);
}
模块化(JDK9)
模块化概述
模块的基本使用
-
创建2个模块,myOne,myTwo
-
在模块的src目录下新建一个名为module-info.java的描述性文件,该文件专门定义模块名,访问权限,模块依赖等信息,描述性文件中使用模块导出和模块依赖来进行配置并使用(Jdk9才能使用)