美文网首页
Java 泛型

Java 泛型

作者: 滨岩 | 来源:发表于2020-02-09 16:50 被阅读0次

泛型概述

泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。
什么是泛型?为什么要使用泛型?

《Java 核心技术》中对泛型的定义是:“泛型” 意味着编写的代码可以被不同类型的对象所重用。

Java泛型的内部原理:
Java有Java编译器和Java虚拟机,编译器将Java源代码转换为.class文件,虚拟机加载并运行.class文件。对于泛型类,Java编译器会将泛型代码转换为普通的非泛型代码,就像的普通ArrayList<T>类代码及其使用代码一样,将类型参数T擦除,替换为Object,插入必要的强制类型转换。Java虚拟机实际执行的时候,它是不知道泛型这回事的,它只知道普通的类及代码。

再强调一下,Java泛型是通过擦除实现的,类定义中的类型参数如T会被替换为Object,在程序运行过程中,不知道泛型的实际类型参数,比如ArrayList<Integer>,运行中只知道ArrayList,而不知道Integer,认识到这一点是非常重要的,它有助于我们理解Java泛型的很多限制。

值得注意一点,并不是所有的擦除都是T会被替换为Object,Java中还支持多个上界,多个上界之间以&分隔,类似这样:

T extends Base&Comparable&Seiralizable

Base 为上界类,Comparable和Serializable为上界接口,如果有上界类,类应该放在第一个,类型擦除时,会用第一个上界Base替换T

指定边界后,类型擦除时就不会转换为Object了,而是会转换为它的边界类型。

所以有时候,我们说Java的泛型其实是假泛型,或者叫擦除泛型(java),和C++的真泛型或者模版泛型是有区别的。

Java为什么要这么设计呢?泛型是Java 1.5以后才支持的,这么设计是为了兼容性而不得已的一个选择。

例子

    public static void main(String[] args) {

        ArrayList arrayList=new ArrayList();

        arrayList.add(1);
        arrayList.add(2);
        arrayList.add("泛型测试");
        
        
        for(Object item:arrayList){
            String t=(String)item;
            System.out.println(t);
        }
    }

ArrayList 可以存放任意类型,例子中添加了Integer类型,也添加了String类型,使用时都以String的方式使用,因此程序崩溃了。为了解决这个问题,泛型应运而生,在编译阶段就可以解决。

    public static void main(String[] args) {

        ArrayList<String> arrayList=new ArrayList();

        arrayList.add(1);  //编译器直接报错
        arrayList.add(2); //编译器直接报错
        arrayList.add("泛型测试");


        for(Object item:arrayList){
            String t=(String)item;
            System.out.println(t);
        }
    }

我们将ArrayList声明初始化list的代码更改一下 ArrayList<String> ,编译器会在编译阶段就能够帮我们发现类似这样的问题。

泛型的使用

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

泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

泛型类的最基本写法

class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
  private 泛型标识 /*(成员变量类型)*/ var; 
  .....

  }
}

例子:

//此处E可以随便写为任意的标识,比如T、E、K、V 等形式的参数,常用于表示泛型
//实例化泛型类时,必须制定E 的具体类型
public class GenericDemo<E> {
    
    private E  val;
    
    
    //泛型构造方法形参key的类型也为E,E的类型由外部指定
    public GenericDemo(E val){
        this.val=val;
    }


    //泛型方法getKey的返回值类型为E,E的类型由外部指定
    public E getVal(){
        return val;
    }
}
    public static void main(String[] args) {
        GenericDemo<Integer> integerGenericDemo=new GenericDemo<Integer>(123456789);
        GenericDemo<String> stringGenericDemo=new GenericDemo<String>("泛型测试");

        //也可以不指定具体类型 相当于GenericDemo<Object>
        GenericDemo generic1 = new GenericDemo("111111");
        GenericDemo generic2 = new GenericDemo(false);


        GenericDemo<Object> objectGenericDemo=new GenericDemo<>("tttt");


        System.out.println(objectGenericDemo.getVal());
        System.out.println(objectGenericDemo.getClass().equals(integerGenericDemo.getClass()));

    }

注意:

1、泛型的类型参数只能是类类型,不能是简单类型。
2、不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

        if(integerGenericDemo instanceof GenericDemo<Integer> ){

        }

泛型接口

泛型接口和泛型类一样,泛型接口在接口名后添加类型参数,比如下面的GenericInterface<T>,接口声明类型后,接口方法就可以直接使用这个类型。
实现类在实现泛型接口时需要指明具体的参数类型,不然默认类型是Object,这就失去了泛型接口的意义。

//定义一个泛型接口
public interface GenericInterface<T> {
    T getEntity();
}

未指明类型的实现:

public class GenericIImpl implements GenericInterface {
    @Override
    public Object getEntity() {
        return null;
    }
}

指明了类型的实现:

public class GenericStringImpl implements  GenericInterface<String> {

    @Override
    public String getEntity() {
        return "我是泛型";
    }
}
public class GenericInterImpl implements GenericInterface<Integer> {
    @Override
    public Integer getEntity() {
        return 100;
    }
}

泛型方法

泛型类,是在实例化类的时候指明泛型的具体类型,泛型方法,是在调用方法的时候指明泛型的具体类型。

泛型类中的泛型方法

//这个类是个泛型类
public class GenericMethod<T>{
    private T val;

    public GenericMethod(T val){
        this.val=val;
    }

    //虽然在方法中使用了泛型,但是这个并不是一个泛型方法
    //这只是类中的一个普通成员方法,只不过它的返回值是在声明泛型类已经声明过的泛型
    //所以在这个方法中才可以继续使用T 这个泛型
    public T getVal(){
        return val;
    }

    /**
     * 编译器会报错  “cannot reslove symbol E”
     *  因为在类的声明中并未声明泛型E ,所以在使用E做形参和返回值类型时,编译器无法识别
     */
//        public E setVal(E val){
//            this.val=val;
//        }


    /**
     *  真正的泛型方法
     *  首先在public 与返回值之间的<E> 必不可少,这表明这是一个泛型方法,并且声明了一个泛型E
     *  这个E可以出现在这个泛型方法的任意位置
     *  泛型的数量也可以任意多个
     *  如 public <T,K> K showKeyName(Generic<T> container){...}
     *
     *
     */

    public <E> E showValName(GenericMethod<E> container){
        E e=container.getVal();
        return e;
    }



    //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
    public void showValName2(GenericMethod<Number> obj){

    }

    //静态方法不能使用泛型
//    public static T getStaticVal(){
//        return  new GenericMethod<Integer>(222).getVal();
//    }

}

调用方法

public class GenericMethodMain {
    public static void main(String[] args) {
        GenericMethod<Integer> genericMethod = new GenericMethod(123456);
        Integer t = genericMethod.<Integer>showValName(genericMethod);
        System.out.println(t);
    }
}

普通类中的泛型方法

public class GenericMethodNT {

    public  <T> void copy(List<T> source, List<T> dest,T t) {
        System.out.println("普通类的泛型方法!");
    }
    
    public static void main(String[] args) {

        GenericMethodNT genericMethodNT=new GenericMethodNT();
        genericMethodNT.<String>copy(new ArrayList<>(),new ArrayList<>(),"泛型方法测试");
    }
}

泛型的通配符

有时候希望传入的类型有一个指定的范围,从而可以进行一些特定的操作,这时候通配符边界登场了。

泛型中有三种通配符形式:
1.<?> 无限制通配符
2.<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
3.<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

看个例子:

public class GenericExtend<E> {

    public void add(E e) {
        System.out.println("GenericExtend.add");
    }
    
    public void addAll(GenericExtend<E> genericExtend){
        System.out.println("GenericExtend.addAll");
    }

    public <T extends E> void addAllExtend(GenericExtend<T> genericExtend) {
        System.out.println("GenericExtend.addAllExtend");
    }
}

下面我们操作一下:

    public static void main(String[] args) {
        GenericExtend<Number> numberGenericExtend = new GenericExtend<>();
        GenericExtend<Integer> integerGenericExtend = new GenericExtend<>();
        integerGenericExtend.add(100);
        integerGenericExtend.add(200);
        //numberGenericExtend.addAll(integerGenericExtend);  //会报错 
        numberGenericExtend.addAllExtend(integerGenericExtend); //正确
    }

我们看到numberGenericExtend.addAll(integerGenericExtend); 会报错!
这行代码上提示编译错误,提示,addAll需要的参数类型为GenericExtend<Number>,而传递过来的参数类型为GenericExtend<Integer>,不适用,Integer是Number的子类,怎么会不适用呢?

再看下面的例子:

        GenericExtend<Integer> integerGenericExtend1 = new GenericExtend<>();
       //假设下面这行是合法的
        GenericExtend<Number> numberGenericExtend1 = integerGenericExtend1;

        numberGenericExtend1.add(new Double(12.34));

这时,GenericExtend<Integer>中就会出现Double类型的值,而这,显然就破坏了Java泛型关于类型安全的保证。

虽然Integer是Number的子类,但GenericExtend<Integer>并不是GenericExtend<Number>的子类,GenericExtend<Integer>的对象也不能赋值给GenericExtend<Number>的变量。

将Integer添加到Number容器中其实是很合理,那我们怎么解决这个问题呢?调用addAllExtend方法来解决这个问题

    public <T extends E> void addAllExtend(GenericExtend<T> genericExtend) {
        System.out.println("GenericExtend.addAllExtend");
    }

这个写法有点啰嗦,它可以替换为更为简洁的通配符形式:

    public  void addAllExtendSimple(GenericExtend<? extends  E> genericExtend) {
        System.out.println("GenericExtend.addAllExtendSimple");
    }

这个方法没有定义类型参数,genericExtend的类型是GenericExtend<? extends E>,?表示通配符,<? extends E>表示有限定通配符,匹配E或E的某个子类型,具体什么子类型,我们不知道

<T extends E>与<? extends E>

那么问题来了,同样是extends关键字,同样应用于泛型,<T extends E>和<? extends E>到底有什么关系?

它们用的地方不一样,我们解释一下:

<T extends E>用于定义类型参数,它声明了一个类型参数T,可放在泛型类定义中类名后面、泛型方法返回值前面。

<? extends E>用于实例化类型参数,它用于实例化泛型变量中的类型参数,只是这个具体类型是未知的,只知道它是E或E的某个子类型。

虽然它们不一样,但两种写法经常可以达成相同目标,比如,前面例子中,下面两种写法都可以:

public void addAll(GenericExtend<? extends E> c)

public <T extends E> void addAll(GenericExtend<T> c)

相关文章

  • 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/jlcnxhtx.html