Java基础快速入门:泛型

本文纲要

  1. 泛型概述
    不写泛型的弊端
    泛型的好处
  2. 泛型类的使用
    ArrayList为例
    源码解析
  3. 自定义泛型类
    定义格式
    案例:Box
  4. 泛型方法的使用
    ArrayList中的toArray方法
    空参 vs 带参
  5. 自定义泛型方法
    定义格式
    案例:批量添加元素
  6. 泛型接口
    定义格式
    两种使用方式
  7. 通配符
    无界通配符 <?>
    上界限定 <? extends 类型>
    下界限定 <? super 类型>
    示例项目结构

本文所有代码均位于 mygenericity 项目中,包结构如下:

mygenericity/src/com/wb/
├── genericitysummarize/
│   ├── GenericitySummarize.java 
│   └── GenericityClass.java 
├── genericityclass/
│   ├── Box.java 
│   └── MyGenericityClass.java 
├── genericitymethod/
│   ├── GenericityMethod1.java 
│   └── GenericityMethod2.java 
├── genericityinterface/
│   └── GenericityInterface.java 
└── genericityglobbing/
    └── genericityglobbing1.java 

泛型概述

在创建集合时,如果不指定泛型,集合中元素的类型会被当作 Object,虽然数据本身可以正常存储,但在取出时会丢失具体类型信息。

不写泛型的弊端演示:

public class GenericitySummarize {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();      // 没有泛型 
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add(123);                        // 添加Integer也没问题 
 
        Iterator it = list.iterator();
        while (it.hasNext()) {
            // 取出时是Object,必须强转 
            String next = (String) it.next(); // 当取到123时会抛出ClassCastException 
            int len = next.length();
            System.out.println(len);
        }
    }
}

问题总结:
集合中可以随意添加不同类型数据(都是 Object)。
取出时必须强转,如果类型不一致,会在运行时抛出 ClassCastException
代码冗余,所有需要强转的地方都得自己处理。

泛型的好处:

  1. 将运行时的错误提前到编译期(添加元素时就会类型检查)。
  2. 避免了强制类型转换,代码更简洁安全。

泛型类的使用

Java中已定义好的泛型类最典型的代表就是 ·ArrayList·。我们可以查看源码发现 ·ArrayList· 的声明:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... }

这里的 <E> 代表一个未知的类型,会在创建对象时被确定。

public class GenericityClass {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(); // 此时 E 被明确为 String 
        list.add("hello");
        String s = list.get(0); // 直接得到String,无需强转 
    }
}

ArrayList 中的 add(E e)get(int index): E 等方法里的 E 都会随着创建对象时的具体类型参数而变化。这样设计让集合类可以适应任意数据类型,同时保证类型安全。

自定义泛型类

定义格式

修饰符 class 类名<泛型标识> { ... }

泛型标识可以是任意符合变量命名规则的字母,常见的有 E(Element)T(Type)K(Key)V(Value)等。

案例:Box类

定义一个万能盒子,可以存放任意类型的元素:

// Box.java 
public class Box<E> {
    private E element;
 
    public E getElement() {
        return element;
    }
 
    public void setElement(E element) {
        this.element = element;
    }
}

使用方式:

// MyGenericityClass.java 
public class MyGenericityClass {
    public static void main(String[] args) {
        // 存放字符串 
        Box<String> box1 = new Box<>();
        box1.setElement("给小丽的土味情话");
        String element1 = box1.getElement();
        System.out.println(element1);
 
        // 存放整数 
        Box<Integer> box2 = new Box<>();
        box2.setElement(19);
        Integer element2 = box2.getElement();
        System.out.println(element2);
    }
}

在创建 Box 对象时,指定的类型会传递给类中的 E,从而使 element 和相应的 get/set 方法拥有具体的类型。

泛型方法的使用

Java内置的 ArrayList 就提供了泛型方法的典型应用:toArray

public class GenericityMethod1 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("给小花同学的土味情话");
        list.add("给小丽同学的土味情话");
        list.add("给小路同学的土味情话");
 
        // 空参:返回Object[]
        Object[] objects = list.toArray();
        System.out.println(Arrays.toString(objects));
 
        // 带参数的泛型方法:返回指定类型的数组 
        String[] strings = list.toArray(new String[list.size()]);
        System.out.println(Arrays.toString(strings));
    }
}

空参的 toArray() 返回的是 Object[],想使用字符串方法仍需强转。
带参的 toArray(T[] a) 会根据传入的数组类型返回相同类型的数组,避免了强转。
这里的 T 就是泛型方法中定义的类型参数。

自定义泛型方法

定义格式

修饰符 <泛型标识> 返回值类型 方法名(参数列表) { ... }

泛型标识必须声明在返回值之前,否则编译器会认为它是一个具体的类名。

案例:批量添加元素

定义一个可以批量添加四个元素到集合中并返回集合的方法:

public class GenericityMethod2 {
    public static void main(String[] args) {
        ArrayList<String> list1 = addElement(new ArrayList<String>(), "a", "b", "c", "d");
        System.out.println(list1);
 
        ArrayList<Integer> list2 = addElement(new ArrayList<Integer>(), 1, 2, 3, 4);
        System.out.println(list2);
    }
 
    // 泛型方法:类型T在调用时确定 
    public static <T> ArrayList<T> addElement(ArrayList<T> list, T t1, T t2, T t3, T t4) {
        list.add(t1);
        list.add(t2);
        list.add(t3);
        list.add(t4);
        return list;
    }
}

调用 addElement 时,根据传入的集合和元素类型自动推导出 T,无需手动指定。

泛型接口

定义格式

修饰符 interface 接口名<泛型标识> { ... }

两种使用方式

假设有如下泛型接口:

interface Genericity<E> {
    void method(E e);
}

方式一:实现类仍然不确定类型(延续泛型)

class GenericityImpl1<E> implements Genericity<E> {
    @Override 
    public void method(E e) {
        System.out.println(e);
    }
}
 
// 使用 
GenericityImpl1<String> genericity = new GenericityImpl1<>();
genericity.method("小丽给我的土味情话");

GenericityImpl1 本身也是一个泛型类,创建对象时才确定类型。

方式二:实现类直接指定具体类型

class GenericityImpl2 implements Genericity<Integer> {
    @Override 
    public void method(Integer integer) {
        System.out.println(integer);
    }
}
 
// 使用 
GenericityImpl2 genericityImpl2 = new GenericityImpl2();
genericityImpl2.method(19);

此时 GenericityImpl2 是一个普通类,类型被固定为 Integer

通配符

通配符 ? 表示未知类型,常用于接收各种泛型集合,但不能添加元素(null除外)

1 ) 无界通配符 <?>

public static void printList(ArrayList<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

2 ) 上界限定 <? extends 类型>

表示可以接收该类型及其所有子类。例如 <? extends Number> 可以接收 NumberIntegerDouble

3 ) 下界限定 <? super 类型>

表示可以接收该类型及其所有父类。例如 <? super Number> 可以接收 NumberObject
完整案例

考虑继承关系:

Object

Number

Integer

代码演示:

public class genericityglobbing1 {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<Number> list2 = new ArrayList<>();
        ArrayList<Object> list3 = new ArrayList<>();
 
        // 上界通配符:接收 Number 及其子类 
        method1(list1);  // OK,Integer是Number的子类 
        method1(list2);  // OK 
        // method1(list3);  // 编译错误,Object不是Number的子类 
 
        // 下界通配符:接收 Number 及其父类 
        method2(list2);  // OK 
        method2(list3);  // OK,Object是Number的父类 
        // method2(list1);  // 编译错误,Integer不是Number的父类 
    }
 
    // 上界:只能是 Number 或 Number 的子类 
    public static void method1(ArrayList<? extends Number> list) {
        for (Number num : list) {
            System.out.println(num);
        }
    }
 
    // 下界:只能是 Number 或 Number 的父类 
    public static void method2(ArrayList<? super Number> list) {
        // 这里只能以Object类型访问,因为父类最低是Number,最高是Object 
        for (Object obj : list) {
            System.out.println(obj);
        }
    }
}

记忆要点:
? extends Number:规定了上边界,最多到 Number,所以只能取(当作 Number),不能随便添加。
? super Number:规定了下边界,至少是 Number,可以添加 Number 及其子类,但取出时只能当作 Object

总结

泛型是Java提供的一种编译时类型安全检测机制,合理使用泛型可以写出更健壮、更优雅的代码。掌握泛型类、泛型方法、泛型接口以及通配符的用法,是Java基础的重要内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值