Java 泛型

基础

Java集合的缺点:将一个对象放进一个集合时,集合就会忘记这个对象的数据类型,当取出这个对象时,该对象的类型就会变成Object类型,对对象进行使用时要进行相应的类型转换。因此指定下面两种方法。
创建集合时指定类型参数:

public static void main(String[] args) {
        //创建一个只能保存String类型的集合
        List<String> list=new ArrayList<String>();
        list.add("beautiful");
        list.add("quickly");
        list.forEach(str->System.out.println(((String)str).length()));
    }

java 7泛型的菱形语法

public static void main(String[] args) {
        //Java自动推断出ArrayList的<>里是String类型
        List<String> list=new ArrayList<>();
        list.add("beautiful");
        list.add("Strongger");
        list.forEach(ele->System.out.println(ele));
        //java自动推断出HashMap的<>里是String,List<String>
        Map<String,List<String>> map=new HashMap<>();
    }

一、泛型简介

概念:
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参在声明变量、创建对象、调用方法时动态指定(即传入实际的类型形参,也可传入类型实参)。

定义泛型接口、类
public interface List<E>
    {
        //接口中的类型E可以作为类型使用
        void add(E x);
        Iterator<E> iterator();
    }
    
    public interface Iterator<E>
    {
        //在接口里E完全可以作为类型使用
        E next();
    }

    public interface Map<K,V>
    {
        //在接口里K,V完全可以作为类型使用
        Set<K> keySet();
        V put(K key,V value);
    }

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

public class Apple<T>{
    //使用T类型形参定义实例变量
    private T info;
    //使用类型形参T定义构造器
    public Apple(T info)
    {
        this.info=info;
    }

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }

    public static void main(String[] args) {
        //由于传给T形参的是String,所以构造器参数指定是String
        Apple<String> a1=new Apple<>("苹果");
        System.out.println(a1.getInfo());
        //传给T形参的类型是Double,所以构造器参数指定是Double
        Apple<Double> a2=new Apple<>(5.66);
        System.out.println(a2.getInfo());
    }

}
泛型类派生子类
//使用Apple类时为T形参传入String类型
//如果使用T类型参数时,所有使用T类型形参的地方都将会被替换成String类型
public class A extends Apple<String>
//使用Apple类时不传入实际的形参
public class A extends Apple
public class A2 extends Apple
{
     public String getInfo()
      {
             //super.getInfo()方法返回值是Object类型
            //必须使用toString()方法才返回String类型
            return super.getInfo().toString();
      }
}
不存在泛型类

instanceof运算符不能使用泛型类,因为系统中不会存在正真的泛型类

List<String> list1=new ArrayList<>();
List<Integer> list2=new ArrayList<>();
//下面将返回True,不管泛型的实际参数是什么,运行时总有相同的class
System.out.println(list1.getClass()==list2.getClass());
public class R<T>
{
    //下面语句错误,不能在静态变量声明中使用类型形参
    static T info;
    T age;
    public void foo(T msg){}
    //下面语句错误,不能在静态方法声明中使用类型形参
    public stativ void bar(T msg){}
}

二、类型通配符

如果Foo是Bar的一个子类型(子类或者接口),而G是具有泛型声明的类或接口,G<Foo>并不是G<Bar>的子类型,这一点非常注意!

数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或接口),那么Foo[]依然是Bar[]的子类型。

使用类型通配符

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

//以上List集合中的元素类型是不确定,使用泛型是会引起泛型警告,修改如下
public void test(List<?> c)
{
    for(int i=0;i<c.size();i++)
    {
        System.out.println(c.get(i))
    }
}

上面程序使用的List<?>,这种写法可以适用于任何支持泛型声明的接口和类,比如:Set<?>、Collection<?>、Map<?,?>等,但这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入其中,唯一例外的是null,它是所有应用类型的实例。另一方面,程序可以调用get()方法来返回List<?>集合指定索引处的元素。
如:

List<?> c=new ArrayList<String>();
//下面程序引起编译错误
c.add(new Object());
设定类型通配符的上限

将Canvas类修改成下面的形式,就可以把List<Circle>对象当成List<? extends Shape>使用。即List<? extends Shape>可以表示成List<Circle>、List<Rectangle>的父类。Shape称为这个通配符的上限。

//定义一个抽象类Shape
public abstract class Shape
{
    public abstract void draw(Canvas c);
}

//定义Shape的子类Circle
public class Circle extends Shape
{
    //实现画图方法
    public void draw(Canvas c)
    {
        System.out.println("在画布"+c+"上画一个圆")
    }
}

//定义Shape的子类Rectangle
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 s : shapes)
        {
            s.draw(this);
        }
    }
}
设定类型形参的上限

设定类型形参的上限时,要么传给类型形参的类型是上限类型,要么是上限类型的子类

public class Apple<T extends Number>
{
    T col;
    public static void main(String[] args)
    {
        Apple<Integer> a1=new Apple<>();
        Apple<Double> a2=new Apple<>();
        //下面代码将会引发编译异常,因为String不是Number的子类型
        Apple<String> as=new Apple<>();
 
    }

}

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

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

三、泛型方法

定义泛型方法
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);
        
        Integer[] ia=new Integer[100];
        Float[] fa=new Float[100];
        Number[] na=new Number[100];
        Collection<Number> cn=new ArrayList<>();
        //下面代码中T代表Number类型
        fromArrayToCollection(ia, cn);
        fromArrayToCollection(fa, cn);
        fromArrayToCollection(na, cn);
        
        //下面将出现编译错误因为Number既不是String类型,也不是String的子类
        //fromArrayToCollection(na, cs);

    }

}

在方法中一个类型形参是另一个类型形参的子类时,可以使用如Collection<? extends T>的语法

public class RightTest
{
    //声明一个泛型方法,两个形参存在继承关系时,只需要提供一个T形参
    static <T> void test(Collection<? extends T> from,Collection<T> to)
    {
        for(T ele : from)
        {
            to.add(ele);
        }
    }
    public static void main(String[] args)
    {
        List<Object> ao=new ArrayList<>();
        List<String> as=new ArrayList<>();
        //调用方法
        test(as,ao);
    }
}
泛型方法和类型通配符的区别
//类型通配符方式
public interface Collection<E>
{
    <T> boolean containsAll(Collection<T> c);
    <T extends E> boolean addAll(Collection<T> c);
}
//泛型方法
public interface Collection<E>
{
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
}

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

Java 7 的“菱形”语法与泛型构造器

泛型构造器

class Foo
{
    public <T> Foo(T t)
    {
        System.out.println(t);
    }
}

public class GenericConstructor
{
    public static void main(Stringp[] args)
    {
        //泛型方法中的T参数是String
        new Foo("张三");
        //泛型方法中的T参数是Integer
        new Foo(10);
        //显示指定泛型构造器的T参数是String类型
        //传给构造器的实参也是String类型
        new <String> Foo("张三");
        //下面语句将出现编译错误
        new <String> Foo(10);
            
    }
}

菱形语法

class Foo<E>
{
    public <T> Foo(T t)
    {
        System.out.println(t);
    }
}

public class GenericConstructor
{
    public static void main(Stringp[] args)
    {
        //Foo类声明中的E形参是String类型
        //泛型方法中的T参数是Integer类型
        Foo<String> f1=new Foo<>(10);
        //显示指定泛型构造器中声明的T形参是Integer类型
        Foo<String> f2=new <Integer> Foo<String>(5);
        //如果显示指定泛型构造器中声明的T形参是Integer类型
        //就不能使用菱形语法,下面语句错误
        //Foo<String> f3=new <Integer> Foo<>(5);
            
    }
}
设定通配符下限

<? extends Type>,这个通配符表示他必须是Type本身,或是其父类
实现集合复制:

public class MyUtil
{
    //下面dest集合元素的类型必须与src集合元素的类型相同,或是其父类
    public static <T> T copy(Collection<? super T> dest,Collection<T> src)
    {
        T last=null;
        for(T ele : src)
        {
            last=ele;
            dest.add(ele);
        }
        return last;
    }
    public static void main(String[] args)
    {
        List<Number> ln=new ArrayList<>();
        List<Integer> li=new ArrayList<>();
        li.add(5);
        //此处可以确保最后一个被复制的类型是Integet类型
        Integer last=copy(ln,lu);
        System.out.println(ln);
    }
}

四、擦除和转换

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

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();
        //下面语句将发生编译错误
        //Integet size2=b.getSize();
    }
}

擦除

public class ErasureTest2
{
    public static void main(String[] args)
    {
        List<Integer> li=new ArrayList<>();
        li.add(5);
        li.add(6);
        List list=li;
        //下面代码引起警告,编译、运行时完全正常
        List<String> ls=list;
        //但是要访问集合里面的元素就会发生运行时异常
        System.out.println(ls.get(0));
    }
}

上面的类转换成下面的类:

public class ErasureTest2
{
    public static void main(String[] args)
    {
        List<Integer> li=new ArrayList<>();
        li.add(5);
        li.add(6);
        System.out.println((String)li.get(0));
    }
}

五、泛型与数组

Java允许创建无上限的通配符泛型数组:

List<?>[] lsa=new ArrayList<?>[10];
Object[] oa=lsa;
List<Integer> li=new ArrayList<Integer>();
li.add(new Integer(3));
oa[1]=li;
//下面代码将引发异常
String s=(String)lsa[1].get(0);

上面代码修改成如下代码

List<?>[] lsa=new ArrayList<?>[10];
Object[] oa=lsa;
List<Integer> li=new ArrayList<Integer>();
li.add(new Integer(3));
oa[1]=li;

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

推荐阅读更多精彩内容

  • 开发人员在使用泛型的时候,很容易根据自己的直觉而犯一些错误。比如一个方法如果接收List作为形式参数,那么如果尝试...
    时待吾阅读 1,042评论 0 3
  • 一、泛型简介1.引入泛型的目的 了解引入泛型的动机,就先从语法糖开始了解。 语法糖 语法糖(Syntactic S...
    Android进阶与总结阅读 1,025评论 0 9
  • 一、泛型简介 1.引入泛型的目的 了解引入泛型的动机,就先从语法糖开始了解。 语法糖 语法糖(Syntactic ...
    Ruheng阅读 4,403评论 2 50
  • 什么是泛型 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化...
    不知名的蛋挞阅读 454评论 0 1
  • 天凉好个秋,谁知霜叶落心头,思绪无痕飞细雨,早也休休,晚也休休
    君末未白阅读 232评论 0 0