Java 中十大函數
資料來源:
DZone 本文列出了開發人員在日常工作中經常使用的十個 Java 程式設計功能。
1.集合工廠方法
集合是程式設計中最常使用的功能之一。它們被用作我們儲存物件並傳遞物件的容器。集合也用於排序、搜尋和重複對象,使程式設計師的工作變得更加輕鬆。它們有幾個基本接口,如List、Set、Map,以及幾個實作。對於許多開發人員來說,創建集合和地圖的傳統方式似乎很冗長。這就是為什麼 Java 9 引進了幾個簡潔的工廠方法。
列表:
List countries = List.of("Bangladesh", "Canada", "United States", "Tuvalu");
放:
Set countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
地圖:
Map countriesByPopulation = Map.of("Bangladesh", 164_689_383,
"Canada", 37_742_154,
"United States", 331_002_651,
"Tuvalu", 11_792);
當我們想要創建不可變的容器時,工廠方法非常有用。但如果您要建立可變集合,建議使用傳統方法。
2. 局部類型推斷
Java 10 增加了局部變數的型別推論。在此之前,開發人員在聲明和初始化物件時必須指定兩次類型。很累。看下面的例子:
Map> properties = new HashMap<>();
此處指示雙方的資訊類型。如果我們將它定義在一個地方,那麼程式碼閱讀器和Java編譯器就會很容易地理解它一定是一個Map類型。局部類型推斷就是這樣做的。這是一個例子:
var properties = new HashMap>();
現在一切都只寫一次,程式碼看起來也沒差多少。當我們呼叫一個方法並將結果儲存在一個變數中時,程式碼變得更短。例子:
var properties = getProperties();
並進一步:
var countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
儘管局部類型推斷似乎是一個方便的功能,但有些人批評它。一些開發人員認為這會降低可讀性。這比簡潔更重要。
3. 進階開關表達式
傳統的 switch 語句從一開始就存在於 Java 中,讓人想起當時的 C 和 C++。這很好,但隨著語言的發展,這個運算子直到 Java 14 才為我們提供任何改進。當然,它也有一些缺點。最臭名昭著的是
失敗:為了解決這個問題,開發人員使用了break語句,這些語句主要是樣板程式碼。然而,Java 14 引入了 switch 語句的改進版本,具有更大的函數列表。現在我們不再需要加入break語句,這解決了失敗問題。此外,switch 語句可以傳回一個值,這意味著我們可以將其用作表達式並將其指派給變數。
int day = 5;
String result = switch (day) {
case 1, 2, 3, 4, 5 -> "Weekday";
case 6, 7 -> "Weekend";
default -> "Unexpected value: " + day;
};
4. 記錄
儘管 Records 是 Java 16 中引入的相對較新的功能,但許多開發人員發現它非常有用,主要是由於不可變物件的建立。通常我們需要在程式中使用資料物件來儲存值或將值從一種方法傳遞到另一種方法。例如,一個用於傳輸x、y、z座標的類,我們將其寫如下:
package ca.bazlur.playground;
import java.util.Objects;
public final class Point {
private final int x;
private final int y;
private final int z;
public Point(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public int x() {
return x;
}
public int y() {
return y;
}
public int z() {
return z;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Point) obj;
return this.x == that.x &&
this.y == that.y &&
this.z == that.z;
}
@Override
public int hashCode() {
return Objects.hash(x, y, z);
}
@Override
public String toString() {
return "Point[" +
"x=" + x + ", " +
"y=" + y + ", " +
"z=" + z + ']';
}
}
這門課似乎太冗長了。在條目的幫助下,所有這些程式碼都可以替換為更簡潔的版本:
package ca.bazlur.playground;
public record Point(int x, int y, int z) {
}
5.可選
方法是我們定義條件的契約。我們指定參數的類型以及傳回類型。然後我們期望當該方法被呼叫時,它將按照約定行事。但是,我們經常從方法中得到 null,而不是指定類型的值。這是錯誤的。為了解決這個問題,發起者通常會使用 if 條件來測試該值,無論該值是否為 null。例子:
public class Playground {
public static void main(String[] args) {
String name = findName();
if (name != null) {
System.out.println("Length of the name : " + name.length());
}
}
public static String findName() {
return null;
}
}
看上面的程式碼。
findName方法應該回傳
String,但它會傳回 null。發起者現在必須先檢查空值來處理問題。如果發起者忘記這樣做,那麼我們最終將得到
一個 NullPointerException。另一方面,如果方法簽名指示不返回的可能性,那麼這將解決所有混亂。
這就是Optional可以幫助我們的地方。
import java.util.Optional;
public class Playground {
public static void main(String[] args) {
Optional optionalName = findName();
optionalName.ifPresent(name -> {
System.out.println("Length of the name : " + name.length());
});
}
public static Optional findName() {
return Optional.empty();
}
}
在這裡,我們使用
Optional選項 重寫了
findName方法,以不傳回任何值。這會提前提醒程式設計師並修復問題。
6.Java 日期時間 API
每個開發人員都對計算日期和時間感到不同程度的困惑。這並不誇張。這主要是由於缺乏用於處理日期和時間的良好 Java API。現在這個問題不再重要,因為Java 8在java.time套件中引入了一組優秀的API,它解決了與日期和時間相關的所有問題。java.time 套件有許多介面和類,可以消除大多數問題,包括時區。該包中最常用的類別是:
- 本地日期
- 當地時間
- 本地日期時間
- 期間
- 時期
- 分區日期時間
使用 java.time 套件中的類別的範例:
import java.time.LocalDate;
import java.time.Month;
public class Playground3 {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2022, Month.APRIL, 4);
System.out.println("year = " + date.getYear());
System.out.println("month = " + date.getMonth());
System.out.println("DayOfMonth = " + date.getDayOfMonth());
System.out.println("DayOfWeek = " + date.getDayOfWeek());
System.out.println("isLeapYear = " + date.isLeapYear());
}
}
使用 LocalTime 類別計算時間的範例:
LocalTime time = LocalTime.of(20, 30);
int hour = time.getHour();
int minute = time.getMinute();
time = time.withSecond(6);
time = time.plusMinutes(3);
新增時區:
ZoneId zone = ZoneId.of("Canada/Eastern");
LocalDate localDate = LocalDate.of(2022, Month.APRIL, 4);
ZonedDateTime zonedDateTime = date.atStartOfDay(zone);
7.空指針異常
每個開發人員都討厭 NullPointerException。當 StackTrace 不提供有關問題到底是什麼的有用資訊時,這可能會特別困難。為了演示這一點,讓我們來看看範例程式碼:
package com.bazlur;
public class Main {
public static void main(String[] args) {
User user = null;
getLengthOfUsersName(user);
}
public static void getLengthOfUsersName(User user) {
System.out.println("Length of first name: " + user.getName().getFirstName());
}
}
class User {
private Name name;
private String email;
public User(Name name, String email) {
this.name = name;
this.email = email;
}
}
class Name {
private String firstName;
private String lastName;
public Name(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
看看這篇文章中的基本方法。我們看到接下來會拋出
一個 NullPointerException。如果我們在Java 14之前的版本中執行並編譯程式碼,我們將得到以下StackTrace:
Exception in thread "main" java.lang.NullPointerException
at com.bazlur.Main.getLengthOfUsersName(Main.java:11)
at com.bazlur.Main.main(Main.java:7)
這裡關於NullPointerException發生的 地點和原因的資訊很少。但在Java 14及之後的版本中,我們在StackTrace中獲取了更多的信息,這非常方便。在 Java 14 中我們將看到:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "ca.bazlur.playground.User.getName()" because "user" is null
at ca.bazlur.playground.Main.getLengthOfUsersName(Main.java:12)
at ca.bazlur.playground.Main.main(Main.java:8)
8. 完整的未來
我們一行一行地編寫程序,並且它們通常是逐行執行的。但有時我們需要並行執行以使程式更快。為此,我們通常使用 Java 執行緒。Java 線程程式設計並不總是關於平行程式設計。相反,它使我們能夠編寫多個獨立的程式模組,這些模組將獨立執行,甚至通常非同步執行。然而,線程程式設計是相當困難的,尤其是對於初學者來說。這就是為什麼 Java 8 提供了更簡單的 API,讓您可以非同步執行程式的一部分。讓我們來看一個例子。假設我們需要呼叫三個 REST API,然後合併結果。我們可以一一稱呼它們。如果每個訊息大約需要 200 毫秒,那麼接收它們的總時間將需要 600 毫秒。如果我們可以並行運行它們怎麼辦?由於現代處理器通常是多核心的,因此它們可以輕鬆處理三個不同處理器上的三個剩餘呼叫。使用 CompletableFuture 我們可以輕鬆做到這一點。
package ca.bazlur.playground;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class SocialMediaService {
public static void main(String[] args) throws ExecutionException, InterruptedException {
var service = new SocialMediaService();
var start = Instant.now();
var posts = service.fetchAllPost().get();
var duration = Duration.between(start, Instant.now());
System.out.println("Total time taken: " + duration.toMillis());
}
public CompletableFuture> fetchAllPost() {
var facebook = CompletableFuture.supplyAsync(this::fetchPostFromFacebook);
var linkedIn = CompletableFuture.supplyAsync(this::fetchPostFromLinkedIn);
var twitter = CompletableFuture.supplyAsync(this::fetchPostFromTwitter);
var futures = List.of(facebook, linkedIn, twitter);
return CompletableFuture.allOf(futures.toArray(futures.toArray(new CompletableFuture[0])))
.thenApply(future -> futures.stream()
.map(CompletableFuture::join)
.toList());
}
private String fetchPostFromTwitter() {
sleep(200);
return "Twitter";
}
private String fetchPostFromLinkedIn() {
sleep(200);
return "LinkedIn";
}
private String fetchPostFromFacebook() {
sleep(200);
return "Facebook";
}
private void sleep(int millis) {
try {
TimeUnit.MILLISECONDS.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
9. Lambda 表達式
Lambda 表達式可能是 Java 語言最強大的功能。他們改變了我們編寫程式碼的方式。lambda 表達式就像一個可以接受參數並傳回值的匿名函數。我們可以將函數指派給一個變數並將其作為參數傳遞給一個方法,並且該方法可以傳回它。他有身體。與該方法唯一的區別是沒有名稱。表達方式簡短而簡潔。它們通常不包含大量樣板程式碼。讓我們來看一個範例,我們需要列出目錄中副檔名為 .java 的所有檔案。
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}
});
如果你仔細觀察這段程式碼,我們已經將匿名內部類別
list()傳遞給了方法。在內部類別中我們放置了過濾文件的邏輯。本質上,我們感興趣的是這部分邏輯,而不是邏輯周圍的模式。lambda 表達式允許我們刪除整個模板,我們可以編寫我們感興趣的程式碼。這是一個例子:
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list((dir, name) -> name.endsWith(".java"));
當然,這只是一個範例;lambda 表達式還有許多其他好處。
10. 串流API
在我們的日常工作中,常見的任務之一就是處理資料集。它有一些常見的操作,例如過濾、轉換和收集結果。在 Java 8 之前,此類操作本質上是命令式的。我們必須根據我們的意圖(即我們想要實現的目標)以及我們希望如何做到這一點來編寫程式碼。隨著 lambda 表達式和 Stream API 的發明,我們現在可以以宣告方式編寫資料處理函數。我們只需要表明我們的意圖,我們不需要寫下我們是如何得到結果的。以下是一個例子:我們有一個書籍列表,我們想要找到所有 Java 書籍的名稱,以逗號分隔並排序。
public static String getJavaBooks(List books) {
return books.stream()
.filter(book -> Objects.equals(book.language(), "Java"))
.sorted(Comparator.comparing(Book::price))
.map(Book::name)
.collect(Collectors.joining(", "));
}
上面的程式碼簡單、可讀、簡潔。但下面您可以看到替代的命令式程式碼:
public static String getJavaBooksImperatively(List books) {
var filteredBook = new ArrayList();
for (Book book : books) {
if (Objects.equals(book.language(), "Java")){
filteredBook.add(book);
}
}
filteredBook.sort(new Comparator() {
@Override
public int compare(Book o1, Book o2) {
return Integer.compare(o1.price(), o2.price());
}
});
var joiner = new StringJoiner(",");
for (Book book : filteredBook) {
joiner.add(book.name());
}
return joiner.toString();
}
儘管兩種方法返回相同的值,但我們可以清楚地看到差異。
GO TO FULL VERSION