浏览以下内容前,请点击并阅读 声明

6 类型推测

  java编译器能够检查所有的方法调用和对应的声明来决定类型的实参,即类型推测,类型的推测算法推测满足所有参数的最具体类型,如下例所示:

//泛型方法的声明
static <T> T pick(T a1, T a2) { return a2; }
//调用该方法,根据赋值对象的类型,推测泛型方法的类型参数为Serializable
//String和ArrayList<T>都实现接口Serializable,后者是最具体的类型
Serializable s = pick("d", new ArrayList<String>());

6.1 泛型方法的类型推测

  类型的推测可以使泛型方法的使用语法和普通的方法一样,不必指定尖括号内的类型,如上述例子。

6.2 泛型类的类型推测

  对于泛型类的使用,java编译器也可以进行类型的推测,因此调用泛型类时,可以不用指定尖括号内的类型参数,不过尖括号不可省略,之前的总结已经提到,空的尖括号又叫钻石(中文怪怪的),如下例所示:

//以下用法没有指定类型参数,尖括号为空
Map<String, List<String>> myMap = new HashMap<>();
//注意,空的简括号不能省略,如下代码编译器会发出警告
Map<String, List<String>> myMap = new HashMap();

  上述代码中的第二个赋值语句中new HashMap() 实际是用的原始类型。

6.3 非泛型类的泛型构造器的类型参数推测

  无论是泛型还是非泛型的类都可以使用泛型的构造器,如方法一样。

//类定义
class MyClass<X> {
<T> MyClass(T t) {
// ...
}
}
//以下是实例化以上类的表达式
new MyClass<Integer>("")

  以上代码中的实例化表达式虽然没有指定构造器的类型参数,但是可以根据传入的参数推测其类型参数为String。

  java7以前的版本能够推测出构造其的参数类型,而java7以后,使用钻石的语法也推测泛型类的参数类型。

  需要注意的是,类型参数的推测算法只会使用传入的参数,目的类型或者和明显的返回类型来推测类型。

6.4 目的类型

  java编译器充分利用了目的类型来推测泛型方法或者类的类型参数,如下例:

//Collections中的一个方法的声明如下
static <T> List<T> emptyList();
//现在调用该方法
List<String> listOne = Collections.emptyList();

  以上中的第二个语句中,listOne变量类型为List<string>,就是目的类型,所以需要方法emptyList的返回类型也必须是List<Stirng>,这样可以推测泛型方法声明中的T为String,java7和8都可以实现这样的推测,当然你可以在调用泛型方法时指明方括号中的类型参数。

  值得注意的是java7中方法的参数还不属于目的类型,而java8则把方法参数加入目的类型,如下例所示:

//如下方法接受的参数为List<String>
void processStringList(List<String> stringList) {
// process stringList
}
//Collections中的emptyList方法的签名如下
static <T> List<T> emptyList();
//java7中,下列调用语句的编译会报错,而java8则不存在这样的问题
processStringList(Collections.emptyList());

7 通配符

  在泛型的代码中问号(?)代表通配符,代表未知的类型,通配符可以用在许多场合,可用作参数,字段,返回值的类型,但是通配符不能用作方法调用,泛型实例的创建和父类型的实参。

7.1 上限通配符

  利用上限通配符可以放松对变量的限制。

  上限通配符的声明方法如下例所示:

public static void process(List<? extends Foo> list) { /* ... */ }

  上述声明的方法,的泛型参数使用了上限通配符,通配符"?"加extends关键词后跟其上限,此处的extends类似于通常意义上的extends和implements,意思是该方法是针对于Number类型的子类型,包括Integer,Float等的列表。

  通配符<? extends Foo>匹配所有的Foo的子类型和Foo类型自身。

7.2 无限制通配符

  无限制通配符就是简单的"?",如List<?>就代表未知类型的列表,以下两种情况适合使用无限制通配符:

  • 声明一个要用到继承的Object类中的方法时
  • 当代码中需要用到不依赖于类型参数的泛型类的方法时, 如List.size或者List.clear,Class<?> 经常被用到,因为Class<T>中的许多方法是不依赖于类型参数T的。

以下例子很好的说明使用Object类中的方法时使用无限制通配符的好处:

//普通的方法声明
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
//使用通配符的泛型作为方法参数,该方法的参数能够传入任何类型的列表(List)
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}

  注意:既然定义了列表List<?>的类型的广泛性,就要承担广泛性的造成的后果,在方法声明中,只能对List<?>类型的变量插入null,因为你无法预知传入方法的类型变量,而List<Object>作为参数则可以插入任何类型的对象。

7.3 下限通配符

  与上限通配符类似,下限通配符指定了类型参数的下限,未知的类型必须是指定类型的父类型,下限通配符的写法:<? super A>,此处关键词为super

  注意:不能同时指定上限和下限。

7.4 通配符和子类型

  之前提到过,泛型之间的关系不仅仅是由他们的类型实参决定的,如不能说List<Number>就是List<Integer>的父类,不过使用通配符可以构成如下关系:

  箭头表示“是其子类型”的关系,如List<Integer>是List<? extends Integer>的子类型,可以这样理解:List<Integer>是一种List<? extends Integer>。

7.5 通配符的捕获与辅助方法

  有时候编译器会推测通配符的类型,如果一个字段的类型被定义为List<?>,当运算一个表达式的时候,编译器会从代码中推测该字段为一个特定的类型,这就叫通配符的捕获。

import java.util.List;
public class WildcardError {
void foo(List<?> i) {
i.set(0, i.get(0));
}
}

  上述代码会编译出错,foo方法调用List.set(int,E),编译器首先将set方法内作为参数的i视为Object类型,无法判断将要插入的对象类型是否和目标列表类型是否一致,所以编译不能通过。

  此时可以加入一个辅助方法,使其能能够顺利通过编译:

public class WildcardFixed {

    void foo(List<?> i) {
fooHelper(i);
}
// 创建辅助方法,调用该方法可以通过类型推测来实现通配符的捕获
private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}
}

  再来看一下一个例子:

import java.util.List;

public class WildcardErrorBad {

    void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
Number temp = l1.get(0);
l1.set(0, l2.get(0));
l2.set(0, temp);
}
}

  上述代码中的方法功能是将两个列表的首个元素交换,然而无法判断两个传入的实参的类型参数是否兼容,所以,无法编译通过,此处代码本质上就是错误的,没有相应的辅助方法。

7.6 通配符使用原则

  泛型的使用有一点让人疑惑的就是不知道什么时候该用上限通配符,什么时候使用下限通配符,一下是几点原则:

  为了说明问题,先列出两种变量1)In变量:作为代码中的数据来源,比如复制的方法copy(src,dest)中的src参数就是in变量,;2)out变量,在代码中用来存储数据作为他用,如copy(src,dest)中的dest参数就是out变量。变量列出之后,说原则:

  • in变量使用上限通配符,使用extends关键词
  • out变量使用下限通配符,使用super关键词
  • 当需要使用的in变量可以通过Object类中的方法访问时,使用无限制通配符
  • 当代码中既需要访问的变量既要当做in变量使用,又要当做out变量使用时,不要使用通配符

  上述原则不试用与方法的返回类型,不建议在返回类型中使用通配符,否则将必须处理通配符的问题。

java基础-泛型2的更多相关文章

  1. 一天一个Java基础——泛型

    这学期的新课——设计模式,由我仰慕已久的老师传授,可惜思维过快,第一节就被老师挑中上去敲代码,自此在心里烙下了阴影,都是Java基础欠下的债 这学期的新课——算法设计与分析,虽老师不爱与同学互动式的讲 ...

  2. Java 基础 -- 泛型、集合、IO、反射

    package com.java.map.test; import java.util.ArrayList; import java.util.Collection; import java.util ...

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

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

  4. Java基础 - 泛型详解

    2022-03-24 09:55:06 @GhostFace 泛型 什么是泛型? 来自博客 Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了&quo ...

  5. java基础-泛型3

    浏览以下内容前,请点击并阅读 声明 8 类型擦除 为实现泛型,java编译器进行如下操作进行类型擦除: 如果类型参数有限制则替换为限制的类型,如果没有则替换为Object类,变成普通的类,接口和方法. ...

  6. java基础 泛型

    泛型的存在,是为了使用不确定的类型. 为什么有泛型? 1. 为了提高安全 2. 提高代码的重用率 (自动 装箱,拆箱功能) 一切好处看代码: package test1; import java.la ...

  7. java基础-泛型1

    浏览以下内容前,请点击并阅读 声明 泛型的使用能使类型名称作为类或者接口定义中的参数,就像一般的参数一样,使得定义的类型通用性更强. 泛型的优势: 编译具有严格的类型检查 java编译器对于泛型代码的 ...

  8. Java基础---泛型、集合框架工具类:collections和Arrays

    第一讲     泛型(Generic) 一.概述 1.JDK1.5版本以后出现的新特性.用于解决安全问题,是一个类型安全机制. 2.JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类 ...

  9. Java基础——泛型

    一.定义 泛型(generic)是指参数化类型的能力.可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它(泛型实例化).使用泛型的主要优点是能够在编译时,而不是在运行时检测出错误.它是jd ...

随机推荐

  1. <!DOCTYPE>标签的定义与用法

    <!DOCTYPE> 声明不是 HTML 标签:它是指示 web 浏览器关于页面使用哪个 HTML 版本进行编写的指令. 在 HTML 4.01 中,<!DOCTYPE> 声明 ...

  2. python 网络编程

    一.网络知识的一些介绍 socket是网络连接端点.例如当你的Web浏览器请求www.pythontik.com上的主页时,你的Web浏览器创建一个socket并命令它去连接www.pythontik ...

  3. Java递归算法——三角数字(消除递归)

    import java.io.*; // for I/O //类名:Params //属性: //方法: class Params //这个类的对象被压入栈中 { public int n; //用来 ...

  4. SVN和Git下载地址

    SVN: TortoiseSVN:https://tortoisesvn.net/downloads.html (安装包和语言) Git: Git for Windows:https://git-fo ...

  5. 深入理解javascript原型和闭包(10)——this

    接着上一节讲的话,应该轮到“执行上下文栈”了,但是这里不得不插入一节,把this说一下.因为this很重要,js的面试题如果不出几个与this有关的,那出题者都不合格. 其实,this的取值,分四种情 ...

  6. nginx图片处理

    前言 不管一个系统或网站的大与小,都存在相应的图片处理,生成缩略图.为图片加水印等等,如果涉及到APP端,这个图片的处理需求变得更加重要了,因为在目前看来,客户端的屏幕大小不一,会导致以下问题: 1. ...

  7. 2014 Multi-University Training Contest 9#11

    2014 Multi-University Training Contest 9#11 Killing MonstersTime Limit: 2000/1000 MS (Java/Others)   ...

  8. shell--3.运算符

    1.注意 原生bash不支持简单的数学运算,但是可以用其它命令来实现如 awk 和expr ,expr最常用 val=`expr 2 + 3` echo "结果 ${val}" # ...

  9. C++成员初始化顺序

    #include <iostream> using namespace std; int seti() {cout << "seti" << e ...

  10. ms-dos中 MSCDEX命名语法详解

    一.MSCDEX的语法及参数:        MSCDEX可以在AUTOEXEC.BAT文件中自动加载,也可以在DOS的命令行中载入但要使用光驱,前提条件是在Config.sys文件中加载了光驱的驱动 ...