前言
本文使用jdk17,如果你使用的是jdk8,那么本文可能会有部分对你没用。
如有更简洁的写法希望大家能讨论讨论学习学习
容器创建
基本容器
才学Java时都是先创建好一个空的List然后一个一个add,特别麻烦,也许老版本可以使用Arrays.asList
来创建,但是创建出来的不是java.util
包下的而是java.util.Arrays
包下的,有时使用可能会出点意想不到的bug。
此时在外面包一层java.util.ArrayList就好了
public class Main {
public static void main(String[] args) {
var list = new ArrayList<>(Arrays.asList(1, 2, 3));
}
}
那么有没有Maps
和 Sets
来方便我们创建呢,好像没有😂,不过咱们的Set
有接收List
的构造函数,就像这样。
public class Main {
public static void main(String[] args) {
var list = new ArrayList<>(Arrays.asList(1, 2, 3, 2));
var set = new HashSet<>(list);
// 或者直接写成一行
var set1 = new HashSet<>(Arrays.asList(1, 2, 3, 2));
}
}
至于Map嘛,只能上点java9的新特性了。
在java9中,List
, Map
, Set
都有个叫of
的方法,可以传入多个元素直接构建。但是默认创建的都是不可变的,如果想要可变的可以在外面包一层我们以前常用的即可
public class Main {
public static void main(String[] args) {
var list = List.of(1, 2, 3, 4, 5);
var mutableList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
var map = Map.of("k1", "v1", "k2", "v2");
var mutableMap = new HashMap<>(Map.of("k1", "v1", "k2", "v2"));
var set = Set.of(1, 1, 2, 3);
var mutableSet = new HashSet<>(Set.of(1, 1, 2, 3));
}
}
递增数组
需求:创建x~y的递增序列,比如 0 到 10
这在写并查集时创建parent数组以及下标排序时会有这个需求
一般的写法是创建一个数组,然后使用一个for循环一个一个添加,虽说可以,但对我来说感觉不够优雅,我们可以使用java8的stream流来一行搞定。
import java.util.*;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
// IntStream.range(0, 11) // range是不包含末尾,rangeClosed包含末尾
int[] p = IntStream.rangeClosed(0, 10).toArray();
System.out.println(Arrays.toString(p));
//~out: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
}
递减数组
如果想要递减数组怎么办(虽然不知道哪儿有这种需求😂),可以使用map映射
import java.util.*;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int x = 0, y = 11;
int[] p = IntStream.range(x, y).map(i -> y - i).toArray();
System.out.println(Arrays.toString(p));
//~out: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
}
}
使用map之后你还可以创建等比数列,我这里先实现一下等差数列,等比数列可以自己照着摸索摸索。
等差数列
创建一个首项为1,公差为5的等差数列的前十项
public class Main {
public static void main(String[] args) {
int a0 = 1, d = 5, len = 10;
int[] a = IntStream.range(0, len).map(i -> a0 + d * i).toArray();
System.out.println(Arrays.toString(a));
//~out:[1, 6, 11, 16, 21, 26, 31, 36, 41, 46]
}
}
随机数组
生成10~20以内的随机数
使用IntStream.generate
会生成一个无限流,使用limit
来指定取出流的前len
个,这个limit
就像mysql的limit一样。
import java.util.*;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int len = 10;
var random = new Random();
int[] a = IntStream.generate(() -> random.nextInt(10, 21))
.limit(len)
.toArray();
System.out.println(Arrays.toString(a));
//~out: [15, 15, 14, 12, 12, 10, 13, 20, 17, 11]
}
}
Map优化
一步步优化词频统计
这里有一段文本
Look at the sky, I'm still here
I'll be alive next year
I can make something good, oh
Something good
Look at the sky, I'm still here
I'll be alive next year
I can make something good, oh
Something good
需求是统计每个单词出现的次数。首先是最朴素的写法。
import java.util.*;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
String s = "Look at the sky, I'm still here\n" +
"I'll be alive next year\n" +
"I can make something good, oh\n" +
"Something good\n" +
"Look at the sky, I'm still here\n" +
"I'll be alive next year\n" +
"I can make something good, oh\n" +
"Something good";
var cnt = new HashMap<String, Integer>();
String[] words = s.split("\\PL+");
for (String word: words) {
if (cnt.containsKey(word)) {
cnt.put(word, cnt.get(word) + 1);
} else {
cnt.put(word, 1);
}
}
System.out.println(cnt);
//~out:
// {sky=2, here=2, ll=2, next=2, still=2, be=2, alive=2, year=2, I=6, m=2, good=4, something=2, the=2, can=2, at=2, oh=2, Look=2, Something=2, make=2}
}
}
s.split("\\PL+")
的作用是将字符串按照非字符进行分割(来自Java核心技术卷二)
这个if else判断显得有点繁琐,这里优化方式有点多。
把if else优化成下面两行,
cnt.putIfAbsent(word, 0);
cnt.put(word, cnt.get(word) + 1);
putIfAbsent
是键不存在则put
优化成一行。
cnt.put(word, cnt.getOrDefault(word, 0) + 1);
这就完了?这一行还不够优雅。更好的方法是使用merge
cnt.merge(word, 1, Integer::sum);
这句的意思是跟键原有的值按照第三个参数进行合并,如果键不存在则会与值类型的默认值进行操作,比如Integer默认值就是0.
然后我们就把这个if else四合一了,就这样。
import java.util.*;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
String s = \...\;
var cnt = new HashMap<String, Integer>();
String[] words = s.split("\\PL+");
for (String word: words) {
cnt.merge(word, 1, Integer::sum);
}
System.out.println(cnt);
}
}
优雅。
当然,这还不是最终版本,我的终极目标是一行流。
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
String s = \...\;
Map<String, Long> cnt = Arrays.stream(s.split("\\PL+")).collect(Collectors.groupingBy(k -> k, Collectors.counting()));
System.out.println(cnt);
}
}
至此整个词频统计的核心变为一行。
其他分组需求
我想将下标按照数组中的元素值进行分组,即每个值在哪些下标出现过。
这也可以使用一行流实现。
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
var a = List.of(1, 1, 4, 5, 1, 4);
Map<Integer, List<Integer>> map = IntStream.range(0, a.size()).boxed().collect(Collectors.groupingBy(a::get));
System.out.println(map);
//~out: {1=[0, 1, 4], 4=[2, 5], 5=[3]}
}
}
这个Collectors
的功能挺强大的,可以自己摸索一下。
最后
等我发现有什么更简单的写法时再回来更新。