三十八、Java基础之注解
一、自定义注解
1、创建自定义注解含义
/** * 创建自定义注解: * 1、新建一个普通的Java类 * 2、将class关键字替换成@interface * 3、类型的作用:a、声明变量,b、声明方法参数,c、声明方法的返回类型,d、强制类型转换 * * 我们声明的自定义注解实际上是一个接口,这个接口默认的继承自java.lang.annotation.Annotation接口 * 声明自定义注解时,不需要指明继承自java.lang.annotation.Annotation接口,只要使用了@interface,编译器就会自动完成响应操作 * * * 注解默认使用位置:Annotation和修饰符一样,应用于包,类型,构造方法,方法,成员变量,参数,本地变量的声明中 */
2、实例注解1
public @interface MyAnnotation { /** * 在java中声明属性,又声明了方法 * 注解中声明顺序的语法:属性类型 属性名称(); * 属性命名类型,名词或名词性短语,第一个单词手字符小写,其余单词首字符大写 * @return */ String name(); int age(); String[] arr(); String spaceName() default "石油大学"; }
3、实例注解2
@Target({ElementType.TYPE,ElementType.METHOD})//注解可以同时选择多个使用范围,规定自定义注解在类型上使用,在方法上使用
@Retention(RetentionPolicy.RUNTIME)//规定自定义注解在运行时保留,只有注解在运行时保留,我们才能在运行时通过反射获取注解信息
@Inherited//父类中使用的注解也可以被子类继承,子类也可以使用这个注解
import java.lang.annotation.*; /** * *1、 @Target说明了Annotation所修饰的对象范围: * 即注解的作用域,用于说明注解的使用范围(即注解可以用在什么地方,比如类的注解,方法注解,成员变量注解等等) * 注意:如果Target元注解没有出现,那么定义的注解可以应用于程序的任何元素。 * 取值是在java.lang.annotation.ElementType这个枚举中规定的: * 1.CONSTRUCTOR:用于描述构造器 * 2.FIELD:用于描述域 * 3.LOCAL_VARIABLE:用于描述局部变量 * 4.METHOD:用于描述方法 * 5.PACKAGE:用于描述包 * 6.PARAMETER:用于描述参数 * 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明 * * * 注解可以同时选择多个使用范围 * * 2、@Retention定义了该Annotation被保留的时间长短: * (1)某些Annotation仅出现在源代码中,而被编译器丢弃; ---------------------------------注解信息只能在源代码中出现 * (2)而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,----注解信息只在字节码中出现 * (3)而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。----也就是注解信息在JVM运行注解的时候依旧保留 * * 注意:只有注解信息在运行时出现,我们才能在运行时使用反射操作获取注解信息 * * 使用这个meta-Annotation可以对 Annotation的“生命周期”限制。 * @Retention的取值是在RetentionPoicy这个枚举中规定的 * 1.SOURCE:在源文件中有效(即源文件保留) * 2.CLASS:在class文件中有效(即class保留) * 3.RUNTIME:在运行时有效(即运行时保留) */ @Target({ElementType.TYPE,ElementType.METHOD})//注解可以同时选择多个使用范围,规定自定义注解在类型上使用,在方法上使用 @Retention(RetentionPolicy.RUNTIME)//规定自定义注解在运行时保留,只有注解在运行时保留,我们才能在运行时通过反射获取注解信息 @Inherited//父类中使用的注解也可以被子类继承,子类也可以使用这个注解 public @interface MyAnn { }
二、@Override注解:方法执行覆盖
/** * 方法重写也称为方法覆盖,当子类中从父类继承的方法不再合适子类的需要的时候,需要在子类中对该方法重新定义,即为方法覆盖(重写) * 方法覆盖的要求: * 1、 访问权限不能降低(低--->高:private----default---protected---public) * 2、返回类型必须一致 * 3、方法名必须一致 * 4、参数列表必须一致 * 5、抛出的异常不能扩大(高--->低:Exception--IOException--FileOutFountException) */ //@Override /就是一个注解,这个注解的作用告诉编译器我们就是要执行方法覆盖,则编译器会按照方法覆盖的要求进行检测
public class Person { private String name; private int age; /** * 方法重写也称为方法覆盖,当子类中从父类继承的方法不再合适子类的需要的时候,需要在子类中对该方法重新定义,即为方法覆盖(重写) * 方法覆盖的要求: * 1、 访问权限不能降低(低--->高:private----default---protected---public) * 2、返回类型必须一致 * 3、方法名必须一致 * 4、参数列表必须一致 * 5、抛出的异常不能扩大(高--->低:Exception--IOException--FileOutFountException) */ //@Override /就是一个注解,这个注解的作用告诉编译器我们就是要执行方法覆盖,则编译器会按照方法覆盖的要求进行检测 @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public Person(String name, int age) { this.name = name; this.age = age; } public Person() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
三、@Deprecated注解:在一个类中由于某种原因某个方法已经不推荐再使用了,可以使用Deprecated注解
public class Test01 { public static void main(String[] args){ Person per = new Person("张三",18); System.out.println(per); System.out.println(per.toString()); test1(); } /** * 在一个类中由于某种原因某个方法已经不推荐再使用了,可以使用Deprecated注解 */ @Deprecated public static void test1(){ System.out.println("废弃"); } }
四、@SuppressWarnings({"rawtypes","unused"})注解:让编译器忽略多个警告
/** * 使用集合的时候,应该使用泛型,如果没有使用泛型,编译器就会出现警告信息 * 警告信息不影响代码的执行,但是影响代码的美观 * 可使用SuppressWarnings注解,忽略警告,格式:属性=属性值 */ //@SuppressWarnings(value={"rawtypes"})//让编译器忽略警告 //@SuppressWarnings(value={"rawtypes","unused"})//让编译器忽略多个警告
import java.util.ArrayList; import java.util.Collection; public class Test02 { public static void main(String[] args){ /** * 使用集合的时候,应该使用泛型,如果没有使用泛型,编译器就会出现警告信息 * 警告信息不影响代码的执行,但是影响代码的美观 * 可使用SuppressWarnings注解,忽略警告,格式:属性=属性值 */ //@SuppressWarnings(value={"rawtypes"})//让编译器忽略警告 //@SuppressWarnings(value={"rawtypes","unused"})//让编译器忽略多个警告 @SuppressWarnings({"rawtypes","unused"})//直接写属性值 Collection coll = new ArrayList(); } }
五、注解的作用
/** *注解默认使用位置:Annotation和修饰符一样,应用于包,类型,构造方法,方法,成员变量,参数,本地变量的声明中 * * 注解中属性没有顺序,多个属性之间用逗号隔开 * * * 自定义注解中如果只有一个属性,就可以直接使用属性值 * */
@MyAnnotation(name="李四",age=18 , arr={"hh","ww"}) public class Test03 { @MyAnnotation(age=18 , arr={"hh","ww"},name="李四",spaceName ="北京大学")//成员变量 private String name; //方法 @MyAnnotation(name="李四",age=18 , arr={"hh","ww"}) public static void main(String[] args){ //局部变量 @MyAnnotation(name="李四",age=18 , arr={"hh","ww"}) String str = "北京"; } }
六、元注解
就是注解的注解,就是给你自己定义的注解添加注解,你自己定义了一个注解,但你想要你的注解有什么样的功能,此时就需要用元注解对你的注解进行说明
七、反射
/** * class class{ * * * } * Class类描述类对象的信息: * 类对象信息包括: * 1、类对象基本信息:包名,修饰符,类名,基类,实现的接口 * 2、类属性的信息:修饰符,属性类型,属性名称,属性值 * 3、类的方法信息:修饰符,返回方法,方法名称,参数列表,抛出的异常 * 4、类构造方法的信息:修饰符,类名,参数列表,抛出的异常 * 5、类中携带的注解信息 * 因为:类对象信息保存在class类中,所以Class类中会提供获取这些信息的方法,java中每个类型都有对应的class * * * 要想执行反射操作,必须要获取指定类型的Class * 获取Class的方式: * 1、获取基本类型的Class: * 1,基本类型.class,例如int.class,获取的就是int类型的Class对象 * 2,包装器类型.TYPE,例如Integer.TYPE,基本类型.class,例如int.class, * 2、获取引用类型的Class: * 1,引用类型.class,例如String.class,获取的是String类型的Class对象 * 2,通过对象获取: * Object obj = "Hello"; * Class cls=obj。getClass();获取的是String类型的Class对象 *3、CLass.forName("java.lang.String"),获取的是String类型的Class对象 */
八、@Inherited//父类中使用的注解也可以被子类继承,子类也可以使用这个注解;@Retention(RetentionPolicy.RUNTIME)//规定自定义注解在运行时保留,只有注解在运行时保留,我们才能在运行时通过反射获取注解信息
1、注解1
import java.lang.annotation.*; @Target({ElementType.TYPE,ElementType.METHOD})//注解可以同时选择多个使用范围,规定自定义注解在类型上使用,在方法上使用 @Retention(RetentionPolicy.RUNTIME)//规定自定义注解在运行时保留,只有注解在运行时保留,我们才能在运行时通过反射获取注解信息 @Inherited//父类中使用的注解也可以被子类继承,子类也可以使用这个注解 public @interface MyAnn1 { }
2、注解2
import java.lang.annotation.*; @Target({ElementType.TYPE})//注解可以同时选择多个使用范围,规定自定义注解在类型上使用,在方法上使用 @Retention(RetentionPolicy.RUNTIME)//规定自定义注解在运行时保留,只有注解在运行时保留,我们才能在运行时通过反射获取注解信息 public @interface MyAnn2 { }
3、父子类使用注解1
@MyAnn2 public class SubClass extends SuperClass{ }
@MyAnn1 public class SuperClass { }
4、测试类
import java.lang.annotation.Annotation; public class Test { public static void main(String[] args){ getAnnotations(); } //获取子类中使用的所有的注解 @SuppressWarnings("all") public static void getAnnotations(){ //获取子类的类对象 Class cls=SubClass.class; //在一个类中可以使用多个注解,所以cls.getAnnotations();返回的是一个注解的数组 Annotation[] annos = cls.getAnnotations(); System.out.println("数组的长度为:"+annos.length); for (Annotation anno: annos ) { System.out.println(anno); } } }
输出:
数组的长度为:2
@com.csjin.edu.TestAnnotations.other1.MyAnn1()
@com.csjin.edu.TestAnnotations.other1.MyAnn2()
九、@Inherited//父类中使用的注解也可以被子类继承,子类也可以使用这个注解;@Retention(RetentionPolicy.CLASS)//规定自定义注解在字节码中保留
1、注解1
import java.lang.annotation.*; @Target({ElementType.TYPE,ElementType.METHOD})//注解可以同时选择多个使用范围,规定自定义注解在类型上使用,在方法上使用 @Retention(RetentionPolicy.CLASS)//规定自定义注解在字节码中保留 @Inherited//父类中使用的注解也可以被子类继承,子类也可以使用这个注解 public @interface MyAnn1 { }
2、注解2
import java.lang.annotation.*; @Target({ElementType.TYPE})//注解可以同时选择多个使用范围,规定自定义注解在类型上使用,在方法上使用 @Retention(RetentionPolicy.RUNTIME)//规定自定义注解在运行时保留,只有注解在运行时保留,我们才能在运行时通过反射获取注解信息 public @interface MyAnn2 { }
3、父子类
@MyAnn2 public class SubClass extends SuperClass { }
@MyAnn1 public class SuperClass { }
4、测试类
import java.lang.annotation.Annotation; public class Test{ public static void main(String[] args){ getAnnotations(); } //获取子类中使用的所有的注解 @SuppressWarnings("all") public static void getAnnotations(){ //获取子类的类对象 Class cls= SubClass.class; //在一个类中可以使用多个注解,所以cls.getAnnotations();返回的是一个注解的数组 Annotation[] annos = cls.getAnnotations(); System.out.println("数组的长度为:"+annos.length); for (Annotation anno: annos ) { System.out.println(anno); } } }
输出:
数组的长度为:1
@com.csjin.edu.TestAnnotations.other1.other.MyAnn2()
九、使用注解,返回一个创建数据库的SQL
1、创建注解Column
import java.lang.annotation.*; /** * 用来携带字段信息的Column注解,再类的属性中使用 * 字段信息包括:字段名称,字段类型,字段长度,字段约束 */ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String columnName(); //字段名称 String columnType(); //字段类型 String columnLength() default "10"; //字段长度 String columnConstraint() default ""; //字段约束 }
2、创建一个实体类,JavaBean类
JavaBean的规范:
* 1、类中声明私有属性,并且提供公开的getter和setter方法
* 2、类中有公开的无参构造方法
* 3、类中要覆盖toString()方法
* 4、类中要覆盖hashCode()/equals(),为把JavaBean对象放到HashSet/HashMap中做好了准备
* 5、类中要实现Serializable接口,为序列化做准备
import java.io.Serializable; import java.lang.annotation.Target; import java.sql.Date; import java.util.Objects; /** * 一个实体对应一个数据库的表,实体类中的属性对应数据库中的字段 * 一个数据库中的表对应一个Java类,表中的字段对应类中的属性和数据库表对应的Java类称为JavaBean * * JavaBean的规范: * 1、类中声明私有属性,并且提供公开的getter和setter方法 * 2、类中有公开的无参构造方法 * 3、类中要覆盖toString()方法 * 4、类中要覆盖hashCode()/equals(),为把JavaBean对象放到HashSet/HashMap中做好了准备 * 5、类中要实现Serializable接口,为序列化做准备 * * * 根据一个已有的JavaBean类,自动生成数据库的建表语句DDL * 数据库建表需要以下信息 * 1、数据库表名称 * 2、字段相关信息:字段名称,字段类型,字段长度,字段约束 * * 这些信息如何携带? * 注解在实际开发中的作用,通过注解在类中携带信息,然后通过反射操作来获取信息 */ //携带表名信息的@Table注解,在类型上面使用,需要携带表明信息,在注解中通过属性来携带信息 // @Table(tableName = "EMP1") public class Emp implements Serializable{ @Column(columnName = "emp_no",columnType = "INT",columnLength = "4",columnConstraint = "PRIMARY KEY") private Integer empno; @Column(columnName = "e_name",columnType = "VARCHAR",columnLength = "64") private String ename; @Column(columnName = "hire_date",columnType = "DATE") private Date hiredate; @Column(columnName = "e_job",columnType = "VARCHAR",columnLength = "20") private String job; @Column(columnName = "e_sal",columnType = "DOUBLE",columnLength = "10,2") private double sal; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Emp emp = (Emp) o; return Double.compare(emp.sal, sal) == 0 && empno.equals(emp.empno) && ename.equals(emp.ename) && hiredate.equals(emp.hiredate) && job.equals(emp.job); } @Override public int hashCode() { return Objects.hash(empno, ename, hiredate, job, sal); } @Override public String toString() { return "Emp{" + "empno=" + empno + ", ename='" + ename + '\'' + ", hiredate=" + hiredate + ", job='" + job + '\'' + ", sal='" + sal + '\'' + '}'; } public Emp() { } public Integer getEmpno() { return empno; } public void setEmpno(Integer empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } }
3、创建一个表名称的注解Table
import java.lang.annotation.*; /** * 携带表名信息的@Table注解,在类型上面使用,需要携带表明信息,在注解中通过属性来携带信息 * */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String tableName(); }
4、测试类
代码实现顺序:
* 1、按照规范声明一个JavaBean类型
* 2、为了携带表名信息,创建一个Table注解,然后在EMP类中使用,Table注解
* 3、为了携带字段信息,创建一个Column注解,然后在EMP表中的属性使用Column注解
* 4、使用反射读取注解信息,生成数据库建表语句
* 5、连接JDBC,在数据库中生成表
import java.lang.annotation.Annotation; import java.lang.reflect.Field; /** * * Java中携带信息的方式: * 1、通过注解来携带信息,然后通过反射来获取信息 * 2、通过文件来携带信息,然后通过IO流来获取信息 * * * 代码实现顺序: * 1、按照规范声明一个JavaBean类型 * 2、为了携带表名信息,创建一个Table注解,然后在EMP类中使用,Table注解 * 3、为了携带字段信息,创建一个Column注解,然后在EMP表中的属性使用Column注解 * 4、使用反射读取注解信息,生成数据库建表语句 * 5、连接JDBC,在数据库中生成表 */ public class Test { public static void main(String[] args){ String s = buildSql(); System.out.println(s); } private static String buildSql() { StringBuilder str = new StringBuilder("CREATE TABLE "); //1、获取Emp对应的Class Class cls = Emp.class; //2、通过Class获取Emp类中携带的表名信息 //(1)获取Emp表中携带的注解 Table table = (Table) cls.getAnnotation(Table.class); //(2)获取注解中的表明信息 String tableName = table.tableName(); //(3)将表名拼接到sql中 str.append(tableName + "("); //3、通过Class获取Emp表中字段的信息 //(1)获取类中所有的声明的所有属性,包括公开的,私有的,缺省的,保护的属性,但是没有继承的属性 Field[] fields = cls.getDeclaredFields(); //4、遍历数据 for (Field field : fields ) { //判断属性上是否携带注解 if (field.isAnnotationPresent(Column.class)){ //获取注解中的信息 Column column = field.getDeclaredAnnotation(Column.class); String columnName = column.columnName(); String columnType = column.columnType(); String columnLength = column.columnLength(); String columnConstraint = column.columnConstraint(); //拼接到sql if ("hire_date".equalsIgnoreCase(columnName)){ str.append(columnName + " " + columnType + " " + columnConstraint + ","); }else { str.append(columnName + " " + columnType + "(" + columnLength + ") " + columnConstraint + ","); } } } //5、去掉最后一个逗号,并且加上最后一个) String sql = str.substring(0, str.length() - 1)+")"; return sql; } }
当有些人一出生就有的东西,我们要为之奋斗几十年才拥有。但有一样东西,你一辈子都不会有,那就是我们曾经一无所有。