8.1 认过泛型方法的脸(<T> 写在返回类型前);这节讲透三件新货:类型推断(调用时不用写尖括号)、静态泛型方法(8.2"static 用不了 T"那颗雷的官方正解)、泛型类 vs 泛型方法怎么选——学完你会发现 JDK 工具类清一色全是这套。
1. 先认门:方法私有的占位符
泛型类里的 T 是全类共用的占位符;泛型方法的 T 是只归这一个方法的临时占位符——声明位置在返回类型之前:
修饰符 <T> 返回类型 方法名(参数列表) {
// 方法体里 T 随便用
}
// 三个岗位一起看:
public static <T> T pick(T[] arr) { // ①<T>声明 ②返回值 ③参数
return arr[new Random().nextInt(arr.length)];
}
规矩没有新的,就是 8.1 的组合:T 只能当引用类型用;new T() 不行;T[] 能收但别自己造。
2. 类型推断:调用时不用写尖括号
泛型方法最大的福利是编译器帮你填空——从实参的类型反推 T 是谁:
String[] names = {"波仔", "墨池", "AI"};
Integer[] scores = {88, 95, 76};
String s = pick(names); // 实参 String[] → 推断 T = String,返回 String
Integer i = pick(scores); // 实参 Integer[] → 推断 T = Integer
// 也能显式指定(语法怪:尖括号在点号和方法名之间),少见,会认即可:
String s2 = this.<String>pick(names);
看实参:传 String[] 进去,T 就是 String。所以同一个方法,今天服务 String、明天服务 Integer——一份代码通吃,这正是"泛"字的含义。
3. 头号正戏:静态泛型方法
8.2 埋的雷在这收网:static 成员用不了类上声明的 T(类加载时 T 未定)——但 static 方法自己声明一个 <T> 就完全合法:
class Util<T> { // 类上的 T
// private static T shared; ✗ 8.2 的雷
// public static T get() { } ✗ 同一颗雷
public static <E> E first(E[] arr) { // ✓ 方法自己声明 E,与类上的 T 无关
return arr[0];
}
}
两个细节:
- 方法上的占位符和类上的占位符是两码事——哪怕都叫 T,方法里的 T 也遮住(优先于)类上的 T;惯例上方法自己另起一个名字(E、U)避免误会。
- 这就是 JDK 工具类的标准长相:
Collections.swap、Arrays.asList、Collections.singleton……全是static <T>开头——工具类不能 new(6.3 的套路),想泛型就只剩这一条路。
4. 泛型类 vs 泛型方法怎么选
| 对比项 | 泛型类 class Box<T> |
泛型方法 static <T> T pick |
|---|---|---|
| T 声明在哪 | 类名后 | 返回类型前 |
| T 的作用范围 | 全类共享(字段、方法都能用) | 只归本方法 |
| static 能不能用 T | ✗ | ✓(方法自带声明) |
| T 什么时候定型 | new Box<String>() 时 |
调用时从实参推断 |
| 适合 | 装东西的容器 | 干活的工具(一次定型用完就扔) |
选型口诀:到处都用 T → 泛型类;只有一个方法用 → 泛型方法。 拿不准就想想 ArrayList(容器,泛型类)和 Collections.swap(工具,静态泛型方法)。
5. 实战主场:参数之间共用一个 T
泛型方法的杀手锏是让多个参数和返回值对齐到同一个类型——编译器替你把关:
// 交换:arr 里两个位置必须是同一数组的,天然成立
public static <T> void swap(T[] arr, int i, int j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 数组转 List:进什么类型,出什么类型
public static <T> List<T> toList(T[] arr) {
List<T> list = new ArrayList<>();
for (T item : arr) { list.add(item); }
return list;
}
swap(strs, 0, 1) 合法;swap 里想 arr[i] = 42 而 arr 是 String[]?编译器当场拦下——这就是 8.1"把错误提前到编译期"思想的方法版。
Collections.swap(list, i, j)、Collections.sort(list) 底层都是这套。7.7 用的工具,今天看清了身份证:静态泛型方法 + 泛型接口(Comparable,8.3)的组合拳。
6. 动手时间(Demo04)
import java.util.*;
public class Demo04 {
// 泛型方法 1:随机抽一个
public static <T> T pick(T[] arr) {
return arr[new Random().nextInt(arr.length)];
}
// 泛型方法 2:交换
public static <T> void swap(T[] arr, int i, int j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 泛型方法 3:数组转 List
public static <T> List<T> toList(T[] arr) {
return new ArrayList<>(Arrays.asList(arr));
}
public static void main(String[] args) {
String[] names = {"波仔", "墨池", "AI"};
Integer[] scores = {88, 95, 76};
// 类型推断:不用写尖括号
System.out.println("点名: " + pick(names));
System.out.println("抽分: " + pick(scores));
// 交换后类型依旧被安检盯着
swap(names, 0, 2);
System.out.println("换位后: " + Arrays.toString(names));
// 数组转 List
List<Integer> list = toList(scores);
list.add(99); // 转成 List 才能加元素
System.out.println("转集合加分: " + list);
}
}
7. 30 秒实验
实验 1:把 pick 的 <T> 删掉——编译报错 cannot find symbol: class T。占位符没声明就上岗,必炸。
实验 2:在 class Util<T> 里写 public static T get() { }(不声明方法自己的 T)——编译报错,8.2 那颗雷重现;改成 static <E> E get() 立刻合法——正解收网。
实验 3:swap(names, 0, 1) 后再写 names[0] = 42——编译报错。T 已被推断为 String,别想塞 Integer。
实验 4:pick(scores) 的返回值赋给 String s——编译报错 incompatible types。推断出的 Integer 不会偷偷变身。
实验 5:Arrays.asList("a", "b", "c") 按住 Ctrl 点进去——JDK 源码签名就是 static <T> List<T> asList(T... a),亲眼确认工具类套路。
8. 常见误区
<T>写错位置——必须紧贴在返回类型之前(static <T> T pick),写在 static 前面或方法名后面都报错。- 以为 static 从此能随便用 T——静态泛型方法合法的是方法自己声明的 T;类上的 T 照旧与 static 无缘。
- 以为方法上的 T 和类上的 T 是同一个——方法自己声明的 T 优先(遮住类上的);惯例方法内另起名 E/U,望文生义不踩坑。
- 调用时手写
<String>当万能开关——推断已经够用;显式指定是给编译器糊涂时的备用手段(比如传 null),日常别写。 - 以为泛型方法比泛型类高级——两者分工不同:容器用类、工具用方法;一个类里完全可以两种并存(ArrayList 就是一堆泛型方法住在泛型类里)。
- 基本类型直接推断——
pick(new int[]{1,2,3})的 T 会被推断成int[]而不是 Integer,返回的是"整个数组"——想逐个操作请传包装类型数组(8.1 铁规矩第一条)。
9. 折叠自测题
修饰符(static 等)之后、返回类型之前:static <T> T pick(...)。口诀:尖括号贴着返回类型的脸。
不用——编译器从实参自动推断 T = String。显式写法 this.<String>pick(names) 存在但极少用。推断是常态,显式是备用。
方法自己声明占位符:static <E> E get(...)。类上的 T 对 static 不可见(8.2 的雷),方法自带 <E> 是唯一合体姿势——JDK 工具类全这么写。
不是。方法声明自己的 <T> 时,它遮住类上的同名占位符,优先按方法自己的算。所以惯例上方法内另起名(E、U),避免自己糊涂。
public static <T> String join(T[] arr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
if (i > 0) sb.append("-");
sb.append(arr[i]);
}
return sb.toString();
}
要点:<T> 贴返回类型前;arr 里是 Object 一族,append 什么类型都收(6.2 StringBuilder 的 toString 收尾)。
10. 下一步学什么
- ☑ 8.5 类型通配符 ✅ —— 点此复习:? 无界/extends/super 三扇门拆掉「泛型不传递」的墙,PECS 口诀与 Collections.copy 签名。
相关笔记
- 第八章 泛型/8.1 泛型基础:泛型方法初识、三条铁规矩——本节的地基
- 第八章 泛型/8.2 泛型类:static 用不了 T 的雷——本节给出官方正解
- 第八章 泛型/8.3 泛型接口:Comparable/Comparator——工具方法背后的合同
- 第六章 Java API/6.3 System类:工具类不能 new 的套路——静态泛型方法的宿主
- 第七章 集合/7.7 常用工具类:Collections/Arrays 的方法全是静态泛型方法,本节掀开身份证