JavaRush /จาวาบล็อก /Random-TH /คอฟฟี่เบรค #155. 10 ฟังก์ชั่นยอดนิยมใน Java

คอฟฟี่เบรค #155. 10 ฟังก์ชั่นยอดนิยมใน Java

เผยแพร่ในกลุ่ม

10 ฟังก์ชั่นยอดนิยมใน Java

ที่มา: DZone บทความนี้แสดงรายการคุณสมบัติการเขียนโปรแกรม Java สิบประการที่นักพัฒนามักใช้ในการทำงานประจำวัน คอฟฟี่เบรค #155.  ฟังก์ชัน 10 อันดับแรกใน Java - 1

1. วิธีการรวบรวมโรงงาน

คอลเลกชันเป็นหนึ่งในคุณสมบัติที่ใช้บ่อยที่สุดในการเขียนโปรแกรม พวกมันถูกใช้เป็นภาชนะที่เราจัดเก็บสิ่งของและส่งต่อ คอลเลกชันยังใช้ในการจัดเรียง ค้นหา และทำซ้ำวัตถุ ทำให้ชีวิตของโปรแกรมเมอร์ง่ายขึ้นมาก มีอินเทอร์เฟซพื้นฐานหลายอย่าง เช่น รายการ ตั้งค่า แผนที่ ตลอดจนการใช้งานหลายอย่าง วิธีดั้งเดิมในการสร้างคอลเลกชั่นและแผนที่อาจดูเหมือนซับซ้อนสำหรับนักพัฒนาหลายคน นั่นเป็นสาเหตุที่ 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 แน่นอนว่ามันมีข้อเสียอยู่บ้าง สิ่งที่ฉาวโฉ่ที่สุดคือการล้มเหลว: เพื่อแก้ปัญหานี้ นักพัฒนาจึงใช้คำสั่งแบ่งซึ่งส่วนใหญ่เป็นโค้ดสำเร็จรูป อย่างไรก็ตาม Java 14 ได้เปิดตัวคำสั่ง switch เวอร์ชันปรับปรุงพร้อมรายการฟังก์ชันที่ใหญ่กว่ามาก ตอนนี้เราไม่จำเป็นต้องเพิ่มคำสั่งแบ่งอีกต่อไปและจะช่วยแก้ปัญหาความล้มเหลวได้ นอกจากนี้ คำสั่ง 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 โดยไม่คำนึงว่าค่าจะเป็นโมฆะหรือไม่ ตัวอย่าง:
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();
    }
}
ที่นี่เราได้เขียน เมธอด findName ใหม่ ด้วย ตัวเลือก Optionalเพื่อไม่ให้ส่งคืนค่าใดๆ สิ่งนี้จะแจ้งเตือนโปรแกรมเมอร์ล่วงหน้าและแก้ไขปัญหา

6. Java วันที่ เวลา API

นักพัฒนาซอฟต์แวร์ทุกคนสับสนกับการคำนวณวันที่และเวลาในระดับหนึ่ง นี่ไม่ใช่การพูดเกินจริง สาเหตุหลักมาจากการขาด Java API ที่ดีสำหรับการทำงานกับวันที่และเวลา ตอนนี้ปัญหานี้ไม่เกี่ยวข้องอีกต่อไปแล้ว เนื่องจาก Java 8 เปิดตัวชุด API ที่ยอดเยี่ยมในแพ็คเกจ java.time ซึ่งแก้ไขปัญหาทั้งหมดที่เกี่ยวข้องกับวันที่และเวลา แพ็คเกจ java.time มีอินเทอร์เฟซและคลาสมากมายที่ช่วยขจัดปัญหาส่วนใหญ่ รวมถึงเขตเวลาด้วย คลาสที่ใช้บ่อยที่สุดในแพ็คเกจนี้คือ:
  • LocalDate
  • เวลาท้องถิ่น
  • LocalDateTime
  • ระยะเวลา
  • ระยะเวลา
  • โซนวันที่และเวลา
ตัวอย่างการใช้คลาสจากแพ็คเกจ 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

นักพัฒนาทุกคนเกลียด 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;
    }

   //getter
   //setter
}

class Name {
    private String firstName;
    private String lastName;

    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

   //getter
   //setter
}
ดูวิธีการพื้นฐานในข้อนี้ เราเห็นว่า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 Thread การเขียนโปรแกรมเธรด 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 อาจเป็นคุณลักษณะที่ทรงพลังที่สุดของภาษา Java พวกเขาเปลี่ยนวิธีการเขียนโค้ดของเรา นิพจน์แลมบ์ดาเปรียบเสมือนฟังก์ชันนิรนามที่สามารถรับอาร์กิวเมนต์และส่งกลับค่าได้ เราสามารถกำหนดฟังก์ชันให้กับตัวแปรและส่งผ่านเป็นอาร์กิวเมนต์ให้กับเมธอดได้ และเมธอดก็สามารถคืนค่าฟังก์ชันนั้นได้ เขามีร่างกาย ข้อแตกต่างเพียงอย่างเดียวจากวิธีนี้คือไม่มีชื่อ สำนวนที่สั้นและกระชับ โดยปกติแล้วจะไม่มีโค้ดสำเร็จรูปจำนวนมาก มาดูตัวอย่างที่เราจำเป็นต้องแสดงรายการไฟล์ทั้งหมดในไดเร็กทอรีที่มีนามสกุล .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");
    }
});
หากคุณดูโค้ดนี้อย่างใกล้ชิด เราได้ส่งผ่าน class 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 การดำเนินการดังกล่าวมีความจำเป็นโดยธรรมชาติ เราต้องเขียนโค้ดตามความตั้งใจของเรา (เช่น สิ่งที่เราต้องการทำให้สำเร็จ) และวิธีที่เราต้องการจะทำมัน ด้วยการประดิษฐ์นิพจน์แลมบ์ดาและ 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();
}
แม้ว่าทั้งสองวิธีจะส่งกลับค่าเดียวกัน แต่เราก็สามารถเห็นความแตกต่างได้อย่างชัดเจน
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION