反射机制
反射机制概述
1、反射机制是干什么的?有什么用?
答:通过反射机制可以操作字节码文件、操作代码片段(class 文件),可以使程序更加灵活(如:通过修改配置文件来创建不同对象)。
2、反射机制的相关类在哪个包下?重要的类有哪些?
答:在java.lang.reflect.*;
包下。重要的类有java.lang.Class
代表整个字节码(整个类)、一个类型;java.lang.reflect.Method
代表字节码中的方法字节码;java.lang.reflect.Constructor
代表字节码中的构造方法字节码;java.lang.reflect.Field
代表字节码中的属性字节码,代表类中的成员变量(静态变量+实例变量)。
3、获取类的三种方式
方式一:在java.lang.Class
包中的static Class<?> forName(String className)
返回带有给定字符串的类或接口相关联的 Class 对象。(作用将 XXX.class 字节码文件装载到JVM中的方法区内)
/*Class.forName(),静态方法,参数是一个字符串,字符串需要的是一个完整类名(类名必须带有包名),使用该方法要抛异常
*/
try{
Class c = Class.forName("java.lang.String"); //c代表String.class文件,或者代表String类型;作用将String.class字节码文件装载到JVM中的方法区内
}catch (ClassNotFoundException e){
e.printStackTrace();
}
方式二:在java.lang.Object
包下,Class<?> getClass()
返回此 Object 的运行时类。( java 中任何一个对象都有一个 getClass() 方法)
Class c = null;
try{
c = Class.forName("java.lang.String"); //c代表String.class文件,或者代表String类型;作用将String.class字节码文件装载到JVM中的方法区内
}catch (ClassNotFoundException e){
e.printStackTrace();
}
String s = "asd";
Class cc = s.getClass(); //cc代表String.class字节码文件,cc代表String类型
System.out.println(c == cc); //true (==判断对象的内存地址)
c 和 cc 两个对象的内存地址相同,都指向方法区中的字节码文件。
方式三:Java 中任何一种类型,包括基本数据类型都有.class
属性。
Class ccc = String.class; //ccc代表String类型
System.out.println(c == ccc); //true (内存地址相同)
通过反射机制实例化对象
//用户类
public class User{
public User(){
System.out.println("无参构造");
}
}
//测试类
public class ReflectTest {
public static void main(String[] args) {
try {
Class c = Class.forName("com.reflecttest.www.User");
/*newInstance()方法从JDK9之后过时,该方法会调用User类的无参构造
,实例化User对象*/
Object obj = c.newInstance();
//返回:com.reflecttest.www.User@6e8dacdf
System.out.println(obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
当User
类中只有无参构造方法时,结果返回:无参构造,com.reflecttest.www.User@6e8dacdf
当User
类中只有有参构造方法时,报错显示没有无参构造。
通过读属性文件实例化对象
public class ReflectTest2{
public static void main(String[] args) throws Exception{
//通过IO流读取classinfo.properties文件(在当前工程下,与src目录同级)
FileReader reader = new FileReader("classinfo.properties");
//创建属性类对象Map,对应的key和value都是String
Properties p = new Properties();
//加载
p.load(reader);
//关闭流
reader.close();
//通过key获取value
String className = p.getProperty("className");//com.reflecttest.www.User
//通过反射机制实例化对象
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
}
}
classinfo.properties文件:
className=com.reflecttest.www.User
如果修改classinfo.properties
中的类名,可以在不改变 Java 代码的基础上实例化不同的对象,非常灵活。符合OCP开闭原则:对扩展开放,对修改关闭。
只执行静态代码块的内容时可以使用 Class.forName()
:
public class M{
//静态代码块在类加载时执行,并且只执行一次
static{
System.out.println("M类的静态代码块执行");
}
}
public class Test{
public static void main(String[] args){
try{
//类加载
Class.forName("com.reflecttest.www.M");
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}
}
获取类路径下的文件的绝对路径
为什么要用绝对路径?
答:相对路径移植性差(在IDEA中默认的当前路径是 project 的根),如果代码换到其他位置时,这个路径就失效了。所以要使用绝对路径。
通用的方式:适用于任何操作系统
前提:文件必须在类路径下,即放在src
下,src
是类的根路径。
注意:真正的“类的根路径”是out\production\project工程\...\*.class
编译产生的.class
文件(程序执行的是class文件)。
public class Path {
public static void main(String[] args) {
/**
* Thread.currentThread():当前线程对象
* getContextClassLoader():是当前线程对象的方法,可以获取当前线程类加载器对象
* getResource():(获取资源)是类加载器对象的方法,当前线程的类加载器默认从类的根路径下(从src开始)加载资源
* getPath(): 获取路径
*/
String path = Thread.currentThread().getContextClassLoader()
.getResource("com/reflecttest/www/classinfo.properties").getPath();
/*/E:/IdeaProjects/train/out/production/train/classinfo.properties*/
System.out.println(path);
}
}
Thread.currentThread():当前线程对象。
getContextClassLoader():是当前线程对象的方法,可以获取当前线程类加载器对象。
getResource():(获取资源)是类加载器对象的方法,当前线程的类加载器默认从类的根路径下(从src开始)加载资源。
getPath():获取路径。
为什么取绝对路径不能用以下代码?
FileReader reader = new FileReader("E:/IdeaProjects/train/out/production/train/classinfo.properties");
答:该方式只能在 windows 系统上可行,在 Linux 系统不可行(没有盘符)。
public static void main(String[] args) throws Exception{
/* 方式一
//通过IO流读取classinfo.properties文件
//FileReader reader = new FileReader("src/com/reflecttest/www/classinfo.properties");
*/
/* 方式二
//获取路径
String path = Thread.currentThread().getContextClassLoader()
.getResource("com/reflecttest/www/classinfo.properties").getPath();
//通过IO流读取classinfo.properties文件
FileReader reader = new FileReader(path);
*/
// 方式三:以流的形式返回
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/reflecttest/www/classinfo.properties");
//创建属性类对象Map,对应的key和value都是String
Properties p = new Properties();
//加载
p.load(reader);
//关闭流
reader.close();
//通过key获取value
String className = p.getProperty("className");
System.out.println(className);//com.reflecttest.www.User
资源绑定器
java.util
包中提供了一个资源绑定器,便于获取属性配置文件中的内容,属性配置文件xxx.properties
必须放到类路径(src)下(以src为路径起点)。
public static void main(String[] args){
//资源绑定器,写路径的时候扩展名properties不能写
ResourceBundle rb = ResourceBundle.getBundle("com/reflecttest/www/classinfo");
String className = bundle.getString("className");
System.out.println(className);
}
资源绑定器:只能绑定xxx.properties
文件,且该文件必须在类路径(src)下,写路径的时候扩展名不能写。
类加载器
1、什么是类加载器?有哪些类加载器?
答:专门负责加载类的命令 / 工具,ClassLoader。JDK中自带三个类加载器 启动类加载器、扩展类加载器、应用类加载器。
2、举例:
String s = "svda";
代码开始执行前,会将所需要类全部加载到JVM中,通过类加载器加载,看到以上代码类加载器会找String.class
文件进行加载:
首先通过启动类加载器
加载JDK中JRE下的rt.jar
包(除了String.class文件外,还有很多其他文件,都是JDK最核心的类库)例如:C:\jdk11\jre\lib\rt.jar
。
如果通过启动类加载器
加载不到String.class
文件,会通过扩展类加载器
加载,注意扩展类加载器专门加载C:\jdk11\jre\lib\ext
文件夹。
如果通过扩展类加载器
加载不到,则会通过应用类加载器
加载classpath
中的类。
3、双亲委派机制
Java 中为了保证类加载的安全,使用了双亲委派机制,优先从启动类加载器中加载(这个称为父),再从扩展类加载器中加载(这个称为母),如果都加载不到,才会考虑从应用类加载器中加载,直到加载到为止。
获取 Field
类中属性private int id;
是一个 Field 对象,通过 Field 可以拿到private、int、id
class User{
private int id;
public String name;
protected String adress;
boolean sex;
public static final double PI=3.1415926;
}
//测试类
public class ReflectTest04{
public static void main(String[] args){
//获取整个类
Class userClass = Class.forName("com.reflecttest.www.User");
String className = userClass.getName();
System.out.println("完整类名:"+className); //com.reflecttest.www.User
String simpleName = userClass.getSimpleName();
System.out.println("简类名:"+simpleName); //User
//获取类中所有public修饰的Field
Field[] fields = userClass.getFields();
System.out.println(fields.length); //1,只有一个
//取出这个Field
Field f = fields[0];
//获取这个Field的名字
String fieldName = f.getName();
System.out.println(fieldsName);
//获取所有的Field
Field[] fs = userClass.getDeclaerdFields();
System.out.println(fs.length); //4个
//遍历属性
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();
/*String fName = fieldType.getName();*/
//获取简易的属性类型
String fName = fieldType.getSimpleName();
System.out.println(fName);
//获取属性名
System.out.println(field.getName());
}
}
}
反编译 Field:(了解即可)
public class ReflectTest05{
public static void main(String[] args){
//创建可拼接的字符串
StringBuilder s = new StringBuilder();
//获取类名
Class userClass = Class.forName("com.reflecttest.www.User");
s.append(Modifier.toString(userClass.getModifiers())+" class "+ userClass.getSimpleName()+"{\n");
Field[] fields = userClass.getDeclaredFields();
for(Field field : fields){
s.append("\t");
//获取属性修饰符
s.append(Modifier.toString(field.getModifiers()));
s.append(" ");
//获取属性类型
s.append(field.getType().getSimpleName());
s.append(" ");
//获取属性名字
s.append(field.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);
}
}
访问对象属性
使用反射机制访问对象属性(给属性赋值、获取属性的值)。
public class ReflectTest06{
public static void main(String[] args){
//获取类名
Class userClass = Class.foeName("com.reflecttest.www.User");
//实例化类(类似User user = new User();)
Object obj = userClass.newInstance();//底层调用无参构造
//根据属性名称获取Field
Field fieldName = userClass.getDeclaredField("name");
//给obj对象(User对象)的name属性(public修饰)赋值
fieldName.set(obj,"zhang");//类似:user.name = "zhang";
//读取属性的值
System.out.println(fieldName.get(obj));
}
}
通过配置文件可以修改类和类的属性,只需修改配置文件就可以访问不同的类属性,从而实现代码复用。
反射机制让代码复杂了,但使得代码更灵活了。
反射机制缺点:对于private
修饰的属性要打破封装,才能访问。(在外部也可访问private
修饰的存在安全隐患)
//访问私有属性
Field fieldName = userClass.getDeclaredField("id");
//打破封装
fieldName.setAccessible(true);
//给属性赋值
fieldName.set(obj,5);
//获取属性值
System.out.println(fieldName.get(obj));
反射 Method
public class User{
public boolean login(String name,String password){
if("admin".equals(name) && "123".equals(password)){
return true;
}
return false;
}
public void logout(){
System.out.println("系统已退出");
}
}
public class ReflectTest07{
public static void main(String[] args){
Class userClass = Class.forName("com.reflecttest.www.User");
//获取所有的Method(包含私有的)
Method[] methods = userClass.getDeclaredMethods();
//遍历Method
for(Method method : methods){
//获取修饰符
System.out.println(Modifier.toString(method.getModifiers()));
//获取方法的返回值类型
System.out.println(method.getReturnType().getSimpleName());
//获取方法名
System.out.println(method.getName());
//方法的参数(一个方法可能有很多个参数)
Class[] parameterTypes = method.getParameterTypes();
for(Class parameterType : parameterTypes){
System.out.println(parameterType.getSimpleName());
}
}
}
}
通过反射机制调用对象的方法(※)
1、Java 中怎么区分一个方法?
答:通过方法名和形参进行区分。
public static void main(String[] args){
Class userClass = Class.forName("com.reflecttest.www.User");
//实例化对象(User user = new User();)
Object obj = userClass.newInstance();
//获取Method
Method loginMethod = userClass.getDeclaredMethod("login",String.class,String.class);
//调用方法(boolean returnValue = user.login("admin","123");)
Object returnValue = loginMethod.invoke(obj,"admin","123");
System.out.println(returnValue);//true
}
方法getDeclaredMethod()
:
反编译一个类的Constructor构造方法
class User{
int a;
String b;
public User(){}
public User(int a){
this.a = a;
}
public User(int a,String b){
this.a = a;
this.b = b;
}
}
public class ReflectTest10{
public static void main(String[] args){
StringBuilder s = new StringBuilder();
Class userClass = Class.forName("com.reflecttest.www.User");
s.append(Modifier.toString(userClass.getModifiers()));
s.append(" class ");
s.append(userClass.getSimpleName());
s.append("{\n");
//拼接构造方法
Constructor[] constructors = userClass.getDeclaredConstructors();
for(Constructor constructor : constructors){
s.append("\t");//制表符
s.append(Modifier.toString(constructor.getModifiers()));
s.append(" ");
s.append(userClass.getSimpleName());
s.append("(");
//拼接参数
Class[] parameterTypes = constructor.getParameterTypes();
for(Class parameterType : parameterTypes){
s.append(parameterType.getSimpleName());//获取参数类型
s.append(",");
}
//当参数个数大于0,删除最后一个逗号
if(parameterTypes.length > 0){//不做此判断,无参构造方法会少一个括号
s.deleteCharAt(s.length()-1);
}
s.append("){}\n");
}
s.append("}");
System.out.println(s);
}
}
反射机制调用构造方法:
public class ReflectTest11{
public static void main(String[] args){
//不使用反射机制创建对象
User user = new User();
User user = new User(5,"zhang");
//使用反射机制创建对象
Class c = Class.forName("com.reflecttest.www.User");
//调用无参构造方法创建对象
Object obj1 = c.newInstance();
//调用有参构造方法创建对象
//一:获取有参构造方法
Constructor con = c.getDeclaredConstructor(int.class,String.class);
/*获取无参构造方法
Constructor con = c.getDeclaredConstructor();
Object obj3 = con.newInstance();
System.out.println(obj3);
*/
//二:调用构造方法new对象
Object obj2 = con.newInstance(5,"zhang");
System.out.println(obj2);
}
}
获取父类和父接口
如何获取一个类的父类,以及该类实现了哪些接口?
public class ReflectTest11{
public static void main(String[] args){
//以String为例
Class stringClass = Class.forName("java.lang.String");
//获取String的父类
Class superClass = stringClass.getSuperclass();
System.out.println(superClass.getName());
//获取String类实现的所有接口
Class[] interfaces = stringClass.getInterfaces();
for(Class inter : interfaces){
System.out.println(inter.getName());
}
}
}