Java 개발자를 위한 Scala

Java 개발자를 위한 Scala (Scala for Java Developers)

Java를 잘 아는 개발자가 스칼라를 배우기 시작할 때 가장 궁금한 건 아마 "Java와 뭐가 같고, 뭐가 다른 걸까?" 일 거예요. 이 페이지는 두 언어를 나란히 놓고 비교해 보여드릴게요. Java를 알고 있고, 스칼라의 기능이 Java와 어떻게 비교되는지 보면서 배우고 싶은 분들을 위한 내용이에요.

출처: Scala 3 Book

본문

개요 (Overview)

예제로 들어가기 전에, 이 첫 절에서 이어질 절들에 대한 비교적 간단한 소개와 요약을 드릴게요. Java와 Scala의 유사점과 차이점을 높은 수준에서 살펴보고, 코드를 쓸 때 매일 겪게 될 차이점을 소개할게요.

높은 수준의 유사점

높은 수준에서 Scala는 Java와 이런 점들을 공유해요.

  • Scala 코드는 .class 파일로 컴파일되고, JAR 파일로 패키징되어 JVM에서 실행돼요.
  • 객체지향 프로그래밍(OOP) 언어예요.
  • 정적 타입 언어예요.
  • 두 언어 모두 람다와 고차 함수를 지원해요.
  • 둘 다 IntelliJ IDEA, Microsoft VS Code 같은 IDE에서 쓸 수 있어요.
  • Gradle, Ant, Maven 같은 빌드 도구로 프로젝트를 빌드할 수 있어요.
  • 서버 사이드·네트워크 집약적 애플리케이션을 구축하기 위한 훌륭한 라이브러리와 프레임워크가 있어요. 웹 서버 애플리케이션, 마이크로서비스, 머신러닝 등이 포함돼요("Awesome Scala" 목록 참고).
  • Java와 Scala 모두 Scala 라이브러리를 사용할 수 있어요.
    • Akka 액터 라이브러리로 액터 기반 동시성 시스템을, Apache Spark로 데이터 집약적 애플리케이션을 만들 수 있어요.
    • Play Framework로 서버 사이드 애플리케이션을 개발할 수 있어요.
    • GraalVM으로 프로젝트를 네이티브 실행 파일로 컴파일할 수 있어요.
  • Scala는 Java를 위해 개발된 풍부한 라이브러리를 매끄럽게 사용할 수 있어요.

높은 수준의 차이점

또한 높은 수준에서 Java와 Scala의 차이점은 이래요.

  • Scala는 간결하면서도 읽기 쉬운 문법을 가져요. 우리는 이를 표현력이 뛰어나다(expressive)고 불러요.
  • 정적 타입이지만, Scala는 종종 동적 언어처럼 느껴져요.
  • Scala는 순수한 OOP 언어라서, 모든 객체는 클래스의 인스턴스고, +, +=처럼 연산자처럼 보이는 기호는 실제로는 메서드예요. 따라서 자신만의 연산자를 만들 수 있어요.
  • 순수 OOP 언어인 동시에 Scala는 순수 FP 언어이기도 해요. 사실 OOP와 FP의 융합을 장려해요 — 로직에는 함수를, 모듈성에는 객체를 쓰는 식으로요.
  • Scala는 List, Vector, 불변 Map, Set 구현을 포함한 완전한 불변 컬렉션 세트를 갖추고 있어요.
  • Scala의 모든 것은 표현식이에요. if 문, for 루프, match 표현식, 심지어 try/catch 표현식까지 모두 반환값을 가져요.
  • Scala 관용구는 기본적으로 불변성을 선호해요 — 불변(final) 변수와 불변 컬렉션을 쓰도록 권장해요.
  • 관용적 Scala 코드는 null을 쓰지 않아서, NullPointerException으로 고통받지 않아요.
  • Scala 생태계는 sbt, Mill 등의 다른 빌드 도구를 갖추고 있어요.
  • JVM에서 실행될 뿐 아니라, Scala.js 프로젝트로 JavaScript의 대안으로 Scala를 쓸 수 있어요.
  • Scala Native 프로젝트는 "시스템" 수준 코드를 쓸 수 있게 해 주는 저수준 구성을 추가하고, 네이티브 실행 파일로도 컴파일돼요.

프로그래밍 수준의 차이점

마지막으로, 코드를 쓸 때 매일 보게 될 차이점 몇 가지예요.

  • Scala의 문법은 극도로 일관돼요.
  • 변수와 파라미터는 val(불변. Java의 final과 같음)이나 var(가변)로 정의돼요.
  • 타입 추론 덕분에 코드가 동적 타입처럼 느껴지고, 코드를 짧게 유지하는 데도 도움이 돼요.
  • 단순한 for 루프 외에도, 알고리즘을 바탕으로 결과를 만들어 내는 강력한 for comprehension이 있어요.
  • 패턴 매칭과 match 표현식은 코드 쓰는 방식을 바꿔 놓을 거예요.
  • 기본적으로 불변 코드를 쓰다 보면 문장(statement)보다 표현식(expression)을 쓰게 되는데, 시간이 지나면 표현식이 코드(와 테스트)를 단순화한다는 걸 알게 돼요.
  • 톱레벨 정의(toplevel definitions)로 메서드, 필드 등 정의를 어디에든 둘 수 있어서, 간결하고 표현력 있는 코드를 만드는 데도 이어져요.
  • 여러 트레이트를 클래스와 object에 "섞어"(mixing) 믹스인을 만들 수 있어요(트레이트는 Java 8 이상의 인터페이스와 비슷해요).
  • 클래스는 기본적으로 폐쇄(closed) 되어 있어서, Joshua Bloch의 Effective Java 관용구인 "상속을 위해 설계하고 문서화하거나, 그렇지 않으면 금지하라"를 지원해요.
  • Scala의 **문맥 추상화(contextual abstractions)**와 **용어 추론(term inference)**은 여러 기능을 제공해요.
    • 확장 메서드로 폐쇄된 클래스에 새 기능을 추가할 수 있어요.
    • given 인스턴스로 컴파일러가 사용 지점에서 합성할 수 있는 용어를 정의할 수 있어요. 코드를 덜 장황하게 만들고, 본질적으로 컴파일러가 코드를 대신 쓰게 해 줘요.
    • 다중 우주 동등성(multiversal equality)으로 동등성 비교를 — 컴파일 타임에 — 의미 있는 비교만으로 한정할 수 있어요.
  • Scala에는 최신의, 서드파티, 오픈소스 함수형 프로그래밍 라이브러리가 있어요.
  • Scala 케이스 클래스(case class)는 Java 14의 레코드(record)와 같아요. FP 코드를 쓸 때 데이터를 모델링하는 데 도움이 되고, 패턴 매칭·클로닝 같은 개념을 기본으로 지원해요.
  • by-name 파라미터, 인픽스 표기법, 선택적 괄호, 확장 메서드, 고차 함수 같은 기능 덕분에, 자신만의 "제어 구조"와 DSL을 만들 수 있어요.
  • Scala 파일은 담고 있는 클래스나 트레이트에 따라 이름을 지을 필요가 없어요.
  • 그 밖에도 좋은 것들이 많아요: 컴패니언 클래스와 object, 매크로, 유니언·인터섹션 타입, 숫자 리터럴, 다중 파라미터 리스트, 파라미터 기본값, 이름 붙은 인자 등.

예제로 비교한 기능들

그런 소개를 바탕으로, 다음 절들에서 Java와 Scala 프로그래밍 언어 기능을 나란히 비교해 보여드릴게요.

OOP 스타일 클래스와 메서드

이 절에서는 OOP 스타일 클래스와 메서드와 관련된 기능들을 비교해요.

주석 (Comments):

Java:

// line comment
/* ... */
/** ... */

Scala:

// line comment
/* ... */
/** ... */

OOP 스타일 클래스, 주 생성자 (primary constructor):

Scala는 JavaBeans 표준을 따르지 않아요. 그래서 JavaBeans 스타일로 쓴 Java 코드를 보여주는 대신, 뒤에 이어지는 Scala 코드와 동등한 Java 코드를 보여줄게요.

Java:

class Person {
  public String firstName;
  public String lastName;
  public int age;

  public Person(
    String firstName,
    String lastName,
    int age
  ) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  public String toString() {
    return String.format("%s %s is %d years old.", firstName, lastName, age);
  }
}

Scala:

class Person (
  var firstName: String,
  var lastName: String,
  var age: Int
):
  override def toString = s"$firstName $lastName is $age years old."

보조 생성자 (Auxiliary constructors):

Java:

public class Person {
  public String firstName;
  public String lastName;
  public int age;

  // primary constructor
  public Person(
    String firstName,
    String lastName,
    int age
  ) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  // zero-arg constructor
  public Person() {
    this("", "", 0);
  }

  // one-arg constructor
  public Person(String firstName) {
    this(firstName, "", 0);
  }

  // two-arg constructor
  public Person(
    String firstName,
    String lastName
  ) {
    this(firstName, lastName, 0);
  }
}

Scala:

class Person (
  var firstName: String,
  var lastName: String,
  var age: Int
):
    // zero-arg auxiliary constructor
    def this() = this("", "", 0)

    // one-arg auxiliary constructor
    def this(firstName: String) =
      this(firstName, "", 0)

    // two-arg auxiliary constructor
    def this(
      firstName: String,
      lastName: String
    ) =
      this(firstName, lastName, 0)
end Person

기본적으로 폐쇄된 클래스:

"상속을 위해 계획하거나, 그렇지 않으면 금지하라."

Java:

final class Person

Scala:

class Person

확장을 위해 열린 클래스:

Java:

class Person

Scala:

open class Person

한 줄 메서드:

Java:

public int add(int a, int b) {
  return a + b;
}

Scala:

def add(a: Int, b: Int): Int = a + b

여러 줄 메서드:

Java:

public void walkThenRun() {
  System.out.println("walk");
  System.out.println("run");
}

Scala:

def walkThenRun() =
  println("walk")
  println("run")

불변 필드:

Java:

final int i = 1;

Scala:

val i = 1

가변 필드:

Java:

int i = 1;
var i = 1;

Scala:

var i = 1

인터페이스, 트레이트, 상속

이 절에서는 Java 인터페이스와 Scala 트레이트를 비교하고, 클래스가 인터페이스와 트레이트를 어떻게 상속하는지 다뤄요.

인터페이스/트레이트:

Java:

public interface Marker {};

Scala:

trait Marker

단순 인터페이스:

Java:

public interface Adder {
  public int add(int a, int b);
}

Scala:

trait Adder:
  def add(a: Int, b: Int): Int

구체 메서드를 가진 인터페이스:

Java:

public interface Adder {
  int add(int a, int b);

  default int multiply(
    int a, int b
  ) {
    return a * b;
  }
}

Scala:

trait Adder:
  def add(a: Int, b: Int): Int

  def multiply(a: Int, b: Int): Int =
    a * b

상속:

Java:

class Dog extends Animal implements HasLegs, HasTail

Scala:

class Dog extends Animal, HasLegs, HasTail

여러 인터페이스 상속

이 인터페이스들과 트레이트들은 구체적이고 구현된 메서드(기본 메서드)를 가져요.

Java:

interface Adder {
  default int add(int a, int b) {
    return a + b;
  }
}

interface Multiplier {
  default int multiply (
    int a,
    int b)
  {
    return a * b;
  }
}

public class JavaMath
implements Adder, Multiplier {}

JavaMath jm = new JavaMath();
jm.add(1,1);
jm.multiply(2,2);

Scala:

trait Adder:
  def add(a: Int, b: Int) = a + b

trait Multiplier:
  def multiply(a: Int, b: Int) = a * b

class ScalaMath extends Adder, Multiplier

val sm = new ScalaMath
sm.add(1,1)
sm.multiply(2,2)

믹스인 (Mixins):

Java:

N/A

Scala:

class DavidBanner

trait Angry:
  def beAngry() =
    println("You won't like me ...")

trait Big:
  println("I'm big")

trait Green:
  println("I'm green")

// mix in the traits as DavidBanner
// is created
val hulk = new DavidBanner with Big with Angry with Green

제어 구조 (Control Structures)

이 절에서는 Java와 Scala의 제어 구조를 비교해요.

if 문, 한 줄:

Java:

if (x == 1) { System.out.println(1); }

Scala:

if x == 1 then println(x)

if 문, 여러 줄:

Java:

if (x == 1) {
  System.out.println("x is 1, as you can see:")
  System.out.println(x)
}

Scala:

if x == 1 then
  println("x is 1, as you can see:")
  println(x)

if, else if, else:

Java:

if (x < 0) {
  System.out.println("negative")
} else if (x == 0) {
  System.out.println("zero")
} else {
  System.out.println("positive")
}

Scala:

if x < 0 then
  println("negative")
else if x == 0
  println("zero")
else
  println("positive")

메서드 본문으로서의 if:

Java:

public int min(int a, int b) {
  return (a < b) ? a : b;
}

Scala:

def min(a: Int, b: Int): Int =
  if a < b then a else b

if에서 값 반환

Java에서는 이것을 삼항 연산자라고 불러요.

Java:

int minVal = (a < b) ? a : b;

Scala:

val minValue = if a < b then a else b

while 루프:

Java:

while (i < 3) {
  System.out.println(i);
  i++;
}

Scala:

while i < 3 do
  println(i)
  i += 1

for 루프, 한 줄:

Java:

for (int i: ints) {
  System.out.println(i);
}

Scala:

//preferred
for i <- ints do println(i)

// also available
for (i <- ints) println(i)

for 루프, 여러 줄:

Java:

for (int i: ints) {
  int x = i * 2;
  System.out.println(x);
}

Scala:

for
  i <- ints
do
  val x = i * 2
  println(s"i = $i, x = $x")

for 루프, 여러 제너레이터:

Java:

for (int i: ints1) {
  for (int j: chars) {
    for (int k: ints2) {
      System.out.printf("i = %d, j = %d, k = %d\n", i,j,k);
    }
  }
}

Scala:

for
  i <- 1 to 2
  j <- 'a' to 'b'
  k <- 1 to 10 by 5
do
  println(s"i = $i, j = $j, k = $k")

가드(if) 표현식을 가진 제너레이터:

Java:

List ints =
  ArrayList(1,2,3,4,5,6,7,8,9,10);

for (int i: ints) {
  if (i % 2 == 0 && i < 5) {
    System.out.println(x);
  }
}

Scala:

for
  i <- 1 to 10
  if i % 2 == 0
  if i < 5
do
  println(i)

for comprehension:

Java:

N/A

Scala:

val list =
  for
    i <- 1 to 3
  yield
    i * 10

// list: Vector(10, 20, 30)

switch/match:

Java:

String monthAsString = "";
switch(day) {
  case 1: monthAsString = "January";
          break;
  case 2: monthAsString = "February";
          break;
  default: monthAsString = "Other";
          break;
}

Scala:

val monthAsString = day match
  case 1 => "January"
  case 2 => "February"
  case _ => "Other"

switch/match, 케이스당 여러 조건:

Java:

String numAsString = "";
switch (i) {
  case 1: case 3:
  case 5: case 7: case 9:
    numAsString = "odd";
    break;
  case 2: case 4:
  case 6: case 8: case 10:
    numAsString = "even";
    break;
  default:
    numAsString = "too big";
    break;
}

Scala:

val numAsString = i match
  case 1 | 3 | 5 | 7 | 9 => "odd"
  case 2 | 4 | 6 | 8 | 10 => "even"
  case _ => "too big"

try/catch/finally:

Java:

try {
  writeTextToFile(text);
} catch (IOException ioe) {
  println(ioe.getMessage())
} catch (NumberFormatException nfe) {
  println(nfe.getMessage())
} finally {
  println("Clean up resources here.")
}

Scala:

try
  writeTextToFile(text)
catch
  case ioe: IOException =>
    println(ioe.getMessage)
  case nfe: NumberFormatException =>
    println(nfe.getMessage)
finally
  println("Clean up resources here.")

컬렉션 클래스 (Collections classes)

이 절에서는 Java와 Scala에서 사용할 수 있는 컬렉션 클래스들을 비교해요.

불변 컬렉션 클래스

불변 컬렉션의 인스턴스를 만드는 예시들이에요.

시퀀스 (Sequences):

Java:

List strings = List.of("a", "b", "c");

Scala:

val strings = List("a", "b", "c")
val strings = Vector("a", "b", "c")

집합 (Sets):

Java:

Set set = Set.of("a", "b", "c");

Scala:

val set = Set("a", "b", "c")

맵 (Maps):

Java:

Map map = Map.of(
  "a", 1,
  "b", 2,
  "c", 3
);

Scala:

val map = Map(
  "a" -> 1,
  "b" -> 2,
  "c" -> 3
)

가변 컬렉션 클래스

Scala는 scala.collection.mutable 패키지에 ArrayBuffer, Map, Set 같은 가변 컬렉션 클래스를 갖고 있어요. 그것들을 현재 스코프로 import하면, 방금 본 불변 List, Vector, Map, Set 예제들과 똑같이 만들면 돼요.

Scala에는 또한 Array 클래스가 있는데, Java 배열 원시 타입의 래퍼라고 생각하면 돼요. Scala의 Array[A]는 Java의 A[]에 매핑되므로, 이 Scala Array[String]을,

val a = Array("a", "b")

이 Java String[]으로 뒷받침된다고 생각할 수 있어요.

String[] a = {"a", "b"};

하지만 Scala의 Array는 Scala 컬렉션에서 기대하는 함수형 메서드도 모두 갖고 있어요. mapfilter를 포함해서요.

val nums = Array(1, 2, 3, 4, 5)
val doubledNums = nums.map(_ * 2)
val filteredNums = nums.filter(_ > 2)

Scala Array는 Java 배열과 같은 방식으로 표현되므로, Java에서 배열을 반환하는 메서드를 Scala 코드에서 쉽게 사용할 수 있어요.

Array에 대한 이런 이야기에도 불구하고, 스칼라에는 종종 Array보다 더 나은 대안이 있다는 점을 명심하세요. 배열은 다른 언어(Java, JavaScript)와 상호운용하는 데 유용해요. 또한 기본 플랫폼에서 최대 성능을 짜내야 하는 저수준 코드를 쓸 때도 유용할 수 있어요. 하지만 일반적으로 시퀀스가 필요할 때, Scala 관용구는 VectorList 같은 불변 시퀀스를 선호하고, 정말 가변 시퀀스가 필요할 때 그때 ArrayBuffer를 사용하는 거예요.

Scala CollectionConverters object로 Java와 Scala 컬렉션 클래스를 변환할 수도 있어요. 서로 다른 패키지에 두 개의 object가 있는데, 하나는 Java에서 Scala로, 다른 하나는 Scala에서 Java로 변환하는 거예요. 이 표는 가능한 변환을 보여줘요.

Java Scala
java.util.Collection scala.collection.Iterable
java.util.List scala.collection.mutable.Buffer
java.util.Set scala.collection.mutable.Set
java.util.Map scala.collection.mutable.Map
java.util.concurrent.ConcurrentMap scala.collection.mutable.ConcurrentMap
java.util.Dictionary scala.collection.mutable.Map

컬렉션 클래스의 메서드

Java 컬렉션을 스트림으로 다루는 능력 덕분에, Java와 Scala는 이제 공통 함수형 메서드들을 많이 갖게 됐어요.

  • map
  • filter
  • forEach/foreach
  • findFirst/find
  • reduce

Java에서 람다 표현식으로 이런 메서드를 쓰는 데 익숙하다면, Scala의 컬렉션 클래스에서 같은 메서드를 쓰는 것도 쉬울 거예요.

Scala에는 head, tail, drop, take, distinct, flatten 등 수십 가지의 다른 컬렉션 메서드도 있어요. 처음에는 왜 이렇게 많은 메서드가 있는지 궁금할 수도 있어요. 하지만 스칼라로 작업하다 보면, 이 메서드들 덕분에 커스텀 for 루프를 거의 쓸 일이 없다는 걸 깨닫게 돼요.

(이것은 커스텀 for 루프를 읽을 일도 거의 없다는 뜻이에요. 개발자들은 코드를 쓰는 시간보다 10배 정도 읽는 데 더 많은 시간을 쓰는 경향이 있으니, 이건 중요한 차이예요.)

튜플 (Tuples)

Java 튜플은 이렇게 만들어요.

Pair<String, Integer> pair =
  new Pair<String, Integer>("Eleven", 11);

Triplet<String, Integer, Double> triplet =
  Triplet.with("Eleven", 11, 11.0);
Quartet<String, Integer, Double, Person> quartet =
  Quartet.with("Eleven", 11, 11.0, new Person("Eleven"));

다른 Java 튜플 이름으로는 Quintet, Sextet, Septet, Octet, Ennead, Decade가 있어요.

Scala에서 크기와 상관없는 튜플은 값을 괄호 안에 넣어 만들 수 있어요.

val a = ("eleven")
val b = ("eleven", 11)
val c = ("eleven", 11, 11.0)
val d = ("eleven", 11, 11.0, Person("Eleven"))

열거형 (Enums)

이 절에서는 Java와 Scala의 열거형(enum)을 비교해요.

기본 enum:

Java:

enum Color {
  RED, GREEN, BLUE
}

Scala:

enum Color:
  case Red, Green, Blue

파라미터화된 enum:

Java:

enum Color {
  Red(0xFF0000),
  Green(0x00FF00),
  Blue(0x0000FF);

  private int rgb;

  Color(int rgb) {
    this.rgb = rgb;
  }
}

Scala:

enum Color(val rgb: Int):
  case Red   extends Color(0xFF0000)
  case Green extends Color(0x00FF00)
  case Blue  extends Color(0x0000FF)

사용자 정의 enum 멤버:

Java:

enum Planet {
  MERCURY (3.303e+23, 2.4397e6),
  VENUS   (4.869e+24, 6.0518e6),
  EARTH   (5.976e+24, 6.37814e6);
  // more planets ...

  private final double mass;
  private final double radius;

  Planet(double mass, double radius) {
    this.mass = mass;
    this.radius = radius;
  }

  public static final double G =
    6.67300E-11;

  private double mass() {
    return mass;
  }

  private double radius() {
    return radius;
  }

  double surfaceGravity() {
    return G * mass /
      (radius * radius);
  }

  double surfaceWeight(
    double otherMass
  ) {
    return otherMass *
      surfaceGravity();
  }
}

Scala:

enum Planet(
  mass: Double,
  radius: Double
):
  case Mercury extends
    Planet(3.303e+23, 2.4397e6)
  case Venus extends
    Planet(4.869e+24, 6.0518e6)
  case Earth extends
    Planet(5.976e+24, 6.37814e6)
    // more planets ...

  private final val G = 6.67300E-11

  def surfaceGravity =
    G * mass / (radius * radius)

  def surfaceWeight(otherMass: Double)
    = otherMass * surfaceGravity

예외와 오류 처리 (Exceptions and error handling)

이 절에서는 Java와 Scala의 예외 처리 차이를 다뤄요.

Java는 체크 예외(checked exceptions)를 사용해요

Java는 체크 예외를 사용하므로, Java 코드에서는 역사적으로 메서드에 throws 절과 함께 try/catch/finally 블록을 작성해 왔어요.

public int makeInt(String s)
throws NumberFormatException {
  // code here to convert a String to an int
}

Scala는 체크 예외를 사용하지 않아요

Scala 관용구는 이렇게 체크 예외를 쓰지 않는 거예요. 예외를 던질 수 있는 코드로 작업할 때는 try/catch/finally 블록으로 던져진 예외를 잡을 수 있지만, 그 이후로 어떻게 진행하는지는 달라요.

이걸 설명하는 가장 좋은 방법은, Scala 코드가 값을 반환하는 표현식의 연속으로 이루어진다는 거예요. 결과적으로 코드를 일련의 대수적 표현식으로 작성하게 돼요.

val a = f(x)
val b = g(a,z)
val c = h(b,y)

좋죠, 그냥 대수학이에요. 작은 문제를 풀기 위한 방정식을 만들고, 더 큰 문제를 풀기 위해 방정식을 결합해요.

그리고 아주 중요한 점 — 대수학 수업에서 기억하듯이 — 대수적 표현식은 단락(short circuit)되지 않아요 — 일련의 방정식을 망가뜨리는 예외를 던지지 않아요.

따라서 Scala에서 우리의 메서드는 예외를 던지지 않아요. 대신 Option 같은 타입을 반환해요. 예를 들어 이 makeInt 메서드는 가능한 예외를 잡아 Option 값을 반환해요.

def makeInt(s: String): Option[Int] =
  try
    Some(s.toInt)
  catch
    case e: NumberFormatException => None

Scala의 Option은 Java의 Optional 클래스와 비슷해요. 보시다시피, 문자열→정수 변환이 성공하면 IntSome 값 안에 담겨 반환되고, 실패하면 None 값이 반환돼요. SomeNoneOption의 서브타입이라서, 메서드는 Option[Int] 타입을 반환한다고 선언돼요.

makeInt가 반환하는 것 같은 Option 값이 있을 때, 여기서는 필요에 따라 다양한 방식으로 다룰 수 있어요. 이 코드는 가능한 접근 방식 하나를 보여줘요.

makeInt(aString) match
  case Some(i) => println(s"Int i = $i")
  case None => println(s"Could not convert $aString to an Int.")

Option은 Scala에서 흔히 사용되며, 표준 라이브러리의 많은 클래스에 내장되어 있어요. Try/Success/Failure, Either/Left/Right 같은 비슷한 클래스 세트들은 훨씬 더 많은 유연성을 제공해요.

Scala에서 오류와 예외를 다루는 더 자세한 내용은 함수형 오류 처리(Functional Error Handling) 절을 참고하세요.

Scala에만 있는 개념 (Concepts that are unique to Scala)

이것으로 Java와 Scala 언어 비교를 마칠게요.

Scala에는 현재 Java 11에 대응하는 개념이 없는 다른 개념들도 있어요. 여기에는 이런 것들이 포함돼요.

  • Scala의 문맥 추상화와 관련된 모든 것
  • 여러 Scala 메서드 기능:
    • 다중 파라미터 리스트
    • 파라미터 기본값
    • 메서드 호출 시 이름 붙은 인자(named arguments) 사용
  • 케이스 클래스(Java 14의 "레코드"와 같음), 케이스 object, 컴패니언 클래스와 object(도메인 모델링 챕터 참고)
  • 자신만의 제어 구조와 DSL을 만들 수 있는 능력
  • 톱레벨 정의
  • 패턴 매칭
  • match 표현식의 고급 기능
  • 타입 람다
  • 트레이트 파라미터
  • 불투명 타입 별칭(opaque type aliases)
  • 다중 우주 동등성
  • 타입 클래스
  • 인픽스 메서드
  • 매크로와 메타프로그래밍

더 알아보기 (Learn more)