集合
java.util提供了集合类,包括
Collection:根接口
List:有序列表
Set:无重复元素集合
Map:通过Key查找Value的映射表
Java集合支持泛型,通过迭代器(Iterator)访问集合
Java集合设计的特点:
1)接口和实现相分离
2)支持泛型
3)访问集合有统一的方法(Iterator)
JDK的部分集合类时遗留类,不应该继续使用
1)Hashtable:一种线程安全的Map实现
2)Vector:一种线程安全的List实现
3)Stack:基于Vector实现的LIFO的栈
4)Enumeration\<E>:已被Iterator\<E>取代
List
使用List
List是一种有序列表,通过索引访问元素
1)void add(E e):在末尾添加一个元素
2)void add(int index, E e):在指定索引位置添加一个元素
3)int remove(int index):删除指定索引位置的元素
4)int remove(Object e):删除某个元素
5)E get(int index):获取指定索引的元素
6)int size():获取链表大小(包含元素的个数)
List有ArrayList和LinkedList两种实现
ArrayList | LinkedList | |
---|---|---|
获取指定元素 | 速度很快 | 需要从头开始查找元素 |
添加元素到末尾 | 速度很快 | 速度很快 |
在指定位置添加/删除 | 需要移动元素 | 不要移动元素 |
内存占用 | 少 | 较大 |
List中的元素可以重复,元素也可以是null
遍历List使用Iterator或者foreach循环
把List转换为Array的方法
1)Object[] toArray()
2) T[] toArray(T[] a)
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Integer[] array = list.toArray(new Integer[2]);
// {1, 2, 3}
Integer[] array = list.toArray(new Integer[5]);
// {1, 2, 3, null, null}
// 推荐===> Integer[] array = list.toArray(new Integer[list.size()]);
把Array转换为List的方法
1) List Arrays.asList(T… a)
Integer[] array = {1, 2, 3};
List<Integer> list = Arrays.asList(array);
// 注意返回的list并不是ArrayList
// list.add(4); // UnsupportedOperatationException
List<Integer> arrayList = new ArrayList<>(); // ^_^
arrayList.addAll(list); // ^_^
编写equals方法
List是一种有序链表,List内部按照放入元素的先后顺序存放,每个元素都可以通过索引确定自己的位置
判断元素是否存在或者查找元素索引:
1)boolean contains(Object o):是否包含某个元素
2)int indexOf(Object o):查找某个元素的索引,不存在返回-1
要正确调用contains、indexOf方法,放入的实例要正确实现equals(),因为这两个方法底层是用equals比较实现的
equals()编写方法:
1)判断 this == o
2)判断 o instanceof Person
3)强制转型,并比较每个对应的字段
基本类型字段用 == 直接比较
引用类型字段借助Objects.equals()判断
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof Person) {
Person p = (Person) o;
// return (p.name==this.name || p.name!=null &&
// p.name.equals(this.name)) && p.age==this.age;
return Objects.equals(p.name, this.name) && p.age == this.age;
}
return false;
}
}
public class Main {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("aa", 12));
list.add(new Person("bb", 34));
list.add(new Person("cc", 56));
System.out.println(list);
System.out.println(list.contains(new Person("cc", 56))); // true
}
}
Map
使用Map
Map是一种键值映射表,可以通过Key快速查找Value
常用方法:
1)V put(K key, V value):把Key-Value放入Map
2)V get(K key):通过Key获取Value
3)boolean containsKey(K key):判断Key是否存在
遍历Map用for...each循环:
1)循环Key:keySet()
2)循环Key和Value:entrySet()
常用的实现类:
HashMap:不保证有序(遍历时的顺序不一定是put放入时的顺序,也不一定是Key的排序顺序)
SortedMap:保证按Key排序,实现类有TreeMap
TreeMap按元素顺序排序,可以自定义排序算法
Map<String, Integer> map = new TreeMap<>(new Comparator<String>(){
public int compare(String s1, String s2){
return -s1.compareTo(s2); // 按字母倒序
}
})
编写equals和hashCode
正确使用Map必须保证:
1)作为Key的对象必须正确覆写了equals()方法
2)作为Key的对象必须正确覆写了hashCode()方法
覆写hashCode:
1)如果两个对象相等,则两个对象的hashCode必须相等
2)如果两个对象不相等,则两个对象的hashCode尽量不相等(可能相等,会造成效率下降)
hashCode可以通过Objects.hashCode()辅助方法实现
@Override
public int hashCode() {
return Objects.hash(this.name, this.age);
}
@Override
public boolean equals(Object obj) {
if(obj == this){
return true;
}
if(obj instanceof Person){
Person p = (Person) obj;
return Objects.equals(this.name, p.name) && this.age==p.age;
}
return false;
}
public static void main(String[] args) {
List<Person> list = Arrays.asList(
new Person("aa", 12),
new Person("bb", 34),
new Person("cc", 56));
Map<Person, String> map = new HashMap<>();
for(Person p : list){
map.put(p, p.getName());
}
System.out.println(map.get(new Person("cc", 56)));
}
使用Properties
Properties用于读取配置
1).properties文件只能使用ASCII编码,可以从文件系统和ClassPath读取
2)读取对个.properties文件,后读取的Key-Value会覆盖已读取的Key-Value
Properties实际上是从Hashtable派生,但只需调用getProperty和setProperty
String getProperty(String key); // √
void setProperty(String key, String value); // √
Object get(Object key); // ×
void put(Object key, Object value); // ×
Set
使用Set
Set用于存储不重复的元素集合:
1)boolean add(E e)
2)boolean remove(Object o)
3)boolean contains(Object o)
4)int size()
利用Set可以去除重复元素
放入Set的元素要正确实现equals()和hashCode()
Set不保证有序:
HashSet是无序的
TreeSet是有序的(与TreeMap类似,可以自定义排序算法)
实现了SortedSet接口的是有序Set
练习
请将List的元素去重,但保留元素在List中的原始顺序,即:
[“abc”, “xyz”, “abc”, “www”, “edu”, “www”, “abc”];
去重是应该删除:
[“abc”, “xyz”, “abc” , “www”, “edu”, “www” , “abc” ];
所以结果为
[“abc”, “xyz”, “www”, “edu”];
提示:LinkedHashSet
public static void main(String[] args) {
List<String> list = Arrays.asList("abc", "xyz", "abc", "www", "edu", "www", "abc");
System.out.println(list); // [abc, xyz, abc, www, edu, www, abc]
Set<String> set = new LinkedHashSet<>();
for(String s : list){
set.add(s);
}
System.out.println(set); // [abc, xyz, www, edu]
}
Queue
使用Queue
队列(Queue)是一种先进先出(FIFO)的数据结构
实现类:ArrayDeque、LinkedList
操作Queue的元素的方法:
1)添加至队尾压栈:add()、offer()
2)获取队列头部元素并删除:E remove()、E poll()
3)获取队列头部元素但不删除:E element()、E peek()
4)获取队列长度:size()
两组方法的区别,是当添加或获取元素失败时:
throw Exception | 返回false或null | |
---|---|---|
添加元素到队尾 | add(E e) | boolean offer(E e) |
取队首元素并删除 | E remove() | E poll() |
取队首元素但不删除 | E element() | E peek() |
避免把null添加到队列
扫描二维码关注公众号,回复:
4834707 查看本文章
使用PriorityQueue
PriorityQueue的出队顺序与元素的优先级有关
从队首取元素,总是获取优先级最高的元素
默认按元素比较的顺序排序(必须实现Comparable接口)
可以通过Comparator自定义排序算法(不必实现Comparable接口)
Deque
Deque实现一个双端队列(Double Ended Queue)
1)既可以添加到队尾,也可以添加到队首
2)既可以从队首获取,又可以从队尾获取
3)添加元素到队尾:addLast(E e)、offerLast(E e)
4)取队首元素并删除:E removeFirst()、E pollFirst()
5)取队首元但不删除:E getFirst()、E peekFirst()
总是调用xxxFirst、xxxLast以便与Queue的方法区分开
Deque的实现类:ArrayDeque,LinkedList
Deque<String> obj = new LinkedList<>();
obj.offerLast("z"); // √
LinkedList<String> obj = new LinkedList<>();
obj.offerLast("z"); // ×
List<String> obj = new LinkedList<>();
obj.add("z"); // √
Stack
栈(Stack)是一种后进先出(LIFO)的数据结构
操作栈的元素的方法:
入栈:push(E e)
出栈:pop()
取出栈顶元素但是不出栈:peek()
Java使用Deque实现栈的功能,注意只调用push/pop/peek,避免调用Deque的其他方法
不要使用遗留类Stack
Stack的作用
方法(函数)的嵌套调用
嵌套调用过多会造成栈溢出StackOverflowError
将中缀表达式编译为后缀表达式
public class Calculator {
public Object[] compile(String s) throws IllegalAccessException {
Object[] parsed = parseAsExpression(s);
List<Object> output = new LinkedList<>();
Deque<Character> stack = new LinkedList<>();
for (Object e : parsed) {
if (e instanceof Integer) {
output.add(e);
} else {
char ch = (Character) e;
switch (ch) {
case ')':
for (;;) {
if (stack.isEmpty()) {
throw new IllegalAccessException("Compile error "
+ s);
}
char top = stack.pop();
if (top == '(') {
break;
} else {
output.add(top);
}
}
break;
case '(':
stack.push(ch);
break;
case '+':
case '-':
case '*':
case '/':
while (!stack.isEmpty()) {
char first = stack.peek();
if (priority(first) >= priority(ch)) {
stack.pop();
output.add(first);
} else {
break;
}
}
stack.push(ch);
break;
default:
throw new IllegalAccessException("Compile error: " + s);
}
}
}
while (!stack.isEmpty()) {
output.add(stack.pop());
}
return output.toArray();
}
public int calculate(Object[] expression) {
Deque<Integer> stack = new LinkedList<>();
for (Object e : expression) {
if (e instanceof Integer) {
stack.push((Integer) e);
} else {
char op = (Character) e;
int n1 = stack.pop();
int n2 = stack.pop();
int r = operate(op, n2, n1);
stack.push(r);
}
}
return stack.pop();
}
Object[] parseAsExpression(String s) throws IllegalAccessException {
List<Object> list = new ArrayList<>();
for (char ch : s.toCharArray()) {
if (ch >= '0' && ch <= '9') {
int n = Integer.parseInt(String.valueOf(ch));
list.add(n);
} else if ("+-*/()".indexOf(ch) != (-1)) {
list.add(ch);
} else if (ch == ' ') {
// ignore white space
} else {
throw new IllegalAccessException(
"Compile error: invalid char \'" + ch + "\'");
}
}
return list.toArray();
}
// priority from high to low: '*', '/' > '+', '-' > '('
int priority(char op) throws IllegalAccessException {
switch (op) {
case '*':
case '/':
return 2;
case '+':
case '-':
return 1;
case '(':
return 0;
default:
throw new IllegalAccessException("bad operator: " + op);
}
}
int operate(char operator, int a, int b) {
switch (operator) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return a / b;
default:
throw new UnsupportedOperationException();
}
}
}
public class Main {
public static void main(String[] args) throws IllegalAccessException {
String s = "1 + 2 * (9 - 5)";
Calculator calc = new Calculator();
Object[] exp = calc.compile(s);
int result = calc.calculate(exp);
System.out.println("[calculate] " + s + " => " + expressionToString(exp) + " => " + result);
}
static String expressionToString(Object[] exp){
List<String> list = new ArrayList<>(exp.length);
for(Object e : exp){
list.add(e.toString());
}
return String.join(" ", list);
}
}
// [calculate] 1 + 2 * (9 - 5) => 1 2 9 5 - * + => 9
练习
请用Stack将整数转化为十六进制字符串表示,即:toHex(12500) ⇒ “30d4”
进制转换算法:
1)不断对整数除以16,得到商和余数,余数入栈
2)用得到的商重复步骤1
3)当商为0时,计算结束。
将栈中的数依次弹出并组成String
最佳实践
Iterator迭代
for...each循环是编译器实现的Iterator模式
适用于for...each循环的类:
1)实现Iterable接口
2)返回Iterator实例
好处:
1)对任何集合都采用同一种访问模型
2)调用者对集合内部结构一无所知
3)集合类返回的Iterator对象知道如何迭代
4)Iterator是一种抽象的数据访问模型
Collections
Collections是JDK提供的集合工具类
创建空集合(不可变):emptyList、emptySet、emptyMap
创建单元素集合(不可变):singleton、singletonList、singletonMap
对List排序(必须传入可变的List):sort
随机重置List元素(洗牌):suffle
把可变集合变为不可变集合:unmodifiableList、unmodifiableSet、unmodifiableMap
把线程不安群集合变为线程安全集合:synchronizedList、synchronizedSet、synchronizedMap(已不推荐使用)