08-2-泛型-注解
✨你好啊,我是“ 罗师傅”,是一名程序猿哦。
🌍主页链接:楚门的世界 - 一个热爱学习和运动的程序猿
☀️博文主更方向为:分享自己的快乐 briup-jp3-ing
❤️一个“不想让我曾没有做好的也成为你的遗憾”的博主。
💪很高兴与你相遇,一起加油!
前言
目标:Java基础编程,熟练Java开发语法和规则,养成良好编程习惯
泛型
泛型概述
泛型(Generics)的概念是在JDK1.5中引入的,它的主要目的是为了解决类型安全性和代码复用的问题
泛型是一种强大的特性,它允许我们在定义类、接口和方法时使用参数化类型
1
2
3
4
5
6
7
8
9
10
11 //T是数据类型,但是不是确定的数据类型
//程序员在使用该类的时候,传入什么具体的类型给T,T就代表什么类型
public class MyClass<T> { // 泛型类,使用类型参数T
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}泛型能够使我们编写出来通用的代码,提高代码的可读性和重用性。通过使用泛型,我们可以再类、接口和方法中使用类型参数,使得代码可以处理不同类型的数据,同时保持类型安全。
泛型应用
1
2
3
4
5
6
7
8
9
10
11
12 public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("hello1");
c.add("hello2");
c.add("hello3");
//编译报错,add(E e) 已经变为 add(String e)
//int类型的数据1,是添加不到集合中去的
//c.add(1);
for(String str : c) {
System.out.println(str);
}
}可以看出,传入泛型参数后,add方法只能接收String类型的参数,其他类型的数据无法添加到集合中,同时在遍历集合的时候,也不需要我们做类型转换了,直接使用String类型变量接收就可以了,JVM会自动转换的
1
2
3 Collection<String> c = new ArrayList<String>();
// 可以简写为菱形泛型形式:砖石表达式
Collection<String> c = new ArrayList<>();菱形泛型(Diamond Operator)是JDK7中引入的一种语法糖,用于简化泛型的 类型推断过程。
Map接口使用泛型:
1
2
3
4
5
6 //Map接口也是泛型接口
public interface Map<K,V> {
//省略...
V put(K key, V value);
Set<Map.Entry<K, V>> entrySet();
}
自定义泛型
- 泛型类
如果泛型参数定义在类上面,那么这个类就是一个泛型类
泛型类定义格式:
1
2
3
4
5
6 [修饰符] class 类名<泛型类型名1,泛型类型名2,...> {
0个或多个数据成员;
0个或多个构造方法;
0个或多个成员方法;
}
//注意:之前用确定数据类型的地方,现在使用自定义泛型类型名替代泛型类实例化对象格式: 泛型类名<具体类型1,具体类型2,…> 对象名 = new 泛型类名<>(实参列 表);
注意:实际开发中,我们自定义泛型类的情况并不多,大家掌握定义泛型类、实例化泛型类对象的固定格式即可。
- 泛型接口
如果泛型参数定义在接口上面,那么这个接口就是一个泛型接口
定义格式: [修饰符] interface 接口名<泛型类型名1,泛型类型名2,…> { }
1
2
3
4
5
6
7 public interface Action<T> {...}
public static void main(String[] args) {
//创建匿名内部类
Action<String> a = new Action<>() {
//...
};
}
- 泛型方法
如果泛型参数定义在方法上面,那么这个方法就是一个泛型方法
泛型方法定义格式:
1
2
3 [修饰符] <泛型类型名> 返回值类型 方法名(形式参数列表){
方法具体实现;
}泛型方法调用格式:
1
2 类对象.泛型方法(实参列表);
类名.static 泛型方法(实参列表);注意:泛型方法调用时不需要额外指定泛型类型,系统自动识别泛型类型。
案例展示: 在上述Circle泛型类中,补充 泛型方法disp()和static show() ,并调用, 验证上述格式。
1
2
3
4
5
6
7
8
9
10
11
12 public class Circle<T,E> { // T,E
//省略...
//泛型类中定义 泛型方法
public <F> void disp(F f) {
System.out.println("in 泛型方法disp, f: " + f);
}
// 下面写法虽然不会报错,不建议大家这样写
// 因为泛型方法上的 T 会和 泛型类上的 T 产生歧义
public static <T> void show(T t) { // T
System.out.println("in 泛型static方法show, t: " + t);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 public static void main(String[] args) {
Circle<Integer,Integer> c = new Circle<>();
//public <F> void disp(F f);
//调用时系统自动识别泛型方法类型
c.disp(1); //Integer
c.disp(2.3); //Double
c.disp("hello");//String
c.disp('h'); //Character
System.out.println("--------------");
//public static <T> void show(T t);
//通过类名可以直接调用,不需要额外指定泛型类型
Circle.show(2.3);
Circle.show(2);
Circle.show("hello");
}
注意事项
先看俩种错误的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 //编译通过
//父类型的引用,指向子类对象
Object o = new Integer(1);
//编译通过
//Object[]类型兼容所有的【引用】类型数组
//arr可以指向任意 引用类型 数组对象
Object[] arr = new Integer[1];
//编译失败
//注意,这个编译报错,类型不兼容
//int[] 是基本类型数组
Object[] arr = new int[1];
//编译失败
//错误信息:ArrayList<Integer>无法转为ArrayList<Object>
//在编译期间,ArrayList<Integer>和ArrayList<Object>是俩个不同的类型,并且没有子父类型的关系
ArrayList<Object> list = new ArrayList<Integer>();注意, =号两边的所指定的泛型类型,必须是要一样的
这里说的泛型类型,指的是<>中所指定的类型
虽然 Integer 是 Object 的子类型,但是 ArrayList 和 ArrayList 之间没有子父类型的关系,它们就是俩个不同的类型所以,
Object o = new Integer(1); 编译通过
ArrayList list = new ArrayList(); 编译报错
也就是说,俩个类型,如果是当做泛型的指定类型的时候,就没有多态的特点了
通配符
?:可以通配任意类型
1 public void test(Collection<?> c) {}注意:这使用test方法中的参数类型,使用了泛型,并且使用问号来表示这个泛型的类型,这个问号就是通配符,可以匹配所有的泛型类型
test方法可以接收,泛型是任意引用类型的 Collection集合对象、
1
2
3
4
5
6
7 public static void main(String[] args){
Test t = new Test();
t.test(new ArrayList<String>());
t.test(new ArrayList<Integer>());
t.test(new ArrayList<Double>());
t.test(new ArrayList<任意引用类型>());
}使用通配符(?)所带来的问题:
1
2
3
4
5
6
7
8
9
10
11 Collection<?> c;
c = new ArrayList<String>();
// 编译报错
// 因为变量c所声明的类型是Collection,同时泛型类型是通配符(?)
// 那么编译器也不知道这个?将来会是什么类型,因为这个?只是一个通配符
// 所以,编译器不允许使用变量c来向集合中添加新数据。
c.add("hello");
// 编译通过
// 但是有一个值是可以添加到集合中的,null
// 集合中一定存的是引用类型,null是所有引用类型共同的一个值,所以一定可以添加进去。
c.add(null);虽然使用通配符(?)的集合,不能再往其中添加数据了,但是可以遍历集合取 出数据:
1
2
3
4
5
6
7
8
9
10
11
12
13 public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello1");
list.add("hello2");
list.add("hello3");
list.add("hello4");
Collection<?> c = list;
//编译报错
//c.add("hello5");
for(Object obj : c) {
System.out.println(obj);
}
}
泛型边界
在默认情况下,泛型的类型是可以任意设置的,只要是引用类型就可以。
如果在泛型中使用 extends 和 super 关键字,就可以对泛型的类型进行限制。 即:规定泛型的上限和下限。
泛型上限
- 例如: List <? extends Number> list
- 将来引用list就可以接收泛型是Number 或者 Number子类型的List集合对象
1
2
3
4
5
6
7
8
9 public static void main(String[] args) {
List<? extends Number> list;
//list可以指向泛型是Number或者Number【子】类型的集合对象
list = new ArrayList<Number>();
list = new ArrayList<Integer>();
list = new ArrayList<Double>();
//编译报错,因为String不是Number类型,也不是Number的子类型
//list = new ArrayList<String>();
}能表示数字的类型都是Number类型的子类型,例如Byte Short Integer Long 等
泛型下限
- 例如:List <? super Number> list
- 将来引用list就可以接收泛型是Number或者Number父类型的List集合对象
1
2
3
4
5
6
7
8
9
10
11 public static void main(String[] args) {
List<? super Number> list;
//list可以指向泛型是Number或者Number【父】类型的集合对象
list = new ArrayList<Number>();
list = new ArrayList<Serializable>();
list = new ArrayList<Object>();
//编译报错,因为String不是Number类型,也不是Number的父类型
//list = new ArrayList<String>();
//编译报错,因为Integer不是Number类型,也不是Number的父类型
//list = new ArrayList<Integer>();
}
类型擦除
泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。
由于泛型是JDK1.5才加入到Java语言特性的,Java让编译器擦除掉关于泛型类型的信息,这样使得Java可以向后兼容之前没有使用泛型的类库和代码,因为在字节码(class)层面是没有泛型概念的。
- 擦除前
1
2
3
4
5
6
7
8
9 class Generic<T> {
private T obj;
public Generic(T o) {
obj = o;
}
public T getObj() {
return obj;
}
}
- 擦除后
1
2
3
4
5
6
7
8
9 class Generic { // T --> 消失了
private Object obj;
public Generic(Object o) { // T --> Object
obj = o;
}
public Object getObj() {
return obj;
}
}
- 注意事项
1
2
3
4
5
6
7
8
9
10
11 public static void main(String[] args) {
// 编译报错
// ArrayList<Integer>和new ArrayList<Long>在编译期间是不同的类型
// ArrayList<Integer> list = new ArrayList<Long>();
// 但是编译完成后,它们对应的是同一份class文件:ArrayList.class
ArrayList<Integer> list1 = new ArrayList<Integer>();
ArrayList<Long> list2 = new ArrayList<Long>();
// 大家大致能看懂即可,后续反射章节会补充
System.out.println(list1.getClass() == list2.getClass());
// true
}注意,泛型信息被擦除后,所有的泛型类型都会统一变为原始类型:Object
1
2
3
4
5
6
7
8
9 //编译报错
//因为在编译后,泛型信息会被擦除
//所以下面两个run方法不会构成重载,本质上都是 public void run(List list)
public class Test {
public void run(List<String> list){ // String -> Object
}
public void run(List<Integer> list){ // Integet -> Object
}
}可以看出,Java的泛型只存在于编译时期,泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行是由于对象类型不匹配引发的异常。
但是在编译成功后,所有泛型信息会被擦除,变为原始类型Object。
泛型小结
注解
概述
注解(Annotation),是JDK1.5引入的技术,用它可以对Java中的某一个段程序进行说明或标注,并且这个注解的信息可以被其他程序使用特定的方式读取到, 从而完成相应的操作。
例如, @Override 注解
1
2
3
4
5
6 public class Person {
public String toString() {
return super.toString();
}
}@Override功能解析:
编译器在编译Person类的时候,会读取到toString方法上的注解@Override,从而帮我们检查这个方法时候是重写父类中的,如果父类中没有这个方法,则编译报错。
注解和注释的区别:
- 注解是给其他程序看的,通过参数的设置,可以在编译后class文件中【保留】注解的信息,其他程序读取后,可以完成特定的操作
- 注释是给程序员看的,无论怎么设置,遍历后class文件中都是【没有】注释信息,方便程序员快速了解代码的作用或结构。
格式
- 没有属性的注解:
1
2 public 注解名称 {
}
- 有属性,但没有默认值的注解:
1
2
3 public 注解名称 {
public 属性类型 属性名();
}
- 有属性,有默认值的注解:
1
2
3
4 public 注解名称 {
属性类型 属性名() default 默认值 ;
}注意, public 可以省去不写,默认就是 public
范围
注解的使用范围,都定义在了一个枚举类中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 import java.util.annotation;
/* @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 4.1 The Kinds of Types and Values
*/
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
- TYPE,使用在类、接口、注解、枚举等类型上面
1
2
3
public class Hello {
}
- FIELD,使用在属性上面
1
2
3
4
5 public class Hello {
private String msg;
}
- METHOD,使用在方法上面
1
2
3
4
5 public class Hello {
public void say() {
}
}
- PARAMETER,使用在方法的参数前面
1
2
3
4 public class Hello {
public void say( { String name)
}
}
- CONSTRUCTOR,使用在构造器上面
1
2
3
4
5 public class Hello {
public Hello() {
}
}
- LOCAL_VARIABLE,使用在局部变量上面
1
2
3
4
5
6 public class Hello {
public void say() {
int num = -1;
}
}
- ANNOTATION_TYPE,使用在注解类型上面
1
2
3
public Hello {
}
- PACKAGE,使用在包上面
1
2
3
package com.briup.test;注意:包注解只能在package-info.java文件中(ecplise中有这个,idea中也找到啦)
- ecplise
- idea
注意,package-info.java文件里面,只能包含package声明,并做出描 述,以便将来生成doc文件,可以从API源码src.zip中,看到每个包下面 都可以对应的package-info.java文件对该包做出描述
- TYPE_PARAMETER,使用在声明泛型参数前面,JDK8新增
1
2
3
4
5
6 public class Hello{
public < T> void sendMsg(T t) {
}
}
interface Action< T> {
}
- TYPE_USE,使用在代码中任何类型前面,JDK8新增
1
2
3
4
5
6
7 public class Hello{
public void say( { String name)
List<null; String> list =
Object obj = new String("briup");
String str = ( String) obj;
}
}
保持
类中使用的注解,根据配置,可以【保持】到三个不同的阶段:
- SOURCE,注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃。
- CLASS,注解被保留到class文件,但jvm加载class文件时候被遗弃
- RUNTIME,注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import java.util.annotation;
/* @author Joshua Bloch
* @since 1.5
*/
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}注意,Retention是保留、保持的意思,Policy是政策、策略的意思
- 如果需要在运行时去动态获取注解信息,那只能用RUNTIME注解,比如@Deprecated使用RUNTIME注解
- 如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用CLASS注解
- 如果只是做一些检查性的操作,比如@Override和@SupperessWarnings,使用SOURCE注解
注意:因为RUNTIME的生命周期最长,所以其他两种情况能作用到的阶段,使用RUNTIME也一定能作用到
元注解
在我们进行自定义注解的时候,一般会使用到元注解,来设置自定义注解的基本 特点。所以,元注解也就是对注解进行基本信息设置的注解。
常用的元注解:
- @Target:用于描述注解的使用范围,例如用在类上面还是方法上面
- @Retention:用于描述注解的保存策略,是保留到源代码中、Class文件中、还是加载到内存中
- @Documented:用于描述该注解将会被javadoc生产到API文档中
- @Inherited:用户表示某个被标注的类型是被继承的,如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class子类。
1
2
3
4
public Override {
}
1
2
3
4
5
6
MyAnnoation {
}
1
2
3
4
5
6
public SuppressWarnings {
String[] value(); // 有属性
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Resource {
String name() default "";
String lookup() default "";
Class<?> type() default java.lang.Object.class;
enum AuthenticationType {
CONTAINER,
APPLICATION
}
AuthenticationType authenticationType() default
AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
}
小结
- 元数据信息:注解本身不会直接影响程序的执行,而是提供了一种用于存储和传递元数据的方式。元数据是关于程序中元素的描述信息,可以包含名称、类型、范围、约束等信息,用于辅助程序的开发、编译和执行。
- 内置注解:Java提供了一些内置的注解,例如**@Overried、@Deprecated、@SuppressWarnings等。这些注解用于提供关于方法覆盖、过时方法、警告抑制**等信息,编译器和工具可以根据注解来执行相应的处理。
- 自定义注解:Java也允许开发者自定义注解,通过**@interface关键字**定义新的注解类型。自定义注解可以包含元素(成员变量),可以为这些元素指定默认值,并可以在程序中使用注解,并提取注解中的元素值。
- 应用领域:注解在Java中被广泛应用于各个领域,例如框架开发、测试工 具、持久化框架、Web开发等。通过注解,可以提供更丰富的配置和行为控 制,简化了代码的编写和配置。
- 本章对于注解的掌握仅限于元数据信息的认识、内置注解的认识等,在后期学习反射的时候会完善自定义注解的学习,并且使用反射机制获取注解 并进行操作。
❤️❤️❤️忙碌的敲代码也不要忘了浪漫鸭!
自信人生二百年,会当水击三千里。💪