bsj805 / study_doc

java 8버전에서 17버전으로의 변화
0 stars 0 forks source link

java 8 에서 17까지 특징들 #3

Open bsj805 opened 2 years ago

bsj805 commented 2 years ago

https://reflectoring.io/java-release-notes/

bsj805 commented 2 years ago

8. JAVA 8 특징

8.1 method Refrence

lambda 표현식을 더 간단하게 표현하는 방법으로,

Consumer<String> func = text -> System.out.println(text);
func.accept("Hello");
// 실행 결과
// Hello

이런식으로 표현해야 했던 것을,

Consumer<String> func = System.out::println;
func.accept("Hello");
// 실행 결과
// Hello
``` 이렇게 표현할 수 있게 된 것.
인자가 두개 이상인 경우
https://stackoverflow.com/questions/23139725/using-method-reference-instead-of-multi-argument-lambda
static 메서드냐, instance 메서드냐에 따라서 
```java
(a, b, c) -> SomeClass.func(a, b, c); //static 메서드의 경우 SomeClass::func와 동일
(a, b, c) -> a.func(b, c); // instance 메서드의 경우 SomeClass::func와 동일

8.2 람다 표현식 및 stream API

8.3 Default Methods

추상클래스나 인터페이스에 디폴트 메서드를 넣어서 필요한 메서드만 오버라이딩 할 수 있도록 사용가능

8.4 Type Annotations

1) null을 체크하는 모듈을 만들어요. 모듈 이름을 "NonNull"이라고 할게요

2) 다음과 같이 사용하면되요

@NonNull String str; // https://www.javatpoint.com/java-8-type-annotations-and-repeating-annotations 이 경우 str이 null이면 throw해버린다.

empty arrayList 못 만들게 하려면

public class TypeAnnotations {

    public static void main(String[] args) {
        List<String> request =
                new @NotEmpty ArrayList<>(Arrays.stream(args).collect(
                        Collectors.toList()));
    }
}

이런식으로 해야 한다. Emtpy이면 throw하게 될 것이다.

public class TypeAnnotations {

    public static void main(String[] args) {
        List<@Email String> emails;
    }
}

이런식으로 이메일 포맷에 맞는 String만 있다는 것을 보장할 수 있다.

8.5 Repeating Annotations

Cron식처럼

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }

여러번 annotation을 사용하고 싶을 수 있다. 아래는, annotation으로 된 값을 가져와서 단순히 출력해보는 예제

// Importing required packages for repeating annotation   
import java.lang.annotation.Repeatable;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
// Declaring repeatable annotation type  
@Repeatable(Games.class)  
@interfaceGame{  
    String name();  
    String day();  
}  
// Declaring container for repeatable annotation type  
@Retention(RetentionPolicy.RUNTIME)  
@interfaceGames{  
    Game[] value();  
}  
// Repeating annotation  
@Game(name = "Cricket",  day = "Sunday")  
@Game(name = "Hockey",   day = "Friday")  
@Game(name = "Football", day = "Saturday")  
public class RepeatingAnnotationsExample {  
    public static void main(String[] args) {  
        // Getting annotation by type into an array  
        Game[] game = RepeatingAnnotationsExample.class.getAnnotationsByType(Game.class);  
        for (Gamegame2 : game) {    // Iterating values  
            System.out.println(game2.name()+" on "+game2.day());  
        }  
    }  
}  

8.6 Method Parameter Reflection

파라미터의 이름을 runtime에 얻는 것을 허용하는 문법이다. https://stackoverflow.com/questions/2237803/can-i-obtain-method-parameter-name-using-java-reflection If I have a class like this:

public class Whatever
{
  public void aMethod(int aParam);
}

is there any way to know that aMethod uses a parameter named aParam, that is of type int?

즉 파라미터로 aParam을 쓰는데, 이 이름을 얻을 수 있다.

public static List<String> getParameterNames(Method method) {
        Parameter[] parameters = method.getParameters();
        List<String> parameterNames = new ArrayList<>();

        for (Parameter parameter : parameters) {
            if(!parameter.isNamePresent()) {
                throw new IllegalArgumentException("Parameter names are not present!");
            }

            String parameterName = parameter.getName();
            parameterNames.add(parameterName);
        }

        return parameterNames;
    }

파라미터 이름들을 가져올 수 있다.

bsj805 commented 2 years ago

Java 9 특징

9.1 module 변화

9.2 Try-with-resources

autoclosable resource on a try-catch block. JVM보고 이 코드가 실행 된 이후에 이 resource들을 해제 시켜도 된다 라고 말할 수 있다. 단, declared resource가 Autoclosable interface를 implement했어야 한다.

이를테면, 수동으로 자원을 해제하려면 예전에는

public class TryWithResources {
    public static void main(String[] args) {
        BufferedReader br = new BufferedReader(
                new StringReader("Hello world example!"));
        try {
            System.out.println(br.readLine());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

이런식으로 finally를 이용해야 했다. 하지만, java8부터는 try 를 선언하면서 resource를 선언할 수 있다.

public class TryWithResources {
    public static void main(String[] args) {
        final BufferedReader br3 = new BufferedReader(
                new StringReader("Hello world example3!"));
        try (BufferedReader reader = br3) {
            System.out.println(reader.readLine());
        } catch (IOException e) {
            System.out.println("Error happened!");
        }
    }
}

reader가 매번 해제될 것을 알 수 있다.

https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

9.3 Diamond operator를 inner Annonymous Class에 붙일 수 있다.

public class DiamondOperator {

    StringAppender<String> appending = new StringAppender<>() {
        @Override
        public String append(String a, String b) {
            return new StringBuilder(a).append("-").append(b).toString();
        }
    };

    public abstract static class StringAppender<T> {
        public abstract T append(String a, String b);
    }
}

단순히 String a와 b가 주어지면, a - b 를 만드는 append라는 함수가 있다. 지금 append라는 메서드를 쓰려고 익명클래스를 사용한 경우인데, StringAppender<> 이 부분에 diamond operator가 있으면 java8까지는 컴파일 에러가 났다. 하지만 이제 유추할 수 있기 때문에 java9부터는 오류가 나지 않는다.

9.4 Usage of Private Interface Methods

java8에서는 인터페이스에서 private methods를 사용할 수 없다. 보통 복잡한 구현 로직에 필요한 함수들은 private으로 감추는 경우가 많은데, java 9에서는 interface에서도 private method를 사용할 수 있게 한다.

public class PrivateInterfaceMethods {

    public static void main(String[] args) {
        TestingNames names = new TestingNames();
        System.out.println(names.fetchInitialData());
    }

    public static class TestingNames implements NamesInterface {
        public TestingNames() {
        }
    }

    public interface NamesInterface {
        default List<String> fetchInitialData() {
            try (BufferedReader br = new BufferedReader(
                    new InputStreamReader(this.getClass()
                            .getResourceAsStream("/names.txt")))) {
                return readNames(br);
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        private List<String> readNames(BufferedReader br)
                throws IOException {
            ArrayList<String> names = new ArrayList<>();
            String name;
            while ((name = br.readLine()) != null) {
                names.add(name);
            }
            return names;
        }
    }
}

이런식으로, 원래는 readNames(BufferedReader br)를 다른 utils 클래스 같은데에서 static 메서드로 두어서 처리를 하거나, 위의 fetchInitialData()함수가 엄청 길어지는 코드를 짰어야 했을 것이다.

bsj805 commented 2 years ago

JAVA 10

10.1 Local Variable Type Inference

자바에서 항상 지역변수를 선언할 때 explicit type이 필요 했는데, 사실 우리가 쓰고 읽을 때 어떤 type이 필요한지 그냥 뻔하다. 따라서 많은 부분의 코드는 쓸모없는 type선언으로 채워져 있다. var 타입은 우리가 type을 신경쓰지 않도록 해준다.

public class LocalTypeVar {

    public void explicitTypes() {
        Person Roland = new Person("Roland", "Deschain");
        Person Susan = new Person("Susan", "Delgado");
        Person Eddie = new Person("Eddie", "Dean");
        Person Detta = new Person("Detta", "Walker");
        Person Jake = new Person("Jake", "Chambers");

        List<Person> persons =
                List.of(Roland, Susan, Eddie, Detta, Jake);

        for (Person person : persons) {
            System.out.println(person.name + " - " + person.lastname);
        }
    }
}

이 옛날 버전의 코드는 그냥 Person list를 만들어서 print하는 코드이다.

var을 이용하면 type 선언이 따로 필요없다.

public class LocalTypeVar {

    public void varTypes() {
        var Roland = new Person("Roland", "Deschain");
        var Susan = new Person("Susan", "Delgado");
        var Eddie = new Person("Eddie", "Dean");
        var Detta = new Person("Detta", "Walker");
        var Jake = new Person("Jake", "Chambers");

        var persons = List.of(Roland, Susan, Eddie, Detta, Jake);

        for (var person : persons) {
            System.out.println(person.name + " - " + person.lastname);
        }
    }
}
bsj805 commented 2 years ago

Java 11

11.1 Local Variable Type in Lambda Expressions

var 키워드를 이제 lambda expressions에서 사용할 수 있다.

a가 이름에 들어 있지 않은 이름들을 필터링하는 로직을 다시 짜보면,

public class LocalTypeVarLambda {

    public void explicitTypes() {
        var Roland = new Person("Roland", "Deschain");
        var Susan = new Person("Susan", "Delgado");
        var Eddie = new Person("Eddie", "Dean");
        var Detta = new Person("Detta", "Walker");
        var Jake = new Person("Jake", "Chambers");

        var filteredPersons =
                List.of(Roland, Susan, Eddie, Detta, Jake)
                        .stream()
                        .filter((var x) -> x.name.contains("a"))
                        .collect(Collectors.toList());
        System.out.println(filteredPersons);
    }
}

사실 근데 var 키워드 빼고 (x) -> x.name.contains("a") 를 해도 var 이 생략된 것이다. 내부 로직은 똑같이 동작한다.

bsj805 commented 2 years ago

JAVA 14

14.1 Old Way of Switch Statements

월 별로 날짜를 리턴하는 코드를 switch문을 짠다고 했을 때,

public class SwitchExpression {

    public static void main(String[] args) {
        int days = 0;
        Month month = Month.APRIL;

        switch (month) {
            case JANUARY, MARCH, MAY, JULY, AUGUST, OCTOBER, DECEMBER :
                days = 31;
                break;
            case FEBRUARY :
                days = 28;
                break;
            case APRIL, JUNE, SEPTEMBER, NOVEMBER :
                days = 30;
                break;
            default:
                throw new IllegalStateException();
        }
    }
}

근데 이제 break 없이도 리턴시켜서 바로 스위치문을 탈출하는 로직을 만들 수 있다

public class SwitchExpression {

    public static void main(String[] args) {
        int days = 0;
        Month month = Month.APRIL;

        days = switch (month) {
            case JANUARY, MARCH, MAY, JULY, AUGUST, OCTOBER, DECEMBER -> 31;
            case FEBRUARY -> 28;
            case APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30;
            default -> throw new IllegalStateException();
        };
    }
}

두개를 섞어 쓸 수는 없다.

String name = switch (id) {
            case "a", "b", "c" -> {
                System.out.println("hello" + id);
                yield "case" + id;
            }
            case "szz":
            case "sd" -> "sd";
            default -> "default" + id;
        };

이런식으로 : 을 쓰려고 하면 Different case kinds used in the switch라고 에러가 뜨면서 실패한다.

yield 키워드는 switch안에서의 리턴값을 만들어준다. (return을 내부에서 쓸 수는 없다.) block에서의 return값을 yield로 처리한다고 한다.

bsj805 commented 2 years ago

JAVA15

15.1 Text Blocks

public class TextBlocks {

    public static void main(String[] args) {
        System.out.println(
        """
                <!DOCTYPE html>
                <html>
                    <head>
                        <title>Example</title>
                    </head>
                    <body>
                        <p>This is an example of a simple HTML 
                        page with one paragraph.</p>
                    </body>
                </html>      
                """
        );
    }
}

이런식으로, text를 뭉텅이로 쓸 수 있게 바뀌었다.

"""라는 문법이 있다. 단, 이걸 쓴 다음에 무조건 newline이 들어가줘야 제대로 인식한다. Illegal text block start: missing new line after opening quotes. 에러가 날 수 있다.

bsj805 commented 2 years ago

JAVA16

16.1 Pattern Matching of instanceof

inline으로 variable을 선언해서 if-else블럭 안에서 사용할 수 있다. 만약 instanceof 가 맞으면 해당 variable에 assign이 된다.

    public static double priceOld(Vehicle v) {
        if (v instanceof Car) {
            Car c = (Car) v;
    }

이런식으로 썼다면

    public static double priceOld(Vehicle v) {
        if (v instanceof Car c) {
           //  c를 사용할 수 있다
    }

단, 여기서의 local variable c 의 scope는 저 if 블록 안으로 한정된다.

16.2 Records

default로 final인 enum같은 클래스로,

public record VehicleRecord(
String code, 
String engineType
) {}

이 한줄만 써도

등이 생성되어 간편하게 쓸 수 있다.

bsj805 commented 2 years ago

JAVA 17

17.1 SEALED classes

final을 class에 붙이면 다른 클래스들이 extend할 수 없다. 일부 class들만 extend할 수 있게 허용하려면 어떻게 해야 할까.

sealed class가 있다.

public sealed class Vehicle permits Bicycle, Car {...} 이렇게 하면 Vehicle은 무조건 BicycleCar중 하나라는 보장이 있다. 하위 클래스인 Bicyclepublic final class Bicycle extends Vehicle {...} 이런식으로 final을 붙여놔야 한다.