美文网首页
从0开始复习java(6)--GenericType

从0开始复习java(6)--GenericType

作者: ifeelok0319 | 来源:发表于2017-04-26 18:52 被阅读37次

Java5增加泛型支持在很大程度上都是为了让集合能记住其元素的数据类型。在没有泛型之前,一旦把一个对象放进集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换。而泛型可以让集合记住其元素的数据类型。

一、认识泛型

java7之前的语法:

List<String> strList = new ArrayList<String>();
Map<String,String> scores = new HashMap<String,String>();

从Java7开始使用菱形语法:

List<String> strList = new ArrayList<>()

二、深入泛型

泛型:允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。Java5为集合框架中的全部接口和类增加了泛型支持。

定义泛型接口、类

//定义接口时指定类型形参
public interface List<E>{
    void add(E x);
    Iterator<E> iterator();
}
public interface Iterator<E>{
    E next();
    boolean hasNext();
}
public interface Map<K,V>{
    Set<K> keySet();
    V put(K key, V value){
        
    }
}

Iterator<E>Set<K>表面他们是一种特殊的数据类型,是一种和IteratorSet不同的数据类型,可以认为是他们的子类。

包含泛型声明的类型可以在定义变量、创建对象时传入一个类型参数,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在。

创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。

从泛型类派生子类

//错误的
public class A extends Apple<T>{}
//正确的
public class A extends Apple<String>{}
//会出现警告
public class A extends Apple{}

并不存在泛型类

//l1.getClass()==l2.getClass()
List<String> l1 = new ArrayList<>();
List<String> l1 = new ArrayList<>();

不管为泛型形参传入哪一种类型的实参,他们依然被当作同一个类处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。

public class R<T>{
    //错误,不能在静态变量声明中使用类型形参
    static T info;
    T age;
    public void foo(T msg){}
    //错误,不能在静态方法声明中使用类型形参
    public static void var(T msg){}
}
java.util.Collection<String> cs = new java.util.ArrayList<>();
//错误,并不会生成泛型类
if (cs instanceof java.util.ArrayList<String>){}

三、类型通配符

//会有泛型警告
public void test(List c){
    for(int i=0; i<c.size(); i++){
        System.out.println(c.get(i));
    }
}

改成

//会有泛型警告
public void test(List<Object> c){
    for(int i=0; i<c.size(); i++){
        System.out.println(c.get(i));
    }
}
List<String> str = new ArrayList<>();
test(str);

上面代码产生异常。

无法将Test中的test(java.util.List<java.lang.Object>)应用于(java.util.List<java.lang.String>)

如果FooBar的子类型(子类或者子接口),而G是一个具有泛型声明的类或接口,G<Foo>并不是G<Bar>的子类型。

数组与泛型不同,Foo[]bar[]的子类型。

使用类型通配符

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

c中包含的是Object

这种带通配符的List仅表明它是各种泛型List的父类,并不能把元素加入进去。

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

c中放的类型是Object,而add的参数是E类的对象或者其子类的对象。该例中不知道E是什么类型,产生编译错误。null是所有引用类型的实例,它可以添加进去。

get返回值是一个未知类型,可以赋值给Object类型的变量。

设定类型通配符的上限

public abstract class Shape{
    public abstract void draw();
}
public class Circle extends Shape{
    public void draw(Canvas c){
        System.out.println(c+"圆");
    }
}
public class Rectangle extends Shape{
    public void draw(Canvas c){
        System.out.println(c+"长方形");
    }
}
public class Canvas{
    public void drawAll(List<? extends Shape> shapes){
        for (Shape shape:shapes){
            shape.draw(this);
        }
    }
}
List<Circle> c = new ArrayList<>();
Canvas cv = new Canvas();
c.drawAll(c);

以上程序会将List<Circle>对象当成List<? extends Shape>使用。List<? extends Shape>可以表示他们的父类--只要List尖括号里的类型是Shape的子类型即可。

当前程序无法确定这个受限制的通配符的具体类型。所以不能把Shape对象或者其子类型的对象加入这个泛型集合中。

设定类型形参的上限

public class Apple<T extends Number>{
    T col;
    public static void main(String[] args){
        Apple<Integer> a = new Apple<Integer>();
        //报错
        Apple<String> b = new Apple<String>();
    }
}

程序可以为类型形参设置多个上限(至多一个父类上限,可以有多个接口上限)。

public class Apple<T extends Number&java.io.Serializable>{

}

和类同时继承父类和实现接口相似,所有的接口上限必须位于类上限之后。

四、泛型方法

泛型方法的格式如下:

修饰符 <T,S> 返回值类型 方法名(形参){
    //方法体
}
import java.util.ArrayList;
import java.util.Collection;

import com.sun.org.apache.xpath.internal.operations.Number;

public class GenericMethodTest{
    static <T> void fromArrayToCollection(T[] t, Collection<T> c){
        for(T o: t){
            c.add(o);
        }
    }
    public static void main(String[] args) {
        Object[] oa = new Object[100];
        Collection<Object> co = new ArrayList<>();

        String[] sa = new String[100];
        Collection<String> cs = new ArrayList<>();

        //T为Object类型
        fromArrayToCollection(sa, co);

        Integer[] ia = new Integer();
        Collection<Number> cn = new ArrayList<>();

        //T为Number类型
        fromArrayToCollection(ia, cn);

    }
}

泛型方法和类型通配符的区别

大多数时候可以使用泛型方法代替类型通配符。

public interface Collection<E>{
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);

    ...
}

采用泛型方法实现:

public interface Collection<E>{
    <T> boolean containsAll(Collection<T> c);
    <T extends E> boolean addAll(Collection<T> c);

    ...
}

通配符被设计用来支持灵活的子类化的。

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

如果又需要,也可以同时使用泛型方法和通配符:

public class Collections{
    public static <T> void copy(List<T> dest, List<? extends T> src){}
}

可以使用下面的泛型方法替换:

public static <T, S extends T> void copy(List<T> dest, List<S> src)

S仅使用了一次,其他参数的类型和方法返回值都不依赖于它,没有存在的必要,可以用通配符替换。

显著区别:
类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型;但泛型方法中的类型形参必须在对应方法中显示声明。

菱形语法与泛型构造器

java允许在构造器签名中声明类型形参。

class Foo{
    public <T> Foo(T t){
        ...
    }
}
class Foo <E>{
    public <T> Foo(T t){

    }
}
public class GenericTest{
    public static void main(String[] args){
        Foo<String> s1 = new Foo<>(5);
        Foo<String> s2 = new <Integer> Foo<String>(5);
        //下面代码出错
        Foo<String> s3 = new <Integer> Foo<>();
    }
}

设定通配符下限

 public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    if (srcSize > dest.size())
        throw new IndexOutOfBoundsException("Source does not fit in dest");

    if (srcSize < COPY_THRESHOLD ||
        (src instanceof RandomAccess && dest instanceof RandomAccess)) {
        for (int i=0; i<srcSize; i++)
            dest.set(i, src.get(i));
    } else {
        ListIterator<? super T> di=dest.listIterator();
        ListIterator<? extends T> si=src.listIterator();
        for (int i=0; i<srcSize; i++) {
            di.next();
            di.set(si.next());
        }
    }
}

java8改进的类型推断

  • 可通过调用方法的上下文来推断类型参数的目标类型
  • 可在方法调用链中,将推断得到的类型参数传递到最后一个方法。

五、擦除和转换

如果没有为一个泛型类制定实际的类型参数,则该类型参数被成为raw type,默认是声明该类型参数时制定的第一个上限类型。

public class Test{
    public static void main(String[] args){
        List<Integer> li = new ArrayList<>();
        List list=li;
        //擦除,提示"未经检查的转换"
        List<String> li = list;
        //运行时异常
        System.out.println(li.get(0));
    }
}

六、泛型与数组

数组的类型不可以是类型变量,除非是采用通配符的方式。

数组元素类型不能包含泛型变量或者泛型形参,除非是无上限的泛型通配符,但可以声明元素类型包含泛型变量或泛型形参的数组。

//不允许
List<String>[] lsa = new List<String>[10];
//允许
List<String>[] lsa = new ArrayList[10];
Object[] oa = (Object[])lsa;
List<integer> li = new ArrayList<>();
li.add(3);
oa[1] = li;
//下面代码将不会有警告,但引发ClassCastException
String s = lsa[1].get(0);

Object target = lsa[1].get(0);
if(target instanceof String){
    String s =(String)target;
}

创建元素类型是泛型变量的数组对象也会导致编译错误:

<T> T[] makeArray(Collection<T> coll){
    //下面语句出错
    return new T[coll.size()];
}

相关文章

  • 从0开始复习java(6)--GenericType

    Java5增加泛型支持在很大程度上都是为了让集合能记住其元素的数据类型。在没有泛型之前,一旦把一个对象放进集合中,...

  • 从0开始复习java(1)

    java语言概述 1、java8的新特性。 重新设计的接口语法,Lambda表达式,方法引用,构造器引用,函数式编...

  • 从0开始复习java(2)

    面向对象 1、方法的参数传递机制 java里方法的参数传递方式只有一种:值传递。 2、方法的所属性 方法不能独立定...

  • 从0开始复习Java(10)

    一、线程概述 进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。 进...

  • 从0开始复习java(3)

    一、面向对象 1、包装类 java5之后有自动装箱和自动拆箱功能。 包装类可以实现基本类型变量和字符串之间的转换。...

  • 从0开始复习java(5)--Collection

    Java 集合类是一种特别有用的工具类,大致可分为Set、List、Queue、Map四种体系。 Set代表无序、...

  • 从0开始复习Java(7)--Exception

    Java将异常分为两类:Checked异常和Runtime异常。Java认为前者都是可以在编译阶段被处理的异常,所...

  • 从0开始复习Java(8)--Annotation

    Java5增加了对MetaData的支持,也就是Annotation。这些标记在编译、类加载、运行时被读取,并执行...

  • 从0开始复习java(4)--Class

    oracle为java提供了丰富的类库,java8提供了4000多个基础类。java程序员至少要熟悉java中70...

  • 从0开始复习java(9)--IO

    Java的io通过java.io包下的类和接口支持。主要有输入、输出流,又分为字节流和字符流。Java的io流使用...

网友评论

      本文标题:从0开始复习java(6)--GenericType

      本文链接:https://www.haomeiwen.com/subject/ncrwzttx.html