Java 枚举&注解&反射

4-9 枚举&注解&反射

枚举

  • 引入的目的:用于定义有限数量的一组同类常量,限定使用者在使用时常量的取值范围

  • Java中枚举的演变:

    • java1.5前使用类+final修饰变量+私有构造方法来实现,外部无法使用构造方法创建对象,只能使用类名来使用定义好的常量。

    • java1.5引入了枚举类型,在定义的时候,简化了语法:

      • 第一种方法(简化内部调用构造方法):

        public enum Level {
            //使用Enum方法比较大小时根据传入的数字比较
            LOW(30), MEDIUM(15), HIGH(7), URGENT(1);
            
            private int levelValue;
            private Level(int levelValue) {
                this.levelValue = levelValue;
            }
            public int getLevelValue() {
                return levelValue;
            }
        }
        
      • 第二种方法(直接写出枚举变量不传参)

        public enum Level {
            //使用Enum方法比较大小时根据先后顺序比较
            LOW, MEDIUM, HIGH, URGENT;
        }
        
  • 枚举类型继承的是抽象类Enum,对枚举的变量不能直接使用运算符操作,需要使用该抽象类的方法:

    • 若使用没有指定数字的枚举变量,则其默认的起始序号为0,它们之间使用compareTo方法进行比较,其比较的值也是根据序号值进行运算的。
  • 每个枚举对象,都可以实现自己的抽象方法:

    • 自定义一个接口,然后让该枚举类型继承该接口:

      interface LShow{
      	void show();
      }
      public enum Level implements LShow{}
      
    • 可以在枚举中实现公共接口(没有单独实现的将调用公共方法),也可以对每个枚举对象单独实现接口:

      interface LShow{
          void show();
      }
      public enum Level implements LShow {
          //比较大小时根据传入的数字比较
          LOW{
              @Override
              public void show() {
                  System.out.println("LOW 实现方法");
              }
          }, MEDIUM(){
              @Override
              public void show() {
                  System.out.println("MEDIUM 实现方法");
              }
          }, HIGH;
          
          @Override
          public void show() {
              System.out.println("公共实现方法");
          }
      }
      
  • 枚举注意事项:

    • 一旦定义了枚举,最好不要妄图修改里面的值,除非修改是必要的。
    • 枚举类默认继承的是java.lang.Enum类而不是Object类
    • 枚举类不能有子类,因为其枚举类默认被final修饰
    • 只能private构造方法
    • switch中使用枚举时,直接使用常量名,不用携带类名,因为类名在条件语句中已经获得。
    • 不能定义name属性,因为自带name属性
    • 不要为枚举类中的属性提供set方法,不符合枚举最初设计初衷。

注解

  • Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制,但不是注释!。

    • 注释是不会进入编译阶段的,在class文件中不会出现,因为注释是给人看的。
    • 默认注解是会进入字节码中的。因此注解是给机器看的。
    • 在Java框架中经常会使用注解。
  • 注解的应用场景

    • 编译格式检查
    • 反射中解析
    • 生成帮助文档
    • 跟踪代码依赖等
  • 常用的内置注解

    • @Override: 重写,定义在java.lang.Override
    • @Deprecated:废弃 ,定义在java.lang.Deprecated
    • @FunctionalInterface: 函数式接口。
      • 标识一个匿名函数或函数式接口。
      • 函数式接口是具有一个方法的接口,用作lambda表达式的类型。
    • @SuppressWarnings:抑制编译时的警告信息。
      • 定义在java.lang.SuppressWarnings

      • @SuppressWarnings("unchecked") 抑制单类型的警告

      • @SuppressWarnings("unchecked","rawtypes") 抑制多类型的警告

      • @SuppressWarnings("all") 抑制所有类型的警告

      • 还有其他参数,例如装箱警告boxing,空操作警告null等等,具体可以查表。

      • 生效的范围与编写的位置有关,可以针对变量、方法、类等进行注解。

  • 元注解(用来配置注解):

    • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。

    • @Documented - 标记这些注解是否包含在用户文档中javadoc

    • @Target - 标记这个注解应该是哪种 Java 成员。

    • @Inherited - 标记这个注解是自动继承的:

      • 子类会继承父类使用的注解中被@Inherited修饰的注解
      1. 接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有
        @Inherited修饰
      2. 类实现接口不会继承任何接口中定义的注解.
  • 注解的整体架构:

  • 注解少于两个参数

    • 注解参数无参时,可以不传参数

    • 注解参数为1个参数时,推荐定义默认的value属性,这样可以省略书写时vaule = "xxxx"中的前面部分,直接写"xxxx"即可。

    • 注解参数传入超过1个时,不能省略,必须写全,例如:vaule = "xxxx",num=100

    • 可以定义一个默认的参数,使用default xxx,当不传入该参数,则默认使用定义的参数取值。

    • 出入数组参数且长度大于1个时,需要添加大括号,例如:value = {"x","y"}

      @MyAnnotation("param")
      public class Demo {
          public static void main(String[] args) {
      
          }
      }
      
      @Documented
      //限定MyAnnotation用于在类/接口/枚举、方法中进行注解
      @Target({ElementType.TYPE,ElementType.METHOD})
      //MyAnnotation注解 保存的策略 保存在class中且被JVM可读
      @Retention(RetentionPolicy.RUNTIME)
      //可以被子类继续继承MyAnnotation注解
      @Inherited
      @interface MyAnnotation{
          String value();
          int num() default 100;
      }
      

反射

  • JAVA反射机制是在运行状态中,获取任意一个类的结构 , 创建对象 , 得到方法,执行方法 , 属性。这种在运行状态动态获取信息以及动态调用对象方法的功能被称为java语言的反射机制。

    • 强调与正常的流程不一样,正常流程下必须在编译阶段就确定好类的结构,然后其他代码根据权限去创建和访问该类,而反射机制使得不必在编译阶段就确定类结构,而且不用考虑权限问题,这有利于在代码不停机不重启的情况下更新部分代码的应用场景。
    • 总结就是三个点:无权限问题、动态应用场景、反封装机制。
  • 类加载器

    • 引导启动类加载器(BootstrapClassLoader

      • C++语言写内嵌在JVM中的加载器,主要加载JAVA_HOME/lib下类库,无法被程序员直接使用。
    • 扩展类加载器(ExtensionClassLoader

      • Java语言编写,父加载器是BootstrapClassLoader,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类库。
    • 应用类加载器(App ClassLoader

      • 负责加载应用程序classpath目录下的所有jar和class文件。它的父加载器为ExtensionClassLoader
    • 类加载的Java委派概念:

      • 如果一个类加载器收到加载请求,它会把请求转交给父类加载器,如果父类加载器已经加载或者响应加载则子类加载器才会尝试自己去加载。

      • 按需加载,第一次使用时才进行加载,且只加载一次。

      • 于有了类加载器,Java运行时系统不需要知道文件与文件系统。

  • 加载配置文件

    • 注意,在未设置resource root目录前,默认从src目录进行加载,否则从resource root目录下加载配置文件,加载代码如下:

      public class Test {
          public static void main(String[] args) throws IOException {
              InputStream is = Test.class.getClassLoader().
                      getResourceAsStream("config.txt");
              BufferedReader br = new BufferedReader(new InputStreamReader(is));
              System.out.println(br.readLine());
          }
      }
      
    • 配置resource root目录方法:IDEA中右键选择某个目录,Mark Directory as Resources Root即可。

      Java 枚举&注解&反射_第1张图片

  • 加载类的三种方法,注意区别第三种和前两种的区别:

    public static void main(String[] args) throws ClassNotFoundException {
        //方法1:通过类名来加载类
        Class<Person> c1 = Person.class;//class com.forwardxiang.Person
        System.out.println(c1);
        //方法2:通过类的对象来加载类
        Person p = new Person();
        Class<Person> c2 = (Class<Person>)p.getClass();
        System.out.println(c1 == c2);//true,说明是同一块内存
        //方法3:通过类的全名来加载类,无需通过创建类或对象来加载类
        Class<Person> c3 = (Class<Person>)Class.forName("com.forwardxiang.Person");
        System.out.println(c1 == c3);//true,说明是同一块内存
        //方法3的另一个不强转的情况下,可以在没有Person类的情况下编译通过,
        // 只需要在运行到这行之前能够生成Person类即可,
        // 也就是说可以单独编译该行代码无需Person参与
        Class c4 = Class.forName("com.forwardxiang.Person");
    }
    
  • 反射中的构造方法:注意getDeclaredConstructorc3.setAccessible(true)的配合使用

    Class pClass = Class.forName("com.forwardxiang.Person");
    
    //使用获取的类的无参构造方法
    Constructor c1  = pClass.getConstructor();
    Object p = c1.newInstance();
    System.out.println(p);//类中有override toString方法
    
    //使用获取的类的全参构造方法
    Constructor c2  = pClass.getConstructor(String.class,int.class);
    Object p2 = c2.newInstance("xiangwei",18);
    System.out.println(p2);//类中有override toString方法
    
    //使用获取的类的私有构造方法 c3.setAccessible(true);
    Constructor c3  = pClass.getDeclaredConstructor(String.class,int.class);
    c3.setAccessible(true);
    Object p3 = c3.newInstance("xiangwei",18);
    System.out.println(p3);//类中有override toString方法
    
  • 反射中的方法:注意getDeclaredMethodageSet.setAccessible(true)的配合使用

    Class pClass = Class.forName("com.forwardxiang.Person");
    //使用获取的类的无参构造方法
    Constructor constructor  = pClass.getConstructor();
    Object o = constructor.newInstance();
    System.out.println(o);
    //获取类的方法
    Method nameSet = pClass.getMethod("setName", String.class);
    nameSet.invoke(o,"xiangwei");
    System.out.println(o);
    //获取类的私有方法
    Method ageSet = pClass.getDeclaredMethod("setAge", int.class);
    ageSet.setAccessible(true);
    ageSet.invoke(o,22);
    System.out.println(o);
    
  • 反射中的属性:注意getDeclaredFieldnamer.setAccessible(true)的配合使用

    //获取类属性
    Field number = pClass.getField("num");
    number.set(o,10086);
    System.out.println(o);
    //获取类的私有属性
    Field namer = pClass.getDeclaredField("name");
    namer.setAccessible(true);
    namer.set(o,"xiangwei");
    System.out.println(o);
    
  • 反射与注解

    • 设置注解并传入值,以数据库表名称和表中的字段做注解为例:

      @TableAnnotation("test_Book")
      public class Book {
          @ColumnAnnotation(columnName = "name",type = "varchar",length = "11")
          private String name;
          @ColumnAnnotation(columnName = "info",type = "varchar",length = "50")
          private String info;
          @ColumnAnnotation(columnName = "id",type = "int",length = "11")
          private int id;
          //......此处省略
      }
      
      @Documented
      @Target(ElementType.FIELD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface ColumnAnnotation {
          String columnName();//描述列名
          String type();//描述类型
          String length();//描述长度
      }
      
      @Documented
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface TableAnnotation {
          String value();//用于标注类对应的表格名称
      }
      
    • 获取注解中传入的值:

      Class pClass = Class.forName("com.forwardxiang.Book");
      TableAnnotation ta = (TableAnnotation) pClass.getAnnotation(TableAnnotation.class);
      System.out.println(ta.value());//test_Book
      Field[] fs = pClass.getDeclaredFields();
      for (Field f:fs) {
          ColumnAnnotation ca = f.getAnnotation(ColumnAnnotation.class);
          System.out.println(f.getName()+"属性,对应数据库中字段:"+ca.columnName()+" "+ca.type()+" "+ca.length());
          //name属性,对应数据库中字段:name varchar 11
          //info属性,对应数据库中字段:info varchar 50
          //id属性,对应数据库中字段:id int 11
      }
      
  • 内省:基于反射,Java提供一套应用到JavaBean的API,对于反射的操作,进行了封装 。

    • 由于应用场景是bean对象,要求至少有:

      • 拥有无参构造器
      • 所有属性私有
      • 所有属性提供get/set方法,注意:boolean类型对应的get方法is属性名
      • 实现了序列化接口
    • 方法调用层次(内省使用起来比直接用反射要更容易):

      Introspector--->getBeanInfo--->BeanInfo--->getPropertyDescriptors()--->
      					--->getReadMethod()--->Method(属性的get方法)
      MethodDescriptor[]两个分支
      					--->getWriterMethod()--->Method(属性的set方法)
      
    • 代码举例:

      Class c = Express.class;
      Constructor constructor = c.getConstructor();
      Object o = constructor.newInstance();
      
      BeanInfo bi = Introspector.getBeanInfo(c);
      PropertyDescriptor[] pds = bi.getPropertyDescriptors();
      
      for (PropertyDescriptor pd:pds) {
          System.out.println("属性名称:" + pd.getName() + "\t  属性类型:" + pd.getPropertyType());
          //由于属性排列顺序并非定义时的顺序 因此不要想当然直接用pds[]下标操作
          if (Objects.equals(pd.getName(),"address")){
              Method get = pd.getReadMethod();
              Method set = pd.getWriteMethod();
              set.invoke(o, "shanghai");
              System.out.println(get.invoke(o));
          }
      }
      

你可能感兴趣的