Java泛型

Java泛型

概述

泛型(Generic)是Java 5引入的新特性,用于解决数据类型的安全性问题,其中一种是在类声明通过一个标识符表示某个属性、方法以及参数类型,在声明时指定具体类型即可,避免了ClassCastException的异常和大量的类型转换,此外它还可以用于方法,接口上。

下面是常用的泛型通配符,这些都是约定好的字母标识,用于增加代码可读性,当然也可以是任意字母。

  • E - Element (在集合中使用,因为集合中存放的是元素),E是对各方法中的泛型类型进行限制,以保证同一个对象调用不同的方法时,操作的类型必定是相同的。E可以用其它任意字母代替。
  • T - Type(Java 类),T代表在调用时的指定类型,会进行类型推断。
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • ?- 表示不确定的类型,是类型通配符,代表所有类型,不会进行类型推断。

它只在编译阶段有效,在检验泛型结果后,将泛型信息擦除,它不会进入到运行阶段。

泛型类

泛型类的简单用法:

public class Test <T>{
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}
Test<String> test = new Test<>();
test.setValue("hello world");
System.out.println(test.getValue());

泛型类和普通类的区别就是类名后有类型参数列表 <T>,既然叫“列表”了,当然这里的类型参数可以有多个,比如 public class HashMap<K, V>

泛型接口

它和定义泛型类类似:

public interface Test <T>{
    void printValue(T t);
}
((Test<String>) System.out::println).printValue("hello world");

如果实现接口的类是一个泛型类,那么该类必须和接口泛型相同。

泛型方法

在函数返回值前加入泛型声明,并可以在任何位置使用泛型,不管所在类是否是泛型化的。下面是几个例子:

public class Test <T>{
    private T t;
    public <E> E fun(E e){
        //类所定义的泛型可在任何方法中使用
        System.out.println(t.equals(e));
        return e;
    }
    
    public <E> void forEach(E ... es){
        for (E e : es) {
            System.out.println(es);
        }
    }

    //静态方法不可使用实体类所定义的泛型
    public static <E> void staticFun(E t){
        System.out.println(t);
    }
}
public class Main {
    public static void main(String[] args) {
        //泛型函数的泛型类型取决于传递参数的类型,在调用时才确定具体的类型。
        Test.staticFun("hello");
        Test.staticFun(123);
        Test<Integer> test = new Test<>();
        test.fun("s");
    }
}

泛型通配符

?用于表示不确定的泛型类型,要使用泛型,但是不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 <?> ),表示可以持有任何类型,通常在变量赋值或变量声明时候使用, ?Object 不一样,List<?> 表示未知类型的列表,而 List<Object> 表示任意类型的列表,下面是一个例子:

public class Test <T>{
    public static void fun(List<?> t){
        System.out.println(t);
    }
}
public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        Test.fun(list);
    }
}

有界类型

当我们确定好泛型后,如果将其子类传入泛型类型,将编译不能通过,下面方法使泛型可以接受具有继承关系的类型

描述用途
<? extends E>泛型必须是E的子类
<? super E>泛型必须是E的超类
<? extends Interface>泛型必须是实现Interface接口的类
public <K extends ChildClass, V extends SuperClass> V test2(K arg1, V arg2){
    V temp = arg2;
    temp.compareTo(arg1);
    return temp;
}
public <E> void add(List<? super E> dst, List<E> src){
    for (E e : src) {
        dst.add(e);
    }
}

泛型擦除

在 Java 中,泛型是 Java 编译器的概念,用泛型编写的 Java 程序和普通的 Java 程序基本相同,只是多了一些参数化的类型同时少了一些类型转换,实际上泛型程序也是首先被转化成一般的、不带泛型的 Java 程序后再进行处理的,编译器自动完成了从 Generic Java 到普通 Java 的翻译,Java 虚拟机运行时对泛型基本一无所知。

当编译器对带有泛型的 Java 代码进行编译时,它会去执行类型检查类型推断,然后生成普通的不带泛型的字节码,这种普通的字节码可以被一般的 Java 虚拟机接收并执行,这在就叫做 类型擦除(type erasure)

实际上无论你是否使用泛型,集合框架中存放对象的数据类型都是 Object

List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass() == integers.getClass());//true

泛型中没有逻辑上的父子关系,如 List 并不是 List 的父类。两者擦除之后都是List,所以形如下面的代码,编译器会报错:

void method(List<Object> numbers) {
}

void method(List<String> strings) {		//error
}

泛型的这种情况称为 不可变性,与之对应的概念是 协变、逆变:

  • 协变:如果 A 是 B 的父类,并且 A 的容器(比如 List< A>) 也是 B 的容器(List< B>)的父类,则称之为协变的(父子关系保持一致)
  • 逆变:如果 A 是 B 的父类,但是 A 的容器 是 B 的容器的子类,则称之为逆变(放入容器就篡位了)
  • 不可变:不论 A B 有什么关系,A 的容器和 B 的容器都没有父子关系,称之为不可变

Java 中数组是协变的,泛型是不可变的。

如果想要让某个泛型类具有协变性,就需要用到边界。

边界

泛型运行时被擦除成原始类型,这使得很多操作无法进行,如果没有指明边界,类型参数将被擦除为 Object。

如果我们想要让参数保留一个边界,可以给参数设置一个边界,泛型参数将会被擦除到它的第一个边界(边界可以有多个),这样即使运行时擦除后也会有范围。

比如:

public class GenericErasure {
    interface Game {
        void play();
    }
    interface Program{
        void code();
    }

    public static class People<T extends Program & Game>{
        private T mPeople;

        public People(T people){
            mPeople = people;
        }

        public void habit(){
            mPeople.code();
            mPeople.play();
        }
    }
}

上述代码中,People 的类型参数 T 有两个边界,编译器事实上会把类型参数替换为它的第一个边界的类型。

#END

  • 0
    点赞
  • 5
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论 5 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页

打赏作者

'Best Wishes

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值