Java高级特性
数组
在Java中,数组是一串连续的,不可改变长度的,对象被固定的,类型固定的连续空间。数组中的随机访问非常迅速,但为了速度放弃了灵活性。而效率也是数组最大的优点。
在使用泛型的容器中,Java通过储存Object对象储存数据,因此数组有了自己的优点,也就是编译期的类型检查,因此可以避免一些类型错误。
数组可以持有基本类型,而容器只能持有基本类型的包装类。
数组的使用
数组标识符实际上只是一个引用,数组对象储存在内存中,可以通过{Obj1,Obj2...}在数组声明处隐式地创建数组对象或者使用new进行创建。length成员表明数组的长度。[]语法是唯一的数组对象访问方法。
数组引用与其他非基本类型的引用使用方法一致。数组对象中的元素会被自动初始化为相应类型的空值或者0值或者false。
在Java中,可以将多个方法返回值包装进数组中然后返回。这也是Java的特性,传递的都是引用,而数组的引用与其他引用并不不同。
数组常用方法有:
Arrays.fill()
System.arraycopy()
Arrays.equals()
Arrays.sort()和比较器/比较接口
Array.binarySearch()
多维数组
Java的多维数组就是上一维数组的引用放进一个一维数组中进行叠加。
Array.deepToString()方法对基本类型数组和对象数组都有用,都可以将其转化为String对象。
数组与泛型
数组不能储存泛型类型,但是数组可以泛型其引用本身。也可以将泛型容器放置到数组中(因为数组是类型检查的,而泛型是没有类型的,但泛型容器是有类型的)。
将数组声明为Objecr[]就可以将基本类型以外的所有类型放入数组中,而不会有问题出现。
容器深入认识
容器比数组更加好用,数组有的方法在容器中基本上都有迹可循,比如fill()方法。容器还有一些其他的方法,比如addAll()。
容器可以用另一个容器去填充,或者是使用自定义的生成器方法填充。
Collection没有get()方法,因为它包含set类,而对于自己维护内部顺序的set来说,随机访问是没有意义的。
容器提供了大量方法,具体内容可以在文档中查询:java 8 文档
可选操作
为了避免容器接口过多和接口过剩,Java允许接口被定义为可选操作,实现类并不需要为这些方法提供功能。
对于绝大多数容器来说,大多数操作都是“共性”,是都可以被支持的,而有一种少见的情况,就是“未获支持的操作”异常,在运行时会发生,常见于数据结构尺寸固定的容器,比如List。
set
Set内部的每个元素都是唯一的。set必须在存入的数据类型中创建equals()方法,而为了良好的编程风格,应该同时覆盖hashCode方法。
HashSet是为了快速查找而设计的Set,为了效率,HashSet是首选。
TreeSet是保持次序的Set,对于TreeSet,因为其内部排序,要实现存入对象的compareAble()接口。
SortedSet可以保证元素处于排序状态,其内部排序按照比较函数结果确定,如果要保证元素插入顺序,那么可以使用LinkedHashSet。
Queue
队列按照插入的顺序抽取出来,遵循先进先出的规则。
PriorityQueue是优先级队列,按照比较函数确定的优先级,对队列内容进行排序。
Map
Map中按照“键-值”的方式储存数据,Map的基本实现包括HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap。
Map中的get()方法使用线性搜索,因此效率较低。而HashMap则基于Hash的方法取代了对键的顺序搜索(Hash值来自类的hashCode()方法),更为高效。
- HashMap插入和查询的开销是固定的,可以通过构造器设置容量和负载因子,可以调整容器性能。
- LinkedHashMap类似于HashMap,但在迭代时取得的键值对按照插入顺序或者最近最少使用顺序,仅比HashMap慢一点而且迭代操作更快,内部结构由链表维护。
- TreeMap基于红黑树实现,内部实现排序,得到的结果是有序的,可以通过subMap()方法得到一个子树。
- WeakHashMap可以释放映射所指向的对象,如果映射之外没有引用指向某个键,则键会被垃圾处理器回收。
- ConcurrentHashMap,线程安全的Map。
- IdentityHashMap,使用
==
对键进行比较的散列映射。
当使用自己的类作为HashMap键时,必须同时重载hashCode()和equals()方法。
正确的equals()方法应必须满足已下5个条件:
- 自反性,自己与自己比较为true
- 对称性,x与y为true,那么y与x也为true
- 传递性,x与y为true,y与z为true,那么x与z也为true
- 一致性,对于x和y,只要等价比较信息没变,那么无论调用多少次,结果保持一致
- 对于任何不是null的对象,与null比较一定返回false
具体的Hash思想与数据结构中学的一样。
HashMap的性能因子:
容量:表中的桶位数
初始容量:表在创建时拥有的桶位数
尺寸:表中当前储存的项数
负载因子:尺寸/容量,表示表中满和空的程度,半满的时候负载因子为0.5,负载越低冲突的概率越小,当负载因子增加到某个水平(默认为0.75)时,容器会加大其容量,然后重新将现有的对象分布到新桶位集中(这叫做再散列)。
容器的选择
默认使用ArrayList,仅当存在随机插入和删除操作时,才使用LinkedList,如果时固定数量元素,那么可以使用List或者数组。
默认使用HashSet,当需要一个排好序的Set或者需要迭代的时候,使用TreeSet,需要按照插入顺序的时候使用LinkedHashSet。
默认使用HashMap,当需要Map保持有序时使用TreeMap,LinkedHashMap在需要保持插入顺序和迭代时使用。IdentityHashMap使用==
进行比较,所以用处不同。
容器的不可修改
Java中提供了Collections.unmodifiableCollection()方法,该方法可以返回一个传入容器的不可修改版本。
同步控制
Java中同步控制可以使用synchronized
关键词,Collections类中有synchronizedCollection()方法可以返回自动同步的类型。
若其他进程在本进程使用容器时修改容器对象,Java会采用快速报错机制,抛出ConcurrentModificationException
。
ConcurrentHashMap,CopyOnWriteArrayList和CopyOnWriteArraySet可以避免该异常。
持有引用
java.lang.ref类包含了为垃圾回收提供灵活性的类,SoftReference、WeakReference和PhantomReference,即软引用、弱引用和虚引用。平时这种SoftReference会正常使用,而当内存濒临耗尽等情况出现时,垃圾回收器会将其释放,而不是像其他对象一样继续被持有。弱引用对象生命周期比软引用短,当弱引用被垃圾回收进程发现时,就会被直接回收。虚引用只是用来跟踪对象被垃圾回收器回收的活动,完全不影响对象生命周期。
如软引用和弱引用可以选择是否放入ReferenceQueue,虚引用依赖于ReferenceQueue。
weakHashMap是一种特殊的Map,被用来保存WeakReference,我理解是说这个Map中储存的键是弱引用的,Map中的键如果没有其他强引用则会被GC每次回收动作回收掉,因此可以用作缓存。WeakHashMap允许垃圾回收器自动清理键。