java基础-枚举类与注解-注解(Annotation)

Java基础之枚举类与注解-注解(Annotation)

主要内容:

  • 注解(Annotation)概述
  • 常见的Annotation示例
  • 自定义Annotation
  • JDK中的元注解
  • 利用反射获取注解信息
  • JDK8中注解的新特性
  1. 注解(Annotation)的概述
  • 从JDK5.0开始,Java增加了对元数据(MetaData)的支持,也就是Annotation(注解)

  • Annotation其实就是代码里的“特殊标记”,这些标记可以在编译时,类加载时,运行时被读取,并执行相应的处理。通常使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署

  • Annotation可以像修饰符一样被使用,可用于修饰包,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的"name=value"对中。

  • 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用的任何切面,代替JavaEE旧版中锁遗留的繁冗代码和XML配置等。

  • 未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解了,注解是一种趋势,一定程度上可以说:框架=注解+反射+设计模式。

  • 使用Annotation时要在前面加@符号,并把该Annotation当初一个修饰符使用。用于修饰它支持的程序元素

    • 示例一:生成文档相关的注解

    • 注解名用处
      @author表明开发该类模块的作者,多个作者之间使用,分割
      @version标明该类模块的版本
      @see参数转向,也就是相关主题
      @since从哪个版本开始增加的
      @param对方法中某参数的说明,如果没有参数就不能写
      @return对方法返回值的说明,如果方法的返回值类型是void就不能写
      @exception对方法可能抛出的异常进行寿命,如果方法没有用throws显示抛出的异常就不能写
    • 其中

      • @param @return和@exception 这三个标记都是只用于方法的。
      • @param的格式要求:@param 形参名 形参类型 形参说明
      • @return的格式要求:@return 返回值类型 返回值说明
      • @exception的格式要求:@exception 异常类型 异常说明
      • @param和@exception可以并列多个
    • /**
       * @author jiangl
       * @version 1.0
       * @date 2021/4/21 8:44
       */
      public class EnumTest1 {
      
          /**
           *
           * @param name 名字
           * @param age 年龄
           * @return
           * @throws Exception 异常
           * @throws IOException IO异常
           */
          public String testAnnotation(String name,String age)throws Exception, IOException{
              return name;
          }
      }
      
    • 示例二:在编译时进行格式检查(JDK内置的三个基本注解)

      • @Override:限定重写父类方法,该注解只能用于方法
      • @Deprecated:用于表示所修饰的元素(类、方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择(做一个提示,用处为了向下兼容)
      • @SuppressWarnings:抑制编译器警告
      package com.jl.java.base.myannotation;
      
      /**
       * @author jiangl
       * @version 1.0
       * @date 2021/4/21 11:19
       */
      public class AnnotationTest1 {
          public static void main(String[] args) {
              Dog dog = new BigDog();
              dog.run();
          }
      }
      
      interface Animal{
          @SuppressWarnings({})
          void sleep();
      }
      
      class Dog {
      
          private String name;
          private int age;
      
          public Dog() {
          }
      
          public Dog(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public void eat() {
              System.out.println("吃骨头");
          }
      
          @Deprecated
          public void run(){
              System.out.println("狗跑步");
          }
      }
      
      
      
      class BigDog extends Dog implements Animal{
          /**
           * 如果此处不写@Override注解,编译期可能不认为是重写,调用时会调用父类的方法
           */
          @Override
          public void run() {
              System.out.println("大狗跑步");
          }
      
          @Override
          public void sleep() {
              System.out.println("睡觉");
          }
      }
      
    • 示例三:跟踪代码依赖性,实现替代配置文件功能

      • Servlet3.0提供了注解(Annotation),使得不再需要在web.xml文件中进行Servlet的部署

      • package com.jl.java.base.myannotation;
        
        import javax.servlet.ServletException;
        import javax.servlet.annotation.WebServlet;
        import javax.servlet.http.HttpServlet;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import java.io.IOException;
        
        /**
         * @author jiangl
         * @version 1.0
         * @date 2021/4/21 11:00
         */
        @WebServlet("/loging")
        public class LoginServlet extends HttpServlet {
            private static final long serialVersionUID = 1L;
        
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                super.doGet(req, resp);
            }
        
            @Override
            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                super.doPost(req, resp);
            }
        }
        
      • Spring框架中关于“事务”的管理啊

      • 另外,Junit单元测试中也有大量注解的使用

        • @Test:标记在非静态的测试方法上,只有标记@Test的方法才能被作为一个测试方法单独测试。一个类中可以有多个@Test标记的方法。运行时如果只想运行一个@Test标记的方法,那么选择这个方法名,然后单独运行,否则整个类的所有标记@Test的方法都会被执行
          • @Test(timeout =1000):设置超时时间,如果测试时间超过了你定义的timeout,测试失败
          • @Test(excepted):申明出会发生的异常,比如@Test(excepted = Exception.class)
        • 其他还有:
        • @BeforeClass:标记在静态方法上。因为这个方法只执行一次。在类初始化时执行。
        • @AfterClass:标记在静态方法上。因为这个方法只执行一次,在所有方法完成后执行。
        • @Before:标记在非静态方法上,在@Test方法前面执行,而且是在每一个@Test方法后面都执行
        • @After:标记在非静态方法上,在@Test方法后面执行,而且是在每一个@Test方法后面都执行
        • @Ignore:标记在本次不参与测试的方法上,这个注解的含义就是“某些方法尚未完成,暂不参与测试”。
        • @BeforeClass,@AfterClass,@Before,@After,@Ignore都是配合@Test使用的,单独使用没有意义
      • package com.jl.java.base.myannotation;
        
        import org.junit.*;
        
        import java.util.Arrays;
        
        /**
         * @author jiangl
         * @version 1.0
         * @date 2021/4/21 11:14
         */
        public class MyJunitTest {
        
            private static Object[] array;
            private static int total;
        
            @BeforeClass
            public static void beforeClass(){
                System.out.println("初始化数组");
                array = new Object[5];
            }
        
            @Before
            public void before(){
                System.out.println("调用之前");
            }
        
            @Test
            public void test(){
                //往数组中添加一个元素
                System.out.println("add");
                array[total++] = "hello";
            }
        
            @After
            public void after(){
                System.out.println("调用之后");
            }
        
            @AfterClass
            public static void afterClass(){
                System.out.println(Arrays.toString(array));
            }
        }
        
  1. 使用自定义注解
  • 定义新的Annotation类型使用@interface关键字
  • 自定义注解自动继承了java.lang.Annotation接口
  • Annotation的成员变量在Annotation定义中以无参方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是“八种基本数据类型、String型、Class型、enum型、Annotation类型、以及以上所有类型的数组”(不能为包装类,Collection容器,例如Map,List)。
  • 可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字
  • 如果只有一个参数成员,建议使用参数名为value
  • 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value="
  • 没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元数据Annotation
  • 注意:自定义注解必须配上注解的信息处理流程(使用反射)才有意义

定义新的Annotation类型使用@interface关键字

public @interface MyOwnAnnotation {
}

自定义注解自动继承了java.lang.Annotation接口

package java.lang.annotation;

/**
 * The common interface extended by all annotation types.  Note that an
 * interface that manually extends this one does <i>not</i> define
 * an annotation type.  Also note that this interface does not itself
 * define an annotation type.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
public interface Annotation {
    /**
     * Returns true if the specified object represents an annotation
     * that is logically equivalent to this one.  In other words,
     * returns true if the specified object is an instance of the same
     * annotation type as this instance, all of whose members are equal
     * to the corresponding member of this annotation, as defined below:
     * <ul>
     *    <li>Two corresponding primitive typed members whose values are
     *    <tt>x</tt> and <tt>y</tt> are considered equal if <tt>x == y</tt>,
     *    unless their type is <tt>float</tt> or <tt>double</tt>.
     *
     *    <li>Two corresponding <tt>float</tt> members whose values
     *    are <tt>x</tt> and <tt>y</tt> are considered equal if
     *    <tt>Float.valueOf(x).equals(Float.valueOf(y))</tt>.
     *    (Unlike the <tt>==</tt> operator, NaN is considered equal
     *    to itself, and <tt>0.0f</tt> unequal to <tt>-0.0f</tt>.) 
     *
     *    <li>Two corresponding <tt>double</tt> members whose values
     *    are <tt>x</tt> and <tt>y</tt> are considered equal if
     *    <tt>Double.valueOf(x).equals(Double.valueOf(y))</tt>.
     *    (Unlike the <tt>==</tt> operator, NaN is considered equal
     *    to itself, and <tt>0.0</tt> unequal to <tt>-0.0</tt>.)
     *
     *    <li>Two corresponding <tt>String</tt>, <tt>Class</tt>, enum, or
     *    annotation typed members whose values are <tt>x</tt> and <tt>y</tt>
     *    are considered equal if <tt>x.equals(y)</tt>.  (Note that this
     *    definition is recursive for annotation typed members.)
     *
     *    <li>Two corresponding array typed members <tt>x</tt> and <tt>y</tt>
     *    are considered equal if <tt>Arrays.equals(x, y)</tt>, for the
     *    appropriate overloading of {@link java.util.Arrays#equals}.
     * </ul>
     *
     * @return true if the specified object represents an annotation
     *     that is logically equivalent to this one, otherwise false
     */
    boolean equals(Object obj);

    /**
     * Returns the hash code of this annotation, as defined below:
     *
     * <p>The hash code of an annotation is the sum of the hash codes
     * of its members (including those with default values), as defined
     * below:
     *
     * The hash code of an annotation member is (127 times the hash code
     * of the member-name as computed by {@link String#hashCode()}) XOR
     * the hash code of the member-value, as defined below:
     *
     * <p>The hash code of a member-value depends on its type:
     * <ul>
     * <li>The hash code of a primitive value <tt><i>v</i></tt> is equal to
     *     <tt><i>WrapperType</i>.valueOf(<i>v</i>).hashCode()</tt>, where
     *     <tt><i>WrapperType</i></tt> is the wrapper type corresponding
     *     to the primitive type of <tt><i>v</i></tt> ({@link Byte},
     *     {@link Character}, {@link Double}, {@link Float}, {@link Integer},
     *     {@link Long}, {@link Short}, or {@link Boolean}).
     *
     * <li>The hash code of a string, enum, class, or annotation member-value
     I     <tt><i>v</i></tt> is computed as by calling
     *     <tt><i>v</i>.hashCode()</tt>.  (In the case of annotation
     *     member values, this is a recursive definition.)
     *
     * <li>The hash code of an array member-value is computed by calling
     *     the appropriate overloading of
     *     {@link java.util.Arrays#hashCode(long[]) Arrays.hashCode}
     *     on the value.  (There is one overloading for each primitive
     *     type, and one for object reference types.)
     * </ul>
     *
     * @return the hash code of this annotation
     */
    int hashCode();

    /**
     * Returns a string representation of this annotation.  The details
     * of the representation are implementation-dependent, but the following
     * may be regarded as typical:
     * <pre>
     *   &#064;com.acme.util.Name(first=Alfred, middle=E., last=Neuman)
     * </pre>
     *
     * @return a string representation of this annotation
     */
    String toString();

    /**
     * Returns the annotation type of this annotation.
     */
    Class<? extends Annotation> annotationType();
}

Annotation的成员变量在Annotation定义中以无参方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是“八种基本数据类型、String型、Class型、enum型、Annotation类型、以及以上所有类型的数组”(不能为包装类,Collection容器,例如Map,List)。

/**
 * @author jiangl
 * @version 1.0
 * @date 2021/4/21 14:02
 */
public @interface MyOwnAnnotation {
    /**
     * 看着是方法,其实是注解的成员变量
     * Annotation的成员变量在Annotation定义中以无参方法的形式来声明。
     * 其方法名和返回值定义了该成员的名字和类型。
     * @return
     */
    public String value();
}

可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字

package com.jl.java.base.myannotation;

import java.util.List;

/**
 * @author jiangl
 * @version 1.0
 * @date 2021/4/21 14:02
 */
public @interface MyOwnAnnotation {
    /**
     * 看着是方法,其实是注解的成员变量
     * Annotation的成员变量在Annotation定义中以无参方法的形式来声明。
     * 其方法名和返回值定义了该成员的名字和类型。
     * @return
     */
    public String value() default "1231";

    public String[] value1() default {"dasfd","dfasdf"};


    public int[] value2() default {1,2};

}

如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value="

/**
 * @author jiangl
 * @version 1.0
 * @date 2021/4/21 14:10
 */
@MyOwnAnnotation(value = "12121",value1 = {"ddd","da"})
public class UserMyAnnotation {
}


/**
 * @author jiangl
 * @version 1.0
 * @date 2021/4/21 14:02
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
@Documented
@Inherited
public @interface MyOwnAnnotation {
    /**
     * 看着是方法,其实是注解的成员变量
     * Annotation的成员变量在Annotation定义中以无参方法的形式来声明。
     * 其方法名和返回值定义了该成员的名字和类型。
     * @return
     */
    public String value() default "1231";

    public String[] value1() default {"dasfd","dfasdf"};


    public int[] value2() default {1,2};

}
  1. JDK中的元注解
  • JDK的元Annotation用于修饰其他Annotation定义,对现有的注解进行解释说明的注解

  • JDK5.0提供了4中元注解

    • Retention
    • Target
    • Documented
    • Inherited
  • 解释四个元注解

    • @Retention:只能用于修饰一个Annotation定义,用于指定该Annotation的生命周期,@Retention包含一个RetentionPolicy类型的成员变量,使用@Retention时必须为该value成员变量指定值:

      • RetentionPolicy.SOURCE:在源文件中有效(即修饰的注解仅在源文件中保留),编译器直接丢弃这种策略的注解

      • RetentionPolicy.CLASS:在class文件中有效(即修饰的注解仅在class中保留),当运行java程序时,jvm不会保留注解,这是默认策略

      • RetentionPolicy.RUNTIME:在运行时有效(即修饰的注解在运行时保留),当运行java程序时,jvm会保留注解。被RetentionPolicy.RUNTIME 策略修饰的注解,在程序中可以通过反射获取到该注解

      • RetentionPolicy作用
        SOURCE在源文件中有效(即修饰的注解仅在源文件中保留),编译器直接丢弃这种策略的注解
        CLAS在class文件中有效(即修饰的注解仅在class中保留),当运行java程序时,jvm不会保留注解,这是默认策略
        RUNTIME在运行时有效(即修饰的注解在运行时保留),当运行java程序时,jvm会保留注解。被RetentionPolicy.RUNTIME 策略修饰的注解,在程序中可以通过反射获取到该注解
      • package java.lang.annotation;
        
        public enum RetentionPolicy {
            /**
             * 在源文件中有效(即修饰的注解仅在源文件中保留),编译器直接丢弃这种策略的注解
             */
            SOURCE,
        
            /**
             * 在class文件中有效(即修饰的注解仅在class中保留),当运行java程序时,jvm不会保留注解,这是默认策略
             */
            CLASS,
        
            /**
             * 在运行时有效(即修饰的注解在运行时保留),当运行java程序时,jvm会保留注解。被	RetentionPolicy.RUNTIME 策略修饰的注解,在程序中可以通过反射获取到该注解
             *
             * @see java.lang.reflect.AnnotatedElement
             */
            RUNTIME
        }
        
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2MRb6pn7-1618993675344)(D:\personal\文档\文档\javaBase\枚举类与注解\注解\pic\RetentionPolicy作用视图.png)]

    • @Target:用于修饰Annotation定义,用于指定被修饰的Annotation能用于修饰哪些程序元素。@Target也包含一个名为value[] 的成员变量。value[]的类型为ElementType

      • ELEMENTYPE作用
        TYPE作用于class类、interface接口、@interface注解、enum枚举类
        CONSTRUCTOR作用于描述构造器
        FIELD作用于描述域(属性、成员变量)
        LOCAL_VARIABLE作用于描述局部变量
        METHOD作用于描述方法
        PACKAGE作用于描述包
        PARAMETER作用于描述参数
      • public enum ElementType {
            /** Class, interface (including annotation type), or enum declaration */
            TYPE,
        
            /** Field declaration (includes enum constants) */
            FIELD,
        
            /** Method declaration */
            METHOD,
        
            /** Parameter declaration */
            PARAMETER,
        
            /** Constructor declaration */
            CONSTRUCTOR,
        
            /** Local variable declaration */
            LOCAL_VARIABLE,
        
            /** Annotation type declaration */
            ANNOTATION_TYPE,
        
            /** Package declaration */
            PACKAGE
        }
        
    • @Documented:用于指定该元Annotation修饰的Annotation类将被javadoc工具提取成文档。默认情况下,javadoc是不包括注解的。

      • 定义为@Documented的注解必须设置RetentionPolicy值为RUNTIME
    • @Inherited:被@Inherited修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

      • 比如:如果把标有@Inherited注解的自定义注解标注在类级别上,子类则可以继承父类类级别的注解

      • 实际应用中,使用较少

      • /**
         * @author jiangl
         * @version 1.0
         * @date 2021/4/21 14:10
         */
        @MyOwnAnnotation(value = "12121",value1 = {"ddd","da"})
        public class UserMyAnnotation {
            public static void main(String[] args) {
                Class childClass = Child.class;
                Annotation[] annotations = childClass.getAnnotations();
                for(int i=0;i<=annotations.length-1;i++){
                    System.out.println(annotations[i]);
                }
            }
        }
        
        class Child extends UserMyAnnotation{
        
        }
        
      • 结果:

        • @com.jl.java.base.myannotation.MyOwnAnnotation(value=12121, value1=[ddd, da], value2=[1, 2])
  1. 通过反射来获取注解

    package com.jl.java.base.myannotation;
    
    import java.lang.annotation.Annotation;
    
    /**
     * @author jiangl
     * @version 1.0
     * @date 2021/4/21 14:10
     */
    @MyOwnAnnotation(value = "12121",value1 = {"ddd","da"})
    public class UserMyAnnotation {
        public static void main(String[] args) {
            Class childClass = Child.class;
            Annotation[] annotations = childClass.getAnnotations();
            for(int i=0;i<=annotations.length-1;i++){
                System.out.println(annotations[i]);
            }
        }
    }
    
    class Child extends UserMyAnnotation{
    
    }
    
  2. jdk8中的注解新特性:可重复注解,类型注解

    可重复注解:

    1.在Father上声明@Repeatable,成员值为Fathers.class

    2.Father的Target比Fathers的小 ,Retention 比Fathers的大(要包含),但是具体可重复的作用是以Fathers的为准的,@Inherited两者需要相同

    场景:

    在jdk8之前,如果有需求需要在一个类上加两个一样的注解,如果使用以下方式会编译报错Duplicate annotation

    import static java.lang.annotation.ElementType.*;
    import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
    
    /**
     * @author jiangl
     * @version 1.0
     * @date 2021/4/21 15:28
     */
    @Father
    @Father
    public class RepeatAnnotationTest {
    
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Documented
    @Inherited
    @interface Father{
    
    }
    

    解决方法:

    定义一个注解数组

    package com.jl.java.base.myannotation;
    
    import java.lang.annotation.*;
    
    import static java.lang.annotation.ElementType.*;
    import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
    
    /**
     * @author jiangl
     * @version 1.0
     * @date 2021/4/21 15:28
     */
    @Fathers({@Father,@Father})
    public class RepeatAnnotationTest {
    
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Documented
    @Inherited
    @interface Father{
    
    }
    
    @interface Fathers{
        Father[] value();
    }
    

​ 在jdk8及以后,新增了@Repeatable注解

​ 1.先定义一个数组注解@interface Fathers

​ 2.在需要使用重复注解的注解上加上@Repeatable()

​ 3.将数组注解的类对象作为参数传给@Repeatable()的value属性,如@Repeatable(value = Fathers.class)

​ 4.最后可以使用重复注解了

​ 5.需要注意的是,数组注解的@Retention 策略 和 @Target 策略 需要大于 使用的可重复的注解的@Retention 策略 和 @Target 策略

  • package com.jl.java.base.myannotation;
    
    import java.lang.annotation.*;
    
    import static java.lang.annotation.ElementType.*;
    import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
    
    /**
     * @author jiangl
     * @version 1.0
     * @date 2021/4/21 15:28
     */
    @Father
    @Father
    public class RepeatAnnotationTest {
    
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(Fathers.class)
    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Documented
    @Inherited
    @interface Father{
    
    }	
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @interface Fathers{
        Father[] value();
    }
    

​ 类型的注解:

  • ​ 在JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个:TYPE_PARAMETER,TYPE_USER
  • 在java 8 之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方了。
    • ElementType.TYPE_PARAMETER 表示该注解能卸载类型变量的声明语句中(如:泛型声明)
    • ElementType.TYPE_USER 表示注解能写在使用类型的任何语句中
public enum ElementType {
    ...

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}
package com.jl.java.base.myannotation;

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

/**
 * @author jiangl
 * @version 1.0
 * @date 2021/4/21 16:18
 */
public class TypeAnnotationTest<@Annotation1 T>  {

    public static void main(String[] args) throws @Annotation1  Exception{
        @Annotation1 String a = "dfasfd";
        List<@Annotation1  String> str = null;
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_PARAMETER,ElementType.TYPE_USE})
@interface Annotation1{

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值