天天看点

Java学习笔记一泛型

Java 5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在。

当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不用增加泛型声明。例如,为Apple<T>类定义构造器,其构造器名依然是Apple,而不是Apple<T>。调用该构造器时却可以使用Apple<T>的形式,当然应该为T形参传入实际的类型参数。Java 7提供了菱形语法,允许省略<>中的类型实参。

不管未泛型形参传入哪种类型实参,对于Java来说它们依然被当成同一类处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化或者静态变量的声明和初始化中不允许使用泛型形参。instanceof运算符后不能使用泛型类

List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
//调用getClass方法比较
System.out.println(l1.getClass() == l2.getClass());  // 输出true
public class R<T>{
  //错误
  static T info;
  public static void bar(T msg){}
}
java.util.Collection<String> cs = new java.util.ArrayList<>();
//下面代码编译时引发错误
if(cs instanceof java.util.ArrayList<String>())      

从泛型类派生子类

当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,使用这些接口、父类时不能再包含泛型形参。

// 使用Apple类时为T形参传入String类型
public class A extends Apple<String>
// 使用Apple类时没有为T形参传入实际类型
public class A extends Apple //使用Apple类省略泛型被称为原始类型
{
  //重写父类方法
  public String getInfo()
  {
    //super.getInfo方法返回值是Object类型,加上toString才能返回String类型
    super.getInfo().toString();
  }
}      

如果使用Apple类时没有传入实际的类型(即使用原始类型),Java编译器可能发出警告:使用了未经检查或不安全的操作-这就是泛型检查的警告。系统会把Apple<T>类里的T形参当成Object类型处理。

类型通配符

我们知道不管未泛型形参传入哪种类型实参,对于Java来说它们依然被当成同一类处理。但是Java又有如下限制,比如java.util.List<java.lang.Object>无法应用于java.util.List<java.lang.String>。因此无法使用实参List<String>赋值给形参List<Object>,即使Object是String的父类。

为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号,将一个问号作为类型实参传给List集合,它的元素类型可以匹配任何类型。

public void test(List<?> c){
  for(int i = 0; i < c.size(); i++)
    System.out.println(c.get(i));
}      

现在使用任何类型的List来调用它,程序依然可以访问集合c中的元素,其类型是Object,这永远是安全的,因为不管List的真实类型是什么,它包含的都是Object。

带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中。

List<?> c = new ArrayList<String>();
//错误
c.add(new Object());      

<? extends 类型> 指定通配符上限的集合,只能从集合中取出元素(取出的元素总是上限的类型),不能向集合中添加元素(因为编译器没法确定集合元素实际是哪种子类型)。

<? super 类型> 指定通配符的下限的集合,编译器知道集合元素是下限的父类型,但具体是哪种父类型则不确定。因此,能向集合中添加元素(因为实际赋值的集合元素总是声明的父类),从集合中取元素时只能被当成Object类型处理(编译器无法确定取出的到底是哪个父类的对象)。

泛型方法

修饰符 <T, S> 返回值类型 方法名(形参列表)
{
  // 方法体
}      

与类、接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数,因为编译器根据实参推断出泛型所代表的类型,它通常推断出最直接的类型。

泛型方法允许泛型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。

public class GenericMethodTest
{
  //声明一个泛型方法,该泛型方法中带一个T泛型形参
  static <T> void fromArrayToCollection(T[] a. Collection<T> c)
  {
    for(T o : a)
      c.add(o);
  }
  public static void main(String[] args)
  {
    Object[] oa = new Object[100];
    Collection<Object> co = new ArrayList<>();
    // 下面代码T代表Object类型
    fromArrayToCollection(oa, co);
    String[] sa = new String[100];
    Collection<String> cs = new ArrayList<>();
    // 下面代码T代表String类型
    fromArrayToCollection(sa, cs);
    // 下面代码T代表Object类型
    fromArrayToCollection(sa, co);
  }
}      

如果某个方法中一个形参(a)的类型或返回值的类型依赖于另一个形参(b)的类型,则形参(b)的类型声明不应该使用通配符-因为形参(a)或返回值的类型依赖于该形参(b)的类型,如果形参(b)的类型无法确定,程序就无法定义形参(a)的类型。在这种情况下,只能考虑使用在方法签名中声明泛型-也就是泛型方法。

泛型构造器

class Foo
{
  public <T> Foo(T t)
  {
    System.out.println(t);
  }
}
class MyClass<E>
{
  public <T> MyClass(T t)
  {
    System.out.println("t参数的值为:"+t);
  }
}
public class GenericConstructor
{
  public static void main(String[] args)
  {
    //泛型构造器中的T类型为String
    new Foo("疯狂Java讲义");
    //泛型构造器中的T类型为Integer
    new Foo(200);
    //显式指定泛型构造器中的T类型为String
    new <String> Foo("疯狂Andriod讲义");
    //显式指定泛型构造器中的T类型为String,但传的实参是Double,错误
    new <String> Foo(12.3);
  
    //MyClass类声明中的E形参是String类型,泛型构造器中声明的T形参是Integer类型
    MyClass<String> mc1 = new MyClass<>(5);
    //显式指定泛型构造器中声明的T形参是Integer类型
    MyClass<String> mc2 = new <Integer> MyClass<String>(5);
    //显式指定泛型构造器中声明的T形参是Integer类型,就不能用菱形语法,错误
    MyClass<String> mc3 = new <Integer> MyClass<>(5);
  }
}      

擦除和转换

在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定实际的类型。如果没有为这个泛型类指定实际的类型,此时被称为raw type原始类型,默认是声明该泛型形参时指定的第一个上限类型。

擦除:当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉。

class Apple <T extends Number>
{
  T size;
  public Apple(){}
  public Apple(T size){ this.size = size; }
  public void setSize(T size){ this.size = size; }
  public T getSize(){ return this.size; }
}
public class ErasureTest
{
  public static void main(String[] args)
  {
    Apple<Integer> a = new Apple<>(6);
    // a的getSize方法返回Integer对象
    Integer as = a.getSize();
    // 把a对象赋给Apple变量,丢失尖括号里的类型信息
    Apple b = a;
    // b只知道size的类型是Number
    Number size1 = b.getSize();
      // 下面代码会出错
      Integer size2 = b.getSize();
  }
}      
public class ErasureTest2
{
  List<Integer> li = new ArrayList<>();
  li.add(6);
  li.add(9);
  List list = li; // 编译器丢失li的泛型信息,即丢失li集合里元素的类型信息
  // 下面代码引发未经检查的转换警告
  List<String> ls = list; //允许将List对象赋给LIST<Type>类型的变量
  //当试图把元素当作String对象取出时引发ClassCastException异常
  System.out.println(ls.get(0)); //错误,运行时异常
}      

泛型与数组

Lsit<String>[] las = new ArrayList[10]; //[unchecked]未经检查的转换警告

//允许创建无上限的通配符泛型数组
List<?>[] lsa = new ArrayList<?>[10]; //ArrayList<?>集合的数组
Object[] oa = lsa;
List<Integer> li = new ArrayList<>(); //ArrayList集合
li.add(3);
oa[1] = li;
Object target = lsa[1].get(0);
if(target instanceof String)
{
  //下面代码安全
  String s = (String)target;
}