1) Data Class

데이터를 담기 위한 클래스

  • toString(), hashCode(), equals(), copy() 메서드를 자동으로 생성
  • override 하면, 직접 구현한 코드를 사용
  • 1개 이상의 property가 있어야 함
  • 데이터 클래스는 abstract, open, sealed, inner 를 붙일 수 없음
  • 상속이 불가능

 

1-1) 코틀린/자바 코드 비교

fun main() {

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

class Cat() // 클래스는 property가 없어도 괜찮음

코틀린 코드를 자바 코드로 바꿔보기 Tools > Kotlin > Show Kotlin Bytecode > Decompile

코드의 일부를 살펴보면,

Person 은 그냥 클래스로 구현되었으므로 Get 메서드, 생성자가 있다.

// Person.java
public final class Person {
   @NotNull
   private final String name;
   private final int age;
   public static final int $stable;

   public Person(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }
}

 

 

Dog 데이터 클래스는,

copy(), toString(), hashCode(), equals() 4개의 함수가 자동생성된 것을 확인할 수 있음

  • copy() 값 복사할 때
  • toString() Dog 클래스 내부 값 확인을 할 때, 그냥 Class에서 toString() 하면 해당 객체의 주소값을 가져옴
  • toString() : 클래스 내부 데이터를 볼 수 있도록 구현됨
  • hashCode()
  • equals() 동등성 비교할 때
// Dog.java
public final class Dog {
   @NotNull
   public final Dog copy(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new Dog(name, age);
   }

   @NotNull
   public String toString() {
      return "Dog(name=" + this.name + ", age=" + this.age + ')';
   }

   public int hashCode() {
      int result = this.name.hashCode();
      result = result * 31 + Integer.hashCode(this.age);
      return result;
   }

   public boolean equals(@Nullable Object other) {
      if (this == other) {
         return true;
      } else if (!(other instanceof Dog)) {
         return false;
      } else {
         Dog var2 = (Dog)other;
         if (!Intrinsics.areEqual(this.name, var2.name)) {
            return false;
         } else {
            return this.age == var2.age;
         }
      }
   }
}

1-2) toString() 출력

fun main() {
    val person = Person("주연", 11)
    val dog = Dog("삽살개", 2)

    println(person.toString())
    println(dog.toString())

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

Person 에는 아무 것도 하지 않아서 참조 주소값이 출력됨

Dog 는 데이터 클래스로 내부 데이터를 볼 수 있는 함수가 생성되었으므로, 내부 데이터가 출력됨


1-3) toString 메서드 override 해서 직접 구현

fun main() {
    val person = Person("주연", 11)
    val dog = Dog("삽살개", 2)

    println(person.toString())
    println(dog.toString())

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
) {
    override fun toString(): String  {
        return "Dog 직접 구현: $name $age"
    }
}

 


1-4) copy 메서드로 특정 값만 변경

fun main() {
    val dog = Dog("삽살개", 2)
    println(dog.copy(age = 3)) // age 만 변경된 객체를 얻을 수 있음

}

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

1-5) 데이터 클래스는 상속 불가능

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

data class Corgi (
    val cute : Boolean = true
) : Dog()

데이터 클래스가 다른 클래스를 상속받았을 때 4가지 자동 생성된 메서드를 어떻게 생성해야할 지 알 수 없기 때문에

데이터 클래스는 상속이 불가능하다.


2) Sealed Class

추상 클래스로, 상속받은 자식 클래스의 종류를 제한

  • 컴파일러가 sealed 클래스의 자식 클래스가 어떤 것인지 앎
  • when과 함께 쓰일 때, 장점을 느낄 수 있음

Cat 추상 클래스를 상속받은 클래스는 BlueCat, RedCat, GreenCat 3개 뿐이므로 when 절 안에서 else 에 들어갈 case가 없다.

컴파일러는 Cat 을 상속받은 애들이 누군지 모른다.

fun main() {
    val cat : Cat = BlueCat()

    val result = when(cat) {
        is BlueCat -> "blue"
        is RedCat -> "red"
        is GreenCat -> "green"
        else -> "none"
    }

    // 상속받은 Cat 의 색깔을 출력
    println(result)
}

// sealed class
abstract class Cat
class BlueCat : Cat()
class RedCat : Cat()
class GreenCat : Cat()

여기서 abstract class 를 sealed class 로 변경하면, sealed class Cat

else 구문이 redundant 하다고 알려준다. 컴파일러가 Cat 이 어떤 자식을 가지고 있는지 알게된다. else branch 가 필요없어지는 것

  1. Recycler View 에서 View 타입 체크하거나
  2. 에러 체크할 때 유용함
fun main() {
    val cat : Cat = BlueCat()

    val result = when(cat) {
        is BlueCat -> "blue"
        is RedCat -> "red"
        is GreenCat -> "green"
        else -> "none"
    }

    // 상속받은 Cat 의 색깔을 출력
    println(result)
}

// sealed class
sealed class Cat
class BlueCat : Cat()
class RedCat : Cat()
class GreenCat : Cat()

예를 들어, 위 코드에서 class WhiteCat : Cat() 을 추가한다면 (흰 고양이 추가)

컴파일러가 Cat이 어떤 자식을 가지고 있는지 아므로, is WhiteCat → {} 구문이 빠졌다는 것을 알려줌

컴파일러 단계에서 컴파일 에러를 뱉어주기 때문에 실수를 줄일 수 있다.

else 문으로 모호하게 WhiteCat을 처리하는 것이 아니라 명시적으로 처리할 수 있음


3) Class 배경지식

Java 에서도 데이터를 holding 하기 위해 클래스를 많이 만든다.

네트워크 통신을 통해서 서버의 내용을 받는다면,

서버의 response를 그냥 가지고 있는게 아니고, parsing 해서

원하는 데이터를 담을 수 있는 클래스에 넣는 경우가 많음

[목차]

[내용]

1) Data Class

데이터를 담기 위한 클래스

  • toString(), hashCode(), equals(), copy() 메서드를 자동으로 생성
  • override 하면, 직접 구현한 코드를 사용
  • 1개 이상의 property가 있어야 함
  • 데이터 클래스는 abstract, open, sealed, inner 를 붙일 수 없음
  • 상속이 불가능

1-1) 코틀린/자바 코드 비교

fun main() {

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

class Cat() // 클래스는 property가 없어도 괜찮음

코틀린 코드를 자바 코드로 바꿔보기 Tools > Kotlin > Show Kotlin Bytecode > Decompile

코드의 일부를 살펴보면,

Person 은 그냥 클래스로 구현되었으므로 Get 메서드, 생성자가 있다.

// Person.java
public final class Person {
   @NotNull
   private final String name;
   private final int age;
   public static final int $stable;

   public Person(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }
}

Dog 데이터 클래스는,

copy(), toString(), hashCode(), equals() 4개의 함수가 자동생성된 것을 확인할 수 있음

  • copy() 값 복사할 때
  • toString() Dog 클래스 내부 값 확인을 할 때, 그냥 Class에서 toString() 하면 해당 객체의 주소값을 가져옴 toString() : 클래스 내부 데이터를 볼 수 있도록 구현됨
  • hashCode()
  • equals() 동등성 비교할 때
// Dog.java
public final class Dog {
   @NotNull
   public final Dog copy(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new Dog(name, age);
   }

   @NotNull
   public String toString() {
      return "Dog(name=" + this.name + ", age=" + this.age + ')';
   }

   public int hashCode() {
      int result = this.name.hashCode();
      result = result * 31 + Integer.hashCode(this.age);
      return result;
   }

   public boolean equals(@Nullable Object other) {
      if (this == other) {
         return true;
      } else if (!(other instanceof Dog)) {
         return false;
      } else {
         Dog var2 = (Dog)other;
         if (!Intrinsics.areEqual(this.name, var2.name)) {
            return false;
         } else {
            return this.age == var2.age;
         }
      }
   }
}

1-2) toString() 출력

fun main() {
    val person = Person("주연", 11)
    val dog = Dog("삽살개", 2)

    println(person.toString())
    println(dog.toString())

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

 

 

Person 에는 아무 것도 하지 않아서 참조 주소값이 출력됨

Dog 는 데이터 클래스로 내부 데이터를 볼 수 있는 함수가 생성되었으므로, 내부 데이터가 출력됨


1-3) toString 메서드 override 해서 직접 구현

fun main() {
    val person = Person("주연", 11)
    val dog = Dog("삽살개", 2)

    println(person.toString())
    println(dog.toString())

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
) {
    override fun toString(): String  {
        return "Dog 직접 구현: $name $age"
    }
}

 


1-4) copy 메서드로 특정 값만 변경

fun main() {
    val dog = Dog("삽살개", 2)
    println(dog.copy(age = 3)) // age 만 변경된 객체를 얻을 수 있음

}

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)


1-5) 데이터 클래스는 상속 불가능

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

data class Corgi (
    val cute : Boolean = true
) : Dog()

데이터 클래스가 다른 클래스를 상속받았을 때

4가지 자동 생성된 메서드를 어떻게 생성해야할 지 알 수 없기 때문에

데이터 클래스는 상속이 불가능하다.


2) Sealed Class

추상 클래스로, 상속받은 자식 클래스의 종류를 제한

  • 컴파일러가 sealed 클래스의 자식 클래스가 어떤 것인지 앎
  • when과 함께 쓰일 때, 장점을 느낄 수 있음

Cat 추상 클래스를 상속받은 클래스는 BlueCat, RedCat, GreenCat 3개 뿐이므로 when 절 안에서 else 에 들어갈 case가 없다.

컴파일러는 Cat 을 상속받은 애들이 누군지 모른다.

fun main() {
    val cat : Cat = BlueCat()

    val result = when(cat) {
        is BlueCat -> "blue"
        is RedCat -> "red"
        is GreenCat -> "green"
        else -> "none"
    }

    // 상속받은 Cat 의 색깔을 출력
    println(result)
}

// sealed class
abstract class Cat
class BlueCat : Cat()
class RedCat : Cat()
class GreenCat : Cat()

여기서 abstract class 를 sealed class 로 변경하면, sealed class Cat

else 구문이 redundant 하다고 알려준다. 컴파일러가 Cat 이 어떤 자식을 가지고 있는지 알게된다. else branch 가 필요없어지는 것

  1. Recycler View 에서 View 타입 체크하거나
  2. 에러 체크할 때 유용함

fun main() {
    val cat : Cat = BlueCat()

    val result = when(cat) {
        is BlueCat -> "blue"
        is RedCat -> "red"
        is GreenCat -> "green"
        else -> "none"
    }

    // 상속받은 Cat 의 색깔을 출력
    println(result)
}

// sealed class
sealed class Cat
class BlueCat : Cat()
class RedCat : Cat()
class GreenCat : Cat()

예를 들어, 위 코드에서 class WhiteCat : Cat() 을 추가한다면 (흰 고양이 추가)

 

컴파일러가 Cat이 어떤 자식을 가지고 있는지 아므로,

is WhiteCat → {} 구문이 빠졌다는 것을 알려줌

컴파일러 단계에서 컴파일 에러를 뱉어주기 때문에 실수를 줄일 수 있다.

else 문으로 모호하게 WhiteCat을 처리하는 것이 아니라 명시적으로 처리할 수 있음


3) Class 배경지식

Java 에서도 데이터를 holding 하기 위해 클래스를 많이 만든다.

네트워크 통신을 통해서 서버의 내용을 받는다면,

서버의 response를 그냥 가지고 있는게 아니고, parsing 해서

원하는 데이터를 담을 수 있는 클래스에 넣는 경우가 많음

 

+ Recent posts