ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • kotlin) 코틀린에서의 OOP
    kotlin 2024. 7. 6. 15:04

    코틀린에서 클래스를 다루는 방법

    클래스와 프로퍼티, 생성자와 init,  커스텀 getter, setter, backing field

    - Java 생성자 코드

    public class JavaPerson {
    
      private final String name;
      private int age;
    
      public JavaPerson(String name) {
        this(name, 1);
      }
    
      public String getName() {
        return name;
      }
    
      public int getAge() {
        return age;
      }
    
      public void setAge(int age) {
        this.age = age;
      }
    
      public boolean isAdult() {
        return this.age >= 20;
      }
    
    }

    - Kotlin 생성자 코드 변환

    //class Person constructor(name: String, age: Int){       //constructor : 생성자 선언 하지만 생략이 가능
    class Person (                          //constructor 생략이 가능
        val name: String,
        var age: Int = 1                    //하지만 기본 default Parameter 를 쓰는게 더 명확하고 좋다
    ){     //필드 선언을 함으로써
    //    val name = name                   //생략 가능
    //    var age = age
    
        //Getter 와 Setter 는 자동으로 만들어지기 떄문에 만들필요가 없다
    
        init{               //생성자 시점에 호출되는 코드 init 을 통해 (초기화블록)
            if(age <= 0){
                throw IllegalArgumentException("age must be > 0")
            }
        }
    
        constructor(name: String): this(name,1)     //생성자 추가 가능, this 를 통해 위 생성자 호출
    
        fun isAdult():Boolean {     //함수처럼 만들 수 있고
            return this.age >= 20
        }
        val isAdult: Boolean        //프로퍼티처럼 만들 수 있다.
            get() = this.age >= 20
    
        val uppercaseName: String       //get 할때 사용
            get() = this.name.uppercase()
    }
    
    
    fun main(){
        val person = Person("JS",100)
        println(person.name)        //.을 통해 바로 호출이 가능하다.
        person.age = 10             //.을 통해 set 처럼 이용 가능
        println(person.age)
    }

    *Setter 를 지양하기에 잘 안쓴다


    코틀린에서 상속을 다루는 방법

    1. 추상 클래스

    - Java 와 Kotlin 에서 큰 차이점은 없다.

    //Java
    public abstract class JavaAnimal {
    
      protected final String species;
      protected final int legCount;
    
      public JavaAnimal(String species, int legCount) {
        this.species = species;
        this.legCount = legCount;
      }
    
      abstract public void move();
    
      public String getSpecies() {
        return species;
      }
    
      public int getLegCount() {
        return legCount;
      }
    
    }
    
    //Kotlin
    abstract class Animal(val species: String, val legCount: Int) {
        abstract fun move()
    }
    //Java
    public class JavaCat extends JavaAnimal {
    
      public JavaCat(String species) {
        super(species, 4);
      }
    
      @Override
      public void move() {
        System.out.println("고양이가 사뿐 사뿐 걸어가~");
      }
    }
    
    //Kotlin
    class Cat(
        species: String
    ) : JavaAnimal(species, 4){     //상속받을때는 : 를 사용한다, 생성자는 변수로 생성
    
        override fun move(){		//@Ovveride 어노테이션 대신, fun 앞에 ovveride 를 붙인다. 
            println("고양이가 사뿐 사뿐 걸어가~")
        }
    }
    //java 
    public final class JavaPenguin extends JavaAnimal implements JavaSwimable, JavaFlyable {
    
      private final int wingCount;
    
      public JavaPenguin(String species) {
        super(species, 2);
        this.wingCount = 2;
      }
    
      @Override
      public void move() {
        System.out.println("펭귄이 움직입니다~ 꿱꿱");
      }
    
      @Override
      public int getLegCount() {
        return super.legCount + this.wingCount;
      }
    
      @Override
      public void act() {
        JavaSwimable.super.act();
        JavaFlyable.super.act();
      }
    
    }
    
    
    //Kotlin
    class Penguin(
        species: String
    ) : JavaAnimal(species, 2){
    
        private val wingCount : Int = 2
        override fun move(){
            println("펭귄이 움직입니다~ 꿱꿱")
        }
    
        override val legCount: Int                  //상속받은 추상클래스에서 override 할 수 있도록 open 을 붙여야한다.
            get() = super.legCount + this.wingCount //getter 새로 생성
    }

    2. 인터페이스

    //Kotlin
    
    interface Flyable {
        fun fly()                   //Java 의 추상메소드와 동일함
        fun act() {                 //Java 의 default 와 동일함
            println("파닥 파닥")
        }
    }
    
    interface Swimable {
        val swimAbility: Int        //Getter 를 상속받은곳에서 구현해줄것을 기대
        fun act() {                 //Java 의 default 와 같은 역할
            println("어푸 어푸")
        }
    }
    
    class Penguin(
        species: String
    ) :Animal(species, 2), Swimable,Flyable{
        private val wingCount: Int = 2
    
        override fun move(){
            println("펭귄이 움직입니다~ 꿱꿱")
        }
    
        override val legCount: Int
            get() = super.legCount + this.wingCount
    
        override fun act(){
            super<Swimable>.act()       //Java 의 Swimable.super.act(); 문법과는 다르다.
            super<Flyable>.act()
        }
        
        override val swimAbility: Int   //상속받은 swimAbility 를 Get 으로 구현
            get() = 3
    }

    3. 클래스를 상속할 때 주의할 점

    - 상위 클래스에 constructor 와 및 init 블락에서는 하위 클래스의 field 에 접근하면 안된다.

    fun main() {
        Derived(300)
    }
    open class Base(
        open val number: Int = 100
    ) {
        init {
            println("Base Class")
            println(number)             //number 값은 100 도 아니고 300도 아닌 0이 나온다,
                                        //상위 클래스에서 하위 클래스 field 값을 호출할 경우 초기화가 이루어지지 않은 상태에서 불러옴
        }
    }
    
    
    class Derived(
        override val number: Int
    ) : Base(number) {
        init {
            println("Derived Class")
        }
    }


    4. 상속 관련 지시어 정리

    1. final : override 를 할 수 없게 한다. default 로 보이지 않게 존재한다.
    2. open : override 를 열어 준다.
    3. abscract : 반드시 override 해야 한다.
    4. override : 상위 타입을 오버라이드 하고 있다.

    정리

    • 상속 또는 구현을 할 때에 : 을 사용해야 한다.
    • 상위 클래스 상속을 구현할 때 생성자를 반드시 호출해야 한다.
    • override 를 필수로 붙여야 한다.
    • 추상 멤버가 아니면 기본적으로 오버라이드가 불가능하다.
      • open 을 사용해주어야 한다.
    • 상위 클래스의 생성자 또는 초기화 블록에서 open 프로퍼티를 사용하면 얘기치 못한 버그가 발생할 수 있다.

    코틀린에서 접근 제어를 다루는 방법

    1. 자바와 코틀린의 가시성 제어

    Java 의 가시성 제어 Kotlin 의 가시성 제어
    public 모든 곳에서 접근 가능 public 모든 곳에서 접근 가능
    protected 같은 패키지 또는
    하위클래스에서만 접근 가능
    protected 선언된 클래스 또는
    하위 클래스에서만
    접근 가능
    default 같은 패키지에서만 접근 가능 internal 같은 모듈에서만 접근 가능
    private 선언된 클래스 내에서만 접근 가능 private 선언된 클래스 내에서만 접근 가능

     

    • Kotlin 에서는 패키지를 namespace를 관리하기 위한 용도로만 사용, 가시성 제어에는 사용되지 않는다.
    • 모듈 : 한번에 컴파일 되는 Kotlin 코드
      • ex. IDEA Module, Maven Project, Gradle Source Set, Ant Task <kotlinc> 의 호출로 컴파일 파일의 집합
    • Java 의 기본 적근 지시어는 default
    • Kotlin 의 기본 접근 지시어는 public

    2. 코틀린 파일의 접근 제어

    - 코틀린은 .kt 파일에 변수, 함수, 클래스 여러개를 바로 만들 수 있다.

    public 기본값
    어디서든 접근할 수 있다.
    protected 파일(최상단) 에는 사용 불가능
    internal 같은 모듈에서만 접근 가능
    private 같은 파일 내에서만 접근 가능
    private val NUM = 3     //파일에서도 선언 가능
    protected val NUM2 = 3  //파일에서는 선언 불가능
    fun add(a: Int, b: Int): Int {
        println(NUM)
        return a+b;
    }
    class Cat()

    3. 다양한 구성요소의 접근 제어

    클래스

    public 모든 곳에서 접근 가능
    protected 선언된 클래스 또는 하위 클래스에서만 접근 가능
    internal 같은 모듈에서만 접근 가능
    private 선언된 클래스 내에서만 접근 가능
    class Dog public constructor(){			//클래스에 접근제어를 넣으려면 constructor 를 선언해야한다.
    
    }
    
    open class Cat protected constructor() { //protected 는 하위 클래스와 본인 자신만 쓸 수 있다,
        // 그런데 하위클래스를 애당초 못만드는 final 이 자동으로 붙어있으니 open 이 필요하다
    }

    생성자

    //Java
    public abstract class StringUtils {		//abstract 와 private constuctor 를 이용해서 
    										//인스턴스화를 막았다.
      private StringUtils() {}
    
      public boolean isDirectoryPath(String path) {
        return path.endsWith("/");
      }
    
    }
    
    //Kotlin
    fun isDirectoryPath(path: String): Boolean{	//직접 파일에 유틸성 코드를 작성할 수 있다.
        return path.endsWith("/")
    }

     

    프로퍼티

    class Car(
        internal val name : String,     //getter, setter 한번에 접근 지시어를 정하거나
        private var_price: Int
    ){
        var price = _price				//price 에 접근하는 getter 는 public 상태
        private set                     //Setter 에만 추가로 가시성을 부여할 수 있다.
    }

    4. Java 와 Kotlin 을 함께 사용할 경우 주의할 점

    - Kotlin 의 protected 와 Java 의 protected 는 다르다.

    - Java 는 같은 패키지의 Kotlin protected 멤버에 접근할 수 있다 


    코틀린에서 object 키워드를 다루는 방법

    1. static 함수와 변수

    //Java
    public class JavaPerson {
    
      private static final int MIN_AGE = 1;
    
      public static JavaPerson newBaby(String name) {
        return new JavaPerson(name, MIN_AGE);
      }
    
      private String name;
    
      private int age;
    
      private JavaPerson(String name, int age) {
        this.name = name;
        this.age = age;
      }
    }
    
    
    
    //Kotlin
    fun main() {
        Person.Companion.newBaby("ABC")
        Person.oldBaby("ABC")               //(1)JamStatic 선언한 경우 바로 호출 가능
    }
    
    class Person private constructor(
        val name: String,
        var min_age: Int,
        var max_age: Int,
    ) {
    //    static                    //Kotlin 에는 static 이 없어졌다.
        companion object {          //static 대신 companion object 사용
            private val MAX_AGE = 100       //런타임시 할당
            private const val MIN_AGE = 1   //const 를 붙이면 컴파일시 변수가 할당
            fun newBaby(name: String): Person {
                return Person(name, MIN_AGE, MAX_AGE)
            }
    
            @JvmStatic                      //(1)JvmStatic 선언시 바로 호출 가능
            fun oldBaby(name: String): Person {
                return Person(name, MIN_AGE, MAX_AGE)
            }
        }
    }

    - static : 클래스가 인스턴스화 될 때 새로운 값이 복제되는게 아니라 정적으로 인스턴스끼리의 값을 공유

    - companion object : 클래스와 동행하는 유일한 오브젝트

    2. 싱글톤

    //Java
    public class JavaSingleton {
    
      private static final JavaSingleton INSTANCE = new JavaSingleton();
    
      private JavaSingleton() { }
    
      public static JavaSingleton getInstance() {
        return INSTANCE;
      }
    
    }
    
    fun main() {
        println(Singleton.a)    //0
        Singleton.a += 10
        println(Singleton.a)    //10
    }
    object Singleton{           //object 선언만 해도 싱글턴 생성이 가능하다.
        var a: Int = 0
    }

    - 애당초 인스턴스가 하나이기 때문에 인스턴스화 할 필요 없이 코드에서 바로 사용이 가능하다.

    3. 익명 클래스

    설명 : 특정 인터페이스나 클래스를 상속받은 구현체를 일회성으로 사용할 때 쓰는 클래스

    fun main() {
        moveSomehing(object : Movable {     //Movable 을 상속받은 object 를 쓴다
            override fun move(){
                println("Moving!")
            }
            override fun fly(){
                println("Flying!")
            }
        })
    }
    
    private fun moveSomehing(movable: Movable){
        movable.move()
        movable.fly()
    }

    코틀린에서 중첩 클래스를 다루는 방법

    1. 중첩 클래스의 종류

    • static 을 사용하는 중첩 클래스 : 클래스 안에 static 을 붙인 클래스, 밖의 클래스 직접 참조 불가
    • static 을 사용하지 않는 중첩 클래스
      • 내부 클래스 : 클래스 안에 클래스, 밖의 클래스 직접 참조 가능
      • 지역 클래스 : 매소드 내부에 클래스 (실제로 거의 사용 안함)
      • 익명 클래스 : 일회성 클래스

     

    2. 코틀린의 중첩 클래스와 내부 클래스

    //Java
    public class JavaHouse {
    
      private String address;
      private LivingRoom livingRoom;
    
      public JavaHouse(String address) {
        this.address = address;
        this.livingRoom = new LivingRoom(10);
      }
    
      public LivingRoom getLivingRoom() {
        return livingRoom;
      }
    
      public class LivingRoom {
        private double area;
    
        public LivingRoom(double area) {
          this.area = area;
        }
    
        public String getAddress() {
          return JavaHouse.this.address;
        }
      }
    }
    
    //Kotlin
    class JavaHouse(
        private val address: String,
        private val livingRoom: LivingRoom,
    ){
        class LivingRoom(
            private val area : Double
        )
    }
    Java Kotlin
    클래스 안의 static 클래스 바깥 클래스 참조 없음
    권장되는 유형
    클래스 안의 클래스(static) 바깥 클래스 참조 없음
    권장되는 유형
    클래스 안의 클래스 바깥 클래스 참조 있음 클래스 안의 inner 클래스  바깥 클래스 참조 있음

    코틀린에서 다양항 클래스를 다루는 방법

    1. Data Class

    //Java
    public class JavaPersonDto {
    
      private final String name;
      private final int age;
    
      public JavaPersonDto(String name, int age) {
        this.name = name;
        this.age = age;
      }
    
      public String getName() {
        return name;
      }
    
      public int getAge() {
        return age;
      }
    
      @Override
      public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        JavaPersonDto that = (JavaPersonDto) o;
        return age == that.age && Objects.equals(name, that.name);
      }
    
      @Override
      public int hashCode() {
        return Objects.hash(name, age);
      }
    
      @Override
      public String toString() {
        return "JavaPersonDto{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
      }
    }
    
    
    //Kotlin
    fun main() {
        val dto1 = PersonDto("JS",100)
        val dto2 = PersonDto("JS",100)
        println(dto1 == dto2)       //true
    }
    
    data class PersonDto(       //data 는 equals,hashCode,toString 을 만들어줌
        val name: String,
        val age: Int,
    )

    2. Enum Class

    //Java 
    public enum JavaCountry {
    
      KOREA("KO"),
      AMERICA("US"),
      ;
    
      private final String code;
    
      JavaCountry(String code) {
        this.code = code;
      }
    
      public String getCode() {
        return code;
      }
    
    }
    
    
    private static void handleCountry(JavaCountry country) {
      if (country == JavaCountry.KOREA) {
        // 로직 처리
      }
    
      if (country == JavaCountry.AMERICA) {
        // 로직 처리
      }
    }
    
    //Kotlin
    enum class Country(
        private val code : String,
    ){
        KOREA("KO"),
        AMERICA("US")
        ;
    }
    
    
    fun handleCountry(country: Country): Country {
        when (country){
            Country.KOREA -> TODO()
            Country.AMERICA -> TODO()
        }
    }

    3. Seale Class, Sealed Interface

    • 상속이 가능하도록 추상클래스를 만드는데 외부에서는 이 클래스를 방속받지 않았으면 할때 사용
    • 컴파일 타입 때 하위 클래스의 타입을 모두 기억한다.
      • 즉, 런타임때 클래스 타입이 추가될 수 없다.
    • 하위 클래스는 같은 패키지에 있어야 한다.
    • Enum 과 다른점
      • 클래스를 상속받을 수 있다.
      • 하위 클래스는 멀티 인스턴스가 가능하다.
    fun main() {
        handleCar(Avante())
    }
    private fun handleCar(car: HyundaiCar){
        when(car){
            is Avante -> TODO()
            is Sonata -> TODO()
            is Grandeur -> TODO()
        }
    }
    
    sealed class HyundaiCar(		//sealed 
        var name: String,
        var price: Long
    ){
    
    }
    
    class Avante : HyundaiCar("아반떼", 1_000L)
    class Sonata : HyundaiCar("소나타", 2_000L)
    class Grandeur : HyundaiCar("그랜저", 3_000L)

    댓글

Designed by Tistory.