java编程思想读书笔记----第十五章 泛型

来源:互联网 发布:centos squid安装配置 编辑:程序博客网 时间:2024/06/05 14:18

1、简单泛型

  有多种原因促进了泛型的生成,而最引人注目的一个原因就是创建容器类。泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。
这里写图片描述
在Holder3中 你只能存入该类型(或者其子类,因为泛型和多态不冲突)。
1.1、一个元组类库
  为了解决仅一次调用就返回多个对象的情况,可以创建一个对象,用它来持有想要返回的多个对象。这个概念称为元组,它是将一组对象直接打包存储于其中的一个单一对象。这个容器对象允许读取其中的元素,但是不允许向其中存放新的对象(也可称为数据传输对象,或信使)。通常,元组可以具有任意长度,同时,元组中的对象可以使不同的类型。通过泛型可以为每一个对象指明具体的类型。

2、泛型接口

  

3、泛型防范

  即使不是泛型类,也可以包含泛型方法。泛型方法可以使得该方法能独立于类而产生变化。如果可以,尽量优先使用泛型方法而不是泛型类。另外,对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
  要定义泛型方法,只需要将泛型参数列表置于返回值之前。使用泛型方法的时候通常不用指明参数类型,编译器会自动找出具体的类型,这称为类型参数推断(type argument inference)。类型推断只对赋值操作有效,其他时候并不起作用。如果你讲一个泛型方法调用的结果作为参数,传递给另外一个方法,这时候编译器不会执行类型推断。这种情况下,编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。
  在泛型方法中,可以显示地指明类型,不过这种语法很少用。要显示地指明类型,必须在点操作符与方法名之前插入尖括号,然后把类型置于尖括号中。若是在定义该方法的类的内部,必须在点操作符前加上this 关键字。如果是使用的static方法,必须在点操作符之前加上类名。这样可以解决上一段中的非赋值问题。
  泛型方法和可变参数列表可以很好的共存。

4、匿名内部类

5、构建复杂模型

6、擦除的神秘之处

  当对泛型进行更加深入的探究时,会发现有大量的东西初看起来毫无意义。例如:可以声明ArrayList.class,却不能声明ArrayList<String>.class。另外,ArrayList<String>和ArrayList<Integer>被认为是相同类型。
  通过Class.getTypeParameters()方法获得的是用作参数占位符的标识符而已。因此,在泛型代码内部,无法获得任何有关泛型参数类型的信息。因此,你可以知道诸如类型参数标识符和泛型类型边界这类的信息——却无法知道用来创建某个特定实例的实际的类型参数。
  java泛型是通过擦除来实现的,这意味着当你使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。
这里写图片描述
  由于有了擦除,编译器无法将Manipulate()必须能够在obj上调用f()这一需求映射到Hasf有f*()这一事实上。为了调用f(),必须协助泛型类,给定泛型类的边界,以此告知编译器只能接收遵循这个边界的类型。可以使用extends,<T extends Hasf>表示T必须是Hasf类或者Hasf的导出类。泛型类型参数将擦除到它的第一个边界。编译器实际上会将类型参数替换为它的擦除,T擦除到了Hasf,就好像类的声明中用Hasf替换了T一样。
  只有当你希望使用的类型参数比某个具体类型(以及它所有的子类型)更加“泛化”时——即,当你希望代码能够跨越多个类工作时,使用泛型才有所帮助。必须查看所有代码,并确定它是否“足够复杂”到必须使用泛型的程度。
6.1、迁移兼容性
  擦除减少了泛型的泛化性。在基于擦除的实现中,泛型被当作第二类类型处理,即不能在某些重要的上下文环境中使用的类型。泛型类型只有在静态类型检查期间才会出现,在此之后,程序中所有的的泛型都将被擦除,替换为它们的非泛型上界。
6.2、擦除的问题
  擦除的代价是显著的。泛型不能用于显示地引用运行时类型的操作中,例如转型、instanceof操作和new表达式。因为关于参数的类型信息都丢失了。
6.3、边界处的动作
  即使擦除在方法或类的内部移除了有关实际类型的信息,编译器依旧可以确保在方法或类中使用的类型的内部一致性。
  因为擦除在方法体中移除了类型信息,所以在运行时的问题就是边界:即对象进入和离开方法的地点。这些正是编译器执行类型检查并插入转型代码的地点。
这里写图片描述
  对进入set的类型进行检查是不需要的,这将由编译器执行。而从get中返回的值进行转型仍旧是需要的,但这与手动执行是一样的——此处它将由编译器自动插入。
  在泛型中的所有动作都发生在边界处——对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。

7、擦除的补偿

  有时可以通过引入类型标签来进行补偿,可以使用动态的isInstance()。

8、边界与通配符

  边界使得你可以在用于泛型的参数类型上设置限制条件。其潜在的一个重要效果是你可以按照自己的边界类型来调用方法。
  与数组不同,泛型没有内建的协变类型。这是因为数组在语言中是完全定义的,因此可以内建编译期和运行期检查,但是使用泛型的时候,编译器和运行时系统都不知道你想用类型做些什么, 以及应该采用什么样的规则。
此处输入图片的描述
如上图所示,flist现在是一个“任何从Fruit继承的类型的列表”,但是,这并不意味着这个类型将持有任何类型的Fruit(和List不同),通配符引用的是明确的类型,因此,它意味着“某种flist引用,没有指定具体的类型”。此时,你不能向flist中添加任何对象(Fruit、Apple),即使是Object也不行,因为编译器并不知道flist指定的具体类型究竟是什么,因为类型信息都被擦除了。编译器将拒绝对参数列表中设计通配符方法的作用,比如add()。
此处输入图片的描述
另一方面,如上图所示,你可以从flist中返回一个Fruit,因为编译器知道flist中任何对象都一定是一个Fruit。又比如contains()和indexOf(),参数类型是Object,可以被调用。
8.2、逆变
  可以在走另一条路,即使用超类型通配符,可以申明通配符是由某个特定类的任何基类来确定边界,方法是指定<? super Myclass>,甚至可以<? super T>(不能申明<T super Myclass>)。
此处输入图片的描述

  如上图所示,此时Apple是下界,向apples中添加Apple或Apple的子类是安全的,添加Fruit是不安全的。

此处输入图片的描述

writeExact()使用了确切参数类型(无通配符),此时Apple只能放入List<Apple>中,不允许放入List<Fruit>中。在writeWithWildcard()中,其参数现在是List<? super T >,因此这个List将持有T导出的某种类型,这样就可以安全的将一个T类型的对象或者从T中导出的任何对象作为参数传递给List的方法。

9、问题

9.1、任何基本类型都不能作为类型参数
  
9.2、实现参数化接口
  一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。
9.3、转型和警告
  
9.4、重载
  
9.5、基类劫持了接口
  

10、自限定类型

  class SelfBound<T extends SelfBound<T>>

class SelfBound<T>{}class GenSelfBound extends SelfBound<GenSelfBound>{}

  上述代码是一个没有自限定的简单版本,意思是“创建一个新类,它继承自一个泛型类型,该泛型类型的接收创建的类作为其参数”。这个泛型基类能实现什么呢?java中的泛型关乎参数和返回类型,因此它能够产生使用导出类作为其参数和返回类型的基类。
此处输入图片的描述
  BasicHolder是一个普通的泛型类,它的一些方法将接受和产生带有其参数类型的对象,还有一个方法在其存储的域上进行操作(尽管只是进行Object操作)。
此处输入图片的描述
  在这里,新类Subtype接受的参数和返回的值具有Subtype类型而不仅仅是基类BasicHolder类型。这是CRG的本质:基类用导出类代替其参数。这意味着泛型基类变成了所有导出类的公共模板,但是这些功能对于其所有参数和返回值,将使用导出类型。也就是说,在所产生的类中使用确切类型而不是基类型。
10.1、自限定
  自限定采取额外的步骤,强制泛型当作其自己的边界参数来使用。自限定的参数有何意义呢?它保证了类型参数必须与正在被定义的类相同。
10.2、参数协变
  自定义类型的价值在于它们可以产生协变参数类型———方法参数随子类而变化。自限定泛型实际上将产生确切的导出类型作为其返回值。

11、动态类型安全、异常、混型

  

0 0
原创粉丝点击