【Java基础|第二十六篇】泛型与注解

目录

(六)泛型与注解

1、泛型:

(1)为什么要有泛型?

(2)泛型的概述:

(3)泛型的应用:

(4)自定义泛型:

<1>泛型类:

<2>泛型接口:

<3>泛型方法:   

(5)注意事项:

(6)通配符:?

(7)泛型边界:

(8)类型擦除:

2、注解:了解即可

(1)概述:

(2)注解的特性:

(3)注释与注解的区别:

(4)常见注解:


(六)泛型与注解

1、泛型:
(1)为什么要有泛型?

① 解决 “通用逻辑重复实现” 的问题,提升代码复用性

在没有泛型的场景中,要实现 “存储任意类型元素的集合” 或 “通用工具方法”,通常有两种低效方式:

  • Object类型兼容所有类型:比如 Java 早期的ArrayList内部用Object[]存储元素,看似通用,却埋下了类型混乱的隐患;

  • 为每种类型重复写逻辑:比如为String写一个StringList,为Integer写一个IntegerList,逻辑完全相同,只是类型不同,导致代码冗余。

泛型通过类型参数(如TKV)解决了这个问题:定义时用占位符表示 “任意类型”,使用时再指定具体类型(如ArrayList<String>Map<Integer, User>)。这意味着一套通用逻辑(如集合的 “添加”“获取” 操作)可以适配所有类型,无需重复编码。

实现 “编译时类型安全”,从源头减少运行时错误

没有泛型时,集合可以存储任意引用类型(如StringIntegerDate),但编译器无法判断开发者的真实意图,导致两个风险:

  • 无意识的类型混乱:比如往 “本应存字符串的集合” 里塞数字(list.add(123)),编译器不报错,但运行时取出元素强转时会抛出ClassCastException

  • 错误只能在运行时暴露:类型不匹配的问题无法在开发阶段发现,可能直到线上运行才崩溃。

泛型将类型检查提前到编译期:通过List<String>明确告诉编译器 “此集合只能存String”,一旦尝试添加其他类型(如int),编译直接报错,从源头拦截错误。

避免 “冗余的强制转换”,让代码更简洁、高效

你可能会问:“集合存Object,直接用Object类型变量不行吗?” 但实际开发中,我们几乎不会只把对象当Object用 —— 我们需要调用其具体类型的方法(如Stringlength()IntegerintValue())。

没有泛型时,从集合取元素必须手动强转(String s = (String) list.get(0)),既繁琐又容易写错;
有泛型时,编译器已知集合元素的具体类型(如List<String>),取出时直接是String类型(String s = list.get(0)),可直接调用length(),无需转换。这不仅让代码更简洁,还减少了运行时类型检查的性能开销。

支持 “可控的灵活性”,平衡通用与具体

泛型并没有剥夺集合 “存储多种类型” 的能力,而是让这种灵活性更可控:

  • 存储同一类别的不同子类型:比如List<Number>可以存IntegerDouble等所有数字类型,取出时无需强转成具体子类,直接用Number的通用方法(如doubleValue());

  • 显式声明 “任意类型”:如果确实需要存完全无关的类型(虽然不推荐),可以用List<Object>,效果等同于不指定泛型,但代码意图更清晰(明确表示 “此处可能有多种类型”);

  • 通过通配符细化控制:用? extends T(上限)、? super T(下限)进一步限制类型范围,比如void print(List<? extends Number> list)可接收所有数字集合,既通用又安全。

(2)泛型的概述:

泛型(Generics)的概念是在JDK1.5中引入的,它的主要目的是为了解决类型安全性和代码复用的问题。

泛型是一种强大的特性,它允许我们在定义类、接口和方法时使用参数化类型

对泛型类型参数化时,只能表示引用数据类型

(3)泛型的应用:

接口源码:

//Collection接口定义,其是一个泛型接口
public interface Collection<E> {
    //省略...
    boolean add(E e);
}

Collection是一个泛型接口,泛型参数是Eadd方法的参数类型也是E类型。在使用Collection接口的时候,给泛型参数指定了具体类型,那么就会防止出现类型转换异常的情况,因为这时候集合中添加的数据已经有了一个规定的类型,其他类型是添加不进来的。

泛型参数化:这里我们指定为String

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);
可简写为菱形泛型形式:   Collection<String> c = new ArrayList<>();
(4)自定义泛型:
<1>泛型类:

        如果泛型参数定义在类上面,那么这个类就是一个泛型类

        定义格式:
[修饰符] class 类名<泛型类型名1,泛型类型名2,...> {
    0个或多个数据成员;
    0个或多个构造方法;
    0个或多个成员方法;
}
//注意:之前用确定数据类型的地方,现在使用自定义泛型类型名替代

        实例化格式:

泛型类名<具体类型名1,具体类型名2……> 对象名 = new 泛型类名<>(实参);

        举例:

//自定义泛型类:圆
//class 类名<泛型类型1,泛型类型2,...>
// 泛型类型名字可以自行定义
public class Circle<T, E> {
    //原来具体数据类型的地方,使用泛型类型名替换即可
    private T x;
    private T y;
    private E radius;
    //无参构造器没有任何改变
    public Circle() {}
    //原来具体数据类型的地方,使用泛型类型名替换即可
    public Circle(T x, T y, E radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
    }
    public T getX() {
        return x;
    }
    public void setX(T x) {
        this.x = x;
    }
    public T getY() {
        return y;
    }
    public void setY(T y) {
        this.y = y;
    }
    public E getRadius() {
        return radius;
    }
    public void setRadius(E radius) {
        this.radius = radius;
    }
    @Override
    public String toString() {
        return "Circle [x=" + x + ", y=" + y + ", radius=" + radius + "]";
    }

} 


//测试类:
package com.briup.chap08.test;
import com.briup.chap08.bean.Circle;
public class Test014_GenericsClass {
public static void main(String[] args) {
    //实例化泛型类对象:
    // 泛型类<具体类型1,具体类型2,...> 对象 = new 泛型类<>(实参s);
    //1.实例化具体类对象,2种泛型设置为Integer和Double
    // 注意,泛型类可以是任意引用类型
    Circle<Integer, Double> c1 = new Circle<>(2,3,2.5);
    int x = c1.getX();
    double r = c1.getRadius();
    System.out.println("x: " + x + " radius: " + r);
    System.out.println("------------------");
    //2.实例化具体类对象,2种泛型设置为Double和Integer
    Circle<Double, Integer> c2 = new Circle<>(2.0,3.0,2);
    double x2 = c2.getX();
    int r2 = c2.getRadius();
    System.out.println("x2: " + x2 + " r2: " + r2);
    }
}
//运行结果:
x: 2 radius: 2.5
------------------
x2: 2.0 r2: 2
<2>泛型接口:

        如果泛型参数定义在接口上面,那么这个接口就是一个泛型接口

        定义格式:

[修饰符] interface 接口名<泛型类型名1,泛型类型名2,...> { }

        举例:

    public interface Action<T> {...}

    public static void main(String[] args) {
        //创建匿名内部类
        Action<String> a = new Action<>() {
            //...
        };
    }
    
<3>泛型方法:   

        如果泛型参数定义在方法上面,那么这个方法就是一个泛型方法

        定义格式:

    [修饰符] <泛型类型名> 返回值类型 方法名(形式参数列表){
        方法具体实现;
    }

        调用格式:

    类对象.泛型方法(实参列表);
    类名.static泛型方法(实参列表);

注意:泛型方法调用时不需要额外指定泛型类型,系统自动识别泛型类型

(5)注意事项:
//编译通过
//父类型的引用,指向子类对象
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<Integer> ArrayList<Object> 之间没有子父类型的关系,它们就是俩个不同的类型 。所以,也就是说,俩个类型,如果是当做泛型的指定类型的时候,就没有多态的特点了 

(6)通配符:?

        他可以表示任意的引用数据类型

        <1>应用:在方法参数中用通配符,则表示可以传各种类型的集合

public void test(Collection<?> c) {

}


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<任意引用类型>());
}

        <2>通配符带来的问题:   

通配符的 "只读" 特性:

        Java 泛型的通配符(?)本质上是一种 "未知类型" 的表示,它有一个重要特性:只能读取,不能写入(除了null)。这一特性导致了它在方法参数和类定义中表现不同。

当方法的参数是泛型类型为通配符的集合时,方法中只能读取元素(遍历或者get),不能添加会修改元素,只能添加null。

如果一个类的泛型参数是通配符(如class MyClass<?>),会导致严重的使用限制,类内部的方法无法向泛型变量中写入任何具体类型的元素(除了null),实例化时也无法指明集合存放的数据类型,使类失去意义,因此实际开发中绝对不会讲类的泛型参数定义为通配符。

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);
    }
}
(7)泛型边界:

        在默认情况下,泛型的类型是可以任意设置的,只要是引用类型就可以。

        如果在泛型中使用 extends super 关键字,就可以对泛型的类型进行限制,即规定泛型的上限下限

泛型的上限

格式类型名<? extends 类型 > 对象名称

意义只能接收该类型及其子类

举例:

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>();
}

泛型的下限

格式类型名<? super 类型 > 对象名称

意义只能接收该类型及其父类型

(8)类型擦除:

泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。

由于泛型是JDK1.5才加入到Java语言特性的,Java让编译器擦除掉关于泛型类型的信息,这样使得Java可以向后兼容之前没有使用泛型的类库和代码,因为在字节码(class)层面是没有泛型概念的。

注意:因为他们的最终的字节码文件是相同的,因此最后无法构成重载。

总结:

2、注解:了解即可
(1)概述:

        注解(Annotation),是JDK1.5引入的技术,用它可以对Java中的某一个段程序进行说明或标注,并且这个注解的信息可以被其他程序使用特定的方式读取到,从而完成相应的操作。

(2)注解的特性:

①声明方式:@符号开头

②可附加的位置:可以在类,方法,字段,参数,注解本身等几乎所有程序元素上

③作用:不直接执行逻辑,而是作为“标记”或配置,有其他工具(编译器,框架)解析并处理

(3)注释与注解的区别:

①注解是给其他程序看的,通过参数的设置,可以在编译后class文件中【保留】注解的信息,其他程序读取后,可以完成特定的操作

②注释是给程序员看的,无论怎么设置,编译后class文件中都是【没有】注释信息,方便程序员快速了解代码的作用或结构

(4)常见注解:

①内置注解:@Override 标记方法是对父类方法的重写,编译器直接验证重写是否正确

                      @SupperssWarnings 抑制编译器产生特定警告

②元注解:修饰注解的注解,一般用于定义注解中的行为(生命周期,适用范围)

                       @Target 指定注解可以附加的位置

@Target(ElementType.METHOD)
//指定该注解只能用于修饰方法
@interface MyAnnotation{……}

③自定义注解:通过@interface关键字定义,默认为public

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值