学习廖雪峰的网站中java集合部分和其他资料中集合部分的学习记录。
List
1、在java9中添加了List新的初始化方法:List<> list = List.of();但是使用这种方法初始化的list是定长的,不能在使用add()函数添加元素,返回的list是只读的。
2、遍历list的方法最高效的是使用Iterator。
public class Main {
public static void main(String[] args) {
List<String> list = List.of("apple", "pear", "banana");
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
}
}
要记住,通过Iterator遍历List永远是最高效的方式。并且,由于Iterator遍历是如此常用,所以,Java的for each循环本身就可以帮我们使用Iterator遍历。
3、List to array的方法
第一种:直接调用toArray()
List<String> list = List.of("apple", "pear", "banana");
Object[] array = list.toArray();
这种方法会丢失类型信息,所以实际应用很少。
第二种:
给toArray(T[])传入一个类型相同的Array,List内部自动把元素复制到传入的Array中:
List<Integer> list = List.of(12, 34, 56);
Integer[] array = list.toArray(new Integer[3]);
第三种:
实际上可以传入其他类型的数组,例如我们传入Number类型的数组,返回的仍然是Number类型:
List<Integer> list = List.of(12, 34, 56);
Number[] array = list.toArray(new Number[3]);
但是,如果我们传入类型不匹配的数组,例如,String[]类型的数组,由于List的元素是Integer,所以无法放入String数组,这个方法会抛出ArrayStoreException。
如果我们传入的数组大小和List实际的元素个数不一致怎么办?根据List接口的文档,我们可以知道:如果传入的数组不够大,那么List内部会创建一个新的刚好够大的数组,填充后返回;如果传入的数组比List元素还要多,那么填充完元素后,剩下的数组元素一律填充null。
一种简洁的写法:Integer[] array = list.toArray(Integer[]::new); //也是java8之后的写法,具体 开始的版本还没有查到
4、array to list
第一:jdk11之前,使用Arrays.asList(),但是这种方法返回的并不是ArrayList或者LinkedList。他返回的list实际上是在Arrays中的一个内部ArrayList类,如果执行正常ArrayList一些操作会报java.lang.UnsupportedOperationException错误。
第二:jdk11之厚,直接使用List.of()就行。
第三种:使用流操作。如果array是Integer[],
Arrays.stream(array)
.collect(Collectors.toList())
如果是int[],
Arrays.stream(array)
.boxed()
.collect(Collectors.toList())
编写equals
如何正确编写equals()方法?equals()方法要求我们必须满足以下条件:
- 自反性(Reflexive):对于非null的x来说,x.equals(x)必须返回true;
- 对称性(Symmetric):对于非null的x和y来说,如果x.equals(y)为true,则y.equals(x)也必须为true;
- 传递性(Transitive):对于非null的x、y和z来说,如果x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)也必须为true;
- 一致性(Consistent):对于非null的x和y来说,只要x和y状态不变,则x.equals(y)总是一致地返回true或者false;
- 对null的比较:即x.equals(null)永远返回false。
因此,我们总结一下equals()方法的正确编写方法:
- 先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
- 用instanceof判断传入的待比较的Object是不是当前类型,如果是,继续比较,否则,返回false;
- 对引用类型用Objects.equals()比较,对基本类型直接用==比较。
Map
- HashMap=数组+链表+红黑树,访问速度快
- HashMap扩容相关的属性:capacity:当前数组容量,始终保持2^n,可以扩容,扩容之后数组大小为当前的2倍。loadFactory:负载因子,默认为0.75.threshold:扩容的阈值,等于capacity*loadFactory。
- 在Java8中,当链表中的元素超过8个以后,会将链表转换为红黑树。
- TreeMap可排序,LinkedHashMap记录 插入顺序
遍历Map
第一种:对Map来说,要遍历key可以使用for each循环遍历Map实例的keySet()方法返回的Set集合,它包含不重复的key的集合:
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 123);
map.put("pear", 456);
map.put("banana", 789);
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
}
}
第二种:
同时遍历key和value可以使用for each循环遍历Map对象的entrySet()集合,它包含每一个key-value映射:
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 123);
map.put("pear", 456);
map.put("banana", 789);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
}
}
与List不同的是,Map存储的是key-value的映射关系,并且,它不保证顺序
在Map的内部,对key做比较是通过equals()实现的,这一点和List查找元素需要正确覆写equals()是一样的,即正确使用Map必须保证:作为key的对象必须正确覆写equals()方法。
因此,正确使用Map必须保证:
- 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true;
- 作为key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循以下规范:
- 如果两个对象相等,则两个对象的hashCode()必须相等;
- 如果两个对象不相等,则两个对象的hashCode()尽量不要相等。
我们在计算hashCode()的时候,经常借助Objects.hash()来计算
EnumMap
如果作为key的对象是enum类型,那么,还可以使用Java集合库提供的一种EnumMap,它在内部以一个非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费。
TreeMap
还有一种Map,它在内部会对Key进行排序,这种Map就是SortedMap。注意到SortedMap是接口,它的实现类是TreeMap。
SortedMap保证遍历时以Key的顺序来进行排序。
使用TreeMap时,放入的Key必须实现Comparable接口。String、Integer这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。
如果作为Key的class没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法。