> 出於各種限制,很多公司依然停留在Java8,部分小伙伴轉向了Kotlin。Kotlin作為靜態編譯語言,提供大量語法糖,而且編譯後的位元組碼跟Java一致。 > > 當時,Java8於2014年發佈,Kotlin於2016年,很多宣稱的語法糖都是對比的Java8。不禁要問,相對今天的Java17, ...
出於各種限制,很多公司依然停留在Java8,部分小伙伴轉向了Kotlin。Kotlin作為靜態編譯語言,提供大量語法糖,而且編譯後的位元組碼跟Java一致。
當時,Java8於2014年發佈,Kotlin於2016年,很多宣稱的語法糖都是對比的Java8。不禁要問,相對今天的Java17,Kotlin優勢還在嗎?
現在就用最新的Kotlin1.9.0,對前三篇文章里的lambda、StreamAPI依次改造,實踐出真知!
編寫lambda、調用
Java
import Java.util.*;
import Java.util.function.*;
/**
*
* @author 燒哥burn.red
*/
public class Test1 {
public static void main(String[] args) {
Predicate<String> predicate = s -> s.length() == 3;
Consumer<String> consumer = s -> System.out.println(s);
Supplier<String> supplier = () -> "Hello Duke!";
Function<String, Integer> function = s -> s.length();
IntSupplier intSupplier = () -> 1;
IntConsumer intConsumer = s -> System.out.println(s);
IntPredicate intPredicate = i -> i > 10;
ToIntFunction<String> toIntFunction = s -> s.length();
UnaryOperator<String> unaryOperator = s -> s.toUpperCase();
BiConsumer<String, Integer> biConsumer = (s, number) -> s.indexOf(number);
ObjIntConsumer<String> objIntConsumer = (s, value) -> System.out.printf("%s,%d\n", s, value);
BiPredicate<String, Integer> biPredicate = (word, length) -> word.length() == length;
BiFunction<String, String, Integer> biFunction = (word, sentence) -> sentence.indexOf(word);
ToIntBiFunction<String, String> toIntBiFunction = (word, sentence) -> sentence.indexOf(word);
String a = "aaa";
if (predicate.test(a)) {
consumer.accept(a);
supplier.get();
function.apply(a);
intConsumer.accept(1);
intSupplier.getAsInt();
intPredicate.test(11);
toIntFunction.applyAsInt(a);
unaryOperator.apply(a);
biConsumer.accept(a, 2);
objIntConsumer.accept(null, 1);
biPredicate.test(a, 3);
biFunction.apply("fdsa", a);
toIntBiFunction.applyAsInt("fdsa", a);
}
List<String> strings = new ArrayList<>(List.of("a", "bb", "ccc"));
strings.forEach(consumer);
strings.removeIf(predicate);//不應該在不可變集合上調用
System.out.println(strings);
strings = Arrays.asList("a", "bb", "ccc");
strings.replaceAll(unaryOperator);
System.out.println(strings);
// int i = 0;
// Consumer<Integer> add = s -> i++;//報錯,從lambda 表達式引用的本地變數必須是最終變數或實際上的最終變數
}
}
Kotlin
/**
*
* @author 燒哥burn.red
*/
fun main() {
val predicate = { s: String -> s.length == 3 }
val consumer = { s: String? -> println(s) }
val supplier = { "Hello Duke!" }
val function = { s: String -> s.length }
val intSupplier = { 1 }
val intConsumer = { s: Int -> println(s) }
val intPredicate = { i: Int -> i > 10 }
val toIntFunction = { s: String -> s.length }
val unaryOperator = { s: String -> s.uppercase() }
val biConsumer = { s: String, number: Int -> s.indexOf(number.toChar()) }
val objIntConsumer = { s: String?, value: Int -> println("$s,$value") }
val biPredicate = { word: String, length: Int -> word.length == length }
val biFunction = { word: String?, sentence: String -> sentence.indexOf(word!!) }
val toIntBiFunction = { word: String?, sentence: String -> sentence.indexOf(word!!) }
val a = "aaa"
if (predicate(a)) {
consumer(a)
supplier()
function(a)
intConsumer(1)
intSupplier()
intPredicate(11)
toIntFunction(a)
unaryOperator(a)
biConsumer(a, 2)
objIntConsumer(null, 1)
biPredicate(a, 3)
biFunction("fdsa", a)
toIntBiFunction("fdsa", a)
}
var strings = mutableListOf("a", "bb", "ccc")
strings.forEach(consumer)
strings.removeIf(predicate) //不應該在不可變集合上調用
println(strings)
strings = arrayListOf("a", "bb", "ccc")
strings.replaceAll(unaryOperator)
println(strings)
var i = 0
val add = { s: Int? -> i++ } //不報錯
add(i)
println(i)
}
可以看出:
- Kotlin的lambda,沒有那四種劃分,調用時類似函數,
(參數..)
,非常簡潔 - Kotlin的lambda,
可以
改變外層變數的值 - Kotlin沒有自己的removeIf,replaceAll,但可以直接調用Java的
- Java為原始類型準備了特別版,Kotlin預設都是原始類型
- Kotlin變數預設都是非null
這一局,Kotlin勝出。
方法引用、鏈接
Java
import red.burn.bean.User;
import Java.util.*;
import Java.util.function.*;
import Java.util.logging.Logger;
/**
* @author 燒哥burn.red
*/
public class Test2 {
public static void main(String[] args) {
//方法引用
DoubleUnaryOperator sqrt = Math::sqrt;
IntBinaryOperator max = Integer::max;//靜態方法引用
Supplier<List<String>> newListOfStrings = ArrayList::new;//構造方法引用
Consumer<String> printer = System.out::println;//綁定到System.out
Function<String, Integer> toLength = String::length;//非綁定,綁定到String的實例
//Lambla的鏈接
Predicate<String> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNullOrEmpty = isNull.or(isEmpty);
Predicate<String> isNotNullNorEmpty = isNullOrEmpty.negate();
Predicate<String> shorterThan5 = s -> s.length() < 5;
Predicate<String> p = isNotNullNorEmpty.and(shorterThan5);
Logger logger = Logger.getLogger("MyApplicationLogger");
Consumer<String> log = logger::info;
Consumer<String> printStr = System.out::println;
Consumer<String> printAndLog = log.andThen(printStr);//
printAndLog.accept("test");
Function<String, Integer> function1 = String::length;
Function<Integer, Integer> function2 = s -> ++s;
Function<String, Integer> function = function1.andThen(function2);
System.out.println("new=" + function.apply("abc")); //4
Function<String, String> id = Function.identity();
//Comparator
Comparator<Integer> comparator = Integer::compare;
Comparator<String> comparator1 = (s1, s2) -> Integer.compare(s1.length(), s2.length());
Comparator<String> comparator2 = (s1, s2) -> Integer.compare(toLength.apply(s1), toLength.apply(s2));
Comparator<String> comparator3 = Comparator.comparing(String::length);
Comparator<User> byFirstName = Comparator.comparing(User::getFirstName);
Comparator<User> byLastName = Comparator.comparing(User::getLastName);
Comparator<User> byFirstNameThenLastName = byFirstName.thenComparing(byLastName)
.thenComparingInt(User::getAge);
Comparator<User> byFirstNameThenLastName1 = Comparator.comparingInt(User::getAge)
.thenComparing(
Comparator.nullsLast(Comparator.naturalOrder()));
List<String> strings = Arrays.asList("one", "two", "three", "four", "five");
strings.sort(comparator3.reversed());
System.out.println(strings);
}
}
import lombok.Builder;
import lombok.Data;
import org.jetbrains.annotations.NotNull;
/**
* @author 燒哥burn.red
*/
@Data
@Builder
public class User implements Comparable<User> {
private String name;
private int age;
private String firstName;
private String lastName;
@Override
public int compareTo(User o) {
return this.name.compareTo(o.name);
}
}
Kotlin
import Java.util.logging.Logger
import Kotlin.math.sqrt
import red.burn.bean.UserKT
import Kotlin.math.max
/**
*
* @author 燒哥burn.red
*/
fun main() {
//方法引用
val sqrt = ::sqrt
// val max = ::max //報錯,歧義
// val newListOfStrings = ::ArrayList //報錯,歧義
// val printer =::println //報錯,歧義
val ar = 5.run<Int, ArrayList<String>>(::ArrayList)
"a".run(::println)
val kt = ::UserKT //構造方法引用
val user = kt("abc", 10)
var (name, age) = UserKT("csc")
val firstName = user::firstName //屬性引用
val addAge = user::addAge //函數引用
val toLength = String::length //非綁定,綁定到String的實例
//Lambla的鏈接
val isNull = { obj: String? -> obj == null }
val isEmpty = { obj: String -> obj.isEmpty() }
val isNullOrEmpty = { obj: String? -> obj == null || isEmpty(obj) }
val isNotNullNorEmpty = { obj: String? -> !isNullOrEmpty(obj) }
val shorterThan5 = { s: String -> s.length < 5 }
val p = { s: String -> isNotNullNorEmpty(s).and(shorterThan5(s)) }
val logger = Logger.getLogger("MyApplicationLogger")
val log = { message: String? -> logger.info(message) }
val printStr = { message: String? -> println(message) }
val printAndLog = { message: String? ->
log(message).also { printStr(message) }
}
printAndLog("test")
val function1 = String::length
// val function2 = { s: Int -> ++s }//報錯 Val cannot be reassigned
val function2 = { s: Int ->
var i = s;
++i;
}
val function = { s: String -> function1(s).let(function2) }
println("new=" + function("abc")) //4
val id = { s: String? -> s }
//Comparator
val comparator = { x: Int, y: Int -> (x).compareTo(y) }
val comparator1 = { s1: String, s2: String -> s1.length.compareTo(s2.length) }
val comparator2 = { s1: String, s2: String -> toLength(s1).compareTo(toLength(s2)) }
val comparator3 = compareBy(String::length)
val byFirstName = compareBy(UserKT::firstName)
val byLastName = compareBy(UserKT::lastName)
val byFirstNameThenLastName = byFirstName.then(byLastName).thenBy(UserKT::age)
val byFirstNameThenLastName1 = compareBy(UserKT::age).then(nullsLast(naturalOrder()))
val strings = arrayListOf("one", "two", "three", "four", "five")
strings.sortWith(comparator3.reversed())
println(strings)
}
/**
*
* @author 燒哥burn.red
*/
data class UserKT(var name: String, var age: Int = 1) : Comparable<UserKT> {
var firstName: String? = null
var lastName: String? = null
override fun compareTo(other: UserKT): Int {
return name.compareTo(other.name)
}
fun printUser(s: UserKT) {
println(s)
}
fun addAge(i: Int, j: Int): Int {
return i + j
}
}
可以看出:
- Kotlin的lambda,可以有類引用、函數引用、屬性引用、構造引用,其中函數引用不能有歧義
- Kotlin的lambda,因為沒有四種劃分,缺乏Java里豐富的鏈接方式,不過可以自己實現
- Kotlin的lambda,無法修改自己的參數,只能讀取
- Kotlin可讀性比較強,Java容易看的分神
這一局,Kotlin跟Java打平。
StreamAPI
Java
import Java.util.List;
import Java.util.Map;
import Java.util.function.*;
import Java.util.stream.*;
/**
* @author 燒哥burn.red
*/
public class Test3 {
public static void main(String[] args) {
//flatmap
Function<String, Stream<Integer>> flatParser = s -> {
try {
return Stream.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
}
return Stream.empty();
};
List<String> strings = List.of("1", " ", "2", "3 ", "", "3");
List<Integer> ints = strings.stream().flatMap(flatParser).toList();
System.out.println("ints = " + ints);
//mapMulti
ints = strings.stream().<Integer>mapMulti((string, consumer) -> {
try {
consumer.accept(Integer.parseInt(string));
} catch (NumberFormatException ignored) {
}
}).toList();
System.out.println("ints = " + ints);
List<Integer> ints2 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> result = ints2.stream().skip(2).limit(5).toList();
System.out.println("result = " + result);
List<Integer> list0 = List.of(1, 2, 3);
List<Integer> list1 = List.of(4, 5, 6);
List<Integer> list2 = List.of(7, 8, 9);
// 1st pattern: concat
List<Integer> concat = Stream.concat(list0.stream(), list1.stream()).toList();
// 2nd pattern: flatMap
List<Integer> flatMap = Stream.of(list0.stream(), list1.stream(), list2.stream())//類似city的外層組成的流
.flatMap(Function.identity()).toList();
System.out.println("concat = " + concat);
System.out.println("flatMap = " + flatMap);
//reduce
Stream<String> strings1 = Stream.of("one", "two", "three", "four");
BinaryOperator<Integer> combiner = Integer::sum;
Function<String, Integer> mapper = String::length;
BiFunction<Integer, String, Integer> accumulator = (partialReduction, element) -> partialReduction + mapper.apply(element);
int result1 = strings1.reduce(0, accumulator, combiner);
System.out.println("sum = " + result1);
//groupby map
List<String> strings2 = List.of("two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve");
Map<Integer, Long> histogram = strings2.stream().collect(Collectors.groupingBy(String::length, Collectors.counting()));
histogram.forEach((k, v) -> System.out.println(k + " :: " + v));
Map<Long, List<Integer>> map = histogram.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
Map.Entry<Long, List<Integer>> result2 = map.entrySet().stream().max(Map.Entry.comparingByKey())//再求max
.orElseThrow();
System.out.println("result = " + result2);
}
}
Kotlin
/**
* @author 燒哥burn.red
*/
fun main() {
//flatmap
val flatParser = label@{ s: String ->
try {
return@label listOf(s.toInt())
} catch (_: NumberFormatException) {
}
emptyList<Int>()
}
val strings = listOf("1", " ", "2", "3 ", "", "3")
var ints = strings.flatMap(flatParser)
println("ints = $ints")
//mapMulti
/*ints = strings.mapMulti { string: String, consumer: Consumer<Int?> ->
try {
consumer.accept(string.toInt())
} catch (ignored: NumberFormatException) {
}
}
println("ints = $ints")*/
val ints2 = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
val result = ints2.drop(2).take(5)
println("result = $result")
val list0 = listOf(1, 2, 3)
val list1 = listOf(4, 5, 6)
val list2 = listOf(7, 8, 9)
// 1st pattern: concat
val concat = list0 + list1
// 2nd pattern: flatMap
val flatMap = listOf(list0, list1, list2).flatten()
println("concat = $concat")
println("flatMap = $flatMap")
//reduce
val strings1 = listOf("one", "two", "three", "four")
val mapper = String::length
val accumulator = { partialReduction: Int, element: String -> partialReduction + mapper(element) }
val result1 = strings1.fold(0, accumulator)
println("sum = $result1")
//groupby map
val strings2 = listOf("two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve")
val histogram: Map<Int, Int> = strings2.groupingBy { it.length }.eachCount()
histogram.forEach({ k, v -> println("$k :: $v") })
val map = histogram.map { it }.groupBy({ it.value }, { it.key }).maxBy { it.key }
println(map)
}
可以看出:
- Kotlin的lambda,不支持multiMap,但可以自己實現
- Kotlin有運算符重載,可以對集合進行+-
flatten
簡化了flatmap
,fold
簡化了reduce
,eachCount
簡化了分組計數- 集合直接就是流,集合上的
groupby
等直接調用,不需要collect()
- 函數很多有混淆,像
groupBy
、groupingBy
,maxBy
、maxOf
- 不過中間可能有null,還需要人工判斷,不如Java,Optional總不會報錯
groupBy({ it.value }, { it.key }).maxBy { it.key }
這個能亮瞎
總體來說,代碼量減少非常多,這局Kotlin勝出。
綜合
Java
import red.burn.bean.*;
import Java.util.*;
import Java.util.function.BiFunction;
import Java.util.function.Function;
import Java.util.stream.*;
/**
*
* @author 燒哥burn.red
*/
public class Test4 {
public static void main(String[] args) {
Author au1 = new Author("Au1");
Author au2 = new Author("Au2");
Author au3 = new Author("Au3");
Author au4 = new Author("Au4");
Author au5 = new Author("Au5");
Article a1 = new Article("a1", 1991, List.of(au1));
Article a2 = new Article("a2", 1992, List.of(au1, au2));
Article a3 = new Article("a3", 1993, List.of(au1, au3, au4));
Article a4 = new Article("a4", 1992, List.of(au1, au2, au3, au4));
List<Article> articles = List.of(a1, a2, a3, a4);
BiFunction<Article, Author, Stream<PairOfAuthors>> buildPairOfAuthors =
(article, firstAuthor) -> article.authors().stream().flatMap(
secondAuthor -> PairOfAuthors.of(firstAuthor, secondAuthor).stream());//Optional的Stream
Function<Article, Stream<PairOfAuthors>> toPairOfAuthors =
article -> article.authors().stream().flatMap(firstAuthor -> buildPairOfAuthors.apply(article, firstAuthor));
Collector<PairOfAuthors, ?, Map<PairOfAuthors, Long>> collector1 =
Collectors.groupingBy(Function.identity(), Collectors.counting());
// System.out.println("numberOfAuthorsTogether=" + numberOfAuthorsTogether);
Function<Map<PairOfAuthors, Long>, Map.Entry<PairOfAuthors, Long>> finisher1 =
map1 -> map1.entrySet().stream().max(Map.Entry.comparingByValue()).orElseThrow();
Map.Entry<PairOfAuthors, Long> result11 =
articles.stream().flatMap(toPairOfAuthors).collect(Collectors.collectingAndThen(collector1, finisher1));
Map.Entry<PairOfAuthors, Long> result12 =
articles.stream().collect(Collectors.flatMapping(toPairOfAuthors, Collectors.collectingAndThen(collector1, finisher1)));
//找出每年發表文章最多的兩位聯合作者
Collector<Article, ?, Optional<Map.Entry<PairOfAuthors, Long>>> flatMapping = Collectors.flatMapping(toPairOfAuthors,
Collectors.collectingAndThen(
collector1,
map2 -> map2.entrySet()
.stream()
.max(Map.Entry.comparingByValue())));
Map<Integer, Optional<Map.Entry<PairOfAuthors, Long>>> result13 =
articles.stream().collect(Collectors.groupingBy(Article::inceptionYear, flatMapping));
Map<Integer, Map.Entry<PairOfAuthors, Long>> result14 = result13.entrySet()
.stream()
.flatMap(entry -> entry.getValue()
.map(value -> Map.entry(entry.getKey(),
value))
.stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(result11);
System.out.println(result12);
System.out.println(result13);
System.out.println(result14);
}
}
public record Article(String title, int inceptionYear, List<Author> authors) {
}
public record Author(String name) implements Comparable<Author> {
public int compareTo(Author other) {
return this.name.compareTo(other.name);
}
}
public record PairOfAuthors(Author first, Author second) {
public static Optional<PairOfAuthors> of(Author first, Author second) {
if (first.compareTo(second) > 0) {
return Optional.of(new PairOfAuthors(first, second));
} else {
return Optional.empty();
}
}
}
Kotlin
可以看出:
- 這個例子主要體現Optional跟StreamAPI的結合,Kotlin里沒有Optional,所以很難寫出。
這局Java勝。
最終
Kotlin以2:1微弱優勢勝出。
Java用他的嚴謹,證明能實現從簡單到複雜的各種場景。
Kotlin用它的簡潔,通常情況下能減少工作量。
Kotlin還提供了委托、擴展、運算符重載、作用域函數、協程等等。
子曾經曰過”越簡潔,越有坑“
。想完全用Kotlin取代Java,還有一段路,目前二者可以互操作,建議同時使用。
複雜場景下,用什麼語言並不是決定性的,解決方案才是。