1、什么是泛型
Java 泛型(generics)是 JDK5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发人员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
2、泛型的使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。
2.1、泛型类
泛型类型用于类的定义中,被称为泛型类。
通过泛型可以完成对一组类对外开放相同的接口。最典型的就是各种容器类,比如:List、Set、Map。
一个普通的泛型类:
/**
* @param <T>
* @author Arley
* date: 2020/02/09 night 20:56
* desc: 泛型类
*/
public class Generic<T> {
/* key这个成员变量的类型为T,T的类型由外部指定 */
private T key;
public Generic(){
}
/* 泛型构造方法形参key的类型也为T,T的类型由外部指定 */
public Generic(T key) {
this.key = key;
}
/* 泛型方法getKey的返回值类型为T,T的类型由外部指定 */
public T getKey() {
return key;
}
}
定义的泛型类,并不以一定非得传入泛型类型实参!
如果在使用泛型的时候传入泛型实参,则会根据传入的泛型实参做出限制。
如果没有传入泛型实参,在泛型类中用泛型的方法或成员变量定义的类型可以为任何类型。
下面我们看一个栗子:
Generic generic1 = new Generic(666666);
Generic generic2 = new Generic("666666");
Generic generic3 = new Generic(666666L);
Generic generic4 = new Generic(true);
2.2、泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,下面是一个简单的泛型接口:
/**
* @param <T>
* @author Arley
* desc:泛型接口
*/
public interface Generator<T> {
T method();
}
当实现泛型接口的类,未传入泛型实参时候:
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,
* 编译器会报错:"Unknown class"
*/
public class FruitGenerator<T> implements Generator<T> {
@Override
public T method() {
return null;
}
}
当实现泛型接口的类,传入泛型实参的时候:
/**
* 传入泛型实参时:
* 定义一个类实现这个接口,虽然我们只创建了一个泛型接口 Generator<T>
* 但是我们可以为 T 传入无数个参数类型,形成了无数个参数的 Generator 接口
* 在实现类实现泛型接口时,如果将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator<String>, public T method()中的 T 也要替换成 String类型
*/
class FruitGenerator2<T> implements Generator<String> {
@Override
public String method() {
return "good";
}
}
2.3、泛型方法
在 Java 中,泛型类的定义十分简单,但是泛型方法就有点复杂了。
尤其是我们看到大多数的泛型类中的成员方法也都使用了泛型,有的甚至在泛型类中包含泛型方法。
泛型类:是指在实例化类的时候指定泛型的具体类型;
泛型方法:实在调用方法的时候指明泛型的具体类型;
泛型方法介绍:
/**
* @param Clazz 传入的泛型实参
* @return 返回值为 T 类型
* @throws InstantiationException
* @throws IllegalAccessException
* desc:
* 1): public 与返回值中间 <T> 非常重要,可以理解为声明此方法为泛型方法
* 2): 只有声明了 <T> 的方法才是泛型方法,泛型类中使用了泛型的成员方法不是泛型方法
* 3): <T> 表明该方法将使用泛型类型 T,此时才可以在泛型方法中使用 T
* 4): 与泛型类的定义一样,此处 T 可以随便写为任何标识
*/
public <T> T genericMethod(Class<T> Clazz){
T instance = null;
try {
instance = Clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return instance;
}
2.3.1、泛型类中的泛型方法
泛型方法可以出现在任何地方和任何场景中使用。
但是有一种情况特别特殊,当泛型方法出现在泛型类中的时候,我们再来看一下:
/**
* @author Arley
* desc:泛型类中的泛型方法
*/
@SuppressWarnings("all")
class Generic2<T> {
/**
* 这里的 T 只能是泛型类实参或者实参的子类
* @param t
*/
public void test1(T t) {
System.out.println(t.toString());
}
/**
* 在泛型类中使用泛型方法,使用泛型 <E>,这种泛型可以为任意类型,可以与泛型类中的声明 T 不是同一种类型
* 由于泛型方法在声明的时候会声明泛型 <E>,因此即使在泛型类中并未声明泛型,编译器也会正确识别泛型方法中 * 的类型
*
* @param t
* @param <E>
*/
public <E> void test2(E t) {
System.out.println(t.toString());
}
/**
* 在泛型类中声明一个泛型方法,使用泛型 <T>
* 注意这个泛型 <T> 是一个全新的类型,可以与泛型类中声明的 <T> 不是一个类型
* @param t
* @param <T>
*/
public <T> void test3(T t) {
System.out.println(t.toString());
}
public static void main(String[] args) {
Fruit fruit = new Fruit();
Apple apple = new Apple();
Person person = new Person();
Generic2<Fruit> generic2 = new Generic2<>();
/* apple是 Fruit 的子类,这里编译通过*/
generic2.test1(fruit);
generic2.test1(apple);
/* 泛型指定实参为 Fruit,这里传入 Person 编译不通过*/
//generic2.test1(person);
/* 使用这个方法两个都可编译通过 */
generic2.test2(apple);
generic2.test2(person);
/* 使用这个方法两个都可编译通过 */
generic2.test3(apple);
generic2.test3(person);
}
}
2.3.2、泛型方法和可变参数
/**
* 泛型方法
* @param args 参数可变
*/
public <T> void pringMsg(T... args){
for (T arg : args) {
System.out.print(arg);
}
}
pringMsg("11","22","33");
pringMsg(11,22,33);
pringMsg(1.1,2.2,3.3);
2.3.3、静态方法与泛型
静态方法有一种情况需要注意,那就是在类中的静态方法上使用泛型:
静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型,必须将静态方法定义为泛型方法。
/**
* @author Arley
* date:2020/02/10 forenoon 10:54
* desc:静态方法泛型
*/
@SuppressWarnings("all")
public class StaticGenerator<T> {
/**
* 如果在类中定义使用泛型的静态方法,需要将这个方法定义为泛型方法
* 即使静态方法中要使用泛型类中已经声明过的泛型也不可以
* 如:
* public static void test(T t)
* 此时会编译错误!
* @param t
* @param <T>
*/
public static <T> void test(T t) {
System.out.println(t.toString());
}
}
2.4、泛型上下边界通配符
在使用泛型的时候,我们可以为传入的泛型类型实参进行上下文边界的限制。
如:类型实参只允许传入某种类型的父类或者某种类型的子类。
2.4.1、上边界通配符
利用 ? extends Number
形式的通配符,可以实现泛型的向上转型。
- 为泛型添加上边界通配符,即传入的类型实参必须是指定的类型的子类型。
/**
* @author Arley
* desc:泛型的上边界通配符
*/
@SuppressWarnings("all")
class Generic3<T> {
private T key;
public Generic3() {
}
public Generic3(T key) {
this.key = key;
}
public T getKey() {
return key;
}
/**
* 为泛型方法添加上边界通配符,传入实参必须是Number的子类
* @param obj
*/
public static void test(Generic3<? extends Number> obj) {
System.out.println(obj.getKey());
}
public static void main(String[] args) {
Generic3<String> generic1 = new Generic3("11");
Generic3<Integer> generic2 = new Generic3(11);
Generic3<Long> generic3 = new Generic3(11L);
Generic3<Float> generic4 = new Generic3(11F);
Generic3<Double> generic5 = new Generic3(1.1);
/* 因为 String 不是 Number 的子类,这行代码编译错误*/
//test(generic1);
test(generic2);
test(generic3);
test(generic4);
test(generic5);
}
}
如果我们将泛型类修改一下:
class Generic3<T extends Number>
/* 这一行代码也会直接报错 */
Generic3<String> generic1 = new Generic3("11");
声明上边界通配符需要注意的地方:
/**
* 在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛 * 型声明的时候添加
* 如:
* public <T> T showKeyName(Generic<T extends Number> container)
* 这样编译会报错
*/
public <T extends Number> T test2(Generic3<T> t){
return t.getKey();
}
2.4.2、下边界通配符
通配符的另一个方向是 “超类型的通配符“ ? super T
,T
是类型参数的下界。使用这种形式的通配符,我们就可以 ”传递对象” 了
- 为泛型添加下边界通配符,即传入的类型实参必须是指定的类型的父类型,直至Object。
/**
* @author Arley
* desc:泛型的下边界通配符
*/
@SuppressWarnings("all")
class Generic4<T> {
private T key;
public Generic4() {
}
public Generic4(T key) {
this.key = key;
}
public T getKey() {
return key;
}
/**
* 为泛型方法添加下边界通配符,传入实参必须是Apple的父类,直至Object
* @param obj
*/
public static void test(Generic4<? super Apple> obj) {
System.out.println(obj.getKey());
}
public static void main(String[] args) {
Object obj = new Object();
Fruit fruit = new Fruit();
Apple apple = new Apple();
Person person = new Person();
Generic4<Object> generic1 = new Generic4<>(obj);
Generic4<Fruit> generic2 = new Generic4(fruit);
Generic4<Apple> generic3 = new Generic4(apple);
Generic4<Person> generic4 = new Generic4(person);
/* Object Fruit 是Apple父类*/
test(generic1);
test(generic2);
test(generic3);
/* Person 不是 Apple 的父类,编译不通过*/
//test(generic4);
}