일급 객체
모든 일급 객체는 변수나 데이터에 담을 수 있어야 한다
public class main {
public static void hello() {
System.out.println("Hello World");
}
public static void main(String[] args) {
// java에서는 아래 코드처럼 특정 변수에 메서드 할당이 불가능
Object a = hello;
}
}
// 반면 js에서는 변수에 메서드 할당 가능
const hello = function() {
console.log("Hello World");
}
함수의 파라미터로 전달할 수 있어야 한다
public class Main {
public static void hello() {
System.out.println("Hello World");
}
// 아래처럼 특정 메서드를 받는 것 자제가 불가능
public static void print(Object func) {
func();
}
// 특정 메서드를 파라미터로 받는 것 자체가 불가능하므로 전달하여 사용도 불가능
public static void main(String[] args) {
print((Object) hello);
}
}
const hello = function() {
console.log("Hello World");
}
function print(func) {
func();
}
// js에서는 메서드 파라미터로 메서드를 전달하여 사용 가능
print(hello);
함수의 리턴값으로 사용할 수 있어야 한다
const hello = function() {
console.log("Hello World");
return function() {
console.log("Hello World 22");
}
}
// js에서는 return을 함수로 가능하도록 함
const hello2 = hello();
hello2();
자바 람다 함수의 일급 객체
자바의 람다식, 익명 클래스는 변수나 매개변수로 사용할 수 있고 리턴값으로 사용할 수 있기 때문에 일급객체 조건을 만족
변수나 데이터에 담을 수 있어야 한다
public class Main {
public static void main(String[] args) {
// 아래처럼 작성 후 Consumer 인터페이스 타입을 받는 파라미터로 사용 가능
Consumer<String> c = (t) -> System.out.println(t);
c.accept("Hello World");
}
}
함수의 파라미터로 전달할 수 있어야 한다
public class Main {
public static void print(Consumer<String> c, String str) {
c.accept(str);
}
public static void main(String[] args) {
// 아래의 경우 Consumer 함수형 인터페이스 파라미터를 받는 print 메서드 사용
// Consumer 함수형 인터페이스를 람다식으로 전달해서 사용
print((t) -> System.out.println(t), "Hello World");
}
}
함수의 리턴값으로 사용할 수 있어야 한다
public class Main {
// return을 Consumer 함수형 인터페이스 타입으로 하여 호출한 부분에서 사용 가능
public static Consumer<String> hello() {
return (t) -> {
System.out.println(t);
}
}
public static void main(String[] args) {
Consumer<String> c = hello();
c.accept("Hello World");
}
}
내부/외부 반복
default method
인터페이스는 기본적으로 기능의 구현보다는 선언에 초점을 맞춤
예시로 기존의 인터페이스를 구현한 클래스를 사용하던 중 인터페이스 보완 과정에서 추가적으로 구현해야 할, 필수적인 부분이 존재, 이 인터페이스를 구현한 클래스와의 호환성이 떨어짐 → 이 경우에 default method를 이용해서 하위 호환성을 유지하고 인터페이스의 보완 가능
예시 코드
public interface Vehicle {
default void print(String str) {
System.out.println("I want to " + str);
}
}
public class Car implements Vehicle {}
Car car = new Car();
car.print("Car");
// result -> I want to Car 출력
Optional
NPE(Null Pointer Exception)란
개발할 때 가장 많이 발생하는 예외 중 하나로 null을 참조할 떄의 오류
null에 대한 검사를 해서 예방이 가능하나 코드가 복잡해짐
이에 따라 null에 민감한 코드는 초깃값을 사용하는 것이 권장됨
예시 코드
List<String> names = getNames();
names.sort(); // names가 null이라면 NPE 발생
List<String> names = getNames();
// NPE 예방을 위한 NULL 검사
if(names != null) {
names.sort();
}
Optional이란
Optional<T> 클래스를 이용하면 NPE 방지가능
Optional<T>은 NULL 가능성을 가진 값을 감싸는 Wrapper 클래스
Optional 클래스는 내부에서 static 변수로 EMPTY 객체를 미리 생성하고 가짐
이러한 이유로 빈 객체를 여러번 생성하는 경우에도 1개의 EMPTY 객체를 공유하여 메모리를 절약
예시 코드
Optional.empty() → 값이 NULL인 경우
Optional<String> optional = Optional.empty();
System.out.println(optional); // Optional.empty 출력
System.out.println(optional.isPresent()); // false 출력
Optional.of() → 값이 NULL이 아닌 경우
// 만약 어떤 데이터가 절대 null이 아닌 경우 Optional.of()로 생성 가능
// Optional.of()로 null을 저장하려고 하면 NPE발생
Optional<String> optional = Optional.of("MyName);
Optional.ofNullable() → 값이 Null가능성을 가지는 경우
// Optional 값이 null 가능성을 가지는 경우 사용
// orElse, orElseGet 메서드를 이용해서 null의 경우라도 안전하게 가져올 수 있음
Optional<String> optional = Optional.ofNullable(getName());
Stringn name = optional.orElse("anonymous");
Optional 실전 사용법
기존에 null 검사를 한 후에 null인 경우에는 새로운 객체를 생성해야 했음
Optional<T> Lambda를 이용하면 간단하게 표현 가능
// java 8 이전
List<String> names = getName();
List<String> tempNames = list != null
? list
: new ArrayList<>();
// java 8 이후
List<String> nameList = Optional.ofNullable(getName())
.orElseGet(() -> new ArrayList<>());
객체의 객체를 사용하는 경우 null 검사 때문에 복잡해짐
// java 8 이전
public String findPostCode() {
UserVO userVO = getUser();
if(userVO != null) {
Address address = userVO.getAddress();
if(address != null) {
String postCode = address.getPostCode();
if(postCode != null) {
return postCode;
}
}
}
return "우편번호 없음";
}
// java 8 이후
public String findPostCode() {
// Optional로 펼친 경우
Optional<UserVO> userVO = Optional.ofNullable(getUser());
Optional<Address> address = Optional.ofNullable(userVO.getAddress());
Optional<String> postCode = Optional.ofNullable(address.getPostCode());
String result = postCode.orElse("우편번호 없음");
// 축약형
String result = user.map(UserVO::getAddress)
.map(Address::getPostCode)
.orElse("우편번호 없음");
}
예외 처리의 불필요
// java 8 이전
String name = getName();
String result = "";
try {
result = name.toUpperCase();
} catch (NullPointerException e) {
throw new CustomUpperCaseException();
}
// java 8 이후
// null인 값을 Optional로 가져왔을 때 null인 경우 예외 발생
Optional<String> nameOpt = Optional.ofNullable(getName());
String result = nameOpt.orElseThrow(CustomUpperException::new)
.toUpperCase();
orElse, orElseGet의 차이
orElse: 파라미터로 값을 받음 → 값이 미리 존재하는 경우에만 사용
orElseGet: 파라미터로 함수형 인터페이스를 받음 → 값이 미리 존재하지 않는 거의 대부분의 경우에 사용
예시 코드
// Optional.ofNullable로 "EMPTY"를 갖는 Optional 객체 생성
// getUserEmail()가 실행되어 반환겂을 orElse파라미터로 전달
// orElse가 호출됨, "EMPTY"가 null이 아니므로 "EMPTY"를 그래도 가짐
// 즉 orElse는 파라미터로 함수형 인터페이스르 받지 못해 함수가 작성되면 무조건 실행
public void findUserEmailOrElse() {
String userEmail = "Empty";
String result = Optional.ofNullable(userEmail)
.orElse(getUserEmail());
System.out.println(reuslt);
}
// Optional.ofNullalbe로 "EMPTY"를 갖는 Optional 객체 생성
// getUserEmail 함수 자체를 orElseGet 파라미터로 전달
// orElseGet이 호출, "EMPTY"가 null이 아니므로 "EMPTY"를 그대로 가지며 getUserEmail()이 호출되지 않음
public void findUserEmailOrElseGet() {
String userEmail = "Empty";
String result = Optional.ofNullable(userEmail)
.orElseGet(this::getUserEmail);
System.out.println(result);
}
private String getUserEmail() {
System.out.println("getUserEmail() called");
return "[email protected]";
}
정리
Optional을 사용할 때의 위험성
NPE 대신 NoSuchElementException 발생
Optional<User> optionalUser = ...;
// optional의 value가 없으면 NoSuchElementExcetpion 발생
User user = optionalUser.get();
이전에는 없던 새로운 문제
코드의 가독성 떨어트림
public void temp(Optional<User> optionalUser) {
if(optionalUser != null && optionalUser.isPresent()) {
// 이후 로직
}
throw new IllegalStateException();
}
시간적 공간적 비용(또는 오버헤드)증가
올바른 Optional 사용법
DRY(Don’t Repeat Yourself)
전략 디자인 패턴
익명 클래스