[TECHIT] 코틀린 3

hegleB·2023년 5월 25일
post-thumbnail

코틀린 상속에 대해 작성해보려고 한다.

[Kotlin]

fun main() {
    val s1 = SubClass1()
    println("s1.subMember1 : ${s1.subMember1}")
    s1.subMethod1()
}

class TestClass()

// 코틀린에서 class를 정의하면 final 클래스가 된다.
// 클래스를 정의할 때 open 키워드를 붙혀주면 일반 클래스고 정의되고
// 상속이 가능해진다.
open class SuperClass{

    constructor(){
        println("SuperClass의 기본 생성자")
    }

    var superMember1 = 100

    fun superMethod1(){
        println("SuperClass1의 메서드 입니다")
    }
}

// 자식 클래스
class SubClass1 : SuperClass(){
    val subMember1 = 200

    fun subMethod1(){
        println("SubClass1의 메서드 입니다")
    }
}

[Java]

public final class MainKt {
   public static final void main() {
      SubClass1 s1 = new SubClass1();
      String var1 = "s1.subMember1 : " + s1.getSubMember1();
      System.out.println(var1);
      s1.subMethod1();
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

public final class TestClass {
}

public class SuperClass {
   private int superMember1 = 100;

   public final int getSuperMember1() {
      return this.superMember1;
   }

   public final void setSuperMember1(int var1) {
      this.superMember1 = var1;
   }

   public final void superMethod1() {
      String var1 = "SuperClass1의 메서드 입니다";
      System.out.println(var1);
   }

   public SuperClass() {
      String var1 = "SuperClass의 기본 생성자";
      System.out.println(var1);
   }
}

public final class SubClass1 extends SuperClass {
   private final int subMember1 = 200;

   public final int getSubMember1() {
      return this.subMember1;
   }

   public final void subMethod1() {
      String var1 = "SubClass1의 메서드 입니다";
      System.out.println(var1);
   }
}

자바 코드에서 TestClass를 보면 final로 선언되어 있다. 즉 상속을 할 수 없다는 것을 알 수 있다. 하지만 open 키워드로 선언된 SuperClass를 보면 final이 선언되어 있지 않다. 따라서 open 키워드를 쓰면 상속을 할 수 있게 된다. 또한 자바 코드에서 SubClass 옆에 보면 extends 키워드로 되어 있다. 따라서 코틀린에서는 콜론(:)을 사용하면 된다.

[Kotlin]

open class SuperClass2(var a1:Int)

class SubClass2 : SuperClass2{
    // 부모의 생성자를 호출한다.
    constructor() : super(100){
        // 필요한 코드를 작성해주세요
    }
}

// 만약 생성자에 작성할 코드가 없다면 ..
class SubClass3 : SuperClass2(100)

자바와 동일하게 클래스의 객체를 생성하면 부모 클래스의 기본 생성자(매개변수가 없는)가 자동으로 호출된다. 만약 부모 클래스에 기본생성자가 없다면 자식 클래스에서 명시적으로 호출해줘야 한다.

예제 문제

중국집
메뉴를 선택해주세요
1. 짜장면, 2. 짬뽕, 3. 볶음밥, 4. 종료
주문 총 금액 : 30000원
짜장면 : 0개
짬뽕 : 0개
볶음밥 : 0개
음식 : 짜장면
가격 : 6000원
곱배기여부 : 곱배기 입니다 or 곱배기 아닙니다
음식 : 짬뽕
가격 : 8000원
홍합여부 : 홍합이 있습니다 or 홍합이 없습니다
음식 : 볶음밥
가격 : 10000원
국물종류 : 짬뽕국물 or 계란국물
짜장면, 짬봉, 볶음밥은 각각 별개의 클래스로 구성해준다.
각 클래스를 통해 생성된 객체를 담을 ArrayList는 각각 따로 만들어준다.
주문내역 출력시 짜장면들, 짬뽕들, 볶음밥들 순서대로 출력해주세요

[내가 작성한 코드]

fun main() {
    val menu = Menu()
    menu.selectMenuFood()
    menu.printTotalMenu()
    menu.printSelectedMenu()
}

class Menu {

    var totalCost = 0
    val jajangmyeonList = ArrayList<Jajangmyeon>()
    val jjambbongList = ArrayList<Jjambbong>()
    val friedRiceList = ArrayList<FriedRice>()

    private val scanner = Scanner(System.`in`)

    fun selectMenuFood() {
        while (true) {
            println("메뉴를 선택해주세요")
            print("1. 짜장면, 2. 짬뽕, 3. 볶음밥, 4. 종료 ")
            val menu = scanner.nextInt()
            selectMenuItem(menu)

            when (menu) {
                in 1..3 -> continue
                4 -> break
                else -> println("잘못 입력하였습니다")
            }
        }
    }

    fun printTotalMenu() {
        println("주문 총 금액 : ${totalCost}원")
        println("짜장면 : ${jajangmyeonList.size}개")
        println("짬뽕 : ${jjambbongList.size}개")
        println("볶음밥 : ${friedRiceList.size}개")
        println()
    }

    fun printSelectedMenu() {
        for (jajangmyeon in jajangmyeonList) {
            jajangmyeon.printInfo()
            println()
        }

        for (jjabbong in jjambbongList) {
            jjabbong.printInfo()
            println()
        }

        for (friedRice in friedRiceList) {
            friedRice.printInfo()
            println()
        }
    }

    fun selectMenuItem(item: Int) {
        when (item) {
            1 -> {
                println("곱배기여부를 선택해주세요")
                val isLarge = scanner.next()
                val jajangmyeon = Jajangmyeon(isLarge)
                addMenu(jajangmyeon)
            }

            2 -> {
                println("홍합여부를 선택해주세요")
                val withMussel = scanner.next()
                val jjambbong = Jjambbong(withMussel)
                addMenu(jjambbong)
            }

            3 -> {
                println("국물종류를 선택해주세요")
                val soup = scanner.next()
                val friedRice = FriedRice(soup)
                addMenu(friedRice)
            }
        }
    }


    fun addMenu(menuItem: Any) {
        when (menuItem) {
            is Jajangmyeon -> {
                totalCost+=menuItem.price
                jajangmyeonList.add(menuItem)
            }
            is Jjambbong -> {
                totalCost+=menuItem.price
                jjambbongList.add(menuItem)
            }
            is FriedRice -> {
                totalCost+=menuItem.price
                friedRiceList.add(menuItem)
            }
        }
    }
}

class Jajangmyeon(
    val name: String,
    val price: Int,
    var large: String
) {
    constructor(large: String) : this(name = "짜장면", price = 6000, large = large) {
        this.large = large
    }
    fun printInfo() {
        println("음식: $name")
        println("가격: ${price}원")
        println("곱배기: ${large}")
    }
}

class Jjambbong(
    val name: String = "짬뽕",
    val price: Int = 8000,
    var withMussels: String,
) {

    constructor(withMussels: String) : this(name = "짬뽕", price  = 8000, withMussels = withMussels) {
        this.withMussels = withMussels
    }

    fun printInfo() {
        println("음식: $name")
        println("가격: ${price}원")
        println("홍합여부: ${withMussels}")
    }
}

class FriedRice(
    val name: String,
    val price: Int,
    var soupType: String
) {

    constructor(soupType: String) : this(name = "볶음밥", price  = 10000, soupType = soupType) {
        this.soupType = soupType
    }

    fun printInfo() {
        println("음식: $name")
        println("가격: ${price}원")
        println("국물종류: ${soupType}")
    }
}

[강사님 코드]

import java.util.*
import kotlin.collections.ArrayList

fun main() {
    val chinessRestaurant = ChinessRestaurant()
    chinessRestaurant.selectMenu()
    chinessRestaurant.printMenu()
    chinessRestaurant.printFoodList()
}

class ChinessRestaurant {
    val scanner = Scanner(System.`in`)

    // 음식들을 담을 리스트
    val jjanJangMyunList = ArrayList<JJaJangMyun>()
    val jjamPPongList = ArrayList<JJamPPong>()
    val bockUmBabList = ArrayList<BockUmBab>()

    // 주문 내역을 출력하는 기능

    //메뉴를 선택하는 기능
    fun selectMenu() {
        var selectNumber = 0

        while (true) {
            println("메뉴를 선택해주세요")
            print("1. 짜장면, 2. 짬뽕, 3. 볶음밥, 4. 종료 : ")
            selectNumber = scanner.nextInt()

            if (selectNumber !in 1..4) {
                println("번호를 다시 입력해주세요")
                continue
            }
            if (selectNumber == 4) {
                break
            }

            when (selectNumber) {
                1 -> {
                    print("곱배기 인가요 ?(1. 곱배기O, 2. 곱배기X) : ")
                    val temp = scanner.nextInt()
                    var temp2 = true
                    if (temp == 2) {
                        temp2 = false
                    }
                    val food = JJaJangMyun("짜장면", 6000, temp2)
                    jjanJangMyunList.add(food)
                }

                2 -> {
                    print("홍합이 있나요? (1. 있음, 2. 없음) : ")
                    val temp = scanner.nextInt()
                    var temp2 = true
                    if (temp == 2) {
                        temp2 = false
                    }
                    val food = JJamPPong("짬뽕", 8000, temp2)
                    jjamPPongList.add(food)
                }

                3 -> {
                    print("국물 종류가 무엇인가요? (1. 짬뽕국물, 2.계란국물) : ")
                    val temp = scanner.nextInt()
                    var temp2 = "짬뽕국물"
                    if (temp == 2) {
                        temp2 = "계란국물"
                    }
                    val food = BockUmBab("볶음밥", 10000, temp2)
                    bockUmBabList.add(food)
                }
            }
        }
    }

    // 주문 내역을 출력하는 기능
    fun printMenu(){
        // 총 주문 금액
        var totalPrice = 0
        // 짜장면 금액 누적
        for(food in jjanJangMyunList){
            totalPrice += food.price
        }
        // 짬뽕 금액 누적
        for(food in jjamPPongList){
            totalPrice += food.price
        }
        // 볶음밥 금액 누적
        for(food in bockUmBabList){
            totalPrice += food.price
        }

        println("주문 총 금액 : ${totalPrice}원")
        println("짜장면 : ${jjanJangMyunList.size}개")
        println("짬뽕 : ${jjamPPongList.size}개")
        println("볶음밥 : ${bockUmBabList.size}개")
        println()
    }

    // 주문한 음식들을 출력하는 기능
    fun printFoodList(){
        // 짜장면
        for(food in jjanJangMyunList){
            food.printFoodInfo()
        }
        // 짬뽕
        for(food in jjamPPongList){
            food.printFoodInfo()
        }
        // 볶음밥
        for(food in bockUmBabList){
            food.printFoodInfo()
        }
    }
}

class JJaJangMyun(var name: String, var price: Int, var isDouble: Boolean) {

    fun printFoodInfo() {
        println("음식 : $name")
        println("가격 : ${price}원")

        if (isDouble == true) {
            println("곱배기 여부 : 곱배기 입니다")
        } else {
            println("곱배기 여부 : 곱배기 아닙니다")
        }
    }
}

class JJamPPong(var name: String, var price: Int, var hasMuseel: Boolean) {

    fun printFoodInfo() {
        println("음식 : $name")
        println("가격 : ${price}원")

        if (hasMuseel == true) {
            println("홍합 여부 : 홍합이 있습니다")
        } else {
            println("홍합 여부 : 홍합이 없습니다")
        }
    }
}

class BockUmBab(var name: String, var price: Int, var soupType: String) {

    fun printFoodInfo() {
        println("음식 : $name")
        println("가격 : ${price}원")
        println("국물 종류 : $soupType")
    }
}

when을 사용하여 메뉴 입력 값을 4일 경우 종료, 1에서 4사이 값이 아닌 경우 잘못 입력했다는 출력한다. 각 메뉴를 클래스로 만들어서 각 ArrayList에 담아 각 메뉴를 출력하도록 하였다.

접근제한자

클래스

  • private : 파일이 같을 경우에만 사용이 가능하다.
  • public : 패키지, 모듈이 달라도 사용이 가능하다.
  • internal : 패키지, 모듈이 다르면 사용이 불가능하다.

변수, 메서드

  • private : 모든 경우에 사용이 불가능하다.
  • public : 모든 경우에 사용이 가능하다.
  • protected : 상속 관계에서만 사용이 가능하다. 패키지, 모듈이 달라도 사용 가능하다.
  • internal : 모듈이 다르면 사용이 불가능하다.

생략시 public으로 설정되고 internal은 같은 모듈이라면 public과 동일하다.

[Kotlin]

fun main() {
    val testClass = TestClass()
//    println(testClass.a1)
    println(testClass.a2)
//    println(testClass.a3)
    println(testClass.a4)
}



class TestClass{
    // kotlin에서는 자바로 변경될 때 모든 맴버 변수가 전부다 private 변수이다
    // kotlin 에서 접근 제한자를 설정하면 Java로 변환될 때 setter/getter 생성에 대한
    // 설정이 된다.
    private var a1 = 100
    public var a2 = 200
    protected var a3 = 300
    internal var a4 = 400
}

[Java]

public final class MainKt {
   public static final void main() {
      TestClass testClass = new TestClass();
      int var1 = testClass.getA0();
      System.out.println(var1);
      var1 = testClass.getA2();
      System.out.println(var1);
      var1 = testClass.getA4$Kotine2();
      System.out.println(var1);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

public final class TestClass {
   private final int a0 = 10;
   private int a1 = 100;
   private int a2 = 200;
   private int a3 = 300;
   private int a4 = 400;

   public final int getA0() {
      return this.a0;
   }

   public final int getA2() {
      return this.a2;
   }

   public final void setA2(int var1) {
      this.a2 = var1;
   }

   protected final int getA3() {
      return this.a3;
   }

   protected final void setA3(int var1) {
      this.a3 = var1;
   }

   public final int getA4$Kotine2() {
      return this.a4;
   }

   public final void setA4$Kotine2(int var1) {
      this.a4 = var1;
   }
}

접근제한자를 사용하는 이유는 데이터 무결성으로 인해 사용한다. 자바 코드를보면 멤버 변수 모두 private로 되어 있고 setter, getter를 통해 데이터를 접근할 수 있다.

프로퍼티

  • 클래스 내에 선언한 변수

val로 선언한 변수는 final가 선언되어 변수를 수정할 수 없다. 따라서 getter만 만들어진다.
var로 선언한 변수는 setter와 getter 모두 만들어진다.

var v3 = 0
        get() {
            println("getter 호출")
            return field
        }
        set(value){
            println("setter 호출")
            if(value in 1..10){
                field = value
            }
        }

여기서 field는 변수를 의미한다.

지연 초기화

  • lateinit : var 변수에만 사용이 가능하며, 변수를 정의할 때 값을 저장하지 않아도 된다. 기본 자료형(Int, Double 등)에서는 사용할 수 없다.
  • lazy : val 프로퍼티에서 사용하는 키워드이며, 저장할 값을 어떠한 처리를 통해서 구해야 한다면 lazy를 사용한다. lazy 코드 블록의 제일 마지막에 작성한 값이나 변수의 값, 수식의 값을 프로퍼티에 저장한다.
if(::a4.isInitialized) {
   println("a4 : $a4")
}

isInitialized은 프로퍼티가 초기화 되었는지 검사하는 코틀린 표준 함수의 API이다. lateinit 프로퍼티는 사용 전에 반드시 값을 저장한 적이 있는지를 확인해야 한다.

profile
성장하는 개발자

0개의 댓글