모던 자바

star_pooh·2024년 11월 18일
0

TIL

목록 보기
22/39
post-thumbnail

모던 자바(Java8 변경점)

자바가 직면했던 새로운 요구사항

  • 병렬 처리
    • 빅데이터, 멀티 코어 컴퓨터와 같은 병렬 프로세싱이 가능한 장비의 보급으로 인해 병렬처리에 대한 요구사항 증가
  • 함수형 프로그래밍
    • 프로그래밍 패러다임의 한 종류
    • 프로그램을 순수한 함수의 모음으로 바라보고 구현하는 것
      - 검증이 쉬움(필요한 부분만의 검증이 가능하기 때문에)
      - 성능 최적화가 쉬움(특정 input에 대한 output을 재사용할 수 있기 때문 → 캐싱)
      - 동시성 문제 해결이 쉬움(다른 값의 변경을 야기하지 않음)

      💡 순수함수란?
      수학의 함수처럼 특정한 데이터에 의존하지 않고 관련 없는 데이터를 변경하지도 않으며, 결과값이 오직 입력값에만 영향을 받는 함수

// 수학의 함수
f(x, y) = x + 2xy
// 순수 함수
// input에 따라 output은 항상 일정
public int someMethod(int x, int y) {
	return x + 2y;
}

class notFunc {
	private int y = 0;
	private int result;

    // 메소드 내부의 제어할 수 없는 y로 인해 output이 변경될 수 있음
	public int anotherMethod(int x) {
		return x + this.y * 2;
	}
    // 메소드 내부에서 this.result의 값을 변경하고 반환
	public int otherMethod(int x, int y) {
		int result = x + 2 * y;
		this.result = result;
		return result;
	}
}

Java8 변경점

  • 함수형 프로그래밍의 아이디어와 문법을 지원
    • 함수를 값으로 다루거나 다른 함수에 넘길 수 있음
    • 익명 함수 사용 가능
  • 스트림 기능의 지원으로 더 간결하고 유연하며 성능 좋은 코드의 작성 가능
    • 데이터 처리 연산을 지원하도록 추출된 연속된 요소
    • 데이터 처리에 초점을 맞춘 인터페이스

람다와 스트림 문법

함수형 인터페이스

public exampleMethod(int parameter1, ??? parameterFunction) { ... }
  • 함수를 전달할 때 타입을 알려주기 위해 함수형 인터페이스를 선언하거나 사용해야함 (인터페이스가 타입 역할을 하기 때문에)
  • 추상 메소드를 딱 하나만 가지고 있음
  • @FunctionalInterface 어노테이션으로 검증 가능
import java.util.ArrayList;
import java.util.List;

public class LambdaAndStream {
    public static void main(String[] args) {
        ArrayList<Car> carsWantToPark = new ArrayList<>();
        ArrayList<Car> parkingLot = new ArrayList<>();

        Car car1 = new Car("Benz", "Class E", true, 0);
        Car car2 = new Car("BMW", "Series 7", false, 100);
        Car car3 = new Car("BMW", "X9", false, 0);
        Car car4 = new Car("Audi", "A7", true, 0);
        Car car5 = new Car("Hyundai", "Ionic 6", false, 10000);

        carsWantToPark.add(car1);
        carsWantToPark.add(car2);
        carsWantToPark.add(car3);
        carsWantToPark.add(car4);
        carsWantToPark.add(car5);
		
		// 함수를 값으로 취급하기 때문에 참조로 불러서 사용 가능
        parkingLot.addAll(parkCars(carsWantToPark, Car::hasTicket));
        parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));

        for (Car car : parkingLot) {
            System.out.println("Parked Car : " + car.getCompany() + "-" + car.getModel());
        }
    }

	// Predicate<Car> 인터페이스를 타입 삼아 함수를 전달
    public static List<Car> parkCars(List<Car> carsWantToPark, Predicate<Car> function) {
        List<Car> cars = new ArrayList<>();

        for (Car car : carsWantToPark) {
        	// 전달된 함수를 통해 주차 가능 여부를 판단
            if (function.parkCheck(car)) {
                cars.add(car);
            }
        }
        return cars;
    }
}

class Car {
    private final String company;
    private final String model;

    private final boolean hasParkingTicket;
    private final int parkingMoney;

    public Car(String company, String model, boolean hasParkingTicket, int parkingMoney) {
        this.company = company;
        this.model = model;
        this.hasParkingTicket = hasParkingTicket;
        this.parkingMoney = parkingMoney;
    }

    public String getCompany() {
        return company;
    }

    public String getModel() {
        return model;
    }

    public boolean hasParkingTicket() {
        return hasParkingTicket;
    }

    public int getParkingMoney() {
        return parkingMoney;
    }

	// 클래스 내부에 메소드 구현
    public static boolean hasTicket(Car car) {
        return car.hasParkingTicket;
    }

    public static boolean noTicketButMoney(Car car) {
        return !car.hasParkingTicket && car.getParkingMoney() > 1000;
    }
}

// 함수형 인터페이스 정리
interface Predicate<T> {
    boolean parkCheck(T t);
}

람다(익명 함수)

  • 함수를 값으로 전달할 때, 함수를 따로 구현하지 않아도 람다식으로 전달 가능
  • 함수 값으로서 한 번만 사용됨
  • 모든 유형의 함수에 적용 가능하며, 간결하게 작성 가능
(파라미터 값, ...) -> { 실행문 }

// 리턴 타입, return문 여부에 따라 {}까지도 생략 가능
public int toLambdaMethod(int x, int y) {
	return x + y;
}
👇
(x, y) -> x + y

public int toLambdaMethod2() {
	return 100;
}
👇
() -> 100

public void toLambdaMethod3() {
	System.out.println("Hello World");
}
👇
() -> System.out.println("Hello World")

스트림

  • 한 번 더 추상화된 자료구조와 자주 사용하는 프로그래밍 API를 제공
  • 자료구조를 한 번 더 추상화했기 때문에 자료구조의 종류에 상관없이 같은 방식으로 처리 가능
  • 스트림의 특징
    • 원본 데이터를 변경하지 않음
      • Java 컬렉션으로부터 스트림을 받아서 사용
    • 한 번 사용한 스트림은 어디에도 남지 않음 (일회용)
ArrayList<Car> benzParkingLotWithoutStream = new ArrayList<>();

for (Car car : carsWantToPark) {
    if (car.getCompany().equals("Benz")) {
        benzParkingLotWithoutStream.add(car);
    }
}
👇
List<Car> benzParkingLot = 
							// carsWantToPark의 스트림을 받아서
							carsWantToPark.stream()
                            // 제조사가 Benz인 요소를
                        	.filter((Car car) -> car.getCompany().equals("Benz"))
                            // List로 리턴
                        	.toList();

Optional

  • null은 아무것도 참조하지 않는다는 것을 의미

null의 나쁜 예

public class NullIsDanger {
    public static void main(String[] args) {
        SomeDBClient myDB = new SomeDBClient();
        String userId = myDB.findUserIdByUsername("HelloWorldMan");
        System.out.println("HelloWorldMan's user Id is : " + userId);
    }
}

class SomeDBClient {
    public String findUserIdByUsername(String username) {
        String data = "DB Connection Result";

        if (data != null) {
            return data;
        } else {
            return null;
        } 
    }
}
  • 논리적으로도 환경적으로도 null이 리턴될 여지가 있음에도 null이 리턴될 수 있음을 명시하지 않음
  • 메인 함수에서 null 체크를 하지 않는다면 NullPointerException 발생

개선 Ver.1

public class NullIsDanger {
    public static void main(String[] args) {
        SomeDBClient myDB = new SomeDBClient();
        String userId = myDB.findUserIdByUsernameOrThrowNull("HelloWorldMan");
        // 개선 1: null이 리턴될 경우를 대비
        if (userId != null) {
            System.out.println("HelloWorldMan's user Id is : " + userId);
        }
    }
}

class SomeDBClient {
    // 개선 1: 메소드에서 null이 리턴될 수 있음을 명시
    public String findUserIdByUsernameOrThrowNull(String username) {
		String data = "DB Connection Result";

        if (data != null) {
            return data;
        } else {
            return null;
        }
    }
}
  • 실수로 null 체크를 하지 않게 된다면 NullPointerException 발생

개선 Ver.2

class SomeObjectForNullableReturn {
    private final String returnValue;
    private final Boolean isSuccess;

    SomeObjectForNullableReturn(String returnValue, Boolean isSuccess) {
        this.returnValue = returnValue;
        this.isSuccess = isSuccess;
    }

    public String getReturnValue() {
        return returnValue;
    }

    public Boolean isSuccess() {
        return isSuccess;
    }
}

public class NullIsDanger {
    public static void main(String[] args) {
        SomeDBClient myDB = new SomeDBClient();
        // 개선 2 : 객체를 리턴 받기 때문에 메소드가 위험할 수 있다는 것을 더 쉽게 인지 가능
        SomeObjectForNullableReturn getData = myDB.findUserIdByUsername("HelloWorldMan");

        if (getData.isSuccess()) {
            System.out.println("HelloWorldMan's user Id is : " + getData.getReturnValue());
        }
    }
}

class SomeDBClient {
    // 개선 2 : 결과값을 감싼 객체를 리턴
    public SomeObjectForNullableReturn findUserIdByUsername(String username) {
        String data = "DB Connection Result";

        if (data != null) {
            return new SomeObjectForNullableReturn(data, true);
        } else {
            return new SomeObjectForNullableReturn(null, false);
        }
    }
}

개선 Ver.3

class SomeObjectForNullableReturn<T> {
    private final T returnValue;
    private final Boolean isSuccess;
 
    SomeObjectForNullableReturn(T returnValue, Boolean isSuccess) {
        this.returnValue = returnValue;
        this.isSuccess = isSuccess;
    }

    public T getReturnValue() {
        return returnValue;
    }

    public Boolean isSuccess() {
        return isSuccess;
    }   
}
  • 개선 Ver.2의 발전시켜서 감싸는 객체를 조금 수정하면, 위험할 수 있는 모든 메소드에 사용 가능

Optional

  • 개선 Ver.3의 아이디어를 발전시킨 것 (java.util.Optional)
  • Optional<T> 클래스를 사용하여 NullPointerException을 방지
  • null인 값을 참조해도 NullPointerException이 발생하지 않도록 값을 감싸는 래퍼 클래스
// 값이 null인 Optional 생성
Optional<Car> emptyOptional = Optional.empty();
// 값이 있는 Optional 생성
Optional<Car> hasDataOptional = Optional.of(new Car());
// 값이 있을 수도 없을 수도 있는 Optional 생성
Optional<Car> hasDataOptional = Optional.ofNullable(getCarFromDB());
// 값이 없을 경우 대체할 값, 함수, 예외처리 설정 가능
String carName = getCarNameFromDB().orElse("NoCar");
Car car = getCarNameFromDB().orElseGet(Car::new);
Car car = getCarNameFromDB().orElseThrow(() -> new CarNotFoundException("NO CAR!)

0개의 댓글

관련 채용 정보