이번 포스팅에서는 Chapter2의 동작 파라미터화 코드 전달하기 에 대해 진행하도록 하겠습니다.
진행하기에 앞서 동작 파라미터화의 의미를 설명하자면 아래와 같습니다.
아직은 어떻게 실행할것인지 결정하지 않은 코드 블록
2. 변화하는 요구사항에 대응하기
책에서는 사과 농부의 요구사항을 예로 들고 있습니다.
요구사항으로는 녹색인 사과 혹은 특정 무게 이상인 사과와 같이 filter 류의 요구사항들입니다.
1) 첫 번째 시도 : 녹색 사과 필터링
private enum Color {
GREEN,
RED
}
@Getter
@RequiredArgsConstructor
private static class Apple {
private final Color color;
private final int weight;
}
private static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> result = new ArrayList<>();
for(Apple apple: apples) {
if(Color.GREEN.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
아직, 색이 다양해짐에 따라 메소드들이 늘어나야 하는 불편함이 존재합니다.
아래는 이를 해결하기 위해 값을 파라미터화한 케이스입니다.
2) 두 번째 시도 : 색을 파라미터화
private enum Color {
GREEN,
RED
}
@Getter
@RequiredArgsConstructor
private static class Apple {
private final Color color;
private final int weight;
}
private static List<Apple> filterApplesByColor(List<Apple> apples, Color color) {
List<Apple> result = new ArrayList<>();
for(Apple apple: apples) {
if(color.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
private static List<Apple> filterApplesByWeight(List<Apple> apples, int weight) {
List<Apple> result = new ArrayList<>();
for(Apple apple: apples) {
if(apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
값을 파라미터화한 이 코드가 나쁘진 않습니다. 다만, 중복되는 코드가 존재하는것을 볼 수 있습니다.
아래는 그것을 해결하기 위해 flag 값을 두어 필터링하는 케이스입니다.
3) 세 번째 시도 : 가능한 모든 속성으로 필터링
private static List<Apple> filterApples(List<Apple> apples, Color color, int weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for(Apple apple: apples) {
if((flag && apple.getColor().equals(color))
|| (!flag && apple.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}
public static void main(String[] args) {
List<Apple> appleList = Arrays.asList(new Apple(Color.RED, 200), new Apple(Color.GREEN, 30), new Apple(Color.GREEN, 300));
final List<Apple> greenAppleList = filterApples(appleList, Color.GREEN, 0, true);
final List<Apple> heavyAppleList = filterApples(appleList, null, 150, false);
}
한개의 메서드로 줄어지긴 했지만 코드가 너무 지저분하며 이해하기가 어렵습니다.
이를 해결하기 위해, 이제 동작 파라미터화를 진행해보겠습니다.
반응형
3. 동작 파라미터화
private interface ApplePredicate {
boolean test (Apple apple);
}
private static class AppleHeavyWeightPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
private static class AppleGreenColorPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return Color.GREEN.equals(apple.getColor());
}
}
이번 포스팅에서는 Modern Java In Action 의 1장인 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가? 에 대해 알아보도록 하겠습니다.
2. 왜 아직도 자바는 변화하는가?
프로그래밍 언어 생태계는 끊임없이 변화하고 있습니다.
이러한 생태계에서 살아남기위해 프로그래밍 언어들은 계속 발전을 해왔고, 자바도 그 중 하나입니다.
자바는 살아남기 위해 무엇을 발전했는지 아래 설명하도록 하겠습니다.
1) 스트림 처리
자바8에서 java.util.stream 패키지가 추가되었습니다.
이 패키지에 있는 기능들은 리눅스의 파이프라인과 동일하다고 생각하시면 편합니다.
아래는 리눅스의 파일을 읽어 소문자로 변경하고 정렬하여 마지막 3줄만을 출력하는 command입니다.
cat file1 | tr "[A-Z]" "[a-z]" | sort | tail -3
각 파일읽기, 소문자 치환, 정렬 같은 일련의 작업을 ' | ' 사용하여 연결하는 것을 볼 수 있습니다.
이와 같이 자바 8에서도 Stream 패키지를 통해 각각의 작업을 하나의 일련의 작업으로 만들 수 있게 제공하고 있습니다.
2) 동작 파라미터화로 메서드에 코드 전달하기
자바 8에서는 메서드를 다른 메서드로 전달할 방법이 없었습니다.
하지만 자바 8에서 이를 지원하도록 개선되었고 이를 통해 복잡한 코드구조가 사라지게 되었습니다.
아래는 대표적인 메서드에 코드를 전달할 수 있는 함수형 인터페이스입니다.
패키지는 java.util.function 입니다.
Predicate
Consumer
Supplier
3) 병렬성과 공유 가변 데이터
자바 8에서는 스트림API를 지원하면서 간단히 병렬성을 가져갈수 있도록 제공하고 있습니다.
하지만, 이를 위해서는 멀티 쓰레드 환경에서도 서로 코드를 동시에 수행하더라도 안전하게 바꿔야하는 번거로움이 존재합니다.
스트림 함수 사용 시 외부접근 데이터를 Atomic한 객체로 만들어 사용해야 합니다. 이는, 위의 안전한 코드여야 하기 때문입니다.
반응형
3. 자바 함수
프로그래밍 언어에서 함수라는 용어는 메서드와 같은 의미로 사용됩니다.
자바 8에서는 이러한 메서드를 일급시민으로 올리도록 제공하고 있습니다.
일급시민이란, 각 구조체의 값을 전달할 수 있는 것을 의미합니다.
그러므로, 위의 메서드를 일급시민으로 올린다는 의미는 메서드라는 구조체의 값을 다른 구조체에게 전달할 수 있다는 의미입니다.
1) 메서드와 람다를 일급시민으로
1 - 메서드 참조
자바8 에서는 메서드참조를 제공합니다.
아래는 숨겨진 파일들만을 filter하여 얻어오는 코드입니다.
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isHidden();
}
});
위의 코드는 자바 8이 나오기전의 코드입니다.
자바 8에서는 람다와 메서드 참조를 제공하였고 그 결과 아래와 같이 더욱 readable 있는 코드로 리팩토링이 가능해졌습니다.
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
람다를 통해 익명클래스를 간단하게 바꾸었으며, ::isHidden 인 메서드 참조를 통해 한결 간단해진 코드가 되었습니다.
2- 코드 넘겨주기
아래는 사과 리스트에서 초록색이며, 무게가 200이 넘는 사과만 분류하는 코드입니다. - 자바 8 이전
private enum Color {
GREEN,
RED
}
public static void main(String[] args) {
List<Apple> appleList = Arrays.asList(new Apple(Color.RED, 200), new Apple(Color.GREEN, 30), new Apple(Color.GREEN, 300));
final List<Apple> filterdGreenAppleList = filterGreenApples(appleList);
final List<Apple> filterdGreenWithHeavyAppleList = filterHeavyApples(filterdGreenAppleList, 200);
}
@Getter
@RequiredArgsConstructor
private static class Apple {
private final Color color;
private final int weight;
}
private static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> result = new ArrayList<>();
for(Apple apple: apples) {
if(Color.GREEN.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
private static List<Apple> filterHeavyApples(List<Apple> apples, int weight) {
List<Apple> result = new ArrayList<>();
for(Apple apple: apples) {
if(apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
하지만, 자바 8의 스트림 함수와 람다를 사용하면 아래와 같이 간단하게 바뀔 수 있습니다.
public static void main(String[] args) {
List<Apple> appleList = Arrays.asList(new Apple(Color.RED, 200), new Apple(Color.GREEN, 30), new Apple(Color.GREEN, 300));
final List<Apple> filterdGreenWithHeavyAppleList = appleList
.stream()
.filter(apple -> Color.GREEN.equals(apple.getColor()))
.filter(apple -> apple.getWeight() > 200)
.collect(Collectors.toList());
}
단, filter 에 있는 조건들이 일회성이 아니라면 별도 함수로 추출해서 사용해야 합니다.
4. 스트림
이전 옛날 자바에서는 한개의 CPU만을 사용하는 단점이 있었습니다.
하지만 자바 8에서는 한개가 아닌 멀티 CPU를 점유해서 사용하도록 변경되었고 이는 성능의 극대화를 가져다 주었습니다
대표적으로 parallelStream 을 들 수 있습니다.
parallelStream은 멀티 CPU를 통해 분할로 처리할때 사용합니다.
단, Parallel Stream 작업이 독립적이면서CPU사용이 높은 작업에 사용해야합니다.
5. 디폴트 메서드와 자바 모듈
자바 8에서는 인터페이스에 디폴트 메서드를 제공합니다.
디폴트 메서드는 기존의 코드를 수정하지 않고 확장하기 위해서 만들어진 것이며,
간단하게 인터페이스에도 기본적으로 정의된 구현 메서드가 있을 수 있다고 생각하면 됩니다.
아래는 List의 default sort 함수입니다.
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
6. 함수형 프로그래밍에서 가져온 다른 유용한 아이디어
자바 8에서 제공되는 기능들은 대체로 함수형 프로그래밍 특성을 가져다 주는 것입니다.
이런 함수형 프로그래밍의 패러다임을 가져오면서 많은 프로그램에 도움이 되는 아이디어가 나왔습니다.
한가지 예로, null을 회피하는 방법이며 자바에서는 Optional<T> 라는 컨테이너 클래스를 통해 제공하고 있습니다.
대게, 스트리밍이라하면 실시간 처리로 알고 있습니다. 스파크는 마이크로 배치로 짧게 여러번 수행하여 스트리밍 처리를 제공합니다.
2. 주요 용어
1) 스트리밍 컨텍스트
스파크 스트리밍을 수행하기 위해서는 스트리밍 모듈에서 제공하는 스트리밍 컨텍스트를 사용해야 합니다.
아래는 스트리밍 컨텍스트를 생성하고 사용하는 예제입니다. - (스칼라)
val conf = new SparkConf()
conf.setMaster("local[*]")
conf.setAppName("StreamingSample")
conf.set("spark.driver.host", "127.0.0.1")
val sc = new SparkContext(conf)
val ssc = new StreamingContext(sc, Seconds(3))
val rdd1 = sc.parallelize(List("Spark Streaming Sample ssc"))
val rdd2 = sc.parallelize(List("Spark Queue Spark API"))
val inputQueue = Queue(rdd1, rdd2)
val lines = ssc.queueStream(inputQueue, true)
val words = lines.flatMap(_.split(" "))
words.countByValue().print()
ssc.start()
ssc.awaitTermination()
예제에서 볼 수 있듯이 StreamingContext는 SparkContext에서 생성 할 수 있습니다.
추가로, 어느 주기로 수행할 지의 정보도 같이 넘겨야 합니다.
예제에서는 Seconds(3)을 통해 3초에 한번씩 수행되도록 하였습니다.
그리고, 마지막에 있는 start 메소드를 실행해야만 스파크 스트리밍은 수행됩니다.
또한, awaitTermination 를 통해 임의로 애플리케이션이 종료되지 않도록 합니다.
종료는 개발자가 직접 어떤 시점 혹은 상황에 하도록 추가하여야 합니다.
2) DStream (Discretized Streams)
DStream은 RDD 와 같이 스파크에서 스트리밍을 위해 제공하는 데이터 모델입니다.
단순하게 RDD의 시퀀스로 이해하시면 되며, 지정한 배치 간격마다 input 에서 데이터를 가져와 DStream으로 변경하면서 처리하게 됩니다.
인자로 받은 문자열 중 나란히 있는 중복문자를 반복적으로 제거하고, 남은 문자열을 반환하는 문제입니다.
위 예제를 보면 아래와 같이 처리가 흐르게 됩니다.
1. abbaca -> bb 제거
2. aaca -> aa 제거
3. ca 반환
저는 아래와 같이 풀이하였습니다.
class Solution:
def removeDuplicates(self, S: str) -> str:
stack = []
stack.append(S[0])
for i in S[1:]:
if stack and stack[-1] == i:
stack.pop()
else:
stack.append(i)
return ''.join(stack)
stack으로 이용할 list를 하나 만들었습니다.
그 후, 문자열을 iterate하며 stack에서 peek한 결과와 동일하면 pop 아니면 append 하였습니다.