学会反射后,我被录取了!(干货)

 

本文系小菠萝的投稿java

 

 

反射是一个很是重要的知识点,在学习Spring 框架时,Bean的初始化用到了反射,在破坏单例模式时也用到了反射,在获取标注的注解时也会用到反射······mysql

固然了,反射在平常开发中,咱们没碰到过多少,至少我没怎么用过。但面试是造火箭现场,可爱的面试官们又怎会轻易地放过咱们呢?反射是开源框架中的一个重要设计理念,在源码分析中少不了它的身影,因此,今天我会尽可能用浅显易懂的语言,让你去理解下面这几点:面试

(1)反射的思想以及它的做用: 概念篇算法

(2)反射的基本使用及应用场景: 应用篇spring

(3)使用反射能给咱们编码时带来的优点以及存在的缺陷: 分析篇sql

反射的思想及做用

有反必有正,就像世间的阴和阳,计算机的0和1同样。天道有轮回,苍天...(净会在这瞎bibi)docker

在学习反射以前,先来了解正射是什么。咱们日常用的最多的 new 方式实例化对象的方式就是一种正射的体现。假如我须要实例化一个HashMap,代码就会是这样子。数据库

Map<Integer, Integer> map = new HashMap<>(); map.put(11); 

某一天发现,该段程序不适合用 HashMap 存储键值对,更倾向于用LinkedHashMap存储。从新编写代码后变成下面这个样子。编程

Map<Integer, Integer> map = new LinkedHashMap<>(); map.put(11); 

假如又有一天,发现数据仍是适合用 HashMap来存储,难道又要从新修改源码吗?微信

发现问题了吗?咱们每次改变一种需求,都要去从新修改源码,而后对代码进行编译,打包,再到 JVM 上重启项目。这么些步骤下来,效率很是低。


对于这种需求频繁变动但变动不大的场景,频繁地更改源码确定是一种不容许的操做,咱们可使用一个开关,判断何时使用哪种数据结构。

public Map<Integer, Integer> getMap(String param) {     Map<Integer, Integer> map = null;     if (param.equals("HashMap")) {         map = new HashMap<>();     } else if (param.equals("LinkedHashMap")) {         map = new LinkedHashMap<>();     } else if (param.equals("WeakHashMap")) {         map = new WeakHashMap<>();     }     return map; } 

经过传入参数param决定使用哪种数据结构,能够在项目运行时,经过动态传入参数决定使用哪个数据结构。

若是某一天还想用TreeMap,仍是避免不了修改源码,从新编译执行的弊端。这个时候,反射就派上用场了。

在代码运行以前,咱们不肯定未来会使用哪种数据结构,只有在程序运行时才决定使用哪个数据类,而反射能够在程序运行过程中动态获取类信息调用类方法。经过反射构造类实例,代码会演变成下面这样。

public Map<Integer, Integer> getMap(String className) {     Class clazz = Class.forName(className);     Consructor con = clazz.getConstructor();     return (Map<Integer, Integer>) con.newInstance(); } 

不管使用什么 Map,只要实现了Map接口,就可使用全类名路径传入到方法中,得到对应的 Map 实例。例如java.util.HashMap / java.util.LinkedHashMap····若是要建立其它类例如WeakHashMap,我也不须要修改上面这段源码

咱们来回顾一下如何从 new 一个对象引出使用反射的。

  • 在不使用反射时,构造对象使用 new 方式实现,这种方式在编译期就能够把对象的类型肯定下来。

  • 若是需求发生变动,须要构造另外一个对象,则须要修改源码,很是不优雅,因此咱们经过使用 开关 ,在程序运行时判断须要构造哪个对象,在运行时能够变动开关来实例化不一样的数据结构。

  • 若是还有其它扩展的类有可能被使用,就会建立出很是多的分支,且在编码时不知道有什么其余的类被使用到,假如往后 Map 接口下多了一个集合类是 xxxHashMap ,还得建立分支,此时引出了反射:能够在 运行时 才肯定使用哪个数据类,在切换类时,无需从新修改源码、编译程序。

第一章总结:

  • 反射的思想在程序运行过程当中肯定和解析数据类的类型。

  • 反射的做用:对于在 编译期 没法肯定使用哪一个数据类的场景,经过 反射 能够在程序运行时构造出不一样的数据类实例

反射的基本使用

Java 反射的主要组成部分有4个:

  • Class :任何运行在内存中的全部类都是该 Class 类的实例对象,每一个 Class 类对象内部都包含了原本的全部信息。记着一句话,经过反射干任何事,先找 Class 准没错!

  • Field :描述一个类的属性,内部包含了该属性的全部信息,例如数据类型,属性名,访问修饰符······

  • Constructor :描述一个类的构造方法,内部包含了构造方法的全部信息,例如参数类型,参数名字,访问修饰符······

  • Method :描述一个类的全部方法(包括抽象方法),内部包含了该方法的全部信息,与 Constructor 相似,不一样之处是 Method 拥有返回值类型信息,由于构造方法是没有返回值的。

我总结了一张脑图,放在了下面,若是用到了反射,离不开这核心的4个类,只有去了解它们内部提供了哪些信息,有什么做用,运用它们的时候才能易如反掌


咱们在学习反射的基本使用时,我会用一个SmallPineapple类做为模板进行说明,首先咱们先来熟悉这个类的基本组成:属性,构造函数和方法

public class SmallPineapple {     public String name;     public int age;     private double weight; // 体重只有本身知道          public SmallPineapple() {}          public SmallPineapple(String name, int age) {         this.name = name;         this.age = age;     }     public void getInfo() {         System.out.print("["+ name + " 的年龄是:" + age + "]");     } } 

反射中的用法有很是很是多,常见的功能有如下这几个:

  • 在运行时获取一个类的 Class 对象

  • 在运行时构造一个类的实例化对象

  • 在运行时获取一个类的全部信息:变量、方法、构造器、注解

获取类的 Class 对象

在 Java 中,每个类都会有专属于本身的 Class 对象,当咱们编写完.java文件后,使用javac编译后,就会产生一个字节码文件.class,在字节码文件中包含类的全部信息,如属性构造方法方法······当字节码文件被装载进虚拟机执行时,会在内存中生成 Class 对象,它包含了该类内部的全部信息,在程序运行时能够获取这些信息。

获取 Class 对象的方法有3种:

  • 类名.class :这种获取方式只有在 编译 前已经声明了该类的类型才能获取到 Class 对象

Class clazz = SmallPineapple.class; 
  • 实例.getClass() :经过实例化对象获取该实例的 Class 对象

SmallPineapple sp = new SmallPineapple(); Class clazz = sp.getClass(); 
  • Class.forName(className) :经过类的全限定名获取该类的 Class 对象

Class clazz = Class.forName("com.bean.smallpineapple"); 

拿到 Class对象就能够对它随心所欲了:剥开它的皮(获取类信息)、指挥它作事(调用它的方法),看透它的一切(获取属性),总之它就没有隐私了。

不过在程序中,每一个类的 Class 对象只有一个,也就是说你只有这一个奴隶。咱们用上面三种方式测试,经过三种方式打印各个 Class 对象都是相同的。

Class clazz1 = Class.forName("com.bean.SmallPineapple"); Class clazz2 = SmallPineapple.class; SmallPineapple instance = new SmallPineapple(); Class clazz3 = instance.getClass(); System.out.println("Class.forName() == SmallPineapple.class:" + (clazz1 == clazz2)); System.out.println("Class.forName() == instance.getClass():" + (clazz1 == clazz3)); System.out.println("instance.getClass() == SmallPineapple.class:" + (clazz2 == clazz3)); 

内存中只有一个 Class 对象的缘由要牵扯到 JVM 类加载机制双亲委派模型,它保证了程序运行时,加载类时每一个类在内存中仅会产生一个Class对象。在这里我不打算详细展开说明,能够简单地理解为 JVM 帮咱们保证了一个类在内存中至多存在一个 Class 对象

构造类的实例化对象

经过反射构造一个类的实例方式有2种:

  • Class 对象调用 newInstance() 方法

Class clazz = Class.forName("com.bean.SmallPineapple"); SmallPineapple smallPineapple = (SmallPineapple) clazz.newInstance(); smallPineapple.getInfo(); // [null 的年龄是:0] 

即便 SmallPineapple 已经显式定义了构造方法,经过 newInstance()  建立的实例中,全部属性值都是对应类型的初始值,由于 newInstance() 构造实例会调用默认无参构造器

  • Constructor 构造器调用 newInstance() 方法

Class clazz = Class.forName("com.bean.SmallPineapple"); Constructor constructor = clazz.getConstructor(String.class, int.class); constructor.setAccessible(true); SmallPineapple smallPineapple2 = (SmallPineapple) constructor.newInstance("小菠萝"21); smallPineapple2.getInfo(); // [小菠萝 的年龄是:21] 

经过 getConstructor(Object... paramTypes) 方法指定获取指定参数类型的 Constructor, Constructor 调用 newInstance(Object... paramValues) 时传入构造方法参数的值,一样能够构造一个实例,且内部属性已经被赋值。

经过Class对象调用 newInstance() 会走默认无参构造方法,若是想经过显式构造方法构造实例,须要提早从Class中调用getConstructor()方法获取对应的构造器,经过构造器去实例化对象。

这些 API 是在开发当中最常遇到的,固然还有很是多重载的方法,本文因为篇幅缘由,且若是每一个方法都一一讲解,咱们也记不住,因此用到的时候去类里面查找就已经足够了。

获取一个类的全部信息

Class 对象中包含了该类的全部信息,在编译期咱们能看到的信息就是该类的变量、方法、构造器,在运行时最常被获取的也是这些信息。


获取类中的变量(Field)

  • Field[] getFields():获取类中全部被 public 修饰的全部变量

  • Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰

  • Field[] getDeclaredFields():获取类中全部的变量,但没法获取继承下来的变量

  • Field getDeclaredField(String name):根据姓名获取类中的某个变量,没法获取继承下来的变量

获取类中的方法(Method)

  • Method[] getMethods():获取类中被public修饰的全部方法

  • Method getMethod(String name, Class...<?> paramTypes):根据名字和参数类型获取对应方法,该方法必须被public修饰

  • Method[] getDeclaredMethods():获取全部方法,但没法获取继承下来的方法

  • Method getDeclaredMethod(String name, Class...<?> paramTypes):根据名字和参数类型获取对应方法,没法获取继承下来的方法

获取类的构造器(Constructor)

  • Constuctor[] getConstructors():获取类中全部被 public 修饰的构造器

  • Constructor getConstructor(Class...<?> paramTypes):根据 参数类型 获取类中某个构造器,该构造器必须被 public 修饰

  • Constructor[] getDeclaredConstructors():获取类中全部构造器

  • Constructor getDeclaredConstructor(class...<?> paramTypes):根据 参数类型 获取对应的构造器

每种功能内部以 Declared 细分为2类:

Declared修饰的方法:能够获取该类内部包含的全部变量、方法和构造器,可是没法获取继承下来的信息

Declared修饰的方法:能够获取该类中public修饰的变量、方法和构造器,可获取继承下来的信息

若是想获取类中**全部的(包括继承)**变量、方法和构造器,则须要同时调用getXXXs()getDeclaredXXXs()两个方法,用Set集合存储它们得到的变量、构造器和方法,以防两个方法获取到相同的东西。

例如:要获取SmallPineapple获取类中全部的变量,代码应该是下面这样写。

Class clazz = Class.forName("com.bean.SmallPineapple"); // 获取 public 属性,包括继承 Field[] fields1 = clazz.getFields(); // 获取全部属性,不包括继承 Field[] fields2 = clazz.getDeclaredFields(); // 将全部属性汇总到 set Set<Field> allFields = new HashSet<>(); allFields.addAll(Arrays.asList(fields1)); allFields.addAll(Arrays.asList(fields2)); 

不知道你有没有发现一件有趣的事情,若是父类的属性用protected修饰,利用反射是没法获取到的。

protected 修饰符的做用范围:只容许同一个包下或者子类访问,能够继承到子类。

getFields() 只能获取到本类的public属性的变量值;

getDeclaredFields() 只能获取到本类的全部属性,不包括继承的;不管如何都获取不到父类的 protected 属性修饰的变量,可是它的的确确存在于子类中。

获取注解

获取注解单独拧了出来,由于它并非专属于 Class 对象的一种信息,每一个变量,方法和构造器均可以被注解修饰,因此在反射中,Field,Constructor 和 Method 类对象均可以调用下面这些方法获取标注在它们之上的注解。

  • Annotation[] getAnnotations():获取该对象上的全部注解

  • Annotation getAnnotation(Class annotaionClass):传入 注解类型 ,获取该对象上的特定一个注解

  • Annotation[] getDeclaredAnnotations():获取该对象上的显式标注的全部注解,没法获取 继承 下来的注解

  • Annotation getDeclaredAnnotation(Class annotationClass):根据 注解类型 ,获取该对象上的特定一个注解,没法获取 继承 下来的注解

只有注解的@Retension标注为RUNTIME时,才可以经过反射获取到该注解,@Retension 有3种保存策略:

  • SOURCE :只在**源文件(.java)**中保存,即该注解只会保留在源文件中,编译时编译器会忽略该注解,例如 @Override 注解

  • CLASS :保存在字节码文件(.class)中,注解会随着编译跟随字节码文件中,可是运行时不会对该注解进行解析

  • RUNTIME :一直保存到运行时用得最多的一种保存策略,在运行时能够获取到该注解的全部信息

像下面这个例子,SmallPineapple 类继承了抽象类PineapplegetInfo()方法上标识有 @Override 注解,且在子类中标注了@Transient注解,在运行时获取子类重写方法上的全部注解,只能获取到@Transient的信息。

public abstract class Pineapple {     public abstract void getInfo(); } public class SmallPineapple extends Pineapple {     @Transient     @Override     public void getInfo() {         System.out.print("小菠萝的身高和年龄是:" + height + "cm ; " + age + "岁");     } } 

启动类Bootstrap获取 SmallPineapple 类中的 getInfo() 方法上的注解信息:

public class Bootstrap {     /**      * 根据运行时传入的全类名路径判断具体的类对象      * @param path 类的全类名路径      */     public static void execute(String path) throws Exception {         Class obj = Class.forName(path);         Method method = obj.getMethod("getInfo");         Annotation[] annotations = method.getAnnotations();         for (Annotation annotation : annotations) {             System.out.println(annotation.toString());         }     }     public static void main(String[] args) throws Exception {         execute("com.pineapple.SmallPineapple");     } } // @java.beans.Transient(value=true) 

经过反射调用方法

经过反射获取到某个 Method 类对象后,能够经过调用invoke方法执行。

  • invoke(Oject obj, Object... args) :参数``1 指定调用该方法的**对象**,参数 2`是方法的参数列表值。

若是调用的方法是静态方法,参数1只须要传入null,由于静态方法不与某个对象有关,只与某个类有关。

能够像下面这种作法,经过反射实例化一个对象,而后获取Method方法对象,调用invoke()指定SmallPineapplegetInfo()方法。

Class clazz = Class.forName("com.bean.SmallPineapple"); Constructor constructor = clazz.getConstructor(String.class, int.class); constructor.setAccessible(true); SmallPineapple sp = (SmallPineapple) constructor.newInstance("小菠萝"21); Method method = clazz.getMethod("getInfo"); if (method != null) {     method.invoke(sp, null); } // [小菠萝的年龄是:21] 

反射的应用场景

反射常见的应用场景这里介绍3个:

  • Spring 实例化对象:当程序启动时,Spring 会读取配置文件 applicationContext.xml 并解析出里面全部的 标签实例化到 IOC 容器中。

  • 反射 + 工厂模式:经过 反射 消除工厂中的多个分支,若是须要生产新的类,无需关注工厂类,工厂类能够应对各类新增的类, 反射 可使得程序更加健壮。

  • JDBC链接数据库:使用JDBC链接数据库时,指定链接数据库的 驱动类 时用到反射加载驱动类

Spring 的 IOC 容器

在 Spring 中,常常会编写一个上下文配置文件applicationContext.xml,里面就是关于bean的配置,程序启动时会读取该 xml 文件,解析出全部的 <bean>标签,并实例化对象放入IOC容器中。

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">     <bean id="smallpineapple" class="com.bean.SmallPineapple">         <constructor-arg type="java.lang.String" value="小菠萝"/>         <constructor-arg type="int" value="21"/>     </bean> </beans> 

在定义好上面的文件后,经过ClassPathXmlApplicationContext加载该配置文件,程序启动时,Spring 会将该配置文件中的全部bean都实例化,放入 IOC 容器中,IOC 容器本质上就是一个工厂,经过该工厂传入 <bean> 标签的id属性获取到对应的实例。

public class Main {     public static void main(String[] args) {         ApplicationContext ac =                 new ClassPathXmlApplicationContext("applicationContext.xml");         SmallPineapple smallPineapple = (SmallPineapple) ac.getBean("smallpineapple");         smallPineapple.getInfo(); // [小菠萝的年龄是:21]     } } 

Spring 在实例化对象的过程通过简化以后,能够理解为反射实例化对象的步骤:

  • 获取Class对象的构造器

  • 经过构造器调用 newInstance() 实例化对象

固然 Spring 在实例化对象时,作了很是多额外的操做,才可以让如今的开发足够的便捷且稳定

在以后的文章中会专门写一篇文章讲解如何利用反射实现一个简易版IOC容器,IOC容器原理很简单,只要掌握了反射的思想,了解反射的经常使用 API 就能够实现,我能够提供一个简单的思路:利用 HashMap 存储全部实例,key 表明 <bean> 标签的 id,value 存储对应的实例,这对应了 Spring IOC容器管理的对象默认是单例的。

反射 + 抽象工厂模式

传统的工厂模式,若是须要生产新的子类,须要修改工厂类,在工厂类中增长新的分支

public class MapFactory {     public Map<Object, object> produceMap(String name) {         if ("HashMap".equals(name)) {             return new HashMap<>();         } else if ("TreeMap".equals(name)) {             return new TreeMap<>();         } // ···     } } 

利用反射和工厂模式相结合,在产生新的子类时,工厂类不用修改任何东西,能够专一于子类的实现,当子类肯定下来时,工厂也就能够生产该子类了。

反射 + 抽象工厂的核心思想是:

  • 在运行时经过参数传入不一样子类的全限定名获取到不一样的 Class 对象,调用 newInstance() 方法返回不一样的子类。细心的读者会发现提到了子类这个概念,因此反射 + 抽象工厂模式,通常会用于有继承或者接口实现关系。

例如,在运行时才肯定使用哪种 Map 结构,咱们能够利用反射传入某个具体 Map 的全限定名,实例化一个特定的子类。

public class MapFactory {     /**      * @param className 类的全限定名      */     public Map<Object, Object> produceMap(String className) {         Class clazz = Class.forName(className);         Map<Object, Object> map = clazz.newInstance();         return map;     } } 

className 能够指定为 java.util.HashMap,或者 java.util.TreeMap 等等,根据业务场景来定。

JDBC 加载数据库驱动类

在导入第三方库时,JVM不会主动去加载外部导入的类,而是等到真正使用时,才去加载须要的类,正是如此,咱们能够在获取数据库链接时传入驱动类的全限定名,交给 JVM 加载该类。

public class DBConnectionUtil {     /** 指定数据库的驱动类 */     private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";          public static Connection getConnection() {         Connection conn = null;         // 加载驱动类         Class.forName(DRIVER_CLASS_NAME);         // 获取数据库链接对象         conn = DriverManager.getConnection("jdbc:mysql://···""root""root");         return conn;     } } 

在咱们开发 SpringBoot 项目时,会常常遇到这个类,可是可能习惯成天然了,就没多大在意,我在这里给大家看看常见的application.yml中的数据库配置,我想你应该会恍然大悟吧。


这里的 driver-class-name,和咱们一开始加载的类是否是以为很类似,这是由于MySQL版本不一样引发的驱动类不一样,这体现使用反射的好处:不须要修改源码,仅加载配置文件就能够完成驱动类的替换

在以后的文章中会专门写一篇文章详细地介绍反射的应用场景,实现简单的IOC容器以及经过反射实现工厂模式的好处。

在这里,你只须要掌握反射的基本用法和它的思想,了解它的主要使用场景。

反射的优点及缺陷

反射的优势

  • 增长程序的灵活性:面对需求变动时,能够灵活地实例化不一样对象

可是,有得必有失,一项技术不可能只有优势没有缺点,反射也有两个比较隐晦的缺点

  • 破坏类的封装性:能够强制访问 private 修饰的信息

  • 性能损耗:反射相比直接实例化对象、调用方法、访问变量,中间须要很是多的检查步骤和解析步骤,JVM没法对它们优化。

增长程序的灵活性

这里再也不用 SmallPineapple 举例了,咱们来看一个更加贴近开发的例子:

  • 利用反射链接数据库,涉及到数据库的数据源。在 SpringBoot 中一切约定大于配置,想要定制配置时,使用 application.properties 配置文件指定数据源

角色1 - Java的设计者:咱们设计好DataSource接口,大家其它数据库厂商想要开发者用大家的数据源监控数据库,就得实现个人这个接口

角色2 - 数据库厂商

  • MySQL 数据库厂商:咱们提供了 com.mysql.cj.jdbc.MysqlDataSource 数据源,开发者可使用它链接 MySQL。

  • 阿里巴巴厂商:咱们提供了 com.alibaba.druid.pool.DruidDataSource 数据源,我这个数据源更牛逼,具备页面监控慢SQL日志记录等功能,开发者快来用它监控 MySQL吧!

  • SQLServer 厂商:咱们提供了 com.microsoft.sqlserver.jdbc.SQLServerDataSource 数据源,若是你想实用SQL Server 做为数据库,那就使用咱们的这个数据源链接吧

角色3 - 开发者:咱们能够用配置文件指定使用DruidDataSource数据源

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource 

需求变动:某一天,老板来跟咱们说,Druid 数据源不太符合咱们如今的项目了,咱们使用 MysqlDataSource 吧,而后程序猿就会修改配置文件,从新加载配置文件,并重启项目,完成数据源的切换。

spring.datasource.type=com.mysql.cj.jdbc.MysqlDataSource 

在改变链接数据库的数据源时,只须要改变配置文件便可,无需改变任何代码,缘由是:

  • Spring Boot 底层封装好了链接数据库的数据源配置,利用反射,适配各个数据源。

下面来简略的进行源码分析。咱们用ctrl+左键点击spring.datasource.type进入 DataSourceProperties 类中,发现使用setType() 将全类名转化为 Class 对象注入到type成员变量当中。在链接并监控数据库时,就会使用指定的数据源操做。

private Class<? extends DataSource> type; public void setType(Class<? extends DataSource> type) {     this.type = type; } 

Class对象指定了泛型上界DataSource,咱们去看一下各大数据源的类图结构


上图展现了一部分数据源,固然不止这些,可是咱们能够看到,不管指定使用哪种数据源,咱们都只须要与配置文件打交道,而无需更改源码,这就是反射的灵活性!

破坏类的封装性

很明显的一个特色,反射能够获取类中被private修饰的变量、方法和构造器,这违反了面向对象的封装特性,由于被 private 修饰意味着不想对外暴露,只容许本类访问,而setAccessable(true)能够无视访问修饰符的限制,外界能够强制访问。

还记得单例模式一文吗?里面讲到反射破坏饿汉式和懒汉式单例模式,因此以后用了枚举避免被反射KO。

回到最初的起点,SmallPineapple 里有一个 weight 属性被 private 修饰符修饰,目的在于本身的体重并不想给外界知道。

public class SmallPineapple {     public String name;     public int age;     private double weight; // 体重只有本身知道          public SmallPineapple(String name, int age, double weight) {         this.name = name;         this.age = age;         this.weight = weight;     }      } 

虽然 weight 属性理论上只有本身知道,可是若是通过反射,这个类就像在裸奔同样,在反射面前变得一览无遗

SmallPineapple sp = new SmallPineapple("小菠萝"21"54.5"); Clazz clazz = Class.forName(sp.getClass()); Field weight = clazz.getDeclaredField("weight"); weight.setAccessable(true); System.out.println("窥觑到小菠萝的体重是:" + weight.get(sp)); // 窥觑到小菠萝的体重是:54.5 kg 

性能损耗

在直接 new 对象并调用对象方法和访问属性时,编译器会在编译期提早检查可访问性,若是尝试进行不正确的访问,IDE会提早提示错误,例如参数传递类型不匹配,非法访问 private 属性和方法。

而在利用反射操做对象时,编译器没法提早得知对象的类型,访问是否合法,参数传递类型是否匹配。只有在程序运行时调用反射的代码时才会从头开始检查、调用、返回结果,JVM也没法对反射的代码进行优化。

虽然反射具备性能损耗的特色,可是咱们不能一律而论,产生了使用反射就会性能降低的思想,反射的慢,须要同时调用上100W次才可能体现出来,在几回、几十次的调用,并不能体现反射的性能低下。因此不要一味地戴有色眼镜看反射,在单次调用反射的过程当中,性能损耗能够忽略不计。若是程序的性能要求很高,那么尽可能不要使用反射。

反射基础篇文末总结

  • 反射的思想:反射就像是一面镜子同样,在运行时才看到本身是谁,可获取到本身的信息,甚至实例化对象。

  • 反射的做用:在运行时才肯定实例化对象,使程序更加健壮,面对需求变动时,能够最大程度地作到不修改程序源码应对不一样的场景,实例化不一样类型的对象。

  • 反射的应用场景常见的有 3 个:Spring的 IOC 容器,反射+工厂模式 使工厂类更稳定,JDBC链接数据库时加载驱动类

  • 反射的 3 个特色:增长程序的灵活性、破坏类的封装性以及性能损耗

《真实世界的算法:初学者指南》 学习算法的第一本技术书;算法尽可能简单,避免读者有挫败感,仅需基本数学基础和计算机常识知识;经过真实世界须要解决的实际问题来介绍算法思想;为各领域高效运用算法提供重要指南

 

往期精选

2w字 + 40张图带你参透并发编程!

好文推荐!Linux 是如何管理 IO 的?

昨天有读者说他不会 docker,今天就给你肝出来了

Spring Cloud 20000 字总结,收藏!

 

本文分享自微信公众号 - Java建设者(javajianshe)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索