java 反射 通俗易懂讲解(java面试)

一、反射概念:

1、官方定义:

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制

静态编译和动态编译概念:
静态编译:在编译时确定类型,绑定对象;
动态编译:运行时确定类型,绑定对象

2、通俗易懂的定义:

二、反射机制优缺点:

1、优点:

运行期类型的判断,动态加载类,提高代码灵活度。
反射被广泛地用于那些需要在运行时检测或修改程序行为的程序中

2、缺点:

(1)性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多;
(2)反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射
(3)使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了
(4)由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用:代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化
(5)反射破坏了代码的封装性

三、应用场景:

1、框架设计:

反射是框架设计的灵魂;
日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制

2、模块化的开发,通过反射去调用对应的字节码

3、动态代理设计模式也采用了反射机制

4、例子:

举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性

四、Java获取反射的三种方法

1、通过new对象实现反射机制:

调用某个对象的getClass(),类名.class和对象.getClass()几乎没有区别,因为一个类被类加载器加载后,就是唯一的一个类对象

2、通过路径实现反射机制:

Class类的forName静态方法,Class类不能被混淆,否则通过名称将找不到Class对象

3、通过类名实现反射机制:

直接获取某一个类的class对象

public class Student {
    private int id;
    String name;
    protected boolean sex;
    public float score;
}

    //获取反射机制三种方式
    public static void main(String[] args) throws ClassNotFoundException {
        //方式一(通过建立对象)
        Student stu = new Student();
        Class classobj1 = stu.getClass();
        System.out.println(classobj1.getName());
        //方式二(所在通过路径-相对路径)
        Class classobj2 = Class.forName("fanshe.Student");
        System.out.println(classobj2.getName());
        //方式三(通过类名)
        Class classobj3 = Student.class;
        System.out.println(classobj3.getName());
    }

五、判断是否为某个类的实例:

1、判断方法:

我们用instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断是否为某个类的实例,它是一个 native 方法:

public native boolean isInstance(Object obj)

2、使用示例:

    public static void main(String[] args) {
        Coder coder = new Coder();
        try {
            Class<?> cls = Class.forName("io.kzw.advance.csdn_blog.Coder");
            // 输出true
            System.out.println(cls.isInstance(coder));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

六、反射来生成对象主要有两种方式:

1、使用Class对象的newInstance()方法来创建Class对象对应类的实例:

    public static void main(String[] args) {
        Class<?> cls = Coder.class;
        try {
            Coder coder = (Coder) cls.newInstance();
            coder.name = "k";
            coder.age = 18;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

2、先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例:

这种方法可以用指定的构造器构造类的实例

public class Coder {
 
    public String name;
    public int age;
 
    public Coder() {
        
    }
    
    public Coder(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
    public static void main(String[] args) {
        try {
            Class<?> c = Coder.class;
            Constructor constructor = c.getConstructor(String.class, int.class);
            Object obj = constructor.newInstance("k", 18);
            System.out.println(obj);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

七、使用反射获取:

1、获取方法:

获取某个Class对象的方法集合,主要有以下几个方法
(1)getDeclaredMethods 方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法:

public Method[] getDeclaredMethods() throws SecurityException

(2)getMethods 方法返回某个类的所有公用(public)方法,包括其继承类的公用方法:

public Method[] getMethods() throws SecurityException

(3)getMethod 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象:

public Method getMethod(String name, Class<?>... parameterTypes)

(4)示例:

    public static void main(String[] args) {
        Class<?> c = Coder.class;
        try {
            Object object = c.newInstance();
            Method[] methods = c.getMethods();
            Method[] declaredMethods = c.getDeclaredMethods();
            Method method = c.getMethod("setName", String.class);
            System.out.println("getMethods获取的方法:");
            for(Method m:methods)
                System.out.println(m);
            System.out.println("getDeclaredMethods获取的方法:");
            for(Method m:declaredMethods)
                System.out.println(m);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

2、获取构造器信息:

获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:

public T newInstance(Object ... initargs)

此方法可以根据传入的参数来调用对应的Constructor创建对象实例。

3、获取类的成员变量(字段)信息:

主要是这几个方法:
getFiled:访问公有的成员变量
getDeclaredField:所有已声明的成员变量,但不能得到其父类的成员变量

八、如何提高反射效率:

1、尽量不要getMethods()后再遍历筛选,而直接用getMethod(methodName)来根据方法名获取方法
2、需要多次动态创建一个类的实例的时候,有缓存的写法会比没有缓存要快很多

void createInstance(String className){
    cachedClass = cache.get(className);
    if (cachedClass == null) {
        // Class.forName耗时
        cachedClass = Class.forName(className);
        cache.set(className, cachedClass);
    }
    return cachedClass.newInstance();
}

3、使用高性能的反射库,比自己写缓存效果好,如joor,或者apache的commons相关工具类
4、使用高版本JDK也很重要,反射性能一直在提高

九、面试难点:

1、常量是否能被反射更改?

对于基本类型的静态常量,JAVA在编译的时候就会把代码中对此常量中引用的地方替换成相应常量值

(1)常量:

public class SysConstant {
 
    public static final Integer INT_VALUE = 100;
}
    public static void main(String[] args) {
        try {
            System.out.println(SysConstant.INT_VALUE);
            Field field = SysConstant.class.getDeclaredField("INT_VALUE");
            field.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(null, 200);
            System.out.println(SysConstant.INT_VALUE);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

执行输出:
100
200

说明反射常量成功

(2)String:

public class SysConstant {
 
    public static final String VERSION = "1.0.0";
}
    public static void main(String[] args) {
        try {
            System.out.println(SysConstant.VERSION);
            Field field = SysConstant.class.getDeclaredField("VERSION");
            field.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(null, "2.0.0");
            System.out.println(SysConstant.VERSION);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

执行输出:
1.0.0
1.0.0

反射没有生效,除了String,然后又试了int、long、boolean,都是反射没生效

(3)总结:

对于基本类型的静态常量,JAVA在编译的时候就会把代码中对此常量中引用的地方替换成相应常量值

2、反射常量会影响程序逻辑吗?

通过第一个问题,就很容易回答了,如果是基本数据类型,包括String,不会影响程序逻辑;
对于基本类型的静态常量,JAVA在编译的时候就会把代码中对此常量中引用的地方替换成相应常量值

public class SysConstant {
 
    public static final String VERSION = "1.0.0";
    public static final boolean DEBUG = true;
 
    public static void print() {
        if (VERSION.equals("1.0.0")) {
            System.out.println("1.0.0");
        }
    }
 
    public static void register() {
        if (DEBUG) {
            System.out.println("register");
        }
    }
}

看看编译后的SysConstant.class:

public class SysConstant {
    public static final String VERSION = "1.0.0";
    public static final boolean DEBUG = true;
 
    public SysConstant() {
    }
 
    public static void print() {
        if ("1.0.0".equals("1.0.0")) {
            System.out.println("1.0.0");
        }
 
    }
 
    public static void register() {
        System.out.println("register");
    }
}

参考博文:https://blog.csdn.net/u014294681/article/details/86441130

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值