31.1 스칼라를 자바에서 사용하기

< 일반적인 규칙 >

: 스칼라는 표준 자바 바이트 코드로 컴파일되도록 구현되어 있다. 가능한 한, 스칼라의 특징을 그에 대응하는 자바 기능으로 직접 변환한다. 예를 들어 클래스, 메소드, 문자열, 예외는 모두 자바와 동일한 바이트 코드로 컴파일 된다.

* 이를 위해 컴파일 시간에 오버로드된 메소드를 찾아낸다.

: 트레이트나 제네릭 같은 경우 자바와 다른 부분이 있어 자신만의 설계를 따르고 자바에 존재하는 구조를 조합해 인코딩한다.

 

< 값 타입 >

: Int 같은 값 타입은 자바 int로 변환해 더 나은 성능을 얻으려 노력하거나 컴파일러가 변환하는 데이터가 Int인지 다른 데이터 타입인지 확신할 수 없을 때는 java.lang.Integer로 값 타입을 감싼다. (* List[Any]에 Int만 들어갈 수도 있지만 컴파일러로서는 이를 알 방법이 없다.)

 

< 싱글톤 객체 >

: 싱글톤 객체를 정적 메소드와 인스턴스 메소드를 조합해 표현하고 객체 이름 끝에 달러 표시를 붙인 자바 클래스로 만든다.

" App 싱글톤 객체가 있을 때 "

object App {
  def main(args: Array[String]) {
    println("Hello, world!")
  }
}

- App 클래스가 존재할 경우

$ javap App$
public final class App$ extends java.lang.Object             // 클래스 이름 옆에 $를 붙임, 싱글톤 객체의 모든 메소드와 필드 존재
implements scala.ScalaObject{
  public static final App$ MODULE$;                   // 동반 클래스에서 싱글톤 내부 함수에 접근하도록 싱글톤 객체 정적 필드 정의
  public static {};
  public App$();
  public void main(java.lang.String[]);
  public int $tag();
}

- App 클래스가 존재하지 않을 경우 App$ 클래스뿐만 아니라 App 클래스 별도로 생성한다.

$ javap App
Compiled from "App.scala"            
public final class App extends java.lang.Object{       
  public static final int $tag();
  public static final void main(java.lang.String[]);
}

 

< 트레이트를 인터페이스로 만들기 >

: 트레이트를 컴파일하면 같은 이름으로 자바 인터페이스를 만든다.

 

31.2 애노테이션

< 표준 애노테이션의 추가 효과 >

- 컴파일러는 @deprecated라고 표시된 메소드나 클래스에 자바의 사용금지 애노테이션을 추가한다.

- @volatile로 표시된 필드는 자바 volatile 수식자가 붙는다.

- @serializable 클래스는 자바의 Serializable 인터페이스가 추가된다.

- @SerialVersionUID(1234L) 같은 애노테이션은 "private final static long SerialVersionUID = 1234L" 자바 필드 정의로 변환된다.

- @transient로 표시된 변수에는 자바의 transient 수식자가 붙는다.

 

< 발생하는 예외 >

: 스칼라는 자바 메소드에 있는 throws 선언과 동등한 것이 없고 모든 스칼라 메소드는 예외를 던지지 않는 자바 메소드로 번역된다.

: 자바를 위해 어떤 예외를 던질지 표시하는 스칼라 코드가 필요할 때는 @throws 애노테이션을 추가한다.

import java.io._
class Reader(fname: String) {
  private val in =
    new BufferedReader(new FileReader(fname))

  @throws(classOf[IOException])      // read 메소드는 IOException을 던진다.
  def read() = in.read()
}

 

$ javap Reader
Compiled from "Reader.scala"
public class Reader extends java.lang.Object implements
scala.ScalaObject{
  public Reader(java.lang.String);
  public int read()       throws java.io.IOException;
  public int $tag();
}

 

< 자바 애노테이션 >

: 자바 프레임워크의 기존 애노테이션을 스칼라 코드에서 바로 사용할 수 있다.

import org.junit.Test
import org.junit.Assert.assertEquals

class SetTest {

  @Test
  def testMultiAdd {
    val set = Set() + 1 + 2 + 3 + 1 + 2 + 3
    assertEquals(3, set.size)
  }
}

 

< 새로운 애노테이션 만들기 >

: 애노테이션을 만들기 위해서는 자바 표현을 사용해 작성하고 javac로 컴파일해야 한다.

: 스칼라 컴파일러가 애노테이션 작성을 지원하지 않는 이유는 스칼라 컴파일러가 자바 애노테이션의 모든 가능성을 완벽히 다룰 수 없기 때문

" 애노테이션 정의, Ignore.java "

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore { }

 

" Tests.scala "

object Tests {
  @Ignore
  def testData = List(0, 1, -1, 5, -5)

  def test1 {
    assert(testData == (testData.head :: testData.tail))
  }

  def test2 {
    assert(testData.contains(testData.head))
  }
}

 

" FindTests.scala "

object FindTests {
  def main(args: Array[String]) {
    for {
      method <- Tests.getClass.getMethods
      if method.getName.startsWith("test")
      if method.getAnnotation(classOf[Ignore]) == null
    } {
      println("found a test method: " + method)
    }
  }
}

 

$ javac Ignore.java
$ scalac Tests.scala
$ scalac FindTests.scala
$ scala FindTests
found a test method: public void Tests$.test2()
found a test method: public void Tests$.test1()

 

31.3 와일드카드 타입

: 스칼라에서 자바 코드에 접근할 수 있어야 하기 때문에 모든 자바 타입은 스칼라에 동등한 타입이 존재한다.

: Iterator<?>나 Iterator<? extends Component> 같은 자바 와일드 카드 타입은 스칼라의 와일드 카드 타입이라고 하는 다른 유형의 타입으로 처리한다.

: Iterator<?>는 Iterator[_]와 동등하고 원소 타입이 알려지지 않은 Iterator를 표현하는 타입이라고 말한다.

: 위치 표시자를 사용하면서 동시에 상위/하위 바운드 지정이 가능하다.( Iterator[_ <: Component]

" 와일드 카드를 사용하는 자바 클래스 "

public class Wild {
  Collection<?> contents() {
    Collection stuff = new Vector();
    stuff.add("a");
    stuff.add("b");
    stuff.add("see");
    return stuff;
  }
}

 

scala> val contents = (new Wild).contents
contents: java.util.Collection[_] = [a, b, see]    // 와일드 카드 타입

 

scala> contents.size()
res0: Int = 3


import scala.collection.mutable.Set
val iter = (new Wild).contents.iterator
val set = Set.empty[???]     // set이 어떤 타입이어야 하는 지 알 수 없다.
while (iter.hasMore)
  set += iter.next()

: set이 어떤 타입인지 알 수 없다.

=> 와일드 카드 타입을 메소드에 전달할 때 메소드에 위치 표시자에 대한 파라미터를 추가한다. 와일드 카드 타입에 이름이 붙었기 때문에 원할 때 원하는 만큼 그 이름을 사용할 수 있다.

=> 메소드에서 와일드 카드 타입을 반환하지 않고, 위치 표시자 타입 각각에 대한 추상 멤버를 갖는 객체를 반환한다.

import scala.collection.mutable.Set
import java.util.Collection

abstract class SetAndType {
  type Elem
  val set: Set[Elem]
}

def javaSet2ScalaSet[T](jset: Collection[T]): SetAndType = {
  val sset = Set.empty[T]  // now T can be named!

  val iter = jset.iterator
  while (iter.hasNext)
    sset += iter.next()

  return new SetAndType {
    type Elem = T
    val set = sset
  }
}

 

31.4 스칼라와 자바를 함께 컴파일하기

: 보통 자바 코드에 의존적인 스칼라 코드를 컴파일하려면, 먼저 자바 코드를 컴파일해서 클래스 파일로 만든다. 자바 코드의 클래스 파일을 클래스 경로에 넣어서 스칼라 코드를 빌드한다.

: 하지만 자바 코드가 다시 스칼라 코드를 참조한다면 이미 자바 코드가 클래스 파일로 컴파일됬기 때문에 불가능하다.

=> 스칼라는 자바 클래스 파일을 참조해 컴파일하는 방법뿐만 아니라 자바 소스 코드를 참조해 컴파일 하는 방법을 지원한다.

=> 스칼라 컴파일러는 자바 파일을 컴파일 하지 않고 자바 소스 파일을 사용해 스칼라 코드를 먼저 컴파일하고 그런 다음 자바 코드를 스칼라 클래스 파일을 사용해 컴파일한다.

 

31.5 자바 8과 스칼라 2.12의 통합

< 람다 표현식과 SAM(single abstract method) 타입 >

: 스칼라 2.12에서 자바 8과 관련해 눈에 띄는 개선은 자바 8에서 람다 표현식으로 익명 클래스 인스턴스 식을 간략히 표현하는 것처럼 익명 클래스 인스턴스가 들어가는 곳에 스칼라의 함수 리터럴을 사용할 수 있다.

" 자바 8 이전 메소드 "

JButton button = new JButton();
button.addActionListener(
  new ActionListener {
    public void actionPerformed(ActionEvent event){
      System.out.println("pressed!")
    }
  }
);

 

" 자바 8 이후 SAM만 있는 클래스나 인터페이스의 인스턴스가 필요한 곳에 람다 표현식 사용 가능 "

JButton button = new JButton();
button.addActionListener(
  event -> System.out.println("pressed!")
);

 

" 스칼라는 함수 리터럴을 통해 나타낼 수 있다. "

val button = new JButton
button.addActionListener(
  _ => println("pressed!")
)

: ActionEvent => Unit 함수 타입을 ActionListener로 변환하는 암시적인 변환을 정의하면 위와 같이 할 수 있지만 스칼라 2.12에서는 암시적인 변환이 없더라도 함수 리터럴을 사용할 수 있다. 자바 8과 마찬가지로 SAM만을 선언한 클래스나 트레이트의 인스턴스가 필요한 곳에 함수 타입을 사용하도록 허용했기 때문

 

< 스칼라 2.12에서 자바 8 스트림 사용하기 >

: 자바의 Stream은 java.util.function.IntUnaryOperator를 인자로 받는 map 메소드를 제공하는 함수형 데이터 구조다.

: IntUnaryOperator가 SAM 타입이기 때문에 스칼라 2.12에서는 함수 리터럴을 사용해 더 간결하게 처리할 수 있다.

: 함수 타입을 만들어내는 표현식이 모두 SAM 타입으로 변활될 수 있는 게 아니고 오직 함수 리터럴만 SAM 타입으로 변환된다.

: f를 정의하면서 그것의 타입을 Stream.map이 원하는 타입인 IntUnaryOperator로 지정 가능

: 스칼라 함수 타입은 트레이트로 정의되고 그 안에는 구체적인 메소드들이 있지만 트레이트를 디폴트 메소드가 있는 자바 인터페이스로 컴파일하기 때문에 자바에서 볼 때 그것은 apply 추상 메서드를 가지는 SAM이 된다.

 

 

+ Recent posts