什么是泛型?
泛型就是定义一种规范,这种规范规定了类、接口、方法中可以接受的数据类型。它允许类、接口和方法在定义时使用参数化类型。这样可以在不确定具体类型的情况下编写通用的代码,并在实际使用时指定具体的类型参数。
泛型的格式
在Java中,泛型的基本格式包括在类、接口或方法名后面使用尖括号(<>)来声明泛型参数。
泛型类: 在类名后面使用尖括号(<>)声明泛型参数。例如:
public class Box<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
-
在上面的例子中,
Box<T>
中的<T>
是泛型参数的声明,它表明这个类可以存储任意类型的数据。在类的实例化时,可以具体指定T
的类型,例如Box<Integer>
或Box<String>
。 -
也可以public class Box<T extends Object> 通过extends Object 来限制泛型只能接收Object类及其子类
泛型方法: 在方法的返回类型前使用尖括号(<>)声明泛型参数。例如:
public <T> T genericMethod(T t) {
// 方法体
return t;
}
-
在上面的例子中,
<T>
声明了一个泛型参数T
,并将其用于方法的参数类型和返回类型中。这使得genericMethod
方法可以接受任意类型的参数并返回相同的类型。 -
也可以public <T extends Object> T method(T t) 通过extends Object 来限制泛型只能接收Object类及其子类
多个泛型参数: 如果需要在类、接口或方法中使用多个泛型参数,可以用逗号分隔它们。例如:
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
-
在上面的例子中,
Pair<K, V>
定义了两个泛型参数K
和V
,分别表示键和值的类型。在实例化Pair
对象时,需要指定具体的类型,例如Pair<String, Integer>
。
为什么要有泛型?
使用泛型的主要目的是提高代码的重用性、类型安全性和可读性。
重用性: 泛型使得代码可以独立于特定的数据类型,可以被多种类型重复使用。
类型安全性: 在编译时会进行类型检查,避免了在运行时出现类型转换错误。
可读性: 泛型代码通常更加清晰和易于理解,因为它表达了代码的意图和约束。
当使用泛型时,有时我们希望限制泛型参数只能是特定的类或其子类。这种需求可以通过泛型的通配符和泛型的上界来实现。
泛型通配符 ?
泛型通配符 ?
表示未知类型,可以用于限制方法或类接受特定的类型。例如,我们有一个方法希望接受 Box
类型的对象,但是不关心具体是哪种类型:
public void printBox(Box<?> box) {
System.out.println(box.getValue());
}
在上面的例子中,printBox
方法接受一个 Box<?>
类型的参数 box
,表示这个方法可以接受任意类型的 Box
对象。?
表示通配符,可以接受任何类型。
使用具体类型参数 Box<T>
当使用具体类型参数 Box<T>
定义方法时,你需要明确指定 Box
的类型参数 T
,并且方法只能接受这种具体类型的 Box
对象作为参数。例如,定义方法如下:
public void printBox(Box<Integer> box) {
System.out.println("Box value: " + box.getValue());
}
这个方法 printBox
只能接受 Box<Integer>
类型的对象作为参数,不能接受 Box<String>
或其他类型的 Box
。这种方式限制了方法的适用范围,但也提供了类型安全的保证。
区别和适用场景
灵活性和通用性:
使用
<?>
泛型通配符更加灵活,可以接受多种类型的Box
对象,适用于对泛型参数不敏感的情况。使用具体类型参数
Box<T>
更具体,用于确切知道需要什么类型的Box
对象的情况。
类型安全性:
<?>
通配符在编译时无法获取具体类型信息,但可以安全地对泛型对象进行操作。具体类型参数
Box<T>
在编译时可以确保方法只能接受特定类型的Box
对象,提供更严格的类型检查和安全性。
适用场景:
使用
<?>
通配符适合于能够接受和处理任何类型的泛型对象的情况,例如打印、遍历等操作。使用具体类型参数
Box<T>
适合于需要针对特定类型执行特定操作的情况,例如对整数Box
进行特定的数学计算。
泛型的上界
通过使用泛型的上界,可以限制泛型参数必须是某个类的子类。例如,我们希望泛型参数必须是 Number
类或其子类:
public class Box<T extends Number> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
在上面的例子中,Box<T extends Number>
表示 T
必须是 Number
类或其子类。这样,就可以确保在 Box
类中只能存储 Number
类型的数据。
泛型方法的上界
类似地,对于泛型方法,也可以使用泛型的上界来限制方法的泛型参数。例如,我们有一个方法希望接受 Comparable
接口的实现类作为参数:
public <T extends Comparable<T>> int compare(T obj1, T obj2) {
return obj1.compareTo(obj2);
}
在上面的例子中,<T extends Comparable<T>>
声明了一个泛型方法 compare
,它要求 T
必须是实现了 Comparable<T>
接口的类。这样,就可以在方法中使用 compareTo
方法来比较泛型参数 obj1
和 obj2
。
通过泛型的通配符
?
和泛型的上界,可以有效地限制泛型参数的类型,使得代码更加类型安全和灵活。通配符?
可以接受任意类型,而泛型的上界则可以确保泛型参数必须是指定类或其子类。这些技术在Java中非常常见,用于提高代码的健壮性和可维护性。
实战
写一个compare()方法,可以比较两个对象的大小,由于不同对象的比较规则不同,所以需要传入一个比较方法。
每个对象的比较方法不同,我们可以设计一个接口,让每个对象实现这个接口。
public interface MyCompare<T> {
boolean compare(T t1,T t2);
}
public class Main<T> {
/**
* 写一个compare方法,参数为要比较的对象以及比较的方式
*/
public boolean compare(T o1, T o2,MyCompare<T> myCompare){
return myCompare.compare(o1,o2);
}
public static void main(String[] args) {
Main<Integer> main = new Main<>();
//lambda表达式
boolean ret = main.compare(1,2,(first,second)->first>second);
//匿名内部类
boolean ret1 = main.compare(1, 2, new MyCompare<Integer>() {
@Override
public boolean compare(Integer t1, Integer t2) {
return t1>t2;
}
});
System.out.println(ret+" "+ ret1);
}
}