JAVA 문법종합반 5주차(모던자바)

SJ.CHO·2024년 9월 12일

자바 8 변경점

함수형 프로그래밍

  • 수학의 함수처럼, 특정한 데이터에 의존하지 않고, 관련 없는 데이터를 변경하지도 않으며, 결과값이 오직 입력값에만 영향을 받는 함수를 순수 함수라고함
  • 외부의 값에 영향을 받지않음 입력값으로만 동작.
// 수학의 함수
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;
	}

	// 순수 메서드가 아닌 메서드 2
	// 메서드 내에서 this.result 값을 변경하고 반환하기 때문에
	// 순수 메서드라고 보기는 어렵습니다!
	public int otherMethod(int x, int y) {
		int result = x + 2 * y;
		this.result = result;
		return result;
	}
}

장점

  • 검증이 쉽다.
    (내부 로직으로만 돌아가기 때문에/ 결과성의 보장)

  • 성능 최적화가 쉽다.
    (특정 input에 대한 output을 재사용 할 수 있음 - 캐싱)

  • 동시성문제를 해결하기쉬움.
    (내부로직으로만 작동하기에 다른값을 변경하지않음)

아이디어

  • 함수를 객체처럼 사용한다. (일급값으로 사용한다)
  • 메서드를 객체처럼 다룰수 있게 된다.
  1. 함수형 프로그래밍의 아이디어와 문법을 자바 8에서 지원한다.

  2. 함수형 프로그래밍의 아이디어인 (함수를 값으로 다루거나, 다른 함수에 넘길 수 있다)와 같은 일들이 가능하다.

  3. 함수형 프로그래밍의 문법인 익명 함수 문법을 지원한다.

  4. 스트림이라는 컬렉션의 흐름과 같은 것을 지원한다.

  5. 스트림 기능의 지원으로 우리는 더 간결하고, 유연하고, 성능 좋은 코드를 작성할 수 있다.

함수형 인터페이스

  • 추상메소드를 하나만 가지고 있음.
  • @FunctionalInterface 어노테이션으로 검증할 수 있음
  • 함수를 매개변수로 넘겨주기위한 타입지정
parkingLot.addAll(parkCars(carsWantToPark, Car::hasTicket));
parkingLot.addAll(parkCars(carsWantToPark, Car::noTicketButMoney));
  • 함수를 인터페이스 다형성처럼 메소드 리턴값 반환이 아닌 함수자체를 매개변수로 넘겨주고있음.
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());
        }


    }

    public static List<Car> parkCars(List<Car> carsWantToPark, Predicate<Car> function) {
        List<Car> cars = new ArrayList<>();

        for (Car car : carsWantToPark) {
            if (function.test(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 test(T t);
}

스트림

  • 자료구조를 한번 더 추상화한 개념.
    (자료구조의 종류와 상관없이 사용가능)
  • 원본데이터를 컬렉션으로 받아서 가공하여 단 1회만 사용.
List<Car> benzParkingLot =
			  // carsWantToPark의 스트림값을 받아와서
   carsWantToPark.stream()
				// 거기 구현되어 있는 filter()메서드를 사용합니다.
				// filter메서드는 함수를 파라미터로 전달받습니다.
				// 여기서 함수는 제조사가 벤츠면 true를 반환하는 함수네요.
				// 필터 메서드는 이름처럼 false를 반환한 스트림의 원소들을 제거합니다.
   .filter((Car car) -> car.getCompany().equals("Benz"))
				// 이 결과도 반환을 받아서 다시 리스트로 묶어줍니다.
   .toList();

NULL

  • Null 이라는 개념이 존재함으로 모든상황에서 null이 발생가능성이 있음.
  • 이로인하여 모든 번환메서드에 null체크에 필요성이 생김.

개선 1.

public class NullIsDanger {
    public static void main(String[] args) {

        SomeDBClient myDB = new SomeDBClient();

        String userId = myDB.findUserIdByUsernameOrThrowNull("HelloWorldMan");
        // 개선 1: null이 반환 될 수 있음을 인지한 메서드 사용자는, null을 대비합니다.
        if (userId != null) {
            System.out.println("HelloWorldMan's user Id is : " + userId);
        }
    }
}

class SomeDBClient {
    // 개선 1: 이 메서드는 null이 반환 될 수 있음을 명시합니다.
    public String findUserIdByUsernameOrThrowNull(String username) {
        // ... db에서 찾아오는 로직
				String data = "DB Connection Result";

        if (data != null) {
            return data;
        } else {
            return null;
        }
    }

}
  • 개발자가 직접 코드에 NULL 체크를 하여 조심하기.
  • 사람은 실수를 하기에 모든 메소드를 체크하기엔 무리가 있음.

개선 2.

// 개선 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) {
        // ... db에서 찾아오는 로직
        String data = "DB Connection Result";

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

}
  • 반환된 데이터를 바로 받는것이 아닌 객체로 감싸서 반환.
  • 객체 자체의 멤버를 통해 해당데이터가 존재하는지 NULL인지 확인작업이 가능.

개선 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;
    }
    
}
  • 해당 객체를 제네릭타입으로 구현한다면 어느 타입이든 재사용성이 높아짐.

Optional<T>

  • 개선사항 3번을 Java에서 제공해주는 객체
  • Java8에서는 Optional 클래스를 사용해 Null Pointer Exception을 방지할 수 있도록 도와줌.
  • Optional는 null이 올 수 있는 값을 감싸는 Wrapper 클래스
  • Optional이 비어있더라도, 참조해도 Null Pointer Exception가 발생하지 않음.
Optional<String> carName = getCarNameFromDB();
// orElse() 를 통해 값을 받아옵니다, 파라미터로는 null인 경우 반환할 값을 적습니다.
String realCarName = carName.orElse("NoCar");

// 위는 예시코드고 실제는 보통 아래와 같이 사용하겠죠?
String carName = getCarNameFromDB().orElse("NoCar");

// orElseGet()이라는 메서드를 사용해서 값을 받아올 수 있습니다.
// 파라미터로는 없는 경우 실행될 함수를 전달합니다.
Car car = getCarNameFromDB().orElseGet(Car::new);

// 값이 없으면, 그 아래 로직을 수행하는데 큰 장애가 되는경우 에러를 발생시킬수도 있습니다.
Car car = getCarNameFromDB()
						.orElseThrow(() -> new CarNotFoundException("NO CAR!)

숙제

 // 카테고리가 여행인 책
        bookList.stream().filter(book -> book.getCategory().equals("여행")).forEach(f -> System.out.println("카테고리가 여행인 책 = " + f.getBookName()));
        System.out.println("=================================================================================================");
        // 16200원 이하의 가격을 지닌 책
        bookList.stream().filter(book -> book.getPrice() <= 16200).forEach(f -> System.out.println("16200 원 이하의 책 = " + f.getBookName()));
        System.out.println("=================================================================================================");
        // 책이름의 경제가 들어간 책
        bookList.stream().filter(book -> book.getBookName().contains("경제")).forEach(f -> System.out.println("책 제목에 경제가 들어간 책 = " + f.getBookName()));
        System.out.println("=================================================================================================");
        // 가장 비싼 책
        double maxPrice = bookList.stream().max(Comparator.comparing(Book::getPrice)).get().getPrice();
        System.out.println("가장 비싼 책 = " + maxPrice);
        System.out.println("=================================================================================================");
        // IT 책들의 가격 합
        double sum = bookList.stream().filter(book -> book.getCategory().equals("IT")).mapToDouble(Book::getPrice).sum();
        System.out.println("IT 카테고리의 책 가격의 합 = " + sum);
        System.out.println("=================================================================================================");
        // IT 책들의 40% 세일 List
        List<Book> discountedBookList = bookList.stream().filter(book -> book.getCategory().equals("IT")).map(Book -> {
            Book.setPrice(Book.getPrice() * 0.6);
            return Book;
        }).collect(Collectors.toList());
        for (Book book : discountedBookList) {
            System.out.println("할인된 책 제목: " + book.getBookName());
            System.out.println("할인된 책 가격: " + book.getPrice() + "\n");
        }
profile
70살까지 개발하고싶은 개발자

0개의 댓글