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

public class GlmapperGenericTwo<A> {
}
2.泛型接口

public interface GlmapperGenericTwo<A> {
}
3.泛型方法

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),即没有类型参数化的类和方法。相比之下,泛型带来了许多好处:
-
类型安全:在使用原始类型时,需要进行手动的类型转换,这可能导致运行时的 ClassCastException 异常。而泛型在编译时可以进行类型检查,提供了更高的类型安全性。
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); -
代码重用:使用原始类型时,为了处理不同类型的数据,需要编写多份相似的代码,而泛型允许我们编写通用的代码,从而提高了代码的重用性。
-
可读性和可维护性:泛型使得代码更加清晰,因为它们表达了程序员的意图,提高了代码的可读性和可维护性。
-
防止错误使用:使用原始类型时,很容易出现类型不匹配、错误的类型转换等问题,而泛型可以在编译时捕获这些错误,避免了一些潜在的 bug。
public static void useGeneric() {
ArrayList<String> names = new ArrayList<>();
names.add("奥特曼");
names.add(123); //编译不通过
} -
提高性能:泛型可以减少装箱和拆箱操作,从而提高了程序的性能。
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
或其子类(例如 Integer
、Double
)的列表。
在方法体内部,我们可以安全地将列表中的元素转换为 Number
类型,然后进行求和操作。因为 Number
是 Integer
和 Double
的共同父类,所以这种类型转换是合法的。
类型参数列表中如果有多个类型参数上限,用逗号分开。
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
或其父类(例如 Number
、Object
)的列表。
在方法体内部,我们可以安全地向列表中添加整数,因为我们知道 Integer
是 Number
和 Object
的子类,所以符合下界通配符的要求。
1.5 ?和 T 的区别
泛型通配符 ?
和类型参数 T
在 Java 泛型中有一些区别,下面是它们之间的主要区别:
-
?
是通配符,表示未知类型,可以匹配任意类型。而T
是一个类型参数,表示具体的类型,可以在定义类或方法时指定。 -
?
通配符用于限制某些操作的范围,可以用于声明变量、方法参数或方法返回类型。而T
类型参数用于在类或方法内部引用某个具体的类型。 -
?
通配符不能用于实例化对象,即不能直接创建?
类型的对象。而T
类型参数可以在需要时实例化为具体的类型。// 可以
T t = operate();
// 不可以
?car = operate(); -
?
通配符无法在方法中安全地使用,因为编译器无法确定?
表示的类型,无法对其进行操作。而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
类型参数可以在方法内部使用,并且能够安全地对其进行操作。
总结来说,
?
通配符用于表示未知类型,可以匹配任意类型,但在方法中使用时有限制;而 T
类型参数表示具体的类型,在类或方法内部引用某个具体的类型。
四、泛型的应用
当谈论Java 8中的泛型应用时,我们主要指的是Java 8引入的新特性和语法糖。以下是几个在Java 8中泛型应用的例子:
-
类型推断 Java 8引入了类型推断机制,使得在某些情况下可以省略泛型参数的显式声明。这样可以使代码更加简洁和易读。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach((number) -> System.out.println(number));
在这个例子中,我们创建了一个包含整数的列表,并使用forEach
方法对列表中的每个元素进行操作。Lambda表达式中的参数number
的类型是由编译器根据上下文自动推断出来的。
-
函数式接口和泛型 Java 8引入了函数式接口和Lambda表达式,这两者结合起来可以方便地处理泛型类型。
@FunctionalInterface
interface Converter<T, R> {
R convert(T t);
}
Converter<String, Integer> converter = (s) -> Integer.parseInt(s);
Integer result = converter.convert("123");
System.out.println(result);
在这个例子中,我们定义了一个函数式接口Converter
,它有两个泛型参数T
和R
,并且只有一个抽象方法convert
。我们创建了一个匿名的Converter
对象,实现了convert
方法来将字符串转换为整数。
-
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编程语言中的一个重要特性,它提供了一种在编译时期进行类型检查和类型安全的机制。以下是对泛型的总结:
-
基本概念:泛型通过使用类型参数来实现,类型参数表示一种未知的类型,在类、接口、方法等定义中使用。常用的类型参数符号包括 <T>
、E
、K
、V
等。 -
优势: -
类型安全:泛型可以在编译时期进行类型检查,避免了在运行时出现类型转换异常,提高代码的健壮性和可靠性。 -
代码复用:泛型可以编写通用的代码,避免了重复编写类似的代码,提高了代码的重用性和可维护性。 -
可读性和可维护性:泛型可以增强代码的可读性和可维护性,使代码更加清晰和易于理解。 -
泛型的使用限制: -
不能使用基本类型作为泛型参数,只能使用引用类型。 -
运行时类型信息(RTTI)不适用于泛型类型,因为在编译时会进行类型擦除,泛型信息在运行时被擦除为原始类型。 -
不能创建参数化类型的数组,但可以使用泛型列表或其他集合类。 -
泛型的通配符: -
通配符 ?
表示未知类型,可以用于灵活处理不确定的泛型类型。 -
通配符上界 extends
限定了泛型类型的上界,使其只能是指定父类或实现指定接口的子类。 -
通配符下界 super
限定了泛型类型的下界,使其只能是指定类的父类或包括指定接口的类。 -
泛型应用的示例: -
类型推断:Java 8引入了类型推断机制,根据上下文自动推断泛型参数类型,简化代码。 -
Lambda表达式和函数式接口:泛型可以用于定义函数式接口的参数类型和返回类型,结合Lambda表达式实现灵活的函数式编程。 -
Stream API:Stream API中的一些方法使用泛型来提供更好的类型安全性和可读性,简化集合元素的处理。
通过合理利用泛型的特性和灵活运用泛型的语法,可以编写出更加通用、灵活和可复用的代码。泛型是Java编程中重要的工具之一,对于提高代码质量、可维护性和可读性都有积极的影响。
原文始发于微信公众号(明月予我):Java 泛型详解(超详细的java泛型方法解析)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/272731.html