java中的泛型

要解决的问题
Java集合有个缺点,就是把一个对象存入集合之后,集合就会忘记这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了Object类型(运行内存不变),这时候就需要进行类型向下转换,这个问题会使代码看起来烦琐,并且容易引起ClassCastExeception异常。
方法
为了解决上面的问题,java增加了泛型来支持集合,增加了泛型后的集合,完全可以记住集合中对象的类型,并可以在编译时检查集合中的元素类型。
使用泛型
java5之后,java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型,这种参数化类型被称为泛型

public class GenerList {
    public static void main(String[] args) {
        //创建一个只想保存字符串的List集合
        ArrayList<String> strList = new ArrayList<>();
        strList.add("java");
        strList.add("python");
        //由于已经使用泛型来指定了集合存储对象的类型为字符串,所以不能在存储数值型
        strList.add(6);
    }
}

如果想要存储进去就必须进行类型转换

 strList.add(String.valueOf(6));

以为集合只能记住String类型的对象。

深入泛型

所谓泛型就是允许在定义类、接口、方法时使用类型参数,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。

定义泛型接口、类

public interface List<E> {
    //在该接口里,E作为类型使用
    //下面的方法可使用E作为参数类型
    void add(E x);
    demo1.Iterator<E> iterator();
}
public interface Iterator<E> {
    //在该接口里完全可以作为类型使用
    E next();
    boolean hasNext();
}

泛型的实质
允许在定义接口、类时声明类型参数,类型参数在整个接口、类体内可当类型使用,几乎所有可使用普通类型的地方都可以使用这种类型参数。

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

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

//定义类A继承Apple类,Apple类不能跟类型形参
public class A extends Apple<T>()

这种是错误的,和使用方法一样,使用定义好的方法的时候不能再传入形参,必须传入实参让方法使用。所以应该把T换成一个具体的实际的类型,比如String或者自己定义的类型。
像这样:

public class A extends Apple<String>()

需要注意的是并不存在泛型类,不管泛型的类型形参传入哪一种类型实参,对于java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间。

类型通配符

如果Foo是Bar的一个子类型,而G是具有泛型声明的类或接口,G<Foo>并不是G<Foo>的子类型。
使用类型通配符
为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),写作:List<?>意思是元素类型未知的List。这个问号被称为通配符,它的元素类型可以匹配任何类型。

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

上面程序中的List是一个有泛型声明的接口,此处使用List接口时没有传入实际类型参数,这将引起泛型警告。为此考虑为List接口传入实际的类型参数。

import java.util.ArrayList;
import java.util.List;

public class Fanxing {
    public static void main(String[] args) {
        ArrayList<String> str = new ArrayList<>();
        test(str);
        //Error:(11, 14) java: 不兼容的类型: java.util.ArrayList<java.lang.String>
        // 无法转换为java.util.List<java.lang.Object>
    }
    public static void test(List<Object> c){
        for (int i = 0; i <c.size() ; i++) {
            System.out.println(c.get(i));
        }
    }
}

上面的程序出现了编译错误,这说明了List<String>不是List<Object>类的子类。
使用通配符:

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

?可以匹配到任何类型类型。但这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中。比如下面的程序会引起编译错误:

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

因为程序无法确定c集合中元素的类型,所以不能向其中添加对象。

设定类型通配符的上限

当使用List<?>这种形式时,即表明List集合可以使任何泛型List的父类,但还有一种特殊情况,程序不希望这个List<?>是任何List的父类,只希望他代表某一泛型List的父类。


public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    @Override
    public void draw(Canvas c) {
        System.out.println("在画布"+c+"上画一个圆");
    }
}
package demo1;


public class Rectangle extends Shape {
    @Override
    public void draw(Canvas c) {
        System.out.println("把矩形画在"+c+"画布上");
    }
}

上面定义了三个类,其中Shape是一个抽象父类,该抽象父类有两个子类:Circle和Rectangle。

public class Canvas {
    public void drawAll(List<Shape> shapes){
        for (Shape s : shapes) {
            s.draw(this);
        }
    }
}

将格式改成这样就相当于List<? extends Shape> shapes表示了List<Circle>、List<Rectangle>的父类。这里的?是未知类型,但它一定是Shape的子类型。

    public void drawAll(List<? extends Shape> shapes){
        for (Shape s : shapes) {
            s.draw(this);
        }
    }

设定类型形参的上限
不仅可以在通配符形参时设定上限,还可以在定义类型形参时设定上限用于表示传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。

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

表明T类型必须是Number类或其子类,并必须实现java.io.Serailizable接口。

泛型方法

定义泛型方法
该方法负责将一个Object数组的所有元素添加到Collection集合中。

    static void fromArrayToCollection(Object[] a, Collection<Object> c){
        for (Object o:a){
            c.add(o);
        }
    }

上面代码只能将Object[]数组的元素复制到集合里。
为了解决上面的这个问题,java5提供的泛型方法,就是在声明方法时定义一个或多个类型形参。语法如下:

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

可以看到上面的方法签名比普通方法多了类型形参声明。具体实现如下:

public class FanXing01 {
    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);
    }
}

与类、接口中使用的泛型参数不同,方法中的泛型参数无需显示传入实际类型参数,因为编译器会根据实参推断类型参数的值。

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

  • 泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的依赖关系,如果没有这种关系,就不应该使用泛型方法。
  • 通配符是被设计用来支持灵活的子类化的。
  • 类型通配符既可以在方法签名中定义形参类型,也可以用来定义变量的类型,但泛型方法必须在对应方法中显式声明。

设定通配下限符
实现将src集合里的元素复制到dest集合里的功能,并返回最后一个元素:

class Test01 {
    public static <T> T copy(Collection<T> dest,Collection<? extends T> src){
        T last=null;
        for(T ele:src){
            last=ele;
            dest.add(ele);
        }
        return last;
    }

    public static void main(String[] args) {
        ArrayList<Number> ln = new ArrayList<>();
        ArrayList<Integer> li = new ArrayList<>();
        Integer last=copy(ln,li);
    }
}

运行结果,引起编译错误
在这里插入图片描述

原因:
ln的类型是List<Number>,与copy签名的形参类型进行对比得到T的实际类型为Number,而方法的最终返回值类型也是Number,但是从src中取出的元素的类型是Integer。
对于上面的copy方法,可以这样理解两个集合之间的依赖关系:不管src集合元素的类型是什么,只要dest集合元素的类型与前者相同或是其父类即可。java允许设定通配符下限:<? super Type>,这个通配符表示是Type本身或其父类。
改写上面的程序:

    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;
    }

这样copy返回的值类型就和src元素类型一致了。

java8改进的类型推断

  • 可通过调用方法的上下文来判断类型参数的目标类型;
  • 可在方法调用链中,将判断得到的类型参数传递到最后一个方法;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值