JavaSE进阶650-667 反射(一) forName/访问属性

开始时间: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原则- 对扩展开放,对修改关闭

参考properties

配置文件

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");

所以全部写出来,可以这样表示

复习下StringBuilder

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值