java基础-泛型2

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变量使用时,不要使用通配符

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

时间: 2024-12-12 04:06:59

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

JAVA基础—泛型小结

概念: 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java语言引入泛型的好处是安全简单. 泛型的常用字符代表意思: ? 表示不确定的java类型. T  表示java类型. K V 分别代表java键值中的Key Value. E 代表Element. 下面转载于cnblog上一个写的很好的例子 真的很好,我写了半天,当看到他这个后,立即删除~ 普通泛型

一天一个Java基础——泛型

这学期的新课——设计模式,由我仰慕已久的老师传授,可惜思维过快,第一节就被老师挑中上去敲代码,自此在心里烙下了阴影,都是Java基础欠下的债 这学期的新课——算法设计与分析,虽老师不爱与同学互动式的讲课,但老师讲的挺好,不过由于数据结构欠缺课听的有点烧脑,都是数据结构欠下的债 这学期的新课——英语口语,虽外教老师风骚逗趣浪荡不羁爱自由,但我辈词汇量欠缺,表明淡定说yeah,但心中一万匹草泥马策马奔腾,都是英语欠下的债 1.泛型类 实体类(容器类),经常重用的类,下面是一个没有用泛型的实体类: 1

Java基础 -- 泛型之泛型参数

泛型机制常用的参数有3个: “?”代表任意类型.如果只指定了<?>,而没有extends,则默认是允许任意类. extends关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类. super关键字声明了类型的下界,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object 前提 Fruit是Apple和Orange的超类. 本章通过java代码逐一分析泛型参数的意义和区别 extends参数: [java] public void extend(Li

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

第一讲     泛型(Generic) 一.概述 1.JDK1.5版本以后出现的新特性.用于解决安全问题,是一个类型安全机制. 2.JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类型的数据,无法加入指定类型以外的数据. 3.泛型是提供给javac编译器使用的可以限定集合中的输入类型说明的集合时,会去掉“类型”信息,使程序运行效率不受影响,对参数化的泛型类型,getClass()方法的返回值和原始类型完全一样. 4.由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就

Java基础——泛型

转http://www.cnblogs.com/1693977889zz/p/7095460.html 一.定义 泛型(generic)是指参数化类型的能力.可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它(泛型实例化). 使用泛型的主要优点是能够在编译时,而不是在运行时检测出错误. 它是jdk1.5之后出现的特性,为了增强安全性.我的理解是,它更像一种特殊规范,比如程序员在调用的时候,或者客户端在引入的时候,总不能鱼龙混杂,想怎样就怎样啊?!前面定义说输入一个String型的,这

java基础 泛型

1.泛型集合中的元素是存在继承关系的 public class Main { public static void main(String[] args) { List<Shape> list=new ArrayList<Shape>(); list.add(new Circle()); list.add(new Rectangle()); } } class Canva { public void drawAll(List<? extends Shape> list)

java基础-泛型的优点

1.性能 对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作.装箱和拆箱的操作很容易实现,但是性能损失较大.假如使用泛型,就可以避免装箱和拆箱操作. 1 ArrayList list=new ArrayList(); 2 list.Add(20); //装箱,list存放的是object类型元素,须将值类型转化为引用类型 3 int i=(int)list[0]; //拆箱,list[0]的类型是object,要赋值就得把引用类型转化为值类型

Java基础——集合(三)——泛型、增强for、工具类

         接上篇,<Java基础--集合(二)--迭代器.Map集合> 六.泛型 (1)定义: 泛型是一种把明确类型的工作放在了创建对象或者调用方法时候才去明确的特殊的类型. (2)格式: <数据类型> ArrayList<String> aList=new ArrayList<String>(); <String> 即表示的是泛型 (3)好处:(掌握) A:解决了黄色警告线问题 B:把运行期间的转换异常给提前到了编译期间 C:优化了程序

java基础之泛型用法

1.什么是泛型 泛型是java中保障类型安全的一种机制.它通过对变量类型的假设,实现变量类型的进行限制,从而在一定程度上保证程序编译时变量类型的安全.反之,如果没有泛型,这些假设则只存在于程序员的大脑中,这样会导致类型转换的异常在程序运行时才被发现. //不用泛型 List list1 = new ArrayList(); list1.add(1); Object o = list1.get(0); //返回Object类型对象 Integer i = (Integer) o; //需预先知道类