모던 자바_Java8
Java 8에서 새로 추가된 개념
- 함수형 프로그래밍(lambda)
- 객체지향 프로그래밍의 핵심 아이디어
- 코드의 재사용성 높아짐
- 유지보수 및 확장하기 쉬워짐
- 신뢰성 있게 사용하기 쉬워짐
- 함수형 프로그래밍의 핵심 아이디어
- 수학의 함수처럼, 특정한 데이터에 의존하지 않고, 관련없는 데이터를 변경하지 않으며, 결과 값이 오직 입력 값에만 영향을 받는 함수(순수 함수)
- 프로그램을 순수한 함수의 모음으로 바로보고 구현
- 효용
- 검증이 쉬움(검증이 필요한 부분만 검증 가능)
- 성능 최적화가 쉽다(특정 input에 대한 output을 재사용할 수 있음 - 캐싱)
- 동시성 문제를 해결하기 쉽다(함수는 다른 값의 변경을 야기하지 않음)
- 자바 함수의 변화
- 함수를 일급 값으로
- 일급 값(일급 객체) 특징
- 함수에 인자로 넘길 수 있고
- 함수의 결과로 반환할 수 있다
- 값을 수정할 수 있다
- 값을 변수에 대입할 수 있다
- 일급 값(일급 객체) 특징
- 람다: 익명 함수
- 이름 없는 함수를 뜻하며, 일급 객체로 취급
- 함수를 값으로 사용할 수도 있으며 파라미터에 전달하기
- 변수에 대합 하기와 같은 연산들이 가능
- 함수를 일급 값으로
- 객체지향 프로그래밍의 핵심 아이디어
- 스트림
- 데이터 처리연산을 지원하도록 소스에서 추출된 연속된 요소
- 컬렉션이 데이터를 저장하거나 접근하는데 초점을 맞춘 인터페이스, 스트림은 데이터를 처리하는데 초점을 맞춘 인터페이스
함수형 프로그래밍(람다 사용) 예제
package week05.thread.modern;
import java.util.ArrayList;
import java.util.List;
// 주차장 예제
// 티켓, 파킹머니 -> 주차하게 하는
public class LambdaAndStream {
public static void main(String[] args) {
// 주차대상 차량
ArrayList<Car> carWantToPark = new ArrayList<>();
// 주차장
ArrayList<Car> parkingLot = new ArrayList<>();
// 주말 주차장
ArrayList<Car> weekendParkingLot = new ArrayList<>();
// 5개의 car instance
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);
carWantToPark.add(car1);
carWantToPark.add(car2);
carWantToPark.add(car3);
carWantToPark.add(car4);
carWantToPark.add(car5);
// parkingLot.addAll(parkingCarWithTicket(carWantToPark));
parkingLot.addAll(parkCars(carWantToPark, Car::hasTicket));
// parkingLot.addAll(parkingCarWithMoney(carWantToPark));
parkingLot.addAll(parkCars(carWantToPark, Car::noTicketButMoney));
// 익명함수 적용
parkingLot.addAll(parkCars(carWantToPark, (Car car) -> car.hasParkingTicket() && car.getParkingMoney() > 1000));
for (Car car : parkingLot) {
System.out.println("Parked Car : " + car.getCompany() + "-" + car.getModel());
}
}
// 타입 -> (함수형) 인터페이스
// 인터페이스는 타입 역할을 할 수 있기 대문(멀티 리모컨 예제)
// 함수형 인터페이스 : 추상 메서드를 딱 하나만 가지고 있음
// public exampleMethod(int parameter1, parameterFunction) {
// parameterFunction
// }
// 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;
// }
// 메서드 2개를 하나로 : 내부 주요 로직을 함수로 전달
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);
}
스트림(Stream API)
- 특징
- 원본의 데이터를 변경하지 않음(자바 컬렉션으로 부터 스트림을 받아서 한 번 사용)
- 일회용(한 번 사용한 스트림은 어디에도 남지 않음)
- 사용 방법
- 스트림을 받아오기 (.stream( ))
- 스트림 가공하기 (.filter((Car car) -> car.getCompany( ).equals("Benz")))
- filter : 조건에 맞는 것만 반환
- map : 모든 요소를 가공해서 반환
- forEach() : 각각의 원소에 넘겨받은 함수를 실행(넘겨받은 반환 값으로 하는 것은 없고, 있어도 무시 가능)
- 스트림 결과 만들기 (.toList( ))
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) {
// ... db에서 찾아오는 로직
String data = "DB Connection Result";
if (data != null) {
return data;
} else {
return null;
}
}
}
- 출력 부분에서 userId 가 Null일 경우 nullPointerException 발생
개선 1 - null 반환될 여지를 명시하고, 그 메서드를 사용하는 사람이 조심하기
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);
}
}
}
- 해당 메서드는 결과를 감싼 객체를 리턴하고, 객체를 리턴받는 유저는 이 메서드가 위험할 수 있다는 것을 더 쉽게 인지 가능
개선 3 - 개선 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;
}
}
- 제네릭 문법 사용으로 위험할 수 잇는 모든 메서드에 사용 가능
Optional
- Null Pointer Exception을 방지할 수 있도록 도와주는 클래스
- 사용법
- 값이 null인 Optional 생성
Optional<Car> emptyOptional = Optional.empty();
- 값이 있는 Optional 생성
Optional<Car> hasDataOptional = Optional.of(new Car());
- 값이 있을 수도 없ㅇ르 수도 있는 Optional 생성
Optional<Car> hasDataOptional = Optional.ofNullable(getCarFromDB());
- Optional 객체 사용(값 받아오기)
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!)
과제
package week05.test;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Book> bookList = Arrays.asList(
new Book(1L, "모두의 딥러닝", "조태호", "IT", 21600),
new Book(2L, "이득우의 게임 수학", "이득우", "IT", 40500),
new Book(3L, "자바 웹 개발 워크북", "구멍가게 코딩단", "IT", 31500),
new Book(4L, "실전 시계열 분석", "에일린 닐슨", "IT", 34200),
new Book(5L, "데이터 분석가의 숫자유감", "권정민", "IT", 14400),
new Book(6L, "스프링 부트 실전 활용 마스터", "그렉 턴키스트", "IT", 25200),
new Book(7L, "오늘부터 IT를 시작합니다", "고코더", "IT", 16200),
new Book(8L, "그림으로 이해하는 인지과학", "기타하라 요시노리", "IT", 16200),
new Book(9L, "괜찮아, 그 길 끝에 행복이 기다릴 거야", "손미나", "여행", 17100),
new Book(10L, "여행의 이유", "김영하", "여행", 12150),
new Book(11L, "여행의 시간", "김진애", "여행", 16200),
new Book(12L, "로봇 시대 살아남기", "염규현", "역사", 14850),
new Book(13L, "경제 전쟁의 흑역사", "이완배", "역사", 15750),
new Book(14L, "100가지 동물로 읽는 세계사", "사이먼 반즈", "역사", 29700),
new Book(15L, "k 배터리 레볼루션", "박순혁", "경제", 17100),
new Book(16L, "정하준의 경제학 레시피", "장하준", "경제", 16200),
new Book(17L, "레버리지", "롭 무어", "경제", 16200)
);
// 카테고리가 여행인 책 제목 조회 (filter category.equals("여행"))
bookList.stream().filter(book -> book.getCategory().equals("경제"))
.forEach(f -> System.out.println("카테고리가 여행인 책 제목: " + f.getBookName()));
// 가격이 16200원 이하인 책 제목 조회 (filter price <= 16200)
bookList.stream().filter(book -> book.getPrice() <= 16200)
.forEach(f -> System.out.println("가격이 16200원 이하인 책 제목: " + f.getBookName()));
// 첵 제목에 "경제" 용어라 들어간 책 제목 조회 (filter bookName.contains("경제"))
bookList.stream().filter(book -> book.getBookName().contains("경제"))
.forEach(f -> System.out.println("책 제목에 경제 용어가 들어간 책 제목: " + f.getBookName()));
// 가격이 가장 비싼 책 가격 조회 (mapToDouble(Book::getPrice).max())
double maxPrice = bookList.stream().mapToDouble(Book::getPrice).max().getAsDouble();
System.out.println("책 목록 중 가장 비싼 금액: " + maxPrice);
System.out.println();
// 카테고리가 IT인 책들의 가격 합 조회 (filter category.equals("IT").mapToDouble(BooK::price).sum())
double sum = bookList.stream().filter(book -> book.getCategory().equals("IT")).mapToDouble(Book::getPrice).sum();
System.out.println("카테고리 IT 책들의 가격 합: " + sum);
System.out.println();
// IT책 할인 이벤트 (filter category.equals("IT").map(price -> price*0.4).toList()
List<Book> discountedBookList = bookList.stream().filter(book -> book.getCategory().equals("IT"))
.peek(book -> book.setPrice(book.getPrice() * 0.4)).toList();
for (Book book : discountedBookList) {
System.out.println("할인된 책 제목: " + book.getBookName());
System.out.println("할인된 책 가격: " + book.getPrice() + "\n");
}
}
}
class Book {
// 분류번호
private Long id;
// 책 이름
private String bookName;
// 작가 이름
private String author;
// 카테고리
private String category;
// 가격
private double price;
public Book(Long id, String bookName, String author, String category, double price) {
this.id = id;
this.bookName = bookName;
this.author = author;
this.category = category;
this.price = price;
}
public String getBookName() {
return bookName;
}
public String getCategory() {
return category;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
'항해 99 > Java' 카테고리의 다른 글
WIL - 3 (0) | 2024.02.25 |
---|---|
프로그래밍 테스트 및 IDE 프로젝트 빌드 오류 해결 (0) | 2024.02.19 |
Java - 프로세스, 쓰레드 (0) | 2024.02.19 |
WIL - 2 (0) | 2024.02.18 |
페어 프로그래밍 - 코딩 테스트 5 (1) | 2024.02.17 |