Java 8 新特性深度解析:探索 Lambda 表達式、Stream API 和函數式編程的革新之路

Java8 新特性

Java 8 的革新之路

自 1995 年首次發布以來,Java 已經成為世界上最廣泛使用的編程語言之一。隨著時間的推移,Java 經歷了多次版本更新,其中最具里程碑意義的便是 Java 8 的發布。這個版本引入了許多重大變革,包括 Lambda 表達式、Stream API 和函數式編程等新特性。這些變化不僅提升了 Java 開發者的生產力,還使得 Java 在性能、簡潔性和可讀性方面達到了新的高度。

本文將深入探討 Java 8 的主要新特性,并通過示例代碼和實際應用案例幫助你理解這些特性的優勢和使用場景。無論你是 Java 初學者還是經驗豐富的開發者,這篇文章都將為你提供一個全面了解 Java 8 新特性的平臺,幫助你在日常開發中更高效地利用這些功能。

讓我們一起踏上 Java 8 的革新之旅,探索如何通過這些新特性提高我們的代碼質量和開發效率。

主要內容

  1. Lambda 表達式(核心)
  2. 函數式接口
  3. 方法引用與構造器引用
  4. Stream API
  5. 新時間日期 API
  6. 接口中默認方法與靜態方法
  7. 其他新特性

新特性簡介

  • 速度更快
  • 代碼更少(簡潔,增加了新的語法 Lambda 表達式
  • 強大的 Stream API
  • 便于并行
  • 可以最大化減少空指針異常 Optional – 因為這個類的方法

為什么便于運行?

三點原因:

  1. Stream API 可以讓我們輕松地對集合進行并行處理。
  2. Lambda表達式 可以讓我們以一種簡潔的方式定義匿名函數,這種方式可以使代碼更加簡潔。
  3. 方式引用已經存在的方法或構造函數,可以避免我們重復編寫相似的代碼。

綜上所述,Java8 新特性便于并行的原因是,它們可以讓我們以一種簡潔的方式處理集合數據,同時充分利用多核 CPU 的優勢,從而提高程序的執行效率。

1、Lambda 表達式

何為 Lambda 表達式?

Lambda 是一個匿名函數,作為一種更緊湊的代碼風格,使 Java 的語言表達能力得到了提升,可以寫出更簡潔、更靈活的代碼。

代碼示例

  1. Runnable 接口,從匿名內部類到 Lambda 的轉換**(無參)**
// 匿名內部類
Runnable r1 = new Runnable() {@Overridepublic void run() {System.out.println("Hello World!");}
};// Lambda 表達式
Runnable r1 = () -> System.out.println("Hello Lambda!");
  1. Comparator 接口,參數傳遞 – 從匿名內部類到 Lambda 的轉換**(有參)**
// 使用匿名內部類作為參數傳遞
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return Interger.compare(o1.length(), o2.length());}
});// 使用 Lambda 表達式作為參數傳遞
TreeSet<String> ts2 = new TreeSet<>((o1, o2) -> Integer.compare(o1.length(), o2.length()) // 升序排序
);
  1. Listener 接口
JButton button = new JButton();
button.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {e.getItem();
}
});// Lambda
button.addItemListener(e -> e.getItem());

語法

-> 箭頭操作符簡介

Lambda 表達式在 Java 語言中引入了一個新的語法元素和操作符。這個操作符為 “->”,該操作符被稱為 Lambda 操作符或箭頭操作符

它將 Lambda 分為兩個部分:

  • 左側:指定了 Lambda 表達式需要的所有參數
  • 右側:指定了 Lambda 體,即 Lambda 表達式要執行的功能
六種格式
格式一:

無參,無返回值,Lambda 體只需要一條語句即可

Runnable r1 = () -> System.out.println("Hello Lambda!");
格式二:

Lambda 需要一個參數

Consumer<String> fun = (args) -> System.out.println(args);
格式三:

Lambda 只需要一個參數時,參數的小括號可以省略

Consumer<String> fun = args -> System.out.println(args);
格式四:

Lambda 需要兩個參數,并且有返回值時

BinaryOperator<Long> bo = (x, y) -> {System.out.println("實現函數接口方法!");return x + y;
};
格式五:

當 Lambda 體只有一條語句時,return 與大括號可以省略

BinaryOperator<Long> bo = (x, y) -> x + y;
格式六:

帶參數的數據類型 – 數據類型可以省略(如上所示),因為可由編譯器推斷得出,稱為“類型推斷”

BinaryOperator<Long> bo = (Long x, Long y) -> {System.out.println("實現函數接口方法!");return x + y;
};
類型推斷

所謂的“類型推斷”是指:Lambda 表達式中的參數類型依賴于上下文環境,都是由編譯器推斷得出的。

在 Lambda 表達式中無需指定類型,程序依然可以編譯,這是因為 javac 根據程序的上下文,在后臺推斷出了參數的類型

總結

大致可分為三種類型:

  1. 格式為一種 – 無需參數,無返回值,Lambda 體一條語句即可
  2. 格式二、三為一種 – 需要一個參數,無返回值,可省略參數小括號,也是一條語句即可
  3. 格式四、五、六為一種 – 需要兩種參數,有返回值,可以省略大括號與 return

參數類型均可省略,推薦使用一、二、五

2、函數式接口

何為函數式接口?

  • 函數式接口是指:只包含一個抽象方法的接口,該接口的對象可以通過 Lambda 表達式來創建。

    (若 Lambda 表達式拋出一個受檢異常,那么該異常需要在目標接口的抽象方法上進行聲明)

  • 一般建議函數式接口上使用 @FunctionalInterface 注解修飾, 這樣做可以檢查它是否是一個函數式接口,同時 javadoc 也會包含一條聲明,說明這個接口是一個函數式接口

@FunctionalInterface 注解:用于標識該接口是函數式接口,防止在接口中添加多余的抽象方法。

不是所有的函數式接口都有 @FunctionalInterface 注解修飾,但是建議所有函數式接口都應該加上該注解,因為這有助于編譯器檢查該接口是否符合函數式接口的要求,即只有一個抽象方法。如果該注解標注在有多個抽象方法的接口上,則編譯器會報錯,提示該接口不符合函數式接口的定義。同時,加上該注解也可以讓其他開發者更容易地理解該接口的用途和特點。

什么是抽象方法?

抽象方法指的是:沒有實現代碼的方法,只有方法聲明(包括方法名、返回值類型、參數列表和異常列表)。在 Java 中,如果一個類中包含了抽象方法,那么這個類必須被聲明為抽象類,因為抽象方法無法被直接調用。只有當一個類繼承了抽象類并實現了抽象方法,才能創建該類的對象并調用該方法。

抽象方法的語法格式為:在方法聲明中使用 abstract 關鍵字修飾方法,方法體中不包含實現代碼。例如:

public abstract void draw();

抽象方法的作用是:為了讓子類實現自己的方法邏輯,從而實現多態性和代碼復用。在設計模式中也經常使用抽象方法來定義基礎框架和算法流程,具體實現由子類來完成。

在函數式接口中,由于該接口只包含一個抽象方法,因此該方法默認為抽象方法,不需要顯式使用 abstract 關鍵字進行修飾。

代碼示例:

@FunctionalInterface
public interface MyInterface {void myMethod();
}
MyInterface myInterface = () -> System.out.println("Hello World!");
myInterface.myMethod(); // 輸出:Hello World!

自定義函數式接口

只要方法的參數是函數式接口都可以用 Lambda 表達式

@FunctionalInterface
public interface MyNumber {double getValue();
}// 在函數式接口中使用泛型
public interface MyFunc<T> {T getValue(T t);
}// 作為參數傳遞 Lambda 表達式
public String toUpperString(MyFunc<String) mf, String str) {return mf.getValue(str);
}String newStr = toUpperString((str) -> str.toUpperCase(), "abcdef"); // 該 Lambda 表達式的作用是將字符串轉換成大寫字母并返回
System.out.println(newStr); // "ABCDEF"

作為參數傳遞 Lambda 表達式:

為了將 Lambda 表達式作為參數傳遞,接收 Lambda 表達式的參數類型必須是與該 Lambda 表達式兼容的函數式接口的類型。

例如一個計算器接口,可以定義如下的函數式接口:
@FunctionalInterface
public interface Calculator {int calculate(int x, int y);
}

在該接口中,定義了一個抽象方法 calculate(int x, int y),接受兩個整型參數 xy ,返回一個整型結果。該接口標記了 @FunctionalInterface 注解,表示該接口是一個函數式接口。 然后,可以使用 Lambda 表達式來創建該接口的實例,例如:

Calculator add = (x, y) -> x + y;
Calculator subtract = (x, y) -> x - y;
Calculator multiply = (x, y) -> x * y;
Calculator divide = (x, y) -> x / y;

在這個例子中,分別使用 Lambda 表達式創建了加法、減法、乘法、除法四種計算器操作的實例,這些實例都是 Calculator 函數式接口的實例。 然后,可以使用這些實例進行計算,例如:

int result = add.calculate(2, 3); // 加法運算,結果為 5

在這個例子中,使用加法操作的實例 add 對參數 2 和 3 進行了加法運算,并將結果賦值給 result 變量。

總之,自定義函數式接口可以根據具體的業務需求定義,然后使用 Lambda 表達式來創建該接口的實例,從而實現對業務邏輯的封裝和復用。

Java 內置四大核心函數式接口

四種:消費型接口、供給型接口、函數型接口、斷定型接口

1. Consumer 接口 – 消費型接口

Consumer 接口定義了一個接受一個泛型參數并返回 void 的操作。該接口有一個抽象方法 accept(T t),接收一個泛型參數 T,表示該操作的輸入參數,無返回值。

源碼解析:

package java.util.function;import java.util.Objects;@FunctionalInterface
public interface Consumer<T> {/*** 對給定的參數執行操作** @param t 要執行操作的參數*/void accept(T t);/*** 返回一個組合了多個Consumer的新Consumer,表示執行順序為該Consumer接口先執行,然后執行after中的Consumer接口** @param after 要執行的另一個Consumer接口* @return 組合后的新Consumer接口*/default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> {accept(t);after.accept(t);};}
}

在這個源碼中,首先使用 @FunctionalInterface 注解標記了該接口是一個函數式接口。然后定義了一個抽象方法accept(T t),表示對給定的參數執行操作,并無返回值。接著定義了一個默認方法 andThen(Consumer<? super T> after),用于組合多個 Consumer 實例,表示執行順序為該 Consumer 接口先執行,然后執行 after 中的Consumer 接口。

總之,Consumer接口可以用于對給定的參數執行操作,例如打印、修改狀態等。該接口提供了一個默認方法 andThen 可以用于組合多個 Consumer 實例,從而實現更加復雜的操作。

應用場景:

Consumer 接口可以用于對集合進行遍歷、對異步任務結果進行處理、以及對對象狀態進行修改等場景中。使用Consumer 接口可以使代碼更加簡潔、易于維護,增強代碼的可讀性和可重用性。

以下是一些常見的場景:

  1. 集合遍歷:可以使用 Consumer 接口對集合中的每個元素進行操作。

    例如對一個字符串列表中的每個字符串進行大寫轉換并輸出:

List<String> list = Arrays.asList("apple", "banana", "orange");
Consumer<String> toUpperCase = s -> System.out.println(s.toUpperCase());
list.forEach(toUpperCase);

在這個例子中,定義了一個字符串列表 list,其中包含三個字符串元素。然后定義了一個 Consumer 實例 toUpperCase,用于將字符串轉換為大寫并輸出。接著使用 forEach 方法遍歷 list 中的每個元素,并將 toUpperCase 實例作為參數傳入,實現對每個字符串的大寫轉換和輸出操作。

  1. 異步任務處理:可以使用 Consumer 接口對異步任務的結果進行處理。

    例如對一個異步任務的結果進行打印:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!");
Consumer<String> printResult = s -> System.out.println(s);
future.thenAccept(printResult); // "Hello, World!"

在這個例子中,使用 CompletableFuture 創建一個異步任務,該任務返回一個字符串 "Hello, World!"。然后定義了一個 Consumer 實例 printResult,用于打印異步任務的結果。使用 thenAccept 方法注冊了一個 Consumer 類型的回調函數,該回調函數的作用是在 future 對象執行完畢后,將結果傳遞給 printResult Lambda 表達式并輸出。表示當異步任務完成時對結果進行處理,體現了異步任務執行完成后的回調機制。

  1. 狀態修改:可以使用 Consumer 接口對對象的狀態進行修改。

    例如修改一個訂單對象的狀態:

public class Order {private String status;// getter和setter方法// ...
}
Consumer<Order> updateStatus = o -> o.setStatus("已發貨");
Order order = new Order();
updateStatus.accept(order);

在這個例子中,定義了一個 Order 類,其中包含一個狀態屬性 status。然后定義了一個 Consumer 實例 updateStatus,用于將訂單狀態修改為 "已發貨"。接著創建了一個訂單對象 order,并將 updateStatus 實例作為參數傳入,實現對訂單狀態的修改。

2. Supplier 接口 – 供給型接口

Supplier 接口產生給定泛型類型的結果。與 Function 接口不同,Supplier 接口不接受參數。

該接口有一個抽象方法 get(),用于返回一個泛型參數 T,表示該操作的結果。

源碼解析:

package java.util.function;@FunctionalInterface
public interface Supplier<T> {/*** 獲取一個結果** @return 表示操作結果的泛型參數T*/T get();
}

在這個源碼中,首先使用 @FunctionalInterface 注解標記了該接口是一個函數式接口。然后定義了一個抽象方法 get(),用于獲取一個結果并返回一個泛型參數 T。該方法沒有參數,僅返回一個表示操作結果的泛型參數。

總之,Supplier 接口主要用于提供一個泛型參數類型的結果,例如獲取當前時間、生成隨機數、從數據庫中獲取數據等。Supplier 接口的 get() 方法沒有參數,返回一個表示操作結果的泛型參數,可以與其它函數式接口進行組合使用,實現更加復雜的操作。

應用場景:

用于提供一個泛型參數類型的結果,例如生成隨機數、從數據庫中獲取數據、獲取當前時間等場景中。

  1. 生成隨機數
Supplier<Integer> randomSupplier = () -> new Random().nextInt(100);
int randomNum = randomSupplier.get();
  1. 從數據庫中獲取數據
public class SupplierExample {public static void main(String[] args) {Supplier<List<String>> supplier = () -> {List<String> list = new ArrayList<>();try {ResultSet resultSet = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password").createStatement().executeQuery("select * from user");while (resultSet.next()) {list.add(resultSet.getString("name"));}} catch (SQLException e) {e.printStackTrace();}return list;};List<String> result = supplier.get();System.out.println(result.stream().collect(Collectors.joining(", ")));}
}
  1. 獲取當前時間
Supplier<LocalDateTime> timeSupplier = LocalDateTime::now;
LocalDateTime currentTime = timeSupplier.get();
3. Function 接口 – 函數型接口

Function 接口接收一個參數并生成結果。默認方法可用于將多個函數鏈接在一起(compose, andThen):

package java.util.function;import java.util.Objects;@FunctionalInterface
public interface Function<T, R> {//將Function對象應用到輸入的參數上,然后返回計算結果。R apply(T t);//將兩個Function整合,并返回一個能夠執行兩個Function對象功能的Function對象。default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {Objects.requireNonNull(before);return (V v) -> apply(before.apply(v));}//返回一個由原始Function執行完后再執行after Function的新Functiondefault <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}//接收任意類型參數,并返回相同類型參數的Function//作用:快速創建一個無需處理參數的Function實例static <T> Function<T, T> identity() {return t -> t;}
}

應用場景:

Function 接口主要用于將一個類型的值轉化為另一個類型的值。它接受一個參數并返回一個結果,通常用于在數據處理、轉換或映射過程中使用。

  1. 數據轉換和映射
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123");     // "123"
  1. 數據過濾和篩選
List<String> list = Arrays.asList("apple", "banana", "orange", "grape");
Function<String, Boolean> filterFunction = s -> s.startsWith("a");
List<String> filteredList = list.stream().filter(filterFunction::apply).collect(Collectors.toList());
System.out.println(filteredList); // [apple]
  1. 數據處理和轉換
List<String> list = Arrays.asList("apple", "banana", "orange", "grape");
Function<String, Integer> mapFunction = String::length;
List<Integer> lengthList = list.stream().map(mapFunction::apply).collect(Collectors.toList());
System.out.println(lengthList); // [5, 6, 6, 5]
4. Predicate 接口 – 斷定型接口

Predicate 接口是只有一個參數的返回布爾類型值的 斷言型 接口。該接口包含多種默認方法來將 Predicate 組合成其他復雜的邏輯(比如:與,或,非):

源碼解析:

package java.util.function;
import java.util.Objects;@FunctionalInterface
public interface Predicate<T> {// 該方法是接收一個傳入類型,返回一個布爾值.此方法應用于判斷.boolean test(T t);//and方法與關系型運算符"&&"相似,兩邊都成立才返回truedefault Predicate<T> and(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) && other.test(t);}// 與關系運算符"!"相似,對判斷進行取反default Predicate<T> negate() {return (t) -> !test(t);}//or方法與關系型運算符"||"相似,兩邊只要有一個成立就返回truedefault Predicate<T> or(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) || other.test(t);}// 該方法接收一個Object對象,返回一個Predicate類型.用于判斷兩個對象是否相等static <T> Predicate<T> isEqual(Object targetRef) {return (null == targetRef)? Objects::isNull: object -> targetRef.equals(object);}

應用場景:

Predicate 接口可以用于對集合進行過濾、對方法參數進行校驗、以及實現復雜的邏輯判斷等場景中。使用Predicate 接口可以使代碼更加簡潔、易于維護,增強代碼的可讀性和可重用性。

以下是一些常見的場景:

  1. 集合過濾:可以使用 Predicate 接口對集合進行過濾,例如篩選出所有大于等于 10 的整數:
List<Integer> list = Arrays.asList(1, 5, 10, 15, 20);
Predicate<Integer> predicate = i -> i >= 10;
List<Integer> filteredList = list.stream().filter(predicate).collect(Collectors.toList());

在這個例子中,首先創建了一個整型列表 list,然后定義了一個 Predicate 實例 predicate,用于判斷一個整數是否大于等于 10。接著使用 stream 對列表進行流操作,在流操作中使用 filter 方法傳入 predicate 實例對整數進行過濾,最后將過濾結果收集到一個新的列表中。

  1. 參數校驗:可以使用 Predicate 接口對方法參數進行校驗,例如校驗一個字符串是否為空:
public void doSomething(String str) {Predicate<String> isEmpty = s -> s == null || s.length() == 0;if (isEmpty.test(str)) {throw new IllegalArgumentException("字符串不能為空");}// ...
}

在這個例子中,定義了一個 Predicate 實例 isEmpty,用于判斷一個字符串是否為空。在方法中,對傳入參數str進行校驗,如果為空則拋出異常。

  1. 復雜邏輯判斷:可以使用 Predicate 接口對多個條件進行組合,實現更加復雜的邏輯判斷。例如判斷一個字符串是否包含數字和大寫字母:
String str = "Hello, World! 123";
Predicate<String> containsDigit = s -> s.matches(".*\\d.*");
Predicate<String> containsUpperCase = s -> s.matches(".*[A-Z].*");
if (containsDigit.and(containsUpperCase).test(str)) {System.out.println("字符串符合要求");
}

在這個例子中,定義了兩個 Predicate 實例 containsDigitcontainsUpperCase,分別用于判斷一個字符串是否包含數字和大寫字母。在判斷中使用 and 方法將兩個實例組合起來,表示字符串必須同時包含數字和大寫字母才符合要求。最后使用 test 方法對字符串進行判斷,如果符合要求則輸出相應信息。

小結
  1. Consumer 接口

    • 抽象方法 void accept(T t); 接收一個泛型參數 T,表示該操作的輸入參數,無返回值;

    • 一個默認方法 andThen(Consumer<? super T> after),用于組合多個 Consumer 實例,表示執行順序為- 該 Consumer 接口先執行,然后執行 after 中的Consumer 接口。

  2. Supplier 接口

    • 抽象方法 T get; 用于返回一個泛型參數 T,表示該操作的結果。
  3. Function 接口

    • 抽象方法 R apply(T t); 接收參數 T 然后返回計算結果;

    • 兩個默認方法(compose, andThen),用于將多個函數鏈接在一起;

    • 一個靜態方法 identity() ,用于快速創建一個無需處理參數的 Function 實例。

  4. Predicate 接口

    • 抽象方法 boolean test(T t); 返回一個布爾值,應用于判斷。

    • 包含多種默認方法來將 Predicate 組合成其他復雜的邏輯(比如:與(and),或(or),取反(negate))

    • 一個靜態方法 isEqual,用于判斷兩個對象是否相等

其他接口

在這里插入圖片描述

3、方法引用與構造器引用

概念

使用操作符 “::” 將方法名和對象或類的名字分隔開來。

使用場景

當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用!(實現抽象方法的參數列表,必須與方法引用方法的參數列表保持一致!)

(一)方法引用

1. 類 :: 靜態方法

類 :: 靜態方法(ClassName::staticMethodName

在這里插入圖片描述

Function<String, Integer> strToInt = Integer::parseInt;
int result = strToInt.apply("123");
2. 對象 :: 實例方法

對象 :: 實例方法(instance::instanceMethodName

在這里插入圖片描述

List<String> list = Arrays.asList("apple", "banana", "orange", "grape");
list.forEach(System.out::println);
3. 類 :: 實例方法

類 :: 實例方法(ClassName::instanceMethodName

在這里插入圖片描述

List<String> list = Arrays.asList("apple", "banana", "orange", "grape");
list.stream().map(StringUtils::toUppercase).forEach(System.out::println);

(二)構造器引用

格式:ClassName::new

與函數式接口相結合,自動與函數式接口中方法兼容。可以把構造器引用賦值給定義的方法,與構造器參數列表要與接口中抽象方法的參數列表一致。

在這里插入圖片描述

Function<String, Person> createPerson = Person::new;
Person person = createPerson.apply("Tom"); 

(三)數組引用

格式:type[] :: new

在這里插入圖片描述

// 創建一個長度為 10 的數組
Function<Integer, String[]> createArray = String[]::new;
String[] array = createArray.apply(10);
// 將其中所有元素設置為 0
Arrays.stream(arr).forEach(int[]::setAll);

4、強大的 Stream API

概念

Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常復雜的查找、過濾和映射數據等操作。 使用 Stream API 對集合數據進行操作,就類似于使用 SQL 執行的數 據庫查詢。也可以使用 Stream API 來并行執行操作。簡而言之, Stream API 提供了一種高效且易于使用的處理數據的方式。

流(Stream)到底是什么?

是數據渠道,用于操作數據源(集合、數組等)所生成的元素序列。

“集合講的是數據,流講的是計算!”

注意:

  1. Stream 自己不會存儲元素。
  2. Stream 不會改變源對象。相反,他們會返回一個持有結果的新 Stream。
  3. Stream 操作是延遲執行的。這意味著他們會等到需要結果的時候才執行。

操作三步驟

在這里插入圖片描述

(一)創建 Stream
順序流與并行流

Java8 中的 Collection 接口被擴展,提供了兩個獲取流的方法:

  • default Stream stream() : 返回一個順序流

  • default Stream parallelStream() : 返回一個并行流

并行流就是把一個內容分成多個數據塊,并用不同的線程分別處理每個數據塊的流。

Java8 中將并行進行了優化,我們可以很容易的對數據進行并行操作。Stream API 可以聲明性地通過 parallel() 與 sequential() 在并行流與順序流之間進行切換。

數組流

Java8 中的 Arrays 的靜態方法 stream() 可以獲取數組流:

static Stream stream(T[] array): 返回一個流

重載形式,能夠處理對應基本類型的數組:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)
由值創建流

可以使用靜態方法 Stream.of(), 通過顯示值創建一個流。它可以接收任意數量的參數。

public static Stream of(T… values) : 返回一個流

無限流

由函數創建流。可以使用靜態方法 Stream.iterate() 和 Stream.generate(), 創建無限流。

  • 迭代

    public static Stream iterate(final T seed, final UnaryOperator f)

  • 生成

    public static Stream generate(Supplier s)

(二)Stream 的中間操作
什么是中間操作?

中間操作是對 Stream 對象進行加工處理的操作,用于對 Stream 中的元素進行處理、篩選、過濾、排序、去重等操作。中間操作可以使用鏈式調用的方式進行連續操作,**每次操作都會返回一個新的 Stream 對象,**這樣可以構建出一條操作流水線。

惰性求值

多個中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,稱為“惰性求值” 。

API
篩選與切片
方法描述
filter(Predicate)接收 Lambda ,從流中排除某些元素
distinct()篩選,通過流所生成元素的 hashCode() 和 equals() 去除重復元素
limit(long maxSize)截斷流,使其元素不超過給定數量。
skip(long n)跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補
映射
方法描述
map(Function f)接收一個函數作為參數,該函數會被應用到每個元素上,并將其映射成一個新的元素。
mapToDouble(ToDoubleFunction f)接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 DoubleStream。
mapToInt(ToIntFunction f)接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 IntStream。
mapToLong(ToLongFunction f)接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 LongStream。flatMap(Function f) 接收一個函數作為參數,將流中的每個值都換成另 一個流,然后把所有流連接成一個流。
排序
方法描述
sorted()產生一個新流,其中按自然順序排序
sorted(Comparator comp)產生一個新流,其中按比較器順序排序
(三)終止操作
何為終止操作?
  • 終止操作是對 Stream 對象執行最終操作的操作,用于觸發 Stream 的處理流程,并返回最終的結果。
  • 終止操作可以是有返回值的,例如collect()方法,也可以是沒有返回值的,例如forEach()方法。
  • 終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void。
API
查找與匹配
方法描述
allMatch(Predicate p)檢查是否匹配所有元素
anyMatch(Predicate p)檢查是否至少匹配一個元素
noneMatch(Predicate p)檢查是否沒有匹配所有元素
findFirst()返回第一個元素
count()返回流中元素總數
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer)內部迭代(使用 Collection 接口需要用戶去做迭代,稱為外部迭代。相反,Stream API 使用內部迭代一一它幫你把迭代做了)
歸約
方法描述
reduce(T iden,BinaryOperator)可以將流中元素反復結合起來,得到一個值返口 T
reduce(BinaryOperator b)可以將流中元素反復結合起來,得到一個值。返 Optional<T>

**備注:**map 和 reduce 的連接通常稱為 map-reduce 模式,因 Google 用它來進行網絡搜索而出名

收集
方法描述
collect(Collector c)將流轉換為其他形式。接收一個 Collector 接口的實現,用于給 Stream 中元素做匯總的方法

Collector 接口中方法的實現決定了如何對流執行收集操作(如收 集到 List、Set、Map)。
但是 Collectors 實用類提供了很多靜態 方法,可以方便地創建常見收集器實例,具體方法與實例如下表:

在這里插入圖片描述在這里插入圖片描述

ForK/Join 框架

什么是 Fork/Join 框架?

**Fork/Join 框架:**就是在必要的情況下,將一個大任務,進行拆分 (fork) 成若干個小任務(拆到不可再拆時),再將一個個的小任務運算的結果進行 join 匯總。

并行流:

parallelStream 可多線程執行,是基于 ForkJoin 框架實現的

forEach() 用到的就是多線程

在這里插入圖片描述

Fork/Join 框架與傳統線程池的區別

采用“工作竊取”模式(work-stealing):當執行新的任務時它可以將其拆分分成更小的任務執行,并將小任務加到線程隊列中,然后再從一個隨機線程的隊列中竊取一個并把它放在自己的隊列中繼續執行。

相對于一般的線程池實現,fork/join 框架的優勢體現在對其中包含的任務的處理方式上

  • 在一般的線程池中,如果一個線程正在執行的任務由于某些原因無法繼續運行,那么該線程會處于等待狀態。

  • 而在 fork/join 框架實現中,如果某個子問題由于等待另外一個子問題的完成而無法繼續運行。那么處理該子問題的線程會主動尋找其他尚未運行的子問題來執行,這種方式是動態的,減少了線程的等待時間,并提高了性能。

  • Fork/Join 框架提供了一些特殊的方法,比如 fork()join() 等,用于處理分治任務,簡化了代碼的編寫和管理。

5、新時間日期 API

LocalDate、LocalTime、LocalDateTime

三種類的實例都是不可變的對象。

  • LocalDate – 表示使用 ISO-8601 日歷系統的日期
  • LocalTime – 表示使用 ISO-8601 日歷系統的時間
  • LocalDateTime – 表示使用 ISO-8601 日歷系統的日期和時間
LocalDateTime.class //日期+時間 format: yyyy-MM-ddTHH:mm:ss.SSS
LocalDate.class //日期 format: yyyy-MM-dd
LocalTime.class //時間 format: HH:mm:ss

注:ISO-8601 日歷系統是國際標準化組織制定的現代公民的日期和時間的表示。

各種方法如下:

在這里插入圖片描述

instant 時間戳

用于“時間戳”的運算。它是以 Unix 元年(傳統的設定為 UTC 時區 1970 年 1 月 1 日午夜時分)開始所經歷的描述進行運算。

Duration 和 Period

  • Duration: 用于計算兩個**“時間”間隔**

    import java.time.Duration;
    import java.time.LocalDateTime;
    public class DurationExample {public static void main(String[] args) {//獲取當前時間LocalDateTime start = LocalDateTime.now();System.out.println("開始時間:" + start);//模擬程序執行try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}//獲取執行結束時間LocalDateTime end = LocalDateTime.now();System.out.println("結束時間:" + end);//計算時間差Duration duration = Duration.between(start, end);System.out.println("程序執行時間:" + duration.toMillis() + " 毫秒");}
    }
    
  • Period: 用于計算兩個**“日期”間隔**

    import java.time.LocalDate;
    import java.time.Period;
    public class PeriodExample {public static void main(String[] args) {//獲取當前日期LocalDate start = LocalDate.now();System.out.println("開始日期:" + start);//模擬時間間隔LocalDate end = start.plusDays(30);System.out.println("結束日期:" + end);//計算日期差Period period = Period.between(start, end);System.out.println("日期間隔:" + period.getDays() + " 天");}
    }
    

操作日期的類

  • TemporalAdjuster: 時間校正器。

例如,有時我們可能需要獲取:將日期調整到“下個周日”等操作。

  • TemporalAdjusters: 該類通過靜態方法提供了大量的常用 TemporalAdjuster 的實現,方便開發者對日期進行各種調整操作。
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public class TemporalAdjusterExample {public static void main(String[] args) {//獲取當前日期LocalDate date = LocalDate.now();System.out.println("當前日期:" + date);//獲取本月的第一天LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());System.out.println("本月第一天:" + firstDayOfMonth);//獲取本月的最后一天LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());System.out.println("本月最后一天:" + lastDayOfMonth);//獲取下一個周二LocalDate nextTuesday = date.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));System.out.println("下一個周二:" + nextTuesday);}
}

日期時間格式化和解析的類

java.time.format.DateTimeFormatter 類:

該類提供了三種格式化方法:

1. 預定義的標準格式

DateTimeFormatter 類提供了一些預定義的標準格式,例如 ISO_DATEISO_TIMEISO_DATE_TIME 等等,這些標準格式已經定義好了日期時間的格式,開發者可以直接使用。

2. 語言環境相關的格式

DateTimeFormatter 類還提供了一些與語言環境相關的格式,例如 ofLocalizedDate(FormatStyle style)ofLocalizedTime(FormatStyle style)ofLocalizedDateTime(FormatStyle style) 等等,這些格式會根據不同的語言環境自動適配不同的日期時間格式

3. 自定義的格式

例如 ofPattern(String pattern) 方法可以傳入一個字符串參數,用于定義自己想要的日期時間格式,"yyyy-MM-dd HH:mm:ss" 就是一個自定義的日期時間格式。

時區的處理

Java8 中加入了對時區的支持,帶時區的時間為分別為:ZonedDateZonedTimeZonedDateTime 其中每個時區都對應著 ID,地區 ID 都為 “{區域}/{城市}”的格式 例如 :Asia/Shanghai 等

  • ZoneId:該類中包含了所有的時區信息
  • getAvailableZoneIds(): 可以獲取所有時區時區信息
  • of(id): 根據指定的時區信息獲取 ZoneId 對象

講一下與傳統日期的轉換

Java8 中的日期和時間 API 與傳統的日期類(如 java.util.Datejava.util.Calendar)之間存在一些差異,因此在進行日期轉換時需要注意以下幾點:

  1. java.util.Datejava.util.Calendar 類是可變的,而 Java8 中的日期和時間類是不可變的,因此在進行轉換時需要特別注意。

  2. java.util.Date 類的精度是毫秒級別,而 Java8 中的日期和時間類的精度可以達到納秒級別,因此在進行轉換時需要注意精度的損失。

  3. Java8 中的日期和時間類之間可以相互轉換,例如可以將 LocalDateTime 對象轉換為 Instant 對象,但是需要注意轉換后的時區和精度問題。

    以下是一個簡單的 Java8 和傳統日期類(java.util.Datejava.util.Calendar)之間的轉換示例:

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
public class DateConversionExample {public static void main(String[] args) {// Java 8日期類轉換為傳統日期類LocalDateTime dateTime = LocalDateTime.now();Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());Calendar calendar = Calendar.getInstance();calendar.setTime(date);System.out.println("Java 8日期類轉換為傳統日期類:" + calendar.getTime());// 傳統日期類轉換為Java 8日期類calendar = Calendar.getInstance();calendar.set(2022, Calendar.APRIL, 28);date = calendar.getTime();dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());System.out.println("傳統日期類轉換為Java 8日期類:" + dateTime);// Java 8日期類之間的轉換dateTime = LocalDateTime.now();Instant instant = dateTime.atZone(ZoneId.systemDefault()).toInstant();dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());System.out.println("Java 8日期類之間的轉換:" + dateTime);}
}

6、接口中的默認方法與靜態方法

概念

Java8 中允許接口中包含具有具體實現的方法,該方法稱為 “默認方法”,默認方法使用 default 關鍵字修飾;并允許添加靜態方法。

接口默認方法的“類優先”原則:

如果一個類實現了多個接口,而這些接口中定義了同名的默認方法,那么編譯器就會產生歧義,因為它不知道應該使用哪個默認方法。為了解決這個問題,Java 8 引入了“類優先”(Class Priority)的原則,即如果一個類繼承了另一個類并且實現了一個接口,而這兩者中都定義了同名的默認方法,那么類中的方法優先于接口中的方法。

注意:在使用“類優先”原則時,如果類中的方法不是完全覆蓋了接口中的方法,那么編譯器會報錯,此時需要在類中顯式地調用接口中的方法。

interface MyInterface {default void myMethod() {System.out.println("Default method in interface");}
}
class MyClass {public void myMethod() {System.out.println("Method in class");}
}
class MyImplementation extends MyClass implements MyInterface {
}
public class DefaultMethodExample {public static void main(String[] args) {MyImplementation obj = new MyImplementation();obj.myMethod(); // 輸出 "Method in class"}
}

7、其他新特性

Optional 類?

Optional 類 (java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用 null 表示一個值不存在,現在 Optional 可以更好的表達這個概念。并且 可以避免空指針異常

常用方法:

  1. Optional.of(T t): 創建一個 Optional 實例
  2. Optional.empty(): 創建一個空的 Optional 實例
  3. Optional.ofNullable(T t): 若 t 不為 null,創建 Optional 實例,否則創建空實例
  4. isPresent(): 判斷是否包含值
  5. orElse(T t): 如果調用對象包含值,返回該值,否則返回 t
  6. orElseGet(Supplier s): 如果調用對象包含值,返回該值,否則返回 s 獲取的值
  7. map(Function f): 如果有值對其處理,并返回處理后的 Optional,否則返回 Optional.empty()
  8. flatMap(Function mapper): 與 map 類似,要求返回值必須是 Optional

重復注解與類型注解

Java8 對注解處理提供了兩點改進:可重復的注解可用于類型的注解

  1. 可重復的注解

    在Java 8之前,同一個注解不能在同一個地方重復使用,這在某些情況下會導致代碼的冗余。Java8 引入了可重復的注解,它允許同一個注解在同一個地方重復使用。這種注解需要使用 @Repeatable 元注解來標注

    舉個例子,假設我們有一個 @Author 注解,用于表示文章的作者。在 Java8 之前,我們無法在同一個類或方法上使用多次 @Author 注解,而必須定義多個注解,如 @Author1@Author2 等。在 Java8 中,我們可以使用可重復的注解來解決這個問題,示例代碼如下:

    @Repeatable(Authors.class)
    @interface Author {String name();
    }
    @interface Authors {Author[] value();
    }
    @Authors({@Author(name = "Alice"),@Author(name = "Bob")
    })
    public class Article {// ...
    }
    
  2. 可用于類型的注解

    Java8 還引入了一種新的注解類型 —— 類型注解(Type Annotation),它可以用于注解類型(如類、接口、枚舉、注解等)上。這種注解需要使用新的元注解 @Target 來標注,同時需要指定 ElementType.TYPE_USE 作為注解的目標類型。

    舉個例子,假設我們有一個 @NotNull 注解,用于表示一個類型不為 null。在 Java8 之前,我們只能將它應用到方法的參數上,而不能將其應用到類型上。在 Java8 中,我們可以使用類型注解將其應用到類型上,示例代碼如下:

    interface NotNull {}
    class Example<@NotNull T> {public void foo(List<@NotNull String> strings) {}
    }
    

8、Java 8 實戰

推薦參考此篇文章:

https://javaguide.cn/java/new-features/java8-common-new-features.html

9、peek()

peek() 方法是 Stream API 中的一個中間操作,它允許你在不改變數據的情況下,對流中的元素進行某種操作。peek() 方法接收一個 Consumer 接口的實現,對流中的每個元素執行該操作,并返回一個新的流,其中包含與原始流相同的元素。

import java.util.stream.Stream;public class Main {public static void main(String[] args) {Stream.of(1, 2, 3, 4, 5).filter(n -> n % 2 == 0).peek(System.out::println).collect(Collectors.toList());}
}

在這個示例中,我們創建了一個包含 1 到 5 的整數流,然后使用 filter() 方法過濾出偶數。接下來,我們使用 peek() 方法打印每個元素,最后使用 collect() 方法將流轉換為列表。輸出結果如下:

2
4

返回一個新的流有啥必要?

返回一個新的流在某些情況下是有必要的,因為這樣可以保留原始流的狀態。當你對一個流進行操作時,例如過濾、映射或排序等,原始流不會受到影響,因為這些操作都是惰性的,只有在需要時才會執行。

然而,如果你在 forEach 方法中對元素進行了修改,那么這些修改會影響到原始流。這是因為 forEach 方法返回的是一個 void,它不能直接返回一個新的流。但是,你仍然可以在 forEach 方法中對原始流進行操作,然后使用 .collect() 方法將流轉換為列表或其他集合類型。

總之,返回一個新的流并不總是必要的,但它在某些情況下是有用的,特別是當你需要保留原始流的狀態時。

應用場景

peek() 方法主要用于調試。它可以用于查看流中的元素,但不會改變流的狀態。例如,你可以使用 peek() 方法來查看一個整數流中的偶數,但不會將它們從流中刪除。這個方法在多線程的場景下也很有用,因為它可以用于讀取隊列頭部元素。

總之,peek() 方法主要用于調試和多線程場景下讀取隊列頭部元素。
html](https://javaguide.cn/java/new-features/java8-common-new-features.html)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/212065.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/212065.shtml
英文地址,請注明出處:http://en.pswp.cn/news/212065.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

開發猿的平平淡淡周末---2023/12/10

天氣陰 溫度適宜17攝氏度 AM 昨晚竟然下小雨了&#xff0c;還好還好&#xff0c;昨天刷的兩個背包基本干了 一覺睡到日三竿&#xff0c;誰是神仙&#xff0c;我是神仙&#xff08;哈哈哈哈哈哈&#xff09; 刷會兒視頻 補充下起床的動力 洗漱&#xff0c;恰飯&#xff0c;肝…

電工--基本放大電路

電壓放大倍數、輸入電阻和輸出電阻是放大電路的三個主要性能指標 共發射極基本交流放大電路 晶體管&#xff1a;電流放大作用。能量較小的輸入信號通過晶體管的控制作用&#xff0c;去控制電源所共給的能量&#xff0c;以在輸出端獲得一個能量較大的信號 集電極電源電壓&#…

traj_dist 筆記:測量軌跡距離

python 筆記 &#xff1a;trajectory_distance包&#xff08;如何可以正確使用&#xff09;【debug篇】-CSDN博客 經過前面的debug后&#xff0c;在setup.py對應的位置&#xff0c;寫代碼&#xff08;直接在別處import traj_dist我還是出問題&#xff09; 1 新建軌跡 import…

電子學會C/C++編程等級考試2021年12月(五級)真題解析

C/C++等級考試(1~8級)全部真題?點這里 第1題:書架 John最近買了一個書架用來存放奶牛養殖書籍,但書架很快被存滿了,只剩最頂層有空余。 John共有N頭奶牛(1 ≤ N ≤ 20,000),每頭奶牛有自己的高度Hi(1 ≤ Hi ≤ 10,000),N頭奶牛的總高度為S。書架高度為B(1 ≤ B ≤ S &…

[LeetCode周賽復盤] 第 375 場周賽20231210

[LeetCode周賽復盤] 第 375 場周賽20231210 一、本周周賽總結100143. 統計已測試設備1. 題目描述2. 思路分析3. 代碼實現 100155. 雙模冪運算1. 題目描述2. 思路分析3. 代碼實現 100137. 統計最大元素出現至少 K 次的子數組1. 題目描述2. 思路分析3. 代碼實現 100136. 統計好分…

Java中多態的一些簡單理解

什么是多態 1.面向對象的三大特性&#xff1a;封裝、繼承、多態。從一定角度來看&#xff0c;封裝和繼承幾乎都是為多態而準備的。這是我們最后一個概念&#xff0c;也是最重要的知識點。 2.多態的定義&#xff1a;指允許不同類的對象對同一消息做出響應。即同一消息可以根據發…

Linux用戶和權限

一、認知root用戶 1.1 了解什么是root用戶&#xff08;超級管理員&#xff09; root用戶&#xff08;超級管理員&#xff09; 無論是Windows、MacOS、Linux均采用多用戶的管理模式進行權限管理。 在Linux系統中&#xff0c;擁有最大權限的賬戶名為&#xff1a;root&#x…

Java9及之后關于類加載器的新特性

為了保證兼容性&#xff0c;JDK9沒有從根本上改變三層類加載器的架構和雙親委派模型&#xff0c;但為了模塊化系統的順利運行&#xff0c;仍然發生了一些值得被注意的變動。 一、變動1 由于引入了模塊化概念&#xff0c;所以不同的類加載器回去加載屬于不同模塊的類 啟動類加…

Nginx負載均衡實戰

&#x1f3b5;負載均衡組件 ngx_http_upstream_module https://nginx.org/en/docs/http/ngx_http_upstream_module.html upstream模塊允許Nginx定義一組或多組節點服務器組&#xff0c;使用時可以通過多種方式去定義服務器組 樣例&#xff1a; upstream backend {server back…

從零開發短視頻電商 在AWS SageMaker已創建的模型列表中進行部署

1.導航到 SageMaker 控制臺。 2.在 SageMaker 控制臺的左側導航欄中&#xff0c;選擇 “模型” 選項。 3.在模型列表中&#xff0c;找到您要部署的模型。選擇該模型。 4.點擊 “創建端點” 選項或者點擊 “創建端點配置” 選項都可以進行部署。 選擇創建端點進去后還是會進行…

k8s中部署基于nfs的StorageClass

部署nfs服務 1.1 創建基礎鏡像(選做) 如果以docker的形式部署nfs server, 參考此步驟, 若否, 該步驟可忽略。 mkdir /data/nfs -p chmod 755 /data/nfs# NFS默認端口: 111、2049、20048 docker run -d \ --privileged \ --name nfs_server \ -p 111:111/tcp \ -p 111:111/ud…

[CTFshow 紅包挑戰] 刷題記錄

文章目錄 紅包挑戰7紅包挑戰8紅包挑戰9 紅包挑戰7 考點&#xff1a;xdebug拓展 源碼 <?php highlight_file(__FILE__); error_reporting(2);extract($_GET); ini_set($name,$value);system("ls ".filter($_GET[1])."" );function filter($cmd){$cmd s…

1832_org-mode的注釋處理

Grey # :OPTIONS ^:nil org-mode的注釋處理 關于這部分其實比較簡單&#xff0c;在我現在的使用訴求上來說要求不多。但是我覺得如果考慮以后把文學式編程作為一種開發的主要體驗的話&#xff0c;掌握這樣的操作很有必要。因為我可以控制部分信息的輸出。 自然&#xff0c;控…

LeetCode-數組-重疊、合并、覆蓋問題-中等難度

435. 無重疊區間 我認為區間類的題型&#xff0c;大多數考驗的是思維能力&#xff0c;以及編碼能力&#xff0c;該類題型本身并無什么算法可言&#xff0c;主要是思維邏輯&#xff0c;比如本題實際上你只需要能夠總結出重疊與不重疊的含義&#xff0c;再加上一點編碼技巧&#…

go-zero開發入門-API服務開發示例

接口定義 定義 API 接口文件 接口文件 add.api 的內容如下&#xff1a; syntax "v1"info (title: "API 接口文件示例"desc: "演示如何編寫 API 接口文件"author: "一見"date: "2023年12月07日"version: "…

Spring Boot 優雅地處理重復請求

前 言 對于一些用戶請求&#xff0c;在某些情況下是可能重復發送的&#xff0c;如果是查詢類操作并無大礙&#xff0c;但其中有些是涉及寫入操作的&#xff0c;一旦重復了&#xff0c;可能會導致很嚴重的后果&#xff0c;例如交易的接口如果重復請求可能會重復下單。 重復的場…

Verilog基礎:$random系統函數的使用

相關閱讀 Verilog基礎?編輯https://blog.csdn.net/weixin_45791458/category_12263729.html $random系統函數語法的BNF范式如下所示&#xff0c;有關BNF范式相關內容&#xff0c;可以瀏覽以往文章Verilog基礎&#xff1a;巴科斯范式(BNF)。 $random系統函數在每次調用時返回一…

【IDEA】IntelliJ IDEA中進行Git版本控制

本篇文章主要記錄一下自己在IntelliJ IDEA上使用git的操作&#xff0c;一個新項目如何使用git進行版本控制。文章使用的IDEA版本 IntelliJ IDEA Community Edition 2023.3&#xff0c;遠程倉庫為https://gitee.com/ 1.配置Git&#xff08;File>Settings&#xff09; 2.去Git…

[gRPC實現go調用go]

1什么是RPC RPC&#xff1a;Remote Procedure Call&#xff0c;遠程過程調用。簡單來說就是兩個進程之間的數據交互。正常服務端的接口服務是提供給用戶端(在Web開發中就是瀏覽器)或者自身調用的&#xff0c;也就是本地過程調用。和本地過程調用相對的就是&#xff1a;假如兩個…

深度優先遍歷(DFS)

時間復雜度與深搜一致&#xff1b;