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

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. js 获取据当前时间n天前的时间

    <script type="text/javascript"> function getLastDate() { var date = new Date(); ; va ...

  2. sde用户下使用sqlplus登录错误ORA-12547: TNS:lost contact

    环境:linux + oracle +arcsde   解决:root用户下增加$ORACLE_HOME/bin/oracle文件的s权限 [oracle@localhost bin]$ chmod ...

  3. 关于学习JavaScript 的 高三编程 一些心得(三)

    最近在学习高三的 过程中,遇到的了一些 难以理解的问题, 在看到第五章之前都是 OK 的.但是到了 引用类型的时候就有点蒙了. 首先我们看下,引用类型的  解释:[引用类型的值(对象)是引用类型的一个 ...

  4. JUnit4 中@AfterClass @BeforeClass @after @before的区别对比

    JUnit4使用Java5中的注解(annotation),以下是JUnit4常用的几个annotation: @Before:初始化方法   对于每一个测试方法都要执行一次(注意与BeforeCla ...

  5. poj1399 hoj1037 Direct Visibility 题解 (宽搜)

    http://poj.org/problem?id=1399 http://acm.hit.edu.cn/hoj/problem/view?id=1037 题意: 在一个最多200*200的minec ...

  6. @property 参数

    /* 1.set方法内存管理相关的参数 * retain : release旧值,retain新值(适用于OC对象类型) * assign : 直接赋值(默认,适用于非OC对象类型) * copy : ...

  7. 如何使用C自带的qsort快速排序

    / you can write to stdout for debugging purposes, e.g. // printf("this is a debug message\n&quo ...

  8. Linux上进行单片机开发

    linux上可以使用sdcc进行单片机开发 ubuntu使用 apt-get install sdcc 即可安装. 附一个比较通用的Makefile PRJ := test SRC := $(wild ...

  9. DAY5 DVWA之SQL注入演练(low)

    1.设置 把安全等级先调整为low,让自己获得点信心,免得一来就被打脸. 2.测试和分析页面的功能       这里有一个输入框 根据上面的提示,输入用户的id.然后我们输入之后,发现它返回了关于这个 ...

  10. 前端XSS攻击和防御

    xss跨站脚本攻击(Cross Site Scripting),是一种经常出现在web应用中的计算机安全漏洞,指攻击者在网页中嵌入客户端脚本(例如JavaScript), 当用户浏览此网页时,脚本就会 ...