泛型
泛型(Generic)是Java编程语言的强大功能。它们提高了代码的类型安全性,使你在编译时可以检测
到更多错误。
- 为什么要使用泛型?
1、在编译时进行更强的类型检查。Java编译器将强类型检查应用于通用代码,如果代码违反类型安
全,则会发出错误。修复编译时错误比修复运行时错误容易,后者可能很难找到
2、消除类型转换。以下不带泛型的代码段需要强制转换
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
当使用泛型重写时,代码不需要强制转换
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
3、使程序员能够实现通用算法。通过使用泛型,程序员可以实现对不同类型的集合进行工作,可以自
定义并且类型安全且易于阅读的泛型算法。
注解与反射
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关
于程序但不属于程序本身的数据
Java中所有的注解,默认实现 Annotation 接口:
- 声明一个"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 保留,因此运行时环境可以使用它。
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文件以达到修改代码执行逻辑的目的。
我们使用普通的编程方式,需要在代码中进行 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对象的三种方式
- 通过类名获取 类名.class
- 通过对象获取 对象名.getClass()
- 通过全类名获取 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)通过代理对象对访问进行控制;
抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口
真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业务逻辑在此。
代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制都放到代理角色中处理!
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。一般来说,被代理对象和代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。
静态代理,一对一则会出现时静态代理对象量多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。
动态代理
在运行时再创建代理类和其实例,因此显然效率更低。要完成这个场景,需要在运行期动态创建一个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。
还带完善。。。