Java 泛型

目录

1 泛型的基本使用

1.1 定义泛型类

1.2 定义泛型方法

1.3 定义泛型接口

2 泛型的高级使用

2.1 泛型通配符

2.1.1 无界通配符(?)

2.1.2 上限通配符(? extends T)

2.1.3 下限通配符 (? super T)

2.2 泛型的继承和实现

2.3 泛型与数组

3 泛型的好处

4 泛型的类型参数


Java 泛型是一种强大的特性,它允许你在编写代码时指定类型参数,从而提高代码的重用性和类型安全性。泛型是在编译时期检查类型安全,并且所有的类型参数在运行时被擦除的编程语言特性。

Java 泛型允许在类、接口和方法中指定类型参数。这些类型参数在实例化或使用时需要被具体的类型所替换。

下面详细介绍 Java 泛型的基本使用方法以及一些常见的应用场景。

1 泛型的基本使用

1.1 定义泛型类
public class Bread<T> { // T 是类型参数
    
    private T item;

    public Bread(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }

    public static void main(String[] args) {
        Bread<String> bread = new Bread<>("Hello, World!");
        System.out.println(bread.getItem()); // 输出 Hello, World!
    }
}

泛型类的意义:

  • 类型安全:通过使用泛型,可以在编译阶段就确保类型安全,避免了运行时的 ClassCastException 错误。
  • 代码重用:泛型类可以被不同类型的对象实例化,提高了代码的重用性。
  • 灵活性:泛型类可以根据实际需要动态决定存储的数据类型,而不需要修改类的实现。
  • 清晰性:通过显式声明类型参数,代码的意图变得更加明确,提高了可读性。
1.2 定义泛型方法

泛型方法的定义格式:

<类型参数列表> 方法名(参数列表) {
    // 方法体
}

 其中 <类型参数列表> 可以包含一个或多个类型参数,用逗号隔开。

public class Electricity {
    public <T> T getSomeValue(T value) {
        return value;
    }

    public <T, U> String combine(T t, U u) {
        return t.toString() + " and " + u.toString();
    }
}

可以在非泛型类中定义泛型方法,或者在一个泛型类中定义泛型方法。

1.3 定义泛型接口
public interface FunctionInterface<T, R> { // T 是输入类型,R 是输出类型
    R apply(T t);
}

泛型接口也是常见的使用场景。以下是具体使用示例:

示例一:实现接口

public class MyFunction implements FunctionInterface<Integer, String> {

    @Override
    public String apply(Integer t) {
        return "The input is: " + t;
    }

    public static void main(String[] args) {
        // 创建一个 MyFunction 实例
        MyFunction myFunction = new MyFunction();

        // 调用 apply 方法
        String result = myFunction.apply(42);
        System.out.println(result); // 输出 "The input is: 42"
    }
}

示例二:使用Lambda 表达式

public class Main {
    public static void main(String[] args) {
        FunctionInterface<Integer, String> lambdaFunction = t -> "The input is: " + t;
        String result = lambdaFunction.apply(42);
        System.out.println(result); // 输出 "The input is: 42"
    }
}

从 Java 8 开始,可以使用 Lambda 表达式来简化函数式接口的使用,而无需显式地创建一个实现类。

函数式接口:一个函数式接口是指仅包含一个抽象方法的接口。Java 8 引入了 @FunctionalInterface 注解来标记这样的接口,虽然不是强制性的,但这个注解有助于确保接口确实是函数式的。

Lambda 表达式:Lambda 表达式提供了一种简洁的方式,可以用来定义匿名函数。Lambda 表达式的形式为 (parameters) -> expression(parameters) -> { statements; }

示例三:使用方法引用

public class Main {

    public static Integer formatInput(String t) {
        return Integer.parseInt(t);
    }

    public static void main(String[] args) {
        FunctionInterface<String, Integer> methodRefFunction = Integer::parseInt;
        Integer result = methodRefFunction.apply("42");
        System.out.println(result); // 输出 42

        FunctionInterface<String, Integer> methodRefFunction1 = Main::formatInput;
        Integer result1 = methodRefFunction1.apply("43");
        System.out.println(result1); // 输出 43
    }
}

方法引用:基于已存在的方法或构造函数,允许程序员通过引用来重用这些方法或构造函数的功能。方法引用可以看作是 Lambda 表达式的一个特殊形式,但它更简洁。

用法了解:Java 方法引用-CSDN博客

2 泛型的高级使用

2.1 泛型通配符

泛型通配符是一种特殊的类型标记,用于指明一个泛型参数的类型未知或者不受限。它们通常用于集合、方法参数或返回类型中,以增加代码的灵活性和可重用性。

Java中常用的泛型通配符主要有三种:

2.1.1 无界通配符(?)

无界通配符表示“任意类型”,意味着你可以将任何类型的数据赋值给该通配符代表的类型。

public void printListContents(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

printListContents(Arrays.asList(1, 2, 3)); // 可以传递 List<Integer>
printListContents(Arrays.asList("a", "b", "c")); // 也可以传递 List<String>
2.1.2 上限通配符(? extends T)

上限通配符表示“类型 T 或其子类型”,这意味着你可以将类型 T 或者它的任何子类型的对象赋值给该通配符代表的类型。

public void printNumbers(List<? extends Number> numbers) {
    for (Number num : numbers) {
        System.out.println(num.doubleValue());
    }
}

printNumbers(Arrays.asList(1, 2, 3)); // 可以传递 List<Integer>
// printNumbers(Arrays.asList("a", "b", "c")); // 编译错误,因为 List<String> 不是 List<Number>
2.1.3 下限通配符 (? super T)

下限通配符表示“类型 T 或其父类型”,这意味着你可以将类型 T 或者它的任何超类型的对象赋值给该通配符代表的类型。

public void addIntegers(List<? super Integer> list) {
    list.add(5); // 合法的,因为可以向 List<Object> 添加 Integer
    list.add(6); // 合法的
}

addIntegers(new ArrayList<>()); // 可以传递 ArrayList<Object>
addIntegers(new ArrayList<Integer>()); // 也可以传递 ArrayList<Integer>
// addIntegers(new ArrayList<String>()); // 编译错误,因为 String 不是 Integer 的超类型

在 Java 中,使用无界通配符(?)和上限通配符(? extends T)时,由于类型不确定性的原因,编译器不允许向这些集合中添加元素。只有下限通配符(? super T)允许向集合中添加元素,因为它确保了集合至少可以接受类型 T 或其超类型(如 Object)的对象。

2.2 泛型的继承和实现

可以使用泛型来定义泛型化的继承关系或实现泛型接口。

public class ConcreteClass<T> extends GenericBaseClass<T> implements GenericInterface<T> {
    // 实现泛型接口或继承泛型基类
}

public interface GenericInterface<T> {
    T process(T t);
}

public abstract class GenericBaseClass<T> {
    protected abstract T doSomething(T t);
}
2.3 泛型与数组

由于 Java 的类型擦除机制,泛型不能用于创建泛型数组。不过,可以通过反射等方式绕过这一限制。

public class GenericArray {
    public static <T> T[] createArray(Class<T> clazz, int size) {
        // 创建泛型数组,注意类型擦除
        @SuppressWarnings("unchecked")
        T[] array = (T[]) java.lang.reflect.Array.newInstance(clazz, size);
        return array;
    }
}

3 泛型的好处

  • 减少强制类型转换:使用泛型可以减少强制类型转换的需求,提高了代码的安全性和简洁性。
  • 更好的性能:泛型避免了运行时的类型检查开销,提高了程序的性能。
  • 易于维护:泛型代码更容易理解和维护,因为类型信息更加明确。

4 泛型的类型参数

在 Java 泛型中,类型参数通常用单个大写字母表示,比如 TEKV 等。这些字母并没有特定的含义,只是约定俗成的使用习惯。以下是一些常用的类型参数名称及其常见用途:

  • T (Type):最常用的类型参数,表示“某种类型”。
  • E (Element):常用在集合框架中,表示集合中的元素类型。
  • K (Key):常用在映射(Map)中,表示键的类型。
  • V (Value):常用在映射(Map)中,表示值的类型。
  • N (Number):表示数字类型。
  • S (Source):表示源类型。
  • R (Result):表示结果类型。  

示例代码:

public class Box<T> { // T 代表任何类型
    private T item;

    public Box(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }

    public void setItem(T item) {
        this.item = item;
    }
}

public class MyMap<K, V> { // K 代表 Key, V 代表 Value
    private Map<K, V> map = new HashMap<>();

    public void put(K key, V value) {
        map.put(key, value);
    }

    public V get(K key) {
        return map.get(key);
    }
}

public class DataContainer<I, O> { // I 代表 Input, O 代表 Output
    private I input;
    private O output;

    public DataContainer(I input, O output) {
        this.input = input;
        this.output = output;
    }

    public I getInput() {
        return input;
    }

    public O getOutput() {
        return output;
    }
}

类型参数的名称可以是任何有效的 Java 标识符,但通常选择有意义的名称可以增加代码的可读性。常用的类型参数名称包括 TEKV 等,这些字母分别表示 Type、Element、Key 和 Value。根据你的具体需求和上下文选择合适的类型参数名称。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值