1.最初版本的Switch语句:

1
2
3
4
5
6
7
8
9
10
switch (表达式) {
case 常量1:
语句;
break;
case 常量2:
语句;
break;
default:
语句;
}

特点

  1. 只能用于 整数类型
    • byte
    • short
    • char
    • int
  2. case 必须是 编译期常量
  3. 必须写 break

如果不写 break 就会出现 fall-through(贯穿执行)

表达式可以是变量,可以是有返回值的函数,也可以是计算结果

1
2
3
4
5
6
7
8
9
10
11
switch (getUser()) {

case null -> System.out.println("用户为空");

case String name -> System.out.println("用户名:" + name);

default -> System.out.println("未知对象");

}

##
1
2
3
4
5
6
7
8
9
10
11
12
int a = 10;
int b = 20;

switch (a + b) {

case 30 -> System.out.println("等于30");

case 40 -> System.out.println("等于40");

default -> System.out.println("其他");

}

2.Java5时期引入了enum枚举类型,Switch也将其引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Day {
MONDAY, TUESDAY, WEDNESDAY
}

Day day = Day.MONDAY;

switch (day) {
case MONDAY:
System.out.println("周一");
break;
case TUESDAY:
System.out.println("周二");
break;
}

3.Java7之后Switch开始支持String字符串类型

1
2
3
4
5
6
7
8
9
10
String fruit = "apple";

switch (fruit) {
case "apple":
System.out.println("苹果");
break;
case "banana":
System.out.println("香蕉");
break;
}

我认为这是一个很重要的开始

对字符串的支持,实际上底层是使用的hashcode()方法实现的;

Java 编译器会把

1
switch(String)

转换成

1
switch(hashCode)

大致逻辑:

1
2
3
4
switch (fruit.hashCode()) {
case 93029210:
if (fruit.equals("apple")) ...
}

流程:

1
2
3
4
5
6
7
String

hashCode()

switch(int)

equals二次判断

这也恰巧说明了重写hashcode()方法也要重写对应的equals()方法,否则会引起一系列的问题;两者hashcode相同但两者不equals的话两者其实是不相同的;但是如果没有重写equals方法的话,就可能认为两者相同,导致一系列问题;

例如在hashset中,如果没有重写存储对象的hashcode方法和equals方法的话会有一系列问题:

如果只重写hashcode但是不重写equals方法: 两个逻辑上相等的对象算出相同的哈希值,由于你没重写 equals,它调用的是 Object 默认的地址比较==),两个对象地址不同,被放进不同的hash桶,就会导致其失去其重要的去重能力,你就会在hashset中取到相同的对象;

如果不重写hashcode但是重写equals方法: 两个逻辑相同的对象,因为内存地址不同,计算出了完全不同的哈希值;直接存进了不同的哈希桶中,也无法去重;

hashset底层就是hashmap,所以hashmap也是如此;

分享一个相关的严重的计算机事故:

划时代的危机:HashDoS 攻击

2011 年左右(正是 Java 7 发布前后),全球互联网爆发了著名的 HashDoS(哈希拒绝服务攻击)

什么是 HashDoS?

由于 Java(以及当时的 PHP、Python 等)使用简单的哈希算法来存储请求参数(如 HashMap),攻击者可以伪造成千上万个 哈希值完全相同 的字符串。

  • 后果:当这些字符串进入服务器时,原本极快的哈希表会退化成一个极其缓慢的链表
  • 计算量爆炸:原本 $O(1)$ 的操作变成了 $O(n)$。攻击者只需发送极小的流量,就能让服务器 CPU 飙升到 100% 导致瘫痪。

连锁反应:数据结构的进化

为了应对这个问题,Java 随后在 Java 8 中对 HashMap 进行了史诗级更新:

  • 红黑树优化:当哈希碰撞超过一定阈值(默认 8 个)时,链表会转换为红黑树
  • 性能保证:即使在极端攻击下,查询时间也能保证在 O(log n),彻底终结了简单 HashDoS 攻击。

这就引发了hashmap底层向数组+链表+红黑树的转变;当数组的长度大于64同时链表长度大于8,链表就会转换为红黑树;

红黑树是一种自平衡的二叉搜索树,查询的时间复杂度是O(log n),相较于链表的O(n)复杂的会快很多;
红黑树实现的是大致平衡,最长的路径不会大于其他路径的两倍,相较于
二叉平衡树
的严格平衡策略(两个子树的高度差最多为 1),在增加和删除节点时需要更少的旋转来平衡,具有更少的消耗; 并且红黑树在Java中已经有更完整的使用方案;

4.Java12又产生了新的写法:

1
2
3
4
5
int result = switch (day) {
case 1 -> 10;
case 2 -> 20;
default -> 0;
};

这样不用写break;也可以避免贯穿执行的问题;

还可以合并多个case:

1
2
3
4
5
int num = switch(day) {
case 1,2,3 -> 10;
case 4,5 -> 20;
default -> 0;
};
1
2
3
4
5
6
7
int result = switch(day) {
case 1 -> {
System.out.println("Monday");
yield 10;
}
default -> 0;
};

对于多行代码要使用 yield 来返回值;

5.Java17之后又推出了模式匹配

1
2
3
4
5
6
7
Object obj = "Hello";

switch (obj) {
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer: " + i);
default -> System.out.println("Other");
}

不再是常量匹配, 出现了类型匹配;

6.Java 21又出现了 条件匹配null匹配:

1
2
3
4
5
6
7
8
9
10
11
switch (obj) {
case String s when s.length() > 5 ->
System.out.println("Long String");

case String s ->
System.out.println("Short String");

default -> {}

}

1
2
3
4
5
switch (obj) {
case null -> System.out.println("null");
case String s -> System.out.println(s);
default -> {}
}