前言:使用泛型的目的是利用Java编译机制,在编译过程中帮我们检测代码中不规范的有可能导致程序错误的代码。例如,我们都知道list容器可以持有任何类型的数据,所以我们可以把String类型和Integer等等同时可以放入同一个list容器中,但这种做法是极其危险的。在泛型机制中这种操作是编译不通过,会强制你修改。故帮我们减少了隐藏的bug.
一:泛型 T
1.1 泛型用法
根据泛型使用的位置,即用在类(class),属性(filed)和方法(method)的不同位置,我把它分别总结如下几种
泛型类:即在类名后添加泛型标识(
泛型属性:泛型属性必须结合泛型类使用,用于接受泛型类持有的类型T
泛型方法:即在方法的返回值前声明泛型
备注1:如果泛型T没有被extends修饰(包含类和方法),我们称之为无界泛型,如果被extends修饰我们称之为有界泛型如下。
备注2:如果方法参数中有泛型T,而方法的返回类型前没有泛型T,该类型不是泛型方法,而是泛型类。
备注3:泛型方法常用在工具类中(即该方法只是一种工具),即与类的实例对象关系(持有的方法无关)。
备注4:当泛型方法中的泛型T与类中的泛型T同名时会产生警报,因为编译器不确定你要使用那个(方法中一个,类中也一个)持有对象。
1.2 有界泛型
相较于无界泛型(没有限定类型)
备注1:有界泛型只有上界(extends),没有下界的用法(相比于通配符?)。
1.3 泛型继承
一行代码加两幅图带你体会它与我们接口,抽象类和类的区别
ArrayList arrayList = new ArrayList<>();
Object object = new Object();
//The method add(String) in the type ArrayList is not applicable for the arguments (Object)
arrayList.add(object);//因为 ArrayList不是 ArrayList
二:通配符?
这是一段java官方对通配符的定义,In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.从这里我们可以看出通配符?是一种未知的类型。
个人小结:常用在方法上(注意与泛型方法的区别,其不需要再方法的返回类型前声明)
2.1 上界通配
即定义通配符的上界,用关键字extends声明,例如
public static void process(List <?extends Foo> list){/ * ... * /}
2.2 无界通配
即不限制通配符的界限,不需要任何关键字修饰‘?’,例如
public static void printList(List <?> list){/*........*/}
说明其功能形式 public static void printList(List
注意:List 和List <?>是不一样的。List
public class TestWildcard {
public void printList(List list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
public void printList2(List> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
public static void main(String[] args) {
TestWildcard testWildcard = new TestWildcard();
ArrayList extends Object> arrayList = new ArrayList<>();
ArrayList
2.3 下届通配
即定义通配符的下super界,用关键字extends声明,例如
public static void addNumbers(List <?super Integer> list){}
2.4 通配子类
The common parent is List>. A hierarchy of several generic List class declarations.
2.5 通配捕获与辅助方法
通配捕获:即操作通配符?参数 会抛出异常,除null外,例如:
public class WildcardError {
void foo(List> i) {
i.set(0, i.get(0));
}
}
分析:根据编译器推断,?是一个Object类型(故可以遍历出?所代表的对象),但如果要操作List>对象,编译器会要求?代表的具体类型,而编译器通过现有的规则(真对 ?的规则)是不允许的,故会包错。
解决捕获辅助方法
public class WildcardFixed {
void foo(List> i) {
fooHelper(i);
}
// Helper method created so that the wildcard can be captured
// through type inference.
private void fooHelper(List l) {
l.set(0, l.get(0));
}
}
通过一个辅助方法 *** fooHelper(List
分析:根据规则(真对T),编译器就知道T的具体类型,故可以安全的操作该对象。
三:小结
泛型与通配符区别:最根本的区别就是,java编译器,把T(泛型)推断成具体类型,而把通配符?推断成未知类型。而java编辑器只能操作具体类型,不能操作未知类型。导致如果有对参数有修改的操作就必须要使用泛型,如果仅是查看就可以使用通配符.
利用以上推断,我们可以利用通配符特性设计出安全的接口,比如我在一个接口的方法定义了通配符参数,则继承该接口的所有方法,都不能修改该方法传递过来的参数。
例如:
public interface GInterface {
void foo(List extends T> list);
}
public class GIterfaceImpl implements GInterface{
@Override
public void foo(List extends T> list) {
/**
* 只能遍历list,不能修改list
*/
for (T t : list) {
System.out.println(t);
}
//list.add(new Object());
}
public static void main(String[] args) {
GIterfaceImpl gIterfaceImpl = new GIterfaceImpl();
ArrayList list = new ArrayList<>();
list.add("1");
gIterfaceImpl.foo(list);
}
}
四:延伸
泛型与java8:
参考资料:https://docs.oracle.com/javase/tutorial/java/generics/index.html