자바 8 인 액션

15. OOP와 FP의 조화 자바 8과 스칼라 비교

Featured image

스칼라 소개

Hello beer

object Beer {
    def main(args: Array[String]) {
        var n : Int = 2
        while(n <= 6) {
            println(s"Hello ${n} bottles of beer")  //  문자열 보간법(자바의 String s와 달리 s : String이라는 문법 구조를 갖는다)
            n += 1
        }
    }
}

/*
Hello 2 bottles of beer
Hello 3 bottles of beer
Hello 4 bottles of beer
Hello 5 bottles of beer
Hello 6 bottles of beer
*/
public class Foo {
    public static void main(String[] args) {
        IntStream.rangeClosed(2, 6)
                 .forEach(n -> System.out.println("Hello " + n + " bottles of beer"));
    }
}
object Beer {
    def main(args: Array[String]) {
        2 to 6 foreach { n => println(s"Hello ${n} bottles of beer") }
    }
}

기본 자료구조: 리스트, 집합, 맵, 튜플, 스트림, 옵션

컬렉션 만들기

val authorsToAge = Map("Raoul" -> 23, "Mario" -> 40, "Alan" -> 53)

val authors = List("Raoul", "Mario", "Alan")
val numbers = Set(1, 1, 2, 3, 5, 8)

스칼라는 자동으로 변수 형식을 추론하는 기능이 있다. 스칼라는 코드를 정적으로 확인한다. 즉, 모든 변수의 형식은 컴파일을 할 때 결정된다.

val은 읽기 전용, 즉 변수에 값을 할당할 수 없고(자바의 final) var은 키워드는 읽고 쓸 수 있는 변수를 가리킨다.

Map<String, Integer> authorsToAge = new HashMap<>();
authorsToAge.put("Raoul", 23);
authorsToAge.put("Mario", 40);
authorsToAge.put("Alan", 53);

불변과 가변

일반적으로 컬렉션을 만들면 변경할 수 없다. 컬렉션이 불변이므로 프로그램에서 언제 컬렉션을 사용하든 항상 같은 요소를 갖게 되고 함수형 프로그래밍에서 유용하게 활용할 수 있다.

스칼라의 불변 컬렉션을 갱신해야 할때는 기존 버전과 가능한 한 많은 자료를 공유하는 새로운 컬렉션을 만드는 방법으로 자료구조를 갱신한다. 결과적으로 암묵적인 데이터 의존성을 줄일 수 있다.

val numbers = Set(2, 5, 3)
val newNumbers = numbers + 8

하지만 스칼라에서 불변 컬렉션만 사용하는 것은 아니다. scala.collection.mutable에서는 가변 버전의 컬렉션을 제공한다.

자바의 변경불가와 불변

자바에서는 변경불가(unmodifiable) 컬렉션을 만드는 여러가지 방법이 있다.

Set<Integer> numbers = new HashSet<>();
Set<Integer> newNumbers = Collections.unmodifiableSet(numbers);

newNumbers는 numbers 집합에서 읽기 전용 값으로 요소를 추출한 변수다. 따라서 newNumbers 변수에는 새로운 요소를 추가할 수 없다. 하지만 변경불가 컬렉션은 값을 고칠 수 있는 컬렉션의 래퍼에 불과하다. 즉, numbers 변수를 이용하면 새로운 요소를 추가할 수 있다.

반면 불변(immutable) 컬렉션은 얼마나 많은 변수가 컬렉션을 참조하는가와 관계없이 컬렉션을 절대 바꿀 수 없다.

튜플

자바는 튜플을 지원하지 않는다.(?) 직접 자료구조를 만들어야 한다.

public class Pair<X, Y> {
    public final X x;
    public final Y y;
    public Pair(X x, Y y) {
        this.x = x;
        this.y = y;
    }
}

Pair<String, String> raoul = new Pair<>("Raoul", "+ 44 007007007"); 
Pair<String, String> alan = new Pair<>("Alan", "+44 003133700");

하지만 스칼라는 튜플 축약어, 즉 간단한 문법으로 튜플을 만들 수 있는 기능을 제공한다.

val raoul = ("Raoul", "+ 44 007007007")
val alan = ("Alan", "+44 003133700")

스칼라는 최대 23개 요소를 그룹화하는 튜플을 만들 수 있다.

스트림

리스트, 집합, 맵, 튜플은 eager evaluation이지만 자바 8의 스트림은 lazy evaluation이다. 이러한 특성 때문에 memory overflow 없이 무한 시퀀스를 표현할 수 있다.

스칼라에서도 lazy evaluation한 스트림을 제공한다.

옵션

스칼라의 Option은 자바 8의 Optional과 같은 기능을 제공한다.

public String getCarInsuranceName(Optional<Persion> person, int minAge) {
    return person.filter(p -> p.getAge() >= minAge)
                 .flatMap(Person::getCar)
                 .flatMap(Car::getInsurance)
                 .map(Insurance::getName)
                 .orElse("Unknown);
}
def getCarInsuranceName(persion: Option[Person], minAge: Int) =
    persion.filter(_.getAge() >= minAge)
           .flatMap(_.getCar)
           .flatMap(_.getInsurance)
           .map(_.getName)
           .getOrElse("Unknown")    

함수

익명 함수와 클로저

스칼라도 익명 함수를 지원한다.

val isLongTweet : String => Boolean =   //  String을 Boolean으로 반환하는 함수 형식의 변수
    (tweet, String) => tweet.length() > 60  //  익명 함수

val isLongTweet : String => Boolean =
    new Function1[String, Boolean] {
        def apply(tweet: String): Boolean = tweet.length() > 60
    }

자바의 람다 표현식으로 함수형 인터페이스의 인스턴스를 만들 수 있다. 스칼라도 비슷한 방식을 지원한다.

Function<String, Boolean> isLongTweet = (String s) -> s.length() > 60;
boolean long = isLongTweet.apply("A very short tweet");

자바에서는 람다 표현식을 사용할 수 있도록 Predicate, Function, Consumer 등의 내장 함수형 인터페이스를 제공했다.

클로저

클로저란 함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스를 가리킨다. 하지만 자바 8의 람다 표현식에는 람다가 정의된 메서드의 지역 변수를 고칠 수 없다는 제약이 있다. 이들 변수는 암시적으로 final로 취급된다(effectively final). 즉, 람다는 변수가 아닌 값을 닫는다는 사실을 기억해야 한다.
하지만 스칼라의 익명 함수는 값이 아니라 변수를 캡처할 수 있다.

클래스와 트레이트

자바의 클래스와 인터페이스를 스칼라와 비교해본다. 스칼라의 클래스와 인터페이스는 자바에 비해 더 유연함을 제공한다.

간결성을 제공하는 스칼라와 클래스

게터와 세터

스칼라에서는 생성자, 게터, 세터가 암시적으로 생성되므로 코드가 훨씬 단순해진다.

스칼라 트레이트와 자바 8 인터페이스

스칼라는 트레이트라는 유용한 추상 기능도 제공한다. 스칼라의 트레이트는 자바의 인터페이스를 대체한다.
자바의 인터페이스처럼 트레이트는 다중 상속을 지원하므로 자바 8의 인터페이스와 디폴트 메서드 기능이 합쳐진 것으로 이해할 수 있다. 다만 추상 클래스와 다른 점은 다중 상속이 가능하다.

요약