Java 方法前面加 <T> 是做什么?泛型方法 原理、样例

在 Java 中,方法前面加上 <T> 表示该方法是一个泛型方法泛型方法允许你在方法签名中指定一个或多个类型参数,从而使得该方法可以处理多种类型的对象。这增加了代码的灵活性和复用性。

一、基本语法

<T1, T2, ..., Tn> 返回类型 方法名(形参列表) {
    // 方法体
}
  • <T1, T2, ..., Tn>:类型参数列表,可以有一个或多个类型参数。
  • 返回类型:方法的返回类型,可以是具体的类型,也可以是类型参数。
  • 方法名:方法的名称。
  • 形参列表:方法的参数列表,可以使用类型参数作为参数类型。

 二、代码示例

1. 单个类型参数的泛型方法

 假设现在富士康(Foxconn)要生产一批手机,它给手机厂商定下了一个规范:

package com.study.genericity.entity;

/**
 * 富士康的手机生产规范
 */
public abstract class MobilePhone {
    abstract void produce();
}

 OPPO、Huawei 都按照 富士康 的规范(extends MobilePhone)生产手机:

package com.study.genericity.entity;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class OPPO extends MobilePhone {
    /** 型号 */
    private String model;
    /** 颜色 */
    private String color;
    /** 运行内存 */
    private String ram;
    /** 存储 */
    private String rom;

    @Override
    public void produce() {
        System.out.println("正在生产 OPPO手机 :型号-"+model+" 颜色-"+color+" 运行内存-"+ram+" 存储容量-"+rom);
    }
}
----------------------------------------
package com.study.genericity.entity;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class Huawei extends MobilePhone {
    /** 型号 */
    private String model;
    /** 颜色 */
    private String color;
    /** 是否折叠 */
    private boolean foldOrNot;
    /** 运行内存 */
    private String ram;
    /** 存储 */
    private String rom;

    @Override
    public void produce() {
        String description = "正在生产 华为手机 :"+(foldOrNot?"折叠屏 ":"")+model+" "+color+" "+ram+" "+rom;
        System.out.println(description);
    }
}

 富士康 为他们开了一条通用生产线生产手机:

package com.study.genericity.entity;

public class Foxconn {
    /** 富士康通用生产线 */
    public static void produce(MobilePhone mobilePhone) {
        mobilePhone.produce();
    }

    public static void main(String[] args) {
        OPPO oppo = new OPPO("Find X7", "白日梦想家", "12GB", "256GB");
        produce(oppo);
        Huawei huawei = new Huawei("Pocket 2", "洛可可白", true, "12GB", "256GB");
        produce(huawei);
    }
}
输出:
正在生产 OPPO手机 :型号-Find X7 颜色-白日梦想家 运行内存-12GB 存储容量-256GB
正在生产 华为手机 :折叠屏 Pocket 2 洛可可白 12GB 256GB

 这个时候来了苹果,他们委托 富士康 生产新版 iPhone。

不过苹果 它财大气粗,它有自己的手机生产规范:

package com.study.genericity.entity;

/**
 * 苹果自己的移动产品生产规范
 */
public abstract class AppleMobile {

    abstract void producePhone();
}
----------------------------------------
package com.study.genericity.entity;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class Apple extends AppleMobile {
    /** 型号 */
    private String model;
    /** 颜色 */
    private String color;
    /** 运行内存 */
    private String ram;
    /** 存储 */
    private String rom;

    @Override
    public void producePhone() {
        System.out.println("正在生产 尊贵的iPhone :"+model+" "+color+" "+ram+" "+rom);
    }
}

 富士康 本来想拒绝,因为苹果的生产流程已经继承了 AppleMobile 规范, 无法再继承 MobilePhone 规范,两个规范并不兼容(Java 单继承)。但如果为苹果单开一条生产线,成本又特别高昂。

package com.study.genericity.entity;

public class Foxconn {
    /** 富士康通用生产线 */
    public static void produce(MobilePhone mobilePhone) {
        mobilePhone.produce();
    }

    /** 为苹果单开一条流水线 */
    public static void produce(AppleMobile appleMobile) {
        appleMobile.producePhone();
    }

    public static void main(String[] args) {
        OPPO oppo = new OPPO("Find X7", "白日梦想家", "12GB", "256GB");
        produce(oppo);
        Huawei huawei = new Huawei("Pocket 2", "洛可可白", true, "12GB", "256GB");
        produce(huawei);
        Apple apple = new Apple("iPhone 16 Pro", "白", "16GB", "128GB");
        produce(apple);
    }
}
输出:
正在生产 OPPO手机 :型号-Find X7 颜色-白日梦想家 运行内存-12GB 存储容量-256GB
正在生产 华为手机 :折叠屏 Pocket 2 洛可可白 12GB 256GB
正在生产 尊贵的iPhone :iPhone 16 Pro 白 16GB 128GB

 这可愁坏了 富士康 的老总,这个时候,聪明的工程师 小林 为老板想了一个办法,他用 泛型方法 的方式,定义了一条通用的流水线,可以忽略类型规范:

package com.study.genericity.entity;

import java.util.function.Consumer;

public class Foxconn {

    /** 用 泛型方法 定义通用生产流水线 */
    public static <T> void produce(Consumer<T> consumer, T phone) {
        consumer.accept(phone);
    }

    public static void main(String[] args) {
        OPPO oppo = new OPPO("Find X7", "白日梦想家", "12GB", "256GB");
        produce(OPPO::produce, oppo);
        Huawei huawei = new Huawei("Pocket 2", "洛可可白", true, "12GB", "256GB");
        produce(Huawei::produce, huawei);
        Apple apple = new Apple("iPhone 16 Pro", "白", "16GB", "128GB");
        // 苹果 定制化生产
        produce(Apple::producePhone, apple);
    }
}
输出:
正在生产 OPPO手机 :型号-Find X7 颜色-白日梦想家 运行内存-12GB 存储容量-256GB
正在生产 华为手机 :折叠屏 Pocket 2 洛可可白 12GB 256GB
正在生产 尊贵的iPhone :iPhone 16 Pro 白 16GB 128GB

富士康 老板高兴坏了,因为这条流水线太好用,老板甚至放弃了自定义的手机生产规范 MobilePhone,给手机厂商更高的自由度(不用再继承 MobilePhone),去生产定制化的手机产品:

package com.study.genericity.entity;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class Huawei {
    /** 型号 */
    private String model;
    /** 颜色 */
    private String color;
    /** 是否折叠 */
    private boolean foldOrNot;
    /** 运行内存 */
    private String ram;
    /** 存储 */
    private String rom;
    /** 定制化生产 */
    public void produceHuawei() {
        String description = "正在生产 华为手机 :"+(foldOrNot?"折叠屏 ":"")+model+" "+color+" "+ram+" "+rom;
        System.out.println(description);
    }
}
---------------------------------------------
package com.study.genericity.entity;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class OPPO {
    /** 型号 */
    private String model;
    /** 颜色 */
    private String color;
    /** 运行内存 */
    private String ram;
    /** 存储 */
    private String rom;
    /** 定制化生产 */
    public void produceOPPO() {
        System.out.println("正在生产 OPPO手机 :型号-"+model+" 颜色-"+color+" 运行内存-"+ram+" 存储容量-"+rom);
    }
}
---------------------------------------------
package com.study.genericity.entity;

import java.util.function.Consumer;

public class Foxconn {

    /** 用 泛型方法 定义通用生产流水线 */
    public static <T> void produce(Consumer<T> consumer, T phone) {
        consumer.accept(phone);
    }

    public static void main(String[] args) {
        OPPO oppo = new OPPO("Find X7", "白日梦想家", "12GB", "256GB");
        // OPPO 定制化生产
        produce(OPPO::produceOPPO, oppo);
        Huawei huawei = new Huawei("Pocket 2", "洛可可白", true, "12GB", "256GB");
        // 华为 定制化生产
        produce(Huawei::produceHuawei, huawei);
        Apple apple = new Apple("iPhone 16 Pro", "白", "16GB", "128GB");
        // 苹果 定制化生产
        produce(Apple::producePhone, apple);
    }
}
输出:
正在生产 OPPO手机 :型号-Find X7 颜色-白日梦想家 运行内存-12GB 存储容量-256GB
正在生产 华为手机 :折叠屏 Pocket 2 洛可可白 12GB 256GB
正在生产 尊贵的iPhone :iPhone 16 Pro 白 16GB 128GB

2、返回类型也是泛型的泛型方法

 输入:

一个列表,和 几个数据。

输出:

数据包含在 列表 中的一个新的列表。

示例:

输入 [2,5,7,9,10] 和 5,27 输出 [5]

输入 ["福建", "江西", "浙江"] 和 "江苏","福建" 输出 ["福建"]

 一种 泛型方法 的实现:

package com.study.genericity.entity;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Test {

    @SafeVarargs
    public static <T> List<T> filter(List<T> list, T... args) {
        List<T> result = new ArrayList<T>();
        for (T t : args) {
            if (list.contains(t)) {
                result.add(t);
            }
        }
        return result;
    }

    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(2,5,7,9,10);
        System.out.println( filter(list1, 5, 27) );
        List<String> list2 = Arrays.asList("福建", "江西", "浙江");
        System.out.println( filter(list2, "江苏", "福建") );
    }
}
输出:
[5]
[福建]

 在这个例子中,filter 方法是一个泛型方法,它的入参是类型参数<T>的 泛型列表 List<T> 和 类型参数<T>的可变参数;它的返回类型还是 类型参数<T>的 泛型列表 List<T>。

这种返回类型也是泛型的泛型方法,在很多框架中都有大量的使用。

比如 cn.hutool.json.JSONUtil :

// 根据 JSON 字符串和 bean Class 类型,解析 JSON 数据 为 指定类型
public static <T> T toBean(String jsonString, Class<T> beanClass) {
        return toBean(parseObj(jsonString), beanClass);
}

<think> 我们正在讨论Java方法中`<T>`和`T`的区别。首先,需要明确这两个概念在方法中的角色。 引用[1]中提到:方法上的`<T>`代表括号里面要用到参数。如果类中已经传递了参数,并且方法中使用的与类中的一致,那么方法上可以不声明。如: ```java class People<T> { public void show(T a) { } // 这里直接使用类定义的T } ``` 但若方法需要独立的(可能与类不同),则必须在方法上声明。 引用[2]和[3]进一步区分了自定义`<T>`和通配符`?`: - `<T>`用于声明一个具体的类参数,在方法签名或类定义中表示一个确定的类- `T`是类参数的引用,在方法内部或类体中用于表示具体的类- `?`是通配符,表示未知类,通常用于化时的灵活性(如`List<?>`)。 现在聚焦问题:**方法中`<T>`和`T`的区别**: 1. **`<T>`的作用**:在方法返回值前声明类参数,表示该方法是一个方法,且`T`是一个类变量。如: ```java public <T> void genericMethod(T param) { ... } ``` 这里的`<T>`声明了类参数`T`,它独立于类的参数(如果有)。 2. **`T`的作用**:作为类变量在方法参数、返回值或方法体中使用。如: ```java public <T> T identity(T input) { return input; } ``` - 第一个`T`(在`<T>`中)是类参数的声明。 - 第二个`T`(返回值类)和第三个`T`(参数类)是类参数的使用。 ### 核心区别总结: - **`<T>`是声明**:在方法签名中定义类参数,告诉编译器这是一个方法,类参数名为`T`。 - **`T`是使用**:在方法内部(参数、返回值、局部变量)引用之前声明的类参数。 ### 示解析: ```java // 方法:声明类参数T,返回T类,参数也是T类 public <T> T merge(T a, T b) { // ... 操作两个T类的对象 } ``` - 调用时,编译器根据传入参数推断`T`的类: ```java merge("Hello", "World"); // T被推断为String merge(1, 2); // T被推断为Integer ``` ### 对比通配符`?`: - `?`不能用于声明类参数,只能用于实(如`List<?>`)。 - `?`无法在方法体中使用(因为类未知),而`T`可以在方法体中作为具体类操作对象: ```java public void printList(List<?> list) { for (Object obj : list) { // 必须用Object接收,因为?未知 System.out.println(obj); } // list.add(new Object()); // 错误!无法添(除了null) } public <T> void addToList(List<T> list, T item) { list.add(item); // 允许,因为T是具体类 } ``` ### 设计原则: - 需要**类间关系**(如参数与返回值类相同)时用`<T>`。 - 只需**类无关性**(如单纯读取集合)时用`?`。 ### 相关问题: 1. 如何在方法中使用多个类参数? 2. Java中`extends`和`super`在通配符中的区别? 3. 为什么有时方法比通配符更灵活? 4. 能否在静态方法中使用类的参数? </think> ### Java方法中`<T>`与`T`的区别详解 在Java方法中,`<T>`和`T`代表完全不同的概念,尽管它们共享同一个字母符号。以下是结构化解析: --- ### 1. **`<T>`:声明符** - **本质**:在方法签名**起始处**声明一个**新的类参数** ```java public <T> void exampleMethod(T param) { ... } // ↑ 这里声明T为参数 ``` - **作用**: - 定义该方法为**方法**(独立于类的参数) - 告诉编译器:`T`是方法作用域内的类变量,需在调用时由编译器推断或显式指定 - **关键特性**: - 必须位于返回值类之前(如`<T> void`或`<T> T`) - 可声明多个类参数(如`<T, U> `) - 与类定义的参数相互独立(即使同名也不同)[^1][^2] > **示**:声明独立方法 > ```java > public <T> T identity(T input) { > return input; // T在方法内作为具体类使用 > } > ``` --- ### 2. **`T`:引用符** - **本质**:在方法参数、返回值或方法体中**引用已声明的类** ```java public <T> T process(T input) { > // ↑ 这里引用声明的T类 > T result = input; // 在方法体内引用T > return result; > } > ``` - **作用**: - 代表一个**具体化的类**(编译时确定) -方法内部作为真实类使用(可调用类相关方法- **关键特性**: - 必须先在`<T>`中声明才能使用 - 在编译后会被擦除为`Object`或上界类 - 支持类安全操作(如`list.add(item)`)[^2] > **示**:强制类关系(输入输出类一致) > ```java > public <T> List<T> convertArray(T[] array) { > return Arrays.asList(array); // T保证数组与列表元素类一致 > } > ``` --- ### 3. **核心区别对比表** | 特性 | `<T>` (声明符) | `T` (引用符) | |---------------------|------------------------------|------------------------------| | **位置** | 方法签名起始处(返回值前) | 参数、返回值、方法体内部 | | **本质** | 类参数声明 | 具体类引用 | | **是否可独立存在** | 是(定义类变量) | 否(需先声明`<T>`) | | **类关系约束** | 无(仅声明) | 强制类一致性(如输入=输出) | | **方法内操作限制** | 不直接使用 | 可作为真实类操作对象[^2] | --- ### 4. **与通配符`?`的关键区别** | 类 | 使用场景 | 类操作能力 | |--------------|----------------------------------|--------------------------| | **`<T>`+`T`** | 需类间关系(如参数与返回值同类) | 强:可读写、调用方法 | | **`<?>`** | 仅需类无关性(如单纯读取集合) | 弱:只能读取(除`null`外)[^3] | > **通配符示**(无法添元素): > ```java > void printCollection(Collection<?> coll) { > for (Object obj : coll) System.out.println(obj); // 只能用Object接收 > // coll.add("new"); // 编译错误! 因为?未知具体类 > } > ``` --- ### 5. **实际应用场景** 1. **需要类转换时用`<T>`+`T`** ```java // 安全转换任意类为List public <T> List<T> safeCast(Object obj, Class<T> type) { return type.cast(obj); // T确保类安全 } ``` 2. **仅需读取集合内容用`<?>`** ```java // 打印任意类集合 void printAll(Collection<?> coll) { coll.forEach(System.out::println); } ``` --- ### 相关问题 1. 如何在静态方法中使用参数? 2. 擦除(`Type Erasure`)对`<T>`和`T`有何不同影响? 3. 为什么通配符`<?>`不能用于方法的类声明? 4. 如何通过`<T extends Comparable<T>`实现类安全比较? 5. 方法Java集合框架中的典应用场景有哪些? [^1]: 引用自Java方法声明规范 [^2]: 源自Java操作约束 [^3]: 参考Java通配符设计原则
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值