// 수학의 함수
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을 재사용 할 수 있음 - 캐싱)
동시성문제를 해결하기쉬움.
(내부로직으로만 작동하기에 다른값을 변경하지않음)

함수형 프로그래밍의 아이디어와 문법을 자바 8에서 지원한다.
함수형 프로그래밍의 아이디어인 (함수를 값으로 다루거나, 다른 함수에 넘길 수 있다)와 같은 일들이 가능하다.
함수형 프로그래밍의 문법인 익명 함수 문법을 지원한다.
스트림이라는 컬렉션의 흐름과 같은 것을 지원한다.
스트림 기능의 지원으로 우리는 더 간결하고, 유연하고, 성능 좋은 코드를 작성할 수 있다.
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);
}
List<Car> benzParkingLot =
// carsWantToPark의 스트림값을 받아와서
carsWantToPark.stream()
// 거기 구현되어 있는 filter()메서드를 사용합니다.
// filter메서드는 함수를 파라미터로 전달받습니다.
// 여기서 함수는 제조사가 벤츠면 true를 반환하는 함수네요.
// 필터 메서드는 이름처럼 false를 반환한 스트림의 원소들을 제거합니다.
.filter((Car car) -> car.getCompany().equals("Benz"))
// 이 결과도 반환을 받아서 다시 리스트로 묶어줍니다.
.toList();

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;
}
}
}
// 개선 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);
}
}
}
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;
}
}
<T>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");
}