JavaRush /Java Blog /Random-KO /커피 브레이크 #155. Java의 상위 10개 함수

커피 브레이크 #155. Java의 상위 10개 함수

Random-KO 그룹에 게시되었습니다

Java의 상위 10개 함수

출처: DZone 이 기사에는 개발자가 일상 작업에서 자주 사용하는 10가지 Java 프로그래밍 기능이 나열되어 있습니다. 커피 브레이크 #155.  Java의 상위 10개 함수 - 1

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. 고급 스위치 표현

전통적인 스위치 문은 처음부터 Java에 있었고 당시 C 및 C++를 연상시켰습니다. 괜찮았지만 언어가 발전함에 따라 이 연산자는 Java 14까지 어떠한 개선도 제공하지 않았습니다. 물론 몇 가지 단점도 있었습니다. 가장 악명 높은 것은 fall -through였습니다. 이 문제를 해결하기 위해 개발자는 상용구 코드인 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. 기록

레코드는 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이 발생합니다. 이것은 실수입니다. 이 문제를 해결하기 위해 개시자는 일반적으로 값이 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을 반환합니다. 이제 개시자는 문제를 처리하기 위해 먼저 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. 자바 날짜 시간 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.NullPointer예외

모든 개발자는 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. 람다 표현식

람다 표현식은 아마도 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");
    }
});
이 코드 조각을 자세히 살펴보면 익명의 내부 클래스 list()를 메소드에 전달했습니다 . 그리고 내부 클래스에는 파일 필터링 논리를 배치했습니다. 본질적으로 우리는 논리 주변의 패턴이 아니라 논리의 이 부분에 관심이 있습니다. 람다 표현식을 사용하면 전체 템플릿을 제거하고 관심 있는 코드를 작성할 수 있습니다. 예는 다음과 같습니다.
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list((dir, name) -> name.endsWith(".java"));
물론 이는 하나의 예일 뿐이며 람다 식에는 다른 많은 이점이 있습니다.

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