Java 中的容器,Java 开发进大厂面试必备技能
}
public class AppleAndOrangesWithGenerics {
public static void main(String[] args) {
ArrayList<Apple> apples=new ArrayList<Apple>();
for(int i=0;i<3;i++){
apples.add(new Apple());
}
for(Apple apple:apples){
System.out.println(apple.name());
}
}
}
Output:
apple0
apple1
apple2
当你指定了某个类型作为泛型参数时,你并不仅限于只能将确切类型的对象放置到容器中。向上转型也可以像作用于其他类型一样作用于泛型。
因此,可以将 Apple 的子类型添加到被指定为保存 Apple 对象的容器中。
1.1 基本概念
Java 容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
Collection:
一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素,Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)
Map:
一组成对的"键值对(key-value)"对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上将,它将数字与对象关联在了一起。映射表允许我们使用另一个对象来查找对象,它也被称为"关联数组",因为它将某些对象与另外一些对象关联在了一起;或者被称为"字典",因为你可以使用键对象来查找值对象,就像在字典中使用单词来定义一样。Map是强大的编程工具。
理想情况下,编写的代码都是在与这些接口打交道,并且你唯一需要指定所使用精确类型的地方就是在创建的时候。下面创建一个 List: List<Apple> apples =new ArrayList<Apple>();
这里,ArrayList 被向上转型为 List。使用接口的目的在于如果你决定去修改你的实现,所需做的只是在创建处修改它,就像下面这样: List<Apple> apples =new LinkedList<Apple>();
所以一般实际中,我们应该创建一个具体类的对象,将其转型为对应的接口,然后在其余的代码中都使用这个接口。
1.2 添加一组元素:
在 java.util 的 Arrays 和 Collection 类中都有很多实用方法,可以在一个 Collection 中添加一组元素。
Arrays.asList()方法:
接受一个数组或是一个用逗号分隔的元素列表,并将其转换为一个List对象。
Collection.addAll()方法:
接受一个Collection对象,以及一个数组或是一个用逗号分割的列表,并将元素添加到Collection中。
public class AsListInference {
public static void main(String[] args) {
Collection<Integer> collection=new ArrayList<Integer>(Arrays.asList(1,2,3));
Integer []arr={4,5,6};
collection.addAll(Arrays.asList(arr));
Collections.addAll(collection,7,8,9);
Collections.addAll(collection,arr);
List<Integer> list= Arrays.asList(1,2,3);
list.set(1,99);
//容器可以在无需任何帮助下打印
System.out.println(collection);
System.out.println(list);
}
}
Output:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 5, 6]
[1, 99, 3]
Collection.addAll() 方法运行起来很快,而且构建一个不包含元素的 Collection,同时调用 Collections.addAll() 这种方式很方便,因此它是首选方式。但是 Collection.addAll() 成员方法只能接受另一个 Collection 对象作为参数,因此它不如 Arrays.asList() 或 Collections.addAll() 灵活,这两个方法使用的都是可变参数列表。
注意:若使用Arrays.asList()给一个List赋值,需要使用显式类型参数说明,否则会自动寻找它们的共同最浅的基类,这样如果向上转型至根类,则会报错;但是若使用,Collections.addAll()则不存在这个问题。
2.1 List
List 承诺可以将元素维护在特定的序列中。List 接口在 Collection 的基础上添加了大量的方法,使得可以在 List 的中间插入和移除元素。
有两种类型的 List:
1. ArrayList: 它可以随机访问元素,但是在 List 的中间插入和移除元素时较慢;
/**
@Author: Ly
@Date: 2020-08-13 08:46
*/
public class ListFeatures {
public static void main(String[] args) {
List<Integer> li = new ArrayList<Integer>();
for(int i = 0; i < 10; i++)
li.add(i);
Integer h = 10;
li.add(h); //将指定的元素追加到此列表的末尾
li.add(1,0); //将指定的元素插入此列表中的指定位置
li.remove(0); //删除该列表中指定位置的元素
System.out.println(li);
System.out.println(li+"集合中含有:h "+li.contains(h));
li.set(0,10); //用指定的元素替换此列表中指定位置的元素
Integer p = li.get(0);//返回此列表中指定位置的元素
System.out.println("数字"+p + "第一次出现位置的索引为:" + li.indexOf(p)); //打印此列表中指定元素的第一次出现的索引
li.remove(p); //删除指定元素的第一次出现
System.out.println(li);
List<Integer> sub = li.subList(1, 4);//用列表中第 1 到第 4 个(不包含第 4 个)元素创建新的集合
Boolean bl=li.containsAll(sub); //看列表是否包含指定集合的所有元素
System.out.println("集合 li 中"+ (bl?"含有":"不含有") +"sub 中的所有元素");
List<Integer> copy = new ArrayList<Integer>(li);
copy.removeAll(sub); //从此列表中删除包含在指定集合中的所有元素
System.out.println("copy 集合:"+copy);
if(copy.size() > 1) //如果列表中的元素数大于 1
copy.addAll(2, sub);//将指定集合中的所有元素插入到此列表中的指定位置
System.out.println("copy 集合:"+copy);
Object[] objects = li.toArray();//以正确的顺序返回一个包含此列表中所有元素的数组
for(Object o:objects){
System.out.print(o+", ");
}
System.out.println(li.isEmpty());//如果此列表不包含元素,则返回 true
li.clear();//从此列表中删除所有元素
System.out.println(li+":"+ li.isEmpty());
}
}
Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]集合中含有: h true
数字 10 第一次出现位置的索引为:0
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
集合 li 中含有 sub 中的所有元素
copy 集合:[1, 5, 6, 7, 8, 9, 10]
copy 集合:[1, 5, 2, 3, 4, 6, 7, 8, 9, 10]
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, false
[]:true
2. LinkedList: 它通过代价较低的在 List 中间插入和删除操作,提供了优化的顺序访问。LinkedList 在随机访问方面相对比较慢,但是它的特性集较 ArrayList 更大。
例:
/**
@Author: Ly
@Date: 2020-08-13 12:24
*/
public class LinkedListFeatures {
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<Integer>();
for (int i = 1; i < 10; i++) {
list.add(i);
}
System.out.print(list.getFirst()+" "); //返回此列表中的第一个元素,为空时抛出异常
System.out.print(list.element()+" "); //检索但不删除此列表的头,为空时抛出异常
System.out.print(list.peek()+" "); //检索但不删除此列表的头,为空时返回 null
System.out.print(list.removeFirst()+" "); //检索并删除此列表的头,为空时抛出异常
System.out.print(list.remove()+" "); //检索并删除此列表的头,为空时抛出异常
System.out.print(list.poll()+" "); //检索并删除此列表的头,为空时返回 null
System.out.println();
System.out.println(list);
list.addFirst(0); //在该列表开头插入指定的元素
list.offer(10); //将指定的元素添加为此列表的末尾
list.add(11); //将指定的元素追加到此列表的末尾
list.addLast(12); //将指定的元素追加到此列表的末尾
list.removeLast(); //从此列表中删除并返回最后一个元素。
System.out.println(list);
}
}
Output:
1 1 1 1 2 3
[4, 5, 6, 7, 8, 9]
[0, 4, 5, 6, 7, 8, 9, 10, 11]
LinkedList 也像 ArrayList 一样实现了基本的 List 接口,但是它执行某些操作时比 ArrayList 更高效,但是随机访问操作方面要差一些。LinkedList中添加了可以使其用作栈、队列或双端队列的方法。
3. Stack:
“栈”通常是指“后进先出(LIFO)”的容器
,有时也称为叠加栈,LinkedList 具有能够直接实现栈的所有功能的方法,因此可以直接将 LinkedList 作为栈使用。
/*
通过使用泛型,引入了在栈的类定义中最简单的可行示例。
类名之后的<T>告诉编译器这将是一个参数化类型(在类被使用时将会被实际类型替换的参数)。
Stack 使用 LinkedList 实现的,而 LinkedList 也被告知它将持有 T 类型对象;
*/
class Stack<T> {
private LinkedList<T> storage=new LinkedList<T>();
public void push(T v){storage.addFirst(v);}
public T peek() {return storage.getFirst();}
public T pop () {return storage.removeFirst();}
public boolean empty () {return storage.isEmpty(); }
public String toString (){return storage.toString();}
}
public class StackTest{
public static void main(String[] args) {
Stack <String> stack = new Stack <String> ();
for (String s : "One Two Three".split(" "))
stack.push(s);
while (!stack.empty())
System.out.print(stack.pop()+" ");
}
}
Output:
Three Two One
2.2 Set
Set不保存重复的元素;
Set 中最常用的是测试归属性,可以很容易地询问某个对象是否在某个 Set 中。正因如此,查找成了 Set 中最重要的操作,因此通常会选择一个 HashSet 的实现,它专门对快速查找进行了优化。(Hash查找最快。)
/**
@Author: Ly
@Date: 2020-08-14 19:27
*/
public class SetOfInteger {
public static void main(String[] args) {
Random rand=new Random(47);
Set<Integer> intSet =new HashSet<Integer>();
//如果想对结果排序,使用 TreeSet 来替代 HashSet,不过在测试 HashSet 时你会发现打印出来的也是排序好的结果
//SortedSet<Integer> intSet =new TreeSet<Integer>();
for (int i = 0; i<10000 ; i++){
intSet.add(rand.nextInt(30));
};
System.out.println(intSet);
//使用 contains()测试 Set 的归属性
System.out.println(intSet.contains(5));
System.out.println(intSet.contains(30));
}
}
Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
true
false
TreeSet 将元素存储在红-黑数据结构中,而 HashSet 使用的是散列函数。LinkedHashSet 因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。
2.3 Map
将对象映射到其他对象的能力是一种解决编程问题的杀手锏;
考虑一个程序,它将用来检查 Java 的 Random 类的随机性。理想情况下,Random 可以产生理想的数字分布,但要想测试它,则需要生成大量的随机数,并对落入各种不同范围的数字进行计数。Map 可以很容易地解决该问题。在这个问题中,键是由 Random 产生的数字,值是该数字出现的次数:
/**
@Author: Ly
@Date: 2020-08-14 20:38
*/
public class Statistics {
public static void main(String[] args) {
Random rand=new Random(47);
Map<Integer,Integer> m=
new HashMap<Integer,Integer>();
for(int i= 0; i <10000 ;i++){
int r=rand.nextInt(20);
Integer freq = m.get(r);
m.put(r,freq==null ? 1 : freq+1);
}
System.out.println(m);
//containsKey()和 containsValue()来测试 Map,以便查看它是否包含某个键或某个值:
System.out.println(m.containsKey(10));
System.out.println(m.containsValue(500));
}
}
Output:
{0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519, 7=471, 8=468, 9=549,
10=513, 11=531, 12=521, 13=506, 14=477, 15=497, 16=533, 17=509, 18=478, 19=464}
true
false
Map 与数组和其他的 Collection 一样,可以很容易地扩展到多维,而我们只需将其值设置为 Map(这些 Map 的值可以是其他容器,甚至是其他 Map)。因此,能够很容易地将容器组合起来从而快速地生成强大的数据结构。
class Person{
private String name;
Person(String name){
this.name=name;
}
@Override
public String toString() {
return name;
}
}
public class MapOfList {
public static Map<Person , List<String>>
petPeople = new HashMap<Person,List<String>>();
static{
petPeople.put(new Person("Dawn"),
Arrays.asList ("Cymric_Molly","Mutt_Spot"));
petPeople.put(new Person("Kate"),
Arrays.asList ("Cat_Shackleton", "Cat_Elsie", "Dog_Margret"));
petPeople.put(new Person("Marilyn"),
Arrays.asList("Pug_Louie", "Cat_Negro", "Cat_Pinkola"));
petPeople.put (new Person("Luke"),
Arrays.asList("Rat_Fuzzy", "Rat_Fizzy"));
petPeople.put(new Person ("Isaac"),
Arrays.asList("Rat_Freckly"));
}
public static void main(String[] args) {
System.out.println("People: "+ petPeople.keySet());
System.out.println("Pets: "+ petPeople.values());
for(Person person : petPeople.keySet()){
System.out.print(person + " has : ");
for (String str : petPeople.get(person))
System.out.print(" "+str);
System.out.println();
}
}
}
Output:
Luke has : Rat_Fuzzy Rat_Fizzy
Marilyn has : Pug_Louie Cat_Negro Cat_Pinkola
Isaac has : Rat_Freckly
Dawn has : Cymric_Molly Mutt_Spot
Kate has : Cat_Shackleton Cat_Elsie Dog_Margret
Map 是一种将对象(而非数字)与对象相关联的设计。HashMap 设计用来快速访问;而 TreeMap 保持“键”始终处于排序状态,所以没有 HashMap 快。LinkedHashMap 保持元素插入的顺序,但是也通过散列提供了快速访问能力。
2.4 Queue
队列是一个典型的先进先出(FIFO)的容器。
即从容器的一端放入事物,从另一端取出,并且将事物放入容器的顺序与取出的顺序是相同的。队列常被当作一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要。
LinkedList 提供了方法以支持队列的行为,并且它实现了 Queue 接口,因此 LinkedList 可以用作 Queue 的一种实现。通过将 LinkedList 向上转型为 Queue,下面的示例使用了在 Queue 接口中与 Queue 相关的方法:
/**
@Author: Ly
@Date: 2020-08-14 21:00
*/
public class QueueDemo {
public static void printQ(Queue queue) {
//peek(),element(): 在不移除的情况下返回队头
//peek():在队列为空时返回 null
//element():在队列为空时抛出 NoSuchElementException 异常
while(queue.peek()!= null)
System.out.print(queue.remove()+" ");
System.out.println();
}
public static void main(String[] args) {
Queue <Integer> queue = new LinkedList<Integer>();
Random rand = new Random(47);
for(int i=0;i<10;i++)
//offer(): 在允许的情况下,将一个元素插入到队尾,或者返回 false
queue.offer(rand.nextInt(i+10));
printQ(queue);
Queue <Character> qc= new LinkedList<Character>();
for(char c :"Brontosaurus".toCharArray())
qc.offer(c);
System.out.println(qc);
}
}
Output:
3 10 7 0 10 3 6 7 4 5
[B, r, o, n, t, o, s, a, u, r, u, s]
Queue 接口窄化了对 LinkedList 的方法的访问权限,以使得只有恰当的方法才可以使用,因此,你能够访问的 LinkedList 的方法会变少。注意:与 Queue 相关的方法提供了完整而独立的功能。即,对于 Queue 所继承的 Collection,在不需要使用它的任何方法的情况下,就可以拥有一个可用的 Queue。
PriorityQueue:
先进先出描述了最典型的队列规则。队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。
先进先出声明的是下一个元素应该是等待时间最长的元素。
优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级)。如果构建了一个消息系统,某些消息比其他消息更重要,因而应该更快地得到处理,那么它们何时得到处理就与它们何时到达无关。
/**
@Author: Ly
@Date: 2020-08-14 21:46
*/
public class PriorityQueueDemo {
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue= new PriorityQueue <Integer>();
Random rand= new Random (47);
for(int i=0;i<10; i++)
priorityQueue.offer(rand.nextInt(i+10));
QueueDemo.printQ(priorityQueue);
List<Integer> ints = Arrays.asList(25,22,20,18,14,9,3,1,1,2,3,9,14,18,21,23,25);
priorityQueue=new PriorityQueue<Integer>(ints);
QueueDemo.printQ(priorityQueue);
priorityQueue=new PriorityQueue<Integer>(ints.size(), Collections.reverseOrder());
priorityQueue.addAll(ints);
QueueDemo.printQ(priorityQueue);
String fact = " EDUCATION SHOULD ESCHEW OBFUSCATION ";
List<String> strings =Arrays.asList(fact.split(" "));
PriorityQueue<String> stringPQ =new PriorityQueue<String>(strings);
QueueDemo.printQ(stringPQ);
stringPQ= new PriorityQueue<String>(
strings.size(),Collections.reverseOrder());
评论