深⼊理解什么是Java泛型?泛型怎么使⽤?【纯转】
本篇⽂章给⼤家带来的内容是介绍深⼊理解什么是Java泛型?泛型怎么使⽤?有⼀定的参考价值,有需要的朋友可以参考⼀下,希望对你们有所助。
⼀、什么是泛型
“泛型” 意味着编写的代码可以被不同类型的对象所重⽤。泛型的提出是为了编写重⽤性更好的代码。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为⼀个参数。
⽐如常见的集合类 LinkedList:
1 2 3 4 5public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>,Deque<E>,Cloneable,Serializable{ //.....
transient Link<E> voidLink;
//.....
}
可以看到,LinkedList<E> 类名及其实现的接⼝名后有个特殊的部分<E>,⽽且它的成员的类型 Link<E> 也包含⼀个<E>,这个符号的就是类型参数,它使得在运⾏中,创建⼀个 LinkedList 时可以传⼊不同的类型。
⼆、为什么引⼊泛型
在引⼊泛型之前,要想实现⼀个通⽤的、可以处理不同类型的⽅法,你需要使⽤ Object 作为属性和⽅法参数,⽐如这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18public class Generic{
private Object[] mData;
jdk怎么使用public Generic(int capacity){
mData = new Object[capacity];
}
public Object getData(int index){
/
/.....
return mData[index];
}
public void add(int index,Object item){ //.....
mData[index] = item;
}
}
它使⽤⼀个 Object 数组来保存数据,这样在使⽤时可以添加不同类型的对象:
1 2 3Generic generic = new Generic(10); generic.add(0,"fangxing"); generic.add(1,23);
Object 是所有类的⽗类,所有的类都可以作为成员被添加到上述类中;当需要使⽤的时候,必须进⾏强制转换,⽽且这个强转很有可能出现转换异常:
1 2String item1 = (String) Data(0); String item
2 = (String) Data(1);
第⼆⾏代码将⼀个 Integer 强转成 String,运⾏时会报错
:
可以看到,使⽤ Object 来实现通⽤、不同类型的处理,有这么两个缺点:
1. 每次使⽤时都需要强制转换成想要的类型
2. 在编译时编译器并不知道类型转换是否正常,运⾏时才知道,不安全
根据《Java 编程思想》中的描述,泛型出现的动机在于:
有许多原因促成了泛型的出现,⽽最引⼈注意的⼀个原因,就是为了创建容器类。
在 JDK 1.5 出现泛型以后,许多集合类都使⽤泛型来保存不同类型的元素,⽐如 Collection:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19public interface Collection<E> extends Iterable<E>{ Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collecion<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
}
实际上引⼊泛型的主要⽬标有以下⼏点:
类型安全
泛型的主要⽬标是提⾼ Java 程序的类型安全
编译时期就可以检查出因 Java 类型不正确导致的 ClassCastException 异常符合越早出错代价越⼩原则
消除强制类型转换
泛型的⼀个附带好处是,使⽤时直接得到⽬标类型,消除许多强制类型转换所得即所需,这使得代码更加可读,并且减少了出错机会
潜在的性能收益
由于泛型的实现⽅式,⽀持泛型(⼏乎)不需要 JVM 或类⽂件更改
所有⼯作都在编译器中完成
编译器⽣成的代码跟不使⽤泛型(和强制类型转换)时所写的代码⼏乎⼀致,只是更能确保类型安全⽽已
三、泛型的使⽤⽅式
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为⼀个参数。
类型参数的意义是告诉编译器这个集合中要存放实例的类型,从⽽在添加其他类型时做出提⽰,在编译时就为类型安全做了保证。参数类型可以⽤在类、接⼝和⽅法的创建中,分别称为泛型类、泛型接⼝、泛型⽅法。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26public class GenericClass<F>{
private F mContent;
public GenericClass(F content){
mContent = content;
}
/*
泛型⽅法
*/
public F getContent(){
return mContent;
}
public void setContent(F content){
mcontent = content;
}
/*
泛型接⼝
*/
public interface GenericInterface<T>{ void doSomething(T t);
}
}
泛型类
泛型类和普通类的区别就是类名后有类型参数列表 <E>,既然叫“列表”了,当然这⾥的类型参数可以有多个,⽐如 public class HashMap<K, V>,参数名称由开发者决定。
类名中声明参数类型后,内部成员、⽅法就可以使⽤这个参数类型,⽐如上⾯的 GenericClass<F> 就是⼀个泛型类,它在类名后声明了类型 F,它的成员、⽅法就可以使⽤ F 表⽰成员类型、⽅法参数/返回值都是 F 类型。
泛型类最常见的⽤途就是作为容纳不同类型数据的容器类,⽐如 Java 集合容器类。
泛型接⼝
和泛型类⼀样,泛型接⼝在接⼝名后添加类型参数,⽐如以下 GenericInterface<T>,接⼝声明类型后,接⼝⽅法就可以直接使⽤这个类型。
/*
泛型接⼝
*/
public interface GenericInterface<T>{
void doSomething(T t);
}
实现类在实现泛型接⼝时需要指明具体的参数类型,不然默认类型是 Object,这就失去了泛型接⼝的意义。
未指明类型的实现类,默认是 Object 类型:
1 2 3 4 5 6public class Generic implements GenericInterface{ @Override
public void doSomething(Object o){
//...
}
}
指明了类型的实现:
1 2 3 4 5 6public class Generic implements GericInterface<String>{ @Override
public void doSomething(String s){
//.....
}
}
泛型接⼝⽐较实⽤的使⽤场景就是⽤作策略模式的公共策略, Comparator就是⼀个泛型接⼝:
1 2 3 4public interface Comparator<T>{
public int compare(T lhs, Trhs);
public bollean equals(Object object); }
泛型接⼝定义基本的规则,然后作为引⽤传递给客户端,这样在运⾏时就能传⼊不同的策略实现类。
泛型⽅法
泛型⽅法是指使⽤泛型的⽅法,如果它所在的类是⼀个泛型类,那就很简单了,直接使⽤类声明的参数。
如果⼀个⽅法所在的类不是泛型类,或者他想要处理不同于泛型类声明类型的数据,那它就需要⾃⼰声明类型。1
2 3 4 5/*
传统的⽅法,会有unchecked ... raw type 的警告*/
public Set union(Set s1, Set s2){
6 7 8 9 10 11 12 13 14 15 16 17 18 Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
/*
泛型⽅法,介于⽅法修饰符和返回值之间的称作类型参数列表<A,V,>(可以有多个)类型参数列表指定参数、返回值中泛型的参数类型范围,命名惯例与泛型相同。
*/
public<E> Set<E> union2(Set<E> s1, Set<E> s2){
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
四、泛型的通配符
通配符:传⼊的类型有⼀个指定的范围,从⽽可以进⾏⼀些特定的操作
泛型中有三种通配符形式:
1.<?>⽆限制通配符
2.<? extends E> extends 关键字声明了类型的上界,表⽰参数化的类型可能是所指定的类型,或者是此类型的⼦类。
3.<? super E> super 关键字声明了类型的下界,表⽰参数化类型可能是指定类型,或者是此类型的⽗类。
⽆限制通配符 < ?>
要使⽤泛型,但是不确定或者不关⼼实际要操作的类型,可以使⽤⽆限制通配符(尖括号⾥⼀个问号,即 <?> ),表⽰可以持有任何类型。和 Object 不⼀样,List<?> 表⽰未知类型的列表,⽽ List<Object> 表⽰任意类型的列表。
如传⼊个 List<String> ,这时 List 的元素类型就是 String,想要往 List ⾥添加⼀个 Object,这当然是不可以的。
上界通配符 < ? extends E>
在类型参数中使⽤ extends 表⽰这个泛型中的参数必须是 E 或者 E 的⼦类,这样有两个好处:
如果传⼊的类型不是 E 或者 E 的⼦类,编辑不成功
泛型中可以使⽤ E 的⽅法,要不然还得强转成 E 才能使⽤
下界通配符 < ? super E>
在类型参数中使⽤ super 表⽰这个泛型中的参数必须是 E 或者 E 的⽗类。
1 2 3 4 5private<E> void add(List<? super E> dst, List<E> Src){ for(E e : src){
dst.add(e);
}
}
上⾯的 dst 类型 “⼤于等于” src 的类型,这⾥的“⼤于等于”是指 dst 表⽰的范围⽐ src 要⼤,因此装得下 dst 的容器也就能装 src。通配符⽐较
⽆限制通配符 < ?> 和 Object 有些相似,⽤于表⽰⽆限制或者不确定范围的场景。
< ? super E> ⽤于灵活写⼊或⽐较,使得对象可以写⼊⽗类型的容器,使得⽗类型的⽐较⽅法可以应⽤于⼦类对象。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论