Java 泛型

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

泛型的好处

  • 类型安全:泛型的主要目标是在编译的时候检查类型安全(将运行期的错误转换到编译期——泛型只在编译阶段有效)。
  • 消除了强制类型转换:所有的强制转换都是自动和隐式的,使得代码可读性好,减少了很多出错的机会
  • 在其作用域内可以统一参数类型,提高代码的重用率。

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

  • E - Element
  • K -Key
  • T -Type
  • N -Number
  • V -Value
  • S,U,V -2nd,3trd,4th

泛型的使用

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

泛型类

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

public class GenericClass {
    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 g = new GenericClass("name", 1970);
        System.out.println(g.getKey() + ":" + g.getValue()); //name:1970
        GenericClass g2 = new GenericClass<>(1970, "year");
        System.out.println(g2.getKey() + ":" + g2.getValue()); //1970:year
    }
}

泛型方法

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

public class GenericExercise {

    public static  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 {
    public T next();
}

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

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

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

public class GenericImplements implements GenericInterface {

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

泛型通配符

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

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

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

基本使用方法

  1. 无边界的通配符的使用:
import java.util.ArrayList;
import java.util.List;
public class GenericExercise {
    public static void main(String[] args) {
        // 无边界的通配符的使用
        List 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 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的方式就是父类引用指向子类对象.
注意:

  • 这里的func方法不能写成public static void func(List list)的形式,虽然Object类是所有类的父类,但是List和其它泛型的List如List,List不存在继承关系,因此会报错.
  • 我们不能对List使用add方法,但是add(null)是例外.因为我们不确定该List的类型,不知道add什么类型的数据才对,只有null是所有引用数据类型都具有的元素
  • List不能使用get方法,只有Object类型是例外.原因很简单,因为不知道要传入的List是什么类型的,所以无法接受得到get,但是Object是所有数据类型的父类,所以只有它可以
  •         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. 固定上边界的通配符的使用
      利用形式的通配符,可以实现泛型的向上转型
    import java.util.ArrayList;
    import java.util.List;
    
    public class GenericExercise {
    
        public static  E method(E[] e) {
            return e[e.length/2];
        }
    
        public static void main(String[] args) {
            List 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 integerList = new ArrayList<>();
            integerList.add(1234);
            integerList.add(5678);
            integerList.add(90);
            number(integerList);
            // 传入的元素是Double类型的list集合
            List doubleList = new ArrayList<>();
            doubleList.add(12.34);
            doubleList.add(56.78);
            doubleList.add(9.0);
            number(doubleList);
        }
        // 固定上边界的通配符的使用
        public static void number(List 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 integerList = new ArrayList<>();
            integerList.add(1234);
            integerList.add(5678);
            integerList.add(90);
            // 传入的元素是Double类型的list集合
            List doubleList = new ArrayList<>();
            doubleList.add(12.34);
            doubleList.add(56.78);
            doubleList.add(9.0);
            
            List objectList = new ArrayList<>();
            numbers(objectList);
            numbers(integerList);
            numbers(doubleList); // 报错,因为传入的必须是Integer类型或者是它的父类
        }
        // 固定下边界的通配符的使用
        public static void numbers(List list) {
            list.add(123);
    //        list.add(123.45);// 报错
        }
    }
     
     
    • 无边界的通配符""不能添加元素
    • 固定上边界的""不能添加元素
    • 固定下边界的""能添加元素

    你可能感兴趣的:(Java 泛型)