Java基础四—泛型、注解、异常、反射

泛型

泛型的本质是为了参数化类型( 在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。
也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

引入泛型的意义在于:

  • 代码复用
  • 类型安全(泛型中的类型在使用时指定,不需要强制类型转换)

泛型类

泛型接口

泛型方法

  • 定义泛型方法语法格式
    Java基础四---泛型、注解、异常、反射
  • 调用泛型方法语法格式
    Java基础四---泛型、注解、异常、反射

定义泛型方法时,必须在返回值前边加一个,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。

Class的作用就是指明泛型的具体类型,而Class类型的变量c,可以用来创建泛型类的对象。

Q:为什么要用变量c来创建对象呢?
既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。

泛型的上下限

为了解决泛型中隐含的转换问题,Java泛型加入了类型参数的上下边界机制。表示该类型参数可以是A(上边界)或者A的子类类型。编译时擦除到类型A,即用A类型代替类型参数。

<?> &#x65E0;&#x9650;&#x5236;&#x901A;&#x914D;&#x7B26;
<? extends E> extends &#x5173;&#x952E;&#x5B57;&#x58F0;&#x660E;&#x4E86;&#x7C7B;&#x578B;&#x7684;&#x4E0A;&#x754C;&#xFF0C;&#x8868;&#x793A;&#x53C2;&#x6570;&#x5316;&#x7684;&#x7C7B;&#x578B;&#x53EF;&#x80FD;&#x662F;&#x6240;&#x6307;&#x5B9A;&#x7684;&#x7C7B;&#x578B;&#xFF0C;&#x6216;&#x8005;&#x662F;&#x6B64;&#x7C7B;&#x578B;&#x7684;&#x5B50;&#x7C7B;
<? super E> super &#x5173;&#x952E;&#x5B57;&#x58F0;&#x660E;&#x4E86;&#x7C7B;&#x578B;&#x7684;&#x4E0B;&#x754C;&#xFF0C;&#x8868;&#x793A;&#x53C2;&#x6570;&#x5316;&#x7684;&#x7C7B;&#x578B;&#x53EF;&#x80FD;&#x662F;&#x6307;&#x5B9A;&#x7684;&#x7C7B;&#x578B;&#xFF0C;&#x6216;&#x8005;&#x662F;&#x6B64;&#x7C7B;&#x578B;&#x7684;&#x7236;&#x7C7B;

// &#x4F7F;&#x7528;&#x539F;&#x5219;&#x300A;Effictive Java&#x300B;
// &#x4E3A;&#x4E86;&#x83B7;&#x5F97;&#x6700;&#x5927;&#x9650;&#x5EA6;&#x7684;&#x7075;&#x6D3B;&#x6027;&#xFF0C;&#x8981;&#x5728;&#x8868;&#x793A; &#x751F;&#x4EA7;&#x8005;&#x6216;&#x8005;&#x6D88;&#x8D39;&#x8005; &#x7684;&#x8F93;&#x5165;&#x53C2;&#x6570;&#x4E0A;&#x4F7F;&#x7528;&#x901A;&#x914D;&#x7B26;&#xFF0C;&#x4F7F;&#x7528;&#x7684;&#x89C4;&#x5219;&#x5C31;&#x662F;&#xFF1A;&#x751F;&#x4EA7;&#x8005;&#x6709;&#x4E0A;&#x9650;&#x3001;&#x6D88;&#x8D39;&#x8005;&#x6709;&#x4E0B;&#x9650;
1. &#x5982;&#x679C;&#x53C2;&#x6570;&#x5316;&#x7C7B;&#x578B;&#x8868;&#x793A;&#x4E00;&#x4E2A; T &#x7684;&#x751F;&#x4EA7;&#x8005;&#xFF0C;&#x4F7F;&#x7528; < ? extends T>;
2. &#x5982;&#x679C;&#x5B83;&#x8868;&#x793A;&#x4E00;&#x4E2A; T &#x7684;&#x6D88;&#x8D39;&#x8005;&#xFF0C;&#x5C31;&#x4F7F;&#x7528; < ? super T>&#xFF1B;
3. &#x5982;&#x679C;&#x65E2;&#x662F;&#x751F;&#x4EA7;&#x53C8;&#x662F;&#x6D88;&#x8D39;&#xFF0C;&#x90A3;&#x4F7F;&#x7528;&#x901A;&#x914D;&#x7B26;&#x5C31;&#x6CA1;&#x4EC0;&#x4E48;&#x610F;&#x4E49;&#x4E86;&#xFF0C;&#x56E0;&#x4E3A;&#x4F60;&#x9700;&#x8981;&#x7684;&#x662F;&#x7CBE;&#x786E;&#x7684;&#x53C2;&#x6570;&#x7C7B;&#x578B;&#x3002;

类型擦除

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了”伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的”类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

泛型的类型擦除的原则是

  • 消除类型参数声明,即删除<>及其包围的部分。
  • 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
  • 为了保证类型安全,必要时插入强制类型转换代码。
  • 自动产生”桥接方法”以保证擦除类型后的代码仍然具有泛型的”多态性”。

如何进行类型擦除?

  • 擦除类定义中的类型参数 – 无限制类型擦除
    当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如和的类型参数都被替换为Object。
  • 擦除类定义中的类型参数 – 有限制类型擦除
    当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,比如形如和的类型参数被替换为Number,被替换为Object。
  • 擦除方法定义中的类型参数
    擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的。

如何理解类型擦除后保留的原始类型

原始类型,就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。
无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

在调用泛型方法时,可以指定泛型,也可以不指定泛型:

  • 在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object
  • 在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类

如何理解泛型的编译期检查?

既然说类型变量会在编译的时候擦除掉,那为什么我们往 ArrayList 创建的对象中添加整数会报错呢?不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?

Java编译器是通过 先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。

类型检查就是编译时完成的,new ArrayList()只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正设计类型检查的是它的引用,因为我们是使用它引用list1来调用它的方法,比如说调用add方法,所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型,所以不行。

public class Test {
    public static void main(String[] args) {
        ArrayList<string> list1 = new ArrayList();
        list1.add("1"); //&#x7F16;&#x8BD1;&#x901A;&#x8FC7;
        list1.add(1); //&#x7F16;&#x8BD1;&#x9519;&#x8BEF;
        String str1 = list1.get(0); //&#x8FD4;&#x56DE;&#x7C7B;&#x578B;&#x5C31;&#x662F;String

        ArrayList list2 = new ArrayList<string>();
        list2.add("1"); //&#x7F16;&#x8BD1;&#x901A;&#x8FC7;
        list2.add(1); //&#x7F16;&#x8BD1;&#x901A;&#x8FC7;
        Object object = list2.get(0); //&#x8FD4;&#x56DE;&#x7C7B;&#x578B;&#x5C31;&#x662F;Object

        new ArrayList<string>().add("11"); //&#x7F16;&#x8BD1;&#x901A;&#x8FC7;
        new ArrayList<string>().add(22); //&#x7F16;&#x8BD1;&#x9519;&#x8BEF;

        String str2 = new ArrayList<string>().get(0); //&#x8FD4;&#x56DE;&#x7C7B;&#x578B;&#x5C31;&#x662F;String
    }
}
</string></string></string></string></string>

类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。

泛型中参数化类型为什么不考虑继承关系?
泛型出现的原因,就是为了解决类型转换的问题,我们不能违背它的初衷

如何理解泛型的多态?泛型的桥接方法

类型擦除会造成多态的冲突,而JVM解决方法就是桥接方法。
见文章https://pdai.tech/md/java/basic/java-basic-x-generic.html 不在此处做过多深入

如何理解基本类型不能作为泛型类型?

因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储int值,只能引用Integer的值。

如何理解泛型类型不能实例化?

不能实例化泛型类型, 这本质上是由于类型擦除决定的
因为在 Java 编译期没法确定泛型参数化类型,也就找不到对应的类字节码文件,所以自然就不行了,此外由于T 被擦除为 Object,如果可以 new T() 则就变成了 new Object(),失去了本意。

如果我们确实需要实例化一个泛型,应该如何做呢?可以通过反射实现。

如何正确的初始化泛型数组实例?

使用反射来初始化泛型数组算是优雅实现,因为泛型类型 T 在运行时才能被确定下来,我们能创建泛型数组也必然是在 Java 运行时想办法,而运行时能起作用的技术最好的就是反射了。

泛型类中的静态方法和静态变量

泛型类中的静态方法和静态变量不能使用泛型类所声明的泛型类型参数

因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。

如何理解异常中使用泛型?

  1. 不能抛出也不能捕获泛型类的对象。
  2. 不能在catch子句中使用泛型变量。
  3. 在异常声明中可以使用类型变量。

如何获取泛型的参数类型?

可以通过反射(java.lang.reflect.Type)获取泛型

注解

注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。
它主要的作用有以下四方面:

  • 生成文档,通过代码里标识的元数据生成javadoc文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

注解的常见分类

内置注解

@Override

这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。
用于告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。

@Deprecated

它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是告诉编译器被修饰的程序元素已被”废弃”,不再建议用户使用。

@SuppressWarnings

它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:

参数 作用 all 抑制所有警告 boxing 抑制装箱、拆箱操作时候的警告 cast 抑制映射相关的警告 dep-ann 抑制启用注释的警告 deprecation 抑制过期方法警告 fallthrough 抑制确在switch中缺失breaks的警告 finally 抑制finally模块没有返回的警告 hiding 抑制与隐藏变数的区域变数相关的警告 incomplete-switch 忽略没有完整的switch语句 nls 忽略非nls格式的字符 null 忽略对null的操作 rawtype 使用generics时忽略没有指定相应的类型 restriction 抑制与使用不建议或禁止参照相关的警告 serial 忽略在serializable类中没有声明serialVersionUID变量 static-access 抑制不正确的静态访问方式警告 synthetic-access 抑制子类没有按最优方法访问内部类的警告 unchecked 抑制没有进行类型检查操作的警告 unqualified-field-access 抑制没有权限访问的域的警告 unused 抑制没被使用过的代码的警告

元注解

@Target

Target注解用来说明那些被它所注解的注解类可修饰的对象范围

@Retention & @RetentionTarget

Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时

@Documented

描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。

@Inherited

Inherited注解的作用:被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

@Repeatable(JDK1.8新增)
@Native(JDK1.8新增)

注解与反射接口

定义注解后,如何获取注解中的内容呢?反射包java.lang.reflect下的AnnotatedElement接口提供这些方法。这里注意:只有注解被定义为RUNTIME后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。

异常

Java异常是Java提供的一种识别及响应错误的一致性机制,java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。

Java通过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的 错误条件。当条件生成时,错误将引发异常。

Java异常结构层次图如下:

Java基础四---泛型、注解、异常、反射

Throwable

Throwable 是 Java 语言中所有错误与异常的超类。
Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

Error

Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。

Exception

程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。

  • 运行时异常
    都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
    运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常 (编译异常)
是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

JVM处理异常的机制

提到JVM处理异常的机制,就需要提及Exception Table,以下称为异常表。

异常表中包含了一个或多个异常处理者(Exception Handler)的信息,这些信息包含如下

  • from 可能发生异常的起始点
  • to 可能发生异常的结束点
  • target 上述from和to之前发生异常后的异常处理者的位置
  • type 异常处理者处理的异常的类信息

异常表用在什么时候呢?

答案是异常发生的时候,当一个异常发生时

  1. JVM会在当前出现异常的方法中,查找异常表,是否有合适的处理者来处理
  2. 如果当前方法异常表不为空,并且异常符合处理者的from和to节点,并且type也匹配,则JVM调用位于target的调用者来处理。
  3. 如果上一条未找到合理的处理者,则继续查找异常表中的剩余条目
  4. 如果当前方法的异常表无法处理,则向上查找(弹栈处理)刚刚调用该方法的调用处,并重复上面的操作。
  5. 如果所有的栈帧被弹出,仍然没有处理,则抛给当前的Thread,Thread则会终止。
  6. 如果当前Thread为最后一个非守护线程,且未处理异常,则会导致JVM终止运行。

反射

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

RRIT(运行时类型识别)

RRIT(Run-Time Type Identification)运行时类型识别,其作用是在运行时识别一个对象的类型和类的信息。
主要有两种方式:一种是”传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是”反射”机制,它允许我们在运行时发现和使用类的信息。

Class类

Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class ans enum)或接口(interface and annotation)(每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName(“类名”)等方法获取class对象)。
数组同样也被映射为为class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 class 对象。

总结

  • Class类也是类的一种,与class关键字是不一样的。
  • 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
  • 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
  • Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
  • Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。

Java类加载机制

见文章《Java基础九—JVM》

反射的使用

在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。
在反射包中,我们常用的类主要有
Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、
Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、
Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)

反射机制的执行流程

Java基础四---泛型、注解、异常、反射

总结

  1. 反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化;
  2. 每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上;
  3. 反射也是考虑了线程安全的,放心使用;
  4. 反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销;
  5. 反射调用多次生成新代理Accessor, 而通过字节码生存的则考虑了卸载功能,所以会使用独立的类加载器;
  6. 当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;
  7. 调度反射方法,最终是由jvm执行invoke0()执行;

Original: https://www.cnblogs.com/winter0730/p/15311832.html
Author: cos晓风残月
Title: Java基础四—泛型、注解、异常、反射

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/576270/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球