1、为什么要使用泛型:
在没有使用泛型时,集合存储和提取数据经常出现两个不足:
1.存储数据时,不能保证数据的类型安全,也就是说,同一集合中可以存储多种类型的数据
这就相当于绕了一个弯,把原本的数据类型--->转换为Object类型存储--->转换为原本的数据类型以便使用。
代码: //在没有使用泛型时:
//1、不能保证存储数据类型的安全
//2、数据需要强转,可能出现ClassCastExcepetion异常
@Test
public void testNoGeneric(){
List list = new ArrayList();
list.add(88);
list.add(90);
list.add(99);
list.add("AA");//不能保证存储数据类型安全
for(int i=0;i<list.size();i++){
int score = (int) list.get(i);//抛出ClassCastException异常
System.out.println(score);
}
}
java 的泛型机制就是解决这么样的问题,通过使用泛型:
1.保证数据存储类型的安全,就是通过指定泛型的类型,使得在存储数据时,只能存储先前指定类型
2.避免了数据类型的转换,也就避免了异常
代码: //使用泛型,保证数据存储的类型安全
//同时,避免了强制类型转换
@Test
public void testGeneric1(){
//在指定泛型之后,该泛型类的所有有关泛型的方法或返回类型,都变成了指定的泛型类型
List<Integer> list =
new ArrayList<Integer>();//实现类的泛型可以不再指定,只需一个<>即可
list.add(88);
list.add(90);
list.add(99);
//list.add("AA");//编译时就会报错,保证数据存储类型的安全
//List容器的遍历方法
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
Integer score = it.next();
System.out.println(score);
}
//或者
// for(Integer i:list){
// System.out.println(i);
// }
}
2、在集合中使用泛型:
其实通过指定泛型,泛型类的所有与泛型相关的方法都自动变成了指定的类型
集合List:见“为什么使用泛型”一节
集合Map: @Test
public void test3(){
Map<String, Integer> map =
new HashMap<String,Integer>();//OR new HashMap<>()即可
map.put("AA", 78);
map.put("BB", 88);
map.put("CC", 99);
Set<Entry<String, Integer>> set = map.entrySet();
for(Entry<String, Integer> o:set){
System.out.println(o);
}
}
3、自定义泛型类、泛型接口和泛型方法
自定义泛型类
通过添加<T>在类型名后边:class ClassName<T>
自定义泛型类Order,
代码:
public class Order<T> {
private String orderName;
private int orderId;
private T t;
public List<T> list = new ArrayList<>();
public void add(){
list.add(t);
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
@Override
public String toString() {
return "Order [orderName=" + orderName + ", orderId=" + orderId + ", t=" + t + "]";
}
//泛型方法,泛型方法的类型与泛型类无关
public <E> E getE(E e){
return e;
}
//泛型方法,实现数组到集合的复制
public <E> List<E> fromArrayToList(E[] arr, List<E> list){
for(E e:arr){//数组也可以这样遍历
list.add(e);
}
return list;
}
}
Order的使用,
代码:
//自定义泛型类的使用
@Test
public void test4(){
Order<Boolean> order = new Order<Boolean>();
order.setT(true);
Boolean b = order.getT();
System.out.println(b);
order.add();
List<Boolean> list = order.list;
System.out.println(list);
}
1.在实例化泛型类对象时,指定泛型的类型,对应的类中所有使用泛型的位置,都变为实例化中指定的泛型的类型
2.如果我们定义了泛型类,但在实例化中未指定泛型类型,那么默认是Object类型
代码:
//定义泛型类的子类时,可以指明泛型类型,通过在父类后指定类型
class subOrder extends Order<Boolean>{
}
//也可以不指定,子类也还是泛型,子类父类都需指定泛型<T>
class subOrder2<T> extends Order<T>{
}
泛型接口同泛型类;
泛型方法
泛型方法其实与泛型类无关,泛型方法可以在泛型类中,也可以在非泛型类中。
泛型方法通过权限修饰符后添加泛型<E>,使函数方法成为泛型方法,参看Order类中两个泛型方法getE()和fromArrayToList()
//泛型方法,泛型方法的类型与泛型类无关
public <E> E getE(E e){
return e;
}
//泛型方法,实现数组到集合的复制
public <E> List<E> fromArrayToList(E[] arr, List<E> list){
for(E e:arr){//数组也可以这样遍历
list.add(e);
}
return list;
}
泛型方法的使用:(其实通过参数,便指定类泛型方法的泛型类型)
代码:
//泛型方法的使用
@Test
public void test5(){
Order<Boolean> order = new Order<Boolean>();
//调用泛型方法
Integer i = order.getE(50);//指定参数,返回类型确定为Integer
Double d = order.getE(4.3);//返回类型为Double
Integer[] ints = new Integer[]{1,2,3};
List<Integer> list = new ArrayList<>();
List<Integer> list3 = order.fromArrayToList(ints, list);
System.out.println(list3);
}
4、泛型与继承的关系
类之间存在继承关系,String类是Object类的子类,那么泛型中List<String>与List<Object>存在怎么样的关系呢?
List<String>与List<Object>是并列关系,不存在继承关系:
/*
* List<Object>与List<String>不存在子父类关系,属于并列关系
* 他们有共同的父类:List<?>
*/
@Test
public void test6(){
Object obj = null;
String str = "AA";
obj = str;//没问题,子类赋给父类
Object[] objs = null;
String[] strs = new String[]{"AA","BB","CC"};
objs = strs;//没问题,子类数组赋给父类数组
List<Object> listObject=null;
List<String> listString = new ArrayList<String>();
listObject = listString;//编译不通过,说明List<Object>与List<String>不存在子父类关系
}
其实,List<String>与List<Object> 有共同的父类:List<?> ,这就是通配符?的作用,看下节。
5、通配符
通配符,就是应对泛型类之间的继承关系的,分三种:List<?> 是所有List<ClassName>的父类
代码:
/**
* 通配符?
* List<?> 是所有List<Object>、List<String> ... 的父类
*/
public void test7(){
List<?> list=null;//共同的父类,通配符?
List<Object> listObject = new ArrayList<Object>();
List<String> listString = new ArrayList<String>();
list = listObject;
list = listString;
show(listObject);
show(listString);
}
List<? extends A>是List<A类及其子类> 的父类
List<? super B> 是List<B类及其父类> 的父类
/*
* List<? extends A>是所有A类及其子类的父类,即:
* 可以存放A的所有子类及其本身ClassName类;
* List<? super B> 是所有B类及其父类的父类,即:
* 可以存放所有B的父类及B类;
*/
public void test8(){
List<? extends Number> list1 = null;
List<Integer> list2= null;
list1 = list2;//可以,因为Integer是Number的子类
List<Object> list3 = null;
// list1 = list3;//编译不通过,因为Object 是Number的父类
List<? super Number> list4 = null;
list4 = list3;//可以
}
对于通配符的泛型集合List<?> ,是可以进行遍历查看操作的,但是不允许进行写入,只可以写入null
@Test
public void test9(){
List<String> list = new ArrayList<>();
list.add("AA");
list.add("BB");
list.add("CC");
List<?> list1= list;
//可以查看遍历声明通配符List<?>中的元素
Iterator<?> it = list1.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//不可以向声明为通配符的泛型List<?>中添加元素,唯一例外的是null
// list1.add("AA");//编译不通过
list1.add(null);
}
6、其他注意点
1.静态方法中不能使用类的泛型
在泛型类中,静态方法中不能使用泛型,因为静态方法在类加载时就加载了,此时还未指定泛型类的类型,不能使用。
但是在静态方法中可以使用泛型方法,即结合静态方法与泛型方法:
public static <E> E test(E e){
return e;
}
2.如果泛型类是一个接口或者抽象类,则不可创建泛型类的对象
3.不能在catch语句中使用泛型
public void test(){
try{
}catch(T e){//编译错误
}
}
但是这样使用是可以的:
public void test(){
try{
T t;
}catch(Exception e){
T t;
}
}