1,反射机制
- 反射机制有什么作用
- 通过java语言中的反射机制可以操作字节码文件;有点类似于黑客,可以读和修改字节码文件。
- 通过反射机制可以操作代码片段。(class文件)
- 反射机制的相关类在哪个包下?
- java.lang.reflect
- 反射机制相关的重要类:
- java.lang.Class 代表字节码文件 代表一个类型
- java.lang.reflect.Method 代表字节码中的方法字节码
- java.lang.reflect.Constructor 代表字节码中的构造方法字节码
- java.lang.reflect.Field 代表字节码中的属性字节码
- 获取一个类的字节码文件有三种方式
- 1,Class c = Class.forName("完整的类名带包名")
- 2,Class c = 引用/对象.getClass();
- 3,Class c = 任何类型.class();
代码示例
import java.util.Date;
public class ReflectTest {
public static void main(String[] args) {
/*
第一种方式:
Class.forName();
静态方法
方法的参数是一个字符串
字符串需要是一个完整的类名
完整类名必须带有包名
*/
Class c1 = null;
try {
c1 = Class.forName("java.lang.String"); // c1代表String.class字节码文件,或者说代表String类型
Class c2 = Class.forName("java.lang.System");// c2代表Date类型
Class c3 = Class.forName("java.util.Date"); // c3代表Integer类型
Class c4 = Class.forName("java.lang.Integer");// c4代表System类型
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 第二种方式:java对象中都有一个方法:getClass()
// 在方法区中,同一个类型字节码文件只有一个。
String s = "abc";
Class x = s.getClass();// x代表String.class字节码文件,或者说代表String类型。
// 在方法区中,String类型字节码文件只有一个。
System.out.println(c1 == x); // true 说明c1和x的内存地址是一样的
// 第三种方式:java语言中任何一种类型,包括基本数据类型,都有.class属性。
Class z = String.class; // z代表String类型。
Class k = Date.class; // k代表Date类型。
Class f = int.class; // f代表int类型。
Class e = double.class; // e代表double类型。
System.out.println(x == z); // true 说明x和z的内存地址是一样的
}
}
2,通过反射机制实例化对象
- 获取到Class能做什么
- 通过Class的newInstance方法来实例化对象。
- 注意:newInstance内部调用的是无参构造方法,必须保证无参构造方法的存在。
- Class.forName()方法的执行时发生了什么
- Class.forName()方法的执行会导致类加载;类加载时,静态代码块执行。
- 如果你只希望执行一个类的静态代码块,其他代码不执行可以使用它。
代码示例
public class ReflectTest01 {
public static void main(String[] args) {
try {
// 通过反射机制获取Class,通过Class来实例化对象
Class c = Class.forName("com.javaSE.reflects.User");
// newInstance这个方法会调用User类的无参构造方法,完成对象的创建。
// newInstance调用的是无参构造方法,必须保证无参构造方法的存在。
Object obj = c.newInstance();
System.out.println(obj);
// Class.forName()方法的执行会导致类加载
Class.forName("com.javaSE.reflects.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
class User{
public User() {
System.out.println("User无参构造方法");
}
public User(String s) {
System.out.println("User有参构造方法");
}
}
class MyClass{
// 静态代码块在类加载时执行,并且只执行一次。
static {
System.out.println("静态代码块执行了");
}
}
3,验证反射机制的灵活性
java代码写一遍,在不改变java源代码的情况下,可以做到不同对象的实例化,非常灵活。(符合OCP开闭原则:对扩展开发,对修改关闭)
代码示例
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class ReflectTest02 {
public static void main(String[] args) {
// 1,创建一个名为ClassName的properties配置文件,把User类放到配置文件里:className=com.javase.reflects.User
FileReader reader = null;
try {
// 通过IO流读取ClassName.properties文件
reader = new FileReader("src/ClassName.properties");
// 创建属性类对象
Properties pro = new Properties();
// 加载
pro.load(reader);
// 通过key获取value
String cn = pro.getProperty("className");
System.out.println(cn);
// 通过反射机制实例化对象
Class c = Class.forName(cn);
Object obj = c.newInstance();
System.out.println(obj);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
if (reader != null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4,研究一下文件路径问题
怎么获取一个文件的绝对路径,以下讲解的这种方式是通用的,前提:文件需要在类路径下。
代码示例
public class ReflectTest03 {
public static void main(String[] args) throws Exception {
// 这种方式路径缺点:移植性差,在IDEA中默认的当前路径是project的跟。
// 这个代码假设离开了IDEA,换到了其他位置,可能当前路径就是project的跟了,这个时候路径就无效了。
// FileReader reader = new FileReader("src/ClassName.properties");
// 下面说一种比较通用的路径,即使代码位置更改了,仍然是有用的。
// 注意:使用以下通用方式的前提是:这个文件必须在类路径下。
// 什么是类路径?在src下的都是类路径下。src是类的跟路径
/*
解析以下代码:
Thread.currentThread() 当前线程对象。
getContextClassLoader() 是线程对象的方法,获取当前线程的类加载器对象。
getResource() [获取资源]这是类加载器的方法,当前线程的类加载器默认从类的根路径下加载资源。
*/
String className = "ClassName.properties";
// String className = "com/javaSE/reflects/ReflectTest.class";
System.out.println(className);
// 获取绝对路径
String path = Thread.currentThread().getContextClassLoader().
getResource(className).getPath();
// 采用以上的代码可以拿到一个文件的绝对路径
System.out.println(path);
// 把绝对路径传给流
// FileReader reader = new FileReader(path);
// 直接以流的反射返回
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassName.properties");
// --------------------------------------------------------------------------------------------------------------------------
// 创建属性对象
Properties pro = new Properties();
// 加载流
pro.load(reader);
// 关闭流
reader.close();
// 通过key值获取value
String cl = pro.getProperty("className");
System.out.println(cl);
Class c2 = Class.forName(cl);
c2.newInstance();
}
}
5,资源绑定器(ResourceBundle)
- java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
- 使用这种方式的时候,属性配置文件xx.properties必须放到类路径下。
代码示例
import java.util.ResourceBundle;
public class ReflectTest04 {
public static void main(String[] args) throws Exception {
// 资源绑定器 只能绑定xx.properties文件 这个文件必须在类路径下 文件扩展名不用写
ResourceBundle bundle = ResourceBundle.getBundle("ClassName");
// 通过key值获取value
String cl = bundle.getString("className");
System.out.println(cl);
// 获取字节码文件
Class c2 = Class.forName(cl);
c2.newInstance();
}
}
关于JDK中自带的类加载器(了解)
-
什么是类加载器
- 专门负责加载类的命令/工具。ClassLoader
-
JDK中自带了3个类加载器
- 启动类加载器
- 扩展类加载器
- 应用类加载器
-
假设有这样一段代码:String s = "abc";
代码再开始执行之前,会将所需要类全部加载到JVM当中,通过类加载器加载,看到以上代码类加载器会找到Sting.class文件进行加。
-
怎么进行加载的?
- 首先通过启动类加载器加载;
- 启动类加载器专门加载:jdk目录下的/jre/lib/rt.jar
- rt.jar中都是JDK中最核心的类库。
- 如果通过启动加载器加载不到,就会通过扩展类加载器进行加载;
- 扩展类加载器专门加载:jdk目录下的/jre/lib/ext/*.jar
- 如果通过扩展类加载器也加载不到,就会通过应用类加载器进行加载。
- 应用类加载器专门加载:classpath中的jar包。(class文件)
- 首先通过启动类加载器加载;
-
java中为了保证类加载的安全,使用了双亲委派机制
- 优先从启动类加载器中加载,这个称为"父";"父"无法加载到,再从扩展类加载器加载,
这个称为"母";双亲委派如果都加载不到,才会考虑从应用类加载器中加载,直到加载到为止。
- 优先从启动类加载器中加载,这个称为"父";"父"无法加载到,再从扩展类加载器加载,
6,反射属性Field
6.1,通过反射机制获取一个对象的属性(Filed)
Field 翻译为字段,其实就是属性成员
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest06 {
public static void main(String[] args) throws Exception {
// 获取整个类
Class student = Class.forName("com.javaSE.reflects.Student");
System.out.println("完整类名:" + student.getName());
System.out.println("简写类名:" + student.getSimpleName());
// 获取类中所有public修饰的Field
Field[] fields = student.getFields();
System.out.println(fields.length);// 测试数组中只有2个元素
// 取出整个Field
Field f = fields[0];
// 取出整个Field名字
String fieldName = f.getName();
System.out.println(fieldName);
System.out.println("--------------------------------------------------------------------");
// 获取类中所有的Field
Field[] fs = student.getDeclaredFields();
System.out.println(fs.length);// 测试数组中 有5个元素
// 遍历
for (Field field : fs){
// 获取属性名字
System.out.println(field.getName());
// 获取属性类型
Class fieldType = field.getType();
System.out.println(field.getName() + "完整类名:" + fieldType.getName() + ";简写类名:" + fieldType.getSimpleName());
// 获取属性修饰符列表
int mods = field.getModifiers();// 返回的修饰符是一个数字,每个数组是修饰符的代号。
// 将代号数字转换成修饰符
String mos = Modifier.toString(mods);
System.out.println(field.getName() + "修饰符代号:" + mods + ";修饰符是:" + mos);
}
System.out.println("-------------通过反射机制,反编译一个类的属性---------------------------------------------");
// 通过反射机制,反编译一个类的属性Field
fbyStudent();
}
/**
* 通过反射机制,反编译一个类的属性
* @throws ClassNotFoundException
*/
static void fbyStudent() throws ClassNotFoundException {
// 获取整个类
Class student = Class.forName("com.javaSE.reflects.Student");
// Class student = Class.forName("java.lang.Integer");
// 创建一个拼接字符串对象
StringBuilder sr = new StringBuilder();
// 开始拼写
sr.append(Modifier.toString(student.getModifiers()) + " class " + student.getSimpleName() + "{\n");
Field[] fds = student.getDeclaredFields();
for (Field field : fds){
sr.append("\t");
sr.append(Modifier.toString(field.getModifiers()));
sr.append(" ");
sr.append(field.getType().getSimpleName());
sr.append(" ");
sr.append(field.getName());
sr.append(";\n");
}
sr.append("}");
System.out.println(sr);
}
}
// 反射属性Field
class Student{
// Field 翻译为字段,其实就是属性成员
// 4个Field,分别采用不同的访问控制权限修饰符
public int no;
private String name;
protected int age;
boolean sex;
public static final double MIN_PI = 3.1415926;
}
6.2,通过反射机制操作一个对象的属性(Field)
给属性赋值set, 获取属性的值get。
public class ReflectTest07 {
public static void main(String[] args) throws Exception {
// 使用反射机制获取/修改一个对象属性的值
Class cStudent = Class.forName("com.javaSE.reflects.Student");
Object obj = cStudent.newInstance(); // obj就是Student对象。(底层调用无参构造方法)
// 获取no属性 (根据属性的名称获取Field)
Field noField = cStudent.getField("no");
// 给obj对象的no属性赋值
noField.set(obj,1234);
// 读取no属性的值
System.out.println(noField.get(obj));
// 获取私有属性name
Field nameField = cStudent.getDeclaredField("name");
// 打破封装
// 这样设置之后,在外部也可以访问private的
nameField.setAccessible(true);
// 给name属性赋值
nameField.set(obj,"kitty");
// 获取name属性的值
System.out.println(nameField.get(obj));
}
}
上篇:JavaSE进阶十 线程二
下篇:JavaSE进阶十一 反射机制二