由于blog各种垃圾评论太多,而且本人审核评论周期较长,所以懒得管理评论了,就把评论功能关闭,有问题可以直接qq骚扰我

JAVA基础—反射

JAVA 西门飞冰 1280℃
[隐藏]

1.什么是java反射机制

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

image-20220913124444610

2.反射的优缺点

优点:

  • 提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力
  • 允许程序创建和控制任何类的对象,无需提前硬编码目标类

缺点:

  • 反射的性能较低
    • 反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
  • 反射会模糊程序内部逻辑,可读性较差

3.反射机制应用

不使用反射: 占绝大多数的使用场景。也就是说,在编写代码时,已经完全确定要创建的对象所属的类,以及明确调用的方法。

使用反射:在编写代码前,不确定要创建的对象所属的类型,以及不确定要调用的方法

Java反射机制提供的功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

反射机制的应用场景:

1、JDBC加载驱动连接class.forname

2、Spring容器框架IOC实例化对象

3、自定义注解生效(反射+Aop)

4、第三方核心的框架:框架= 注解 + 反射 + 设计模式

4.应用测试代码:

自定义注解:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}

自定义接口:

public interface MyInterface {
    void method();
}

父类:

public class Creature<T> {

    boolean gender;
    public int id;

    public void breath(){
        System.out.println("呼吸");
    }
    private void info(){
        System.out.println("我是一个生物");
    }

}

子类:

@MyAnnotation("t_persons")
public class Person extends Creature<String> implements Comparable<Person>,MyInterface{
    private String name;
    public int age = 1;
    @MyAnnotation("info")
    private static String info;

     public Person(){
        System.out.println("Person()...");
     }

    protected Person(int age){
        this.age = age;
    }

    private Person(String name, int age){
        this.name = name;
        this.age = age;

    }
    public void show() throws RuntimeException,ClassNotFoundException{
        System.out.println("你好,我是一个Person");
    }

    @MyAnnotation(value="show_nation")
    private String showNation(String nation,int age){
        System.out.println("showNation...");
        return "我的国籍是:" + nation + ",生活了" + age + "年";
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Person o) {
        return 0;
    }

    @Override
    public void method() {

    }

    public static void showInfo(){
        System.out.println("我是一个人");
    }
}

5.反射的应用1:创建运行时类的对象

创建运行时类的对象有两种方式:

方式1:直接调用Class对象的newInstance()方法

方式一的步骤:

1)获取该类型的Class对象

2)调用Class对象的newInstance()方法创建对象

要想创建对象成功,需要满足:
> 运行时类中必须声明空参的构造器。否则,会报InstantiationException异常
> 空参的构造器的权限得够。否则,报IllegalAccessException异常

代码示例:

@Test
public void test() throws InstantiationException, IllegalAccessException {
  	// clazz代表Person类型
    Class<Person> clazz = Person.class;
    // clazz.newInstance()创建的就是Person的对象
    Person per = clazz.newInstance();
    System.out.println(per);
}

或如下方式:

@Test
public void test() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    Class<?> clazz = Class.forName("com.fblinux.reflect.Person");
    Object obj = clazz.newInstance();
    System.out.println(obj);
}

方式2:通过获取构造器对象来进行实例化

方式二的步骤:

1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器

2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。

3)通过Constructor实例化对象。

如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

代码示例:

@Test
public void test1() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
    Class clazz = Person.class;
  	//调用运行时类的空参构造器,创建对象
    Constructor constructor = clazz.getDeclaredConstructor();
    constructor.setAccessible(true);
    Person p = (Person) constructor.newInstance();
    System.out.println(p);
}

6.反射的应用2:获取运行时类的完整结构

可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)。

6.1.获取所有的属性及相关信息

getDeclaredFields():获取当前运行时类中声明的所有属性

@Test
public void test2(){
    Class clazz = Person.class;
    Field[] declaredFields = clazz.getDeclaredFields();
    for(Field f : declaredFields){
        System.out.println(f);
    }
}

获取属性中的权限修饰符、变量类型、变量名

@Test
public void test2(){
    Class clazz = Person.class;
    Field[] declaredFields = clazz.getDeclaredFields();
    for(Field f : declaredFields){
        //1.权限修饰符
        int modifier = f.getModifiers();
        System.out.print(Modifier.toString(modifier) + "\t");
        //2.数据类型
        Class type = f.getType();
        System.out.print(type.getName() + "\t");
        //3.变量名
        String fName = f.getName();
        System.out.print(fName);
        
        System.out.println();
    }
}

6.2.获取所有方法及相关信息

getDeclaredMethods():获取当前运行时类中声明的所有方法

@Test
public void test2(){
    Class clazz = Person.class;
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method m : declaredMethods) {
        System.out.println(m);
    }
}

获取方法中声明的注解信息、权限修饰符、返回值类型、方法名、形参列表和抛出的异常

@Test
public void test2(){
    Class clazz = Person.class;
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method m : declaredMethods) {
        // 1.获取方法声明的注解
        Annotation[] annos = m.getAnnotations();
        for (Annotation a : annos) {
            System.out.println(a);
        }
        // 2.权限修饰符
        System.out.print(Modifier.toString(m.getModifiers()) + "\t");
        // 3.返回值类型
        System.out.print(m.getReturnType().getName() + "\t");
        // 4.方法名
        System.out.print(m.getName());
        System.out.print("(");
        // 5.形参列表
        Class[] parameterTypes = m.getParameterTypes();
        if (!(parameterTypes == null && parameterTypes.length == 0)) {
            for (int i = 0; i < parameterTypes.length; i++) { if (i == parameterTypes.length - 1) { System.out.print(parameterTypes[i].getName() + " args_" + i); break; } System.out.print(parameterTypes[i].getName() + " args_" + i + ","); } } System.out.print(")"); // 6.抛出的异常 Class[] exceptionTypes = m.getExceptionTypes(); if (exceptionTypes.length > 0) {
            System.out.print("throws ");
            for (int i = 0; i < exceptionTypes.length; i++) {
                if (i == exceptionTypes.length - 1) {
                    System.out.print(exceptionTypes[i].getName());
                    break;
                }
                System.out.print(exceptionTypes[i].getName() + ",");
            }
        }
        System.out.println();
    }
}

6.3.获取其他结构

public class OtherTest {
/*
获取当前类中的所有的构造器
*/
@Test
public void test1(){
Class clazz = Person.class;
Constructor[] cons = clazz.getDeclaredConstructors();
for(Constructor c :cons){
System.out.println(c);
}
}
/*
获取运行时类的父类
*/
@Test
public void test2(){
Class clazz = Person.class;
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
}
/*
获取运行时类的所在的包
*/
@Test
public void test3(){
Class clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
/*
获取运行时类的注解
*/
@Test
public void test4(){
Class clazz = Person.class;
Annotation[] annos = clazz.getAnnotations();
for (Annotation anno : annos) {
System.out.println(anno);
}
}

/*
获取运行时类所实现的接口
*/
@Test
public void test5(){
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);
}
}
/*
获取运行时类的带泛型的父类
*/
@Test
public void test6(){
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
}

7.反射的应用3:调用运行时类的指定结构

7.1.调用指定的属性

在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。

(1)获取该类型的Class对象

Class clazz = Class.forName(“包.类名”);

(2)获取属性对象

Field field = clazz.getDeclaredField(“属性名”);

(3)如果属性的权限修饰符不是public,那么需要设置属性可访问

field.setAccessible(true);

(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象

Object obj = clazz.newInstance(); //有公共的无参构造

Object obj = 构造器对象.newInstance(实参…);//通过特定构造器对象创建实例对象

(5)设置指定对象obj上此Field的属性内容

field.set(obj,”属性值”);

如果操作静态变量,那么实例对象可以省略,用null表示

(6)取得指定对象obj上此Field的属性内容

Object value = field.get(obj);

如果操作静态变量,那么实例对象可以省略,用null表示

示例代码:

public class Student {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
public class TestField {
    public static void main(String[] args)throws Exception {
        //1、获取Student的Class对象
        Class clazz = Class.forName("com.fblinux.reflect.Student");

        //2、获取属性对象,例如:id属性
        Field idField = clazz.getDeclaredField("id");

        //3、如果id是私有的等在当前类中不可访问access的,我们需要做如下操作
        idField.setAccessible(true);

        //4、创建实例对象,即,创建Student对象
        Object stu = clazz.newInstance();

        //5、获取属性值
        /*
         * 以前:int 变量= 学生对象.getId()
         * 现在:Object id属性对象.get(学生对象)
         */
        Object value = idField.get(stu);
        System.out.println("id = "+ value);

        //6、设置属性值
        /*
         * 以前:学生对象.setId(值)
         * 现在:id属性对象.set(学生对象,值)
         */
        idField.set(stu, 2);

        value = idField.get(stu);
        System.out.println("id = "+ value);
    }
}

关于setAccessible方法的使用:

  • Method和Field、Constructor对象都有setAccessible()方法。
  • setAccessible启动和禁用访问安全检查的开关。
  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言访问检查。

7.2.调用指定的方法

(1)获取该类型的Class对象

Class clazz = Class.forName(“包.类名”);

(2)获取方法对象

Method method = clazz.getDeclaredMethod(“方法名”,方法的形参类型列表);

(3)创建实例对象

Object obj = clazz.newInstance();

(4)调用方法

Object result = method.invoke(obj, 方法的实参值列表);

如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

如果方法是静态方法,实例对象也可以省略,用null代替

示例代码:

public class TestMethod {
    @Test
    public void test()throws Exception {
        // 1、获取Student的Class对象
        Class<?> clazz = Class.forName("com.fblinux.reflect.Student");

        //2、获取方法对象
        /*
         * 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载
         *
         * 例如:void setName(String name)
         */
        Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);

        //3、创建实例对象
        Object stu = clazz.newInstance();

        //4、调用方法
        /*
         * 以前:学生对象.setName(值)
         * 现在:方法对象.invoke(学生对象,值)
         */
        Object setNameMethodReturnValue = setNameMethod.invoke(stu, "张三");

        System.out.println("stu = " + stu);
        //setName方法返回值类型void,没有返回值,所以setNameMethodReturnValue为null
        System.out.println("setNameMethodReturnValue = " + setNameMethodReturnValue);

        Method getNameMethod = clazz.getDeclaredMethod("getName");
        Object getNameMethodReturnValue = getNameMethod.invoke(stu);
        //getName方法返回值类型String,有返回值,getNameMethod.invoke的返回值就是getName方法的返回值
        System.out.println("getNameMethodReturnValue = " + getNameMethodReturnValue);//张三
    }

    @Test
    public void test02()throws Exception{
        Class<?> clazz = Class.forName("com.fblinux.reflect.Student");
        Method printInfoMethod = clazz.getMethod("printInfo", String.class);
        //printInfo方法是静态方法
        printInfoMethod.invoke(null,"飞冰");
    }
}

8.反射的应用4:读取注解信息

一个完整的注解应该包含三个部分:

(1)声明

(2)使用

(3)读取

8.1.声明自定义注解

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String columnName();
    String columnType();
}
  • 自定义注解可以通过四个元注解@Retention,@Target,@Inherited,@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到API文档中。
  • Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
  • 可以使用 default 关键字为抽象方法指定默认返回值
  • 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为value,可以省略“value=”,所以如果注解只有一个抽象方法成员,建议使用方法名value。

8.2.使用自定义注解

@Table("t_stu")
public class Student {
    @Column(columnName = "sid",columnType = "int")
    private int id;
    @Column(columnName = "sname",columnType = "varchar(20)")
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

8.3.读取和处理自定义注解

自定义注解必须配上注解的信息处理流程才有意义。

我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是RetentionPolicy.RUNTIME。

public class TestAnnotation {
    public static void main(String[] args) {
        Class studentClass = Student.class;
        Table tableAnnotation = (Table) studentClass.getAnnotation(Table.class);
        String tableName = "";
        if(tableAnnotation != null){
            tableName = tableAnnotation.value();
        }

        Field[] declaredFields = studentClass.getDeclaredFields();
        String[] columns = new String[declaredFields.length];
        int index = 0;
        for (Field declaredField : declaredFields) {
            Column column = declaredField.getAnnotation(Column.class);
            if(column!= null) {
                columns[index++] = column.columnName();
            }
        }
        
        String sql = "select ";
        for (int i=0; i<index; i++) {
            sql += columns[i];
            if(i<index-1){
                sql += ",";
            }
        }
        sql += " from " + tableName;
        System.out.println("sql = " + sql);
    }
}

转载请注明:西门飞冰的博客 » JAVA基础—反射

喜欢 (0)or分享 (0)