书接上文。
上文地址:https://www.jianshu.com/p/d13ed2b58c8a
本内容均属原创,转载请注明出处:
https://www.jianshu.com/p/4f9b47e44b0e
本次主要解释清楚泛型的分类,以及jdk1.7中的一些对于泛型的增强以及坑。
泛型分类
接下来我会通过四个方向依次详细说明泛型在不同情境下定义要遵守的一些规则。
泛型类:
定义普通泛型类
public class Test01 {
public static void main(String[] args) {
//create generic type Gen instance and assign
//generic type of Gen is Integer
//it`s use of autoboxing to encapsulate
//the value 12 within an Integer Object
Gen<Integer> g1 = new Gen<Integer>();
g1.t = 12;
g1.getT();
//jdk1.7 enhancement type inference
Gen<String> g2 = new Gen<>();
g2.t = "hello";
g2.getT();
//create generic type Gen instance and
//don`t assign generic type
Gen g3 = new Gen();
g3.t = new Date();
g3.getT();
}
}
//declared a generic type Gen
class Gen<T>{
T t;
public void getT(){
System.out.println(t.getClass().getName());
}
}
在这里说明了一个Generic类->Gen,泛型类型是T,我们上文提到过,泛型就是一个类型,那么这里的T你不防理解为一个类型。并且为了获取T类型方便一些,我们将T类型也声明为了一个成员变量。我们在代码中通过getT()方法获取成员变量T的Class对象的名称。其次我们在测试类创建了3个Gen的对象。
在第一个用例中:创建对象时指定类型为Integer,且通过给对象中的T类型赋值为12。我们发现获取到的Class对象的名称为Integer。
第二个用例中:创建对象时指定类型为String,且这里我们在对象创建中只通过<>
,而没有在括号中给具体的类型,这是jdk1.7中的增强类型推断,因为已经声明过时String类型,所以会自动推断出Gen对象的T类型是String。同样这里获取到的Class对象名称为String。
第三个用例中:创建对象时没有指定泛型类型,我们这里直接赋值发型可以给T传入任何Object类型。其实这就是典型的擦除。我们后续分享会逐一解开这个面纱。这里获取到的泛型的name成为了Object。
总结:在一个类声明时通过<T>会指定泛型类型。创建对象时可以在<>指定具体的泛型类型。
如果不指定则经过擦除之后,类型变为Object。我们可以将g1Gen<Integer>和g2<String>
理解为一个Gen<T>类的一个特殊子类。这个子类是不存在。(注意只是这样理解,方便我们后续
理解类型擦除)。
注意:创建泛型类型对象时,构造器的类名还是原来的类名。不需要增加泛型声明。不需要写为Gen<T>。
泛型类中的继承
我们通过以下几个小例子阐述,在创建泛型子类时的编写问题:
class Gen<T>{
T t;
public void getT(){
System.out.println(t.getClass().getName());
}
}
//declared a subclass for Gen
//subclass don`t define generic tyoes, allow the definition of declared
class SubGen1 extends Gen{}
//allows subclass to specify the superclass specific generic type
class SubGen2 extends Gen<String>{}
//don`t allows superclass definition generic type
class SubGen3 extends Gen<T>{}//compile-time error
总结:
1、定义子类继承泛型类,不做任何泛型方法,那么创建子类对象时泛型类中的类型自动转为Object
2、定义子类继承泛型类,在父类中指定具体泛型类型,那么创建子类对象时泛型类中的类型为指定类型
3、定义子类继承泛型类,在父类中指定不具体的泛型类型,那么是不允许的。
泛型接口:
//define generic interface
interface List<E> extends Iterator<E>{
void add(E e);
Iterator<E> iterator();
}
//define generic interface
interface Iterator<E>{
E next();
boolean hasNext();
}
//define subclass for interface
class MyList implements List <String>{
@Override
public String next() {return null;}
@Override
public boolean hasNext() {return false;}
@Override
public void add(String e) {}
@Override
public Iterator<String> iterator() {return null;}
//test generic interface
public static void main(String[] args) {
MyList ls = new MyList();
ls.add("hehe");//execute add method can only add Stirng type
}
}
结论:
1、定义泛型接口时,实现类如果声明时指定了泛型类型,那么后续调用时,在编译阶段只能使用该类型。
2、如果定义泛型接口时,实现类不指定接口的泛型类型,那么会报警告。
List is a raw type. References to generic type List<E> should be parameterized。
说明当前泛型接口是一个 raw type.最好指定泛型是参数化的。
而且对于后续程序来讲这样也是不太可取的。
包含泛型的类型,不论是类、子类、实现类、对象。我们其实在理解层面上都可以认为是当前类、实现类的一个逻辑子类。比如Gen<String>可以理解为一个Gen<Object>的子类,List<String>是List<T>的一个子类,但是这个是一个逻辑,在物理上不存在。只是为了方便理解。
泛型类的注意事项
测试并不存在的泛型类型
测试用例:
public static void main(String[] args) {
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass()==l2.getClass());
}
结论:对于java来说,不论泛型的类型具体是什么,运行期间两个List都拥有同一个List对应的Class对象。所以无法再一个泛型类,接口中定义静态的内容去直接使用泛型中的定义的泛型类型,比如,不允许下面代码出现:
测试用例:
class Gen<T>{
static T t;//Cannot make a static reference to the non-static type T
}
不能使用instanceof对于泛型判定
系统不会给每一个泛型的实例对象创建一个泛型对象,也就意味着无法使用instanceOf运算符。比如:
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass()==l2.getClass());
//Cannot perform instanceof check against parameterized type
//List<String>. Use the form List<?> instead since further
//generic type information will be erased at runtime
System.out.println(l1 instanceof List<String>);
我们泛型以上的内容,大概是说是不能用instanceOf去检查参数化类型的,也就是我们的泛型。这样不允许做。因为泛型类型的类型信息在运行期就会被抹去。也叫类型擦除。
泛型方法 jdk1.5支持
定义方法,完成功能,将数组中的数据填充到集合中。
测试用例:
public static void main(String[] args) {
Object[] obj = new Object[]{"hello",123.12,"java"};
ArrayList<Object> as = new ArrayList<>();
fillIntoColls(obj, as);
obj = new Object[]{"hello",123.12,"java"};
Collection<String> ass = new ArrayList<>();
//fic(Object[], Collection<Object>)
//not applicable for the arguments (Object[], ArrayList<String>)
fillIntoColls(obj, ass);//compile-time error
}
public static void fillIntoColls(Object[] objs,Collection<Object> cols){
for(Object obj:objs){
cols.add(obj);
}
}
结论:虽然看着方法没有问题,但是在编译阶段Collection<String>对象不能作为Collection<Object>的对象使用。并且Collection<String>也不是Collection<Object>的子类型。
解决办法:
1、可以通过通配符以及上下限解决。后续分享中我们在讨论。
2、泛型方法
测试用例:
public static void main(String[] args) {
Object[] obj = new Object[]{"hello",123.12,"java"};
ArrayList<Object> as = new ArrayList<>();
fillIntoColls(obj, as);
Collection<String> ass = new ArrayList<>();
String[] strs = new String[]{"hello","java"};
fillIntoColls(strs, ass);
}
public static <T> void fillIntoColls(T[] objs,Collection<T> cols){
for(T t:objs){
cols.add(t);
}
}
结论:在方法中通过<>
声明了一个泛型类型,在fillIntoColls方法中使用,将数组以及集合的类型都声明为泛型类型。当然也可以定义多个值。这里有个细节注意,因为是在static方法中声明的,所以当前的泛型方法中的泛型类型只能在当前方法中使用。如果是非static,也可以直接使用泛型类中声明的,具体大家也可以参照java.util.ArrayList
。编译器会在这里根据实际参数推断出类型的T的参数,所以其实当你传入的数组类型和集合的泛型类型不匹配,调用方法时也会报错。
java7菱形语法以及泛型构造器
java7增加的菱形语法:
测试用例:
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
l1.add("hello");
l2.add(12);
//compile-time error
l1.add(12);
l2.add("hello");
结论:jdk1.7之后增强了泛型的类型推断。就是l1,l2对象创建时,指定了泛型的具体类型,那么构造器后就无序指定完整的泛型信息。并且后续的add()方法对于l1而言只能添加String,对于l2而言只能增加Integer对象。左右后面的两行代码都会在编译期间报错。
泛型构造器:
注意:java允许构造器声明时可以使用泛型的。这里的构造器就变成了泛型构造器。
测试用例:
public class Test {
public static void main(String[] args) {
new Gen(123);
new <String>Gen("123");
new <Integer>Gen(123.1);//compile-time error
Gen<Integer> g = new Gen<>(123); //compile-time error
}
}
class Gen{
//define constructor use generic
public <T> Gen(T t){
System.out.println(t);
}
}
结论:
-
泛型构造器如果调用时不指定泛型类型,具体传入的类型决定了泛型类型。其实经过擦除之后就是Object
-
泛型构造器调用时在new关键词后加入泛型构造器的泛型具体类型,那么传入值的时候,会自动推断出来是String类型(比如上述代码的第2、3行);但是如果声明的类型和实际传入的类型不一致,则会在编译器报错。
-
这里千万注意,泛型构造器使用时,书写是new <类型> 类名();而泛型类创建对象时是new 类名<>();所以这一点千万注意。但是这是泛型构造器和泛型类只存在一个的情况下。
泛型构造器的坑
你觉的下面几行代码那几行会报错呢?
public class Test05 {
public static void main(String[] args) {
Gen<Integer> g1 = new Gen<>(123);
Gen<String> g2 = new <Integer>Gen(123);
Gen<String> g3 = new <Integer>Gen<String>(123);
Gen<String> g4 = new <Integer>Gen<>(123);
}
}
class Gen<E>{
//define constructor use generic
public <T> Gen(T t){
System.out.println(t);
}
}
结论:
- 第一行,指定了泛型类的类型是Integer,没有指定泛型构造器的具体类型,根据类型自动推断的类型是Integer。
- 第二行,指定泛型构造器的类型是Integer,传入的实际类型是123 没有问题。
- 第三行,指定了泛型了您先给为String,指定了泛型构造器为Integer,传入实参是123,没问题。
- 第四行,如果声明了泛型构造器的实际类型,那么这时候千万注意,不能再使用jdk7的菱形语法了。这里会报错。不支持这种写法。其实就是不能写了泛型构造器而通过;菱形语法让编译器推断泛型类的具体类型。
网友评论