目录
2、线性表具体类:ArrayList类和LinkedList类
Comparable接口和Comparator接口之间的不同
五、Collections类:执行集合和线性表中通用操作的静态方法
Java中的数据结构通常称为Java集合框架
一、集合:Collection接口
Set -- 存储一组不重复的元素
List -- 有序元素集合
Stack -- 采用后进先出方式处理的对象(堆)
Queue -- 采用后进后出方式处理的对象(队列)
Collection接口是处理对象集合的根接口。接口衍伸出的抽象类和具体类如下,其中可以看到熟悉的ArrayList类:
接下来几乎都围绕这张图来学习。
二、迭代器:Iterable接口
Collection接口继承自Iterable接口。迭代器的工作是遍历并选择序列中的对象,提供了一种“不必暴露内部细节”就能访问数据结构中各个元素的方法。
迭代器与for循环
通常什么情况下使用迭代器呢?我们知道遍历一个线性表也可以用for循环,但是for循环需要一个具体的“叫停数字”。那么相比for循环而言,当我们不确定一个线性表中元素个数的时候,我们可以使用迭代器中的next来判断下一个元素是否存在。
Iterable接口实现一个迭代器:
1、iterator()方法:获取迭代器
可以使用iterator()方法从集合中获取迭代器。
public class TestIterator {
public static void main(String[] args){
Collection<String> collection = new ArrayList<>();
collection.add("NNn");
collection.add("AAn");
collection.add("DDn");
collection.add("MMm");
Iterator<String> iterator = collection.iterator(); //获取集合对象的迭代器
while(iterator.hasNext()){
//返回下一个元素的大写形式(遍历)
System.out.println(iterator.next().toUpperCase() + "");
}
System.out.println();
}
}
/*
NNN
AAA
DDD
MMM
*/
其中语句:
Iterator<String> iterator = collection.iterator();
collection.iterator()获取了一个针对实例对象collection的迭代器。
2、forEach()方法:每个都操作
可以使用forEach()方法对集合中的每个元素执行一个操作:
collection.forEach(e -> System.out.print(e.toUpperCase() + " "));
三、线性表:List接口
前面我们用的ArrayList类其实还有个兄弟---LinkedList类,他俩都定义在List接口下。List接口增加了面向位置的操作,并且增加一个可以双向遍历线性表的迭代器:
其中实现双向遍历的方法listIterator()返回一个ListIterator接口的一个实例。
1、双向遍历:ListIterator接口
ListIterator接口继承自Iterator接口。 在Iterator接口中我们看到的方法都是对元素的下一位(顺序)操作,而ListIterator接口在继承了这些顺序操作的同时,增加了一系列对上一位(逆序)操作,也就刚好应对了上面所说的双向顺序遍历:
其中 add(元素) 用于将指定元素插入到线性表中:
- next()返回非空 --> 插入next方法返回的元素之前
- previous()返回非空 --> 插入previous方法返回的元素之后
其中注意next方法为Iterator接口中的方法,但因为ListInterator接口继承自Iterator接口,所以ListIterator接口实例也可以调用该方法。
2、线性表具体类:ArrayList类和LinkedList类
数组线性表类ArrayList类和链表类LinkedList类是实现List接口的两个具体类:
- ArrayList类:数组存储元素(通过下标随机访问元素,选它)
- LinkedList类:链表存储元素(要在线性表中插入/删除元素,选它)
上面那个选择标准是怎么来的呢?因为数组创建即固定了大小,而链表是可以动态增加减小的。所以如果我们需要增加或删除元素,对于数组而言如果元素个数超过了定义的大小,那么就只能再创建一个新数组,然后把原数组复制到新数组中,就没有动态链表高效。
对比两个类中的方法,可以看出链表类LinkedList中添加了一些对元素的插入和删除操作方法。
下面的程序创建一个元素是数字的数组线性表,并向里面添加元素。然后从该线性表中创建了一个链表,并向链表中删除和插入元素。最后使用迭代器顺序和逆序遍历打印线性表:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class TestArrayAndLinked {
public static void main(String[] args) {
//数组线性表
List<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
arrayList.add(1);
arrayList.add(4);
arrayList.add(0, 10);
arrayList.add(3, 30);
System.out.println("A list of integers in the array list: ");
System.out.println(arrayList); // [10, 1, 2, 30, 3, 1, 4]
//线性表链表结构
LinkedList<Object> linkedList = new LinkedList<Object>(arrayList);
linkedList.add(1, "red");
linkedList.removeLast();
linkedList.addFirst("green");
System.out.println("Display the linked list forward: ");
//迭代器遍历操作
ListIterator<Object> listIterator = linkedList.listIterator();
while(listIterator.hasNext()) { // 顺序遍历打印
System.out.print(listIterator.next() + " "); // green 10 red 1 2 30 3 1
}
System.out.println("Display the linked list backward: ");
while(listIterator.hasPrevious()){ // 逆序遍历打印
System.out.print(listIterator.previous() + " "); // 1 3 30 2 1 red 10 green
}
}
}
四、Comparator接口
前面有学习使用Comparable接口重写CompareTo方法即可对元素进行比较。那么对于一些没有实现Comparable接口的类,我们可以让其实现Comarator接口,通过重写它的compare方法来实现比较。
public int compare(T element1, T element2)
/*
1 = 2返回0
1 > 2返回正值
1 < 2返回负值
*/
Comparator接口中包括compare方法和equals方法。
-
Comparable接口和Comparator接口之间的不同
- Comparable在java.lang包中,Comparator在java.util包中;
- 使用Comparable接口,则必须在类内部实现这个接口,并重写comparaTo()方法;
- 但是Comparator接口可以在类外部使用,通过将该接口的一个匿名类对象当做参数传递给Collections.sort()方法或者Arrays.sort()方法实现排序,这样我们就可以不用改变类本身的代码而实现对类对象进行排序。
也就是说:当类可以被修改时,我们使用Comparable接口;当你无法修改类时,Comparator接口派上用场。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Child {
private Integer id;
private Integer age;
public Child() {
}
public Child(Integer id, Integer age) {
this.id = id;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Child{" +
"id=" + id +
", age=" + age +
'}';
}
}
public class TestComparator2 {
public static void main(String[] args) {
Child child1 = new Child(1, 14);
Child child2 = new Child(2, 12);
Child child3 = new Child(3, 10);
List<Child> list = new ArrayList<>();
list.add(child1);
list.add(child2);
list.add(child3);
Collections.sort( list, new Comparator<Child>() { // 匿名类对象当做参数传递
@Override // 在类外重写compare方法
public int compare(Child o1, Child o2) {
return o1.getAge() > o2.getAge() ? 1 : (o1.getAge() == o2.getAge() ? 0 : -1);
}
});
list.stream().forEach(System.out::println);
}
}
上面这段代码先是使用Child类创造实例对象,然后创造一个数组线性表,并将三个实例化对象添加到线性表中。最后使用Collections.sort()方法排序。使用sort方法需要保证对象是可比较的,要么使用Comparable接口重写compareTo方法,要么使用Comparator接口重写compare方法。上面代码在类外重写compare方法。
-
一些排序和比较的编程设计
(1)编写一条语句,按最后一个字符的递增顺序,对名为list的字符串的ArrayList进行排序
list.sort((e1, e2) -> {
if (e1.length() == 0)
return -1;
else (e2.length() == 0)
return 1;
else
return charAt(e1.size() - 1) - charAt(e2.size() - 1);
}
(2)编写一条语句,按照double[][]的第二列的递增顺序对其二维数组进行排序。例如,如果数组double[][] x = {{3,1},{2,-1},{2,0}},则排序后的数组将是{{2,-1},{2,0},{3,1}
java.util.Arrays.sort(x, (e1, e2) -> (int)(e1[1] - e2[1]));
sort方法用于排序。参数 (e1, e2) -> (int)(e1[1] - e2[1]) 表达的是当相减返回负数时,交换。
完整代码:
public class Tst {
public static void main(String args[]){
double[][] x = {{3, 1}, {2, -1}, {2, 0}};
java.util.Arrays.sort(x, (e1, e2) -> (int)(e1[1] - e2[1]));
for(int i = 0; i < 3; i++){
for(int j = 0; j < 2; j++){
System.out.print(x[i][j] + " ");
}
System.out.println();
}
}
}
五、Collections类:执行集合和线性表中通用操作的静态方法
这个类里有很多静态方法:升序排序sort、降序排列Collections.sort(list, Collection.reverseOrder( ))、逆序排列reverse、查找键值binarySearch、随机重新排序shuffle、复制copy等等。
六、Vector类和Stcak类:向量类和栈类
Vector类是AbstractList类的子类(注意,ArrayList类也是AbstractList类的子类):
而栈类Stack类是Vector类的子类:
七、队列
队列和栈是一种特殊的,对插入、删除加以限制的线性表。
1、Queue接口:队列
Queue接口中的方法实现对队列的操作:
2、Deque接口:双端队列
链表类LinkedList类实现了Deque接口,而Deque接口又继承自Queue接口,所以我们可以使用LinkedList创建一个队列。
与Queue接口相比,Deque接口增加了从队列两端插入和删除的方法:addFirst (E)、removeFirst()、getFirst();addLast (E)、removeLast()、getLast()。
3、PriorityQueue类:优先队列
PriorityQueue类实现一个优先队列。优先队列中元素被赋予优先级。当访问元素时,拥有最高优先级的元素首先被删除。
比如:创造一个将元素的自然顺序颠倒的优先队列 ---
new PriorityQueue(初始容量, Collections.reverseOrder())
八、栈类实操:表达式求值
import java.util.Stack;
public class EvaluateExpression {
public static void main(String[] args){
if(args.length != 1){
System.out.println("Usage: java EvaluateExpression \"expression \"");
System.exit(1);
}
try{
System.out.println(evaluateExpression(args[0]));
}
catch(Exception e){
System.out.println("Wrong expression: " + args[0]);
}
}
public static int evaluateExpression(String expression){
Stack<Integer> operandStack = new Stack<>(); // new Stack1
Stack<Character> operatorStack = new Stack<>(); // new Stack2
//提取被空格分隔的操作符、操作数和括号
expression = insertBlanks(expression); //保证操作符、操作数、括号至少被一个空格分隔
String[] tokens = expression.split("");
for(String token: tokens){
if(token.length() == 0)
continue; //标记为空,跳过
else if(token.charAt(0) == '+' || token.charAt(0) == '-'){ // + or -
while(!operandStack.isEmpty() &&
(operatorStack.peek() == '+' ||
operatorStack.peek() == '-' ||
operatorStack.peek() == '*' ||
operatorStack.peek() == '/')){
processAnOperator(operandStack,operatorStack);
}
operatorStack.push(token.charAt(0)); //压入operatorStack
}
else if(token.charAt(0) == '*' || token.charAt(0) == '/'){ // * or /
while(!operatorStack.isEmpty() &&
(operatorStack.peek() == '*' ||
operatorStack.peek() == '/')){
processAnOperator(operandStack,operatorStack);
}
operatorStack.push(token.charAt(0)); //压入operatorStack
}
else if(token.trim().charAt(0) == '('){ // (
operatorStack.push('('); 压入operatorStack
}else if(token.trim().charAt(0) == ')'){ // )
while(operatorStack.peek() != '('){ //处理栈顶的所有操作符直到看到)符号为止
processAnOperator(operandStack, operatorStack);
}
operatorStack.pop(); // 然后从栈中弹出)
}
else{
operandStack.push(new Integer(token)); //标记是操作数,压入operandStack
}
}
while(!operatorStack.isEmpty()){ //处理剩余的操作符
processAnOperator(operandStack, operatorStack);
}
return operandStack.pop();
}
//处理一个操作符
//从operatorStack中弹出一个操作符,从operandStack弹出两个操作数。然后完成对应的操作后,将结果压回operandStack中
public static void processAnOperator(Stack<Integer> operandStack,Stack<Character> operatorStack){
char op = operatorStack.pop();
int op1 = operandStack.pop();
int op2 = operandStack.pop();
if(op == '+')
operandStack.push(op2 + op1);
else if(op == '-')
operandStack.push(op2 - op1);
else if(op == '*')
operandStack.push(op2 * op1);
else if(op == '/')
operandStack.push(op2 / op1);
}
//保证操作符、操作数、括号至少被一个空格分隔
public static String insertBlanks(String s){
String result = "";
for(int i = 0; i < s.length(); i++){
if(s.charAt(i) == '(' || s.charAt(i) == ')'||
s.charAt(i) == '+' || s.charAt(i) == '-'||
s.charAt(i) == '*' || s.charAt(i) == '/')
result += " " + s.charAt(i) + " ";
else
result += s.charAt(i);
}
return result;
}
}