注解Annotation

基本概念

Annotation是JDK5.0新增的功能,其和类Class、枚举Enum以及接口Interface均属于并列关系。
注解&泛型&反射&枚举 - 图1
Annotation就是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理逻辑。程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗。
使用场所

  • 场所1:生成文档相关的注解
  • 场所2:在编译时进行格式检查

JDK内置的三个基本注解
@Override:限定重写父类方法,该注解只能用于方法
@Deprecated:用于表示所修饰的元素(类, 方法等)已过时。
@SuppressWarnings:抑制编译器警告

  • 场所3:跟踪代码依赖性,实现替代配置文件功能

    自定义注解与4种元注解(JDK8.0之前)

  1. 自定义注解格式(可参考系统提供的@SuppressWarnings注解的写法)
    1. 注解声明为:@interface
    2. 内部定义成员,通常使用value表示
    3. 可以使用default指定成员的默认值
    4. 如果自定义注解没有成员,表明是一个标识作用。
  2. JDK提供的4种元注解(元注解:针对注解的注解)
    1. @Retention:指定所修饰的注解的生命周期。值:SOURCE/CLASS(默认)/RUNTIM(只有它才能通过反射获取)
    2. @Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素。

注解&泛型&反射&枚举 - 图2
*如下出现的频率较低*

  1. Documented:表示所修饰的注解在被javadoc解析时可以被保留下来。
  2. Inherited:被它修饰的 Annotation 将具有继承性。
    1. 自定义注解案例

注解&泛型&反射&枚举 - 图3

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
  3. @Inherited
  4. public @interface MyAnnotation {
  5. String value() default "hello";
  6. }

JDK8.0新增的注解特性

  1. 新增可重复注解

注解&泛型&反射&枚举 - 图4

  1. 新增两个类型注解:TYPE_PARAMETER、TYPE_USE

注解&泛型&反射&枚举 - 图5注解&泛型&反射&枚举 - 图6

泛型Generic

概念说明

泛型的核心本质

  • 参数化类型(Parameterized type),即向类/接口/方法传入附加参数。如允许程序员在创建集合时再指定集合元素的类型,如:List,这表明该List只能保存字符串类型的对象,该String类型也可以在List类其他多个地方使用。
  • JDK1.5之后支持泛型。

泛型的重要特征

  • 泛型只在编译阶段有效(不同参数类型T组成的泛型,其本质都是同一个类型)。在编译之后(生成class文件)程序会采取去泛型化的措施。
  • 在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

image.png
使用注意点

  • 不能在静态方法和异常类里面使用类定义的泛型类型(静态方法可以使用泛型方法)
  • 可以自定义的泛型结构:类/接口/方法

    泛型类和泛型接口

    基本概念
  1. 泛型类型用于类/接口的定义中,被称为泛型类/接口。
  2. 泛型的类型参数只能是引用类型,不能是基本类型
  3. 在实例化泛型类时,可以不指定T的具体类型,如不指定的话则为Object类型。 ```xml //自定义泛型类格式 public class Fly {
    1. //构造器不需要再手动指明泛型了
    public Fly() { } private T name; public T getName() {
    1. return name;
    } public void setName(T name) {
    1. this.name = name;
    } }

//自定义泛型接口格式 public interface IFlyable { T say(T name); }

//使用 Fly fly = new Fly<>(); Fly fly = new Fly<>();

  1. **泛型类/接口在继承时的使用**<br />父类有泛型,子类可以选择保留泛型也可以选择自定义泛型类型。<br />有如下一个父类:`_public class Fly<T,K>_`
  2. 1. 子类不是泛型
  3. 继承时擦除父类的泛型规定:`_public class FlySon<T> extends Fly_`等价于`_public _class _FlySon<T> _extends Father<Object>`<br />继承时指定父类泛型具体类型:`_public class FlySon extends Fly<Integer,Integer>_`
  4. 2. 子类也是泛型
  5. 全部保留:`_public class FlySon<T,K> extends Fly<T,K>_`<br />部分保留:`_public class FlySon<T> extends Fly<T,Integer>_`<br />子类保留并扩展__`_public class FlySon<T,K,V> extends Fly<T,K>_`
  6. <a name="I6qW5"></a>
  7. ## 泛型方法
  8. **泛型方法和泛型类具有本质差别**
  9. - 泛型类是在实例化类的时候指明泛型的具体类型;泛型方法是在调用方法的时候指明泛型的具体类型。
  10. - 泛型类和泛型方法的使用是并列的。
  11. **静态方法与泛型方法**<br />静态方法无法访问泛型类中的T,即带T的属性/方法不能加static字段修饰,但是泛型方法可以和static同时使用。<br />原因:泛型类是在实例化阶段才能确定具体参数类型(如果使用static,那么类加载还未实例化就没办法确定类型);而泛型方法是在调用的时候才确定类型,不管是实例对象还是实例类都是传入数据调用时确定。<br />**泛型方法的格式**:`[访问权限]<泛型>返回类型 方法名([泛型标识 参数名称]) 抛出的异常`
  12. ```xml
  13. public class Fly<T> {
  14. private T name;
  15. //这不是泛型方法,这只是一个普通的方法,只不过其参数使用了泛型T而已
  16. public T getName(T name) {
  17. this.name = name;
  18. return this.name;
  19. }
  20. //格式:[访问权限]<泛型>返回类型 方法名([泛型标识 参数名称]) 抛出的异常
  21. //这是泛型方法,E的类型需要等到调用的时候传入
  22. public <E> E getValue(E tableName) {
  23. //忽略代码意义
  24. E value = null;
  25. return value;
  26. }
  27. }

类型通配符?的使用

通配符?的使用背景
A:我们知道Ingeter是Number的一个子类,同时也知道Generic与Generic实际上是相同的一种类型,其之间没有子父类关系。那么问题来了,在使用Generic作为形参的方法中,能否使用Generic的实例传入呢?在逻辑上类似于Generic和Generic是否可以看成具有父子关系的泛型类型呢?
Q:如果直接使用强转则会报错。此时可以选择使用泛型通配符Generic<?>,可以默认将其从逻辑上当成Generic和Generic的父类。
如:List<?>是List、List等各种泛型List的父类。
使用

  • 使用类型通配符?。比如List<?>是List、List等各种泛型List的父类。
  • 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
  • 写入list中的元素时,不行。因为我们不知道具体的元素类型,我们不能向其中添加对象。唯一的例外是null,它是所有类型的成员。
  • 有限制的通配符
    上限:<? extends Number>:只允许泛型为Number及Number子类的引用调用
    下限:<? super Number>:只允许泛型为Number及Number父类的引用调用
    注解&泛型&反射&枚举 - 图8

    思考如下代码为什么会报错?
    image.png

    反射Reflection

    基本概念

    含义:ClassLoader类的加载器在加载完类之后,会在堆内存的方法区中产生了一个Class类型的对象(一个类只有一个Class对象,即Klass对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构。所以,我们形象的称之为:反射。
    反射被视为是动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
    反射主要的API
    java.lang.Class:代表一个类
    java.lang.reflect.Method:代表类的方法
    java.lang.reflect.Field:代表类的成员变量
    java.lang.reflect.Constructor:代表类的构造

    类加载器

    03-类的加载过程(类的生命周期)详解
    注解&泛型&反射&枚举 - 图10
    注解&泛型&反射&枚举 - 图11

    类的反射对象:java.lang.Class

    基础概念
    在Object类中定义了以下的方法,此方法将被所有子类继承:public final Class getClass()。方法返回值的类型是一个Class类,此类是Java反射的源头。
    对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构class/interface/enum/annotation/primitive type/void/[]的有关信息。

    • Class本身也是一个类,其只能由系统建立对象,一个加载的类在 JVM 中只会有一个Class实例。
    • 每个类的实例都会记得自己是由哪个 Class 实例所生成。
    • 通过Class可以完整地得到一个类中的所有被加载的结构。

    哪些类型可以有Class对象

    • class:外部类、成员(成员内部类,静态内部类)、局部内部类、匿名内部类
    • interface:接口
    • []:数组
    • enum:枚举
    • annotation:注解@interface
    • primitive type:基本数据类型
    • void

    扩展:对于数组来说,只要元素类型与维度一样,就是同一个Class

    1. int[] a = new int[10];
    2. int[] b = new int[100];
    3. Class c10 = a.getClass();
    4. Class c11 = b.getClass();
    5. //输出结果
    6. System.out.println(c10 == c11);//true

    创建反射Class对象的四种方法

    1. 得到某个类存在于内存方法区里唯一类对象的四种方法
    2. /*方式一:【推荐】调用类的class属性。该方法最为安全可靠,程序性能最高* */
    3. Class refClass1 = Student.class;
    4. /*方式二:通过Class类的静态方法forName(类的全限定名)。参数必须包含包名和具体类名,且该类在类路径下。如果类填写错误,会抛出:classNotFoundException的异常*/
    5. Class refClass2 = Class.forName("sub.Student");
    6. /*方式三:已知某个类的实例,调用实例的getClass方法获得。getClass()方法是Object父类的 */
    7. Student son = new Student();
    8. Class refClass4 = son.getClass();
    9. /*方式四:通过类的加载器创建
    10. 通过当前类对象得到ClassLoader(当前方法为静态的类.class;非静态的:this.getClass());再通过ClassLoader.loadClass(类的全限定名)*/
    11. ClassLoader loader=this.getClass().getClassLoader();//当前方法为静态的
    12. ClassLoader loader = Fly.class.getClassLoader();//当前方法为非静态的
    13. Class refClass3 = loader.loadClass("sub.Student");
    14. //效果//
    15. System.out.println(refClass1 == refClass2);//true
    16. System.out.println(refClass2 == refClass3);//true
    17. System.out.println(refClass3 == refClass4);//true

    通过反射Class对象创建类的实例对象
    有了Class对象就可以创建类的实例对象,有两种方式:

    1. 调用类无参的构造器Class.newInstance()

    此方式对类的要求:类必须有一个无参的构造器;类的构造器的访问权限需足够(public)。此方式一般可用于框架反射生成使用,故JavaBean就是要求有无参且public修饰的构造器

    1. Class flyClass = Student.class;
    2. Student stu1 = (Student) flyClass.newInstance();
    1. 调用类有参的构造器生成 ```xml Class flyClass = Student.class; //调用有参的构造器,生成Constructor实例对象 Constructor con = flyClass.getConstructor(Integer.class, String.class); //通过Constructor的实例创建对应类的对象,并初始化类属性 Student stu2 = (Student) con.newInstance(30, “Jack”); System.out.println(stu2);

    //注意:此时Student的类写法 public class Student { //给有参的构造器,参数必须是包装类型的,不可以为int值类型 public Student(Integer age, String name) { this.age = age; this.name = name; } //给无参的构造器 public Student() { } }

    1. **通过反射Class对象得到类的构造器/方法/属性/注解/泛型/所在包**
    2. ```xml
    3. Class flyClass = Student.class;
    4. //1.0 得到类实现的全部接口
    5. Class[] interfaces = flyClass.getInterfaces();
    6. //2.0 得到类继承的父类
    7. Class superclass = flyClass.getSuperclass();
    8. //3.0获取当前运行时类中声明为public的构造器
    9. Constructor[] constructors = flyClass.getConstructors();
    10. //获取当前运行时类中声明的所有的构造器
    11. Constructor[] declaredConstructors = flyClass.getDeclaredConstructors();
    12. //4.0 获取当前运行时类及其所有父类中声明为public权限的方法
    13. Method[] methods = flyClass.getMethods();
    14. //获取当前运行时类中声明的所有方法(不包含父类中声明的方法)
    15. Method[] classDeclaredMethods = flyClass.getDeclaredMethods();
    16. //5.0 获取当前运行时类及其父类中声明为public访问权限的属性
    17. Field[] fields = flyClass.getFields();
    18. //获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
    19. Field[] declaredFields = flyClass.getDeclaredFields();

    Java.lang.reflect.Field(属性的反射对象)

    1. //1.0 创建运行时类的对象
    2. Class clazz = Student.class;
    3. //2.0 调用实例变量
    4. Student student = (Student) clazz.newInstance();
    5. //创建反射Field字段,并要确保其是可访问的
    6. Field clazz_age = clazz.getDeclaredField("age");
    7. clazz_age.setAccessible(true);
    8. //获取、设置指定对象的属性值
    9. clazz_age.set(student, 12);
    10. System.out.println(clazz_age.get(student));
    11. //2.1 调用静态变量
    12. Field clazz_name = clazz.getDeclaredField("name");
    13. clazz_name.setAccessible(true);
    14. clazz_name.set(null, "Hello");
    15. System.out.println(clazz_name.get(null));
    16. //此时Student类
    17. public class Student {
    18. //无参构造器必须设置,否则newInstance()将找不到数据
    19. public Student() {
    20. }
    21. //变量必须为引用类型Integer,不能为值类型int
    22. private Integer age;
    23. //静态变量
    24. private static String name;
    25. }

    方法的反射对象:java.lang.reflect.Method

    通过反射Method对象得到方法的注解/返回值/方法名/参数等

    1. @Test
    2. public void getClassFun(){
    3. Class clazz = Person.class;
    4. Method[] declaredMethods = clazz.getDeclaredMethods();
    5. for(Method m : declaredMethods){
    6. //1.得到方法声明的注解
    7. Annotation[] annos = m.getAnnotations();
    8. for(Annotation a : annos){
    9. System.out.println(a);
    10. }
    11. //2.得到方法的权限修饰符
    12. System.out.print(Modifier.toString(m.getModifiers()) + "\t");
    13. //3.得到方法的返回值类型
    14. System.out.print(m.getReturnType().getName() + "\t");
    15. //4.得到方法名
    16. System.out.print(m.getName());
    17. System.out.print("(");
    18. //5.得到方法的形参列表
    19. Class[] parameterTypes = m.getParameterTypes();
    20. if(!(parameterTypes == null && parameterTypes.length == 0)){
    21. for(int i = 0;i < parameterTypes.length;i++){
    22. if(i == parameterTypes.length - 1){
    23. System.out.print(parameterTypes[i].getName() + " args_" + i);
    24. break;
    25. }
    26. System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
    27. }
    28. }
    29. System.out.print(")");
    30. //6.得到方法抛出的异常
    31. Class[] exceptionTypes = m.getExceptionTypes();
    32. if(exceptionTypes.length > 0){
    33. System.out.print("throws ");
    34. for(int i = 0;i < exceptionTypes.length;i++){
    35. if(i == exceptionTypes.length - 1){
    36. System.out.print(exceptionTypes[i].getName());
    37. break;
    38. }
    39. System.out.print(exceptionTypes[i].getName() + ",");
    40. }
    41. }
    42. System.out.println();
    43. }
    44. }

    通过反射Method方法对象调用类或对象的方法

    1. //1.0 创建运行时类的对象和类对象的方法对象
    2. Class clazz = Student.class;
    3. Method clazz_Method = clazz.getDeclaredMethod("printAge", Integer.class);
    4. //====2.0 调用实例方法====
    5. //创建实例对象
    6. Student student = (Student) clazz.newInstance();
    7. //保证当前方法是可访问的(可访问私有方法)
    8. clazz_Method.setAccessible(true);
    9. /*调用invoke()方法 参数1:方法的调用者 参数2:给方法形参赋值的实参
    10. invoke()的返回值即为对应类中调用的方法的返回值 * */
    11. Object returnValue = clazz_Method.invoke(student, 20);//约等于student.printAge();
    12. //====2.1 调用静态方法====
    13. clazz_Method.invoke(null, 20);
    14. //或使用如下方法
    15. clazz_Method.invoke(Student.class, 30);
    16. //对应的Student类
    17. public class Student {
    18. //参数必须为引用类型Integer,不能为值类型int
    19. private static void printAge(Integer age) {
    20. System.out.println(age);
    21. }
    22. }

    构造器的反射对象:java.lang.reflect.Constructor

    调用运行时构造器对象生成实例对象。调用有参的构造器必须使用此方法,无参的可以使用newInstance())。

    1. //1.创建运行时类对象
    2. Class clazz = Person.class;
    3. //2.获取指定的构造器
    4. Constructor constructor = clazz.getDeclaredConstructor(String.class);
    5. //3.保证此构造器是可访问的
    6. constructor.setAccessible(true);
    7. //4.调用此构造器创建运行时类的对象
    8. Person per = (Person) constructor.newInstance("Tom");
    9. System.out.println(per);

    枚举Enum和enum

    自定义枚举类和系统enum对比

    注解&泛型&反射&枚举 - 图12

    enum关键字的使用

    如下特征描述了使用关键字enum定义的枚举类

    • 枚举类的实现:JDK1.5之前需要自定义枚举类;JDK1.5新增了enum关键字用于定义枚举类
    • enum定义的枚举类默认继承了java.lang.Enum类,因此不能再继承其他类
    • 枚举类的构造器只能使用 private 权限修饰符
    • 枚举类的所有实例对象必须在枚举类中显式列出(,分隔;结尾)列出
    • 实例系统会自动添加 public static final 修饰
    • 必须在枚举类的第一行声明枚举类对象

      Enum类的使用

      ```xml //1、values():返回枚举类型的对象数组,该方法可以遍历所有的枚举值 LogTypeEnum[] arrs = LogTypeEnum.values(); for (LogTypeEnum e : arrs) { //2、toString():返回当前枚举类对象常量的名称 System.out.println(e.toString()); }

    //3、返回当前枚举类对象常量的名称 LogTypeEnum logTypeEnum = LogTypeEnum.valueOf(“Warn”); System.out.println(logTypeEnum.getTypeValue()); ```