《Java核心技术卷I》泛型篇笔记(一) 泛型的基本使用

写在最前:本笔记全程参考《Java核心技术卷I》,添加了一些个人的思考和整理

泛型概述

1. 为什么要使用泛型

泛型程序设计(generic programming)意味着编写的代码可以对多种不同类型的对象重用

在还没有泛型之前,泛型程序设计是通过继承实现的,例如ArrayList只维护一个Object[]引用的数组。这样存在两个问题:获取值时需要强转,且可以存入任意值

泛型提供了类型参数(type parameter)用于指定类型,解决了上述的问题。它会让你的程序更加易读安全

泛型的作用是:用户想要使用哪种类型,就在创建的时候指定类型。使用的时候,该泛型就会自动转换成用户想要使用的类型。

泛型类和泛型方法

2. 定义简单的泛型类

泛型类就是有一个或多个变量的类。

public class Pair<T> {} // 一个泛型变量
public class Pair<T, U> {} // 多个泛型变量

类型变量在整个类定义中用于指定方法的返回值类型以及字段和局部变量的类型:

private T getFirst(); // 泛型类中的泛型方法

3. 泛型类的实际应用

如果有一个结点类:

public class Node {
    private int data;
    private Node next;
    public void setData(int data) {
        this.data = data;
    }
    public int getData() {
        return data;
    }
    // 其他的setter getter,以及链表的功能,比如get remove等等
}

这个节点类只能够存储int类型的数据

但是如果我想要一个能储存double类型的结点,我就需要重写一遍这个代码,同时把所有的代码都复制一次(比如get/remove等等),这么麻烦就只为了把data还有get/set方法改成double类型。现在使用泛型就可以很好地解决这个问题:

public class Node<T> { // 声明为泛型类
    // 使用泛型替代原有的数据类型
    T data; 
    Node next;
    
    // 使用泛型替代原有的数据类型
    public void setData(T data) { 
        this.data = data;
    }
    public T getData() {
        return data;
    }
    // 其他的setter getter,以及链表的功能,比如get remove等等
}

此时的泛型结点,不仅支持基本数据类型(的包装类型),还支持自定义的类对象

严格来说,泛型不支持基本数据类型,如int/float/double等等,不过可以转换成包装类型Integer/Float/Double
具体原因请看:《Java核心技术卷I》泛型篇笔记(三) 泛型的特性和局限

例如对于员工Employee类,可以使用Node<Employee> node = new Node<>();来创建员工类的结点,进而拓展到员工链表。这样子,每次拥有一个新的类,要一个类型的链表时,不必完全复制一次原代码,就可以轻松完成需求。

4. 泛型方法

声明泛型方法

如果泛型方法不在泛型类中声明,则还需要在方法处声明泛型:

public static <T> T getMiddle(T... a) {} 

注意,在非泛型类中,使用泛型T作为返回值之前,需要先用<T>声明泛型方法

实际上,只要不是在泛型类中,每个用到泛型的方法的地方都要先使用<T>声明泛型:

public class Test {
	// 不是泛型类
	
	// 返回值为void,但是参数用到了泛型,所以方法声明中需要先写上<T>
	public <T> void set(T t) {...}
}

在C++中,需要将类型参数放在方法名之后,这样可能会导致解析的二义性,例如:

getMiddle(fun<a,b>(c))可以理解为“用fun<a, b>(c)的结果调用getMiddle”,或者理解为“用两个布尔值fun<ab>(c)调用getMiddle

调用泛型方法

在调用一个泛型方法之前,可以把具体的类型包围在建括号中,放在方法前,也可以省略:

String middle = ArrayAlg.<String>getMiddle("John", "Q.", "Public");
String middle = ArrayAlg.getMiddle("John", "Q.", "Public");

有些时候,如果参数或返回值会引起歧义,编译器可能会报错:

double middle = ArrayAlg.getMiddle(3.14, 123, 0); // 出错

上面这句代码,编译器将把参数自动装箱为一个Double和2个Integer对象,然后寻找这些类共同的父类:NumberComparable接口。此时的补救措施是将所以参数都定义为double

泛型方法的使用:

菜鸟教程——Java 泛型

public class GenericMethodTest {
   // 泛型方法,E表示Element,其实你写成T也是可以的,T表示Type                         
   public static <E> void printArray(E[] inputArray) {
       // 输出数组元素            
       for (E element : inputArray){        
           System.out.printf("%s ", element);
       }
       System.out.println();
    }

    public static void main( String args[]) {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
        Character[] charArray = {'H', 'E', 'L', 'L', 'O'};

        System.out.println("Array integerArray contains:");
        printArray(intArray); // 传递一个整型数组

        System.out.println("\nArray doubleArray contains:");
        printArray(doubleArray); // 传递一个双精度型数组

        System.out.println("\nArray characterArray contains:");
        printArray(charArray); // 传递一个字符型型数组
    } 
}

类型变量的限定

限定类型变量继承于某个类或接口(规定上界):

public static <T extends Comparable> T min(T[] a)

一个类型变量或通配符可以有多个限定,但最多只能有一个类限定,且它必须是限定列表的第一个限定类型,限定类型使用&分隔,而泛型使用,分隔:

T extends Comparable & Serializable
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值