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

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()];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容

  • 开发人员在使用泛型的时候,很容易根据自己的直觉而犯一些错误。比如一个方法如果接收List作为形式参数,那么如果尝试...
    时待吾阅读 1,034评论 0 3
  • 一、泛型简介1.引入泛型的目的 了解引入泛型的动机,就先从语法糖开始了解。 语法糖 语法糖(Syntactic S...
    Android进阶与总结阅读 1,024评论 0 9
  • 一、泛型简介 1.引入泛型的目的 了解引入泛型的动机,就先从语法糖开始了解。 语法糖 语法糖(Syntactic ...
    Ruheng阅读 4,366评论 2 50
  • 一、泛型的概念 泛型就是:类型参数化,处理的数据类型不是固定的,而是可以作为参数传入;疯狂讲义定义:泛型就是允许在...
    Serenity那年阅读 653评论 2 0
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,830评论 6 13