开始时间:2021-02-13
反射机制
- 反射机制有什么用?
通过java语言中的反射机制可以操作字节码文件。优点类似于黑客-(可以读和修改字节码文件-)通过反射机制可以操作代码片段-(class文件-) - 反射机制的相类在哪个包下?
java . lang . reflect.*; - 反射机制相关的重要的类有哪些?
java.lang.class:代表整个字节码,代表一个类型
java. lang.reflect.Method:代表字节码中的方法字节码。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码java.lang.reflect.Field:代表字节码中的属性字节码。
java.lang.class:
public class User{
//Field
int no ;
//constructor
public User{
}
public user (int no){
this.no = no ;
}
获取字节码文件
三种方法
- Class c=Class.forName(“完整类名”);
- Class c=对象.getClass();
- Class c=int.class;
- Class c=String.class;
package BUPT20210209;
public class ReflectTest01 {
public static void main(String[] args) throws ClassNotFoundException {
//Class要大写,是类名
//取出String这个类
/*
Class.forName是个静态方法,传参传字符串
参数要完整类名,包括前面的java.lang这个部分
*/
//方法1 调用forName
Class c1 = Class.forName("java.lang.String");
Class c2 = Class.forName("java.lang.System");
//方法2 调用getClass
String s = "abc";
//x取的也是字节码文件string.class
Class x = s.getClass();
System.out.println(x == c1);
//方法3 调用基本数据类型的class属性
Class z = String.class;
System.out.println(z == x);
}
}
使用/不使用反射生成对象
package BUPT20210209;
public class ReflectTest02 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//复制带有包的类名,需要 alt+shift+ctrl+c
//通过反射机制,获取Class,通过Class来实例化对象
//copy reference
Class c = Class.forName("BUPT20210209.User");
//c代表user类型,再用c去创建实例,调用无参构造方法
Object obj = c.newInstance();
System.out.println(obj);//成功构建了一个对象:BUPT20210209.User@7ef20235
//不使用反射机制
User user = new User();
System.out.println(user);
}
}
使用反射会更加灵活,能够通过读取不同的配置文件,生成对应的对象实例化
配置什么文件,就创建什么对象
符合OCP原则- 对扩展开放,对修改关闭
配置文件
className=BUPT20210209.User
程序代码
package BUPT20210209;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class ReflectTest03 {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
//IO流读文件
FileReader reader = new FileReader("F:\\编程学习\\Java学习\\BUPT20210209\\classinfo.properties");
//创建属性类对象
Properties pro = new Properties();
//加载流
pro.load(reader);
reader.close();
//通过key获得value
String classname = pro.getProperty("className");
System.out.println(classname);
//通过反射机制实例化对象
Class c = Class.forName(classname);
Object obj = c.newInstance();
System.out.println(obj);
}
}
改一下配置文件
className=java.util.Date
输出也不同
forName的应用
forName可以只让静态代码块加载,其他代码不执行
Class.forName(“完整类名”)
JDBC会用这个
package BUPT20210209;
public class ReflectTest04 {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("BUPT20210209.MyClass");
}
}
package BUPT20210209;
public class MyClass {
//静态代码块只在类加载的时候执行一次
static {
System.out.println("MyClass类的静态代码块执行");
}
}
获取文件绝对路径
按理说可以通过右键,复制文件的绝对路径,然后填充进去
但事实上,是一个package下的代码,可能会移植到Linux系统上,这个系统上就没有C盘这些路径名了,所以必须通过代码来获取路径才保险。
- Thread.currentThread()当前线程对象
- getContextClassLoader()是线程对象的方法,可以获取到当前线程的类加载器对象。
- getResource()【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
package BUPT20210214;
public class AboutPath {
public static void main(String[] args) {
//这种方式获取必须是放在同一个src下面
//写路径的时候必须从根路径(src)下面开始写相对路径名
String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo.properties").getPath();
System.out.println(path);
String path2 = Thread.currentThread().getContextClassLoader()
.getResource("BUPT20210209/MyClass.Class").getPath();
System.out.println(path2);
}
}
再来看一个例子
package BUPT20210214;
import java.io.FileReader;
import java.util.Properties;
public class PathTest {
public static void main(String[] args) throws Exception {
String path=Thread.currentThread().getContextClassLoader()
.getResource("classinfo.properties").getPath();
System.out.println(path);
FileReader fileReader=new FileReader("F:\\编程学习\\Java学习\\BUPT20210209\\src\\classinfo.properties");
Properties properties=new Properties();
properties.load(fileReader);
fileReader.close();
String className=properties.getProperty("className");
System.out.println(className);
}
}
这里本来应该通过把path传进去完成文件读写,但是因为传进去的是带有中文的路径名,会错误的转码,所以此路不通
只能完整的写出路径名
稍微修改一下,通过直接用流进行返回
package BUPT20210214;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
public class PathTest {
public static void main(String[] args) throws Exception {
/*
String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo.properties").getPath();
System.out.println(path);
// FileReader fileReader = new FileReader("F:\\编程学习\\Java学习\\BUPT20210209\\src\\classinfo.properties");
*/
//如果直接通过流的方式直接获取,那么可以正常用
InputStream fileReader = Thread.currentThread().getContextClassLoader().getResourceAsStream("classinfo.properties");
Properties properties = new Properties();
properties.load(fileReader);
fileReader.close();
String className = properties.getProperty("className");
System.out.println(className);
}
}
资源绑定器
快速绑定属性资源文件
package BUPT20210214;
import java.util.ResourceBundle;
public class ResourceBundleTest {
public static void main(String[] args) {
//资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。
//并且后缀名也不写
ResourceBundle resourceBundle = ResourceBundle.getBundle("BUPT20210214/classinfo2");
String name = resourceBundle.getString("className");
System.out.println(name);
}
}
类加载器
负责加载类的命令/工具包
classloader
JDK中自带了3个类加载器
- 启动类加载器
- 扩展类加裁器
- 应用类加裁器
假设有这样一段代码:
string s =“abc” ;
代码在开始执行之前,会将所需要类全部加载到JVM当中。通过类加载器加载,看到以上代码类加载器会找string.class文件,找到就加载。
首先通过"启动类加载器"加裁。
注意:启动类加载器专门加载: …\jre\lib\rt.rt.jar中都是JDK最核心的类库.
如果通过"启动类加载器"加载不到的时候,会通过扩展类加载器"加载
注意:扩展类加载器专门加载,
如果"扩展类加载器"没有加载到,那么
会通过"应用类加载器"加载
注意:应用类加裁器专门加载: classpath中的类(最早安装Java配置的)。
为了保证类加载的安全,采用了双亲委派机制
启动类为“父” 扩展类称为“母”
先父后母,最后应用类加载器
获取Field
package BUPT20210218;
public class Student {
public boolean sex;
String length;
public String name;
private int age;
protected String no;
}
package BUPT20210218;
import java.lang.reflect.Field;
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException {
//包名.类名
Class studentClass = Class.forName("BUPT20210218.Student");
//取出的是一个数组
Field[] fields = studentClass.getFields();
Field[] fields1=studentClass.getDeclaredFields();
System.out.println(fields.length);
for (Field f : fields
) {
System.out.println("fields取出了"+f.getName());
}
for (Field f : fields1
) {
System.out.println("fields1取出了"+f.getName());
}
}
}
2
fields取出了sex
fields取出了name
fields1取出了sex
fields1取出了length
fields1取出了name
fields1取出了age
fields1取出了no
Process finished with exit code 0
进一步去探究一下这个代码
Class studentClass = Class.forName("BUPT20210218.Student");
//本身就是一个类,所以直接对他进行操作,取出name属性
String s=studentClass.getName();
//可以定义获取完整类名还是简写名
String s1=studentClass.getSimpleName();
System.out.println(s+"=="+s1);
获取类型
for (Field f : fields
) {
System.out.println("fields取出了"+f.getType());
}
取出修饰符代号
Field[] fields1 = studentClass.getDeclaredFields();
System.out.println(fields.length);
for (Field f : fields1
) {
int i = f.getModifiers();
//返回修饰符的代码号
System.out.println("序号"+i+"代表的是"+Modifier.toString(i));
//调用toString,把代码号转为对应的字符串
System.out.println("fields取出了" + f.getType());
}
反编译Field
反编译这个Student类
package BUPT20210218;
public class Student {
public boolean sex;
String length;
public String name;
private int age;
protected String no;
}
首先要取出的是
public class 以及 Student
//拿出public
Modifier.toString(studentClass.getModifiers())
自己动手写class
s.append(" " + "class" + " ");
取出Student类的简写名
s.append(studentClass.getSimpleName());
再取出 空格 public boolean sex 以及分号
s.append("\t" + Modifier.toString(i) + " ");
s.append(f.getType().getSimpleName());
s.append(" " + f.getName() + ";\n");
所以全部写出来,可以这样表示
package BUPT20210218;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest02 {
public static void main(String[] args) throws ClassNotFoundException {
//包名.类名
Class studentClass = Class.forName("BUPT20210218.Student");
//用Builder即可,这里不涉及线程安全,存储字符串
StringBuilder s = new StringBuilder();
s.append(Modifier.toString(studentClass.getModifiers()));
s.append(" " + "class" + " ");
s.append(studentClass.getSimpleName());
s.append("{" + "\n");
Field[] fields = studentClass.getDeclaredFields();
for (Field f : fields
) {
int i = f.getModifiers();
//返回修饰符的代码号
s.append("\t" + Modifier.toString(i) + " ");
s.append(f.getType().getSimpleName());
s.append(" " + f.getName() + ";\n");
}
s.append("\n}");
System.out.println(s);
}
}
输出:
给不同的class,都可以得到结果
Class studentClass = Class.forName("java.lang.String");
public final class String{
private final byte[] value;
private final byte coder;
private int hash;
private boolean hashIsZero;
private static final long serialVersionUID;
static final boolean COMPACT_STRINGS;
private static final ObjectStreamField[] serialPersistentFields;
public static final Comparator CASE_INSENSITIVE_ORDER;
static final byte LATIN1;
static final byte UTF16;
}
Process finished with exit code 0
通过反射机制访问对象属性
package BUPT20210218;
import java.lang.reflect.Field;
public class ReflectTest03 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//不使用反射机制,对属性进行赋值可以通过实例化对象实现
Student student = new Student();
student.no = "Zhangsan";
System.out.println(student.no);
//使用反射机制来访问属性
Class studentClass = Class.forName("BUPT20210218.Student");
//同样得到Student对象
Object obj = studentClass.newInstance();
Field field = studentClass.getDeclaredField("no");
//给obj的"no"属性赋值
field.set(obj, "Lisi");
//主要区别是,点号前面的不是对象,而是属性
System.out.println(field.get(obj));
}
}
对私有变量的访问
Field field = studentClass.getDeclaredField("age");
//给obj的"age"属性赋值,因为age是私有变量
//必须打破封装才能访问到,这也是反射机制的缺点
field.setAccessible(true);
field.set(obj, 15);
System.out.println(field.get(obj));
2021-02-19