7 min to read
자바 8 인 액션
15. OOP와 FP의 조화 자바 8과 스칼라 비교
스칼라 소개
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은 키워드는 읽고 쓸 수 있는 변수를 가리킨다.
- 자바 8
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과 같은 기능을 제공한다.
- java 8
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);
}
- scala
def getCarInsuranceName(persion: Option[Person], minAge: Int) =
persion.filter(_.getAge() >= minAge)
.flatMap(_.getCar)
.flatMap(_.getInsurance)
.map(_.getName)
.getOrElse("Unknown")
함수
익명 함수와 클로저
스칼라도 익명 함수를 지원한다.
- scala
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
}
자바의 람다 표현식으로 함수형 인터페이스의 인스턴스를 만들 수 있다. 스칼라도 비슷한 방식을 지원한다.
- java 8
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의 인터페이스와 디폴트 메서드 기능이 합쳐진 것으로 이해할 수 있다. 다만 추상 클래스와 다른 점은 다중 상속이 가능하다.
요약
- 자바 8과 스칼라는 객체지향과 함수형 프로그래밍 모두를 하나의 프로그래밍 언어로 수용한다. 두 언어는 모두 JVM에서 실행되며 넓은 의미에서 상호운용성을 갖는다.
- 스칼라는 자바 8처럼 리스트, 집합, 맵, 스트림, 옵션 등의 추상 컬렉션을 제공한다. 또한 튜플도 추가로 제공한다.
- 스칼라는 자바 8에 비해 풍부한 함수 관련 기능을 제공한다. 스칼라는 함수 형식, 지역 변수에 접근할 수 있는 클로저, 내장 커링 형식 등을 지원한다.
- 스칼라의 클래스는 암묵적으로 생성자, 게터, 세터를 제공한다.
- 스칼라는 트레이트를 지원한다. 트레이트는 필드와 디폴트 메서드를 포함할 수 있는 인터페이스다.
Comments