内容
- 掌握注解的本质
- 注解的分类
- 自定义注解
- 注解的属性设置
- 元注解
- 注解解析
- 泛型的作用
- 自定义泛型
- 泛型的使用
- 泛型的继承关系以及上下限
一,注解
1.1 注解的概念
**为什么要学习注解?**注解可以帮助大家做很多事情,简化大量的操作。一般情况下,如果想封装框架,自己封装一些东西,都离不开注解。
定义:注解(annotation),一种代码级别的说明。是JDK1.5以后版本引入的一个特性,与类,接口,是在同一个层次。可以声明在包,类,字段,方法,局部变量,方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
- 编写文档:通过代码里标识的注解生成文档
- 代码分析:通过代码里标识的注解对代码进行解析
- 编译检查:通过对代码里标识的注解让编译器能够实现基本的编译检查 @override
1.2 注解的分类
1.2.1 JDK 内置注解
@Override 检测方法是否是重写父类的方法
@Deprecated 声明该方法是已经过时的方法
@SuppressWarnings("all") 干掉警告
all代表忽略所有 deprecation:忽略不推荐使用 unchecked:不检查,忽略泛型引起的问题
1.2.2 自定义注解
格式:
[元注解]
public @interface 注解名称 {}
**本质:**注解本质上就是一个接口,该接口默认继承Annotation接口
public interface 注解名称 extends java.lang.annotation.Annotation{}
**属性:**指接口中的抽象方法
1. 特点:属性的返回值类型有以下取值
基本数据类型,String类型,枚举类型,注解类型,以上类型的数组。
2. 定义过属性后,在使用时需要给属性赋值。
- 如果定义属性时,使用default关键字可以给属性默认初始值,那么在使用这个注解时,这个属性可以不进行赋值。
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略。
- 数组赋值时,值使用{}包裹。如果数组的值只有一个,则{}可以省略。
1.2.3 元注解
定义:所谓元注解就是标记其他注解的注解
@Target: 用来约束注解可以使用的地方 (方法,类,字段等)
@Retention:用来约束注解的生命周期
@Documented:描述注解是否被抽取到api文档中
@Inherited:描述注解是否被子类继承
@Target 用来约束注解可以使用的地方 (方法,类,字段等)
@Retention用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime),其含有如下:
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等
RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
@Documented:描述注解是否被抽取到api文档中
1.2.4 解析注解
定义:解析注解一般情况下就是通过反射获取类 属性或者方法等上边的注解,然后自己定义你的注解要完成什么。
定义注解类:
@Target(ElementType.TYPE)//定义注解可以使用的位置
@Retention(RetentionPolicy.RUNTIME)//定义注解可以保留的位置
public @interface MyAnno {
String className();
String methodName();
}
定义一个需要被调用到的类
public class People {
public void test(){
System.out.println("执行了People类中的test方法");
}
}
定义测试类:
@MyAnno(className = "com.bai.test1.People",methodName = "test")
public class PeopleTest {
public static void main(String[] args) throws Exception {
//1.解析注解
//1.1获取该类的字节码对象
Class<PeopleTest> pc = PeopleTest.class;
//2.获取上边的注解对象
MyAnno anno = pc.getAnnotation(MyAnno.class);
//3.调用注解对象中的抽象方法
String className = anno.className();//得到上边注解中的类名
String methodName = anno.methodName();//得到上边注解中的方法名
System.out.println(className+"==="+methodName);
//4.根据读取到的className 加载该类进入内存
Class<?> name = Class.forName(className);
//5.创建对象
Object o = name.newInstance();
//6.根据读取到的方法的名字获取方法对象
Method method = name.getMethod(methodName);
//7.执行方法
method.invoke(o);
}
}
二,泛型
2.1 泛型概述
java 泛型,是java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为【泛型类】、【泛型接口】、【泛型方法】。
泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
所谓泛型:就是指在类创建时不指定类中属性的具体类型,而由外部在声明及实例化对象时指定类型。
2.2 为什么要用泛型
1,类型安全。
泛型的主要目标是提高 Java 程序的类型安全。编译时的强类型检查;通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2,消除强制类型转换。
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3,潜在的性能收益。
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
2.3 泛型的使用(自定义泛型)
2.3.1 泛型类
泛型类:创建对象的时候指明具体的数据类型。
定义格式:
修饰符 class 类名<代表泛型的变量> { }
<>中的内容是泛型的占位符。和方法参数变量名类似,只不过参数变量名接收的值,而占位符:数据类型
/**
* 继承中:
* @param <A>
* @param <B>
*
* 如果子类继承的父类中没有写泛型,可以的
* 如果子类继承的父类中写了泛型
* 要么:在子类的泛型中添加上父类中的泛型
* public class Student<A,B,T> extends People<T> { }
* 要么:在父类的泛型中不写 占位符了,直接指定 确定的数据类型
* public class Student<A,B> extends People<String> { }
*/
public class Student<A,B> extends People<String> {}
public class GenericDemo2<A,B,C>{
private A name;
private B age;
private C sex;
public GenericDemo2(A name, B age, C sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public A getName() {
return name;
}
public void setName(A name) {
this.name = name;
}
public B getAge() {
return age;
}
public void setAge(B age) {
this.age = age;
}
public C getSex() {
return sex;
}
public void setSex(C sex) {
this.sex = sex;
}
public GenericDemo2() {
}
public static void main(String[] args) {
//GenericDemo2<Object, Object, Object> genericDemo2 = new GenericDemo2<>();
//创建对象时,传入什么类型,类中的A,B,C占位符就表示什么类型。
GenericDemo2<String, Integer, String> genericDemo2 = new GenericDemo2<>();
genericDemo2.setName("123");
genericDemo2.setAge(12);
genericDemo2.setSex("sss");
}
2.3.2 泛型方法
泛型方法:是指调用的时候才去具体的指明是什么数据类型。
格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
public class Teacher {
public void test(){
}
//泛型方法
public <T> void test1(T t){
System.out.println(t);
}
//静态方法
public static <E> void test2(E e){
System.out.println(e);
}
//泛型方法 当方法被调用时,确定泛型的数据类型
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.test1(new ArrayList<>());
Teacher.test2("asdfads");
}
}
2.3.3 泛型接口
定义格式:
修饰符 interface 接口名<代表泛型的变量> { }
public interface GenericInterface<E> {
void aaa(E e);
}
1、定义类时确定泛型的类型
public class GenericInterfaceImpl implements GenericInterface<String>{
//1.定义类的时候,直接确定泛型接口占位符的具体数据类型
@Override
public void aaa(String s) {
}
*//*@Override
public void aaa(Object o) {
}*//*
}
2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型
public class GenericInterfaceImpl<E> implements GenericInterface<E>{
//2.始终不确定类型,当真正的创建实现类的对象时,才确定泛型的数据类型
@Override
public void aaa(E e) {
System.out.println(e);
}
确定泛型:
public static void main(String[] args) {
//如果不传参数,默认还是Object
GenericInterfaceImpl<Object> objectGenericInterface = new GenericInterfaceImpl<>();
//传什么类型 泛型就是什么类型
GenericInterfaceImpl<String> stringGenericInterface = new GenericInterfaceImpl<>();
}
}
2.3.4 泛型的继承关系以及上下限通配符
泛型通配符是 “?” 任何的参数类型都可以赋值给它,
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<>();
strings.add("123");
String s = strings.get(0);
ArrayList<Integer> integers = new ArrayList<>();
ArrayList<Object> objects = new ArrayList<>();
//泛型通配符是 “?” 任何的参数类型都可以赋值给它,
/**
* 当把确定泛型的变量赋值给不确定类型的变量时,只能取,不能add
*/
List<?> list1 = strings;
Object o = list1.get(0);
System.out.println(o);
//list1.add("");
List<?> list2 = integers;
List<?> list3 = objects;
}
在引用传递中,在泛型操作中也可以设置一个泛型对象的范围上限和范围下限。范围上限使用extends关键字声明,表示参数化的类型可能是所指定的类型或者是此类型的子类,而范围下限使用super进行声明,表示参数化的类型可能是所指定的类型或者此类型的父类型。
例如:有一个people类,具有子类 Student,Teacher ,具有父类Object
class People{}
class Teacher extends People{}
class Student extends Teacher{}
public class Generic02 {
public static void main(String[] args) {
//需求:定义两个方法
ArrayList<People> people = new ArrayList<>();
ArrayList<Teacher> teachers = new ArrayList<>();
ArrayList<Student> students = new ArrayList<>();
ArrayList<Object> objects = new ArrayList<>();
Generic02 generic02 = new Generic02();
/**
* 测试上限
* 参数传递时,可以传递people和people的子类类型
*/
generic02.extendTest(teachers);
generic02.extendTest(students);
generic02.extendTest(people);
//generic02.extendTest(objects);
/**
* 测试下限
* 参数传递时,可以传递people和people的父类类型
*/
generic02.superTest(objects);
generic02.superTest(people);
//generic02.superTest(teachers);
}
// ? 只能是People的子类 people就是?的上限
public void extendTest(List<? extends People> list){}
// ? 只能是People的父类 people就是?的下限
public void superTest(List<? super People> list){}
}
2.3.5 泛型擦除
**概念:**将有泛型信息的对象赋值给没有泛型信息的对象,会丢失泛型信息。称之为擦除。JDK1.5之后才出现 让你的泛型只保留在编译期,运行期间是没有 虚拟机而言所有的泛型占位符都是一个Object。如果将一个具备泛型的对象赋值给不具备泛型的对象,那么泛型丢失。
ArrayList<People> peoples = new ArrayList<>();
List p = peoples;
Object o = p.get(0);
将具有泛型的Peoples赋值给没有具体泛型的的p,此时我们从p中取值,发现本来peoples具有的泛型,现在么得了。