Java 中一些重要知识点

Iterable 和 Iterator

Suppose b is a String array, or an object of java.util.ArrayList<String> , or of java.util.Set<String> . Then, one can write a foreach loop that processes each of b like this:

1
2
3
for(String s : b){
Process s
}

With each iteration of the loop, another element of b is stored in s and the “processed” – whatever that means.

The use of such a foreach loop is made possible by the use of two interfaces: Iterator and Iterable.

假设 b 是一个字符串数组,或者是 java.util.ArrayList<String> 对象,或者是 java.util.Set<String> 对象。那么,可以编写一个 foreach 循环来处理 b 的每个元素,如下所示:

1
2
3
for(String s : b){
Process s
}

随着循环的每次迭代,b 的一个元素会被储存在 s 中并被 “Process” 处理 —— 无论这是什么意思。

这样的 foreach 循环的使用是通过两个接口 Iterator 和 Iterable 来实现。

Iterable

jdk 1.5 之后,添加了 iterable 接口用于支持 foreach 循环。iterable 接口含有 iterator 方法,返回集合的 iterator 对象。所有实现了 iterable 接口的集合都可以使用 foreach 循环进行遍历。

1
2
3
public interface Iterable<T>{
Iterator<T> iterator();
}

Iterator

迭代器(Iterator)主要用来操作集合对象(Collection)。迭代器提供了统一的语法对集合对象(Collection)进行遍历操作,它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。所有集合类都实现了 Collection 接口,而 Collection 继承了 Iterable 接口。Iterator 只能向前移动,不能回退。

1
2
3
4
5
public interface Iterator<E>{
boolean hasNext();
E next();
void remove();
}

方法细节

hasNext
1
2
3
4
5
6
boolean hasNext()

//Returns true if the iteration has more elements.
//(In other words, returns true if next() would return an element rather than throwing an exception.)
//返回一个布尔值,不会抛出异常
//判断是否还有下一个对象,如果有,则返回 true
next
1
2
3
4
5
6
E next()

//Returns the next element in the iteration.
//Throws: NoSuchElementException - if the iteration has no more elements
//返回集合的下一个元素,只能在 hasNext 方法返回 true 时调用
//如果迭代没有更多元素,抛出 NoSuchElementException 异常
remove
1
2
3
4
5
6
void remove()

//Removes from the underlying collection the last element returned by this iterator (optional operation).
//This method can be called only once per call to next().
//The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method.
//删除集合的当前值,仅当 hasNext 方法返回 true 时调用

两者区别

Iterable 接口是为了 foreach 循环设计的。Iterable 接口表示集合可以返回 Iterator 对象,最终还是使用 Iterator 进行遍历。

Iterator 接口提供了一种统一的遍历集合元素的方式,使用 Iterator 对象可以不用关心具体集合对象的具体类型和内部实行,统一使用 Iterator 对象的接口方法就可以遍历集合。

Iterator 接口的核心方法 next() 或 hasNext() 都依赖于迭代器的当前迭代位置。如果 Collection 直接实现 Iterator 接口,势必导致集合对象中包含当前迭代位置的数据。当集合在不同方法间传递时,由于当前迭代位置不可预知,那么 next() 方法的结果会变成不可预知。而 Iterable 每次调用都会返回一个从头开始计数的迭代器。多个迭代器之间互不干扰。

Java SE 8 API 参考文档说明

StringBuilder 和 StringBuffer

在学习 Java 中,String 是最常能碰到的数据类型,但是 Java 中还存在有 StringBuilder 和 StringBuffer 两个类,这三者之间的区别主要集中在两个方面,即运行速度和线程安全,了解这些不同对学习 Java 中的字符串十分重要。

String

1
2
3
public final class String extends Object implements Serializable, Comparable<String>, CharSequence{
...
}

String 为字符串常量,所以对象一旦被创建就不能被修改,从运行速度来说慢于 StringBuilder 和 StringBuffer。

String 类是 final 类,也就意味着 String 类不能被继承,并且它的成员方法都默认为 final 方法,并且 String 类其实是通过 char 属组来保存字符串。

String 是不可变的对象,因此在每次对 String 类型进行改变的时候都等同于生成了一个新的 String 对象,然后将指向新的 String 对象。因此经常改变内容的字符串最好不要用 String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后。

StringBuilder

1
2
3
public final class StringBuilder extends Object implements Serializable, CharSequence{
...
}

StringBuffer

1
2
3
public final class StringBuffer extends Object implements Serializable, CharSequence{
...
}

性能测试

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class PerformTest {

private static int time = 50000;

public static void testString(){

String s = "";
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++){
s += "java";
}
long over = System.currentTimeMillis();
System.out.println("操作" + s.getClass().getName() + "类型使用的时间为:" + (over - begin) + "毫秒");

}

public static void testStringBuffer(){

StringBuffer sbf = new StringBuffer();
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++){
sbf.append("java");
}
long over = System.currentTimeMillis();
System.out.println("操作" + sbf.getClass().getName() + "类型使用的时间为:" + (over - begin) + "毫秒");
}

public static void testStringBuilder(){

StringBuilder sbl = new StringBuilder();
long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++){
sbl.append("java");
}
long over = System.currentTimeMillis();
System.out.println("操作" + sbl.getClass().getName() + "类型使用的时间为:" + (over - begin) + "毫秒");
}

public static void testAddString1(){

long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++){
String s = "I" + "love" + "java";
}
long over = System.currentTimeMillis();
System.out.println("字符串直接相加操作:" + (over - begin) + "毫秒");
}

public static void testAddString2(){

String s1 = "I";
String s2 = "love";
String s3 = "java";

long begin = System.currentTimeMillis();
for (int i = 0; i < time; i++){
String s = s1 + s2 + s3;
}
long over = System.currentTimeMillis();
System.out.println("字符串间接相加操作:" + (over - begin) + "毫秒");
}

public static void main(String[] args){

testString();
testStringBuffer();
testStringBuilder();
testAddString1();
testAddString2();
}
}

1
2
3
4
5
6
7
操作java.lang.String类型使用的时间为:3930毫秒
操作java.lang.StringBuffer类型使用的时间为:2毫秒
操作java.lang.StringBuilder类型使用的时间为:1毫秒
字符串直接相加操作:1毫秒
字符串间接相加操作:2毫秒

Process finished with exit code 0

三者间的联系

接口

charsequence

String, StringBuffer 和 StringBuilder 都实现了 CharSequence 接口,内部都是用一个 char 数组实现。

1
2
3
4
5
6
7
8
9
public interface CharSequence{
int length();
//return the char value at the specified index
char charAt(int index);
//return a new CharSequence that is a subsequence
//of this sequence.
CharSequence subSequence(int start, int end);
public String toString();
}

字符串相加操作细节

1
2
3
4
5
6
7
8
9
10
11
//1
String a = "ab";
String b = "cd";
String ab = a + b;
String s = "abcd";
System.out.println(ab == s); // => false

//2
String c = "ab" + "cd";
String d = "abcd";
System.out.println(c == d); // => true

代码 1 中,当执行 a + b 时,JVM 首先会在堆中创建一个 StringBuilder 类,同时用 a 指向的字符串对象完成初始化,然后调用 append 方法完成对 b 所指向的字符串的合并操作。接着调用 StringBuilder 的 toString() 方法在堆中创建一个 String 对象,最后将刚生成的 String 对象的堆地址存放在局部变量 ab 中。而局部变量 s 存储的是常量池中 "abcd" 所对应的字符串对象的地址,两者地址不同。

代码 2 中 "ab" + "cd" 会直接在编译期就合并成常量 "abcd" ,因此相同字面值常量 "abcd" 所对应的是同一个字符串对象,地址相同。

StringBuilder 和 StringBuffer 两者的区别

StringBuilder 和 StringBuffer 拥有的成员属性以及成员方法基本相同,区别是 StringBuffer 的成员方法前面多了一个关键字:synchronized 。这个关键字是多线程访问时起到安全保护作用,也就说 StringBuffer 是线程安全的。

1
2
3
4
pubilc StringBuilder insert(int index, char str[], int offset, int len){
super.insert(index, str, offset, len);
return this;
}
1
2
3
4
pubilc synchronized StringBuffer insert(int index, char str[], int offset, int len){
super.insert(index, str, offset, len);
return this;
}

总结

  1. String 是字符串常量,StringBuffer 是线程安全的字符串变量,StringBuilder 是直到 jdk 1.5 才加入的线程不安全的字符串变量,之所以设计 StringBuilder 是为了单线程使用提高效率而考虑的。
  2. 相对而言,从运行速度来说,StringBuilder > StringBuffer > String
  3. 从效率来说,如果对于很少改变内容的字符串,使用 String 效率高。如果对于经常改变内容的字符串,使用 StringBuilder 效率高,但是它只适用于单线程下在字符缓冲区进行大量操作的情况,在多线程场景下,容易导致数据不一致的现象出现。多线程场景下,要使用 StringBuffer,适用于多线程下在字符缓冲区进行大量操作的情况。

其他

1
2
3
4
5
6
String a = "hello2";
String b = "hello" + 2;
...
System.out.println(a == b);

// => true

String b = "hello" + 2 在编译期间已经被优化成 "hello2" ,因此在运行期间,变量 a 和变量 b 指向的是同一个对象。

1
2
3
4
5
6
7
String a = "hello2";
String b = "hello";
String c = b + 2;
...
System.out.println(a == c);

// => false

因为有符号引用的存在,所以 String c = b + 2; 不会在编译期被优化,不会把 b + 2 当作字面常量来处理,因此这种方式生成的对象事实上是保存在堆上的。因此 a 和 c 指向的并不是同一个对象。

1
2
3
4
5
6
7
String a = "hello2";
final String b = "hello";
String c = b + 2;
...
System.out.println(a == c);

// => true

对于被 final 修饰的变量,会在 class 文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对 final 变量的访问在编译期间都会直接被替代为真实的值。那么 String c = b + 2; 在编译期间就会被优化成:String c = "hello" + 2;

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args){
String a = "hello2";
final String b = getHello();
String c = b + 2;
System.out.println(a == c);
}

public static String getHello(){
return "hello";
}

// => false

虽然将 b 用 final 修饰,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此 a 和 c 指向的不是同一个对象。

Java SE 8 API 文档参考说明

Comparable 和 Comparator

Comparable

Comparable 是排序接口。若一个类实现了 Comparable 接口,就意味着 “该类支持排序”。 即然实现 Comparable 接口的类支持排序,假设现在存在 “实现 Comparable 接口的类的对象的 List 列表(或数组)”,则该 List 列表(或数组)可以通过 Collections.sort(或 Arrays.sort)进行排序。

此外,“实现 Comparable 接口的类的对象” 可以用作 “有序映射(如TreeMap)” 中的键或 “有序集合(TreeSet)” 中的元素,而不需要指定比较器。

Comparable 定义

1
2
3
public interface Comparable<T> {
public int compareTo(T o);
}

说明:

假设我们通过 x.compareTo(y) 来 “比较 x 和 y 的大小”。若返回 “负数”,意味着 “x 比 y 小”;返回 “零”,意味着 “x 等于 y”;返回 “正数”,意味着 “x 大于 y”。

Comparator

Comparator 是比较器接口。我们若需要控制某个类的次序,而该类本身不支持排序(即没有实现 Comparable 接口),那么,我们可以建立一个 “该类的比较器” 来进行排序,这个 “比较器” 只需要实现 Comparator 接口即可。也就是说,我们可以通过 “实现 Comparator 类来新建一个比较器”,然后通过该比较器对类进行排序。

Comparator 定义

1
2
3
4
5
6
public interface Comparator<T> {

int compare(T o1, T o2);

boolean equals(Object obj);
}

说明:

若一个类要实现 Comparator 接口,它一定要实现 compareTo(T o1, T o2)函数,但可以不实现 equals(Object obj) 函数。

为什么可以不实现 equals(Object obj) 函数呢?因为任何类,默认都是已经实现了 equals(Object obj) 的。Java 中的一切类都是继承于 java.lang.Object ,在 Object.java 中实现了 equals(Object obj) 函数,所以,其它所有的类也相当于都实现了该函数。

int compare(T o1, T o2) 是 “比较 o1 和 o2 的大小”。返回 “负数”,意味着 “o1 比 o2 小”;返回 “零”,意味着 “o1 等于 o2”;返回 “正数”,意味着 “ o1 大于 o2”。

Comparable 和 Comparator 比较

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import java.util.*;
import java.lang.Comparable;

/**
* @desc "Comparator"和“Comparable”的比较程序。
* (01) "Comparable"
* 它是一个排序接口,只包含一个函数compareTo()。
* 一个类实现了Comparable接口,就意味着“该类本身支持排序”,它可以直接通过Arrays.sort() 或 Collections.sort()进行排序。
* (02) "Comparator"
* 它是一个比较器接口,包括两个函数:compare() 和 equals()。
* 一个类实现了Comparator接口,那么它就是一个“比较器”。其它的类,可以根据该比较器去排序。
*
* 综上所述:Comparable是内部比较器,而Comparator是外部比较器。
* 一个类本身实现了Comparable比较器,就意味着它本身支持排序;若它本身没实现Comparable,也可以通过外部比较器Comparator进行排序。
*/
public class CompareComparatorAndComparableTest{

public static void main(String[] args) {
// 新建ArrayList(动态数组)
ArrayList<Person> list = new ArrayList<Person>();
// 添加对象到ArrayList中
list.add(new Person("ccc", 20));
list.add(new Person("AAA", 30));
list.add(new Person("bbb", 10));
list.add(new Person("ddd", 40));

// 打印list的原始序列
System.out.printf("Original sort, list:%s\n", list);

// 对list进行排序
// 这里会根据“Person实现的Comparable<String>接口”进行排序,即会根据“name”进行排序
Collections.sort(list);
System.out.printf("Name sort, list:%s\n", list);

// 通过“比较器(AscAgeComparator)”,对list进行排序
// AscAgeComparator的排序方式是:根据“age”的升序排序
Collections.sort(list, new AscAgeComparator());
System.out.printf("Asc(age) sort, list:%s\n", list);

// 通过“比较器(DescAgeComparator)”,对list进行排序
// DescAgeComparator的排序方式是:根据“age”的降序排序
Collections.sort(list, new DescAgeComparator());
System.out.printf("Desc(age) sort, list:%s\n", list);

// 判断两个person是否相等
testEquals();
}

/**
* @desc 测试两个Person比较是否相等。
* 由于Person实现了equals()函数:若两person的age、name都相等,则认为这两个person相等。
* 所以,这里的p1和p2相等。
*
* TODO:若去掉Person中的equals()函数,则p1不等于p2
*/
private static void testEquals() {
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
if (p1.equals(p2)) {
System.out.printf("%s EQUAL %s\n", p1, p2);
} else {
System.out.printf("%s NOT EQUAL %s\n", p1, p2);
}
}

/**
* @desc Person类。
* Person实现了Comparable接口,这意味着Person本身支持排序
*/
private static class Person implements Comparable<Person>{
int age;
String name;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public String toString() {
return name + " - " +age;
}

/**
* 比较两个Person是否相等:若它们的name和age都相等,则认为它们相等
*/
boolean equals(Person person) {
if (this.age == person.age && this.name == person.name)
return true;
return false;
}

/**
* @desc 实现 “Comparable<String>” 的接口,即重写compareTo<T t>函数。
* 这里是通过“person的名字”进行比较的
*/
@Override
public int compareTo(Person person) {
return name.compareTo(person.name);
//return this.name - person.name;
}
}

/**
* @desc AscAgeComparator比较器
* 它是“Person的age的升序比较器”
*/
private static class AscAgeComparator implements Comparator<Person> {

@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}

/**
* @desc DescAgeComparator比较器
* 它是“Person的age的升序比较器”
*/
private static class DescAgeComparator implements Comparator<Person> {

@Override
public int compare(Person p1, Person p2) {
return p2.getAge() - p1.getAge();
}
}

}

Java 8 中的 Comparator

范例一

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.math.BigDecimal;

public class Developer {

String name;
BigDecimal salary;
int age;

public Developer(String name, BigDecimal salary, int age) {
this.name = name;
this.salary = salary;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public BigDecimal getSalary() {
return salary;
}

public void setSalary(BigDecimal salary) {
this.salary = salary;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Developer [" +
"name='" + name + '\'' +
", salary=" + salary +
", age=" + age +
']';
}
}
经典 Comparator 示例
1
2
3
4
5
6
Comparator<Developer> byName = new Comparator<Developer>() {
@Override
public int compare(Developer developer, Developer compareDeveloper) {
return developer.getName().compareTo(compareDeveloper.getName());
}
};
对应的 Lambda 表达式示例
1
2
Comparator<Developer> byNameLambda =
(Developer developer, Developer compareDeveloper)->developer.getName().compareTo(compareDeveloper.getName());
Java8 更简洁的一种写法
1
Comparator<Developer> byNameLambdaSimple = Comparator.comparing(Developer::getName);

范例二

比较 Developer 的对象的 age 的示例。通常使用 Collections.sort 并传递一个这样的匿名 Comparator 类:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestSorting {

public static void main(String[] args) {

List<Developer> listDevs = getDevelopers();

System.out.println("Before Sort");
for (Developer developer : listDevs) {
System.out.println(developer);
}

//sort by age
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getAge() - o2.getAge();
}
});

System.out.println("After Sort");
for (Developer developer : listDevs) {
System.out.println(developer);
}

}

private static List<Developer> getDevelopers() {

List<Developer> result = new ArrayList<Developer>();

result.add(new Developer("mkyong", new BigDecimal("70000"), 33));
result.add(new Developer("alvin", new BigDecimal("80000"), 20));
result.add(new Developer("jason", new BigDecimal("100000"), 10));
result.add(new Developer("iris", new BigDecimal("170000"), 55));

return result;

}

}

当排序要求更改时,您只需传递另一个新的匿名 Comparator 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//sort by age
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getAge() - o2.getAge();
}
});

//sort by name
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getName().compareTo(o2.getName());
}
});

//sort by salary
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getSalary().compareTo(o2.getSalary());
}
});

它是有效的,但是你不认为仅仅因为要改变一行代码创建一个类是有点奇怪的么?

用 Lambda 排序

在 Java 8 中,List 接口支持直接使用 sort 该方法,不再需要使用 Collections.sort 了。

1
2
3
4
5
6
7
//List.sort() since Java 8
listDevs.sort(new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o2.getAge() - o1.getAge();
}
});

Lambda 表达式示例:

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
30
31
32
33
34
35
36
37
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class TestSorting {

public static void main(String[] args) {

List<Developer> listDevs = getDevelopers();

System.out.println("Before Sort");
for (Developer developer : listDevs) {
System.out.println(developer);
}

System.out.println("After Sort");

//lambda here!
listDevs.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());

//java 8 only, lambda also, to print the List
listDevs.forEach((developer)->System.out.println(developer));
}

private static List<Developer> getDevelopers() {

List<Developer> result = new ArrayList<Developer>();

result.add(new Developer("mkyong", new BigDecimal("70000"), 33));
result.add(new Developer("alvin", new BigDecimal("80000"), 20));
result.add(new Developer("jason", new BigDecimal("100000"), 10));
result.add(new Developer("iris", new BigDecimal("170000"), 55));
return result;

}

}
更多 Lambda 的例子
按年龄排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//sort by age
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getAge() - o2.getAge();
}
});

//lambda
listDevs.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());

//lambda, valid, parameter type is optional
listDevs.sort((o1, o2)->o1.getAge()-o2.getAge());
// lambda
listDevs.sort(Comparator.comparing(Developer::getAge));
按名称排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//sort by name
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getName().compareTo(o2.getName());
}
});

//lambda
listDevs.sort((Developer o1, Developer o2)->o1.getName().compareTo(o2.getName()));

//lambda
listDevs.sort((o1, o2)->o1.getName().compareTo(o2.getName()));
// lambda
listDevs.sort(Comparator.comparing(Developer::getName));
按薪水排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//sort by salary
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getSalary().compareTo(o2.getSalary());
}
});

//lambda
listDevs.sort((Developer o1, Developer o2)->o1.getSalary().compareTo(o2.getSalary()));

//lambda
listDevs.sort((o1, o2)->o1.getSalary().compareTo(o2.getSalary()));
// lambda
listDevs.sort(Comparator.comparing(Developer::getSalary));
反转排序

使用 Lambda 表达式对列表进行工资由少到多的排序

1
Comparator<Developer> salaryComparator = (o1, o2)->o1.getSalary().compareTo(o2.getSalary()); listDevs.sort(salaryComparator);

使用 Lambda 表达式对列表进行工资由多到少的排序

1
Comparator<Developer> salaryComparator = (o1, o2)->o1.getSalary().compareTo(o2.getSalary()); listDevs.sort(salaryComparator.reversed());

Java SE 8 API 文档参考

BigDecimal 方法

首先来看一个简单的加法:

1
System.out.println(1.01+2.02);

这个加法的结果是什么呢?如果我们自己计算的话应该是 3.03 ,但实际上计算机默认给出的结果是 3.0300000000000002 ,因为无论是 float 还是 double 都是浮点数,而计算机为二进制,这两者都会损失一点精确度。

Java 中提供了大数字(超过 16 位有效位)的操作类,即 java.math.BinInteger 类和 java.math.BigDecimal 类,用于高精度计算。

其中 BigInteger 类是针对大整数的处理类,而 BigDecimal 类则是针对大小数的处理类。

BigDecimal 类的实现用到了 BigInteger 类,不同的是 BigDecimal 加入了小数的概念。

在日常使用的过程中 float 和 double 已经足够,但当涉及到商业计算等对精度要求比较高的情况时,我们需要使用到 BigDecimal 类。

创建对象

BigDecimal 类创建的是对象,不能使用传统的 +,-,*,/ 等运算符对其直接进行数学运算,而必须调用其对应的方法,方法的参数也必须是 BigDecimal 类的对象。

创建 BigDecimal 对象主要有两种方法:

1
2
3
BigDecimal bd1 = new BigDecimal("10.511");
//等同于 BigDecimal bd1 = new BigDecimal(Double.toString(10.511));
BigDecimal bd2 = BigDecimal.valueOf(10.511);

注意,写成 BigDecimal bd1 = new BigDecimal(10.511); 也是可以运行的,但输出结果会变成 10.510999999999999232613845379091799259185791015625 ,即精度会出现损失。

特殊情况:

1
2
3
BigDecimal zero = BigDecimal.ZERO;
BigDecimal one = BigDecimal.ONE;
BigDecimal ten = BigDecimal.TEN;

加减乘除运算

1
2
3
4
public BigDecimal add(BigDecimal value);//加法
public BigDecimal subtract(BigDecimal value);//减法
public BigDecimal multiply(BigDecimal value);//乘法
public BigDecimal divide(BigDecimal value);//除法

注意,BigDecimal 的运算都没有对原值进行操作,而是返回一个新的 BigDecimal 对象。

1
2
3
4
5
BigDecimal bd4 = new BigDecimal("3.3");
BigDecimal bd5 = new BigDecimal("4.4");
bd4.add(bd5);
System.out.println(bd4);
//=>3.3

比较

BigDecimal 的比较使用的是 compareTo 方法,将此 BigDecimal 对象和指定的 BigDecimal 对象比较。

当值相等但保留位数不同的两个对象比较时(如 2.0 和 2.00),两者被认为是相等的。

当此 BigDecimal 对象在数字上小于、等于或大于被比较对象时,返回 -1、0 或 1。

1
2
3
4
5
6
7
8
BigDecimal bd4 = new BigDecimal("3.3");
BigDecimal bd5 = new BigDecimal("4.4");
BigDecimal bd6 = new BigDecimal("4.40");

int c1 = bd4.compareTo(bd5);//=>-1
int c2 = bd5.compareTo(bd5);//=>0
int c3 = bd5.compareTo(bd6);//=>0
int c4 = bd5.compareTo(bd4);//=>1

请求转发和请求重定向

当客户端向服务器发送请求时,服务器收到请求后,会将请求封装成一个 HttpServletRequest 对象请求,并且所有的请求参数都封装在 request 对象中,这个对象是 JSP 的内置对象,可以直接在 JSP 中使用。服务器收到请求后,还需要请求别的页面,这时就有两种方式:请求转发和请求重定向。

请求转发

1
request.getRequestDispatcher(URL).forward(request, response)

请求转发,是服务器的行为,请求由服务器转发给另外一个页面处理,如何转发,何时转发,转发几次,客户端是不知道的。请求转发时,从发送第一次到最后一次请求的过程中,web 容器只创建一次 request 和 response 对象,新的页面继续处理同一个请求。也可以理解为服务器将 request 对象在页面之间传递。

转发是在 Web 服务器内部进行的,不能跨域访问。

请求转发之后地址栏的信息并不会有任何的改变。

requestdispatcher

请求重定向

1
response.sendRedirect(URL)

请求重定向,是客户端的行为,每次请求重定向都是由客户端发起的,也就是说重定向一次,就刷新 request 对象的属性,之前的 request 对象的属性值就失效了。

重定向的目的是当 Web 应用升级后,如果请求路径发生了变化,可以将原来的路径重定向到新路径,从而避免浏览器请求原路径找不到资源。

请求重定向可以跨域访问。

请求重定向之后地址栏是会改变的,变为跳转之后的页面地址。

requestredirect

JSP 中四个域对象

域对象 作用范围
pageContext page 域 当前 JSP 页面
request request 域 同一个请求中
session session 域 同一个会话中
application context 域 同一个 web 应用中

常见包名解释

  1. POJO(Plain Ordinary Java Object): 简单的 Java 对象,只有 private 属性和 public 属性中的 get 和 set 方法,只能装载数据,不能实现接口。
  2. PO(Persistent Object): 持久化对象,是与数据库中表相对应的 Java 对象,PO 对象需要实现序列化接口。
  3. VO(View Object): 显示层对象。
  4. DTO(Data Transfer Object): 数据传输对象。
  5. DAO(Data Access Object): 数据访问对象,用于访问数据库,包含了各种数据库的操作方法。
  6. BO(Business Object): 业务对象。由 Service 层输出的封装业务逻辑的对象。

MyBatis 加载 Mapper 配置的方式

Mybatis 加载映射文件主要有三种方式,分别是 resourceclasspackage name=...

依据 Mapper 类具体路径

这种情况下,如果是非注解模式的话 xml 配置文件必须和这个类在同一级目录,且与 Mapper 类同名。

1
2
3
4
5
6
7
<configuration>
<mappers>
<!-- class 级别的指定 -->
<mapper class="com.bestcxx.stu.springmvc.mapper.UserModelMapper"/>
<mapper class="com.bestcxx.stu.springmvc.mapper.UserModelTwoMapper"/>
</mappers>
</configuration>

在存在 xml 配置文件的情况下,文件结构如下:

1
2
3
4
5
* com.bestcxx.stu.springmvc.mapper
* UserModelMapper.java
* UserModelTwoMapper.java
* UserModelMapper.xml
* UserModelTwoMapper.xml

依据 Mapper 类所在的 package 包路径

这种情况下,如果是非注解模式的话 xml 配置文件必须也处于同一级 package 下,且与Mapper类同名。

1
2
3
4
5
<configuration>
<mappers>
<package name="com.bestcxx.stu.springmvc.mapper"/>
</mappers>
</configuration>

文件结构:

1
2
3
4
5
* com.bestcxx.stu.springmvc.mapper
* UserModelMapper.java
* UserModelTwoMapper.java
* UserModelMapper.xml
* UserModelTwoMapper.xml

把 Mapper 的 XML 配置文件单独放在 sources 中

这种方式的好处是便于统一管理 xml 配置文件,不好的的地方是无法使用注解模式

1
2
3
4
5
6
7
<configuration>
<mappers>
<!-- 使用这个方案,可以单独指定Mapper的位置 -->
<mapper resource="mybatis/mappings/UserModelMapper.xml"/>
<mapper resource="mybatis/mappings/UserModelTwoMapper.xml"/>
</mappers>
</configuration>

文件结构:

1
2
3
* com.bestcxx.stu.springmvc.mapper
* UserModelMapper.java
* UserModelTwoMapper.java
1
2
3
4
5
* src/main/resources
* mybatis
* mappings
* UserModelMapper.xml
* UserModelTwoMapper.xml

参考