再次认识java泛型
一、什么是泛型
定义类、接口、方法时;同时声明了一个或多个类型变量(如:<E>),称为泛型类,泛型接口,泛型方法,它们统统称为泛型。
<E>这个写法,表示定义了一个泛型类型,定义好之后我们就可以在后面去使用这个泛型类型。
public class ArrayList<E>{ ...... }
泛型的本质,是把具体的数据类型作为参数传给类型变量。
泛型的作用:提供了在编译阶段所能操作的数据类型,并自动进行检查的能力。这样可以避免强制类型转换,及可能出现的异常。
例子
package org.example.general; import java.util.ArrayList; public class GenExample1 { public static void main(String[] args) {
// ArrayList 本身是一个泛型类,在实例化的时候没有指定泛型类型,那么默认是Object类型 ArrayList arr = new ArrayList(); arr.add("abc"); arr.add(123); arr.add(true); for(int i = 0;i < arr.size();i++){ String temp = (String) arr.get(i); System.out.println(temp); } } }
运行结果:
abc Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at org.example.general.GenExample1.main(GenExample1.java:13)
可以看到获取第二个数据的时候,报了类型转换异常。
我们优化一下代码,给ArrayList定义变量的时候加上泛型标识,如下:
可以发现,在编译阶段就已经报错了。因此后面我们添加元素的时候,就只能添加String类型,这样就规范了值的传入,也就能避免后面的类型转换异常了。
二、泛型类
定义泛型类
修饰符 class 类名 <类型变量,类型变量,...> { ...... }
注意,类型变量建议用大写英文字母,常用的有:E,T,K,V等。
例如,当我们定义类的时候,类中有变量我们不确定它的类型,我们就可以定义泛型类,如下:
package org.example.general; public class Student<T,K> { // 有几个类型不确定,就写几个大写字母,这里的T、K表示定义了两个泛型类型为T、K private T name; // 在这里使用泛型类型 T private K age; // 在这里使用泛型类型 K public Student(){}; public Student(T name,K age){ this.name = name; this.age = age; } public T getName() { return name; } public void setName(T name) { this.name = name; } public K getAge() { return age; } public void setAge(K age) { this.age = age; } }
在我们创建对象的时候,来确定泛型类的数据类型,如下:
package org.example.general; public class GenExample2 { public static void main(String[] args) { Student<String,Integer> stu1 = new Student<>("小明",22); System.out.println(stu1.getName()); } }
再看例子二:
package org.example.general; public class GenExample3{ public static void main(String[] args) { People<Hdance> per1 = new People<>(); per1.setHobby(new Hdance()); per1.getHobby(); People<Hsing> per2 = new People<>(); per2.setHobby(new Hsing()); per2.getHobby(); } } class People<T> { private T hobby; public void setHobby(T hobby) { this.hobby = hobby; } public void getHobby(){ System.out.println(hobby.toString()); } } class Hsing{ @Override public String toString() { return "唱歌"; } } class Hdance{ @Override public String toString() { return "跳舞"; } }
三、泛型接口
定义泛型接口
修饰符 interface 接口名<类型变量,类型变量...>{ ...... }
什么时候用到了泛型接口:接口当中,方法的参数类型或方法的返回值类型确定不了,这个时候就可以使用泛型接口。
例如该场景:
系统需要处理学生和老师的数据
需要提供两个功能:
1、保存对象数据
2、根据名称查询数据
代码如下:
定义学生类:
package org.example.generalinterface; public class Student { }
定义老师类:
package org.example.generalinterface; public class Teacher { }
定义操作类:
package org.example.generalinterface; public interface Operator<E> { public abstract void saveData(E obj); public abstract E getData(); } // 接口中的泛型在什么时候被确定 // 方式一:实现类实现泛型接口,没有指定具体的泛型类型,那么默认是object类型 class OperatorImpl1 implements Operator{ @Override public void saveData(Object obj) { } @Override public Object getData() { return null; } } // 方式二:实现类实现接口时,直接指定接口中泛型的类型(使用最多的方式) class OperatorImpl2 implements Operator<Teacher>{ @Override public void saveData(Teacher obj) { } @Override public Teacher getData() { return null; } } // 方式三:实现类实现了泛型接口,泛型没有指定,那么泛型在实现类实例化的时候被确定 class OperatorImpl3<E> implements Operator<E>{ @Override public void saveData(E obj) { } @Override public E getData() { return null; } }
四、泛型方法
定义泛型方法
修饰符 <类型变量,类型变量...> 返回值类型 方法名(形参列表里){
......
}
例如
public static <T> void test(T t){ }
在调用方法的时候确定泛型类型。
什么时候使用泛型方法:当我们定义方法的时候参数类型或返回值类型不确定的时候,就可以使用泛型方法,当我们调用泛型方法的时候来确定泛型方法。例如:
ackage org.example.generalMethod; public class GenExample1 { public static void main(String[] args) { GenExample1.show("abc"); // 输出string GenExample1.show(123); // 输出integer }
// 第一个<T> 表示我们定义了一个泛型类型,在show后面的T是使用泛型类型。 public static <T> void show(T t){ if(t instanceof String){ System.out.println("string"); } else if (t instanceof Integer) { System.out.println("interger"); } } }
通配符
?号,可以在使用泛型的时候代表一切类型,E T K V 是在定义泛型的时候使用。这个通配符一般会结合集合去用。
package org.example.generalMethod; import java.util.ArrayList; public class GenExample2 { public static void main(String[] args) { ArrayList<String> str = new ArrayList<>(); ArrayList<Integer> inte = new ArrayList<>(); method(str); method(inte); }
// 通配符不需要定义泛型,就可以使用泛型 public static void method(ArrayList<?> arr){ } }
泛型的上下限
泛型上限: ? extends Car,?能接收的必须是Car或者他的子类
泛型下限: ? super Car,?能接收的必须是Car或者其父类。
可以看到超过上限,编译的时候就报错了。