자바와 상호작용하기
자바와 상호작용하기 (Interacting with Java)
도입 (Introduction)
이 섹션에서는 Scala에서 Java 코드를 사용하는 방법과, 그 반대로 Java에서 Scala 코드를 사용하는 방법을 살펴볼게요.
일반적으로 Scala에서 Java 코드를 쓰는 것은 매우 매끄러워요. Java 개념을 Scala로 변환하기 위해 Scala 유틸리티를 쓰고 싶은 경우는 아주 몇 군데뿐이에요. 예를 들면 다음과 같죠.
- Java 컬렉션 클래스
- Java
Optional클래스
반대로 Java 코드를 쓰면서 Scala 개념을 사용하고 싶다면, Scala 컬렉션과 Scala Option 클래스를 변환해야 해요.
다음 섹션들은 실무에서 필요하게 될 가장 흔한 변환을 보여줘요.
- Scala에서 Java 컬렉션 사용하기
- Scala에서 Java Optional 사용하기
- Scala에서 Java 인터페이스 확장하기
- Java에서 Scala 컬렉션 사용하기
- Java에서 Scala Option 사용하기
- Java에서 Scala 트레이트 사용하기
- Java 코드에서 예외를 던지는 Scala 메서드 처리하기
- Java에서 Scala varargs 파라미터 사용하기
- Scala 메서드를 Java에서 쓰기 위한 대체 이름 만들기
이 섹션의 Java 예시들은 Java 11 이상을 사용한다고 가정한다는 점을 참고하세요.
Scala에서 Java 컬렉션 사용하기 (How to use Java collections in Scala)
Scala 코드를 작성하다 보면 어떤 API가 Java 컬렉션 클래스(java.util 패키지의)를 요구하거나 반환할 때가 있어요. 그럴 때는 Java에서 쓰듯이 그 컬렉션을 직접 사용하거나 만들면 유효해요.
하지만 컬렉션에 대한 for 루프, 또는 map과 filter 같은 고차 함수를 적용하는 Scala답고 관용적인(idiomatic) 사용을 위해서는, Scala 컬렉션처럼 동작하는 프록시(proxy)를 만들 수 있어요.
이것이 어떻게 동작하는지 예를 볼게요. java.util.List[String]을 반환하는 이 API가 있다고 합시다.
public interface Foo {
static java.util.List<String> getStrings() {
return List.of("a", "b", "c");
}
}
Scala의 scala.jdk.CollectionConverters 객체에 있는 변환 유틸리티를 사용하면, 그 Java 리스트를 Scala Seq로 변환할 수 있어요.
// Scala 2
import scala.jdk.CollectionConverters._
import scala.collection.mutable
def testList() = {
println("Using a Java List in Scala")
val javaList: java.util.List[String] = Foo.getStrings()
val scalaSeq: mutable.Seq[String] = javaList.asScala
for (s <- scalaSeq) println(s)
}
// Scala 3
import scala.jdk.CollectionConverters.*
import scala.collection.mutable
def testList() =
println("Using a Java List in Scala")
val javaList: java.util.List[String] = Foo.getStrings()
val scalaSeq: mutable.Seq[String] = javaList.asScala
for s <- scalaSeq do println(s)
위 코드에서 javaList.asScala는 java.util.List를 Scala의 mutable.Seq 컬렉션에 적응시키는 래퍼(wrapper)를 만들어요.
Scala에서 Java Optional 사용하기 (How to use Java Optional in Scala)
Scala 코드에서 java.util.Optional 클래스를 사용하는 API와 상호작용할 때는, Java에서처럼 그냥 만들고 사용해도 괜찮아요.
하지만 for와 함께 쓰는 것 같은 관용적인 Scala 사용을 위해서는 Scala Option으로 변환할 수 있어요.
이를 보여주기 위해, Optional[String] 값을 반환하는 Java API를 볼게요.
public interface Bar {
static java.util.Optional<String> optionalString() {
return Optional.of("hello");
}
}
먼저 scala.jdk.OptionConverters 객체의 모든 멤버를 import 하고, 그다음 toScala 메서드로 Optional 값을 Scala Option으로 변환하면 돼요.
// Scala 2
import java.util.Optional
import scala.jdk.OptionConverters._
val javaOptString: Optional[String] = Bar.optionalString
val scalaOptString: Option[String] = javaOptString.toScala
// Scala 3
import java.util.Optional
import scala.jdk.OptionConverters.*
val javaOptString: Optional[String] = Bar.optionalString
val scalaOptString: Option[String] = javaOptString.toScala
Scala에서 Java 인터페이스 확장하기 (Extending Java interfaces in Scala)
Scala 코드에서 Java 인터페이스를 사용해야 한다면, 마치 그것들이 Scala 트레이트인 것처럼 확장하면 돼요. 예를 들어 이 세 개의 Java 인터페이스가 있다고 할게요.
public interface Animal {
void speak();
}
public interface Wagging {
void wag();
}
public interface Running {
// an implemented method
default void run() {
System.out.println("I’m running");
}
}
마치 트레이트를 쓰는 것처럼 Scala에서 Dog 클래스를 만들 수 있어요. run은 기본 구현을 갖고 있으므로, speak와 wag 메서드만 구현하면 됩니다.
// Scala 2
class Dog extends Animal with Wagging with Running {
def speak = println("Woof")
def wag = println("Tail is wagging")
}
def useJavaInterfaceInScala = {
val d = new Dog()
d.speak
d.wag
d.run
}
// Scala 3
class Dog extends Animal, Wagging, Running:
def speak = println("Woof")
def wag = println("Tail is wagging")
def useJavaInterfaceInScala =
val d = Dog()
d.speak
d.wag
d.run
또한 Scala에서는 빈 파라미터 리스트로 정의된 Java 메서드를 .wag()처럼 Java에서 하듯 호출하거나, 괄호 없이 .wag로 호출할 수도 있다는 점을 주목하세요.
Java에서 Scala 컬렉션 사용하기 (How to use Scala collections in Java)
Java 코드에서 Scala 컬렉션 클래스를 사용해야 한다면, scala.jdk.javaapi.CollectionConverters 객체의 메서드를 Java 코드에서 사용해서 변환을 수행하면 돼요.
예를 들어 Scala API가 이런 List[String]을 반환한다고 가정해 볼게요.
// Scala 2
object Baz {
val strings: List[String] = List("a", "b", "c")
}
// Scala 3
object Baz:
val strings: List[String] = List("a", "b", "c")
Java 코드에서 그 Scala List에 이렇게 접근할 수 있어요.
import scala.jdk.javaapi.CollectionConverters;
// access the `strings` method with `Baz.strings()`
scala.collection.immutable.List<String> xs = Baz.strings();
java.util.List<String> listOfStrings = CollectionConverters.asJava(xs);
for (String s: listOfStrings) {
System.out.println(s);
}
그 코드는 줄일 수 있지만, 전체 과정이 어떻게 동작하는지 보여주기 위해 전체 단계를 보여줬어요. Baz에 strings라는 필드가 있지만 Java에서는 그 필드가 메서드로 보이므로 .strings()로 괄호와 함께 호출해야 한다는 점을 꼭 주목하세요.
Java에서 Scala Option 사용하기 (How to use Scala Option in Java)
Java 코드에서 Scala Option을 사용해야 한다면, Scala scala.jdk.javaapi.OptionConverters 객체의 toJava 메서드를 사용해 Option을 Java Optional 값으로 변환할 수 있어요.
예를 들어 Scala API가 이런 Option[String]을 반환한다고 가정해 볼게요.
// Scala 2
object Qux {
val optString: Option[String] = Option("hello")
}
// Scala 3
object Qux:
val optString: Option[String] = Option("hello")
그러면 Java 코드에서 그 Scala Option에 이렇게 접근할 수 있어요.
import java.util.Optional;
import scala.Option;
import scala.jdk.javaapi.OptionConverters;
Option<String> scalaOptString = Qux.optString();
Optional<String> javaOptString = OptionConverters.toJava(scalaOptString);
그 코드는 줄일 수 있지만, 전체 과정이 어떻게 동작하는지 보여주기 위해 전체 단계를 보여줬어요. Qux에 optString이라는 필드가 있지만 Java에서는 그 필드가 메서드로 보이므로 .optString()로 괄호와 함께 호출해야 한다는 점을 꼭 주목하세요.
Java에서 Scala 트레이트 사용하기 (How to use Scala traits in Java)
Java 8부터는 트레이트가 구현된 메서드를 갖고 있더라도 Scala 트레이트를 Java 인터페이스처럼 사용할 수 있어요. 예를 들어, 구현된 메서드를 가진 트레이트와 인터페이스만 가진 트레이트, 이 두 Scala 트레이트가 있다고 할게요.
// Scala 2
trait ScalaAddTrait {
def sum(x: Int, y: Int) = x + y // implemented
}
trait ScalaMultiplyTrait {
def multiply(x: Int, y: Int): Int // abstract
}
// Scala 3
trait ScalaAddTrait:
def sum(x: Int, y: Int) = x + y // implemented
trait ScalaMultiplyTrait:
def multiply(x: Int, y: Int): Int // abstract
Java 클래스는 그 두 인터페이스를 모두 구현하고 multiply 메서드를 정의할 수 있어요.
class JavaMath implements ScalaAddTrait, ScalaMultiplyTrait {
public int multiply(int a, int b) {
return a * b;
}
}
JavaMath jm = new JavaMath();
System.out.println(jm.sum(3,4)); // 7
System.out.println(jm.multiply(3,4)); // 12
Java 코드에서 예외를 던지는 Scala 메서드 처리하기 (How to handle Scala methods that throw exceptions in Java code)
Scala 프로그래밍 관용구로 Scala 코드를 작성한다면, 예외를 던지는 메서드를 절대 쓰지 않을 거예요. 하지만 어떤 이유로 예외를 던지는 Scala 메서드를 갖고 있고, Java 개발자들이 그 메서드를 쓸 수 있게 하고 싶다면, Scala 메서드에 @throws 어노테이션을 추가해서 Java 사용자들이 던질 수 있는 예외를 알 수 있게 해줘야 해요.
예를 들어, 이 Scala exceptionThrower 메서드는 Exception을 던진다고 선언하도록 어노테이션되어 있어요.
// Scala 2
object SExceptionThrower {
@throws[Exception]
def exceptionThrower =
throw new Exception("Idiomatic Scala methods don’t throw exceptions")
}
// Scala 3
object SExceptionThrower:
@throws[Exception]
def exceptionThrower =
throw Exception("Idiomatic Scala methods don’t throw exceptions")
그 결과 Java 코드에서 예외를 처리해야 해요. 예를 들어, 이 코드는 제가 예외를 처리하지 않아서 컴파일되지 않아요.
// won’t compile because the exception isn’t handled
public class ScalaExceptionsInJava {
public static void main(String[] args) {
SExceptionThrower.exceptionThrower();
}
}
컴파일러는 이 오류를 표시해요.
[error] ScalaExceptionsInJava: unreported exception java.lang.Exception;
must be caught or declared to be thrown
[error] SExceptionThrower.exceptionThrower()
이건 좋은 일이에요. 바로 우리가 원하는 거죠. 어노테이션이 Java 컴파일러에게 exceptionThrower가 예외를 던질 수 있다는 걸 알려 주니까요. 이제 Java 코드를 쓸 때 try 블록으로 예외를 처리하거나 Java 메서드가 예외를 던진다고 선언해야 해요.
반대로 Scala exceptionThrower 메서드에서 어노테이션을 빼버리면, Java 코드는 컴파일됩니다. 이것은 아마 우리가 원하는 게 아니에요. Java 코드가 Scala 메서드가 예외를 던진다는 것을 고려하지 않을 수 있으니까요.
Java에서 Scala varargs 파라미터 사용하기 (How to use Scala varargs parameters in Java)
Scala 메서드가 varargs 파라미터를 갖고 있고 Java에서 그 메서드를 쓰고 싶다면, Scala 메서드를 @varargs 어노테이션으로 표시하세요. 예를 들어, 이 Scala 클래스의 printAll 메서드는 String* varargs 필드를 선언해요.
// Scala 2
import scala.annotation.varargs
object VarargsPrinter {
@varargs def printAll(args: String*): Unit = args.foreach(println)
}
// Scala 3
import scala.annotation.varargs
object VarargsPrinter:
@varargs def printAll(args: String*): Unit = args.foreach(println)
printAll이 @varargs 어노테이션으로 선언되었기 때문에, 이 예시에서 보이듯이 가변 개수의 파라미터로 Java 프로그램에서 호출할 수 있어요.
public class JVarargs {
public static void main(String[] args) {
VarargsPrinter.printAll("Hello", "world");
}
}
이 코드를 실행하면 다음과 같은 출력이 나와요.
Hello
world
Scala 메서드를 Java에서 쓰기 위한 대체 이름 만들기 (Create alternate names to use Scala methods in Java)
Scala에서는 기호 문자를 사용한 메서드 이름을 만들고 싶을 수 있어요.
def +(a: Int, b: Int) = a + b
그 메서드 이름은 Java에서 잘 동작하지 않을 거예요. 하지만 Scala에서는 targetName 어노테이션으로 메서드의 “대체” 이름을 제공할 수 있어요. 그 이름은 Java에서 사용될 때의 메서드 이름이 됩니다.
// Scala 2
import scala.annotation.targetName
object Adder {
@targetName("add") def +(a: Int, b: Int) = a + b
}
// Scala 3
import scala.annotation.targetName
object Adder:
@targetName("add") def +(a: Int, b: Int) = a + b
이제 Java 코드에서 별칭(alias) 이름 add를 사용할 수 있어요.
int x = Adder.add(1,1);
System.out.printf("x = %d\n", x);