day24_java的反射机制

发布时间:2023年12月18日

反射

一、反射的概念

反射:加载类,反射出类的各个组成部分(类的成员:构造方法,属性,方法)

java反射机制:在运行状态中,对于任何一个类都能够知道这个类的所有属性和方法;对于任意一个对象,能够调用它的任意属性和方法;这种动态获取信息的方式就称为反射。

二、加载类???怎么加载

当程序要使用某个类时,如果这个类没有加载到内存中,则系统会通过加载,连接,初始化三个步骤来实现对这个类的初始化。

  1. 加载:

    将class(字节码)文件读取到内存中,并为之创建一个Class对象

    任何类被使用时都会被创建一个Class对象(注:一个类只有一个Class对象

  2. 连接:
    • 验证:是否有正确的内部结构,并和其它协调一致
    • 准备:负责为类的静态成员分配内存,并设置默认初始化
    • 解析:将类的二进制数据中的符号引用替换成直接引用
  3. 初始化:

    该阶段主要是为类的类变量初始化值的,初始化有两种方式:

    • 在声明类变量时,直接给变量赋值
    • 在静态初始化块为类变量赋值
(一)类加载的时机
  1. 创建类的实例
  2. 访问类的静态成员或给静态变量赋值
  3. 调用类的吧静态方法
  4. 初始化某个类的子类
  5. java命令运行某个类(如:java HelloWorld)
  6. 使用反射的方法(将某个类加载到类加载区)强制创建某个类的Class对象
(二)类加载器

负责将class 文件加载到内存中,并为之创建一个Class对象,如果了解类加载器的机制,可以的更好的理解程序的运行

类加载器的组成:

  1. 根类加载器: bootstrap classLoader

    也被称为引导类加载类,负责Java核心类的加载

    比如: System, String 等,在 JDK 中的JRE 中 lib 中的 rt.jar文件中

  2. 扩展类加载器: extension classLoader

    负责jre的扩展目录中的jar的加载

  3. 系统类加载器: System classLoader

    负责在JVM启动时加载来自java命令的class文件

三、反射类的成员

(一)获取Class对象的方式
// 先创建了一个Person类
public class Person {
    private String name;
    int age;
    public String address;
    public Person() {}
    private Person(String name){
        this.name = name;
    }
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    public void show(){
        System.out.println("show");
    }
    public void method(String s){
        System.out.println("method"+s);
    }
    public String getString(String s,String m){
        return s+"----"+m;
    }
    private void function(){
        System.out.println("function");
    }

    @Override
    public String toString() {
        return "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
            ", address='" + address + '\'' +
            '}';
    }
}
// 获取Person类的Class对象
// 方法1:
Person person = new Person();
Class c = person.getClass();
// 方法2:
Class c2 = Person.class;
// 方法3:
Class c3 = Class.forName("com.gxa.demo1.Person");
// c=c2=c3:因为一个类的Class对象只有一个

注:在开发中我们常使用第三种方式去做反射,因为第三种传入的是个字符串而不是具体的类名,这样的话就可以把这个值方法配置文件中去,方便修改。

(二)获取类加载器

a: 得到类的Class对象

Class c = Class.forName("com.gxa.demo1.Person");

b: 获取类的加载器

ClassLoader classLoader = c.getClassLoader();
  1. 使用类加载器加载其它的文件
    // 得到Class对象
    Class c = Class.forName("com.gxa.demo1.Person");
    // 获取类加载器对象
    ClassLoader classLoader = c.getClassLoader();
    // 类加载器加载其它文件(加入我的src目录下面有一个jdbc.properties文件)
    InputStream in = classLoader.getResourceAsStream("jdbc.properties");
    

    注:如果找不到文件则会返回null给输入流

(三)反射类的构造方法
  • public Constructor<T> getConstructor(类<?>... parameterTypes):返回一个Constructor对象,返回的是public修饰的构造方法;如果不写参数则返回无参的构造函数对象;如果构造方法不是公共的将会报异常:NoSuchMethodException

    Class c = Class.forName("com.gxa.demo1.Person");
    // 返回公共的无参构造,如果无参构造不是public修饰的将会报错
    Constructor constructor = c.getConstructor();
    System.out.println(constructor);
    // 有参数传入:传入的参数是数据类型类的Class对象;下面是获取一个参数为String类型的构造方法对象
    Constructor constructor = c.getConstructor(String.class);
    System.out.println(constructor);
    
  • public Constructor<?>[] getConstructors():返回一个public修饰的构造方法的Constructor对象的数组

    Constructor[] constructors = c.getConstructors();
    // 返回的构造方法都是public修饰的
    System.out.println(constructors);
    
  • public Constructor<T> getDeclaredConstructor(类<?>... parameterTypes):返回一个Constructor对象,不传参数返回无参的构造方法(没有修饰符的限制),如果构造方法不存在将报异常:NoSuchMethodException

    // 获取无参的构造方法
    Constructor declaredConstructor = c.getDeclaredConstructor();
    System.out.println(declaredConstructor);
    
  • public Constructor<?>[] getDeclaredConstructors():返回所有构造方法的Constructor对象的数组

    // 获取所有方法
    Constructor[] declaredConstructors = c.getDeclaredConstructors();
    System.out.println(declaredConstructors);
    
1、通过Constructor对象创建实例
// 获取Class对象
Class c = Person.class;
// 获取Person类的无参构造
Constructor declaredConstructor = c.getDeclaredConstructor();
// 通过构造方法对象创建一个Person类的实例对象
Object obj = declaredConstructor.newInstance();
System.out.println(obj);

注:如果获取的构造方法对象是一个非私有的,那么将会报IllegalAccessException异常

解决方案:

在Constructor类里面有一个方法叫作:public void setAccessible(boolean flag)的方法,将此对象的accessible标志设置为指示的布尔值。 true的值表示反射对象应该在使用时跳过java的语法检查。

// 获取Class对象
Class c = Person.class;
// 获取Person类的无参构造
Constructor declaredConstructor = c.getDeclaredConstructor();
// 设置constructor对象的访问权限
declaredConstructor.setAccessible(true);
// 通过构造方法对象创建一个Person类的实例对象
Object obj = declaredConstructor.newInstance();
System.out.println(obj);

// 通过有参构造方法创建类的实例
Class c = Person.class;

Constructor declaredConstructor = c.getDeclaredConstructor(String.class);

declaredConstructor.setAccessible(true);

Object obj = declaredConstructor.newInstance("张三");
System.out.println(obj);
(四)反射类的属性

与获取构造方法对象的获取方法相似:

  • public Field getField(String name):返回一个Field对象,它反映此表示的类或接口的指定公共成员字段类对象。

    Class c = Person.class;
    Field addressField = c.getField("address");
    System.out.println(addressField);
    

注:在使用getField方法是如果传的name为不存在的属性或非公共属性将会报异常:NoSuchFieldException

  • public Field[] getFields():返回包含一个数组Field对象反射由此表示的类或接口的所有可访问的公共字段类对象。

    // 获取该类公共属性的field对象的数组
    Field[] fields = c.getFields();
    
  • public Field getDeclaredField(String name):返回一个Field对象,它反映此表示的类或接口的指定字段类对象。

    Field name = c.getDeclaredField("name");
    
  • public Field[] getDeclaredFields():返回的数组Field对象反映此表示的类或接口声明的所有字段类对象。

    获取所有属性的field对象集合
    Field[] fields = c.getDeclaredFields();
    
1、通过反射来给Person的对象赋值
// 没有对象就没有属性
// 要使用属性,必须 要有对象
// 1、反射一个无参的构造方法的Constructor对象
Constructor constructor = c.getDeclaredConstructor();
// 2、创建对应的对象
Object obj = constructor.newInstance(); 
// 3、反射Person类的属性对象
Field namefield = c.getDeclaredField("name");
Field agefield = c.getDeclaredField("age");
Field addressfield = c.getDeclaredField("address");
// 4、给对象实例赋值
namefield.setAccessible(true); // 设置私有属性的访问权限
namefield.set(obj,"张三");
agefield.set(obj,18);
addressfield.set(obj,"成都");
System.out.println(obj);
(五)反射类的方法
  • public 方法 getDeclaredMethod(String name,类<?>... parameterTypes):返回一个方法对象,它反映此表示的类或接口的指定声明的方法类对象。

  • public 方法[] getDeclaredMethods():返回包含一个数组方法对象反射的类或接口的所有声明的方法,通过此表示对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法

  • public 方法 getMethod(String name,类<?>... parameterTypes):返回一个方法对象,它反映此表示的类或接口的指定公共成员方法对象。

  • public 方法[] getMethods():返回包含一个数组方法对象反射由此表示的类或接口的所有公共方法对象,包括那些由类或接口和那些从超类和超接口继承的声明

    // 1、获取Class对象
    Person person = new Person();
    Class aClass = person.getClass();
    // 2、获取Person的方法Method对象
    Class对象.getDeclaredMethod("方法名",数据类型的Class对象);
    Method method = aClass.getDeclaredMethod("method", String.class);
    // invoke是Method类中的方法:表示调用某个具体对象的方法
    Method对象.invoke(类的实例对象,参数...);
    // 通过Method对象调用person对象的method方法,为了防止权限不够(方法为私有的)最好使用setAccessible(true)来跳过java的代码检查
    method.setAccessible(true);
    method.invoke(person,"反射调用了method方法");
    

    案例:

    在ArrayList 对象中添加一个字符串;

    我们知道指定了ArrayList中参数的类型再传入其他类型的话java会报错,那么这个时候我们就可以通过反射的方式去获取ArrayList类中的add方法给ArrayList的对象赋值,因为ArrayList类的add方法传入的参数是一个泛型而不是具体的类型,所以我们可以添加任意数据:

    // 1、创建一个ArrayList<Integer>对象
    ArrayList<Integer> list = new ArrayList<>();
    // 2、通过反射的方式获取ArrayList的Class对象
    Class listClass = list.getClass();
    // 3、通过Class对象反射add方法
    Method listAdd = listClass.getDeclaredMethod("add",Object.class);
    // 4、通过ArrayList类的add方法给ArrayList<Integer>实例对象添加值
      // 4.1、为了防止访问的权限不够加上setAccessible方法跳过java代码检查
    listAdd.setAccessible(true);
      // 4.2、添加值
    listAdd.invoke(list,"我在Integer类型的集合里添加了一个字符串");
    // 5、打印输出list集合
    System.out.println(list);
    

面试题

面试题

第一题:JDBC操作数据库的步骤 ?

1)注册数据库驱动。

2)建立数据库连接。

3)获取statemen对象。

4)执行SQL语句。

5)返回结果(如果是查询操作有结果集:处理结果集)。

6)(释放资源)关闭数据库连接

第二题:JDBC中的Statement 和PreparedStatement,CallableStatement的区别?

1)PreparedStatement是预编译的SQL语句,效率高于Statement。

2)PreparedStatement支持“?”操作符,相对于Statement更加灵活。

3)PreparedStatement可以防止SQL注入,安全性高于Statement。

4)CallableStatement适用于执行存储过程。

第三题:execute,executeQuery,executeUpdate的区别是什么?

· Statement的execute(String query)方法用来执行任意的SQL查询,如果查询的结果是一个ResultSet,这个方法就返回true。如果结果不是ResultSet,比如insert或者update查询,它就会返回false。我们可以通过它的getResultSet方法来获取ResultSet,或者通过getUpdateCount()方法来获取更新的记录条数。

· Statement的executeQuery(String query)接口用来执行select查询,并且返回ResultSet。即使查询不到记录返回的ResultSet也不会为null。我们通常使用executeQuery来执行查询语句,这样的话如果传进来的是insert或者update语句的话,它会抛出错误信息为 “executeQuery method can not be used for update”的java.util.SQLException。

· Statement的executeUpdate(String query)方法用来执行insert或者update/delete(DML)语句,或者 什么也不返回DDL语句。返回值是int类型,如果是DML语句的话,它就是更新的条数,如果是DDL的话,就返回0。

· 只有当你不确定是什么语句的时候才应该使用execute()方法,否则应该使用executeQuery或者executeUpdate方法。

第四题:PreparedStatement的缺点是什么,怎么解决这个问题?

PreparedStatement的一个缺点是,我们不能直接用它来执行in条件语句;需要执行IN条件语句的话,下面有一些解决方案:

1)分别进行单条查询——这样做性能很差,不推荐。

2)使用存储过程——这取决于数据库的实现,不是所有数据库都支持。

3)动态生成PreparedStatement——这是个好办法,但是不能享受PreparedStatement的缓存带来的好处了。

4)在PreparedStatement查询中使用NULL值——如果你知道输入变量的最大个数的话,这是个不错的办法,扩展一下还可以支持无限参数。

第五题:JDBC的ResultSet是什么?

在查询数据库后会返回一个ResultSet,它就像是查询结果集的一张数据表。

ResultSet对象维护了一个游标,指向当前的数据行。开始的时候这个游标指向的是第一行。如果调用了ResultSet的next()方法游标会下移一行,如果没有更多的数据了,next()方法会返回false。可以在for循环中用它来遍历数据集。

默认的ResultSet是不能更新的,游标也只能往下移。也就是说你只能从第一行到最后一行遍历一遍。不过也可以创建可以回滚或者可更新的ResultSet。

当生成ResultSet的Statement对象要关闭或者重新执行或是获取下一个ResultSet的时候,ResultSet对象也会自动关闭。

可以通过ResultSet的getter方法,传入列名或者从1开始的序号来获取列数据。

文章来源:https://blog.csdn.net/qq_64646478/article/details/134962878
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。