泛型与容器
**泛型是计算机程序中一种重要的思维方法,它将数据结构与算法 与 数据类型分离。**使得能将一套数据结构与算法应用于各种数据类型。【安全性&可读性】
泛型作用:复用代码,降低耦合,提高可读性和安全性,泛型最常用于容器类,Java泛型的引入更好地支持了容器。
泛型就是类型参数化,处理数据的类型不是固定的,可以作为参数传入
泛型类
类名,T表示类型参数,一般是最终作用于属性、方法上,也可以传入多个类型参数。
泛型内部原理
泛型对于JVM来说是透明的,都被编译器处理好了,最终以Object的形式传给JVM。
泛型优势
1、可读性
2、安全性:减少类型转换异常的发生,把运行时异常提前到编译期
泛型方法
泛型方法的概念是独立于泛型类的。
泛型方法的定义 : 放于返回值前,也可以有多个参数,调用方法时不用特意指定参数的实际类型,编译器会根据传入参数判断出来。
public static void method(T t){}
public static <U,V> void method(U u,V v){}
泛型接口
public interface A{
public int method(T);
}
接口的实现类应该指定具体的类型。
泛型参数的限定
extends:指定上界
上界是类:传入参数只能是上界及上界的子类。类型擦除后转为上界而不是Object。
上界是接口:常见应用场景,限定类型必须实现某个接口。
T必须实现Comparable接口
上界为其他类型参数:
Integer是Number的子类,但是 ArrayList 不是 ArrayList的子类。
解决方式及使用:
通配符
vs <? extends E>
类型参数与限定通配符
通配符的重要限制:只能读,不能写
?extends Number :Number或它的某个子类,由于无法确定
不能写入Number 的父类 及 与 Number 无关的类: 对的
不能写入Number或它的某个子类:怎么解决?借助带类型参数的泛型方法来解决
Java容器类就有这样类似的用法。
需要写的场合&参数类型间有依赖关系(如:返回值依赖类型参数),只能用类型参数。
超类通配符super:更灵活的写入或比较,无法用参数类型代替
小总结:通配符使得方法接口更灵活,接收更广泛的类型
<?> , <? extends T>用于灵活的读取,可以使用参数类型代替。超类通配符super:更灵活的写入或比较,无法用参数类型代替
泛型的局限性和细节:
基本类型不能用于实例化泛型参数:因为类型参数会替换为Object,可以使用对应的包装类。
运行时类型信息不适用于泛型:内存每个类都有一份类型信息,类对象有一份类型信息的引用,通过<类型>.class引用。类型信息本身也是对象(class类的对象),因此只有一份,与泛型无关。泛型对象的getClass()返回值与原始类型对象是一样的 <类型>.class
类型擦除可能会引发一些冲突
class Base implements Comparable
class Child extends Base implements Comparable :这种写法是不允许的,会进行类型擦除,实际上只能有一个,但有两个:Comparable与Comparable
Comparable与Comparable 类型擦除后是一样的
可以通过重写compareTo方法实现
定义泛型类,方法与接口:
泛型参数类型不能创建对象:容易引起用户误会,以为可以创建参数类型对象,实际上创建的是Object类型对象,所以就干脆禁止。可以通过反射方式创建指定类型参数对象。
泛型参数类型不能用于静态变量和静态方法:类型擦除后只有一份,且与类型参数无关。
泛型与数组
数组允许转换可能会引起运行时异常
Integer[] ints =new Integer[10];
Object[] objs=ints ;
objs[0]=‘hello’;//非Integer类型 引起运行时异常
如果Java允许创建泛型数组
真不会发生编译错误和运行时错误:options 与 objs 运行时类型都是Pair[],但是确实错了。不知道什么时候会爆发的错。
替代方案
1、数组
2、自己实现一个DynamicArray
3、泛型容器
列表和队列
ArrayList:泛型容器,新建时需要实例化泛型参数
主要方法:
迭代:
只要对象实现了Iterable接口,就可以使用foreach语法,foreach会被转换成迭代器相关的代码
迭代器接口:Iterable,要实现iterator()方法。iterator():返回一个实现了iterator接口的对象
类可以不识现Iterable接口,也可以创建iterator对象
ArrayList实现了Iterable接口,还提供了2个返回iterator接口的方法:
第二个方法:返回的迭代器从指定位置开始
ListIterator 扩展至Iterator接口。新增了一些方法。
迭代器常见的误用:
发生了并发修改异常:迭代器内部会维护索引相关的数据,要求迭代过程中不能发生结构性变化。
迭代器实现原理
ArrayList内部iterator实现记录了 expectedModcount :期望修改的次数,初始化为modcount(外部类当前的修改次数),每次迭代器操作会比较一下这两个值判断是否发生结构性变化。
迭代器优势:
- foreach语法简洁,更具有通用型,适用于各类容器
- 迭代器表示的是一种关注点分离的思想,将数据的实际组织方式与数据迭代遍历相分离【不用关心数据的实际组织形式,以一致的方式进行访问】
- 提供Iterator接口的代码,可以提供高效的实现。
迭代器封装了各种数据的组织方式的迭代操作,提供了简单一致的接口
除了Iterator,ArrayList还实现了Collection、List、Random-Access
Collection:没有位置或顺序概念的数据结合
List:有顺序或位置的数据结合,扩展了Collection
RandomAccess:可随机访问;空接口—标记接口,用于声明类中的一种属性
声明了RandomAccess可以使用一些通用算法代码,可以根据这个声明而选择效率更高的实现
ArrayList返回数组的2个方法:返回类型不同
静态方法。Array.asList():返回的List实现类不是ArrayList而是它的内部类,List内部用的数组就是传入的数组,没有拷贝,也不能动态改变大小,数组改了list也会改,List不能调用add,remove方法,要使用ArrayList的全部方法要使用图2
LinkedList:List【list扩展了collection,collection扩展了Iterable】,Queue
可以把linklist作为queue与stack使用
Java中Stack类的底层实现,stack底层使用的是线程安全的vector,不需要线程安全时使用linklist或者是ArrayDeque
栈和队列:有个更通用的操作两端的接口Deque,继承自queue
Deque还有个用于从后往前遍历的迭代器
实现原理
内部组成
ArrayList,linklist等在新增,删除元素时,维护modCount,记录修改次数,便于迭代中间检测结构性变化
内存是按需分配的,不需要预先分配多余的内存
按索引查找:大于数组容量的一半,从后往前;否则从前往后。
LinkList是list,但也实现了Deque接口,可以作为队列、栈和双端队列使用
LinkList内部是一个双向链表,并维护了长度、头结点和尾结点,特点:按需分配空间,不能随机访问。按索引、内容查找效率低。插入首尾效率高,中间插入因为需要先查找位置索引效率也比较低
ArrayDequq:双端队列的实现类,基于数组实现的,插入删除效率高
实现的Dequq接口扩展自Queue,有队列的所有方法
head和tail使其变为逻辑上循环的数组
ArrayDequq内部维护了一个可扩容的动态数组,通过head和tail维护收尾,数组长度为2的幂次,初始容量小于8:8,大于8:大于且最接近于设定值的2次幂,要保证大于设定值:tail指向的位置是下一个将要插入元素的位置
ArrayList,LinkedList,ArrayDeque:查找元素的效率都比较低,都需要逐个进行比较。
Map和Set的查询效率要高得多
Map和Set:接口
HashMap:键值映射
set 扩展自 collection
添加第一个元素,默认初始化值是16,扩展时机与threshold有关
threshold:阀值 table.length*loadFactor
先比较hash:整数,性能高于equals,查找到相同的key,则替换返回旧值,否则modCount++,调用 addEntry
modCount:只有在结构变化时才会改变,方便在迭代中检测结构性变化
判断size与threshold的大小,判断是否需要扩容,再添加节点
特点:存取都为O(1)
HashMap中的键值对是无序的,因为hash值是随机的
hashcode与equals的关系:equals相等hashcode必然相等。他们之间的关系??他们的定义??
需要重写hashcode的equals方法
去重;保存特殊值;集合运算
原理:利用内部的hashmap。存储元素值作为key,value为固定值
无重复元素,可以高效的添加元素、判断元素是否存在,效率o1,无序
排序二叉树:有序,不重复的二叉树(左子树<右子树)
TreeMap:HashMap的局限性—键值对之间没有特定的顺序,Map接口另一重要实现类TreeMap键值对之间按键有序。
默认构造:要求Map的键实现comparable接口,调用接口中compareTo方法
传入比较器对象:调用comparator对象的compareTo方法
迭代时按键的顺序输出
TreeMap内部是红黑树实现的(大致平衡的二叉树)
面向对象
引用型变量分配在栈中,保存实际内容的地址,实际内容保存在堆中。
类的定义方式与要解决的问题密切相关
包的作用:避免命名冲突,方便模块化开发
java.lang包下的类、同一个包下的类之间可以直接引用,不需要引入也不需要使用完全限定名
静态导入:有一个static关键字,可以直接导入静态方法和静态成员,不能使用太多,会难以区分是哪个类的代码
包范围可见性:
不写访问修饰符,默认可见性同一直接包下
protected:包可见性+子类
jar包:Java编译后将多个包打包为一个文件,命令和后缀均为jar,是一个压缩文件
程序的编译与链接
编译:使用javac命令,有源代码变为.class字节码
链接:在运行时动态执行,使用java命令,解析.class文件,转化为机器能识别的二进制代码,然后运行
根据引用到的类加载对应的字节码并执行
继承相关:
Object是所有对象隐含的父类,没有属性,只有方法。
多态和动态绑定是计算机程序设计的一种重要的思维方式,使得操作对象的程序不需要关注对象的实际类型,从而可以统一处理不同的对象,又能实现每个对象的特有行为
子类可以通过super调用父类的构造函数,如果子类没调用,会自动调用父类的默认构造函数,但是父类没有,会提示编译错误。
在父类构造函数中调用可被子类重写的方法,是一种不好的实践,容易引起混淆,应该只调用private方法。
在类中,访问的是自己的public变量与方法。子类可以通过调用super访问父类
在类外,由静态类型决定
------39
向上转型是安全的(均可转为obj),向下转型可以编译通过,但可能引发运行时异常,一个父类变量能不能转为一个子类的变量,取决于这个变量的动态类型是不是这个子类或者该子类的子类,可用 instanceof判断
protected:同一包下其他类+子类 可访问
通过父类调用了子类重写的方法:模板方法,使用prootected的一种常见场景
子类重写父类方法时,可见性只能升级不可降低。
final修饰类,类方法不能被重写,类不能被继承
继承实现原理
类是动态加载的,只会加载一次,且会先加载父类
类初始化代码,是先执行父类在执行子类的,父类执行时,子类的静态变量也是有值的,默认值