Java 泛型

2018-07-23  本文已影响27人  海人为记

Java泛型(generics)提供了编译时类型安全检测机制,该机制允许程序员啊在编译时检测到非法的类型,使更多的bug在编译时期就可以被发现,为代码增加稳定性。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型的由来

Java SE 1.5之前,没有泛型的情况下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。Java语言引入泛型来解决这个安全问题。

public static void main(String[] args) {        
    List list = new ArrayList<>();
    list.add("Cat_and_Mouse");
    list.add(110);
    System.out.println(list); //正常运行
    for(int i = 0; i < list.size(); i++) {
        String name = (String) list.get(i); // 取出Integer时,运行出现异常
        System.out.println(name);
    }
}

当我们在运行如上代码的时候,会报出如下错误:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at com.day16.GenericLearn.main(GenericLearn.java:15)
这里的将数据添加到List集合中并且打印的做法是正确的,没有报错,但是在取出元素的时候报错了,报错的信息是类强制转换异常,解决这个问题,我们就要用到泛型。 编译器报错.png

泛型的好处

类型参数的命名规则:一个大写字母,最仓常用的类型参数名称如下:

泛型的使用

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。

泛型类

泛型类型用于类的定义中,被称为泛型类。在编译期,是无法知道K和V具体是什么类型,只有在运行时才会真正根据类型来构造和分配内存。
实例

public class GenericClass<K, V> {
    private K key;
    private V value;
    
    public GenericClass(K k, V v) {
        this.key = k;
        this.value = v;
    }

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }
    
    public static void main(String[] args) {
        GenericClass<String, Integer> g = new GenericClass<String, Integer>("name", 1970);
        System.out.println(g.getKey() + ":" + g.getValue()); //name:1970
        GenericClass<Integer, String> g2 = new GenericClass<>(1970, "year");
        System.out.println(g2.getKey() + ":" + g2.getValue()); //1970:year
    }
}

泛型方法

定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法.
实例

public class GenericExercise {

    public static <E> E method(E[] e) {
        return e[e.length/2];
    }

    public static void main(String[] args) {
        String[] str = {"第1个元素","第2个元素","第3个元素","第4个元素","第5个元素","第6个元素","第7个元素"};
        System.out.println(method(str)); // 第4个元素
        Integer[] in = {1,10,100,1000,10000,100000,1000000};
        System.out.println(method(in)); // 1000
        // 参数的传递不能传递基本数据类型,必须是引用数据类型
    }
}

泛型接口

泛型接口与泛型类的定义及使用基本相同.泛型接口常被用在各种类的生产器中:
实例

public interface GenericInterface<T> {
    public T next();
}

未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中. 如果不声明泛型,如:class GenericImplements implements GenericInteface<T>编译器会报错:"Unknown class"

public class GenericImplements<T> implements GenericInterface<T> {
    @Override
    public T next() {
        return null;
    }
}

传入泛型实参时:
定义一个类实现这个接口,我们只写了一个泛型接口,但是我们可以为T传入无数个实参,形成无数种类型的GeneratorInterface接口.在实现类实现泛型接口时,如已将泛型类型插入实参类型,则所有使用泛型的地方都要替换成传入的实参类型,即:GenericInterface<T>,public T next();中的T都要替换成传入的String类型

public class GenericImplements implements GenericInterface<String> {

    public String[] string = {"Apple", "Oracle","Google","Facebook","Tencent"};
    @Override
    public String next() {
        return string[2];
    }
}

泛型通配符

通配符只有在修饰一个变量时会用到,使用它可以很方便地引用包含了多种类型的泛型.主要有以下三类:

  1. 无边界的通配符(Unbounded Wildcards),就是<?>,比如List<?>.
    无边界的通配符的主要作用就是让泛型能够接受未知类型的数据.
  2. 固定上边界的通配符(Upper Bounded Wildcards)
    使用固定上边界的通配符的泛型,就能够接受指定类及其子类类型的数据,要声明使用该类通配符,采用<? extends E>的形式,这里的E就是该泛型的上边界,注意:这里虽然使用的是extends关键字,却不仅限于继承了父类E的子类,也可以代指显现了接口E的类.
  3. 固定下边界的通配符(Lower Bounded Wildcards)
    使用固定下边界的通配符的泛型,就能够接受指定类及其父类类型的数据,要声明使用该类通配符,采用<? super E>的形式,这里的E就是该泛型的下边界.

注意:你可以为一个泛型指定上边界或下边界,但是不能同时指定上下边界

基本使用方法

  1. 无边界的通配符的使用:
import java.util.ArrayList;
import java.util.List;
public class GenericExercise {
    public static void main(String[] args) {
        // 无边界的通配符的使用
        List<String> stringList = new ArrayList<>();
        stringList.add("abcd");
        stringList.add("efgh");
        stringList.add("ijkl");
        stringList.add("mnop");
        stringList.add("qrst");
        stringList.add("uvwx");
        stringList.add("yz");
        // 传入的是元素为String类型的List集合
        func(stringList);
        List<Integer> integerList = new ArrayList<>();
        integerList.add(1234);
        integerList.add(5678);
        integerList.add(90);
        // 传入的是元素为Integer类型的List集合
        func(integerList);
    }
    public static void func(List<?> list) {
        for (Object o : list) {
            System.out.println(o);
        }
         list.add("123"); // 报错
    }
}

这种使用List<?>的方式就是父类引用指向子类对象.
注意:

        list.add("abc"); //编译报错
        list.add(255); // 编译报错
        list.add(null); // 正常运行
        String s = list.get(1); // 编译报错
        Integer i = list.get(2); // 编译报错
        Object o1 = list.get(0); // 正常运行
  1. 固定上边界的通配符的使用
    利用<? extends Number>形式的通配符,可以实现泛型的向上转型
import java.util.ArrayList;
import java.util.List;

public class GenericExercise {

    public static <E> E method(E[] e) {
        return e[e.length/2];
    }

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("abcd");
        stringList.add("efgh");
        stringList.add("ijkl");
        stringList.add("mnop");
        stringList.add("qrst");
        stringList.add("uvwx");
        stringList.add("yz");
        // 传入的元素是String类型的list集合
        number(stringList); // 编译报错
        //传入的是Integer类型的list集合
        List<Integer> integerList = new ArrayList<>();
        integerList.add(1234);
        integerList.add(5678);
        integerList.add(90);
        number(integerList);
        // 传入的元素是Double类型的list集合
        List<Double> doubleList = new ArrayList<>();
        doubleList.add(12.34);
        doubleList.add(56.78);
        doubleList.add(9.0);
        number(doubleList);
    }
    // 固定上边界的通配符的使用
    public static void number(List<? extends Number> list) {
        for (Number n : list) {
            System.out.println(n);
        }
        list.add(123.3); // 报错
    }
}
  1. 固定下边界通配符的使用
import java.util.ArrayList;
import java.util.List;

public class GenericExercise {
    public static void main(String[] args) {
        // 传入的是元素为String类型的List集合
        func(stringList);
        List<Integer> integerList = new ArrayList<>();
        integerList.add(1234);
        integerList.add(5678);
        integerList.add(90);
        // 传入的元素是Double类型的list集合
        List<Double> doubleList = new ArrayList<>();
        doubleList.add(12.34);
        doubleList.add(56.78);
        doubleList.add(9.0);
        
        List<Object> objectList = new ArrayList<>();
        numbers(objectList);
        numbers(integerList);
        numbers(doubleList); // 报错,因为传入的必须是Integer类型或者是它的父类
    }
    // 固定下边界的通配符的使用
    public static void numbers(List<? super Integer> list) {
        list.add(123);
//        list.add(123.45);// 报错
    }
}
上一篇 下一篇

猜你喜欢

热点阅读