🎯 var 키워드
Java 10에서 도입된
var
키워드는 로컬 변수의 타입을 컴파일러가 자동으로 추론하는 기능이다.명시적으로 타입을 선언하지 않아도 컴파일러가 변수의 타입을 추론할 수 있는데 반복적인 타입 선언을 해결해 줄 수 있다.
특히 자료구조나 제네릭 타입에 대한 타입 선언이 장황하게 느껴진다면 고려해볼만하다.
하지만 오히려 코드의 타입을 추론하기 어렵다는 단점도 있으므로 트레이드오프 하여 결정하되 조직의 컨벤션을 최우선으로 해야한다.
var
키워드를 사용한 모습이다.
var name = "Java"; // String var age = 25; // int var list = new ArrayList<String>(); // List<String>
타입 추론은 코틀린, 스칼라, 타입스크립트 등과 같은 프로그래밍 언어에서 이미 일반적으로 제공되는 기능이며,
이는 개발자가 비즈니스 로직에 좀 더 집중할 수 있도록 도와준다. 예를 들어
중첩된 자료구조를 리턴하는 🔗QueryService.java 와 해당 자료구조의 총합을 구하는 🔗CommandService.java 가 있고,
이의 결과가 짝수인지 홀수인지 판단하는 🔗SomeService.java 가 있다고 가정해보자.
중첩된 자료구조는
Map<String, Map<String, List<Integer>>>
타입으로 예시를 위해 일급 콜렉션을 사용하지 않는다.짝수인지 홀수인지 판단하는 서비스는 두 개의 서비스를 가지고 있는
Facade
형태로 구현된다.class SomeService { private final QueryService queryService = new QueryService(); private final CommandService commandService = new CommandService(); public Boolean someMethod() { final Map<String, Map<String, List<Integer>>> values = queryService.getValues(); final Integer result = commandService.totalSum(values); return result % 2 == 0; } }
Service
계층에서는 Facade
형태를 자주 볼 수 있는데 이는 관심사에 따라 타입을 명시하지 않아도 된다.Webflux 혹은 🔗customDsl 처럼 체이닝 메서드에서는 더욱더 그렇다.
짝수인지 홀수인지를 판단하는 로직에 더 집중할텐데 이는 명시적으로 드러내는 타입이 방해가 될 수도 있다.
읽어야할 관심사에 집중할 수 있도록
var
키워드를 적용해보자.- 🔗SomeService.java
var
키워드 적용
public Boolean someMethod() { final var values = queryService.getValues(); final var result = commandService.totalSum(values); return result % 2 == 0; }
var
키워드를 적용하면 해당 메서드의 책임에 대해 집중해서 읽을 수 있다.이는 조직 문화에 따라 다르겠지만 함수형 프로그래밍이 대두되는 시점에서 변수를 할당하고 주고받는 메서드 바디는 점점 사라질 것이다.
또 다른 장점은
import
구문이 사라진다는 것인데 이는 귀찮은 것 이상의 장점을 제공한다.리턴 타입을 수정할 때 클라이언트 코드는 수정하지 않아도 된다.
🔗QueryService.getValues()의 리턴 타입이 바뀌어도 지장이 없다.
마찬가지로 🔗CommandService.totalSum() 메서드 매개변수가 🔗QueryService.getValues()의 리턴 타입과 일치한다면 🔗SomeService.java 코드를 수정하지 않아도 된다.
import
구문이 없어도 올바르게 import
된다.컴파일러는
import
구문은 없지만 .getValues()
의 리턴 타입을 확인 했으므로 Map
이 java.util.map
에 정의되어 있는지도 확인하여 결정한다.컴파일된 바이트 코드에서는
java.util.map
이라는 FQN(Fully Qualified Name)을 저장한다.이를 확인하기위해 컴파일해
.class
파일을 만들어 보자.- 전체 패키지를 컴파일하거나, 의존하고 있는 클래스들을 모두 컴파일한다.
$ javac task02/*.java or $ javac CommandService.java QueryService.java SomeService.java
이 과정에서 바이트 코드에 기록된 FQN을 간략하게 표현하기 위해
import
구문이 추가된 것이지 실제 import
가 추가되지 않는다.모든 클래스 참조는 FQN 으로 이루어지므로
var
키워드를 사용하더라도 컴파일러는 문제없이 컴파일타임에 타입을 확정시킬 수 있다.
컴파일 한 결과를 바이트 코드로 확인해보자.$ javap -c SomeService.class
someMethod()
중간에 4: invokevirtual #23 // Method task02/QueryService.getValues:()Ljava/util/Map;
경로를 볼 수 있는데,이는 바이트 코드에서 모든 클래스 참조는 FQN으로 이루어지는 것이다.
- 바이트 코드 일부 모습
public java.lang.Boolean someMethod(); Code: 0: aload_0 1: getfield #10 // Field queryService:Ltask02/QueryService; 4: invokevirtual #23 // Method task02/QueryService.getValues:()Ljava/util/Map;
🎯 var 키워드 - 3항 연산자와 동적 타입
final var value = target.length() > 5 ? "lkdcode" : LocalDateTime.now();
위의 코드에서 변수
value
는 String
타입이 되거나 LocalDateTime
타입이 될 수 있다.컴파일, 런타임에 문제가 없으며 동적으로 타입을 할당하는 것과 다름이 없다.
이는
var
키워드에 타입 추론으로 가능한 상황이다.자바에서 타입에 대한 유연함은 다양한 개발 기법을 제공해준다.
해당 변수는 두 가지 타입을 리턴할 수 있으므로 메서드를 오버로딩해서 해당 변수를 사용해 보자.
static void print(final String value) { System.out.println(value); System.out.println(value.getClass()); } static void print(final LocalDateTime value) { System.out.println(value); System.out.println(value.getClass()); }
두 개의 타입을 받는 메서드를 오버로딩해주고 출력해보자.
class Main { public static void main(String[] args) { input("lkdcode"); } static void input(final String target) { final var value = target.length() > 5 ? "lkdcode" : LocalDateTime.now(); print(value); } static void print(final String value) { System.out.println(value); System.out.println(value.getClass()); } static void print(final LocalDateTime value) { System.out.println(value); System.out.println(value.getClass()); } }
아쉽게도 우리의 생각과 다르게 컴파일 단계에서 에러가 뜬다.
Cannot resolve method 'print(Serializable & Comparable<? extends Comparable<?>>)'
자바에서 오버로딩 메서드의 호출은 컴파일 단계에서 결정되는데,
var
+ 삼항 연산자
를 사용한 피연산자는 서로 관련이 없는 타입이므로 컴파일러는 오브젝트 타입으로 추론한다.오브젝트 타입을 받는 메서드를 추가해야 비로소 문제없이 동작할 수 있다.
static void print(final Object value) { System.out.println(value); System.out.println(value.getClass()); }
🎯 var 키워드 - 롬복의 val
코드를 작성하다보면 재할당을 금지하는
final
키워드와 타입 추론 키워드인 var
를 많이 사용하게 된다.재할당 금지 와 타입 추론 을 합친 키워드는 다른 프로그래밍 언어에서도 흔히 볼 수 있는데, 자바에서는 기본적으로 지원되지 않는다.
이를 위해 롬복에서
val
키워드를 제공해준다. 참조 타입에만 사용이 가능하며 기본형은 지원하지 않는다.