IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  자바  >

바쁜 자바 프로그래머를 위한 스칼라 입문: 구현 상속

스칼라의 상속에서 객체가 함수를 만나다

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Ted Neward, 사장, Neward & Associates

옮긴이: 김도형 dwkorea@kr.ibm.com

2008 년 9 월 02 일

스칼라는 자바(Java™) 언어와 동등한 정도의 구현 상속을 지원합니다. 하지만 스칼라의 상속은 몇몇 특이한 점이 있습니다. 이번 글에서는 함수 언어 스타일과 객체 지향 언어 스타일을 섞으면서도 자바 플랫폼의 상속 모델에 정확히 대응되는 스칼라 식의 다형성(polymorphism)을 소개합니다.

지난 20년 동안 대체로 객체 지향 언어 설계의 핵심은 상속 개념이었다. 비주얼 베이직처럼 상속을 지원하지 않는 언어는 중요 업무에는 맞지 않는 "장난감 언어(toy language)"로 폄하되었다. 하지만 상속을 지원하는 언어라도 각기 방식이 달라 많은 논쟁이 벌어졌다. (C++의 설계자가 생각했듯) 다중 상속이 정말 필요한가? 아니면 (C#과 자바 언어의 설계자가 생각했듯) 쓸모 없고 나쁜 개념인가? 지난 글에서 스칼라의 특성(trait)을 소개하면서 언급했듯 루비와 스칼라는 다중 상속에 있어 중도를 택한 새로운 언어다(참고자료를 보라).

이 연재에 대해

저자인 Ted Neward는 이 연재를 통해 여러분에게 스칼라 프로그래밍 언어를 심층적으로 소개한다. 이 새 developerWorks 연재를 통해 최근의 스칼라를 둘러싼 떠들썩한 호평의 실체를 살펴보고, 스칼라의 언어적 특성의 일부가 실제 어떻게 사용되는지도 배우게 될 것이다. 비교가 필요할 때는 항상 스칼라 코드와 자바 코드를 나란히 보일 예정이다. 하지만 곧 알게 되겠지만 스칼라에 있는 많은 요소는 자바 언어와 직접적인 관계가 없는 것들이고, 이런 부분이 스칼라를 매력적으로 만든다. 그렇다면 자바 코드로도 할 수 있다면 왜 힘들여 스칼라를 배울까?

다른 모든 훌륭한 언어처럼 스칼라도 구현 상속(implementation inheritance)을 지원한다(참고자료를 보라). 자바 언어는 베이스 클래스를 확장해 새로운 메서드와 필드를 추가할 수 있는 단일 구현 상속 모델을 따른다. 몇몇 문법 차이가 있기는 하지만 스칼라의 구현 상속도 자바 언어와 같아 보인다. 차이점은 스칼라가 객체와 함수 언어 설계를 융합하는 방식에 있다. 이번 글에서는 여기에 대해 다뤄보겠다.

구식의 보통 스칼라 객체(Plain Old Scala Object)

이 연재에서 쭉 그랬듯 스칼라의 상속 시스템을 살펴 보는 시작점으로 Person 클래스를 사용하려고 한다. Listing 1은 Person 클래스를 정의한 것이다.


Listing 1. Person 클래스 정의
                
// 스칼라 코드
class Person(val firstName:String, val lastName:String, val age:Int)
{
  def toString = "[Person: firstName="+firstName+" lastName="+lastName+
                         " age="+age+"]"
}

Person은 읽기 전용 필드를 세 개 정의한 아주 단순한 POSO(Plain Old Scala Object)다. 기억할지 모르겠는데 읽기 전용 필드를 읽고 쓸 수 있게 하려면 주 생성자에서 값(value)을 변수(variable)로 바꾸기만 하면 된다.

하여튼 Listing 2처럼 Person 타입을 사용하는 것도 아주 간단하다.


Listing 2. PersonApp
                
// 스칼라 코드
object PersonApp
{
  def main(args : Array[String]) : Unit =
  {
    val bindi = new Person("Tabinda", "Khan", 38)
    System.out.println(bindi)
  }
}

어마어마한 코드는 아니지만 시작점으로는 충분하다.




위로


스칼라의 추상 메서드(abstract method)

이 시스템을 개발해 나가다 보니 Person 클래스에는 사람이기 위해 매우 중요한 뭔가가 빠져 있다는 점이 명백해졌다. "뭔가를 하는" 동작 말이다. 대다수 사람은 그저 존재하고 공간을 차지한다는 것보다는 살면서 무엇을 하느냐로 자신을 규정짓는다. 그래서 Listing 3에 보인 것처럼 Person에 어떤 목적을 주는 새로운 메서드를 하나 추가할 것이다.


Listing 3. 뭔가를 하자!
                
// 스칼라 코드
class Person(val firstName:String, val lastName:String, val age:Int)
{
  override def toString = "[Person: firstName="+firstName+" lastName="+lastName+
                          " age="+age+"]"

  def doSomething = // uh.... what?
}

여기서 문제가 있는데 Person들은 정확히 뭘 할까? 어떤 Person들은 페인트 칠을 하고, 혹자는 노래하고, 혹자는 코드를 작성하고, 혹자는 비디오 게임을 한다. 또 어떤 사람은 뭐든 절제한다(10대 자식을 둔 부모에게 물어보라). 따라서 이를 반영하려면 이런 활동을 곧바로 Person 클래스 자체에 포함하려고 하기보다는 Listing 4에 보인 것처럼 Person서브클래스를 만들어야 한다.


Listing 4. Person은 거의 하는 일이 없다
                
// 스칼라 코드
class Person(val firstName:String, val lastName:String, val age:Int)
{
  override def toString = "[Person: firstName="+firstName+" lastName="+lastName+
                          " age="+age+"]"

  def doSomething = // 음…, 뭐?
}

class Student(firstName:String, lastName:String, age:Int)
  extends Person(firstName, lastName, age)
{
  def doSomething =
  {
    System.out.println("엄마, 맹세컨대 나 공부 열심히 하고 있어요! (그 맥주 이리 줘, 친구!)")
  }
}

이 코드를 컴파일해 보면 컴파일이 되지 않는다. 이는 Person.doSomething 메서드의 정의가 제대로 되지 않았기 때문이다. 메서드에 온전한 몸체가 있거나(아마 상속 받은 클래스에서 재정의해야 한다는 것을 나타내기 위해 예외를 일으키는 코드) 자바 코드의 추상 메서드처럼 아예 몸체가 없어야 한다. Listing 5에 추상 메서드를 사용해 봤다.


Listing 5. 추상 클래스 Person
                
// 스칼라 코드
abstract class Person(val firstName:String, val lastName:String, val age:Int)
{
  override def toString = "[Person: firstName="+firstName+" lastName="+lastName+
                          " age="+age+"]"

  def doSomething; // 세미콜론을 주목하자. 여전히 생략할 수 있지만
                   // 개인적으로는 스타일상 써 주는 쪽을 선호한다.
}

class Student(firstName:String, lastName:String, age:Int)
  extends Person(firstName, lastName, age)
{
  def doSomething =
  {
    System.out.println("엄마, 맹세컨대 나 공부 열심히 하고 있어요! (그 맥주 이리 줘, 친구!)")
  }
}

여기서 Person 클래스에 abstract 키워드가 붙었다는 점을 주목하자. abstract는 "이 클래스는 추상 클래스예요"라고 컴파일러에 알려준다. 이런 면에서 스칼라와 자바 언어는 똑같다.

객체, 함수를 만나다

스칼라는 객체와 함수 언어 스타일을 융합했기 때문에 사실 앞서 살펴본 Person 클래스를 서브타입을 정의하지 않고 모델링할 수도 있었다. 이는 약간 꼬인 발상이기는 하지만, 스칼라에 있어 두 스타일의 통합과 거기서 오는 아주 흥미로운 발상을 잘 보여준다.

이전 글에서 설명했듯 스칼라는 함수도 Int, Float, Double 같은 여타 값과 똑같이 값으로 취급한다. 여기서는 이 사실을 이용해 Person 클래스가, 서브클래스에서 재정의되어야 하는 doSomething이라는 메서드 대신, 호출되고 대체되고 확장되는 함수 값을 가지도록 모델링할 수 있다. Listing 6을 살펴 보자.


Listing 6. 하는 일이 많은 Person 클래스
                
// 스칼라 코드
class Person(val firstName:String, val lastName:String, val age:Int)
{
  var doSomething : (Person) => Unit = 
    (p:Person) => System.out.println("나는 " + p + "(이)고 아직 별다른 일을 하지 않습니다!");
    
  def work() =
    doSomething(this)
    
  override def toString = "[Person: firstName="+firstName+" lastName="+lastName+
                          " age="+age+"]"
}

object App
{
  def main(args : Array[String]) =
  {
    val bindi = new Person("Tabinda", "Khan", 38)
    System.out.println(bindi)
    
    bindi.work()
    
    bindi.doSomething =
      (p:Person) => System.out.println("나는 교과서를 편집합니다")
      
    bindi.work()
    
    bindi.doSomething =
      (p:Person) => System.out.println("나는 HTML 책을 씁니다")
      
    bindi.work()
  }
}

함수를 주된 모델링 수단으로 사용하는 발상은 루비, 그루비(Groovy), ECMAScript(자바스크립트) 같은 동적 언어는 물론이고 많은 함수 언어에서 사용하는 일반적인 기법이다. 다른 언어에서 이 기법을 사용할 수 있기는 하지만(C++의 함수 혹은 멤버 함수 포인터를 사용하거나, 자바에서 인터페이스를 구현한 이름 없는 내부 클래스를 사용하면 가능하다) 스칼라(루비, 그루비, ECMAScript 등도 마찬가지)로 할 때보다 많은 작업이 필요하다. 이는 함수 언어 프로그래머가 흔히 사용하는 "고차 함수(higher order function)" 개념의 확장이다(고차 함수에 대해서는 참고자료를 보라).

함수를 값으로 보는 스칼라의 관점 덕에 런타임에 기능을 바꿔야 하는 경우 언제라도 함수 값을 사용할 수 있다. 혹자는 이 접근법을 객체의 역할을 정적 타입 계층보다 런타임 값으로 더 잘 표현할 때 사용하는(예를 들어 Person의 현재 고용 상태), Gang of Four의 전략 패턴(Strategy pattern)의 변형인 역할 패턴(Role pattern)이라고 생각할지도 모르겠다.




위로


타입 계층상의 생성자들

자바 코딩을 할 때 베이스 클래스의 필드를 초기화하기 위해 종종 상속된 클래스의 생성자에 넘어온 인자를 베이스 클래스 생성자로 넘겨야 하는 경우가 있었음을 기억할 것이다. 스칼라에서는 주 생성자가 "기존" 클래스 멤버가 아니라 클래스 선언에 나타나기 때문에 베이스 클래스로 인자를 넘기는 일이 전혀 새로운 일이 되었다.

스칼라에서는 주 생성자의 인자가 class가 정의되는 그 행에 명시된다. 하지만 이 인자에 val 수정자(modifier)를 사용하면 클래스에 쉽게 접근자(accessor)를 생성할 수 있다(var를 사용하면 변경자(mutator)도 생성된다).

따라서 Listing 5에 보인 스칼라 클래스인 Person은 javap로 보면 Listing 7의 자바 클래스로 변환된다.


Listing 7. javap로 본 Person 클래스
                
// javap로 본 내용
C:\Projects\scala-inheritance\code>javap -classpath classes Person
Compiled from "person.scala"
public abstract class Person extends java.lang.Object implements scala.ScalaObje
ct{
    public Person(java.lang.String, java.lang.String, int);
    public java.lang.String toString();
    public abstract void doSomething();
    public int age();
    public java.lang.String lastName();
    public java.lang.String firstName();
    public int $tag();
}    

JVM의 기본 규칙은 여전히 지켜지고 있다. 즉, 언어가 어떤 규칙을 가지고 있든 Person의 서브클래스는 생성 시 뭔가를 베이스 클래스로 넘겨야 한다(사실 이것도 전적으로 맞는 이야기는 아니다. 하지만 언어가 이 규칙을 건너 뛰려고 하면 JVM이 약간 투덜대기 때문에 대부분의 언어는 여러 방법으로 이 규칙을 따른다). 스칼라의 경우 당연하지만, JVM을 만족시키기 위해서뿐만 아니라 자바로 작성된 베이스 클래스를 만족시키기 위해 이 규칙을 따라야 한다. 따라서 스칼라는 어떤 점에서는 접근자와 변경자를 베이스 클래스에 생성하는 문법을 유지하면서도 서브클래스가 베이스 클래스를 호출하는 문법을 지원해야 한다.

이를 좀 더 구체적으로 설명하기 위해 Listing 5Student 클래스를 그렇게 작성했다고 가정해 보자.


Listing 8. 잘못된 Student 클래스
                
// 스칼라 코드
// 컴파일되지 않음!!!
class Student(val firstName:String, val lastName:String, val age:Int)
  extends Person(firstName, lastName, age)
{
  def doSomething =
  {
    System.out.println("엄마, 맹세컨대 나 공부 열심히 하고 있어요! (그 맥주 이리 줘, 친구!)")
  }
}

이 경우 컴파일러는 오류를 토해 내는데 이는 Student 클래스에 새로운 메서드(firstName, lastName, age)를 정의하려고 했기 때문이다. 이 메서드들은 Person에 정의된 같은 이름의 메서드와 충돌이 날 것이고, 반면 스칼라 컴파일러는 이 코드가 베이스 클래스 메서드를 재정의하려고 한다는 사실을 알 수 없다(이 베이스 클래스 메서드 이면에 구현과 필드를 숨기려는 의도가 있기 때문에 그 자체가 바람직하지 않을 수도 있다). 곧 베이스 클래스의 메서드를 재정의하는 법을 다루겠지만 이 시점에서 핵심은 그게 아니다.

또한 스칼라에서 Person 클래스 생성자에 넘겨진 인자가 Student에 넘어가는 인자와 일대일로 대응할 필요가 없다는 점도 유의하기 바란다. 이는 자바 생성자도 마찬가지다. 여기서 일대일로 맞춰둔 것은 쉽게 읽을 수 있게 하기 위한 것이다. 또한 자바 언어와 마찬가지로 Student 클래스에 생성자 인자를 추가할 수도 있다. Listing 9를 보자.


Listing 9. Student 클래스의 추가 인자
                
// 스칼라 코드
class Student(firstName:String, lastName:String, age:Int, val subject:String)
  extends Person(firstName, lastName, age)
{
  def doSomething =
  {
    System.out.println("엄마, 맹세컨대 나 공부 열심히 하고 있어요! (그 맥주 이리 줘, 친구!)")
  }
}

여기서 다시 한번 최소 상속과 클래스 관계에 있어서는 스칼라 코드와 자바 코드가 비슷하다는 것을 알 수 있다.




위로


문법적 차이점

지금까지 본 미묘한 문법적 차이점에 대해 의아해 했을지도 모르겠다. 결국 스칼라는 자바 언어처럼 필드와 메서드를 구분하지 않는다. 이는 스칼라 프로그래머가 베이스 클래스를 사용하는 쪽으로부터 필드와 메서드 간 차이를 쉽게 감출 수 있게 하기 위한 의도적인 설계다. Listing 10을 살펴 보자.


Listing 10. 나는 어떤 사람인가?
                
// 스칼라 코드
abstract class Person(val firstName:String, val lastName:String, val age:Int)
{
  def doSomething
  
  def weight : Int
    
  override def toString = "[Person: firstName="+firstName+" lastName="+lastName+
                          " age="+age+"]"
}

class Student(firstName:String, lastName:String, age:Int, val subject:String)
  extends Person(firstName, lastName, age)
{
  def weight : Int =
    age // students are notoriously skinny

  def doSomething =
  {
    System.out.println("엄마, 맹세컨대 나 공부 열심히 하고 있어요! (그 맥주 이리 줘, 친구!)")
  }
}

class Employee(firstName:String, lastName:String, age:Int)
  extends Person(firstName, lastName, age)
{
  val weight : Int = age * 4 // 피고용인들은 전혀 마른 편이 아니다.

  def doSomething =
  {
    System.out.println("여보, 나 열심히 일하고 있어요. 맹세해요! (그 맥주 이리 줘, 친구!)")
  }
}

여기서 어떻게 weight가 인자 없이 Int를 돌려주도록 정의됐는지를 유의하자. 이게 바로 "인자 없는 메서드"다. 이는 보기에 자바 언어의 "프로퍼티" 메서드와 아주 비슷하기 때문에 스칼라에서는 weight를 (Student에서처럼) 메서드나 (Employee에서처럼) 필드/접근자 어느 쪽으로든 정의할 수 있다. 이런 문법적 결정은 추상 클래스의 서브클래스를 정의할 때 어느 정도 유연성을 준다. 자바에서는 모든 필드가, 심지어 같은 클래스 내에서도, 해당하는 get/set 메서드를 통해 사용될 때만 똑 같은 유연성을 담보할 수 있다. 맞든 틀리든 그렇게 코딩하는 자바 프로그래머는 그다지 많지 않다. 따라서 이런 유연성을 활용하는 경우도 드물다. 한 가지 더 첨언하면 스칼라 방식은 public 멤버는 물론이고 숨겨진/private 멤버에도 똑같이 쉽게 먹힌다.




위로


@Override에서 override로

서브클래스에서 베이스 클래스 중 하나에서 정의된 메서드의 동작을 바꿔야 하는 경우가 종종 있다. 자바 코드에서는 이런 경우 같은 이름과 시그너처(signature)를 가진 새 메서드를 서브클래스에 추가한다. 이 방법의 문제점은 오타나 시그너처의 미묘한 모호성이 조용히 문제를 일으킬 수 있다는 점이다. 즉, 컴파일은 되지만 런타임에는 의도한 대로 동작하지 않을 수 있다.

이를 해결하기 위해 자바 5 컴파일러에는 @Override 애노테이션(annotation)이 추가되었다. @Override는 서브클래스에 추가된 메서드가 실제 베이스 클래스 메서드를 재정의(override)했다는 것을 javac에 알려주는 역할을 한다. 스칼라에서는 override가 언어에 추가되어 빼먹으면 컴파일 오류가 발생한다. 따라서 재정의된 toString()은 Listing 11과 같아야 한다.


Listing 11. 재정의한 toString
                
// 스칼라 코드
class Student(firstName:String, lastName:String, age:Int, val subject:String)
  extends Person(firstName, lastName, age)
{
  def weight : Int =
    age // students are notoriously skinny

  def doSomething =
  {
    System.out.println("엄마, 맹세컨대 나 공부 열심히 하고 있어요! (그 맥주 이리 줘, 친구!)")
  }
  
  override def toString = "[Student: firstName="+firstName+
                          " lastName="+lastName+" age="+age+
                          " subject="+subject+"]"
}

그 자체로 명료하다.




위로


Final로 만들기

확실한 정보: Ted Neward의 스칼라 강연

함수 언어와 객체 지향 언어의 차이와 자바 언어와 다른 순수 객체 지향 언어가 그저 잘 맞지 않는 몇몇 중요한 분야에 대해 자바월드가 제공하는 이 포드캐스트를 통해 배우자.

서브클래스에서 재정의가 가능하게 만드는 반대는 당연하지만 재정의를 못하게 막는 것이다. 때로 베이스 클래스는 자식 클래스가 자신의 동작을 바꾸지 못하도록 하거나 아예 서브클래스를 정의하지 못하도록 하고 싶을 수 있다. 이 경우 자바 언어에서는 final 수정자를 메서드에 붙이면 해당 메서드가 재정의될 수 없다. 또 클래스에 final을 붙이면 아예 서브클래스를 정의할 수 없게 된다. 이는 스칼라의 경우에도 같다. 메서드나 클래스에 final을 붙이면 자식 클래스가 메서드를 재정의 못하게 되거나 서브클래스를 정의할 수 없다.

지금까지 언급한 abstract, final, override에 대한 내용은 통상적인 이름의 메서드는 물론이고 (자바, C#, C++ 프로그래머가 연산자라고 부르는) "이상한 이름의 메서드"에도 똑같이 적용된다는 점을 명심하자. 따라서 수학적 기능에 대해 어떤 기대를 하고 추상 멤버 함수인 "+", "-", "*", "/"와 powabs 같은 지원되어야 하는 다른 수학 연산을 정의하는 베이스 클래스나 특성을 정의하는 경우가 많다(말하자면 "Mathable"이다). 여기에 다른 프로그래머는 "Mathable"을 구현하거나 확장하는 타입을 추가로 생성하고 언급한 멤버를 정의할 수 있다. 이런 타입은 스칼라가 기본으로 지원하는 다른 내장 산술 타입과 똑같이 동작한다.




위로


차이점은 여기에

스칼라가 지금까지 본 것처럼 자바 상속 모델에 그렇게 쉽게 대응된다면, 자바 언어에서 상속한 스칼라 클래스나 그 반대 경우 모두가 가능해야 한다. 스칼라는 자바 바이트코드로 컴파일되는 다른 언어처럼 java.lang.Object에서 상속받는 객체를 생성해야 하므로 실제로 그렇다. 스칼라 클래스는 특성 같은 기타 요소에서 상속할 수도 있다는 점을 유의하자. 그래서 실제 상속을 구현하고 코드가 생성되는 방식은 다를 수 있다. 하지만 결과적으로 자바 베이스 클래스에서 어떤 방식으로든 상속할 수 있어야 한다(특성은 동작이 있는 인터페이스 같은 것이라는 점을 기억하자. 스칼라 컴파일러는 특성을 인터페이스로 분리하고 구현은 특성이 컴파일되어 들어가는 클래스에 추가하는 방식으로 특성을 구현한다).

하지만 알다시피, 스칼라의 타입 계층은 자바와 살짝 미묘하게 다르다. 기술적으로 Int, Float, Double 및 다른 수치 타입을 포함한 스칼라 클래스가 상속하는 베이스 클래스는 scala.Any다. 이 클래스는 ==, !=, equals, hashCode, toString, isInstanceOf, asInstanceOf 등 이름만 봐도 의미를 알 수 있는 스칼라 내 모든 타입에 공통인 핵심 메서드를 정의한다. 거기서 다시 주된 두 가지로 분화되는데 "기본 타입(primitive type)"은 scala.AnyVal에서, "클래스 타입"은 scala.AnyRe에서 상속받는다(그 다음 scala.ScalaObjectscala.AnyRef에서 상속 받는다).

보통 여기에 대해 직접 걱정할 필요는 없다. 하지만 자바와 스칼라 간의 상속을 생각할 때는 드물지만 몇몇 흥미로운 부작용이 생길 수 있다. 예를 들어 Listing 12의 ScalaJavaPerson을 살펴 보자.


Listing 12. 스칼라와 자바의 혼용
                
// 스칼라 코드
class ScalaJavaPerson(firstName:String, lastName:String, age:Int)
  extends JavaPerson(firstName, lastName, age)
{
  val weight : Int = age * 2 // 스칼라/자바 사람의 몸무게를 누가 알겠는가?

  override def toString = "[SJPerson: firstName="+firstName+
                          " lastName="+lastName+" age="+age+"]"
}

ScalaJavaPerson은 JavaPerson에서 상속 받았다.


Listing 13. JavaPerson 클래스
                
// 자바 코드
public class JavaPerson
{
    public JavaPerson(String firstName, String lastName, int age)
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    
    public String getFirstName()
    {
        return this.firstName;
    }
    public void setFirstName(String value)
    {
        this.firstName = value;
    }
    
    public String getLastName()
    {
        return this.lastName;
    }
    public void setLastName(String value)
    {
        this.lastName = value;
    }
    
    public int getAge()
    {
        return this.age;
    }
    public void setAge(int value)
    {
        this.age = value;
    }
    
    public String toString()
    {
        return "[Person: firstName" + firstName + " lastName:" + lastName +
            " age:" + age + " ]";
    }
    
    private String firstName;
    private String lastName;
    private int age;
}

ScalaJavaPerson을 컴파일하면 JavaPerson에서 상속 받는다. 하지만 스칼라에서 요구하는 대로 ScalaObject 인터페이스도 구현한다. 또한 통상 그렇듯 JavaPerson에서 상속 받은 메서드도 지원한다. 여기서 ScalaJavaPerson은 스칼라 타입이기 때문에 스칼라 규칙에 따라 Any 참조 변수에도 대입할 수 있어야 한다.


Listing 14. ScalaJavaPerson을 사용하는 예
                
	// 스칼라 코드
    val richard = new ScalaJavaPerson("Richard", "Campbell", 45)
    System.out.println(richard)
    val host : Any = richard
    System.out.println(host)

하지만 스칼라에서 JavaPerson 객체를 생성해 Any 참조 변수에 대입하려고 하면 어떻게 될까?


Listing 15. JavaPerson을 사용하는 예
                
// 스칼라 코드
    val carl = new JavaPerson("Carl", "Franklin", 35)
    System.out.println(carl)
    val host2 : Any = carl
    System.out.println(host2)

결국 이 코드는 제대로 컴파일되고 기대한 대로 동작한다. 이는 Any 타입과 java.lang.Object 간의 유사성 덕에 스칼라가 조용하게 JavaPerson이 제대로 동작하도록 했기 때문이다. 사실 java.lang.Object를 확장한 건 모두 Any 참조 변수에 대입할 수 있다고 해도 대체로 맞다(몇몇 극단적인 경우가 있다는 이야기는 들었다. 하지만 지금까지 개인적으로 그런 경우를 겪은 적은 없다).

결과는? 실질적인 목적에서는 자바 언어와 스칼라 사이에서 심각한 고민 없이 서로 상속할 수 있다(큰 골치거리는 "^=!#"나 스칼라의 비슷한 "이상한 이름의 메서드"를 어떻게 재정의하는지 정도일 것이다).




위로


결론

이 글에서 보인 것처럼 스칼라 코드와 자바 코드 사이의 긴밀한 충실성은 스칼라의 상속 모델이 자바 프로그래머가 사용하고 이해하기 쉽다는 것을 의미한다. 메서드 재정의나 멤버 가시성(visibility), 그 밖의 특징은 모두 똑같다. 스칼라의 모든 기능 중 자바 개발 과정에서 봤던 것과 가장 비슷한 것은 상속이 아닐까 한다. 유일하게 미묘한 부분은 뚜렷이 다른 스칼라의 문법이다.

두 언어가 상속을 어떻게 다루는지에 있어 비슷한 부분(그리고 살짝 다른 부분)에 익숙해지면 쉽게 자바 프로그램의 스칼라 구현을 작성할 준비가 된 셈이다. 예를 들어 JUnit, Servlet, Swing, SWT 같은 널리 사용되는 자바 베이스 클래스나 프레임워크의 스칼라 구현을 생각해 보자. 사실 스칼라 개발팀은 JTable을 이용해 정말 몇 줄 안 되는 코드로 간단한 스프레드시트 기능을 구현한 OOPScala(참고자료를 보라)라는 Swing 애플리케이션을 만들었다(자바 언어에 비해 자리 수가 차이 나게 짧은 코드를 사용했다).

따라서 스칼라를 자신의 상용 코드에 어떻게 적용할지 고민해 왔다면 이제 첫 발을 내디딜 준비가 된 것이다. 그저 다음 작성할 프로그램의 몇몇 부분에서 스칼라를 사용해 보자. 이 글에서 배웠듯이 자바 프로그래밍을 할 때처럼 적절한 베이스 클래스에서 상속 받거나 메서드를 재정의하는 데 아무런 문제가 없을 것이다.



참고자료

교육

제품 및 기술 얻기

토론


필자소개

Ted Neward photo

Ted Neward는 Neward & Associates의 사장으로 자바, .NET, XML 서비스와 다른 플랫폼에 대한 컨설팅, 조언, 교육, 강연을 한다. 워싱턴 주 시애틀 근처에 산다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의