쉽게 말해 멀티코어, 병렬, 간결한 코드라는 요구사항
스트림 API를 도입해서 해결한다. 이는 동작 파라미터화와 인터페이스의 디폴트 메서드를 기반으로 가능
어떻게 병철처리? 쉽게 말하면, 라이브러리 내부에서 멀티 CPU를 이용해서 병렬 처리
걱정되는 부분: 데이터 가변성 문제 ⇒ 공유된 데이터에 접근 x
자바 8의 요구사항: 간결한 코드, 멀티코어 프로세서의 쉬운 활용
스레드라는 복잡한 작업을 사용하지않으면서
공짜로 병렬성을 얻을 수 있다.스트림 도입 배경
유닉스 예시를 통한 설명
// 파일의 단어를 소문자로 바꾼 다음, 사전순으로 단어를 정렬했을 때 마지막 세 단어 출력
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3
class LegacyTest {
private static void groupTransaction() {
// 그룹화된 트랜잭션을 더할 Map 생성
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
// 트랜잭션의 리스트를 반복
for (Transaction transcation : transactions) {
// 고가의 트랜잭션을 필터링
if (transaction.getPrice() > 1000) {
// 트랜잭션의 통화 추출
Currency currency = transaction.getCurrency();
List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
// 현재 통화의 그룹화된 맵에 항목이 없으면 새로 만든다.
if (transactionsForCurrency == null) {
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency, transactionsForCurrency);
}
// 현재 탐색된 트랜잭션을 같은 통화의 트랜잭션 리스트에 추가한다.
transactionsForCurrency.add(transaction);
}
}
}
}
----
import static java.util.stream.Collectors.toList;
class LegacyTest {
private static void groupTransaction() {
Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream()
.filter((Transaction t) -> t.getPrice() > 1000) // 고가의 트랜잭션 필터링
.collection(groupingBy(Transaction::getCurrency)); //통화로 그룹화함
}
}
컬렉션
스트림
data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==
자바의 병렬성과 공유되지 않은 가변 상태
라이브러리에서 분할 처리
를 한다. 큰 스트림을 병렬 처리할 수 있도록 작은 스트림으로 분할한다.프로그램이 실행되는 동안 컴포넌트 간에 상호작용이 일어나지 않는다
' 라는 의미도 포함한다.// 자바 8 이전
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isHidden(); // 숨겨진 파일 필터링
}
})
// 자바 8 이후
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
해당 isHidden 메서드 리턴값
을 파라미터로 사용하게 되는 것Predicate란?
filterApples는 Predicate을 파라미터로 받고 있다. 인수로 받아 true나 false를 반환하는 함수를 프레디케이트라고 한다.
List<Apple> heavyApples =
inventory.stream()
.filter((Apple a) -> a.getWeight() > 150)
.collect(toList());
List<Apple> heavyApples =
inventory
.parallelStream()
.filter((Apple a) -> a.getWeight() > 150)
.collect(toList());
예를 들어 자바 8에서는 List에 직접 sort 메서드를 호출할 수 있다. 이는 자바 8의 List 인터페이스에 다음과 같은 디폴트 메서드 정의가 추가되었기 때문이다.
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
따라서 자바 8 이전에는 List를 구현하는 모든 클래스가 sort를 구현해야했지만 자바 8부터는 디폴트 sort를 구현하지 않아도 된다.
번외1 : 자바8의 인터페이스 vs 추상 클래스
자바8 이전에는 인터페이스는 메소드 구현부를 갖지 못한다는 점에서 추상 클래스와 크게 차이가 있었는데, 디폴트 메서드 지원으로 인해 추상 클래스와 유사한 성격을 띄게 되었다. 이 둘을 어떻게 구분하여 사용해야할까?
https://yaboong.github.io/java/2018/09/25/interface-vs-abstract-in-java8/
결론만 간단히 이야기하면, 추상 클래스의 경우는 관련성이 높은 클래스들 간의 코드 공유가 필요한 경우 사용한다. 반대로 인터페이스는 Comparable과 같이 서로 관련성 없는 클래스들이 구현하게 되는 경우 사용된다. 또한, 자바는 추상클래스는 다중 상속(extends)이 안되기 때문에 다중 상속(implement)이 필요한 경우 인터페이스를 사용한다.
추상화
이용변화하는 요구사항에 쉽게 대응
할 수 있는 유용한 패턴이다.chatGPT 예시
예를 들어,
EmailNotificationStrategy, TextNotificationStrategy,
InAppNotificationStrategy 등의 클래스를 만들 수 있습니다.
각 클래스는 동일한 NotificationStrategy 인터페이스를 구현하며,
이 인터페이스는 알림을 보내는 send 메소드를 정의합니다.
이를 통해, 웹 애플리케이션에서는 send 메소드를 호출함으로써 알림을 보낼 수 있으며,
이 메소드는 실행 시점에서 선택된 전략에 따라 서로 다른 방식으로 알림을 보낼 수 있다.
전략 디자인 패턴
이라 한다.행위
를 수정하지 않고 전략
을 바꿔주기만 함으로써 행위를 유연하게 확장할 수 있도록 한다.Predicate
는 Java 8에서 도입된 함수형 인터페이스 중 하나로, 어떤 타입의 객체를 입력받아 boolean 값을 리턴하는 test()
라는 메서드 하나를 정의합니다. Predicate
인터페이스는 주로 컬렉션의 항목에 대한 필터링, 검증, 조건적인 처리 등에 사용됩니다.
Predicate
의 사용 이유와 장점은 다음과 같습니다:
Predicate
를 사용하면, 특정 조건에 따른 데이터 필터링 등의 로직을 간결하고 읽기 쉬운 코드로 표현할 수 있습니다.Predicate
를 통해 정의된 조건은 재사용이 가능하며, 다른 메서드에서도 쉽게 적용될 수 있습니다.Predicate
는 람다 표현식과 함께 사용될 수 있으며, 이를 통해 함수형 프로그래밍의 장점을 활용할 수 있습니다. 예를 들어, 람다 표현식을 사용하여 코드를 더욱 간결하게 만들 수 있습니다.예를 들어, 다음은 리스트에서 특정 조건을 만족하는 항목만 필터링하는 예시입니다:
List<String> names = Arrays.asList("John", "Jane", "Adam", "Eve");
Predicate<String> startsWithJ = name -> name.startsWith("J");
List<String> startsWithJNames = names.stream().filter(startsWithJ).collect(Collectors.toList());
위 코드에서 startsWithJ
는 Predicate
를 통해 정의된 조건으로, 이를 filter()
메서드에 전달하여 "J"로 시작하는 이름만을 선택하도록 하였습니다.
⇒ 추가적인 필터 기준 요구
⇒ 동작 파라미터화 도입
// java.uitl.Comparator
public interface Comparator<T> {
int compare(T o1, T o2);
}
구현 + 인스턴스화
익명 클래스
Comporator를 구현해서 sort 메서드의 동작을 다양화할 수 있다. 익명 클래스를 이용해서 무게가 적은 순서로 목록에서 사과를 정렬할 수 있다.
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWegiht().compareTo(a2.getWeight());
}
});
람다 표현식
을 이용하면 다음처럼 간단하게 코드를 구현할 수 있다.inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo)a2.getWeight()));
//사과 클래스
class Apple {
enum Color{
GREEN, RED
}
public Color color;
public int weight;
public Apple(Color color, int weight){
this.color = color;
this.weight = weight;
}
}
public class Main {
public static List<Apple> Apples = Arrays.asList(new Apple(Apple.Color.GREEN),new Apple(Apple.Color.RED));
public static void main(String[] args) {
List<Apple> result = new ArrayList<>();
for (Apple apple : Apples){
if (apple.color.equals(Apple.Color.GREEN)){
result.add(apple);
}
}
result.stream().forEach(apple -> System.out.println(apple.color));
}
}
public class Main {
public static List<Apple> Apples = Arrays.asList(new Apple(Apple.Color.GREEN),new Apple(Apple.Color.RED));
public static void main(String[] args) {
appleColorFilter(Apples,Apple.Color.GREEN).stream().forEach(apple -> System.out.println(apple.color));
}
public static List<Apple> appleColorFilter(List<Apple> apples, Apple.Color color){
List<Apple> result = new ArrayList<>();
for (Apple apple : Apples){
if (apple.color.equals(color)){
result.add(apple);
}
}
return result;
}
}
public static List<Apple> appleWeightFilter(List<Apple> apples, int weight){
List<Apple> result = new ArrayList<>();
for (Apple apple : Apples){
if (apples.weigth >= weight){
result.add(apple);
}
}
return result;
}
public static List<Apple> appleFilter(List<Apple> apples, Apple.Color color, int weight){
List<Apple> result = new ArrayList<>();
for (Apple apple : Apples){
if (apple.color.equals(color) && apple.weight >= weight){
result.add(apple);
}
}
return result;
}
// 이렇듯 의미없는 0이 들어 가버린다.
appleFilter(apples, [Apple.Color.Green](http://apple.color.green/), 0)
⇒ 아마 여태까지 많은 이들이 이렇게 진행했을 것이다.
⇒ 생성자 vs 빌더
와도 같다. 위 코드를 보고있자면 이게 무엇을 의미하는지 바로바로 알 수가 없어서 불편하다.
동작 파라미터화 도입
**// 필터에 따른 선택 **조건을 결정하는 인터페이스**를 정의
interface ApplePredicate{
public boolean filter(Apple apple);
}
/*컬러 필터*/
class AppleColorFilter implements ApplePredicate{
@Override
public boolean filter(Apple apple) {
return apple.color.equals(Apple.Color.GREEN);
}
}
/*무게 필터*/
class AppleWeightFilter implements ApplePredicate{
@Override
public boolean filter(Apple apple) {
return apple.weight >= 150;
}
}
// 메서드는 객체만 인수로 받으므로 메서드를 ApplePredicate 객체로 감싸서 전달
public static List<Apple> appleFilter(List<Apple> apples,
ApplePredicate p){
List<Apple> result = new ArrayList<>();
for (Apple apple : Apples){
if (p.filter(apple)){
result.add(apple);
}
}
return result;
}
public static void main(String[] args) {
appleFilter(Apples,new AppleColorFilter())
.stream().forEach(apple -> System.out.println(apple.color));
appleFilter(Apples,new AppleWeightFilter())
.stream().forEach(apple -> System.out.println(apple.color));
}
이처럼 각 항목에 적용할 동작을 분리할 수 있는다는 것은 동작 파라미터화의 강점이다.
⇒ 왜냐하면 어떻게 실행할 지 결정하지 않았기에 추상화된 것을 가지고 하나로 통일 가능
⇒ 유연한 API를 만들 때 동작 파라미터화가 중요한 역할을 한다.
- 로직과 관련없는 코드도 많이 추가되기에 시간 낭비 초래
익명 클래스
interface ApplePredicate{
public boolean filter(Apple apple);
}
public static List<Apple> appleFilter(List<Apple> apples, ApplePredicate p){
List<Apple> result = new ArrayList<>();
for (Apple apple : Apples){
if (p.filter(apple)){
result.add(apple);
}
}
return result;
}
public static void main(String[] args) {
appleFilter(Apples, new ApplePredicate() { // 익명 객체 활용
@Override
public boolean filter(Apple apple) {
return apple.color.equals(Apple.Color.GREEN);
}
}).stream().forEach(apple -> System.out.println(apple.color));
appleFilter(Apples,new AppleWeightFilter()) // 상속 활용
.stream().forEach(apple -> System.out.println(apple.color));
}
람다 표현식
public static void main(String[] args) {
appleFilter(Apples,(Apple apple) -> Apple.Color.GREEN.equals(apple.color));
}
또한 추상화를 통하여 문제를 해결 할 수 있다.
추상화를 통한 문제해결
interface ItemPredicate<T>{
public boolean filter(T item);
}
----
public static <T> List<T> filter(List<T> list, ItemPredicate<T> p){
List<T> result = new ArrayList<>();
for (T item : list){
if (p.filter(item)){
result.add(item);
}
}
return result;
}
이런 유용한 정보를 나눠주셔서 감사합니다.