泛型、注解与反射、动态代理原理

泛型

泛型(Generic)是Java编程语言的强大功能。它们提高了代码的类型安全性,使你在编译时可以检测
到更多错误。

  • 为什么要使用泛型?
    1、在编译时进行更强的类型检查。Java编译器将强类型检查应用于通用代码,如果代码违反类型安
    全,则会发出错误。修复编译时错误比修复运行时错误容易,后者可能很难找到
    2、消除类型转换。以下不带泛型的代码段需要强制转换
List list = new ArrayList(); 
list.add("hello"); 
String s = (String) list.get(0);

当使用泛型重写时,代码不需要强制转换

List list = new ArrayList(); 
list.add("hello"); 
String s = list.get(0); // no cast

3、使程序员能够实现通用算法。通过使用泛型,程序员可以实现对不同类型的集合进行工作,可以自
定义并且类型安全且易于阅读的泛型算法。

注解与反射

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关
于程序但不属于程序本身的数据
Java中所有的注解,默认实现 Annotation 接口:


image.png
  • 声明一个"Class"不同的是,注解的声明使用 @interface 关键字
public @interface Demo{ }
  • 元注解
    在定义注解时,注解类也能够使用其他的注解声明。@Documented 与 @Inherited 元注解,前者用于被javadoc工具提取成文档,后者表示允许子类
    继承父类中定义的注解
  • Target:注解标记另一个注解,以限制可以应用注解的 Java 元素类型
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

ElementType.ANNOTATION_TYPE 可以应用于注解类型。
ElementType.CONSTRUCTOR 可以应用于构造函数。
ElementType.FIELD 可以应用于字段或属性。
ElementType.LOCAL_VARIABLE 可以应用于局部变量。
ElementType.METHOD 可以应用于方法级注解。
ElementType.PACKAGE 可以应用于包声明。
ElementType.PARAMETER 可以应用于方法的参数。
ElementType.TYPE 可以应用于类的任何元素。

  • @Retention:指定标记注解的存储方式
    RetentionPolicy.SOURCE - 标记的注解仅保留在源级别中,并被编译器忽略。
    RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
    RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。
image.png

APT注解处理器

APT全称为:"Anotation Processor Tools",意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。注解处理器是对注解应用最为广泛的场景。

  • 在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用
    框架中都有注解处理器的身影。这些框架中对注解的定义并不是 SOURCE 级别,更多的
    是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。

CLASS

定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。


image.png

我们使用普通的编程方式,需要在代码中进行 if-else 的判断,也许存在十个判断点,则需要在每个判断点加入此项判断。此时,可以借助AOP(面向切面)编程思想,将程序中所有功能点划分为: 需要登录 与 无需登录两种类型,即两个切面。对于切面的区分即可采用注解。
[图片上传中...(image.png-29b91a-1589702240867-0)]

//Java源码
 @Target(ElementType.METHOD) 
@Retention(RetentionPolicy.CLASS) 
public @interface Login { }
@Login
 public void jumpA()
{ startActivity(new Intent(this,AActivity.class)); }
public void jumpB()
{ startActivity(new Intent(this,BActivity.class)); }

RUNTIME

注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。

反射

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。

  • Java反射机制主要提供了以下功能:
  • 在运行时构造任意一个类的对象
  • 在运行时获取或者修改任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法(属性)
  • Class
    反射始于Class,Class是一个类,封装了当前对象所对应的类的信息

获取Class对象的三种方式

  1. 通过类名获取 类名.class
  2. 通过对象获取 对象名.getClass()
  3. 通过全类名获取 Class.forName(全类名) classLoader.loadClass(全类名)
  • 用 instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的
    isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法
public native boolean isInstance(Object obj); 

判断是否为某个类的类型

public boolean isAssignableFrom(Class cls)

创建实例

通过反射来生成对象主要有两种方式。

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class c = String.class; 
Object str = c.newInstance();

先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例

//获取String所对应的Class对象
 Class c = String.class;
 //获取String类带一个String参数的构造器 
Constructor constructor = c.getConstructor(String.class);
 //根据构造器创建实例 
Object obj = constructor.newInstance("23333"); 
System.out.println(obj);

动态代理原理

  • 静态代理
    代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
    目的:
    (1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;
    (2)通过代理对象对访问进行控制;


    image.png
  • 抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口

  • 真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业务逻辑在此。

  • 代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制都放到代理角色中处理!

  • 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。一般来说,被代理对象和代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。

  • 静态代理,一对一则会出现时静态代理对象量多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。

动态代理

在运行时再创建代理类和其实例,因此显然效率更低。要完成这个场景,需要在运行期动态创建一个Class。JDK提供了 Proxy 来完成这件事情。基本使用如下:

//抽象角色
 interface Api { void test(String a); }
//真实角色
class ApiImpl{ @Override public void test(String a) 
{ System.out.println("真实实现:" + a); } }
//创建真实角色实例
 ApiImpl api = new ApiImpl(); 
//JDK动态代理:
 Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Api.class}, 
//JDK实现只能代理接口 
new InvocationHandler() 
{ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
//执行真实对象方法
 return method.invoke(api, args); } });
  • 实际上, Proxy.newProxyInstance 会创建一个Class,与静态代理不同,这个Class不是由具体的.java源文件编译而来,即没有真正的文件,只是在内存中按照Class格式生成了一个Class。
image.png

还带完善。。。

你可能感兴趣的:(泛型、注解与反射、动态代理原理)