文章目录

  • 一、原生态类型
    • 1、什么是原生态类型
    • 2、使用原生态类型 有什么不好
    • 3、泛型的子类型规则
    • 4、泛型的可擦除性
  • 二、泛型常用形式
    • 1、泛型方法
    • 2、泛型单例工厂
  • 三、有限制的通配符类型
  • 四、类型安全的异构容器

写在前面: 我是「境里婆娑」。我还是从前那个少年,没有一丝丝改变,时间只不过是考验,种在心中信念丝毫未减,眼前这个少年,还是最初那张脸,面前再多艰险不退却。
写博客的目的就是分享给大家一起学习交流,如果您对 Java感兴趣,可以关注我,我们一起学习。

前言:Java 1.5之前是没有泛型的,以前从集合中读取每个对象都必须先进行转换,如果不小心存入集合中对象类型是错的,运行过程中转换处理会报错。有了泛型之后编译器会自动帮助转换,使程序更加安全,但是要正确使用泛型才能取得事半功倍的效果。因此学会使用Java泛型,可以避免写那种冗余的代码。

一、原生态类型

1、什么是原生态类型

原生态类型(Raw type),即不带任何实际类型参数的泛型名称。如与List对应的原生态类型List。不推荐List list = new ArrayList()这样的方式,主要就会丢掉安全性(为什么不安全呢?具体请往下看),应使用List list = new ArrayList()明确类型。或者使用List(那么List与List有啥区别呢?具体可以看泛型的子类型规则部分)

2、使用原生态类型 有什么不好

我们使用原生态类型List创建一个集合,并往其中放入String类与int类,并迭代循环获取List集合中的元素。


public class Test1 {public static void main(String[] args) {List list = new ArrayList<>();list.add("123");list.add(123);for (Iterator i = list.iterator(); i.hasNext();) {String str = i.next();}}}

必须使用Cast强转,否则编译会报错,在编译期报错对于开发者来说是我们最希望看到的。

但是我们根据提示,增加Cast,好了编译是不会报错了,但是运行时期会报错! Exception in thread “main” java.lang.ClassCastException: ,这就对我们开发者来说大大增加了难度。
由此可见,原生类型是不推荐使用,是不安全的!

  • 问1:那为什么Java还要允许使用原生态类型呢?

是为了提升兼容性,Java1.5之前已经存在很多的原生态类型的代码,那么为了让代码保持合法,并且能够兼容新代码,因此Java才对原生态类型支持!

  • 问2:那我们使用List是不是就可以了呢,两个有啥区别呢?

两者都可以插入任意类型的对象。不严格来说,前者原生态类型List逃避了泛型检查,后者参数化类型List明确告诉编译器能够持有任意类型的对象。但是两个的区别主要是泛型存在子类型规则,具体请往下看

3、泛型的子类型规则

子类型规则,即任何参数化的类型是原生态类型的一个子类型,比如List是原生态类型List的一个子类型,而不是参数化List的子类型。

由于子类型规则的存在,我们可以将List传递给List类型的参数

public static void main(String[] args) {List<String> strings = new ArrayList<>();unsafeAdd(strings, new Integer(1));String s = strings.get(0);}private static void unsafeAdd(List list, Object o){list.add(o);}

虽然编译器是没有报错的,但是编译过程会出现以下提示,表明编写了某种不安全的未受检的操作

但是我们不能将List传递给List类型参数

 public static void main(String[] args) {List<String> strings = new ArrayList<>();unsafeAdd(strings, new Integer(1));String s = strings.get(0);}private static void unsafeAdd(List<Object> list, Object o){list.add(o);}

4、泛型的可擦除性

我们先看一下代码,看看结果:

public static void main(String[] args) {List<String> l1 = new ArrayList<String>();List<Integer> l2 = new ArrayList<Integer>();//  输出为true,擦除后的类型为ListSystem.out.println(l1.getClass() == l2.getClass());}

结果为true,这是因为:泛型信息可以在运行时被擦除,泛型在编译期有效,在运行期被删除,也就是说所有泛型参数类型在编译后都会被清除掉。归根结底不管泛型被参数具体化成什么类型,其class都是RawType.class,比如List.class,而不是List.class或List.class

事实上,在类文字中必须使用原生态类型,不准使用参数化类型(虽然允许使用数组类型和基本类型),也就是List.class、String[].class和int.class都是合法的,而List.class和List<?>.class不合法

二、泛型常用形式

1、泛型方法

常用的形式:

访问修饰符 [static][final] <类型参数列表> 返回值类型 方法名([形式参数列表])

备注:[] 代可有可无的意思,泛型方法可以是实例方法或静态方法,类型参数可以在静态方法中,这是与泛型类最大的区别。

2、泛型单例工厂

有时候我们需要创建不可变但又适合许多不同类型的对象。之前的单例模式满足不可变,但不适合不同类型对象,这次我们可以利用泛型做到这点。

/*** apply方法接收与返回某个类型T的值* @param <T>*/
public interface UnaryFunction<T> {T apply(T arg);
}

现在我们需要一个恒等函数(Identity function,f(x)=x,简单理解输入等于返回的函数,会返回未被修改的参数),如果每次需要的时候都要重新创建一个,这样就会很浪费,如果泛型被具体化了,每个类型都需要一个恒等函数,但是它们被擦除后,就只需要一个泛型单例。

public class SingleGeneric {/*** 返回未被修改的参数arg*/private static UnaryFunction<Object> IDENTITY_FUNCTION = (Object arg) -> {return arg;};/*** 泛型方法identityFunction:*      返回类型:UnaryFunction<T>*      类型参数列表;<T>* 忽略强制转换未受检查的警告:* 因为返回未被修改的参数arg,所以我们知道无论T的值是什么,都是类型安全的* @param <T>* @return*/@SuppressWarnings("unchacked")public static <T> UnaryFunction<T> identityFunction(){return (UnaryFunction<T>) IDENTITY_FUNCTION;}public static void main(String[] args) {String[] strings = {"hello","world"};UnaryFunction<String> sameString = identityFunction();for (String s: strings) {System.out.println(sameString.apply(s));}Number[] numbers = {1,2.0};UnaryFunction<Number> sameNumber = identityFunction();for (Number n: numbers) {System.out.println(sameNumber.apply(n));}}
}

三、有限制的通配符类型

之前提到过的无限制的通配符类型就提到过,无限制的通配符单纯只使用"?"(如Set<?>),而有限制的通配符往往有如下形式,通过有限制的通配符类型可以大大提升API的灵活性。

(1)E的某种超类集合(接口):Collection<? super E>、Interface<? super E>、

(2)E的某个子类集合(接口):Collection<? extends E>、Interface<? extends E>

  • 问1:那么什么时候使用extends关键字,什么什么使用super关键字呢?

有这样一个PECS(producer-extends, consumer-super)原则:如果参数化类型表示一个T生产者,就使用<? extends T>,如果表示消费者就是<? super T>。可以这样助记

  • 问2:什么是生产者,什么是消费者

1)生产者:产生T不能消费T,针对collection,对每一项元素操作时,此时这个集合时生产者(生产元素),使用Collection<? extends T>。只能读取,不能写入

2)消费者:不能生产T,只消费使用T,针对collection,添加元素collection中,此时集合消费元素,使用Collection<? super T>,只能添加T的子类及自身,用Object接收读取到的元素

举例说明:生产者

1)你不能在List<? extends Number>中add操作,因为你增加Integer可能会指向List,你增加Double可能会指向Integer。根本不能确保列表中最终保存的是什么类型。换句话说Number的所有子类从类关系上来说都是平级的,毫无联系的。并不能依赖类型推导(类型转换),编译器是无法确实的实际类型的!

2)但是你可以读取其中的元素,并保证读取出来的一定是Number的子类(包括Number),编译并不会报错,换句话说编译器知道里面的元素都是Number的子类,不管是Integer还是Double,编译器都可以向下转型

  • 举例说明:消费者

1)编译器不知道存入列表中的Number的超类具体是哪一个,只能使用Object去接收。因为你增加Integer可能会指向List,你增加Double可能会指向Integer。根本不能确保列表中最终保存的是什么类型。换句话说Number的所有子类从类关系上来说都是平级的,毫无联系的。并不能依赖类型推导(类型转换),编译器是无法确实的实际类型的!

2)但是你可以读取其中的元素,并保证读取出来的一定是Number的子类(包括Number),编译并不会报错,换句话说编译器知道里面的元素都是Number的子类,不管是Integer还是Double,编译器都可以向下转型

  • 举例说明:消费者

1)编译器不知道存入列表中的Number的超类具体是哪一个,只能使用Object去接收

2)但是只可以添加Interger及其子类(因为Integer子类也是Integer,向上转型),不能添加Object、Number。因为插入Number对象可以指向List对象,你插入Object,因为可能会指向List对象

注意:Comparable/Comparator都是消费者,通常使用Comparator<? Super T>),可以将上述的max方法进行改造:

 public static <T extends Comparable<? super T>> T max(List<? extends T> list) {Iterator<? extends T> iterator = list.iterator();T result = iterator.next();while (iterator.hasNext()) {T t = iterator.next();if (t.compareTo(result) > 0) {result = t;}}return result;}

四、类型安全的异构容器

泛型一般用于集合,如Set和Map等,这些容器都是被参数化了(类型已经被具体化了,参数个数已被固定)的容器,只能限制每个容器只能固定数目的类型参数,比如Set只能一个类型参数,表示它的元素类型,Map有两个参数,表示它的键与值。

但是有时候你会需要更多的灵活性,比如关系数据库中可以有任意多的列,如果以类型的方式所有列就好了。有一种方法可以实现,那就是使用将键进行参数化而不是容器参数化,然后将参数化的键提交给容器,来插入或获取值,用泛型来确保值的类型与它的键相符。

我们实现一个Favorite类,可以通过Class类型来获取相应的value值,键可以是不同的Class类型(键Class<?>参数化,而不是Map<?>容器参数化)。利用Class.cast方法将键与键值的类型对应起来,不会出现 favorites.putFavorite(Integer.class, “Java”) 这样的情况。

public class Favorites {private Map<Class<?>, Object> favorites = new HashMap<>();public <T> void putFavorite(Class<T> type, T instance){if (type == null) {throw new NullPointerException("Type is null");}favorites.put(type, type.cast(instance));}public <T> T getFavorite(Class<T> type){return type.cast(favorites.get(type));}
}

Favorites实例是类型安全(typesafe)的,你请求String时,不会返回给你Integer,同时也是异构(heterogeneous)的,不像普通map,它的键都可以是不同类型的。因此,我们将Favorites称之为类型安全的异构容器(typesafe heterogeneous container)。

 public static void main(String[] args) {Favorites favorites = new Favorites();favorites.putFavorite(String.class, "Java");favorites.putFavorite(Integer.class, 64);favorites.putFavorite(Class.class, Favorites.class);String favoriteString = favorites.getFavorite(String.class);Integer favoriteInteger = favorites.getFavorite(Integer.class);Class<?> favoriteClass = favorites.getFavorite(Class.class);// 输出 Java 40 FavoritesSystem.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getSimpleName());}

Favorites类局限性在于它不能用于在不可具体化的类型中,换句话说你可以保存String,String[],但是你不能保存List,因为你无法为List获取一个Class对象:List.class是错误的,不管是List还是List都会公用一个List.class对象。

  • 附1:相关泛型术语
      1)参数化的类型:List
      2)实际类型参数:String
      3)泛型:List
      4)形式类型参数:E
      5)无限制通配符类型:List<?>
      6)原生态类型:List
      7)递归类型限制:<T extends Comparable>
      8)有限制的通配符类型:List<? extends Number>
      9)泛型方法:static List union()
      10)类型令牌:String.class

  • 附2:常用的形式类型参数
      1)T 代表一般的任何类。
      2)E 代表 Element 的意思,或者 Exception 异常的意思。
      3)K 代表 Key 的意思。
      4)V 代表 Value 的意思,通常与 K 一起配合使用。
      5)S 代表 Subtype 的意思
      
    参考文章:如何正确使用Java泛型

一篇文章教你学会Java泛型相关推荐

  1. 一篇文章教你学会Java基础I/O流

    文章目录 一.初始IO流 1.什么是流 2.IO流的分类 二.字节输入输出流 1.字节输入流(InputStream) 2.字节输出流(OutputStream) 3.文件拷贝 三.处理流和转换流 写 ...

  2. 一篇文章教你学会Java基础JDBC

    文章目录 一.搭建JDBC开发环境 1.搭建工程 2.连接数据库工具类JdbcConnectionUtil 3.main方法测试 二.创建Statement或PreparedStatement接口,执 ...

  3. 一篇文章教你学会使用SpringBoot实现文件上传和下载

    文章目录 一.搭建SpringBoot开发环境 1.创建项目 2.配置application.properties参数 3.实体响应类和异常信息类 4.创建FileController 二.接口测试 ...

  4. 一篇文章教你学会使用SpringBatch 监听器Listener

    文章目录 一.SpringBatch监听器 二.搭建SpringBatch开发环境 三.监听器详细介绍 1.JobExecutionListener 2.StepExecutionListener 3 ...

  5. 一篇文章教你学会实现模糊搜索结果的关键词高亮显示

    一篇文章教你学会实现模糊搜索结果的关键词高亮显示 话不多说,先看效果图: 代码如下: <!DOCTYPE html> <html lang="en">< ...

  6. 一篇文章教你学会如何使用CSS中的雪碧图(CSS Sprite)

    一篇文章教你学会如何使用CSS中的雪碧图(CSS Sprite) 一.什么是雪碧图? 雪碧图(CSS Sprite)又叫CSS精灵图,是一种网页图片应用处理方式,他允许你将一个页面设计到 所有零星图片 ...

  7. 适合零基础学习者的Java学习路线图到底长啥样?一篇文章带你学会Java

    很多小伙伴在转行互联网的时候,都担心自己不能坚持,不知道Java适不适合自己. 那最好的方式就是先不要着急直接转行,自己可以先去试着学习一些基础知识,看看对Java的学习难度能否适应以及自己是否真心喜 ...

  8. 一篇文章教你学会并使用Redis-转自狂神

    一.Nosql概述 为什么使用Nosql 1.单机Mysql时代 90年代,一个网站的访问量一般不会太大,单个数据库完全够用.随着用户增多,网站出现以下问题 数据量增加到一定程度,单机数据库就放不下了 ...

  9. html网页怎么向文章,一篇文章教你学会HTML

    html是学习做网页的基础,漂亮的网页与布局就是由有些html代码组成,大家看完这篇文章就可以简单的了解html了,多写多练. 如果你不致力于成为美工的话,那么作为开发人员,可以读懂HTML.必要时能 ...

最新文章

  1. P4269 [USACO18FEB]Snow Boots G
  2. 10门必看的机器学习免费课程
  3. [django]模板中自定义变量django模板中的变量
  4. yield %%% generator
  5. xpath定位的一些方法
  6. Windows 10的应用体验之二
  7. pandas 提取股票价格
  8. Net中如何操作IIS
  9. JAVA lock 原理讲解
  10. mysql将备份的数据导入_成功将MySQL的大型数据导入导出和备份(转载)
  11. 小程序·云开发实战 - 体重记录小程序
  12. 论无线网络中的网络与信息安全技术
  13. vonic 环境配置_vonic单页面应用
  14. Java排序之Comparable与Comparator详解
  15. plsql11破解注册码
  16. Ubuntu 15.04 折腾手记
  17. Mysql主从切换自动_keepalived实现对mysql主从复制的主备自动切换
  18. 用c语言编程心形,用c语言编写心形图案
  19. 点名时间——创新众筹平台
  20. github创建tag

热门文章

  1. 计算机组成原理白中英作业,计算机组成原理白中英部分作业解答(第二章)
  2. c# mysql timeout expired_C#百万数据查询出现超时问题的解决方法
  3. Java基础day10
  4. 工业用微型计算机(12)-指令系统(7)
  5. 【学术相关】供参考:刚刚,2021 世界大学学术排名发布!
  6. 计算机行业人员的鄙视链
  7. 原文翻译:深度学习测试题(L1 W2 测试题)
  8. LeNet试验(一) 搭建pytorch版模型及运行
  9. 搜索推荐炼丹笔记:单网络内部集成学习
  10. 网易会议开源指南 | 极速构建你的专属会议软件!