JAVA SE

范型

一次问题引发的学习
范型的错误使用
范型很大程度帮助集合,JAVA SE 1.5开始引入,之前的集合不记得元素类型。这样的会引发的问题是:

  1. 一个全是String元素的集合,我可以放入Integer元素,健壮性不行
  2. 取出元素后需要类型转换,代码臃肿,并且转换时容易引发异常

List<String>可以理解为ListString,看作List的子类型

List是范型接口,T是类型参数形参,创建对象时候,必须传入类型参数实参

声明类型,创建对象,都需要传入类型参数实参。构造函数还是一样的,并没有<>,但是调用的时候,需要加上<>,JAVA SE 7支持菱形语法了。

  • 范型(类型参数)
  • 范型声明可以用在 接口 方法
  • 当在类上使用范型声明后,类里面可以把这个类型参数当作普通的类型使用
public class Apple<T>{
  T info;
	
  public Apple(T info){
    this.info = info;
  }

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

  public T getInfo(){
    return info;
  }
}

继承或者实现一个范型声明的类或者接口时,需要指定类型参数的实参。

  • 错误写法
public class A extends Apple<T>
  • 正确写法
public class A extends Apple<String>

这个时候,覆盖父类的正确写法是

public String getInfo(){
  return "子类: " + super.getInfo();
}
/**
* public Object getInfo(){
*   return "子类:" + super.getInfo();
* }
*/

如果写成下面的,这个时候,T被当作Object。

public class A extends Apple

覆盖父类的写法应该是

public String getInfo(){
  return "子类: " + super.getInfo().toString();
}

无论传入的类型实参是什么,范型类是一种类型,在内存空间中只占一块地方

List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
a.getClass() == b.getClass() //true

所以不能用类型参数来声明静态变量,不能用在静态方法的参数声明中使用。

T info;
//static T info;  错误写法
public void setInfo(T info){
  this.info = info;
}
//public static void setInfo(T info){ 错误写法
  //this.info = info;
//}

instanceof后面的错误写法

a instanceof java.util.ArrayList<String>

Foo是Bar的子类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型,但是G<Foo>不是G<Bar>的子类型。

以下代码的问题是会引起范型警告

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

下面代码的问题是无法传入List<String>类型的对象,因为List<String>不是List<Object>的子类

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

看一下数组,不存在如上问题,其实是早期设计的缺陷

public class ArrayErr {
    public static void main(String[] args) {
        Integer[] ia = new Integer[5];
        Number[] na = ia;
        //ia[0] = 0.5; //编译报错
        na[0] = 0.5; //编译正常 运行时报错 java.lang.ArrayStoreException 不安全的设计
    }
}

范型设计时进行了改进,如下代码导致编译错误。

List<Integer> iList = new ArrayList<Integer>();
List<Number> nList = iList; //编译错误

使用类型通配符List<?>,表示所有List范型类的父类

List<?> c = new ArrayList<>();
c.add(new Object()); //引起编译错误 因为不知道c集合中元素的类型 唯一例外的是null,null是所有引用类型的实例。

可以get元素,拿出来的是Object。
Shape是父类,有抽象方法,Rectangle和Circle是子类。
如果使用不加限制的通配符?,那么取出元素后,都得转换成Shape再调用draw

public class Canvas {
    public void drawAll(List<?> shapes) {
        for (Object object : shapes) {
            Shape shape = (Shape) object;
            shape.draw(this);
        }
    }

使用带限制的类型通配符,那么<? extends Shape>,取出来之后直接调用draw。

  • 范型声明也是可以加类型上限的

范型方法声明

修饰符 <参数类型> 返回类型 方法名(参数列表)

static <T> void copyArrayToCollection(T[] a, Collection<T> c);

Collection<T>类型T需要完全吻合,T[]可以传入T的子类。

public class ErrorTest {
    static <T> void test(Collection<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 class RightTest {
    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>
{
    //类型通配符写法
    boolean containAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);

    //范型方法写法
    <T> boolean containsAll(Collection<T> c);
    <T extends E> boolean addAll(Collection<T> c);
}

什么时候使用范型方法而不用通配符?

形参a或返回值的类型依赖于形参b的类型 b的声明不应该使用通配符

java 的Collections.copy()同时使用范型方法和通配符

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

范型构造器

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

public class GenericConstructor {
    public static void main(String[] args) {
        new Foo("疯狂Java讲义");
        new Foo(200);

        //显式指定
        new <String>Foo("疯狂Java讲义");
        //显式指定String 传入Double 出错
        //new <String> Foo(12.3);
    }
}
class MyClass<E> {
    public <T> MyClass(T t) {
        System.out.println("t参数的值为: " + t);
    }
}

public class GenericDiamondTest {
    public static void main(String[] args) {
        MyClass<String> mc1 = new MyClass<>(5);
        MyClass<String> mc2 = new <Integer> MyClass<String>(5);
        //不能使用菱形语法
        //MyClass<String> mc3 = new <Integer> MyClass<>(5);
    }
}

从src集合往dest集合copy元素,src中的元素类型是dest元素类型的子类,这个时候如果要求返回最后加入的元素,使用通配符上限的方法,会造成类型丢失。
使用通配符下限的方式改写copy方法

public class MyUtils {
    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);
        Integer last = copy(ln, li); //准确知道最后一个被复制的元素是Integer类型
    }
}

java集合框架中使用通配符下限的例子

TreeSet(Comparator<? super E> c)

public interface Comparator<T>{
    int compare(T fst, T snd);
}

方法重载

public static <T> void copy(Collection<T> dest, Collection<? extends T> src){...}
public static <T> T copy(Collection<? super T> dest, Collection<T> src) {...}

List<Number> ln = new ArrayList<>();
List<Integer> ln = new ArrayList<>();
copy(ln, li); //编译错误 分不清调用哪个方法

java8增强的范型推断能力

class MyUtil<E> {

    public static <Z> MyUtil<Z> nil() {
        return null;
    }

    public static <Z> MyUtil<Z> cons(Z head, MyUtil<Z> tail) {
        return null;
    }

    E head() {
        return null;
    }
}

public class InferenceTest {
    public static void main(String[] args) {
        MyUtil<String> ls = MyUtil.nil(); //通过赋值的目标参数推断类型参数为String
        MyUtil<String> mu = MyUtil.<String>nil(); //显式指定
        MyUtil.cons(42, MyUtil.nil()); //根据cons方法所需的参数类型来推断
        MyUtil.cons(42, MyUtil.<Integer>nil());//显式指定
        //类型推断不是万能的 编译错误
        //String s = MyUtil.nil().head();//为什么错?答:万一String是Z的父类呢
        String s = MyUtil.<String>nil().head();
    }
}
  • 类型擦除和转换
class Apple<T extends Number>{
    T size;
    public Apple(){}
    public Apple(T size){
        this.size = size;
    }
    public T getSize() {
        return size;
    }
    public void setSize(T size) {
        this.size = size;
    }
}

public class ErasureTest {
    public static void main(String[] args) {
        Apple<Integer> a = new Apple<>(6);
        Integer as = a.getSize();
        //尖括号的类型信息丢失
        Apple b = a;
        Number size1 = b.getSize();
        //编译出错
        //Integer size2 = b.getSize();
    }
}
//引发ClassCastException异常
public class ErasureTest2 {
    public static void main(String[] args) {

        Object o = 6;
        //System.out.println((String)o);

        

        List list1 = new ArrayList();
        list1.add(6);
        list1.add(9);
        //System.out.println((String)list.get(0));

        /

        List<Integer> li = new ArrayList<>();
        li.add(6);
        li.add(9);
        List list = li;
        //下面代码引起"未经检查的转换"
        List<String> ls = list;
        //只要访问ls里面的元素 将引发ClassCastException
        //System.out.println(ls.get(0));
    }
}

范型设计的重要原则

一段代码在编译时没有提出[unchecked] 未经检查的转换警告,则运行时不会引发ClassCastException异常。

  • 允许声明List<String>[],不能创建ArrayList<String>[]
public class GenericArrayTest {
    public static void main(String[] args) {
        //下面代码实际上是不允许的
        List<String>[] lsa = new List<String>[10];
        //将lsa向上转型为Object[]类型的变量
        Object[] oa = (Object[]) lsa;
        List<Integer> li = new ArrayList<>();
        li.add(3);
        //将List<Integer>对象作为oa的第二个元素 代码没有任何警告
        oa[1] = li;
        //下面代码不会有任何警告 但将引发ClassCastException
        lsa[1].get(0); //违背java范型设计原则
    }
}
        //下面代码编译时有警告
        List<String>[] lsa = new ArrayList[10];
        Object[] oa = lsa;
        List<Integer> li = new ArrayList<>();
        li.add(new Integer(3));
        oa[1] = li;
        String s = lsa[1].get(0); //ClassCastException异常

java允许创建无上限的通配符范型数组,如new ArrayList<?>[10]

        //没有unchecked警告
        List<?>[] lsa = new ArrayList<?>[10];
        List<Integer> li = new ArrayList<>();
        li.add(3);
        //lsa[1] = li; //List<?>使用通配符不能加向其中添加元素 这块为什么可以? List<Integer>是List<?>子类
        Object[] oa = lsa;
        oa[1] = li;
        //String s = (String)lsa[1].get(0); //ClassCastException
        //这个错误类似与 Object a = 6; String s = (String) a;类似 不是范型的原因
        //程序应该通过instancof运算符保证数据类型
        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()];
}

类型变量在运行时并不存在,编译器无法确定实际类型!!!
-Xlint:unchecked打印范型警告详细信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值