Radio

一个小小程序员

0%

1,要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱

比如下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import cn.hutool.core.date.DateUtil;

public class Test {
public static void main(String[] args) {
System.out.println(DateUtil.now());
sum();
System.out.println(DateUtil.now());

}

public static long sum() {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
}

我们运行一下

1
2
2020-11-09 19:57:12
2020-11-09 19:57:20

这段程序是没有问题的,慢的原因在哪,在Long sum = 0L这里,意味着程序构造了大约 2^31 个多余的 Long 实例(大约每次往 Long sum 中增加 long 时构造一个实例) 。将sum 的声明从 Long 改成 long ,我们再试一下

1
2
2020-11-09 19:59:44
2020-11-09 19:59:45
2,避免创建不必要的对象

比如下面这个例子

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
System.out.println(isNumber("312"));

}
static boolean isNumber(String s) {
return s.matches("^[0-9]*$");
}
}

一个正则表达式,判断传过来的字符串是不是数字,这个方案看起来没有问题,但是如果这个方法使用的非常频繁,那么这种写法就不太合适了,我们先来看看matcher这个方法

1
2
3
public boolean matches(String regex) {
return Pattern.matches(regex, this);
}

再往里走

1
2
3
4
5
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}

再往里走

1
2
3
public static Pattern compile(String regex) {
return new Pattern(regex, 0);
}

它在内部为正则表达式创建了一个 Pattern 实例,却只用了1次,之后就可以进行垃圾回收了,创建 Pattenr实例的成本很高 ,因为需要将正则表达式编译成一个有限状态机( finite state machine)。

为了提升性能,应该显式地将正则表达式编译成一个 Pattern 实例(不可变),让它成为类初始化的一部分,并将它缓存起来,每当调用 isNumber方法的时候就重用同一个实例:

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.regex.Pattern;

public class Test {
private static final Pattern ISNUM = Pattern.compile("^[0-9]*$");
public static void main(String[] args) {
System.out.println(isNumber("312"));

}
static boolean isNumber(String s) {
return ISNUM.matcher(s).matches();
}
}
3,覆盖equals必须覆盖hashCode

在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable

  1. 只要对象的equals方法的比较操作所用到的信息未被修改,那么对同一个对象调用多次其hashCode返回值不变
  2. 若两个对象通过equals得到是相等的,那么调用这两个对象任意一个对象的hashCode方法产生整数结果一样
  3. 若两个对象通过equals得到是不相等的,那么调用这两个对象任意一个对象的hashCode方法产生的结果也可能相等,但是从提高散列表(hash table)的性能分析,给不相等的对象产生不同的结果会更好
4,Lambda 优先于匿名类,和 java.util.function使用

举个例子,我们看下面这个代码

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
package effectiveJava.enumTest;

public enum OpemrationOld {
PLUS("+") {
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
public double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;

OpemrationOld(String symbol) {
this.symbol = symbol;
}

public abstract double apply(double x, double y);
}

class testold {
public static void main(String[] args) {
double value = OpemrationOld.DIVIDE.apply(1,2);
System.out.println(value);
}
}

这个代码表示对任意两个数进行加减乘除运算,jdk1.8新增了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
package effectiveJava.enumTest;

import java.util.function.DoubleBinaryOperator;

public enum Opemration {
PLUS("+", (x, y) -> x + y),
MINUS("-", (x, y) -> x - y),
TIMES("*", (x, y) -> x * y),
DIVIDE("/", (x, y) -> x / y);


private final String symbol;
private final DoubleBinaryOperator op;

Opemration(String symbol, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}

@Override
public String toString() {
return symbol;
}

public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}

class test {
public static void main(String[] args) {
double value = Opemration.DIVIDE.apply(1, 2);
System.out.println(value);
}
}

这里我们提一下DoubleBinaryOperator这个接口,这个接口是java.util.function包中的,也属于jdk1.8新增的包,用来支持 Java的函数式编程。

那这个接口是什么意思呢 表示:代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。

这个包下提供了非常多的函数

image-20201110202033145

序号 接口 描述
1 BiConsumer<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果
2 BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果
3 BinaryOperator 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4 BiPredicate<T,U> 代表了一个两个参数的boolean值方法
5 BooleanSupplier 代表了boolean值结果的提供方
6 Consumer 代表了接受一个输入参数并且无返回的操作
7 DoubleBinaryOperator 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8 DoubleConsumer 代表一个接受double值参数的操作,并且不返回结果。
9 DoubleFunction 代表接受一个double值参数的方法,并且返回结果
10 DoublePredicate 代表一个拥有double值参数的boolean值方法
11 DoubleSupplier 代表一个double值结构的提供方
12 DoubleToIntFunction 接受一个double类型输入,返回一个int类型结果。
13 DoubleToLongFunction 接受一个double类型输入,返回一个long类型结果
14 DoubleUnaryOperator 接受一个参数同为类型double,返回值类型也为double 。
15 Function<T,R> 接受一个输入参数,返回一个结果。
16 IntBinaryOperator 接受两个参数同为类型int,返回值类型也为int 。
17 IntConsumer 接受一个int类型的输入参数,无返回值 。
18 IntFunction 接受一个int类型输入参数,返回一个结果 。
19 IntPredicate 接受一个int输入参数,返回一个布尔值的结果。
20 IntSupplier 无参数,返回一个int类型结果。
21 IntToDoubleFunction 接受一个int类型输入,返回一个double类型结果 。
22 IntToLongFunction 接受一个int类型输入,返回一个long类型结果。
23 IntUnaryOperator 接受一个参数同为类型int,返回值类型也为int 。
24 LongBinaryOperator 接受两个参数同为类型long,返回值类型也为long。
25 LongConsumer 接受一个long类型的输入参数,无返回值。
26 LongFunction 接受一个long类型输入参数,返回一个结果。
27 LongPredicate 接受一个long输入参数,返回一个布尔值类型结果。
28 LongSupplier 无参数,返回一个结果long类型的值。
29 LongToDoubleFunction 接受一个long类型输入,返回一个double类型结果。
30 LongToIntFunction 接受一个long类型输入,返回一个int类型结果。
31 LongUnaryOperator 接受一个参数同为类型long,返回值类型也为long。
32 ObjDoubleConsumer 接受一个object类型和一个double类型的输入参数,无返回值。
33 ObjIntConsumer 接受一个object类型和一个int类型的输入参数,无返回值。
34 ObjLongConsumer 接受一个object类型和一个long类型的输入参数,无返回值。
35 Predicate 接受一个输入参数,返回一个布尔值结果。
36 Supplier 无参数,返回一个结果。
37 ToDoubleBiFunction<T,U> 接受两个输入参数,返回一个double类型结果
38 ToDoubleFunction 接受一个输入参数,返回一个double类型结果
39 ToIntBiFunction<T,U> 接受两个输入参数,返回一个int类型结果。
40 ToIntFunction 接受一个输入参数,返回一个int类型结果。
41 ToLongBiFunction<T,U> 接受两个输入参数,返回一个long类型结果。
42 ToLongFunction 接受一个输入参数,返回一个long类型结果。
43 UnaryOperator 接受一个参数为类型T,返回值类型也为T。

我们用35 Predicate 接受一个输入参数,返回一个布尔值结果。来举个例子

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
package effectiveJava.functionTest;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Java8Tester {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Predicate<Integer> predicate = n -> true
// n 是一个参数传递到 Predicate 接口的 test 方法
// n 如果存在则 test 方法返回 true

System.out.println("输出所有数据:");
eval(list, n -> true);


// Predicate<Integer> predicate1 = n -> n%2 == 0
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n%2 为 0 test 方法返回 true

System.out.println("输出所有偶数:");
eval(list, n -> n % 2 == 0);

// Predicate<Integer> predicate2 = n -> n > 3
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n 大于 3 test 方法返回 true

System.out.println("输出大于 3 的所有数字:");
eval(list, n -> n > 3);
}


public static void eval(List<Integer> list, Predicate<Integer> predicate) {
for (Integer i : list) {
if (predicate.test(i)) {
System.out.println(i);
}
}
}
}

当然是用Lambda后,eval就可以简写为

1
2
3
public static void eval(List<Integer> list, Predicate<Integer> predicate) {
list.stream().filter(predicate).forEach(System.out::println);
}

java.util.Function 中共有43个接口。别指望能够全部记住它们,但是如果能记住其中6个基础接口,必要时就可以推断出其余接口了。

  1. 基础接口作用于对象引用类型
  2. Operator 接口代表其结果与参数类型一致的函数
  3. Predicate 接口代表带有一个参数 并返回一个 boolean 的函数
  4. Function 接口代表其参数与返回的类型不一致的函数
  5. Supplier 接口代表没有参数并且返回(或“提供”)一个值的函数
  6. Consumer 表的是带有一个函数但不返回任何值的函数,相当于消费掉了其参数

比如下面这个代码

1
2
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
list.stream().filter(a -> a > 6).forEach(System.out::println);

这是个很常见的用来打印大于6的值,我们来看下这个filter接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* predicate to apply to each element to determine if it
* should be included
* @return the new stream
*/
Stream<T> filter(Predicate<? super T> predicate);

这里用到的就是我们的函数式编程。再比如

1
2
//求最大值
Optional<Integer> max1 = list.stream().reduce(Integer::max);
1
Optional<T> reduce(BinaryOperator<T> accumulator);

等等,java8给我们提供了大量的示例,告诉我们要尽量使用函数式编程。

jdk8对于许多常用的类都扩展了一些面向函数,lambda表达式,方法引用的功能,使得java面向函数编程更为方便。其中Map.merge方法就是其中一个,merge方法有三个参数,key:map中的键,value:使用者传入的值,remappingFunction:BiFunction函数接口(该接口接收两个值,执行自定义功能并返回最终值)。当map中不存在指定的key时,便将传入的value设置为key的值,当key存在值时,执行一个方法该方法接收key的旧值和传入的value,执行自定义的方法返回最终结果设置为key的值。

举个例子,当map中存在某个key,那么我们把value取出来加上新值,再存进去;传统办法是先判断,如果不存在key,则直接存,如果存在,则取出值加上新值,再把值重新put进去。当使用了merge后,我们的操作就简便多了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package effectiveJava.functionTest;

import java.util.HashMap;
import java.util.Map;

public class MapMergeTest {

public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("id", 1);
map.merge("id", 1, (oldValue, newValue) -> oldValue + newValue);
map.merge("name", 1, (oldValue, newValue) -> oldValue + newValue);
System.out.println(map.toString());
}
}

运行结果:

1
{name=1, id=2}

与匿名类相比, Lambda 的主要优势在于更加简洁。Java 提供了生成比 Lambda 更简洁函数对象的方法:方法引用(method reference)

比如map.merge("id", 1, (oldValue, newValue) -> oldValue + newValue);这段代码我们就可以简写为

1
map.merge("id", 1, Integer::sum);

记住一句话:只要方法引用更加简洁、清晰,就用方法引用;如果方法引用并不简洁,就坚持使用 Lambda。

5,Stream

Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。

Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

我们先举个实例

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
package effectiveJava.functionTest;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SteamTest {
static List<Person> personList = new ArrayList<>();

public static void list() {
personList.add(new Person("Tom", 8900, 20, "male", "New York"));
personList.add(new Person("Jack", 7000, 20, "male", "Washington"));
personList.add(new Person("Lily", 7800, 20, "female", "Washington"));
personList.add(new Person("Anni", 8200, 20, "female", "New York"));
personList.add(new Person("Owen", 9500, 20, "male", "New York"));
personList.add(new Person("Alisa", 7900, 20, "female", "New York"));
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements Cloneable {
private String name; // 姓名
private int salary; // 薪资
private int age; // 年龄
private String sex; //性别
private String area; // 地区

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
  1. 筛选(filter)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Test
    public void testFilter() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    list.stream().filter(a -> a > 6).forEach(System.out::println);

    Optional<Integer> first = list.stream().filter(a -> a > 6).findFirst();

    Optional<Integer> any = list.stream().filter(a -> a > 6).findAny();

    // 是否包含符合特定条件的元素
    boolean anyMatch = list.stream().anyMatch(x -> x < 6);
    System.out.println("匹配第一个值:" + first.get());
    System.out.println("匹配任意一个值:" + any.get());
    System.out.println("是否存在大于6的值:" + anyMatch);
    }
  2. 聚合(max/min/count)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Test
    public void testMinMaxCount() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    //最小值
    Optional<Integer> min = list.stream().filter(a -> a > 6).min(Integer::compareTo);
    System.out.println(min.get());
    //最大值
    Optional<Integer> max = list.stream().filter(a -> a > 6).max(Comparator.comparingInt(a -> a));
    System.out.println(max.get());
    //长度
    long count = list.stream().filter(a -> a > 6).count();
    System.out.println(count);
    }
  3. 映射(map/flatMap)

    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
    /**
    * map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    * flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
    */
    @Test
    public void testMap() {

    List<String> list = Arrays.asList("abc", "def", "ghi", "jkl");

    List<String> strList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
    System.out.println(strList.toString());

    List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    List<Integer> newNumList = numList.stream().map(a -> a + 3).collect(Collectors.toList());
    System.out.println(newNumList.toString());

    list();
    //不改变
    List<Person> newPersonList = personList.stream().map(a -> {
    Person p = null;
    try {
    p = (Person) a.clone();
    } catch (CloneNotSupportedException e) {
    e.printStackTrace();
    }
    p.setSalary(a.getSalary() + 10000);
    return p;
    }).collect(Collectors.toList());
    System.out.println(personList.toString());
    System.out.println(newPersonList.toString());

    //改变
    personList.stream().map(a -> {
    a.setSalary(a.getSalary() + 10000);
    return a;
    }).collect(Collectors.toList());
    System.out.println(personList.toString());


    List<String> flatList = Arrays.asList("m,k,l,a", "1,3,5,7");

    List<String> newFlatList = flatList.stream().flatMap(a -> {
    String[] strArr = a.split(",");
    return Arrays.stream(strArr);
    }).collect(Collectors.toList());
    System.out.println(flatList.toString());
    System.out.println(newFlatList.toString());
    }
  4. 归约(reduce)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * reduce 归约
    */
    @Test
    public void testReduce() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    //求和
    Optional<Integer> sum = list.stream().reduce(Integer::sum);
    System.out.println(sum.get());

    //求最大值
    Optional<Integer> max1 = list.stream().reduce((a, b) -> a > b ? a : b);
    Optional<Integer> max2 = list.stream().reduce(Integer::max);
    Integer max3 = list.stream().reduce(100, Integer::max);
    System.out.println(max1.get());
    System.out.println(max2.get());
    System.out.println(max3);
    }
  5. 收集(collect)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void testCollect() {
    list();
    List<Person> newList = personList.stream().filter(a -> a.getSalary() > 8000).collect(Collectors.toList());
    System.out.println(newList.toString());

    List<String> nameList = personList.stream().filter(a -> a.getSalary() > 8000).map(Person::getName).collect(Collectors.toList());
    System.out.println(nameList.toString());

    Map<String, Person> nameMap = personList.stream().collect(Collectors.toMap(Person::getName, b -> b, (oldValue, newValue) -> newValue));
    System.out.println(nameMap.toString());
    }
  6. 统计(count/averaging)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Test
    public void testAveraging() {
    list();
    //求和
    Long count = personList.stream().collect(Collectors.counting());
    //平均值
    double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
    //最大值
    Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compareTo));
    //求和
    Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
    //统计
    DoubleSummaryStatistics statistics = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
    statistics.getMax();
    statistics.getMin();
    statistics.getCount();
    statistics.getAverage();
    statistics.getSum();
    }
  7. 分组(partitioningBy/groupingBy)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void testGroupingBy() {
    list();
    Map<Boolean, List<Person>> map = personList.stream().collect(Collectors.partitioningBy(a -> a.getSalary() > 8000));
    Map<String, List<Person>> sexMap = personList.stream().collect(Collectors.groupingBy(Person::getSex));

    Map<String, Map<String, List<Person>>> tmp =
    personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
    }
  8. 接合(joining)

    1
    2
    3
    4
    5
    6
    @Test
    public void testJoin() {
    list();
    String str = personList.stream().map(Person::getName).collect(Collectors.joining(","));
    System.out.println(str);
    }
  9. 排序(sorted)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 排序
    * sorted():自然排序,流中元素需实现Comparable接口
    * sorted(Comparator com):Comparator排序器自定义排序
    */
    @Test
    public void testSorted() {
    list();
    //正序
    List<Person> newList = personList.stream().sorted(Comparator.comparingInt(Person::getSalary)).collect(Collectors.toList());
    System.out.println(newList.toString());
    //倒序
    List<Integer> newList2 = personList.stream().map(Person::getSalary).sorted(Comparator.comparingInt(a -> (int) a).reversed()).collect(Collectors.toList());
    System.out.println(newList2.toString());
    }
  10. 提取/组合

    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
    @Test
    public void testDistinct() {
    String[] arr1 = {"a", "b", "c", "d"};
    String[] arr2 = {"d", "e", "f", "g"};

    List<String> strList = new ArrayList<>(Arrays.asList(arr1));

    Stream<String> stream1 = Stream.of(arr1);
    Stream<String> stream2 = Stream.of(arr2);

    // concat:合并两个流
    // distinct:去重
    List<String> list = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
    System.out.println(list);

    // limit:限制从流中获得前n个数据
    List<String> list2 = strList.stream().limit(2).collect(Collectors.toList());
    System.out.println(list2.toString());

    // skip:跳过前n个数据
    List<String> list3 = strList.stream().skip(1).limit(2).collect(Collectors.toList());
    System.out.println(list3.toString());

    // iterate:遍历
    List<Integer> list4 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
    System.out.println(list4);
    }
6,同步访问共享的可变数据

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package effectiveJava.functionTest;

import java.util.concurrent.TimeUnit;

public class StopThread {
private static boolean stopRequest;

public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
int i = 0;
while (!stopRequest) {
i++;
}
});
t.start();
TimeUnit.SECONDS.sleep(1);
stopRequest = true;
}
}

你可能期待这个程序运行大约一秒钟左右,之后主线程将 stopRequest 设置为 true ,致使后台线程的循环终止。但是实际上,这个程序永远不会终止:因为后台 线程永远在循环!

问题在于,由于没有同步,就不能保证后台线程何时‘看到’主线程对 stopRequest 的值所做的改变。没有同步,虚拟机将以下代码:

1
2
3
while (!stopRequest) {
i++;
}

转变成这样:

1
2
3
4
5
if (!stopRequest){
while(true){
i++;
}
}

这种优化称作提升( hoisting ),正是 OpenJDK Server VM的工作 结果是一个活性失败 (liveness failure ):这个程序并没有得到提升。

修正这个问题的一种方式是同步访问stopRequest域。

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
package effectiveJava.functionTest;

import java.util.concurrent.TimeUnit;

public class StopThread {
private static volatile boolean stopRequest;

private static synchronized void setStopRequest() {
stopRequest = true;
}

private static synchronized boolean getStopRequest() {
return stopRequest;
}

public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
int i = 0;
while (!getStopRequest()) {
i++;
}
});

t.start();
TimeUnit.SECONDS.sleep(1);
setStopRequest();
}
}

注意,我们的读和写操作都要同步,否则无法保证同步起作用。

还有一种方式就是volatile关键字,虽然 volatile 修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package effectiveJava.functionTest;

import java.util.concurrent.TimeUnit;

public class StopThread {
private static volatile boolean stopRequest;

public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
int i = 0;
while (!stopRequest) {
i++;
}
});

t.start();
TimeUnit.SECONDS.sleep(1);
stopRequest = true;
}
}

在使用volatile关键字的时候,我们需要特别注意,不能使用i++(增量操作符)的操作,因为这个操作不是原子的。这个操作域中执行两项操作:首先它读取值,然后写回一个新值,相当于原来的值再加上1。如果第二个线程在第一个线程读取旧值和写回新值期间读取这个域,第二个线程就会与第一个线程一起看到同一个值,并返回相同的序列号,这就是安全性失败( safety failure ):这个程序会计算出错误的结果。修复方法是用synchronized来代替volatile。当然最好的办法是替换成原子类java.util.concurrent.atomic。

1
2
Atomiclang i =new Atomiclong(); 
i.getAndincrement();

总而言之, 当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。

7,要注意程序计算溢出

比如下面这个代码

1
2
3
4
5
6
7
8
9
10
11
12
package test;

public class Add {

public static void main(String[] args) {

int num1 = Integer.MAX_VALUE;
int num2 = Integer.MAX_VALUE;
System.out.println(num1+num2);

}
}

它的输出结果是-2,发生溢出后,既没有抛出异常,也没有任何提示。所以我们在写计算的时候一定要注意。

再比如下面这个计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
package test;

public class Add {

public static void main(String[] args) {

long l = 5 * 2147483647;
long m = (long) 5 * 2147483647;
System.out.println(l);//输出:2147483643
System.out.println(m);//输出:10737418235

}
}
8,注意finally和return的关系

在try-finally语法中,我们都知道finally是肯定会执行的。那如果try里面有return;finally将会在return前执行。但是对引用类型和值类型的数据修改效果是不一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int finally1(){
int num =0;
try {
return num;//0;
}finally {
num++;
}
}

static List<Integer> finally2(){
List<Integer> nums =new ArrayList<>();
try {
return nums;//[1]
}finally {
nums.add(1);
}
}
9,数值字面量

当我们程序中有一些大数值的参数时,允许在数字之间插入下划线,方便我们阅读,对数值没有任何影响。

1
2
3
int a = 100_00_00;
float b = 1_00f;
double c = 1_00;

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例类只能有一个实例。

单例类必须自己创建自己的唯一实例。

单例类必须给所有其他对象提供这一实例。

单例模式的5种实现方式

懒汉式-线程不安全

懒得创建,你调用我的时候再创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package test.singleton;

import java.io.Serializable;

/**
* 懒汉式
*/
public class SingletonOne {

private static SingletonOne instance = null;

private SingletonOne() {
}

public static SingletonOne getInstance() {
if (instance == null) {
instance = new SingletonOne();
}
return instance;
}
}

饿汉式-天生线程安全

早就创建好了,随时等着调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package test.singleton;

/**
* 饿汉式
*/
public class SingletonTwo {
private static SingletonTwo instance = new SingletonTwo();

private SingletonTwo() {
}

public static SingletonTwo getInstance() {
return instance;
}
}
双检锁/双重校验锁(DCL,即 double-checked locking)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package test.singleton;

/**
* 双检锁/双重校验锁(DCL,即 double-checked locking)
*/
public class SingletonThree {
private volatile static SingletonThree instance = null;

private SingletonThree() {
}

public static SingletonThree getInstance() {
if (instance == null) {
//synchronize加在这儿的好处是,第一是为了线程安全,第二是如果已经被实例化了,那么就不用加锁,直接获取对象,相比把锁加在方法上效率要高一些。
synchronized (SingletonThree.class) {
if (instance == null) {
instance = new SingletonThree();
}
}
}
return instance;
}
}

需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值,此时就可以将分配的内存地址赋值给instance字段了。然而该对象可能还没有初始化(写完还未同步主存),若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

由于 synchronized 并不是对 instance实例进行加锁(因为现在还并没有实例),所以线程在执行完instance = new SingletonThree()修改 instance 的值后,应该将修改后的 instance立即写入主存(main memory),而不是暂时存在寄存器或者高速缓冲区(caches)中,以保证新的值对其它线程可见。

至此,我们要加volatile关键字,保证内存可见性。

登记式/静态内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package test.singleton;

/**
* 登记式/静态内部类
*/
public class SingletonFour {
private SingletonFour() {
}

private static class SingletonFourHolder {
private static final SingletonFour INSTANCE = new SingletonFour();
}

public static final SingletonFour getInstance() {
return SingletonFourHolder.INSTANCE;
}
}
枚举
1
2
3
4
5
6
7
8
9
10
11
package test.singleton;

/**
* 枚举
*/
public enum SingletonFive {
INSTANCE;
public void doSomeThing(){
System.out.println("123321");
}
}

使用:

1
2
//枚举调用方式
SingletonFive.INSTANCE.doSomeThing();
说明:

一般情况下,不建议使用第 1 种方式,建议使用第 2 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 4 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 5 种枚举方式。如果有其他特殊的需求,可以考虑使用第 3 种双检锁方式。

破坏单例模式的三种方式

反射,克隆,序列化

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
package test.singleton;

import java.io.*;
import java.lang.reflect.Constructor;

/**
* 破坏单例的两种方式
* 1,反射
* 2,克隆 需要单例实现Cloneable接口
* 3,序列化 需要单例实现Serializable接口
*/
public class SingletonDemo {
public static void main(String[] args) throws Exception {
System.out.println("---------------序列化------------------");
SingletonOne singleton = SingletonOne.getInstance();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(singleton);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois =new ObjectInputStream(bis);
SingletonOne serializeSingleton = (SingletonOne) ois.readObject();
System.out.println(singleton == serializeSingleton);

System.out.println("---------------克隆------------------");
SingletonOne cloneSingleton = (SingletonOne) singleton.clone();
System.out.println(singleton == cloneSingleton);

System.out.println("---------------反射------------------");
Constructor<SingletonOne> cons = SingletonOne.class.getDeclaredConstructor();
cons.setAccessible(true);//SingletonOne类中的成员变量为private,故必须进行此操作
SingletonOne reflextSingleton = cons.newInstance();
System.out.println(singleton == reflextSingleton);
}
}

运行结果:

1
2
3
4
5
6
---------------序列化------------------
false
---------------克隆------------------
false
---------------反射------------------
false

这三种方式的规避方法

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
package test.singleton;

import java.io.Serializable;

/**
* 懒汉式
*/
public class SingletonOne implements Serializable, Cloneable {
//防止反射
private static volatile boolean isCreate = false;//默认是第一次创建

private static SingletonOne instance = null;

private SingletonOne() {
if (isCreate) {
throw new RuntimeException("已创建过");
}
isCreate = true;

}

public static SingletonOne getInstance() {
if (instance == null) {
instance = new SingletonOne();
}
return instance;
}

@Override
protected Object clone() throws CloneNotSupportedException {
//return super.clone();
//防止克隆
return instance;
}

//防止序列化
private Object readResolve() {
return instance;
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---------------序列化------------------
true
---------------克隆------------------
true
---------------反射------------------
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at test.singleton.SingletonDemo.main(SingletonDemo.java:31)
Caused by: java.lang.RuntimeException: 已创建过
at test.singleton.SingletonOne.<init>(SingletonOne.java:16)
... 5 more

这里我们简单说一下 为什么防止序列化要添加readResolve(),返回Object对象

一般来说, 一个类实现了 Serializable接口, 我们就可以把它往内存地写再从内存里读出而”组装”成一个跟原来一模一样的对象.

不过当序列化遇到单例时,这里边就有了个问题: 从内存读出而组装的对象破坏了单例的规则. 单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来,与以前序列化的对象不能equlas。

如果被反序列化的对象的类存在readResolve这个方法,他会调用这个方法来返回一个“array”,然后浅拷贝一份,作为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。

业务代表模式

业务代表模式(Business Delegate Pattern)用于对表示层和业务层解耦。它基本上是用来减少通信或对表示层代码中的业务层代码的远程查询功能。在业务层中我们有以下实体。

客户端(Client) - 表示层代码可以是 JSP、servlet 或 UI java 代码。

业务代表(Business Delegate) - 一个为客户端实体提供的入口类,它提供了对业务服务方法的访问。

查询服务(LookUp Service) - 查找服务对象负责获取相关的业务实现,并提供业务对象对业务代表对象的访问。

业务服务(Business Service) - 业务服务接口。实现了该业务服务的实体类,提供了实际的业务实现逻辑。

示例演示:
  1. 创建peopleService接口

    1
    2
    3
    4
    5
    package test.business;

    public interface PeopleService {
    void doSomething();
    }
  2. 创建两个服务实体类

    1
    2
    3
    4
    5
    6
    7
    8
    package test.business;

    public class EatService implements PeopleService{
    @Override
    public void doSomething() {
    System.out.println("eat...");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    package test.business;

    public class SleepService implements PeopleService{
    @Override
    public void doSomething() {
    System.out.println("sleep...");
    }
    }
  3. 创建查询服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package test.business;

    public class BusinessSeach {

    public PeopleService seach(String serviceType) {
    if (serviceType.equalsIgnoreCase("eat")) {
    return new EatService();
    } else {
    return new SleepService();
    }
    }

    }
  4. 创建业务代表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package test.business;

    public class BusinessDelegate {
    private BusinessSeach businessSeach = new BusinessSeach();
    private PeopleService peopleService;
    private String servcieType;

    public void setServiceType(String servcieType) {
    this.servcieType = servcieType;
    }

    public void doTask() {
    peopleService = businessSeach.seach(servcieType);
    peopleService.doSomething();
    }

    }
  5. 完成上述步骤,我们就可以开始演示了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package test.business;

    public class BusinessDelegateDemo {

    public static void main(String[] args) {
    BusinessDelegate businessDelegate = new BusinessDelegate();
    Client client = new Client(businessDelegate);

    businessDelegate.setServiceType("eat");
    client.doTask();

    businessDelegate.setServiceType("sleep");
    client.doTask();
    }
    }

    运行结果:

    1
    2
    eat...
    sleep...

MVC模式

MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。

Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。

View(视图) - 视图代表模型包含的数据的可视化。

Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。

示例演示:
  1. 新建一个model

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package test.mvc;

    import lombok.Data;

    @Data
    public class PeopleModel {

    private Integer id;
    private String name;
    }
  2. 新建view

    1
    2
    3
    4
    5
    6
    7
    package test.mvc;

    public class PeopleView {
    void showPeople(PeopleModel model) {
    System.out.println(model.toString());
    }
    }
  3. 新建controller

    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
    package test.mvc;

    public class PeopleController {
    private PeopleModel model;
    private PeopleView view;

    public PeopleController(PeopleModel model, PeopleView view) {
    this.model = model;
    this.view = view;
    }

    public void setPeopleId(Integer id){
    model.setId(id);
    }

    public Integer getPeopleId(){
    return model.getId();
    }

    public void setPeopleName(String name){
    model.setName(name);
    }

    public String getPeopleName(){
    return model.getName();
    }
    void showPeople(){
    view.showPeople(model);
    }
    }
  4. 完成上述步骤,我们就可以开始演示了

    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
    package test.mvc;

    public class MvcDemo {
    static PeopleModel getModel() {
    PeopleModel model = new PeopleModel();
    model.setId(1);
    model.setName("abc");
    return model;
    }

    public static void main(String[] args) {
    //模拟数据库取值
    PeopleModel model = getModel();
    //创建视图
    PeopleView view = new PeopleView();
    //把people信息输出给控制台
    PeopleController controller = new PeopleController(model, view);
    controller.showPeople();

    //更新数据
    controller.setPeopleId(2);
    controller.setPeopleName("def");
    //再次输出
    controller.showPeople();
    }
    }

    运行结果:

    1
    2
    PeopleModel(id=1, name=abc)
    PeopleModel(id=2, name=def)
说明:

MVC模式是典型的前后端数据交互模式。

访问者模式

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

示例演示:
  1. 定义一个people接口和三个动作的实现类

    1
    2
    3
    4
    5
    package test.visitor;

    public interface People {
    void action(PeopleVisitor peopleVisitor);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    package test.visitor;

    public class PeopleAwake implements People{
    @Override
    public void action(PeopleVisitor peopleVisitor) {
    peopleVisitor.visit(this);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    package test.visitor;

    public class PeopleEat implements People{
    @Override
    public void action(PeopleVisitor peopleVisitor) {
    peopleVisitor.visit(this);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    package test.visitor;

    public class PeopleSleep implements People{
    @Override
    public void action(PeopleVisitor peopleVisitor) {
    peopleVisitor.visit(this);
    }
    }
  2. 定义一个访问者接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package test.visitor;

    public interface PeopleVisitor {

    void visit(PeopleAwake peopleAwake);

    void visit(PeopleEat peopleEat);

    void visit(PeopleSleep peopleSleep);
    }
  3. 新建一个实现了访问者接口的访问者实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package test.visitor;

    public class PeopleDisplayVisitor implements PeopleVisitor {
    @Override
    public void visit(PeopleAwake peopleAwake) {
    System.out.println("peopleAwake...");
    }

    @Override
    public void visit(PeopleEat peopleEat) {
    System.out.println("peopleEat...");
    }

    @Override
    public void visit(PeopleSleep peopleSleep) {
    System.out.println("peopleSleep...");
    }
    }
  4. 完成上述步骤,我们就可以开始演示了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package test.visitor;

    import java.util.ArrayList;
    import java.util.List;

    public class VisitorDemo {
    private static List<People> peoples = new ArrayList<>();

    static void action(PeopleVisitor peopleVisitor) {
    peoples.add(new PeopleAwake());
    peoples.add(new PeopleEat());
    peoples.add(new PeopleSleep());
    for (int i = 0; i < peoples.size(); i++) {
    peoples.get(i).action(peopleVisitor);
    }
    }

    public static void main(String[] args) {
    action(new PeopleDisplayVisitor());
    }
    }

    运行结果:

    1
    2
    3
    peopleAwake...
    peopleEat...
    peopleSleep...
说明:

优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。

缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

模版模式

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

示例演示:
  1. 我们定义一个抽象类,并提供一个final的方法,防止被重写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package test.template;

    public abstract class People {

    abstract void awake();
    abstract void eat();
    abstract void sleep();

    public final void action(){
    awake();
    eat();
    sleep();
    }

    }
  2. 创建两个实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package test.template;

    public class PeopleOne extends People {
    @Override
    void awake() {
    System.out.println("peopleOne awake");
    }

    @Override
    void eat() {
    System.out.println("peopleOne eat");
    }

    @Override
    void sleep() {
    System.out.println("peopleOne sleep");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package test.template;

    public class PeopleTwo extends People {
    @Override
    void awake() {
    System.out.println("peopleTwo awake");
    }

    @Override
    void eat() {
    System.out.println("peopleTwo eat");
    }

    @Override
    void sleep() {
    System.out.println("peopleTwo sleep");
    }
    }
  3. 完成上述步骤,我们就可以开始演示了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package test.template;

    public class TemplateDemo {
    public static void main(String[] args) {
    People peopleOne =new PeopleOne();
    peopleOne.action();
    System.out.println("---------------------------------");
    People peopleTwo =new PeopleTwo();
    peopleTwo.action();
    }
    }

    运行结果:

    1
    2
    3
    4
    5
    6
    7
    peopleOne awake
    peopleOne eat
    peopleOne sleep
    ---------------------------------
    peopleTwo awake
    peopleTwo eat
    peopleTwo sleep
说明:

优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

策略模式

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

与状态模式的比较

状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 People 所组合的 State 对象,而策略模式是通过 Num 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 People 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。

状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 People 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 People 使用的算法。

示例演示:
  1. 创建一个策略接口

    1
    2
    3
    4
    5
    6
    7
    package test.strategy;

    public interface Strategy {

    public int execute(int num1, int num2);

    }
  2. 分别写一个加法,一个减法的具体实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package test.strategy;

    public class Add implements Strategy {

    @Override
    public int execute(int num1, int num2) {
    return num1 + num2;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    package test.strategy;

    public class Subtract implements Strategy {

    @Override
    public int execute(int num1, int num2) {
    return num1 - num2;
    }
    }
  3. 编写Num具体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package test.strategy;

    public class Num {
    private Strategy strategy;

    public void SetStrategy(Strategy strategy) {
    this.strategy = strategy;
    }

    public int execute(int num1, int num2) {
    return strategy.execute(num1, num2);
    }
    }
  4. 完成上述步骤,我们就可以开始演示了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package test.strategy;

    public class StrategyDemo {
    public static void main(String[] args) {
    Num num = new Num();
    num.SetStrategy(new Add());
    System.out.println("10+6=" + num.execute(10, 6));
    num.SetStrategy(new Subtract());
    System.out.println("10-6=" + num.execute(10, 6));
    }
    }

    演示结果:

    1
    2
    10+6=16
    10-6=4
说明:

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。

缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

空对象模式

在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。

在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。

示例演示:
  1. 新建一个people的抽象类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package test.nullObject;

    public abstract class People {

    protected String name;

    public abstract boolean isNull();

    public abstract String getName();
    }
  2. 分别创建两个对象,一个存在的,一个不存在的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package test.nullObject;

    public class RealPeople extends People {

    public RealPeople(String name) {
    this.name = name;
    }

    @Override
    public boolean isNull() {
    return false;
    }

    @Override
    public String getName() {
    return name;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package test.nullObject;

    public class NullPeople extends People{

    @Override
    public boolean isNull() {
    return true;
    }

    @Override
    public String getName() {
    return "non-existent";
    }
    }
  3. 完成上述步骤,我们就可以开始演示了

    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
    package test.nullObject;

    public class NullDemo {
    public static String[] names = {"a", "b", "c"};

    public static People getPeople(String name) {
    for (int i=0;i<names.length;i++) {
    if(names[i].equalsIgnoreCase(name)){
    return new RealPeople(name);
    }
    }
    return new NullPeople();
    }

    public static void main(String[] args) {
    People a = getPeople("a");
    People b = getPeople("b");
    People c = getPeople("c");
    People d = getPeople("d");

    System.out.println(a.getName());
    System.out.println(b.getName());
    System.out.println(c.getName());
    System.out.println(d.getName());
    }
    }

    运行结果:

    1
    2
    3
    4
    a
    b
    c
    non-existent
说明:

空对象模式就是对于不存在的值,单独进行封装,如果返回为null,我们在最开始就做好null值的处理。

状态模式

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。

示例演示:
  1. 我们创建一个people实体和一个state接口,提供两个方法,一个吃,一个睡

    1
    2
    3
    4
    5
    6
    7
    8
    package test.state;

    public interface State {

    public void eat(People people);

    public void sleep(People people);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package test.state;

    public class People {
    private State state;

    public State getState() {
    return state;
    }

    public void setState(State state) {
    this.state = state;
    }

    public void eat() {
    getState().eat(this);
    }

    public void sleep() {
    getState().sleep(this);
    }
    }
  2. 创建两个状态类,分别实现eat和sleep接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package test.state;

    public class EatState implements State {
    @Override
    public void eat(People people) {

    }

    @Override
    public void sleep(People people) {
    people.setState(new SleepState());
    System.out.println("sleep");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package test.state;

    public class SleepState implements State {
    @Override
    public void eat(People people) {
    people.setState(new EatState());
    System.out.println("eat");
    }

    @Override
    public void sleep(People people) {

    }
    }
  3. 完成上述步骤,我们就可以开始演示了,我们给people一个初始状态,然后让他来回切换状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package test.state;

    public class StateDemo {
    public static void main(String[] args) {
    People people = new People();
    people.setState(new EatState());

    people.sleep();
    people.eat();
    people.sleep();
    people.eat();
    }
    }

    运行结果:

    1
    2
    3
    4
    sleep
    eat
    sleep
    eat
说明:

优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。