学习笔记丨初学Java的反射机制!
反射机制
了解反射机制,首先要知道反射机制的作用:通过java语言中的反射机制可以操作字节码文件。
反射机制比new的方式更加灵活,利用反射机制可以在不改变java源代码的基础之上,做到不同对象的实例化。(符合OCP开闭原则:对扩展开放,对修改关闭)
而new只能创建固定的一种对象,每一次修改都要对源文件进行重新编译。此外, 高级框架的底层实现原理都采用了反射机制。
反射机制相关的类: java.lang.reflect.*
- java.lang.Class :代表整个字节码,代表整个类;
- java.lang.reflect.Method:代表字节码中的方法字节码,代表类中的方法;
- java.lang.reflect.Constructor:代表字节码中的构造方法字节码,代表类中的构造方法;
- java.lang.reflect.Field:代表字节码中的属性字节码,代表类中的属性。
反射机制的灵活性验证
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
/*
利用反射机制可以通过修改配置文件的参数,在不改变java源代码的基础之上,做到不不同对象的实例化,这就是反射机制灵活性的体现。
*/
public class ReflectTest03 {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
FileReader reader = new FileReader("D:\\WorkPlace\\java\\chapter23\\src\\com\\ptu\\javase\\reflect\\classInfo.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
String className = pro.getProperty("className");
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
}
}
获取类的字节码(Class)
获取类名:getName() / getSimpleName
- 第一种方式:Class c = Class.forName(“完整类名带包名”);
- 第二种方式:Class c = 引用.getClass();
- 第三种方式:利用Java中任何一种类型(包括基本数据类型)都有的.class属性
public class ReflectTest01 {
public static void main(String[] args) {
/*
Class.forName()
1. 静态方法
2. 参数是字符串
3. 字符串需要的是一个完整类名
4. 完整类名必须带有包名,java.lang也不能省略
*/
Class c1 = null;
try {
// c1代表String.class文件
c1 = Class.forName("java.lang.String");
// c2代表Date.class文件
Class c2 = Class.forName("java.util.Date");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// java中任何一个对象都有一个getClass
String s = "abc";
Class x = s.getClass();
System.out.println(x == c1); // true
// 第三种方式:java中任何一种类型,包括基本数据类型,都有.class属性
Class z = String.class;
Class y = int.class;
Class c = Integer.class;
System.out.println(z); // class java.lang.String
System.out.println(y); // int
System.out.println(c); // class java.lang.Integer
}
}
关于Class.forName()
Class.forName()这个方法的执行会导致:类加载。如果只是希望一个类的静态代码块执行,其他代码一律不执行,可以使用Class.forName(“完整类名”)。
提示:后面JDBC会使用到这个技术。
public class ReflectTest04 {
public static void main(String[] args) {
try {
// Class.forName()这个方法的执行会导致:类加载。
Class.forName("chapter23.src.com.ptu.javase.reflect.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass{
// 静态代码块在类加载的时候一定会执行,并且只执行一次
static {
System.out.println("这是一个静态代码块的内容!");
}
}
通过字节码文件创建对象(newInstance)
newInstance() 这个方法是通过调用类的无参构造,完成对象的创建,所以必须保证类中存在无参构造方法!
public class ReflectTest02 {
public static void main(String[] args) {
try {
// 通过反射机制获取Class,通过Class来实例化对象
Class c = Class.forName("chapter23.src.com.ptu.javase.reflect.bean.User");
// c就代表User类型
// newInstance() 这个方法会调用User类的无参构造,完成对象的创建
// 所以必须保证无参构造是存在的!
Object obj = c.newInstance();
System.out.println(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
public class User {
public User() {
System.out.println("这是一个无参构造!");
}
}
通过字节码文件获取对象属性(Field)
获取思路:先获取类(Class) —> 利用Class的方法获取Field(getField/getDeclaredField)(getField/getDeclaredField方法的参数中可以写属性的名字,根据属性的名称来获取Field,例子见“通过字节码文件修改对象属性”)
注意:在获取属性的修饰符列表时,使用getModifiers()返回的是修饰符的代号,即返回值为int类型,可以使用Modifier.toString()将代号转为字符串,然后打印输出即可。
获取类名的方法:getType()
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest05 {
public static void main(String[] args) throws ClassNotFoundException {
// 获取整个类
Class studentClass = Class.forName("chapter23.src.com.ptu.javase.reflect.Student");
String className = studentClass.getName();
String simpleName = studentClass.getSimpleName();
System.out.println("类名:" + className + ";简单类名:" + simpleName); // 类名:chapter23.src.com.ptu.javase.reflect.Student;简单类名:Student
// 获取类中所有的 【public修饰的】 Field
Field[] fields = studentClass.getFields();
System.out.println(fields.length); // 测试结果:数组中只有一个元素
// 取出这个field
Field f = fields[0];
// 取出field的名字
String fieldName = f.getName();
System.out.println(fieldName); // no
// 获取所有的Field
Field[] fs = studentClass.getDeclaredFields();
System.out.println(fs.length); // 4
System.out.println("----------------------------");
// 遍历
for (Field field:fs){
// 获取属性的修饰符列表
int i = field.getModifiers(); // 返回的修饰符是一个数字,每个数字是修饰符的代号
System.out.println(i);
// 可以将这个代号转化为字符串
String modifierString = Modifier.toString(i);
System.out.println(modifierString);
// 获取类名
Class fieldType = field.getType(); // 返回一个Class
String fieldName1 = fieldType.getName(); // 通过Class getName,这里也可以getSimpleName
System.out.println(fieldName1);
// 获取属性的名字
System.out.println(field.getName());
}
}
}
这里还有一个例子,是利用反射机制,反编译一个类的属性Field
// 通过反射机制,反编译一个类的属性Field
public class ThreadTest06 {
public static void main(String[] args) throws ClassNotFoundException {
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("chapter23.src.com.ptu.javase.reflect.Student");
s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");
Field[] fields = studentClass.getDeclaredFields();
for (Field f:fields){
s.append("\t");
s.append(Modifier.toString(f.getModifiers()));
s.append(" ");
s.append(f.getType().getSimpleName() + " ");
s.append(f.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);
}
}
通过字节码文件修改对象属性(Field)
/*
通过反射机制获取、修改对象的属性值
*/
public class ReflectTest07 {
public static void main(String[] args) throws Exception {
// 不使用反射机制创建对象
Student s = new Student();
// 不使用反射机制给对象的属性赋值
s.no = 0012;
// 使用反射机制创建对象
Class studentClass = Class.forName("chapter23.src.com.ptu.javase.reflect.Student");
Object obj = studentClass.newInstance();
// 获取no属性(根据属性的名称来获取Field)
Field noField = studentClass.getDeclaredField("no");
// 使用反射机制给obj对象的no属性赋值
noField.set(obj,0041);
// 读取属性的值
noField.get(obj);
// 还是不能直接访问私有属性
Field nameField = Class.forName("chapter23.src.com.ptu.javase.reflect.Student").getDeclaredField("name");
// 访问私有属性,需要打破私有属性的封装
nameField.setAccessible(true); // 缺点:打破封装可能给不法分子留下机会
nameField.set(obj,"Tom");
System.out.println(nameField.get(obj));
// 直接访问私有属性会报错:cannot access a member of class with modifiers "private"
}
// 反射属性Field
public class Student {
// Field翻译为字段,其实就是属性/成员
// 4个Field分别采用不同控制权限修饰
private int age;
public int no;
protected boolean sex;
private String name;
public static final double MATH_PI = 3.1415;
}
通过字节码文件获取类的方法(Method)
获取方法的思路:获取Class —> 通过Class获取Method(getDeclaredMethods)
注意:获取方法的修饰符列表、返回值类型、方法名、参数列表分别对应Method类的getModifiers() 、getReturnType() 、getName() 、getParameterTypes()。(其实就是英文名字对应上而已)
public class ReflectTest08 {
public static void main(String[] args) throws Exception{
Class userClass = Class.forName("chapter23.src.com.ptu.javase.reflect.UserService");
Method[] methods = userClass.getDeclaredMethods();
for (Method m : methods){
// 获取修饰符列表
System.out.println(Modifier.toString(m.getModifiers()));
// 获取方法的返回值类型
System.out.println(m.getReturnType());
// 获取方法名
System.out.println(m.getName());
// 获取方法的参数列表
Class[] parameterType = m.getParameterTypes();
for (Class c:parameterType){
System.out.println(c.getSimpleName());
}
}
}
}
通过字节码文件调用对象的方法
调用方法的四要素:返回值类型,引用,方法名,实参
思路:创建对象 —> 获取对象的方法(参照上一步如何获取Method)—> 调用Method中的invoke方法
public class ReflectTest09 {
public static void main(String[] args) throws Exception{
Class userServiceClass = Class.forName("chapter23.src.com.ptu.javase.reflect.UserService");
// 第一步:先创建对象
Object obj = userServiceClass.newInstance();
// 第二步:获取对象的方法
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
// 调用方法----四要素:返回值类型,引用,方法名,实参
Object retValue = loginMethod.invoke(obj,"admin","1234");
System.out.println(retValue);
}
}
通过字节码文件获取类的父类和已实现接口
获取类的父类思路: 获取类的Class —> 调用Class的getSuperClass()即可获取类的父类
获取已实现的接口思路:获取类的Class —> 调用Class的getInterfaces() (返回值为Class[] )
public class ThreadTest10 {
public static void main(String[] args) throws Exception{
Class stringClass = Class.forName("java.lang.String");
// 获取String的父类
Class superClass = stringClass.getSuperclass();
String superName = superClass.getName();
System.out.println(superName);
// 获取类实现的接口
Class[] interfaces = stringClass.getInterfaces();
for (Class in:interfaces){
System.out.println(in.getName());
}
}
}
补充知识
可变长度参数
可变长度参数——格式:int… args
1. 可变长度参数要求的参数个数是:0-N个
2. 可变长度参数必须在参数列表的最后一个,且只能有一个
3. 可变长度参数可以当做是一个数组,具有数组的属性
public class ArgsTest {
public static void main(String[] args) {
m(1,2,3);
System.out.println("------------------------------");
// 也可以传一个数组
int[] array = {1,2,3,5};
m(array);
}
public static void m(int... args){
System.out.println("我要把我的参数列表遍历出来:");
for (int i = 0; i < args.length; i++) {
System.out.println(i);
}
}
}
Java中的文件路径问题
在使用Java的反射机制时,如果都填文件/包的相对路径,会有一个缺点是:移植性差,因为在IDEA中相对路径的默认当前路径是Project的根,假设这个代码离开了IDEA,换到了其它位置,到时的那个路径不是Project的根,此时这个路径就无效了。
FileReader reader = new FileReader("相对路径");
而我们需要的是:即使代码换位置了,这样编写仍然是通用的,方法如下:
import java.io.FileNotFoundException;
import java.io.FileReader;
/*
关于文件路径的问题
*/
public class AboutPath {
public static void main(String[] args) throws FileNotFoundException {
// 前提:这个文件必须在类路径下。(即:src是类的根路径,凡是在src目录下的文件,就是在类路径下)
/*
getContextClassLoader()是线程对象的方法,可以获取到当前线程的类加载器对象
getResource是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
getResource("")可以获取当期的根目录
*/
String path = Thread.currentThread().getContextClassLoader().getResource("chapter23/src/Info.properties").getPath();
System.out.println(path);
}
}
资源绑定器
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容,这种方式只能绑定属性配置文件(.properties),且属性配置文件必须放到类路径下,同时,写路径时不允许写入文件扩展名。
public class ResourceBundleTest {
public static void main(String[] args) {
// 资源绑定器,只能绑定.properties文件,并且这个文件必须在类路径下
// 而且,写路径的时候,不能写路径后面的扩展名
ResourceBundle bundle = ResourceBundle.getBundle("chapter23/src/Info");
String className = bundle.getString("className");
System.out.println(className);
}
}
本文地址:https://blog.csdn.net/qq_45250341/article/details/107690293