코틀린 언어 정리 2-10

Anonymous inner class( object expression )

개요

객체 표현식(object expressions)을 사용하여 익명 [내부] 클래스(anonymous [inner] class)를 선언하면 익명 객체(anonymous object)가 생성됩니다.(익명 클래스는, 일반 클래스와 다르게, 선언하면 객체 생성 동작까지 수행됩니다.) 클래스를 만들어 여러번 생성하지 않고 단 한번만 사용하는 경우 불필요한 작업( 클래스 이름짓기, 클래스 선언, 클래스 소스코드 배치 등 )을 줄일 수 있는 점에서 유용합니다. 익명 클래스는 전역, 지역(로컬) 또는 클래스 내부에서 사용할 수 있고, 익명 클래스의 내부에서 외부의 멤버, 변수 등을 바로 참조할 수 있습니다. 그런데 익명 내부 클래스(Anonymous inner class)는 자바에서 사용하는 용어라고 합니다. 코틀린에서는 단어 중간에 포함된 inner를 굳이 포함시킬 필요가 없는 것 같습니다.


제약

익명 객체의 타입은 지역(로컬) 및 private 영역 내에서만 참조할 수 있습니다. 예를 들면 클래스의 public 멤버 함수, 전역 함수등의 리턴 값 등으로 익명 객체를 넘긴 후 익명 객체를 전달받은 위치에서 익명 객체의 타입을 사용하려면( object의 멤버에 접근하려면 ) object expressions에 super type을 명시해야( 익명 객체가 다른 클래스를 상속 받아야 ) 합니다. 이 경우도 super type의 멤버 또는 override한 멤버만 접근할 수 있으며 익명 객체 자체만의 멤버에는 접근할 수 없습니다.


sealed class를 상속 받을 수 없습니다.

sealed class를 상속 받는 것은 컴파일 단계에서 명시적으로 금지하고 있습니다.


익명 클래스가 sealed class를 상속 받는다는 것은 sealed class의 존재 의미에 부합하지 않습니다. sealed class 집합에서, sealed class를 상속 받은 모든 하위 클래스는 sealed class와 관련된 로직을 타입 구분을 통해 구현하는 방식을 사용 하므로 최종적으로는 하위 클래스의 타입을 반드시 확인해야 하는데 익명 클래스는 타입을 구분할 수 없기 때문입니다.


만약 상속을 허용한다고 하더라도 sealed class를 상속 받는 객체 표현식(object expressions)을 사용하는 경우 sealed class가 생성되면서 다시 객체 표현식에 명시된 sealed class의 생성자가 호출되어 생성 과정이 재귀적으로 동작하게 됩니다.


sealed class의 하위 클래스는 상속 받을 수 있습니다.

sealed class를 상속 받는 것은 컴파일 단계에서 명시적으로 금지하지 않습니다.


  • sealed class의 멤버로서 익명 클래스를 생성하는 경우 sealed class의 하위 클래스를 상속 받으면 sealed class를 상속 받을 때와 마찬가지로 생성 과정이 재귀적으로 동작하기 때문에 컴파일 단계에서 에러가 발생합니다.

  • sealed class의 멤버 함수 내부 등에서 익명 클래스를 생성하는 경우에는 컴파일 에러가 발생하지 않습니다. sealed class를 생성할 때 익명 객체가 생성되는 것이 아니기 때문에, 익명 클래스가 sealed class의 하위 클래스를 상속 받는 것이 문제가 되지 않습니다.



목적

anonymous class가 선언된 후 anonymous object가 생성되었을 때 이 객체를 다른 곳(함수의 리턴 값 등)으로 전달하지 않고, 한눈에 보이는 동일한 영역(로컬, 클래스 내부 등)에서 일회성으로 활용하는 것입니다.



closure와의 차이점

anonymous inner class가 closure(클로저)처럼 보일 수 있으나 클로저와는 다릅니다. 클로저는 주로 람다함수에 적용되는데, 아직 실행되지 않은 람다함수 구현에서 현재 실행 중인 상위 문맥의 요소들을 참조하게 되는 경우, 이를 나중에 람다함수가 실행될 때 참조할 수 있도록 현재 실행 문맥(람다함수가 정의된 실행환경)을 유지하는 작동을 합니다. 하지만 object expressions은 선언되는 즉시 실행(객체가 생성됨) 되므로 실행 문맥을 유지하고 있을 필요가 없기 때문입니다.



지금까지 언급한 모든 유형의 클래스들과 제약에 대한 전반적인 예제

예제

//////////////////////////////////////////////////

// object expressions, anonymous (inner) class, Local class 와 같은 용어들이 있는데 여기서는 모두 같은 것으로 간주할 수 있습니다.


sealed class Person(var name: String, var age: Int) {

   //////////////////////////////////////////////////

   // 유형: Nested class

   open class Student(name: String, age: Int, var grade: Int) : Person(name, age) {}


   class Baby(name: String, age: Int, var parent: Person) : Person(name, age) {}

   class Worker(name: String, age: Int, var company: String) : Person(name, age) {}

   class Senior(name: String, age: Int, var welfareCenter: String) : Person(name, age) {}


   //////////////////////////////////////////////////

   // 유형: Inner class

   inner class PersonInner {

       var name = this@Person.name

   }



   //////////////////////////////////////////////////

   // 유형: Anonymous (inner) class, 클래스 내부의 object expression, Local class, 익명 객체

   // 예제에서는 Anonymous inner class와 Annonymous class를 구분했지만 둘 다 sealed class가 생성되었을 때만 작동합니다. 즉 동작에 차이가 없으므로 별도로 구분할 필요는 없을 것 같습니다.


   // public/inner: public으로 선언된 익명 객체는 type으로 사용할 수 없음

   var objAnonymousInner = object {

       var name1: String = this@Person.name

       var age1: Int = this@Person.age

   }


   // public: public으로 선언된 익명 객체는 type으로 사용할 수 없음

   var objAnonymous = object {

       var name1: String = "Person::objAnonymous anonymous"

       var age1: Int = 10

   }


   // private: private으로 선언된 익명 객체는 지역 및 private 영역에서 type으로 사용될 수 있음

   private var objAnonymousPrivate = object {

       var name1: String = "Person::objAnonymous anonymous"

       var age1: Int = 10

   }


   // private/inner: private으로 선언된 익명 객체는 지역 및 private 영역에서 type으로 사용될 수 있음

   private var objAnonymousInnerPrivate = object {

       var name1: String = this@Person.name

       var age1: Int = this@Person.age

   }


   fun testAccessPublicAnonymousObject () {

       // public으로 선언되어 type으로 사용할 수 없음. 따라서 멤버에 접근할 수 없음

       //objAnonymousInner.name1; // compile error

       //objAnonymous.name1; // compile error


       // private으로 선언된 익명 객체는 지역 및 private 영역에서 type으로 사용될 수 있음

       objAnonymousInnerPrivate.name1

       objAnonymousPrivate.name1

   }



   //////////////////////////////////////////////////

   // 유형: Anonymous (inner) class, 클래스 내부의 object expression, Local class, 익명 객체

   // Local class에서 sealed class를 상속할 수 없습니다.

   // sealed class 는 sealed class를 상속 받아 개념을 분류하는 하위 클래스의 타입을 반드시 참조해야 하므로 정확한 타입을 참조할 수 없는 object에 상속을 허용하는 것은 sealed class의 무결성을 깨는 것이므로 적절하지 않습니다.


   // inherits sealed class

   //var objAnonymousHasSealedClassSuperType = object: Person("test", 10) {

   //var name1: String = "Person::objAnonymous anonymous"

   //var age1: Int = 10

   //     }


   // inherits sealed class/inner

   // Anonymous inner class, 클래스 내부의 object expressions, Local class

   //var objAnonymousHasSealedClassSuperType1 = object : Person("test", 10) {

   //var name1: String = this@Person.name

   //var age1: Int = this@Person.age

   //}



   //////////////////////////////////////////////////

   // 유형: Anonymous (inner) class, 클래스 내부의 object expression, Local class, 익명 객체

   // Local class에서 sealed class의 하위 클래스도 상속할 수 없습니다.

   // 생성자 호출이 재귀적으로 되어 컴파일 에러가 발생합니다.


   // inherits sealed's subclass/inner

   //var objAnonymousInheritedSealedChildType = object: Person.Student("K", 17, 10) {

   //    var name1: String = name

   //    var name2: String = this@Person.name

   //    var age1: Int = age

   //}



   //////////////////////////////////////////////////

   // 유형: object declarations

   // singleton으로 처리되므로 qualified this syntax로 Outer(Person)를 참조할 수 없습니다. 따라서 object declarations는 inner class와 관련이 없습니다.

   object PersonObject : Person("PersonObject", 10) {

       var name1: String = "PersonObject static"

       var age1: String = "0"// + this@Person.age // error

   }



   //////////////////////////////////////////////////

   // 유형: 멤버 함수 안에서 사용한 object expression( anonymous class, local class )


   // private/func/inner: private으로 선언된 익명 객체는 지역 및 private 영역에서 type으로 사용될 수 있음

   private fun returnPrivateAnonymousObject() =

       object {

           var name: String = "Person::anonymous " + this@Person.name // 멤버 접근

           var age: Int = 10

       }


   // public/func/inner: public으로 선언된 익명 객체는 type으로 사용할 수 없음. 단 익명 객체가 상속을 받은 경우 상속 받은 타입으로는 사용할 수 있음

   fun returnAnonymousObjectSuperType(): Person.Student =

       object : Person.Student("anonymous", 10, 3) { // sealed class의 하위 클래스를 상속 받는 것이 허용됩니다.(멤버 함수 내부여서 재귀호출이 발생하지 않습니다.)

           //override var name: String = "anonymous has super type"

           //override var age: Int = 10

           var name1: String = "anonymous has super type..." + this@Person.name

           var age1: Int = 10

       }



   // inherits sealed class/public/func/inner

   // 재귀호출이 발생하지 않아 허용될 것 같지만, 역시 Local class에서 sealed class는 상속받지 못합니다.

//    fun returnAnonymousObjectSuperType1(): Person =

//        object : Person("anonymous", 10) {

//            //override var name: String = "anonymous has super type"

//            //override var age: Int = 10

//            var name: String = "anonymous has super type..." + this@Person.name

//            var age1: Int = 10

//        }


   fun testAnonymousObjectAccess() {

       println("testAnonymousObjectAccess")

       //println(objAnonymous.name) // error: public으로 선언되어 로컬, private 영역을 벗어날 수 있는 익명 객체는 type으로 사용할 수 없음

       println(returnPrivateAnonymousObject().name) // private 멤버 함수에서 리턴하는 object는 영역 내에서 type으로 사용 가능

       println(returnAnonymousObjectSuperType().name) // public이지만 super type이 있는 경우 type( super type 한정 )으로 사용 가능

       //returnAnonymousObjectSuperType().name1 // error: anonymous object를 전달 받은 후 anonymous object만의 멤버에는 접근 불가능

   }

}


var objAnonymous1 = object {

   var name1: String = "anonymous global"

   var age1: Int = 10

}


fun main(args: Array<String>) {

   var person: Person = Person.Worker("Max", 30, "Mad Company")


   if (person is Person.Worker)

       println("${person.name} ${person.age} ${person.company}")

   else

       println("${person.name} ${person.age}")


   var objNum = when (person) { // when 식에서 sealed class의 모든 경우(타입)를 명시했다면 else 생략 가능

       is Person.Student -> 1

       is Person.Baby -> 2

       is Person.Worker -> 3

       is Person.Senior -> 4

       is Person.PersonObject -> 5 // 새로운 하위 타입이 추가될 경우 when 식이 완전하지 않게 되므로 에러가 발생함

   }

   println(objNum)

   var objAnonymous = object { // 일반 함수 내에서 익명 클래스 선언 및 객체 생성

       var name1: String = person.name // 지역 변수 접근

       var age: Int = person.age

   }

   println(objAnonymous)

   person.testAnonymousObjectAccess()

   person.returnAnonymousObjectSuperType().name

}



+ Recent posts