美文网首页
Java 泛型

Java 泛型

作者: 海人为记 | 来源:发表于2018-07-23 14:25 被阅读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

泛型的好处

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

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

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

泛型的使用

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

泛型类

泛型类型用于类的定义中,被称为泛型类。在编译期,是无法知道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<?>的方式就是父类引用指向子类对象.
注意:

  • 这里的func方法不能写成public static void func(List<Object> list)的形式,虽然Object类是所有类的父类,但是List<Object>和其它泛型的List如List<String>,List<Integer>不存在继承关系,因此会报错.
  • 我们不能对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. 固定上边界的通配符的使用
    利用<? 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);// 报错
    }
}
  • 无边界的通配符"<?>"不能添加元素
  • 固定上边界的"<? extends T>"不能添加元素
  • 固定下边界的"<? super T>"能添加元素

相关文章

  • Java泛型教程

    Java泛型教程导航 Java 泛型概述 Java泛型环境设置 Java泛型通用类 Java泛型类型参数命名约定 ...

  • 第二十八课:泛型

    泛型出现之前 泛型出现之后 Java深度历险(五)——Java泛型

  • Kotlin 泛型

    说起 kotlin 的泛型,就离不开 java 的泛型,首先来看下 java 的泛型,当然比较熟悉 java 泛型...

  • java泛型中类型擦除的一些思考

    java泛型 java泛型介绍 java泛型的参数只可以代表类,不能代表个别对象。由于java泛型的类型参数之实际...

  • Java泛型

    参考:Java知识点总结(Java泛型) 自定义泛型类 自定义泛型接口 非泛型类中定义泛型方法 继承泛型类 通配符...

  • Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型

    Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型 在代码进入和离开的边界处,会处理泛型 Java泛型作...

  • JAVA 核心笔记 || [xxx] 泛型

    泛型 JAVA 的参数化类型 称为 泛型 泛型类的设计 Learn12.java 运行

  • 简单回顾Java泛型之-入门介绍

    什么时候开始有了Java泛型?什么是Java泛型?为什么要引入Java泛型?什么时候用到了泛型?可不可以给泛型下一...

  • Kotlin 泛型

    Kotlin 支持泛型, 语法和 Java 类似。例如,泛型类: 泛型函数: 类型变异 Java 的泛型中,最难理...

  • JAVA-泛型

    JAVA-泛型 sschrodinger 2018/11/15 简介 泛型是Java SE 1.5的新特性,泛型的...

网友评论

      本文标题:Java 泛型

      本文链接:https://www.haomeiwen.com/subject/bynwpftx.html