[TECHIT] 코틀린 5

hegleB·2023년 5월 26일
post-thumbnail

Companion object

fun main(){
    val t1 = TestClass1()
    println("t1.a1 : ${t1.a1}")
    t1.testFun1()

    println("TestClass1.a2 : ${TestClass1.a2}")
    TestClass1.testFun2()

    // java 파일에 정의된 static 맴버 사용
    println("JavaMain.javaA1 : ${JavaMain.JavaA1}")
    JavaMain.javaMethod1()
}

class TestClass1{
    // 일반 멤버 변수
    var a1 = 100

    // companion 멤버
    companion object{
        var a2 = 200
       
        @JvmStatic var kotlinA3 = 300

        @JvmStatic fun kotlinMethod3(){
            println("KotlinMethod3")
        }

        fun testFun2(){
            println("testFun2")
            println("a2 : $a2")
            // companion object 입장에서 봤을 때
            // 클래스를 가지고 객체를 생성했다는 것을 100% 보장받을 수 없기 때문에
            // 일반 멤버의 접근이 불가능하다.
            // testFun1()
            // println("a1 : $a1")
        }
    }


    // 일반 메서드
    fun testFun1(){
        println("testfun1")
        println("a1 : $a1")
        // 객체 입장에서는 companion 맴버가 메모리에 올라가 있다는 것을
        // 보장받을 수 있으므로 사용이 가능하다.
        testFun2()
        println("a2 : $a2")
    }
}

public class JavaMain {

    public static int JavaA1 = 100;

    public static void javaMethod1(){
        System.out.println("javaMethod1");
    }

    public static void main(String [] args){
        // kotlin에서 정의한 companion 멤버 사용
        int a1 = TestClass1.Companion.getKotlinA3();
        System.out.printf("a1 : %d\n", a1);
        TestClass1.Companion.kotlinMethod3();
    }
}

자바에서 static과 동일하다. 클래스내에 companion 멤버로 정의된 요소들은 객체 생성 없이 사용이 가능하며, 클래스 이름을 통해 접근한다. companion 변수의 경우 하나만 생성되어 사용할 수 있다.
@JavaStaticcompanion 멤버를 자바에서 사용할 수 있게 해주는 이노테이션으로, JVM 버전에 따라 실행이 안될 수 있기 때문에 사용을 권장한다.

Data Class

fun main(){
    // 일반 클래스로 객체를 생성한다.
    var obj1 = TestClass1(100, 200)
    var obj2 = TestClass2(100, 200)

    // 멤버 사용
    println("obj1.a1 : ${obj1.a1}")
    println("obj1.a2 : ${obj1.a2}")

    println("obj2.a1 : ${obj2.a1}")
    println("obj2.a2 : ${obj2.a2}")

    obj1.testMethod1()
    obj2.testMethod2()

    println("------------------------------------------")

    // 부생성자를 이용한 객체 생성
    var obj3 = TestClass1(100, 200, 300)
    var obj4 = TestClass2(100, 200, 200)

    println("obj3.a1 : ${obj3.a1}")
    println("obj3.a2 : ${obj3.a2}")
    println("obj3.a3 : ${obj3.a3}")

    println("obj4.a1 : ${obj4.a1}")
    println("obj4.a2 : ${obj4.a2}")
    println("obj4.a3 : ${obj4.a3}")

    println("------------------------------------------")

    var obj5 = TestClass1(100, 200, 300)
    var obj6 = TestClass1(100, 200, 300)

    // 일반 클래스를 통해 만들어진 객체들을 객체의 ID가 같은지를 비교한다.
    if(obj5 == obj6){
        println("obj5와 obj6은 같은 객체 입니다")
    } else {
        println("obj5와 obj6은 다른 객체 입니다")
    }

    var obj7 = TestClass2(100, 200, 300)
    var obj8 = TestClass2(100, 200, 300)

    // data 클래스는 주 생성자를 통해 정의된 멤버 변수의 값이 같은지를 비교한다.
    if(obj7 == obj8){
        println("obj7과 obj8은 같은 객체 입니다")
    } else {
        println("obj7과 obj8은 다른 객체 입니다")
    }

    println("------------------------------------")

    
    val obj9 = obj7.copy()
    println("obj7.a1 : ${obj7.a1}")
    println("obj9.a1 : ${obj9.a1}")

    // obj9.a1의 값을 변경한다.
    obj9.a1 = 1000
    println("obj7.a1 : ${obj7.a1}")
    println("obj9.a1 : ${obj9.a1}")

    println("------------------------------------")

    // data class를 통해 만든 객체는 주 생성자에 정의한 멤버 변수를
    // componentN 메서드로 값을 받아올 수 있다.
    val num1 = obj7.component1()
    val num2 = obj7.component2()

    println("num1 : $num1")
    println("num2 : $num2")

    println("-------------------------------------------")

    val (num10, num11) = obj7
    println("num10 : $num10")
    println("num11 : $num11")
}

// 일반 클래스
class TestClass1(var a1:Int, var a2:Int){
    
    var a3:Int = 0
    
    init{
        println("TestClass1의 init")
    }
    
    constructor(a1:Int, a2:Int, a3:Int) : this(a1, a2){
        this.a3 = a3
    }
    
    fun testMethod1(){
        println("TestClass1의 testMethod1입니다")
    }
}

data class TestClass2(var a1:Int, var a2:Int){
    var a3:Int = 0
    
    init{
        println("TestClass2의 init")
    }
    
    constructor(a1:Int, a2:Int, a3:Int) : this(a1, a2){
        this.a3 = a3
    }
    
    fun testMethod2(){
        println("TestClass2의 testMethod2 입니다")
    }
}

객체의 멤버를 보다 쉽게 관리할 수 있는 기능이 추가되어 있고, abstract, open, sealed, inner 클래스로 정의할 수 없다. 반드시 주생성자를 생성해야한다. 이렇게 주 생성자를 강제적으로 작성하는 이유는 멤버 변수를 갖기 위해서이다. 추가적으로 부생성자, init 블록을 포함 시킬 수 있다.

data class TestClass2(var a1:Int, var a2:Int){
    var a3:Int = 0
    
    init{
        println("TestClass2의 init")
    }
    
    constructor(a1:Int, a2:Int, a3:Int) : this(a1, a2){
        this.a3 = a3
    }
    
    fun testMethod2(){
        println("TestClass2의 testMethod2 입니다")
    }
}

데이터 클래스는 equals(), hashCode(), copy(), toString, ComponentN()이 제공된다.

    if(obj5 == obj6){
        println("obj5와 obj6은 같은 객체 입니다")
    } else {
        println("obj5와 obj6은 다른 객체 입니다")
    }

    var obj7 = TestClass2(100, 200, 300)
    var obj8 = TestClass2(100, 200, 300)
    
    if(obj7 == obj8){
        println("obj7과 obj8은 같은 객체 입니다")
    } else {
        println("obj7과 obj8은 다른 객체 입니다")
    }
  • equals() : 일반 클래스는 객체의 ID가 같은지 비교한다면 데이터 클래스는 주 생성자에 작성한 멤버 변수들의 값이 같다면 true, 다르면 false로 반환된다.
val obj9 = obj7.copy()
    println("obj7.a1 : ${obj7.a1}")
    println("obj9.a1 : ${obj9.a1}")
  • copy() : 객체를 복제하여 새로운 객체를 만든다.
    val num1 = obj7.component1()
    val num2 = obj7.component2()

    println("num1 : $num1")
    println("num2 : $num2")

    println("-------------------------------------------")

    val (num10, num11) = obj7
    println("num10 : $num10")
    println("num11 : $num11")
  • componentN() : 객체를 분해하기 위해 사용하는 것으로, 객체가 가지고 있는 프로퍼티를 개별 변수로 분해하여 할당한다.

제네릭

클래스나 함수를 작성할 때 타입을 추상화하여 일반화된 상태로 작성하는 것으로 코드의 재사용성과 유연성을 높일 수 있다. 제네릭을 사용하면 컴파일 할 때 객체의 자료형을 확인 하기 때문에 객체 자료형의 안정성을 높일 수 있고 형 변환의 번거러움을 줄일 수 있다.

 val t7:TestClass5<SubClass1> = TestClass5<SubClass1>()
 
 // 불변성 (제네릭에 키워드를 붙히지 않는다)
class TestClass5<A>()
  • 불변성 : 객체를 생성할 때 설정한 제네릭과 같은 변수에 담을 수 있다. 클래스간의 관계에 상관없이 제네릭에 설정한 클래스 타입이 다르면 오류가 발생한다.
// 공변성
val t10:TestClass6<SubClass1> = TestClass6<SubClass1>()
    val t11:TestClass6<SuperClass1> = TestClass6<SubClass1>()
    // val t12:TestClass6<SubClass2> = TestClass6<SubClass1>()
    
class TestClass6<out A>()
  • 공변성 : 변수에 설정한 제네릭이 객체를 생성했을 때 사용한 제네릭의 부모 클래스인 경우에도 변수에 담을 수 있다.
 val t13:TestClass7<SubClass1> = TestClass7<SubClass1>()
    // val t14:TestClass7<SuperClass1> = TestClass7<SubClass1>()
    val t15:TestClass7<SubClass2> = TestClass7<SubClass1>()

// 반 공변성
class TestClass7<in A>()
  • 반 공변성 : 변수에 설정한 제네릭이 객체를 생성했을 때 사용한 제네릭의 자식 클래스인경우에도 변수에 담을 수 있다.

중첩클래스

일반 중첩 클래스

class Outer1{
    
    var outerV1 = 100
    
    fun outerMethod(){
        println("Outer1의 outerMethod 입니다")
    }
    
    inner class Inner1{

        fun innerMethod(){
            println("outerV1 : $outerV1")
            outerMethod()
        }
    }
}

내부에 있는 클래스의 객체 생성은 외부 클래스로 부터 생성한 객체를 통해 생성할 수 있다. 내부의 클래스를 가지고 만든 객체는 외부 클래스를 통해 만든 객체가 무조건 있다는 것을 보장받을 수 있기 때문에 외부 클래스에 정의한 멤버의 접근이 자유롭다.

익명 중첩 클래스

interface Inter1{

    fun interMethod1()
    fun interMethod2()
}

class TestClass1 : Inter1{
    override fun interMethod1() {
        println("TestClass1의 interMethod1")
    }

    override fun interMethod2() {
        println("TestClass1의 interMethod2")
    }
}

interface Inter2{
    fun interMethod3()
    fun interMethod4()
}

인터페이스를 구현하거나 클래스를 상속 받은 다음에 메서드를 오버라이딩한 클래스로 만들고 위로 올라가서 객체 생성해 사용해야한다. 만약 클래스를 통해 생성하는 객체가 두 개 이상이면 클래스를 정의하고 객체 생성해서 사용한다.

null 처리

    val value1:String = str1!!
    println("value1 : $value1")

!! 연산자 : 널을 허용하는 타입의 변수 값을 널을 허용하지 않는 타입으로 변환하여 널을 허용하지 않는 타입의 변수에 담을 수 있도록 한다.

val value1:String = str1 ?: "기본문자열"
    println("value1 : $value1")

?: 연산자 : null 아닌 객체의 ID가 들어있으면 그 ID를 지정된 기본값으로 반환한다.

println("t1.str1 : ${t1?.str1}")
    println("t1.str2 : ${t1?.str2}")
    t1?.testMethod1()

?. 연산자
참조변수?.멤버변수 : 참조변수에 null값 들어 있다면 null이 반환된다.
참조변수?.멤버 메서드 : 참조변수에 null값 들어 있다면 메서드를 호출하지 않는다.

    if(str1 != null){
        val value1:String = str1
        println("value1 : $value1")
    }

만약 변수의 값이 null인 경우 코드가 동작하지 않도록 처리해주면 null 안정성을 확보할 수 있다. 이때, null을 허용하는 변수를 null을 허용하지 않는 변수처럼 자유롭게 사용할 수 있다.

profile
성장하는 개발자

0개의 댓글