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

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. Html中<a>标签的样式的设置

    html中<a>标签的样式的设置.. ------------------------ <html> <head> <title>这是网页选项卡的名称& ...

  2. 用U盘安装系统的好用的PE系统:通用PE V6.1下载

    用U盘安装系统的好用的PE系统:通用PE V6.1下载 PE是一款用其他介质(我们最常用的是U盘)启动安装电脑系统的简易操作系统,在XP系统中 最经典的是扬州老毛桃出品的只有100多兆的XP内核的PE ...

  3. Google 地图 API V3 针对移动设备进行开发

    Google官方教程: Google 地图 API V3 使用入门 Google 地图 API V3 针对移动设备进行开发 Google 地图 API V3 之事件 Google 地图 API V3 ...

  4. JDK Collection 源码分析(1)—— Collection

    JDK Collection   JDK Collection作为一个最顶层的接口(root interface),JDK并不提供该接口的直接实现,而是通过更加具体的子接口(sub interface ...

  5. 【09-03】java泛型学习笔记

    静态方法的泛型 /** * @description 静态方法的泛型无法使用类定义的泛型,因为类在实例化时才确定具体的泛型类,因此静态方法要使用泛型需要使用泛型方法的方式 */ public clas ...

  6. ThinkPHP配置简单的mysql读写分离

    ThinkPHP内置了分布式数据库的支持,包括主从式数据库的读写分离,但是分布式数据库必须是相同的数据库类型. 配置DB_DEPLOY_TYPE 为1 可以采用分布式数据库支持.如果采用分布式数据库, ...

  7. angularjs中父,子,兄之间controller值得传递

    使用angularjs,发现controller间的值传递,比较麻烦的,以后几篇文章会陆续说几种方法. 一,angularjs $broadcast $emit $on的处理思想 在一个control ...

  8. 关于BigDecimal 和 double 类型保存金钱,以及精度问题,银行家舍入法

    1. BigDecimal 类型数据 的创建,构造函数 有 public BigDecimal(BigInteger intVal, long val, int scale, int prec); p ...

  9. struts2表单批量提交

    <%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles"%> <% ...

  10. 使用php+swoole对client数据实时更新(上)

    如果想对一个列表做实时的更新,传统的做法是采用轮询的方式.以web为例,通过Ajax定时请求服务端然后获取数据显示在页面.这种方式实现简单,缺点就是浪费资源. HTTP1.1新增加了对websocke ...