카테고리 없음
Java Features
머룽
2023. 4. 23. 14:06
요즘들어 Kotlin만 사용해서 Java의 새로운 문법? 기능들에 관심이 없었다. 그래서 오늘은 자바11~17까지의 문법적인 변화에 대해 알아보도록 하자. 대부분 문법적인 부분들만 살펴볼 예정이니 자세한 내용들은 해당 공식문서를 살펴보면 좋겠다.
Local-Variable Syntax for Lambda Parameters
Java 10부터 지역변수에var
키워드를 통해 타입추론을 할 수 있었다. 그런데 Lambda 표현식의 변수엔 사용 불가 했다. 하지만 이제는 Lambda expression에서도 변수에 var 키워드를 사용가능하다.
Stream.iterate(1, (var number) -> number + 1).limit(10).collect(Collectors.toList())
만약 변수가 2개이상일 경우엔 혼합해서 사용하면 안된다.
BiConsumer<Integer, Integer> foo = (var b1, Integer b2) -> {};
해당 코드는 올바르지 않다.
http client
java.net 의 HttpClient가 java 11부터 정식으로 표준화 되었다. java9에서 인큐베이팅 된 후 두번의 업그레이드 후 출시 되었다. 기존의HttpURLConnection
들은 사용하기 어렵고 사용자 친화적이지 않다. 그래서 다른 써드 라이브러리들을 많이 사용해왔다. 그러나 이제는 조금 편리하고 사용자 친화적인 HttpClient
를 사용하면 된다.
public static void main(String[] args) throws Exception {
var httpClient = HttpClient.newBuilder()
.build();
var httpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create("https://httpstat.us/200"))
.build();
var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
System.out.println(httpResponse.body());
}
근데 사용할 일이 있나 모르겠다.
Pattern Matching for instanceof
java 14부터 진행되고 있는 instanceof Pattern Matching 이다.
if (records instanceof Records) {
var r = (Records) records;
// use r
}
이전에는 위와 같이 instanceof로 비교한 후 해당 타입으로 캐스팅을 했다. 어쩌면 불필요한 코드였을지도 모른다. 그러나 이제는 그럴필요 없다.
if (records instanceof Records r) {
// use r
} else {
// cant use r
}
이제는 해당 타입의 캐스팅하지 않고 바로 사용할 수 있다. 만약 해당 조건에 만족하지 않으면 사용할 수 없다.
if (!(records instanceof Records r)) {
// cant use r
} else {
// use r
}
해당 변수로 조건문을 추가 할 수도 있다.
if (records instanceof Records r && r.name.length() > 10) {
// use r
}
편리한 기능이 추가 되었다.
Switch Expressions and Pattern Matching
java 12 부터 Switch Expressions을 사용할 수 있고 java17 부터 Pattern Matching을 사용할 수 있다. 이전에 switch문은 실수의 여지도 크며 불필요한 코드가 많았다.public static void getTransportPrint(Transport transport) {
switch (transport) {
case BUS:
System.out.println("take a bus");
break;
case TAXI:
System.out.println("take a taxi");
break;
case SUBWAY:
System.out.println("take a subway");
break;
default:
System.out.println("walking");
break;
}
}
break
키웓드를 실수로 넣지 않으면 버그가 나오기 싶다. 또한 break키워드는 불필요한 코드일지도 모른다.
이제는 간단한 표현식으로 바꿀수도 있다.
public static void getTransportPrint(Transport transport) {
switch (transport) {
case BUS -> System.out.println("take a bus");
case TAXI -> System.out.println("take a taxi");
case SUBWAY -> System.out.println("take a subway");
default -> System.out.println("walking");
}
}
실수의 여지도 적으며 코드도 간결해졌다. 물론 case에 여러 조건도 가능하다.
switch (transport) {
case BUS, TAXI -> System.out.println("take a bus and taxi");
case SUBWAY -> System.out.println("take a subway");
default -> System.out.println("walking");
}
표현식이라 리턴도 받을 수 있다.
public static int getTransportNumber(Transport transport) {
return switch (transport) {
case BUS, TAXI -> 1;
case SUBWAY -> 2;
};
}
추가적으로 yield
키워드도 추가되었다. case문에 추가적인 코드가 들어갈때 사용하면 된다.
public static int getTransportOrdinal(Transport transport) {
return switch (transport) {
case BUS -> 0;
case TAXI -> {
var ordinal = transport.ordinal();
// bla bla
yield 1200;
}
case SUBWAY -> {
// bla bla
yield 8000;
}
};
}
이전에는 타입체크를 할 경우 if문과 instanceof를 사용해 체크를 했다.
public static double getTypeNumberSwitch(Object o) {
var result = 0d;
if (o instanceof Number) {
var i = (Integer) o;
result = i.doubleValue();
} else if (o instanceof String) {
var s = (String) o;
result = Double.parseDouble(s);
}
return result;
}
불필요한 코드들도 많고 result에 계속 어싸인을 하고 있어 실수의 여지도 크다. 이제는 그럴 필요 없다.
public static double getTypeNumberSwitch(Object o) {
return switch (o) {
case Number i -> i.doubleValue();
case String s -> Double.parseDouble(s);
default -> 0;
};
}
간단하면서 한눈에 알아 볼수 있는 코드가 되었다.
Text Blocks
java 13 부터 나온 기능이다. 다른언어는 진작에 있었긴 한데 그래도 나오니 한결 나은거 같다. 멀티 라인의 string을 손쉽게 작성할 수 있다. 이전에는 멀티라인의 string 컨트롤하기 힘들었다. 예를들어 json을 예쁘게 만들고 싶다면 아래와 같이 해야 했다.final var text = "{\
" +
"\\t\\"name\\" : \\"wonwoo\\",\
" +
"\\t\\"address\\": \\"Carson, CA, 90746\\"\
" +
"}";
System.out.println(text);
보기만 해도 힘들다. 만들긴 더 힘들다. 그러나 이제는 손쉽게 만들수 있다.
final var text = """
{
"name" : "wonwoo",
"address": "Carson, CA, 90746"
}
""";
마지막 행으로 공백이 결정되므로 마지막행의 위치가 중요하다.
final var text = """
{
"name" : "wonwoo",
"address": "Carson, CA, 90746"
}
""";
위와 같이 작성했다면 아래와 같이 출력된다.
| {
| "name" : "wonwoo",
| "address": "Carson, CA, 90746"
| }
|
해당 표시로 여백을 표시하였다.
만약 text block 에는 여러줄을 표시하고 한줄로 출력이 되게 하고 싶다면 \\
(역슬래스)를 추가적으로 넣으면 된다.
final var text = """
Lorem ipsum dolor sit amet, consectetur adipiscing \\
elit, sed do eiusmod tempor incididunt ut labore \\
et dolore magna aliqua.\\
""";
출력시에는 한줄로 표기 된다.
여러 이스케이프문자들이 있는데 이건 문서를 찾아보자!
아쉬운게 하나 있는데 interpolation
이 없다. 잠깐 보기에는 향후에 추가 될 수도 있다고 하니 기다려보자.
아쉬운대로 몇가지 방법으로 해결 할 수 있다. replace
, String.format
와 text block을 지원하기 위해 추가된 String.formatted
을 사용하면 된다.
replace와 String.format은 이전부터 있었으니 생략하고
var text = """
red
green
blue
%s
""".formatted("white");
내부적으로는 String.format과 동일하다.
Record
java 14부터 추가된 Record는 값 타입을 쉽게 표현할 수 있는 기능이다. 자바는 쓸때없이 너무 장황하다라는 말이 많다. 값 타입의 클래스를 한번 만드려면 생성자, 접근자, equals, hashCode, toString, getter 등 너무 불필요하게 코드들을 작성해야 한다.Record
를 사용하면 이제 그럴 필요 없다.
public record Records(String name, String address) {
}
이제는 위와 같이만 해도 생성자, 접근자, equals, hashCode, toString, getter? 등이 만들어 진다.
추가적으로 생성자를 작성할 수 있다.
public Records(String name) {
this(name, "1528 Fillmore st, San Francisco");
}
Compact 생성자를 만들수 있다. Compact생성자는 지루한 변수할당없이 로직에 집중할 수 있도록 도와준다.
record Records(String name, String address) {
public Records {
if (name == null) {
throw new IllegalArgumentException("name must be not null!");
}
if (address == null) {
throw new IllegalArgumentException("addrss must be not null!");
}
}
}
예를들어 위의 코드는 name 과 address의 대한 유효성을 체크하는 부분이다.
혹은 다음과 같이 변수할당없이 로직에만 집중할 수 있게 도와주기도 한다.
record Rational(int num, int denom) {
Rational {
int gcd = gcd(num, denom);
num /= gcd;
denom /= gcd;
}
}
이는 다음과 같은 코드이다.
record Rational(int num, int denom) {
Rational(int num, int demon) {
// Normalization
int gcd = gcd(num, denom);
num /= gcd;
denom /= gcd;
// Initialization
this.num = num;
this.denom = denom;
}
}
몇가지 제약조건들이 있다. 예를들어 extends절이 없고, final class 이며 abstract도 할 수 없다. 더 많은 제약 조건이 있으니 문서를 참고하면 되겠다.
Sealed Class
kotlin의 Sealed 클래스랑 문법만 다르지 거의 동일하다. Pattern Matching에도 사용할 수 있다. 예제부터 보자.
sealed interface TransportSealed permits BusSealed, TaxiSealed {
}
sealed class BusSealed implements TransportSealed {
public String getBus() {
return "take a bus";
}
}
final class TaxiSealed implements TransportSealed {
public String getTaxi() {
return "take a taxi";
}
}
final class ExpressBus extends BusSealed {
@Override
public String getBus() {
return super.getBus() + " Express";
}
}
sealed class 는 sealed 키워드를 통해 만들수 있으니 봉인된 클래스를 permits 키워드를 통해 선언 할 수 있다. permits에 없는 클래스는 사용할 수 없다.
봉인된 클래스에는 non-sealed
, sealed
와 final
, 세 키워드중에 하나를 선택해야만 한다.
non-sealed 은 상속이 가능하다.
final 은 상속이 불가하다.
sealed은 하위 클래스가 있어야 한다.
static void getTransport(TransportSealed transportSealed) {
switch (transportSealed) {
case ExpressBus expressBus -> System.out.println(expressBus.getBus());
case BusSealed bus -> System.out.println(bus.getBus());
case TaxiSealed taxi -> System.out.println(taxi.getTaxi());
}
}
위와 같이 switch을 이용해서 Pattern Matching도 할 수 있다. switch 문에 봉인된 클래스가 하나라도 없으면 컴파일에러가 발생한다.
또한 record 클래스에도 사용할수 있으니 참고하면 되겠다.
Miscellaneous
Helpful NullPointerExceptions
NullPointException의 message가 좀 더 명확하게 나온다.private static void lowerCase(String str) {
System.out.println(str.toLowerCase());
}
String str = null;
//Null Point Exception
lowerCase(str);
위와 같은 코드가 있을 경우 정확히 어디에서 에러가 발생한지 메시지로 통해 확인할 수 있다.
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toLowerCase()" because "str" is null
사실 위의 코드는 굳이 메시지 말고도 라인만 봐도 명확하다. 하지만 좀 더 복잡한 경우에는 도움이 많이 될 것 같다. 아래와 같이 말이다.
lowerCase(a.b.c.i);
만약 b가 null이라면 메시지는 다음과 같다.
Exception in thread "main" java.lang.NullPointerException: Cannot read field "c" because "a.b" is null
toList
Strem을 자주 사용한다면 많이 사용되는 terminal operation.collect(Collectors.toList())
을 간편하게 줄일 수 있다.
Stream.of(1, 2, 3, 4, 5).toList();
귀찮게 collect를 사용하지 않아도 바로 List로 만들 수 있다.
필자가 알아본건 여기까지다. 물론 더 많은 내용이 있으니 공식문서들을 잘 살펴보면 되겠다. 사용법들을 알았으니 좀 더 각자 딥하게 들어가는 것도 좋겠다.
필자는 언젠가 다시 자바를 사용할 수 있으니 그때 더 딥하게 알아보자.