Java 泛型详解(超详细的java泛型方法解析)

一、简介

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 是一种在编译时期进行类型检查和类型推断的机制,它使得我们能够编写更加通用和类型安全的代码。泛型最主要的作用就是参数化类型,也就是说我们可以在定义类、接口和方法时使用类型参数,这些类型参数可以在使用时指定具体的类型。

1.泛型类

Java 泛型详解(超详细的java泛型方法解析)
public class GlmapperGenericTwo<A> {

}

2.泛型接口

Java 泛型详解(超详细的java泛型方法解析)
public interface GlmapperGenericTwo<A> {

}

3.泛型方法

Java 泛型详解(超详细的java泛型方法解析)
    public static <T> void addToList(List<T> list, T item) {
        list.add(item);
    }
    
    public  <T> void updateToList( List<T> list,T item) {
       
    }

二、泛型带来的好处

在引入泛型之前,Java 中使用的是原始类型(raw types),即没有类型参数化的类和方法。相比之下,泛型带来了许多好处:

  1. 类型安全:在使用原始类型时,需要进行手动的类型转换,这可能导致运行时的 ClassCastException 异常。而泛型在编译时可以进行类型检查,提供了更高的类型安全性。

    List list = new ArrayList();
    list.add("hello");
    String s = (String) list.get(0);
  2. 代码重用:使用原始类型时,为了处理不同类型的数据,需要编写多份相似的代码,而泛型允许我们编写通用的代码,从而提高了代码的重用性。

  3. 可读性和可维护性:泛型使得代码更加清晰,因为它们表达了程序员的意图,提高了代码的可读性和可维护性。

  4. 防止错误使用:使用原始类型时,很容易出现类型不匹配、错误的类型转换等问题,而泛型可以在编译时捕获这些错误,避免了一些潜在的 bug。

    public static void useGeneric() {
    ArrayList<String> names = new ArrayList<>();
    names.add("奥特曼");
    names.add(123); //编译不通过
    }
  5. 提高性能:泛型可以减少装箱和拆箱操作,从而提高了程序的性能。

    object a=1;//由于是object类型,会自动进行装箱操作。
    int b=(int)a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。

下面是一个例子:

public class GlmapperGeneric<T> {
  private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }

    public static void main(String[] args) {
        // do nothing
    }

  /**
    * 不指定类型
    */
  public void noSpecifyType(){
    GlmapperGeneric glmapperGeneric = new GlmapperGeneric();
    glmapperGeneric.set("test");
    // 需要强制类型转换
    String test = (String) glmapperGeneric.get();
    System.out.println(test);
  }

  /**
    * 指定类型
    */
  public void specifyType(){
    GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric();
    glmapperGeneric.set("test");
    // 不需要强制类型转换
    String test = glmapperGeneric.get();
    System.out.println(test);
  }
}

上面这段代码中的 specifyType 方法中 省去了强制转换,可以在编译时候检查类型安全,可以用在类,方法,接口上。

三、泛型的特性

1.泛型的通配符

在 Java 泛型中,通配符用于表示未知类型,可以在泛型类、接口和方法的定义中使用。下面是一些常用的通配符。

1.1 T,E,K,V,?

本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:

  • T:表示任意类型。
  • E:表示集合元素类型。
  • K、V:分别表示键和值的类型。
  • ?:表示通配符,用于表示未知类型。

这些标识符通常在泛型类、接口和方法的定义中使用,用于表示泛型参数的类型。例如,在 Java 的 Map 接口中,定义了泛型类型 K 和 V,分别表示键和值的类型;在集合类中经常使用 E 表示集合元素的类型;在泛型方法中通常使用 T 表示任意类型。而通配符 ? 则用于表示未知类型,可以作为泛型参数、通配符边界或通配符实参使用。

1.2 ?无界通配符

无界通配符(Unbounded Wildcards):使用 ? 表示,表示可以匹配任意类型。例如:List<?> 表示一个元素类型未知的列表。

无界通配符的主要作用是使代码更加灵活,可以处理不同类型的数据,但在使用时只能进行一些与具体类型无关的操作,如获取元素、判断是否为空等。

List<? extends Number> lists

为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。

下面是一个简单的例子:

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

public class Main {
    public static double sumOfList(List<?> list) {
        double sum = 0.0;
        for (Object elem : list) {
            if (elem instanceof Number) {
                sum += ((Number) elem).doubleValue();
            }
        }
        return sum;
    }

    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        intList.add(1);
        intList.add(2);
        intList.add(3);

        List<Double> doubleList = new ArrayList<>();
        doubleList.add(1.5);
        doubleList.add(2.5);
        doubleList.add(3.5);

        System.out.println("Sum of integers: " + sumOfList(intList));
        System.out.println("Sum of doubles: " + sumOfList(doubleList));
    }
}

在上面的例子中,sumOfList 方法接受一个使用了无界通配符 List<?> 类型的列表作为参数。在这个方法中,我们并不关心列表中元素的具体类型,只是希望能够对其中的数值类型进行求和操作。通过使用无界通配符,我们可以处理不同类型的列表,而不必担心具体的元素类型是什么。

需要注意的是,使用无界通配符后,我们只能对其中的元素进行与具体类型无关的操作,比如获取元素、判断是否为空等。无法对列表进行添加元素的操作,因为我们无法确定要添加的元素类型与列表元素类型是否一致。

1.3 上界通配符 < ? extends E>

上界通配符 <? extends E> 是 Java 泛型中的一种特殊写法,其中 E 表示一个类型参数。它表示可以匹配指定类型或其子类的任意类型。

这种通配符的作用是限制泛型参数的类型范围,使得我们只能传递指定类型或其子类作为实参,而不能传递父类或其他不相关的类型。

下面是一个例子来说明上界通配符的使用:

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

public class Main {
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number num : list) {
            sum += num.doubleValue();
        }
        return sum;
    }

    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        intList.add(1);
        intList.add(2);
        intList.add(3);

        List<Double> doubleList = new ArrayList<>();
        doubleList.add(1.5);
        doubleList.add(2.5);
        doubleList.add(3.5);

        System.out.println("Sum of integers: " + sumOfList(intList));
        System.out.println("Sum of doubles: " + sumOfList(doubleList));
    }
}

在上面的例子中,sumOfList 方法接受一个使用了上界通配符 List<? extends Number> 类型的列表作为参数。这意味着我们可以传递一个元素类型为 Number 或其子类(例如 IntegerDouble)的列表。

在方法体内部,我们可以安全地将列表中的元素转换为 Number 类型,然后进行求和操作。因为 NumberIntegerDouble 的共同父类,所以这种类型转换是合法的。

类型参数列表中如果有多个类型参数上限,用逗号分开。

1.4 下界通配符 < ? super E>

下界通配符 <? super E> 是 Java 泛型中的一种特殊写法,其中 E 表示一个类型参数。它表示可以匹配指定类型或其父类的任意类型。

这种通配符的作用是限制泛型参数的类型范围,使得我们只能传递指定类型或其父类作为实参,而不能传递子类或其他不相关的类型。

下面是一个例子来说明下界通配符的使用:

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

public class Main {
    public static void addNumbers(List<? super Integer> list) {
        for (int i = 1; i <= 5; i++) {
            list.add(i);
        }
    }

    public static void main(String[] args) {
        List<Number> numberList = new ArrayList<>();
        addNumbers(numberList);
        System.out.println("Number list: " + numberList);

        List<Object> objectList = new ArrayList<>();
        addNumbers(objectList);
        System.out.println("Object list: " + objectList);
    }
}

在上面的例子中,addNumbers 方法接受一个使用了下界通配符 List<? super Integer> 类型的列表作为参数。这意味着我们可以传递一个元素类型为 Integer 或其父类(例如 NumberObject)的列表。

在方法体内部,我们可以安全地向列表中添加整数,因为我们知道 IntegerNumberObject 的子类,所以符合下界通配符的要求。

1.5 ?和 T 的区别

泛型通配符 ? 和类型参数 T 在 Java 泛型中有一些区别,下面是它们之间的主要区别:

  1. ? 是通配符,表示未知类型,可以匹配任意类型。而 T 是一个类型参数,表示具体的类型,可以在定义类或方法时指定。

  2. ? 通配符用于限制某些操作的范围,可以用于声明变量、方法参数或方法返回类型。而 T 类型参数用于在类或方法内部引用某个具体的类型。

  3. ? 通配符不能用于实例化对象,即不能直接创建 ? 类型的对象。而 T 类型参数可以在需要时实例化为具体的类型。

    // 可以
    T t = operate();

    // 不可以
    ?car = operate();
  4. ? 通配符无法在方法中安全地使用,因为编译器无法确定 ? 表示的类型,无法对其进行操作。而 T 类型参数可以在方法内部使用,并进行类型相关的操作。

下面是一些示例代码来说明这两者之间的区别:

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

public class Main {
    public static void printList(List<?> list) {
        for (Object item : list) {
            System.out.println(item);
        }
    }

    public static <T> void addToList(List<T> list, T item) {
        list.add(item);
    }

    public static void main(String[] args) {
        List<?> wildcardList = new ArrayList<>();
        // wildcardList.add("Hello");  // 编译错误,无法添加元素
        printList(wildcardList);  // 可以打印列表中的元素

        List<String> stringList = new ArrayList<>();
        addToList(stringList, "World");  // 可以向列表中添加元素
        printList(stringList);  // 可以打印列表中的元素
    }
}

在上面的例子中,printList 方法使用了 ? 通配符来接受一个未知类型的列表,并打印其中的元素。而 addToList 方法使用了 T 类型参数来接受一个具体的类型和一个列表,并向列表中添加元素。

需要注意的是,由于 ? 通配符表示未知类型,因此无法直接向其添加元素。而 T 类型参数可以在方法内部使用,并且能够安全地对其进行操作。

Java 泛型详解(超详细的java泛型方法解析)总结来说,? 通配符用于表示未知类型,可以匹配任意类型,但在方法中使用时有限制;而 T 类型参数表示具体的类型,在类或方法内部引用某个具体的类型。

四、泛型的应用

当谈论Java 8中的泛型应用时,我们主要指的是Java 8引入的新特性和语法糖。以下是几个在Java 8中泛型应用的例子:

  1. 类型推断 Java 8引入了类型推断机制,使得在某些情况下可以省略泛型参数的显式声明。这样可以使代码更加简洁和易读。
List<Integer> numbers = Arrays.asList(12345);
numbers.forEach((number) -> System.out.println(number));

在这个例子中,我们创建了一个包含整数的列表,并使用forEach方法对列表中的每个元素进行操作。Lambda表达式中的参数number的类型是由编译器根据上下文自动推断出来的。

  1. 函数式接口和泛型 Java 8引入了函数式接口和Lambda表达式,这两者结合起来可以方便地处理泛型类型。
@FunctionalInterface
interface Converter<TR{
    convert(T t);
}

Converter<String, Integer> converter = (s) -> Integer.parseInt(s);
Integer result = converter.convert("123");
System.out.println(result);

在这个例子中,我们定义了一个函数式接口Converter,它有两个泛型参数TR,并且只有一个抽象方法convert。我们创建了一个匿名的Converter对象,实现了convert方法来将字符串转换为整数。

  1. Stream API和泛型 Java 8的Stream API提供了一种流式处理数据的方式,并且使用泛型来增强类型安全性。
List<String> words = Arrays.asList("Hello""World""Java");
long count = words.stream()
        .filter((word) -> word.length() > 4)
        .count();
System.out.println("Count: " + count);

在这个例子中,我们创建了一个包含字符串的列表,并使用Stream API来过滤出长度大于4的字符串,并计算符合条件的字符串个数。

通过这些例子,我们可以看到Java 8中的泛型应用可以使代码更加简洁和易读。它们结合了类型推断、Lambda表达式、函数式接口和Stream API等新特性,提供了更强大和灵活的泛型编程能力。

五、总结

泛型是Java编程语言中的一个重要特性,它提供了一种在编译时期进行类型检查和类型安全的机制。以下是对泛型的总结:

  1. 基本概念:泛型通过使用类型参数来实现,类型参数表示一种未知的类型,在类、接口、方法等定义中使用。常用的类型参数符号包括<T>EKV等。
  2. 优势:
    • 类型安全:泛型可以在编译时期进行类型检查,避免了在运行时出现类型转换异常,提高代码的健壮性和可靠性。
    • 代码复用:泛型可以编写通用的代码,避免了重复编写类似的代码,提高了代码的重用性和可维护性。
    • 可读性和可维护性:泛型可以增强代码的可读性和可维护性,使代码更加清晰和易于理解。
  3. 泛型的使用限制:
    • 不能使用基本类型作为泛型参数,只能使用引用类型。
    • 运行时类型信息(RTTI)不适用于泛型类型,因为在编译时会进行类型擦除,泛型信息在运行时被擦除为原始类型。
    • 不能创建参数化类型的数组,但可以使用泛型列表或其他集合类。
  4. 泛型的通配符:
    • 通配符?表示未知类型,可以用于灵活处理不确定的泛型类型。
    • 通配符上界extends限定了泛型类型的上界,使其只能是指定父类或实现指定接口的子类。
    • 通配符下界super限定了泛型类型的下界,使其只能是指定类的父类或包括指定接口的类。
  5. 泛型应用的示例:
    • 类型推断:Java 8引入了类型推断机制,根据上下文自动推断泛型参数类型,简化代码。
    • Lambda表达式和函数式接口:泛型可以用于定义函数式接口的参数类型和返回类型,结合Lambda表达式实现灵活的函数式编程。
    • Stream API:Stream API中的一些方法使用泛型来提供更好的类型安全性和可读性,简化集合元素的处理。

通过合理利用泛型的特性和灵活运用泛型的语法,可以编写出更加通用、灵活和可复用的代码。泛型是Java编程中重要的工具之一,对于提高代码质量、可维护性和可读性都有积极的影响。

原文始发于微信公众号(明月予我):Java 泛型详解(超详细的java泛型方法解析)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/272731.html

(0)
明月予我的头像明月予我bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!