泛型是什么?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

在集合中存储对象并在使用前进行类型转换是多么的不方便。泛型防止了那种情况的发生。它提供了编译期的类型安全,确保你只能把正确类型的对象放入 集合中,避免了在运行时出现ClassCastException。

优点

泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时刻类型错误,因为编译器可以在编译时刻就发现很多明显的错误。

类型擦除

泛型实在编译器层次来实现的

在生成的java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉,这个就叫类型擦除。

如在代码中定义的List(Object)和List(String)等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的,java编译器会在编译是尽可能发现出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。

泛型的奇怪特性都与类型擦除有关

  1. 泛型类并没有自己独有的Class对象,比如并不存在List(String).class,z只有List.class
  2. 静态变量是被泛型类的所有实例被共享的。对于声明为MyClass(T)的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass(String)还是new MyClass(Integer)创建的对象,都是共享一个静态变量。
  3. 泛型的类型参数不能用在java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException(String)和MyException(Integer)的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。

类型擦除过程

找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用上界。把代码中的类型参数都替换成具体类。同时去掉同时出现的类型声明,即去掉<>的内容。比如Tget()方法声明就变成了Object get();List(String)就变成了List。接下来就可能需要生辰改一些桥接方法。就是由于擦除了类型之后的类可能缺少默写某些必须的方法。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。

实例分析

了解了类型擦除机制之后,就会明白编译器承担了全部的类型检查工作。编译器禁止某些泛型的使用方式,正是为了确保类型的安全性。以上面提到的List(Object)和List(String)为例来具体分析:

public void insert(List(Object) list) {    for (Object obj : list) {        System.out.println(obj);    }    list.add(1); //这个操作在当前方法的上下文是合法的。
}
public void test() {    List(String) strs = new ArrayList(String)();    inspect(strs); //编译错误
}复制代码

这段代码中,insert方法接受List(Object)作为参数,当在test方法中试图传入List(String)的时候,会出现编译错误。假设这样的做法是允许的,那么在insert方法就可以通过list.add(1)来向集合中添加一个数字。这样在test方法看来,其声明为List(String)的集合中却被添加了一个Integer类型的对象。这显然是违反类型安全的原则的,在某个时候肯定会抛出ClassCastException。因此,编译器禁止这样的行为。编译器会尽可能的检查可能存在的类型安全问题。对于确定是违反相关原则的地方,会给出编译错误。当编译器无法判断类型的使用是否正确的时候,会给出警告信息。

泛型类和泛型方法

泛型类

public class Som<T> {private T value;public T getValue() {return value;}public void setValue(T value) {this.value = value;}
}
复制代码

Som就是一个泛型类,value的类型是T,而T是参数化的。如果有多个类型参数,使用分号隔开,如<U,V>。在编译期,是无法知道U和V具体是什么类型,只有在运行时才会真正根据类型来构造和分配内存。

public class Main {public static void main(String[] args) {Son<String, String> c1 = new Son<String, String>("name", "findingsea");Son<String, Integer> c2 = new Son<String, Integer>("age", 24);Son<Double, Double> c3 = new Son<Double, Double>(1.1, 2.2);System.out.println(c1.getKey() + " : " + c1.getValue());System.out.println(c2.getKey() + " : " + c2.getValue());System.out.println(c3.getKey() + " : " + c3.getValue());}
}输出:name : findingsea
age : 24
1.1 : 2.2复制代码

可以看一下现在Som类对于不同类型的支持情况:
使用泛型类:

Som<String> som = new Som<>();
som.setValue("Hi");
//som.setValue(123);编译不通过
String str = som.getValue();
复制代码

在使用中指定具体的类型实参。

泛型接口

public interface Generator<T> {public T next();
}
然后定义一个生成器类来实现这个接口:public class FruitGenerator implements Generator<String> {private String[] fruits = new String[]{"Apple", "Banana", "Pear"};@Overridepublic String next() {Random rand = new Random();return fruits[rand.nextInt(3)];}
}
调用:public class Main {public static void main(String[] args) {FruitGenerator generator = new FruitGenerator();System.out.println(generator.next());System.out.println(generator.next());System.out.println(generator.next());System.out.println(generator.next());}
}
输出:Apple
Banana
Pear
Pear复制代码

泛型方法

在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。无论何时,只要你能做到,你就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛化,那么应该有限采用泛型方法。

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

如果你定义了一个泛型(类、接口),那么Java规定,你不能在所有的静态方法、静态初块等所有静态内容中使用泛型的类型参数。例如:

public class A<T> {public static void func(T t) {//报错,编译不通过}
}复制代码

泛型的通配符

泛型的通配符增强了方法的灵活性但也容易让人困惑。

Java中有无限定通配符<?>,上界限定通配符<? extends E>,下界限定通配符<? super E>这三种通配符。

无限定通配符

需求:打印List中的元素。List是一个泛型类,有List<String>,List<Number>,List<Object>等可能。使用List<?>通配符,可以匹配任意List泛型。
代码如下:

public static void printList(List<?> list) {for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}
}
复制代码

看起来很简单,但是此时的list是无法进行add操作的,因为List的类型是未知的。这就是<?>的只读性,稍后会有介绍。

在使用泛型类的时候,既可以指定一个具体的类型,如List(String)就声明了具体的类型是String;也可以用通配符?来表示未知类型,如List<?>就声明了List中包含的元素类型是未知的。 通配符所代表的其实是一组类型,但具体的类型是未知的。List<?>所声明的就是所有类型都是可以的。但是List<?>并不等同于List(Object)。List(Object)实际上确定了List中包含的是Object及其子类,在使用的时候都可以通过Object来进行引用。而List<?>则其中所包含的元素类型是不确定。其中可能包含的是String,也可能是 Integer。如果它包含了String的话,往里面添加Integer类型的元素就是错误的。正因为类型未知,就不能通过new ArrayList(?)()的方法来创建一个新的ArrayList对象。因为编译器无法知道具体的类型是什么。但是对于 List(?)中的元素确总是可以用Object来引用的,因为虽然类型未知,但肯定是Object及其子类。

有限通配符

同样是一个打印List元素的例子,但是只接受类型参数是Number及其子类。

public static void printList(List<? extends Number> list) {for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}
}
复制代码

和<?>一样,<? extends E>也具有只读性。

通配符<?>和<? extends E>具有只读性,即可以对其进行读取操作但是无法进行写入。

public static void printList(List<?> list) {for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}//一下操作不可以list.add(1);list.add("123");
}
复制代码

原因在于:?就是表示类型完全无知,? extends E表示是E的某个子类型,但不知道具体子类型,如果允许写入,Java就无法确保类型安全性。假设我们允许写入,如果我们传入的参数是List<Integer>,此时进行add操作,可以添加任何类型元素,就无法保证List<Integer>的类型安全了。

因为对于List(?)中的元素只能用Object来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。 如List(? extends Number)说明List中可能包含的元素类型是Number及其子类。而List(? super Number)则说明List中包含的是Number及其父类。当引入了上界之后,在使用类型的时候就可以使用上界类中定义的方法。比如访问 List(? extends Number)的时候,就可以使用Number类的intValue等方法。

超类型

超类型通配符允许写入,例子如下:

public static void printList(List<? super String> list) {for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}list.add("123");list.add("456");
}
复制代码

这个很好理解,list的参数类型是String的上界,必然可以添加String类型的元素。

泛型与数组

Java不能创建泛型数组,以Som泛型类为例,以下代码编译报错:

Som<String> [] soms = new Som<String>[8];
复制代码

原因是像Integer[]和Number[]之间有继承关系,而List<Integer>和List<Number>没有,如果允许泛型数组,那么编译时无法发现,运行时也不是立即就能发现的问题会出现。参看以下代码:

Som<Integer>[] soms = new Som<Integer>[3];
Object[] objs = soms;
objs[0] = new Som<String>();
复制代码

那我们怎么存放泛型对象呢?可以使用原生数组或者泛型容器。

泛型的命名规范

为了更好地去理解泛型,我们也需要去理解java泛型的命名规范。为了与java关键字区别开来,java泛型参数只是使用一个大写字母来定义。各种常用泛型参数的意义如下:
E — Element,常用在java Collection里,如:List(E),Iterator(E),Set(E)
K,V — Key,Value,代表Map的键值对
N — Number,数字
T — Type,类型,如String,Integer等等

java 基础 泛型相关推荐

  1. Java基础 --- 泛型 Generics

    Java基础 --- 泛型 Generics 为什么需要泛型 泛型 Bounds for Type Variable Java虚拟机如何处理泛型 --- 泛型擦除 Restrictions and L ...

  2. java基础-泛型举例详解

    泛型 泛型是JDK5.0增加的新特性,泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数.这种类型参数可以在类.接口.和方法的创建中,分别被称为泛型类.泛型接口.泛型方法. 一.认识泛型 在没 ...

  3. Java基础----泛型

    一,泛型机制介绍及为何要使用泛型 泛型机制是在Java SE5.0中增加的,使用泛型机制编写的程序代码要比那些杂乱地使用object变量,然后再进行强制转换的代码具有更好的安全性和可读性.泛型对于集合 ...

  4. Java基础—泛型的使用(详细)

    文章目录 目录 零.概念 一.泛型的优点 1.1优点 1.2为什么要使用泛型 二.泛型使用与不使用的区别 1.泛型的没有使用会造成什么后果呢? 2.添加泛型的使用会发生什么效果呢? [一.二] 知识点 ...

  5. [Java基础]泛型基础

    可变参数的使用: 代码如下: package CanChangePack;import java.util.Arrays; import java.util.List;public class Arg ...

  6. 六、JAVA基础--泛型

    泛型方法:泛型方法可以放在普通类中,也可以定义在泛型类中. <T extends 具体类或者接口> T为绑定类型的子类型:T和绑定类型可以是类,也可以是接口. 可以有多个限定类型,用'&a ...

  7. java 获取泛型t的class_阿里巴巴都鼎力推荐的java基础之集合其他内容和泛型3

    第三节 泛型 3.1为什么需要泛型 没有采用泛型之前 1.不安全:添加元素是无检查 宽进 2.繁琐:获取元素时需要强制类型转换 严出 采用泛型之后 1.安全 严进 2.简单 宽出 3.2什么是泛型ge ...

  8. Java基础-我所理解的泛型

    Java基础-我所理解的泛型 引用 [java]泛型中,? extends T 与 ? super T 的区别.看法_winrh的博客-CSDN博客_泛型 extends 前言 Java基础系列,我所 ...

  9. Java编程基础 - 泛型

    Java编程基础 - 泛型 [导读] . 什么是泛型 一般传统而言,泛型允许程序员在强类型语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型. 编程技术发展至今很多语言,尤其是强 ...

最新文章

  1. 2020年最新全球大学学术排名出炉
  2. vsftpd配置文件详解
  3. linux日常笔记3
  4. Python3逻辑运算符
  5. 语言 重量计算_R语言 第五章 高级绘图工具(4)
  6. CSS 锦囊[收藏]
  7. 一份干货满满的PPT,答辩加分手到擒来!
  8. 2021年中国便利店发展报告
  9. ERROR in static/js/vendor.js from UglifyJs UUnexpected token: name (Dom7)
  10. idea非开源安装指南_开发人员开源指南
  11. U盘安装ubuntu server 10.4
  12. keras系列︱keras是如何指定显卡且限制显存用量(GPU/CPU使用)
  13. 20191117每日一句 EVERYBODY DIES, BUT NOT EVERYBODY LIVES
  14. pandas 按列 tolist
  15. 3D打印机之Marlin固件配置
  16. Matlab实现snn代码,SNN系列|神经元模型篇(3)SRM
  17. 金蝶K3案例实验实际成本后台配置
  18. 干货 | 关于SwiftUI,看这一篇就够了
  19. HIVE获取时间函数, regexp_extract正则提取用法
  20. NXP JN5169使用定时器进行PWM输出和定时功能

热门文章

  1. 经典html,经典 HTML
  2. c语言中cot函数图像,cot函数图像
  3. webrtc java api_java – 使用WebSockets实现WebRTC信令
  4. 金融综合(网课+读书笔记)
  5. Sublime Text中全局查找方法
  6. 这些问题你遇见过吗?职场中,新人必须堤防的6大陷阱
  7. java商品名称_Java统计商品信息
  8. congruent matrix
  9. qt-opensource-windows-x86-vs2010-4.8.6 + qt-creator-windows-opensource-2.8.0
  10. html行分割,如何确定一个html标签是否分割成多行