1、什么是泛型

泛型是Java1.5中出现的新特性,也是最重要的一个特性。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。这个类型参数将在程序运行时确定。

我们可以把泛型理解为作用在类或者接口上面的标签。根据这个标签的类型传入规定的数据类型,否则就会出错,其中类型必须是类类型,不能是基本数据类型。例如我们国家中医存药的箱子,每个箱子上面都贴有一个标签,如果上面贴的是冬虫夏草,那么就只能放冬虫夏草,而和其他的药物混合放在一起就非常的乱,很容易出现错误。

泛型就是这样的道理,我们先来看下泛型最简单的使用吧:

ArrayList<String> list=new ArrayList<>();
list.add("Hello");
//只能放字符串,如果放数字编译报错
//list.add(666);

注意:Java1.7之后泛型可以简化,就是变量前面的参数类型必须要写,而后面的参数类型可以写出来,也可以省略不写。

2、为什么要泛型

简单举个例子,这个应该是网上最经典的例子:

    //创建集合对象
ArrayList list=new ArrayList();
list.add("Hello");
list.add("World");
list.add(111);
list.add('a');
//遍历集合内容
for (int i = 0; i < list.size(); i++) {
String str= (String) list.get(i);
System.out.println(str);
}

上面程序运行结果毫无疑问会出现异常java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String,这就是没有泛型的弊端。因为上面的ArrayList中就可以存放任意类型(即Object类型),我们知道,所有的类型都可以用Object类型来表示,而Object在类型转换方面很容易出现错误。

也许有人会想可以先用Object类型代替String接收,然后再转成相对应的数据类型,这样是也可以的,但是万一在转换的时候某个数据类型看错了或者记错了,那么还不是会出现转换异常。就算运气好全部都对了,但是这种强制类型转换一大堆的代码,你的同事或者领导看了可能会拿刀来砍死你,非常不利于代码后期的维护。

可见没有泛型是万万不能的,同时我们还能得出泛型带来的一些好处:

①、可以有效的防止类型转换异常的出现。

②、可以让代码更简洁,从而提高代码的可读性、可维护性和稳定性。

③、可以增强for循环遍历集合,进而提升性能,因为都是相同类型的。

④、可以解决类型安全编译警告。因为没有泛型时所有类型向上转型为Object类型,所以存在类型安全问题。

泛型它有三种使用方式,分别为:泛型类、泛型接口、泛型方法。我们接下来学习它们怎么使用。

3、泛型类

泛型类就是把泛型定义在类上,它的使用比较的简单。我们先来定义一个最普通的泛型类:

//定义泛型类,其中T表示一个泛型标识
public class Generic<T> {
 
private T key; public Generic() {
} public Generic(T key) {
this.key = key;
}
//这里不是泛型方法,它们是有区别的
public T getKey() {
return key;
} public void setKey(T key) {
this.key = key;
}
}

泛型类的测试代码如下,我们只需在类实例化的时候传入想要的类型,然后泛型类中的T就会自动转换成该相应的类型。

    public static void main(String[] args) {
//有参构造器初始化,传入String类型
Generic<String> generic = new Generic<>("Generic1...");
System.out.println(generic.getKey());//Generic1...
//无参构造器初始化,传入Integer类型
Generic<Integer> generic1 = new Generic<>();
generic1.setKey(123456);
int key = generic1.getKey();
System.out.println(key);//
}

然后我们再来看下泛型中泛型标识符,在上面这个泛型类中 T 表示的是任意类型,泛型中还有很多这样的标识,例如K表示键,V表示值,E表示集合,N表示数子类型等等。

在泛型类中还有两种特殊的使用方式,是关于继承的时候是否传入具体的参数。

①、当继承泛型类的子类明确传入泛型实参时:

//子类中明确传入泛型参数类型
class SubGeneric extends Generic<String>{ }
//测试类
class Test{
public static void main(String[] args) {
SubGeneric subGeneric = new SubGeneric();
//调用继承自父类的属性
subGeneric.setKey("SubGeneric...");
String key = subGeneric.getKey();
System.out.println(key);
}
}

可以得出子类在继承了带泛型的父类时,明确的指明了传入的参数类型,那么子类在实例化时,不需要再指明泛型,用的是父类的类型。

②、当继承泛型类的子类没有明确传入泛型实参时:

//子类没有明确传入泛型参数类型
class SubGeneric<T> extends Generic<T>{
private T value; public T getValue() {
return value;
} public void setValue(T value) {
this.value = value;
}
} class Test{
public static void main(String[] args) {
SubGeneric<Integer> subGeneric = new SubGeneric<>();
//调用继承自父类的属性
subGeneric.setKey(123456);
int key = subGeneric.getKey();
System.out.println(key);
//调用子类自己的属性
SubGeneric<String> subGeneric1=new SubGeneric<>();
subGeneric1.setValue("SubGeneric...");
String value = subGeneric1.getValue();
System.out.println(value);
}
}

当子类没有明确指明传入的参数类型时,那么在子类实例化时,都是根据子类中传入的类型来确定的父类的类型。如果父类和子类中有一个明确,另一个没有明确或者两者明确的类型不一样,那么它们的类型会自动提升为Object类型。如下:

//一个明确,一个不明确
class SubGeneric<T> extends Generic<String>{
private T value;
}

4、泛型接口

泛型接口和泛型类的定义和使用几乎相同,只是在语句上面有些不同罢了,所以这里就不多说什么了。直接来看一下例子:

//定义一个泛型接口
public interface IGeneric<T> {
public T show();
} class Test implements IGeneric<String>{ @Override
public String show() {
return "hello";
}
}

泛型接口实现类中是否明确传入参数和泛型类是一样的,所以就不说了,可以参考泛型类。

5、泛型方法

前面介绍了泛型类和泛型接口,它们两者的使用相对来说比较的简单,然后我们再来看一下泛型方法,泛型方法比它们两者稍微复杂一点点。在前面的泛型类中我们也看到了方法中有用到泛型,它的格式是这样的:

    //这里不是泛型方法,它们是有区别的
public T getKey() {
return key;
}

但是它并不是泛型方法。泛型方法的中的返回值必须是用 <泛型标识> 来修饰(包括void),只有声明了<T>的方法才是泛型方法,这里<T>表明该方法将使用泛型标识T,此时才可以在方法中使用泛型标识T。而单独只用一个泛型标识 T 来表示会报错,系统无法解析它是什么。当然我们也可以使用其他的泛型标识符如:K、V、E、N等。

泛型方法的声明格式如下:

    //这里的泛型标识 T 与泛型类中的 T 没有任何关系
public <T> T show(T t){
return t;
}

注意:泛型方法的泛型与所属的类的泛型没有任何关系。我们可以举例说明:

public class GenericMethod<T> {

    //这里的泛型标识 T 与泛型类中的 T 没有任何关系
public <T> T show(T t){
return t;
}
} class Test{
public static void main(String[] args) {
//实例化泛型类,传入String类型
GenericMethod<String> genericMethod = new GenericMethod<>();
//调用方法,传入数字
Integer show = genericMethod.show(123456);
System.out.println(show);//123456
//调用方法,传入字符
Character a = genericMethod.show('a');
System.out.println(a);//a
}
}

在测试类中,创建类的实例时,泛型传入的是String类型,而在调用方法的时候,分别传入了数字类型和字符类型。

至此我们得出结论:泛型方法是在调用方法的时候指明泛型的具体类型,所以泛型方法可以是静态的;泛型类和泛型接口是在实例化类的时候指明泛型的具体类型,所以泛型类和泛型接口中的方法不能是静态的。

6、泛型在继承方面的体现(兼容性)

如果某个类继承了另一个类,那么它们之间的转换就会变得简单,例如Integer继承Number:

    Number number=123;
Integer integer=456;
number=integer;//可以赋值 Number[] numbers=null;
Integer[] integers=null;
numbers=integers;//可以赋值

上面这么做完全可以,但是把它们用在泛型中却不是这么一回事了。

    List<Number> list1=null;
List<Integer> list2=null;
//编译报错,不兼容的类型
//list1=list2;

这是因为Integer继承自Number类,但是List<Integer>并不是继承自List<Number>的,它们两是都继承自Object这个根父类。看到下面这张图片可能会更好的理解(图片引用自https://blog.csdn.net/whdalive/article/details/81751200)

这里用这样一段话来概括:虽然类A是类B的父类,但是G<A>和G<B>它们之间不具备任何子父类关系,二者都是并列关系,唯一的关系就是都继承自Object这个根父类。

再来看一下另一种情况:带泛型的类(接口)与另一个类(接口)有继承(实现)的关系。

举例:在集合中,ArrayList <E>实现List <E> , List <E>扩展Collection <E> 。 因此ArrayList <String>是List <String>的子类型,List <String>是Collection <String>的子类型。 所以只要不改变类型参数,就会在类型之间保留子类型关系。

    Collection<String> collection=null;
List<String> list=null;
ArrayList<String> arrayList=null;
collection=list;
list=arrayList;

这里其实就是普通的继承(实现),类似于Integer继承在Number类一样。图片如下:

参考文章:https://blog.csdn.net/whdalive/article/details/81751200

7、通配符

为什么要用通配符呢?那肯定是泛型在某些地方还不是非常完美,所以才要用到通配符呀,我们来分析一下:

在上面的一节中我们讲了Integer是Number的一个子类,而List<Integer>和List<Number>二者都是并列关系,它们之间毫无关系,唯一的关系就是都继承自Object这个根父类。那么问题来了,我们在使用List<Number>作为方法的形参时,能否传入List<Integer>类型的参数作为实参呢?其实这个时候答案已经很明显了,是不能的。所以这时候就可以用到通配符了。

在没有使用通配符的情况下我们的代码要这样写:

public class Generic {

    public static void main(String[] args) {
List<Number> list1=new ArrayList<>();
list1.add(1);
list1.add(2);
List<Integer> list2=new ArrayList<>();
list2.add(3);
list2.add(4);
show(list1);
//报错说不能应用Integer类型
//show(list2);
} public static void show(List<Number> list){
System.out.println(list.toString());//[1, 2]
}
}

如果还想要支持Integer类型则需要添加新的方法:

    public static void show1(List<Integer> list){
System.out.println(list.toString());//[1, 2]
}

这样就会导致代码大量的冗余,非常不利于阅读。所以此时就需要泛型的通配符了,格式如下:

    public static void show(List<?> list){
System.out.println(list.toString());//[1, 2][3, 4]
}

当我们使用了通配符之后,就能轻松解决以上问题了。在Java泛型中用  号用来表示通配符,?号通配符表示当前可以匹配任意类型,任意的Java类都可以匹配。但是在使用通配符之后一定要注意:该对象中的有些方法任然可以调用,而有些方法则不能调用了。例如:

    public static void show(List<?> list){
//list.add(66);//不能使用add()
list.add(null);//唯独能添加的只有null
Object remove = list.remove(0);//可以使用remove()
System.out.println(remove);
Object get = list.get(0);//可以使用get()
System.out.println(get);
System.out.println(list.toString());
}

这是因为只有调用的时候才知道List<?>通配符中的具体类型是什么。所以对于上面的list而言,如果调用add()方法,那么我们并不知道要添加什么样的类型,所以会报错。而remove()和get()方法都是根据索引来操作的,它们都是数字类型,所以它是可以被调用的。

通配符还有两种扩展的用法:

7.1通配符上限

List<? extends ClassName>,它表示的是传入的参数必须是ClassName的子类(包括该父类),类似于数学中(∞,ClassName],然后简单举例说明:

    public static void main(String[] args) {
List<Number> list1=new ArrayList<>();
list1.add(1);
List<Integer> list2=new ArrayList<>();
list2.add(2);
List<Object> list3=new ArrayList<>();
list3.add(3);
show(list1);//传入Number类型
show(list2);//传入Integer类型
//show(list3);//传入Object类型,报错说不能应用Object类型,因为这里最大只能用到Number类型
} public static void show(List<? extends Number> list){
System.out.println(list.toString());
}

7.2通配符下限

List<? super ClassName>,它表示的是传入的参数必须是ClassName的父类(包括该子类),类似于数学中[ClassName,∞)

    public static void main(String[] args) {
List<Number> list1=new ArrayList<>();
list1.add(1);
List<Integer> list2=new ArrayList<>();
list2.add(2);
List<Object> list3=new ArrayList<>();
list3.add(3);
show(list1);//传入Number类型
//show(list2);//传入Integer类型,报错说不能应用Integer类型,因为此时最小只能用到Number类型
show(list3);//传入Object类型,
} public static void show(List<? super Number> list){
System.out.println(list.toString());
}

夯实Java基础(十八)——泛型的更多相关文章

  1. 黑马程序员 Java基础<十八>---> 网路编程

    --------------- ASP.Net+Android+IO开发S..Net培训.期待与您交流! --------------- 第一  概述 一.概述: 1.网络模型:OSI参考模型和TCP ...

  2. java基础(十八)----- java动态代理原理源码解析

    关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 静态代理 1.静态代理 静态代理:由程序员创建或特定工 ...

  3. 夯实Java基础(八)——代码块

    在Java中代码块指的是使用”{}”括起来的代码称为代码块.代码块一共分为4种:局部代码块,静态代码块,同步代码块,构造代码块. 1.局部代码块 局部代码块就是定义在方法体内部的代码块. public ...

  4. 夯实Java基础系列目录

    自进入大学以来,学习的编程语言从最初的C语言.C++,到后来的Java,. NET.而在学习编程语言的同时也逐渐决定了以后自己要学习的是哪一门语言(Java).到现在为止,学习Java语言也有很长一段 ...

  5. 夯实Java基础系列13:深入理解Java中的泛型

    目录 泛型概述 一个栗子 特性 泛型的使用方式 泛型类 泛型接口 泛型通配符 泛型方法 泛型方法的基本用法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型方法总结 泛型上下边界 泛型常见面试 ...

  6. Java实习生常规技术面试题每日十题Java基础(八)

    目录 1.解释内存中的栈(stack).堆(heap)和静态区(static area)的用法. 2.怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串? 3.运行时异常与受检异常有 ...

  7. 夯实Java基础系列9:深入理解Class类和Object类

    目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); register ...

  8. 夯实Java基础系列14:深入理解Java枚举类

    目录 初探枚举类 枚举类-语法 枚举类的具体使用 使用枚举类的注意事项 枚举类的实现原理 枚举类实战 实战一无参 实战二有一参 实战三有两参 枚举类总结 枚举 API 总结 参考文章 微信公众号 Ja ...

  9. 夯实Java基础系列15:Java注解简介和最佳实践

    Java注解简介 注解如同标签 Java 注解概述 什么是注解? 注解的用处 注解的原理 元注解 JDK里的注解 注解处理器实战 不同类型的注解 类注解 方法注解 参数注解 变量注解 Java注解相关 ...

  10. Bootstrap <基础十八>面包屑导航(Breadcrumbs)

    面包屑导航(Breadcrumbs)是一种基于网站层次信息的显示方式.以博客为例,面包屑导航可以显示发布日期.类别或标签.它们表示当前页面在导航层次结构内的位置. Bootstrap 中的面包屑导航( ...

随机推荐

  1. Tomcat笔试题!

    1.企业常见的中间件产品有哪些? 商业:nginx企业版,jobss开源:nginx社区版,tomcat,apache 2.Tomcat软件早期名字及其主要作用? Tomcat的前身为Catalina ...

  2. Mysql常用的sql语句

    替换某字段的字符串: UPDATE article SET content = replace(content, '解决', '解放') WHERE ID<5000; 清空数据库,id也置空: ...

  3. springAOP实现原理

    spring AOP实现原理, spring 会在初始化的时候,创建一个BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)用来为类注入切 ...

  4. 【知识学习】Sublime Text 快捷键精华版

    Sublime Text 快捷键精华版 Ctrl+Shift+P:打开命令面板 Ctrl+P:搜索项目中的文件 Ctrl+G:跳转到第几行 Ctrl+W:关闭当前打开文件 Ctrl+Shift+W:关 ...

  5. 吴裕雄 python 神经网络——TensorFlow variables_to_restore函数的使用样例

    import tensorflow as tf v = tf.Variable(0, dtype=tf.float32, name="v") ema = tf.train.Expo ...

  6. 吴裕雄 python 神经网络——TensorFlow 花瓣分类与迁移学习(2)

    import glob import os.path import numpy as np import tensorflow as tf from tensorflow.python.platfor ...

  7. thinkphp 取消跳转提示

    $this->redirect('admin'); 就是直接用redirect

  8. [PHP]PHP中申明 declare(strict_types=1)的作用

    strict_types=1 针对参数类型开启严格模式,进行数据类型检验,默认是弱类型校验哪个文件写了declare,哪个文件中的所有代码就需要检查 declare(strict_types=1); ...

  9. 并发队列之ArrayBlockingQueue

    上一篇我们说了并发队列中的LinkedBlockingQueue队列,这次我们看看ArrayBlockingQueue,看看名字,我们想象一下LinkedList和ArrayList的区别,我们可以知 ...

  10. HA: Infinity Stones-Write-up

    下载地址:点我 哔哩哔哩:点我 主题还是关于复仇者联盟的,这次是无限宝石的. 信息收集 虚拟机的IP为:192.168.116.137 ➜ ~ nmap -sn 192.168.116.1/24 St ...