参考书籍:《Data Structures and Algorithm Analysis in Java》3rd — Mark Allen Weiss
动机:
在一组数据中取出特定值,这就是选择问题。选择问题的解法非常多,但能够在实践中能有效地运行下去却不多。我们必须不断改进并突破程序运行的速度、瓶颈,才能应付规模日渐庞大的数据。
基础知识复习:
-
递归
四条基本法则:
- 基准情形:必须有一个无需递归即可知道的先验基准。如
- 不断推进:递归过程必须不断向基准情形靠近
- 设计法则:所有的递归情况都应该是合法的
- 合成效益法则:不应该在递归中多次重复同一工作
- 基准情形:必须有一个无需递归即可知道的先验基准。如
-
泛型
pre-Java 5:使用类继承的方法实现泛型
基本思想:用Object这个超类表示所有的类型。如果是基本类型,就需要包装类如Integer(在Java 5+支持自动装箱/拆箱)
问题:仅仅只是表示以及调用一些Object方法并不能满足所有的需求,如比较两个类型的Value
解决方法:继承Comparable接口,该接口需要实现compareTo(T o)
函数。这种方法涉及到对原类代码的扩展,可以创建子类实现接口或者取得源代码。创建子类或许会破坏原项目的接口协议,而即使能够取得源代码且有理由进行修改-------仅有的一个compareTo
函数依然只能支持对类的有限实例变量进行比较。为了能够自由地对类的各种实例变量进行比较,可以实现Class::Comparator
作为比较类因子。要进行多少种不同比较需求就创建相应数目的比较类即可。
要点:
(1)只有实现了Comparable接口的类才是被接收的-------不管这个接口是官方定义还是个人定义的接口
(2)包装类已实现官方接口但基础类型不能实现因此必须进行包装
Java 5+:提供对泛型方法和类的支持
数组兼容性:
由于多态性,函数能够接受子类数组作为参数传入,但是这可能导致数组被污染----如果代码中涉及到元素的添加。
例如public void go(){ Dog[] dogs = {new Dog(), new Dog(), new Dog()}; takeAnimals(dogs); } public void takeAnimals(Animal[] animals) { animals[0] = new Cat(); }
在这种情况下,编译时就会发生错误。如果是ArrayList之类的容器,则能过编译,但是运行时报错。可以用万用字符/通配符 ? 作为容器类型,使用这个字符后,编译器会在编译时阻止任何企图向父类容器中加入新元素的语句,也就是说,只能调用方法,但不能添加删除元素。
类型界限:
作用是限制泛型必须实现某个接口或继承某个类(都是用extends)最简单的形式:
<T extends Comparable>
但这样更好,限制更明确:
<T extends Comparable<T>>
这样最好:
<T extends Comparable<? super T>>
这种声明的好处在于:如果T因为某种原因没有实现Comparable,但是他的父类实现了且满足T这个子类的实际需求,那么**? super T**就指示编译器:T或T的任意父类K实现 Comparable<T> 接口就行了,T本身可以不实现。
类型擦除:
在进入JVM之前,泛型会被转译为object型或者类型界限(extend/super) 。这种特性可以得出一个推论:使用泛型和不使用泛型(单纯利用多态)其本质运行速度并不会有太大区别,而使用泛型的好处就在于程序员不必自己在代码中进行多态的类型检验
泛型限制:-
不能使用基本类型作为类型参数
-
类型检测只会对原始类型进行检测,如果用到泛型容器->那么可以随意地转换类型,然而转换后的方法调用却可能会出错
-
static语境
不能在static方法中使用类的泛型类型,因为static方法全局只有一个,不知道该使用哪个实例的类型 -
泛型类型实例化
不能用泛型类型进行实例化,如T obj = new T();
这是因为类型擦除后可能无法调用限界类的构造函数(如私有或无意义)
-
泛型数组对象
不能创建泛型数组,原因是类型擦除后上下文不一致 -
参数化类型的数组
-
-
函数对象
一个没有数据而只有一个方法的类,被称作函数对象。创建对象的目的就是为了把函数作为参数传递进别的函数中。典型的例子就是Comparator比较类因子