编程语言
首页 > 编程语言> > 关于Java中泛型、反射和注解的扫盲篇

关于Java中泛型、反射和注解的扫盲篇

作者:互联网

泛型

泛型概念

  泛型是在JDK1.5之后引入的,旨在让我们写出更加通用化,更加灵活的代码。通用化的手段在于让数据类型变得参数化,定义泛型时,对应的数据类型是不确定的,泛型方法被调用时,会指定具体类型,其核心目标是为了解决容器类型在编译时安全检查的问题。

  泛型:一般用在类、方法、接口中,叫做泛型类、泛型接口、泛型方法

泛型的使用

 package demo.generic;
 
 import lombok.Data;
 
 /**
  * 泛型类的定义
  * @param <T>
  */
 @Data
 public class GenericClassExample<T> {
     //member这个成员变量的类型为T,T的类型由外部指定
     private T member;
     //泛型类的构造方法形参member的类型也为T,T的类型由外部指定
     public GenericClassExample(T member) {
         this.member = member;
     }
 
     //泛型类中也可以定义普通方法,普通方法的参数也为泛型
     public T handleSomething(T target) {
         return target;
     }
 }
 
  1. 泛型的参数不支持基本类型;泛型相关的信息不会进入到运行时阶段
     package demo.generic;
    
     public class GenericDemo {
        public static void main(String[] args) {
            GenericClassExample<String> strExample = new GenericClassExample<>("abc");
            GenericClassExample<Integer> intExample = new GenericClassExample<>(123);
            System.out.println(strExample.getClass()); // 打印泛型类的类型
            System.out.println(intExample.getClass()); // 打印泛型类的类型
        }
     }
    
     // **********运行结果*********
     //class demo.generic.GenericClassExample
     //class demo.generic.GenericClassExample
     // 我们可以从运行结果看出strExample和intExample的类型是一样的,因此泛型类的类型约束只在编译时有效
    
  2. 能否在泛型里面使用具备继承关系的类?
    • 使用通配符 ?,但是会使得泛型的类型检查失去意义
    • 给泛型加入上边界 <? extends E>
    • 给泛型加入下边界 <? super E>
      package demo.generic;
      
      public class GenericDemo {
      
          //给泛型加如上边界 ? extends E, 泛型类型必须是E的类型或其子类
          public static void handleMember(GenericClassExample<? extends Number> integerExample) {
              Integer result = (Integer) integerExample.getMember() + 100;
              System.out.println("result is " + result);
          }
      
          //给泛型加入下边界 ? super E ,泛型类型必须是E的类型或其父类
          public static void handleSuperMember(GenericClassExample<? super Integer> integerExample) {
              Integer result = (Integer) integerExample.getMember() + 100;
              System.out.println("result is " + result);
          }
      
          public static void main(String[] args) {
              GenericClassExample<String> strExample = new GenericClassExample<>("abc");
              GenericClassExample<Integer> integerExample = new GenericClassExample<>(123);
              GenericClassExample<Number> numberExample = new GenericClassExample<>(new Integer(123));
      //        handleMember(strExample); // 编译会报错,因为String不是Number的子类
              handleMember(integerExample); // 不会报错,因为Integer是Number的子类
              handleSuperMember(integerExample); // 不会报错,因为Integer和泛型类的类型相同
              handleSuperMember(numberExample ); // 不会报错,因为Number是泛型类Integer的父类
          }
      }
      
  3. 泛型方法: 使用泛型标识符标识的方法
    // <E> 泛型标识符
    public static <E> void printArray(E[] array) {
        for(E element : array){
            System.out.printf("%s",element);
        }
    }
    
  4. 泛型字母的含义
    • E - Element: 在集合中使用,因为集合中存放的是元素
    • T - Type: Java类
    • K - Key: 键
    • V - Value: 值
    • N - Number: 数值类型

反射

反射的概念及作用

  反射允许程序在运行时来进行自我检查并且对内部的成员进行操作。反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

  1. 反射机制的作用
    • 在运行时判断任意一个对象所属的类
    • 在运行时获取类的对象
    • 在运行时访问java对象的属性、方法、构造方法等
  2. java.lang.reflect类库里面主要的类
    • Field: 表示类中的成员变量
    • Method: 表示类中的方法
    • Constructor: 表示类的构造方法
    • Array: 该类提供了动态创建数组和访问数组元素的静态方法
  3. 反射依赖的Class:用来表示运行时类型信息的对应类
    • 每个类都有唯一一个与之相应的Class对象
    • Class类为类类型,而Class对象为类类型对象
    • Class类的特点
      • Class类也是类的一种,class则是关键字
      • Class类只有一个私有的构造函数,只有JVM能够创建Class类的实例
      • JVM中只有唯一一个和类相对应的Class对象来描述其类型信息
    • 获取CLass对象的三种方式
      • Object -> getClass()
      • 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
      • 通过Class类的静态方法:forName(String className) (常用)
         package demo.reflect;
         
         public class ReflectTarget {
             public static void main(String[] args) throws ClassNotFoundException {
                 // 第一种方式获取Class对象
                 ReflectTarget reflectTarget = new ReflectTarget();
                 Class reflectClass01 = reflectTarget.getClass();
                 System.out.println("1st: " + reflectClass01);
         
                 //通过第二种方式获取Class对象
                 Class reflectClass02 = ReflectTarget.class;
                 System.out.println("2nd: " + reflectClass01);
         
                 //比较第一种方式获取得class对象和第二种方式获取得class对象是否为同一个
                 System.out.println(reflectClass01 == reflectClass02);
         
                 // 第三种方式获取Class对象
                 Class reflectClass03 = Class.forName("demo.reflect.ReflectTarget");
                 System.out.println("3rd: " + reflectClass03);
         
                 //比较第二种方式获取得class对象和第三种方式获取得class对象是否为同一个
                 System.out.println(reflectClass02 == reflectClass03);
             }
         }
         
         /************运行结果如下************/
         /*
         * 1st: class demo.reflect.ReflectTarget
         * 2nd: class demo.reflect.ReflectTarget
         * true
         * 3rd: class demo.reflect.ReflectTarget
         * true
         * */
         /**
          * 根据运行结果得知:Class对象有且仅有一个
          * **/
         ```
        
      • Class对象就像一面镜子,透过这面镜子可以看到类的结构

反射的主要用法

注解

注解介绍及作用

  由于反射需要获取到相关的类全名(类名+包名),因此我们还需要记录哪些类是通过反射来获取的。我们可以通过XML来保存类相关的信息已供反射用,此外,我们还可以通过注解来保存类相关信息以供反射调用。
  注解:提供一种为程序元素设置元数据的方法

自定义注解的实现

  自定义注解自动实现了 java.lang.annotation.Annotation

 public @interface 注解名{
    修饰符 返回值 属性名() 默认值;
    修饰符 返回值 属性名() 默认值;
    ...
 }

  注解属性支持的类型:所有的基本类型(int,float,boolean,byte,double,char,long,short)、 String 类型、 Class类型、Enum类型、Annotation类型、以上所有类型的数组。
  我们现在自定义一个注解PersonInfoAnnotation,可以用在字段上,在程序运行时有效,如下:

package demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfoAnnotation {
    //名字
    public String name();
    // 年龄
    public int age() default 19;
    // 性别
    public String gender() default "男";
    // 开发语言
    public String[] language();
}

  我们再自定义一个注解CourseInfoAnnotation,该注解可以用在类和方法上,在程序运行时有效,如下:

package demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CourseInfoAnnotation {
    // 课程名称
    public String courseName();
    // 课程 标签
    public String courseTag();
    // 课程简介
    public String courseProfile();
    // 课程代号
    public int courseIndex() default 107;
}

  新创建一个SelfStudyCourse类,在该类上及该类的字段和方法上,使用我们上面的已经定义好了的注解

package demo.annotation;

@CourseInfoAnnotation(courseName = "计算机网络",courseTag = "计算机基础",
        courseProfile = "计算机网络学习的核心内容就是网络协议的学习。" +
                "网络协议是为计算机网络中进行数据交换而建立的规则、标准或者说是约定的集合。" +
                "因为不同用户的数据终端可能采取的字符集是不同的,两者需要进行通信,必须要在一定的标准上进行")
public class SelfStudyCourse {
    @PersonInfoAnnotation(name = "新一",language = {"Java","C++","Go","Python"})
    public String author;


    @CourseInfoAnnotation(courseName = "Linux 教程",courseTag = "编程基础",
    courseProfile = "Linux 遵循 GNU 通用公共许可证(GPL),任何个人和机构都可以自由地使用 Linux 的所有底层源代码,也可以自由地修改和再发布。" +
            "由于 Linux 是自由软件,任何人都可以创建一个符合自己需求的 Linux 发行版",courseIndex = 208)
    public void getCourseInfo(){

    }

}

  创建测试类AnnotationDemo,调用上面使用了自定义注解的类的方法,查看运行信息

package demo.annotation;

public class AnnotationDemo {
    public static void main(String[] args) {
        SelfStudyCourse selfStudyCourse = new SelfStudyCourse();
        selfStudyCourse.getCourseInfo();
        System.out.println("finish");
    }
}

/**
 * 运行结果:
 * finish
 * 根据运行结果可以看出,在程序中如果不对注解进行处理,和不加注解输出的信息是一致的,
 * */

  如果我们不对注解进行处理,那和不加是没有区别的,那我们如何获取注解上得信息呢?通过前面说到得反射,我们查看反射涉及到得几个主要类(Field,Method,Constructor,Class)得源码可以知道,这些跟反射相关得类都实现了AnnotatedElement接口,我们通过查看AnnotatedElement接口的源码,常用的有如下几个方法:

   现在我们通过反射来获取类注解上的信息,代码实现如下:

package demo.annotation;

import java.lang.annotation.Annotation;

public class AnnotationParse {

    //解析类上面的注解
    public static void parseTypeAnnotation() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("demo.annotation.SelfStudyCourse");
        // 这里获取的是class对象的注解,而不是其里面的方法和成员变量的注解
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            CourseInfoAnnotation courseInfoAnnotation = (CourseInfoAnnotation) annotation;
            System.out.println("课程名: " +courseInfoAnnotation.courseName() + "\n" +
                    "课程标签: " + courseInfoAnnotation.courseTag() + "\n" +
                    "课程简介: "+ courseInfoAnnotation.courseProfile() + "\n" +
                    "课程代号: " + courseInfoAnnotation.courseIndex());
        }
    }

    public static void main(String[] args) throws ClassNotFoundException {
        parseTypeAnnotation();
    }
}

/**
 * 程序运行结果如下:
 * 课程名: 计算机网络
 * 课程标签: 计算机基础
 * 课程简介: 计算机网络学习的核心内容就是网络协议的学习。网络协议是为计算机网络中进行数据交换而建立的规则、标准或者说是约定的集合。因为不同用户的数据终端可能采取的字符集是不同的,两者需要进行通信,必须要在一定的标准上进行
 * 课程代号: 107
 */

  现在通过来反射来获取成员变量和方法上的注解信息

    // 解析字段上的注解信息
    public static void parseFieldAnnotation() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("demo.annotation.SelfStudyCourse");
        // 获取该对象的所有成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 判断成员变量中是否有指定注解类型的注解
            boolean hasAnnotation = field.isAnnotationPresent(PersonInfoAnnotation.class);
            if (hasAnnotation) {
                PersonInfoAnnotation personInfoAnnotation = field.getAnnotation(PersonInfoAnnotation.class);
                System.out.println("名字: " + personInfoAnnotation.name() + "\n" +
                        "年龄: " + personInfoAnnotation.age() + "\n" +
                        "性别: " + personInfoAnnotation.gender());
                for (String language : personInfoAnnotation.language()) {
                    System.out.println("课程名: " + language);
                }
            }
        }
    }

    // 解析方法上的注解信息
    public static void parseMethodAnnotation() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("demo.annotation.SelfStudyCourse");
        // 获取该对象的所有成员变量
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            // 判断方法中是否有指定注解类型的注解
            boolean hasAnnotation = method.isAnnotationPresent(CourseInfoAnnotation.class);
            if (hasAnnotation) {
                CourseInfoAnnotation methodAnnotation = method.getAnnotation(CourseInfoAnnotation.class);
                System.out.println("课程名: " +methodAnnotation.courseName() + "\n" +
                        "课程标签: " + methodAnnotation.courseTag() + "\n" +
                        "课程简介: "+ methodAnnotation.courseProfile() + "\n" +
                        "课程代号: " + methodAnnotation.courseIndex());
            }
        }
    }

  注解获取属性值的底层实现,是通过JVM为注解生成代理对象。

注解的工作原理

标签:Java,String,demo,扫盲,ReflectTarget,java,public,中泛,out
来源: https://www.cnblogs.com/reminis/p/13898899.html