持续发明,加快生长!这是我参加「日新方案 10 月更文应战」的第10天,点击查看活动概况

前言

本系列文章首要是汇总了一下大佬们的技能文章,归于Android根底部分,作为一名合格的安卓开发工程师,咱们肯定要熟练掌握java和android,本期就来说说这些~

[非商业用途,如有侵权,请奉告我,我会删去]

DD一下: Android进阶开发各类文档,也可关注大众号<Android苦做舟>获取。

1.Android高级开发工程师必备根底技能
2.Android功用优化中心知识笔记
3.Android+音视频进阶开发面试题冲刺合集
4.Android 音视频开发入门到实战学习手册
5.Android Framework精编内核解析
6.Flutter实战进阶技能手册
7.近百个Android录播视频+音视频视频dome
.......

架构动态编程技能原理

动态编程中心架构:动态署理

1.前言

署理形式是一种设计形式,能够使得在不修正源方针的前提下,额外扩展源方针的功用。即经过拜访源方针的署理类,再由署理类去拜访源方针。这样一来,要扩展功用,就无需修正源方针的代码了。只需求在署理类上添加就能够了。

重学Android基础系列篇(三):架构动态编程技术原理

其实署理形式的中心思维便是这么简略,在java中,署理又分静态署理和动态署理2种,其间动态署理依据不同完结又区分依据接口的的动态署理和依据子类的动态署理。

其间静态署理因为比较简略,面试中也没啥问的,在署理形式一块,问的最多便是动态署理,而且动态署理也是spring aop的中心思维,spring其他许多功用也是经过动态署理来完结的,比方拦截器,业务控制等。

熟练掌握动态署理技能,能让你业务代码愈加精简而优雅。如果你需求写一些中间件的话,那动态署理技能更是必不可少的技能包。

那此篇文章就带咱们一窥动态署理的一切细节吧。

2.静态署理

在说动态署理前,仍是先说说静态署理。

所谓静态署理,便是经过声明一个清晰的署理类来拜访源方针。

咱们有2个接口,Person和Animal。每个接口各有2个完结类,UML如下图:

重学Android基础系列篇(三):架构动态编程技术原理

每个完结类中代码都差不多一起,用Student来举例(其他类和这个几乎如出一辙)

public class Student implements Person{
  private String name;
  public Student() {
  }
  public Student(String name) {
    this.name = name;
   }
  @Override
  public void wakeup() {
    System.out.println(StrUtil.format("学生[{}]早晨醒来啦",name));
   }
  @Override
  public void sleep() {
    System.out.println(StrUtil.format("学生[{}]晚上睡觉啦",name));
   }
}

假定咱们现在要做一件事,便是在一切的完结类调用wakeup()前添加一行输出晨安~,调用sleep()前添加一行输出晚安~。那咱们只需求编写2个署理类PersonProxyAnimalProxy

PersonProxy:

public class PersonProxy implements Person {
  private Person person;
  public PersonProxy(Person person) {
    this.person = person;
   }
  @Override
  public void wakeup() {
    System.out.println("晨安~");
    person.wakeup();
   }
  @Override
  public void sleep() {
    System.out.println("晚安~");
    person.sleep();
   }
}

AnimalProxy:

public class AnimalProxy implements Animal {
  private Animal animal;
  public AnimalProxy(Animal animal) {
    this.animal = animal;
   }
  @Override
  public void wakeup() {
    System.out.println("晨安~");
    animal.wakeup();
   }
  @Override
  public void sleep() {
    System.out.println("晚安~");
    animal.sleep();
   }
}

最终履行代码为:

public static void main(String[] args) {
  Person student = new Student("张三");
  PersonProxy studentProxy = new PersonProxy(student);
  studentProxy.wakeup();
  studentProxy.sleep();
​
  Person doctor = new Doctor("王教授");
  PersonProxy doctorProxy = new PersonProxy(doctor);
  doctorProxy.wakeup();
  doctorProxy.sleep();
​
  Animal dog = new Dog("旺旺");
  AnimalProxy dogProxy = new AnimalProxy(dog);
  dogProxy.wakeup();
  dogProxy.sleep();
​
  Animal cat = new Cat("咪咪");
  AnimalProxy catProxy = new AnimalProxy(cat);
  catProxy.wakeup();
  catProxy.sleep();
}

输出:

晨安~
学生[张三]早晨醒来啦
晚安~
学生[张三]晚上睡觉啦
晨安~
医生[王教授]早晨醒来啦
晚安~
医生[王教授]晚上睡觉啦
晨安~~
小狗[旺旺]早晨醒来啦
晚安~~
小狗[旺旺]晚上睡觉啦
晨安~~
小猫[咪咪]早晨醒来啦
晚安~~
小猫[咪咪]晚上睡觉啦

结论:

静态署理的代码相信现已不用多说了,代码十分简略易懂。这里用了2个署理类,别离署理了PersonAnimal接口。

这种形式虽然好理解,可是缺陷也很显着:

  • 会存在许多的冗余的署理类,这里演示了2个接口,如果有10个接口,就有必要界说10个署理类。
  • 不易维护,一旦接口更改,署理类和方针类都需求更改。

3.JDK动态署理

动态署理,通俗点说便是:无需声明式的创立java署理类,而是在运转进程中生成”虚拟”的署理类,被ClassLoader加载。从而避免了静态署理那样需求声明许多的署理类。

JDK从1.3版本就开端支撑动态署理类的创立。首要中心类只要2个:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

仍是前面那个比方,用JDK动态署理类去完结的代码如下:

创立一个JdkProxy类,用于一致署理:

public class JdkProxy implements InvocationHandler {
​
  private Object bean;
​
  public JdkProxy(Object bean) {
    this.bean = bean;
   }
​
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (methodName.equals("wakeup")){
      System.out.println("晨安~~~");
     }else if(methodName.equals("sleep")){
      System.out.println("晚安~~~");
     }
​
    return method.invoke(bean, args);
   }
}

履行代码:

public static void main(String[] args) {
  JdkProxy proxy = new JdkProxy(new Student("张三"));
  Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
  student.wakeup();
  student.sleep();proxy = new JdkProxy(new Doctor("王教授"));
  Person doctor = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
  doctor.wakeup();
  doctor.sleep();proxy = new JdkProxy(new Dog("旺旺"));
  Animal dog = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
  dog.wakeup();
  dog.sleep();proxy = new JdkProxy(new Cat("咪咪"));
  Animal cat = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
  cat.wakeup();
  cat.sleep();
}

解说:

能够看到,相关于静态署理类来说,无论有多少接口,这里只需求一个署理类。中心代码也很简略。仅有需求留意的点有以下2点:

  • JDK动态署理是需求声明接口的,创立一个动态署理类有必要得给这个”虚拟“的类一个接口。能够看到,这时分经动态署理类发明之后的每个bean现已不是原来那个方针了。

    重学Android基础系列篇(三):架构动态编程技术原理

  • 为什么这里JdkProxy还需求结构传入原有的bean呢?因为处理完附加的功用外,需求履行原有bean的办法,以完结署理的职责。

    这里JdkProxy最中心的办法便是

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    

    其间proxy为署理过之后的方针(并不是原方针),method为被署理的办法,args为办法的参数。

    如果你不传原有的bean,直接用method.invoke(proxy, args)的话,那么就会堕入一个死循环。

能够署理什么

JDK的动态署理是也平时咱们运用的最多的一种署理办法。也叫做接口署理。前几天有一个小伙伴在群里问我,动态署理是否一次能够署理一个类,多个类可不能够。

JDK动态署理说白了仅仅依据接口”随便“来生成类,至于详细的履行,都被署理到了InvocationHandler 的完结类里。上述比方我是需求持续履行原有bean的逻辑,才将原有的bean结构进来。只要你需求,你能够结构进任何方针到这个署理完结类。也便是说,你能够传入多个方针,或许说你什么类都不署理。仅仅为某一个接口”随便“的生成多个署理实例,这多个署理实例最终都会进入InvocationHandler的完结类来履行某一个段一起的代码。

所以,在以往的项目中的一个实践场景便是,我有多个以yaml界说的规矩文件,经过对yaml文件的扫描,来为每个yaml规矩文件生成一个动态署理类。而完结这个,我只需求事先界说一个接口,和界说InvocationHandler的完结类就能够了,同时把yaml解析过的方针传入。最终这些动态署理类都会进入invoke办法来履行某个一起的逻辑。

4.Cglib动态署理

Spring在5.X之前默许的动态署理完结一直是jdk动态署理。可是从5.X开端,spring就开端默许运用Cglib来作为动态署理完结。而且springboot从2.X开端也转向了Cglib动态署理完结。

是什么导致了spring体系全体转投Cglib呢,jdk动态署理又有什么缺陷呢?

那么咱们现在就要来说下Cglib的动态署理。

Cglib是一个开源项目,它的底层是字节码处理结构ASM,Cglib供给了比jdk更为强壮的动态署理。首要比较jdk动态署理的优势有:

  • jdk动态署理只能依据接口,署理生成的方针只能赋值给接口变量,而Cglib就不存在这个问题,Cglib是经过生成子类来完结的,署理方针既能够赋值给完结类,又能够赋值给接口。
  • Cglib速度比jdk动态署理更快,功用更好。

那何谓经过子类来完结呢?

仍是前面那个比方,咱们要完结相同的作用。代码如下

创立CglibProxy类,用于一致署理:

public class CglibProxy implements MethodInterceptor {
​
  private Enhancer enhancer = new Enhancer();
​
  private Object bean;
​
  public CglibProxy(Object bean) {
    this.bean = bean;
   }
​
  public Object getProxy(){
    //设置需求创立子类的类
    enhancer.setSuperclass(bean.getClass());
    enhancer.setCallback(this);
    //经过字节码技能动态创立子类实例
    return enhancer.create();
   }
  //完结MethodInterceptor接口办法
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    String methodName = method.getName();
    if (methodName.equals("wakeup")){
      System.out.println("晨安~~~");
     }else if(methodName.equals("sleep")){
      System.out.println("晚安~~~");
     }
​
    //调用原bean的办法
    return method.invoke(bean,args);
   }
}

履行代码:

public static void main(String[] args) {
  CglibProxy proxy = new CglibProxy(new Student("张三"));
  Student student = (Student) proxy.getProxy();
  student.wakeup();
  student.sleep();proxy = new CglibProxy(new Doctor("王教授"));
  Doctor doctor = (Doctor) proxy.getProxy();
  doctor.wakeup();
  doctor.sleep();proxy = new CglibProxy(new Dog("旺旺"));
  Dog dog = (Dog) proxy.getProxy();
  dog.wakeup();
  dog.sleep();proxy = new CglibProxy(new Cat("咪咪"));
  Cat cat = (Cat) proxy.getProxy();
  cat.wakeup();
  cat.sleep();
}

解说:

在这里用Cglib作为署理,其思路和jdk动态署理差不多。也需求把原始bean结构传入。原因上面有说,这里不多赘述。

要害的代码在这里

//设置需求创立子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
//经过字节码技能动态创立子类实例
return enhancer.create();

能够看到,Cglib”随便”的发明了一个原bean的子类,并把Callback指向了this,也便是当时方针,也便是这个proxy方针。从而会调用intercept办法。而在intercept办法里,进行了附加功用的履行,最终仍是调用了原始bean的相应办法。

在debug这个生成的署理方针时,咱们也能看到,Cglib是随便生成了原始bean的子类:

重学Android基础系列篇(三):架构动态编程技术原理

5.javassist动态署理

Javassist是一个开源的剖析、编辑和创立Java字节码的类库,能够直接编辑和生成Java生成的字节码。相关于bcel, asm等这些工具,开发者不需求了解虚拟机指令,就能动态改变类的结构,或许动态生成类。

在日常运用中,javassit通常被用来动态修正字节码。它也能用来完结动态署理的功用。

话不多说,仍是一样的比方,我用javassist动态署理来完结一遍

创立JavassitProxy,用作一致署理:

public class JavassitProxy {
​
  private Object bean;
​
  public JavassitProxy(Object bean) {
    this.bean = bean;
   }
​
  public Object getProxy() throws IllegalAccessException, InstantiationException {
    ProxyFactory f = new ProxyFactory();
    f.setSuperclass(bean.getClass());
    f.setFilter(m -> ListUtil.toList("wakeup","sleep").contains(m.getName()));
​
    Class c = f.createClass();
    MethodHandler mi = (self, method, proceed, args) -> {
      String methodName = method.getName();
      if (methodName.equals("wakeup")){
        System.out.println("晨安~~~");
       }else if(methodName.equals("sleep")){
        System.out.println("晚安~~~");
       }
      return method.invoke(bean, args);
     };
    Object proxy = c.newInstance();
     ((Proxy)proxy).setHandler(mi);
    return proxy;
   }
}

履行代码:

public static void main(String[] args) throws Exception{
  JavassitProxy proxy = new JavassitProxy(new Student("张三"));
  Student student = (Student) proxy.getProxy();
  student.wakeup();
  student.sleep();proxy = new JavassitProxy(new Doctor("王教授"));
  Doctor doctor = (Doctor) proxy.getProxy();
  doctor.wakeup();
  doctor.sleep();proxy = new JavassitProxy(new Dog("旺旺"));
  Dog dog = (Dog) proxy.getProxy();
  dog.wakeup();
  dog.sleep();proxy = new JavassitProxy(new Cat("咪咪"));
  Cat cat = (Cat) proxy.getProxy();
  cat.wakeup();
  cat.sleep();
}

解说:

了解的配方,了解的滋味,大致思路也是相似的。同样把原始bean结构传入。能够看到,javassist也是用”随便“生成子类的办法类来处理,代码的最终也是调用了原始bean的方针办法完结署理。

javaassit比较有特点的是,能够对所需求署理的办法用filter来设定,里面能够像Criteria结构器那样进行结构。其他的代码,如果你仔细看了之前的代码演示,应该能很容易看懂了。

重学Android基础系列篇(三):架构动态编程技术原理

6.ByteBuddy动态署理

ByteBuddy,字节码店员,一听就很牛逼有不。

ByteBuddy也是一个大名鼎鼎的开源库,和Cglib一样,也是依据ASM完结。还有一个名望更大的库叫Mockito,相信不少人用过这玩意写过测试用例,其间心便是依据ByteBuddy来完结的,能够动态生成mock类,十分便利。别的ByteBuddy别的一个大的运用便是java agent,其首要作用便是在class被加载之前对其拦截,刺进自己的代码。

ByteBuddy十分强壮,是一个神器。能够运用在许多场景。可是这里,只介绍用ByteBuddy来做动态署理,关于其他运用办法,可能要专门写一篇来叙述,这里先给自己挖个坑。

来,仍是了解的比方,了解的配方。用ByteBuddy咱们再来完结一遍前面的比方

创立ByteBuddyProxy,做一致署理:

public class ByteBuddyProxy {
​
  private Object bean;
​
  public ByteBuddyProxy(Object bean) {
    this.bean = bean;
   }
​
  public Object getProxy() throws Exception{
    Object object = new ByteBuddy().subclass(bean.getClass())
         .method(ElementMatchers.namedOneOf("wakeup","sleep"))
         .intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean)))
         .make()
         .load(ByteBuddyProxy.class.getClassLoader())
         .getLoaded()
         .newInstance();
    return object;
   }
​
  public class AopInvocationHandler implements InvocationHandler {
​
    private Object bean;
​
    public AopInvocationHandler(Object bean) {
      this.bean = bean;
     }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String methodName = method.getName();
      if (methodName.equals("wakeup")){
        System.out.println("晨安~~~");
       }else if(methodName.equals("sleep")){
        System.out.println("晚安~~~");
       }
      return method.invoke(bean, args);
     }
   }
}

履行代码:

public static void main(String[] args) throws Exception{
  ByteBuddyProxy proxy = new ByteBuddyProxy(new Student("张三"));
  Student student = (Student) proxy.getProxy();
  student.wakeup();
  student.sleep();proxy = new ByteBuddyProxy(new Doctor("王教授"));
  Doctor doctor = (Doctor) proxy.getProxy();
  doctor.wakeup();
  doctor.sleep();proxy = new ByteBuddyProxy(new Dog("旺旺"));
  Dog dog = (Dog) proxy.getProxy();
  dog.wakeup();
  dog.sleep();proxy = new ByteBuddyProxy(new Cat("咪咪"));
  Cat cat = (Cat) proxy.getProxy();
  cat.wakeup();
  cat.sleep();
}

解说:

思路和之前仍是一样,经过仔细观察代码,ByteBuddy也是选用了发明子类的办法来完结动态署理。

重学Android基础系列篇(三):架构动态编程技术原理

7.各种动态署理的功用如何

前面介绍了4种动态署理关于同一比方的完结。关于署理的形式能够分为2种:

  • JDK动态署理选用接口署理的形式,署理方针只能赋值给接口,答应多个接口
  • Cglib,Javassist,ByteBuddy这些都是选用了子类署理的形式,署理方针既能够赋值给接口,又能够仿制给详细完结类

Spring5.X,Springboot2.X只要都选用了Cglib作为动态署理的完结,那是不是cglib功用是最好的呢?

我这里做了一个简略而粗暴的试验,直接把上述4段履行代码进行单线程同步循环多遍,用耗时来确认他们4个的功用。应该能看出些端倪。

JDK PROXY循环10000遍所耗时:0.714970125秒
Cglib循环10000遍所耗时:0.434937833秒
Javassist循环10000遍所耗时:1.294194708秒
ByteBuddy循环10000遍所耗时:9.731999042秒

履行的成果如上

从履行成果来看,的确是cglib作用最好。至于为什么ByteBuddy履行那么慢,纷歧定是ByteBuddy功用差,也有可能是我测试代码写的有问题,没有找到正确的办法。所以这只能作为一个大致的参考。

看来Spring挑选Cglib仍是有道理的。

动态署理技能关于一个常常写开源或是中间件的人来说,是一个神器。这种特性供给了一种新的处理办法。从而使得代码愈加优雅而简略。动态署理关于理解spring的中心思维也有着莫大的帮助,期望对动态署理技能感兴趣的同学能试着去跑一遍示例中的代码,来加强理解。

2、动态编程中心:反射

1.Java反射机制概述

Reflection(反射)是 Java 被视为动态言语的要害,反射机制答应程序在履行期借助于 Reflection API 取得任何类的内部信息,并能直接操作恣意方针的内部特点以及办法

Class c = Class.forName("java.lang.String");                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

加载完类之后,在堆内存的办法区中就发生了一个 Class 类型的方针(一个类只要一个 Class 方针),这个方针就包括了完好类的类的结构信息。咱们能够经过对这个方针看到的类的结构。这个方针就像一面镜子,经过这个镜子看到类的结构,所以,咱们形象地称之为:反射

  • 正常办法:引进需求的 “ 包类 ” 称号 ——> 经过 new 实例化 ——> 取得实例化方针
  • 反射办法:实例化方针 ——> getClass() 办法 ——> 得到完好的 “包类” 称号

2.Java 反射机制供给的功用

  • 在运转时判断恣意一个方针所属的类
  • 在运转时结构恣意一个类的方针
  • 在运转时判断恣意一个类所具有的成员变量和办法
  • 在运转时获取泛型信息
  • 在运转时调用恣意一个方针的成员变量和办法
  • 在运转时处理注解
  • 生成动态署理

3.Java 反射优点和缺陷

优点: 能够完结动态创立方针和编译,体现出很大的灵活性

缺陷: 对功用有影响。运用反射根本上是一种解说操作,咱们能够告诉 JVM,咱们期望做什么而且它满足咱们什么要求,这类操作总是慢于直接履行相同的操作

4.反射相关的首要 API

  • java.lang.Class 代表一个类
  • java.lang.reflect.Method 代表类的办法
  • java.lang.reflect.Field 代表类的成员变量
  • java.lang.reflect.Constructor 代表类的结构器

5.理解Class类并获取Class实例

先界说一个实体类

重学Android基础系列篇(三):架构动态编程技术原理

主程序

public static void main(String[] args) throws ClassNotFoundException {
//经过反射获取类Class方针                                                      
Class<?> cl = Class.forName("com.sww.Refelection.User");
System.out.println(cl);
}  
输出:class com.sww.Refelection.User

一个类在内存中只要一个 Class 方针,一个类被加载之后,类的整个结构都会被封装在 Class 方针中

在 Object 类中界说了以下办法,此办法将被一切子类继承

public final native Class<?> getClass();  

上面的办法回来值的类型是一个 Class 类,此类是 Java 反射的源头,实践上所谓反射从程序的运转成果来看也很好理解,即:能够经过方针反射求出类的称号

关于每个类而言,JRE 都为其保留了一个不变的 Class 类型的方针。一个 Class 方针包括了特定某个结构(class / interface / enum / annotation / primitive type/void / [])

  • Class 自身也是一个类
  • Class 方针只能由系统树立方针
  • 一个加载的类在 JVM 中只会有一个 Class 实例
  • 一个 Class 方针对应的是一个加载到 JVM 中的一个 .class 文件
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成
  • 经过 Class 能够完好地得到一个类中的一切被加载的结构
  • Class 类是 Reflection 的根源,针对任何你想动态加载、运转的类、唯有先取得呼应的 Class 方针

Class 类的常用办法

办法名 功用说明
static Class forName(String name) 回来指定类名 name 的 Class 方针
Object newInstance() 调用缺省结构函数,回来 Class 方针的一个实例
getName() 回来此 Class 方针所表明的实体(类、接口、数组类、或 void)的称号
Class getSuperClass() 回来当时 Class 方针的父类的 Class 方针
Class[] getInterfaces() 获取当时 Class 方针的接口
ClassLoader getClassLoader() 回来该类的类加载器
Constructor[] getConstructors() 回来一个包括某些 Constructor 方针的数组
Method getMethod(String name, Class… T) 回来一个 Method 方针,此方针的形参类型为 paramType
Field[] getDeclaredFields() 回来 Field 方针的一个数组

6.获取 Class 类的实例

public static void main(String[] args) throws Exception {
// 办法一:forName 取得
Class aClass = Class.forName("com.java.demo.reflect.User");
System.out.println(aClass);
// 办法二:经过方针取得
Class aClass1 = new User().getClass();
System.out.println(aClass1);
// 办法三:经过类名.class 取得
Class<User> aClass2 = User.class;
System.out.println(aClass2);
}   

7.一切类型的 Class 方针

ublic static void main(String[] args) {
Class objectClass = Object.class;      // 类
Class comparableClass = Comparable.class;  // 接口
Class aClass = String[].class;        // 一维数组
Class aClass1 = int[][].class;        // 二维数组
Class overrideClass = Override.class;    // 注解
Class elementTypeClass = ElementType.class; // 枚举
Class integerClass = Integer.class;     // 根本数据类型
Class voidClass = void.class;        // void
Class classClass = Class.class;       // Class
System.out.println(objectClass);
System.out.println(comparableClass);
System.out.println(aClass);
System.out.println(aClass1); 
System.out.println(overrideClass);
System.out.println(elementTypeClass);
System.out.println(integerClass); 
System.out.println(voidClass); 
System.out.println(classClass);                                                   
}                                                                

8.Java 内存剖析

重学Android基础系列篇(三):架构动态编程技术原理

9.类的加载与ClassLoader

  • 加载:将 class 文件字节码内容加载到内存中,并将这些静态数据转化成办法区的运转时数据结构,然后生成一个代表这个类的 java.lang.Class 方针
  • 链接:将 Java 类的二进制代码合并到 JVM 的运转状态之中的进程
  • 验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题
  • 预备:正式为类变量(static)分配内存并设置类变量默许初始值的阶段,这些内存都将在办法区中进行分配
  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的进程
  • 初始化:
  • 履行类结构器< clint>() 办法的进程,类结构器< clint>() 办法是由编译期主动收集类中一切类变量的赋值动作和静态代码块中的句子合并发生的(类结构器是结构类信息的,不是结构该类方针的结构器)
  • 当初始化一个类的时分,如果发现其父类还没有进行初始化,则需求先触发其父类的初始化
  • 虚拟时机保证一个类的< clint>() 办法在多线程环境中被正确加锁和同步
public static void main(String[] args) {
/**
* 1、加载到内存,会发生一个类对应的 Class 方针
* 2、链接,链接完毕后 m = 0
* 3、初始化     
<clint>() {  
System.out.println("A 类静态代码块初始化");
m = 300;   
m = 100;    
}    
m = 100;  
*/ 
A a = new A();
System.out.println(A.m);
}     
}   
class A {   
static {
System.out.println("A 类静态代码块初始化"); 
m = 300;
} 
static int m = 100; 
public A() {
System.out.println("A 类的无参结构函数初始化");
}                                              
------------------------------------------------------------------------                             
A 类静态代码块初始化                                                         
A 类的无参结构函数初始化
100

10.获取运转时类的完好结构 & 动态创立方针履行办法

Field、Method、Constructor、SuperClass、Interface、Annotation

  • 完结的悉数接口
  • 所继承的父类
  • 悉数的结构器
  • 悉数的办法
  • 悉数的 Field
  • 注解

11.功用比照剖析

setAccessible

  • Method 和 Field、Constructor 方针都有 setAccessible() 办法
  • setAccessible 作用是启动和禁用拜访安全查看的开关
  • 参数值为 true 则提示反射的方针在运用时应该取消 Java 言语拜访查看
  • 进步反射的效率,如果代码中有必要用反射,而该句子需求频频的被调用,那么请设置为 true
  • 使得原本无法拜访的私有成员也能够拜访
  • 参数值为 false 则指示反射的方针应该施行 Java 言语拜访查看
// 一般办法调用
public static void test01() {
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("一般办法履行 1 亿次:" + (endTime - startTime) + "ms");
} 
// 反射办法调用  
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c = user.getClass();
Method getName = c.getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射办法履行 1 亿次:" + (endTime - startTime) + "ms");
 }
// 反射办法调用,封闭检测 
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 
User user = new User();
Class c = user.getClass();
Method getName = c.getDeclaredMethod("getName", null);
getName.setAccessible(true); 
long startTime = System.currentTimeMillis(); 
for (int i = 0; i < 100000000; i++) { 
getName.invoke(user, null);
}    
long endTime = System.currentTimeMillis();
System.out.println("封闭检测办法履行 1 亿次:" + (endTime - startTime) + "ms");
}   
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test01();    
test02(); 
test03();    
}      
---------------------------------------------------------------------
一般办法履行 1 亿次:4ms 
反射办法履行 1 亿次:119ms 
封闭检测办法履行 1 亿次:87ms  
折叠 

12.经过反射去操作泛型

  • Java 选用泛型擦除的机制来引进泛型,Java 中的泛型仅仅是给编译器 javac 运用的,确保数据的安全性和免去强制类型转化问题,可是,一旦编译完结,一切和泛型有关的类型悉数擦除
  • 为了经过反射操作这些类型,Java 新增了 ParameterizedType,GenericArrayType,TypeVariable 和 WildcardType 几种类型来代表不能被归一到 Class 类中的类型可是又和原始类型齐名的类型
  • ParameterizedType:表明一种参数化类型,比方 Collection< String>
  • GenericArrayType:表明一种元素类型时参数化类型或许类型变量的数组类型
  • TypeVariable :是各种类型变量的公共父接口
  • WildcardType :代表一种通配符类型表达式
public void test01(Map<String, User> map, List<User> list) {
  System.out.println("test01");
}
​
public Map<String, User> test02() {
  System.out.println("test02");
  return null;
}
​
// 经过反射获取泛型
public static void main(String[] args) throws NoSuchMethodException {
  Method test01 = Test01.class.getMethod("test01", Map.class, List.class);
  // 获取通用参数类型
  Type[] genericParameterTypes = test01.getGenericParameterTypes();
  // 迭代遍历
  for (Type genericParameterType : genericParameterTypes) {
    System.out.println("# " + genericParameterType);
    if (genericParameterType instanceof ParameterizedType) {
      // 获取实践参数类型
      Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
      for (Type actualTypeArgument : actualTypeArguments) {
        System.out.println(actualTypeArgument);
       }
     }
   }
​
  Method test02 = Test01.class.getMethod("test02", null);
  Type genericReturnType = test02.getGenericReturnType();
  if (genericReturnType instanceof ParameterizedType) {
    Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
    for (Type actualTypeArgument : actualTypeArguments) {
      System.out.println(actualTypeArgument);
     }
   }
}

留意: invoke办法如果供给了过错的参数,会抛出一个反常,所以要供给一个反常处理器。主张在有必要的时分才运用invoke办法,有如下原因:

  • 1、invoke办法的参数和回来值有必要是Object类型,意味着有必要进行多次类型转化
  • 2、经过反射调用办法比直接调用办法要显着慢一些

13.反射类办法

getConstructor(Class<?>… parameterTypes)

获取类的特定 public 结构办法。参数为办法参数对应 Class 的方针

public class ReflectDemo {
  public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    Constructor<CaseMethod> constructor = CaseMethod.class.getConstructor(int.class, String.class);
    System.out.println(constructor);
   }
}
​
class CaseMethod{
​
  private int id;
  private String name;
  private int cap;
​
  public CaseMethod(int id,String name){
    this.id = id;
    this.name = name;
   }
​
  public CaseMethod(int cap){
​
   }
}

getConstructors()

获取类的一切 public 结构办法

public static void main(String[] args) {
    Constructor<?>[] constructors = CaseMethod.class.getConstructors();
    for (Constructor<?> constructor : constructors) {
      System.out.println(constructor);
     }
   }
}
​
class CaseMethod{
​
  private int id;
  private String name;
  private int cap;
​
  public CaseMethod(int id,String name){
    this.id = id;
    this.name = name;
   }
​
  public CaseMethod(int cap){
​
   }

getDeclaredConstructor(Class<?>… parameterTypes)

  public static void main(String[] args) throws NoSuchMethodException {
    // 留意:因为private修饰的CaseMethod结构函数没有参数,所以getDeclaredConstructor()能够为空
    // 默许的getDeclaredConstructor(Class<?>... parameterTypes)办法是要传参数类型的
    Constructor<CaseMethod> constructor = CaseMethod.class.getDeclaredConstructor();
    Constructor<CaseMethod> declaredConstructor = CaseMethod.class.getDeclaredConstructor(int.class, String.class);
    System.out.println(constructor);
    System.out.println(declaredConstructor);
   }
}
​
class CaseMethod{
​
  private int id;
  private String name;
  private int cap;
​
  private CaseMethod(){
​
   }
​
  public CaseMethod(int id,String name){
    this.id = id;
    this.name = name;
   }

getDeclaredConstructors()

  public static void main(String[] args) throws NoSuchMethodException {
    Constructor<?>[] constructors = CaseMethod.class.getDeclaredConstructors();
    for (Constructor<?> constructor : constructors) {
      System.out.println(constructor);
     }
   }
}
​
class CaseMethod{
​
  private int id;
  private String name;
  private int cap;
​
  private CaseMethod(){
​
   }
​
  public CaseMethod(int id,String name){
    this.id = id;
    this.name = name;
   }

14.运用newInstance创立实例

public class ReflectDemo {
  public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    Constructor<CaseMethod> constructor = CaseMethod.class.getConstructor(String.class);
    CaseMethod caseMethod = constructor.newInstance("Lisa");
    System.out.println(caseMethod);
   }
}
​
class CaseMethod{
​
  private String name;
​
  public CaseMethod(String name){
    this.name = name;
   }
​
  @Override
  public String toString() {
    return "CaseMethod{" +
        "name='" + name + ''' +
        '}';
   }
}

总结

Java取得Class方针的引用的办法中,Class.forName() 办法会主动初始化Class方针,而 .class 办法不会, .class 的初始化被延迟到静态办法或十分数静态域的初次引用。