Java 의 큰 진화는 Java 8에서 이루어 졌다.
병렬 처리
* 멀티코어 컴퓨터와 같은 병렬 프로세싱이 가능한 장비들이 보급되면서 새로운 요구사항들이 생겨났다.
Data, Task 병렬처리 모델
함수형 프로그래밍
객체 지향 프로그래밍의 핵심
1) 프로그램을 객체들의 협력과 상호작용을 기준으로 구현
2) 객체 정의에 있어 추상화 개념을 사용
3) 코드의 재사용성, 유지보수, 확장,신뢰성이 높아진다.
함수형 프로그래밍의 핵심
1) 결과값이 오직 입력값에만 영향을 받는 순수함수이다.
2) 검증이 쉬우며, 성능 최적화가 쉽다.(캐싱)
3) 함수가 다른 값의 변경을 야기하지 않으므로 동시성 문제를 해결하기 쉽다.
4) 함수를 일급 값으로 즉 객체로 처리(익명함수) 한다.
캐싱 : 캐시 영역으로 데이터를 가져와서 접근하는 방식, 임시메모리에 값을 넣어서 꺼내 쓰게 되어 메모리 리소스를 아끼는 것을 목표로 한다.
3) 스트림
스트림은 데이터를 처리하는데 초점을 맞춘 인터페이스이다.함수를 값으로 전달하기 위해서는 함수형 인터페이스가 필요하다
클래스 :: 함수명 문법이 필요하다. parkingLot.addAll(parkCars(carsWantToPark, Car::hasTicket));
parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));
//여기서 hasTicket & noTicketButMoney 함수의 return type 은 boolean
아래는 두개의 함수를 하나의 함수로 리펙토링한 예시이다.
// 리펙토링 예시
public static List<Car> parkingCarWithTicket(List<Car> carsWantToPark) {
ArrayList<Car> cars = new ArrayList<>();
for (Car car : carsWantToPark) {
if (car.hasParkingTicket()) {
cars.add(car);
}
}
return cars;
}
public static List<Car> parkingCarWithMoney(List<Car> carsWantToPark) {
ArrayList<Car> cars = new ArrayList<>();
for (Car car : carsWantToPark) {
if (!car.hasParkingTicket() && car.getParkingMoney() > 1000) {
cars.add(car);
}
}
return cars;
}
//////////////리팩토링/////////////
// 변경점 1 : Predicate<Car> 인터페이스를 타입 삼아 함수를 전달합니다!
public static List<Car> parkCars(List<Car> carsWantToPark, Predicate<Car> function) {
List<Car> cars = new ArrayList<>();
for (Car car : carsWantToPark) {
// 변경점 2 : 전달된 함수는 다음과 같이 사용됩니다!
if (function.test(car)) {
cars.add(car);
}
}
return cars;
}
interface Predicate<T> {
boolean test(T t);
}
람다 표현식은 메서드로 전달 가능한 익명함수를 단순화한 것이다.
// 기본적으로 문법은 다음과 같습니다.
(파라미터 값, ...) -> { 함수 몸체 }
// 아래의 함수 두개는 같은 함수입니다.
// 이름 반환타입, return문 여부에 따라 {}까지도 생략이 가능합니다.
public int toLambdaMethod(int x, int y) {
return x + y;
}
(x, y) -> x + y
// 주말의 주차장 추가
ArrayList<Car> weekendParkingLot = new ArrayList<>();
// 두개의 함수를 익명함수를 통해 코드를 줄여 간단하게 구현 가능
weekendParkingLot
.addAll(parkCars(carsWantToPark, (Car car) -> car.hasParkingTicket() && car.getParkingMoney() > 1000));
위와같은 람다식은 함수 값 으로 평가되며, 한번만 사용된다.
스트림 이란 Java 8부터 제공되는, 자료구조의 '흐름' 을 객체로 제공해주고, 그 흐름동안 사용할 수 있는 메서드 들을 api로 제공해준다.
//stream 가져오기
carsWantToPark.stream()
// 스트림 가공하기
// filter() 함수는 boollean type 이다.
.filter((Car car) -> car.getCompany().equals("Benz"))
// 스트림 결과 만들기
.toList();
스트림의 api는 방대해서 코드를 마주할 때 마다 알아보는 것이 중요하다
대표적인 api는 map(), forEach(), filter() 정도가 있다.
List<String> carNames = Arrays.asList("Series 6", "A9", "Ionic 6");
carNames.stream()
.forEach(System.out::println);
carNames.stream()
.map(name -> name.toUpperCase()).toList();
// forEach() 와는 반대로 넘겨 받은 값을 변환 시킨다.
우리가 코드를 작성할 때 흔히 빼먹는 실수가 존재한다 그것은 null 체크 이다.
가장 많이 발생하고 있는 에러는 NullPointException 이고 약 80% 를 차지한다고 한다.
null 이반환될 여지가 있음에도 명시하지 않기에 개선방법(예방)은 다양하다.
1. 객체를 감싸서 반환하기
// 개선 2: 결과값을 감싼 객체를 만듭니다.
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;
}
}
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) {
// ... db에서 찾아오는 로직
String data = "DB Connection Result";
if (data != null) {
return new SomeObjectForNullableReturn(data, true);
} else {
return new SomeObjectForNullableReturn(null, false);
}
}
}
Optional<T> 클래스 이용하기Optional<T> 클래스를 이용해서 Null Pointer Exception 을 방지한다.// 값이 null 인 Optional 생성
Optional<Car> emptyOptional = Optional.empty();
// 값이 있는 Optional 생성
// null return 시 NullPointerException
Optional<Car> hasDataOptional = Optional.of(new Car());
// 참조 변수의 값이 null이 될 가능성이 있다면
// null 이라면 비어있는 Optional 객체를 반환
Optional<Car> hasDataOptional = Optional.ofNullable(getCarFromDB());
3. Optioanl 객체 사용하기
Optional<String> opt = Optional.ofNullable("자바 Optional 객체");
System.out.println(opt.get());
get() 메서드 호출시 Optional 객체에 저장된 값이 null 이면, NoSuchElementException 예외가 발생한다.
때문에 get() 메서드 호출 전 isPresent() 메서드를 이용하여 안전하게 접근하는 것이 좋다.
Optional<String> carName = getCarNameFromDB();
// orElse() 를 통해 값을 받아옵니다, 파라미터로는 null인 경우 반환할 값을 적습니다.
tring carName = getCarNameFromDB().orElse("NoCar");
/ orElseGet()이라는 메서드를 사용해서 값을 받아올 수 있습니다.
// 파라미터로는 없는 경우 실행될 함수를 전달합니다.
Car car = getCarNameFromDB().orElseGet(Car::new);
// 값이 없으면, 그 아래 로직을 수행하는데 큰 장애가 되는경우 에러를 발생시킬수도 있습니다.
Car car = getCarNameFromDB()
.orElseThrow(() -> new CarNotFoundException("NO CAR!)
이번 시간에는 모던 자바의 중요 문법인 람다, 스트림 그리고 Optinal 클래스에 대해서 알아보았다.
Optinal 객체를 이용하여 NullPointException 을 예방할 수 있는 방법, java.collection 내부의 stream() 메서드와 관련된 api 들을 자세하게 알아볼 수 있었다.