본문 바로가기
Programming/Kotlin

Java와 Kotlin 비교 : map, 고차함수, 클래스 - 상속 인터페이스, 코틀린의 property 개념

by Renechoi 2023. 10. 22.

map



public class Exam04 {

    public Exam04(){
        var hashMap = new HashMap<String, Object>();
        hashMap.put("key", "value");
        hashMap.put("key", 10);

        var map = Map.of(
            "key1","value1",
            "key2","value2",
            "key3","value3"
        );

        hashMap.get("key");
    }
}

fun Main() {
    // 코틀린에서도 마찬가지로 key value 형식으로 map을 만들 수 있다. -> 기본형태는 immutable 

    // java의 Object -> 코틀린에서는 Any
    // pair라는 문법 
    val map = mapOf<String, Any>(
        Pair("", ""),
        "key" to "value"
    )

    // 만약 mutable로 하려면 
    val mutableMap = mutableMapOf(
        "key" to "value"
    )

    mutableMap.put("key", "value");
    mutableMap.put("key2", "value2");

    // 이렇게도 쓸 수 있다.
    mutableMap["key"] = "value";


    val value = mutableMap["key"];

    val hashMap = hashMapOf<String, Any>()


    // kotlin에는 triple이라는 자료형태도 있음 
    var triple = Triple<String, String, String>(
        first = "",
        second = "",
        third = ""
    )

}

기본적인 Map 생성

Java

var hashMap = new HashMap<String, Object>();
hashMap.put("key", "value");
hashMap.put("key", 10);

Kotlin

val map = mapOf<String, Any>(
    "key" to "value"
)
  • Java에서는 HashMap 클래스를 이용해 Map을 생성하고, put 메서드로 키와 값을 추가한다.
  • Kotlin에서는 mapOf 함수를 이용하여 불변의 Map을 생성할 수 있다. 키와 값은 to 키워드를 이용하여 연결한다.

불변과 가변 Map

Java

var map = Map.of(
    "key1", "value1",
    "key2", "value2",
    "key3", "value3"
);
  • Java에서는 Map.of 메서드를 이용하여 불변의 Map을 생성할 수 있다.

Kotlin

val mutableMap = mutableMapOf(
    "key" to "value"
)
mutableMap.put("key2", "value2");
  • Kotlin에서는 mutableMapOf 함수를 이용하여 가변의 Map을 생성할 수 있다. 추가적으로 값을 넣을 때는 put 메서드나 대괄호([])를 이용할 수 있다.

값 가져오기

Java

hashMap.get("key");

Kotlin

val value = mutableMap["key"];
  • Java와 Kotlin 모두 키를 이용하여 값을 가져올 수 있다. 하지만 Kotlin에서는 대괄호를 이용하여 좀 더 간결하게 값을 가져올 수 있다.

Kotlin의 추가적인 기능

  • Kotlin에서는 Triple이라는 자료형도 제공한다. 이는 세 개의 다른 타입의 값을 하나로 묶을 수 있다.
var triple = Triple<String, String, String>(
    first = "",
    second = "",
    third = ""
)

고차함수

이번에는 Java와 Kotlin에서 고차함수와 람다식을 어떻게 다루는지 알아보자.


public class Exam05 {

    private Predicate<String> stringPredicate = new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.equals("?");
        }
    };

    // 고차함수 -> 함수를 파라미터로 넘기기
    public Exam05(){

        var strList = List.of(
            "1",
            "2",
            "홍길동",
            "ㅇㅇㅇ"
        );

        // predicate라는 functional interface를 람다식으로
        var result = strList.stream().filter(it->{
            return it.equals("?");
        }).toList();


        // 위에서 선언한 stringPredicate 를 넘겨줄 수도 있다. 
        var result2 = strList.stream().filter(stringPredicate).toList();


        // 어차피 메서드가 하나이기 때문에 화살표 함수 -> 람다식으로 표현한 것임 

        // 코틀린에서는 ? 
    }

}

fun main() {

    var numberList = listOf<Int>(1,2,3,4,5)

    numberList.first{it % 2 == 0}

    val pred1 = object : Predicate<Int> {
        override fun test(t: Int): Boolean{
            return t%2 ==0
        }
    }

    numberList.stream().filter(pred1)

    // 2가지 변수를 받아서 x+y를 리턴 
    val add = { x: Int, y:Int -> x+y}

    println(add(2,3))

    // 익명 메서드를 만들어서 넘겨줄 수 있음 -> 자바에서는 인터페이스가 있어야 하지만     
    val _add = fun(x:Int, y:Int):Int {
        return x+y
    }

    println(_add(3,4))

    lambda(4,5, _add)

    // 코틀린에서는 이렇게 메서드를 넘겨주는 방식이 다양하고 간편 
    // 고차함수, 람다식을 좀 더 편하게 사용 

    numberList.map{
        it-> 
        val t = it
    }
}

// 메서드는 Int 두개를 받아서 Int를 넘겨준다
fun lambda(x:Int, y:Int, method:(Int, Int)-> Int){
    println(method(x,y))
}

고차함수란?

고차함수는 다른 함수를 인자로 받거나 결과로 반환하는 함수.

Java에서의 고차함수와 람다식

Java 8부터 람다식을 지원한다. Predicate 같은 함수형 인터페이스를 이용하여 람다식을 사용할 수 있다.

var strList = List.of(
    "1",
    "2",
    "홍길동",
    "ㅇㅇㅇ"
);

var result = strList.stream().filter(it->{
    return it.equals("?");
}).toList();

Kotlin에서의 고차함수와 람다식

Kotlin은 람다식과 고차함수를 아주 간결하게 표현할 수 있다.

val add = { x: Int, y: Int -> x + y }
println(add(2, 3))

val _add = fun(x: Int, y: Int): Int {
    return x + y
}

println(_add(3, 4))

fun lambda(x: Int, y: Int, method: (Int, Int) -> Int) {
    println(method(x, y))
}
  • Kotlin에서는 함수를 다양한 방식으로 넘길 수 있다. 람다식을 이용하거나 익명 함수를 선언해서 넘길 수도 있다.

예제 비교

Java

var result2 = strList.stream().filter(stringPredicate).toList();
  • Java에서는 Predicate라는 함수형 인터페이스를 이용한다.

Kotlin

lambda(4, 5, _add)
  • Kotlin에서는 직접 함수를 인자로 넘길 수 있다.

결론

Java와 Kotlin, 둘 다 고차함수와 람다식을 지원하지만, Kotlin이 더 다양한 방식을 제공하며 코드도 간결하다. Kotlin을 이용하면 람다식과 고차함수를 더 효율적이고 간단하게 사용할 수 있다.

Kotlin이 고차함수를 다루는 것을 보면 react, typescript 에서 사용하는 함수형 문법이 떠오르는데, 함수형 표현이 좀 더 자유로워지는 거 같다. 확실히 동적이고 유연한 프로그래밍에 있어서 더 편하고 쉽다는 점에서, 장점이 잘 드러나는 것 같고 그 때문에 함수형 프로그래밍이 점점 더 각광을 받는지도...

클래스 - 상속과 인터페이스


public class Exam06 {
    public Exam06(){
        var dog = new Dog("퍼피");
        dog.eat();
        dog.bark();
    }
    public static void main(String[] args) {
        new Exam06();
    }
}

@Getter
abstract class Animal implements Bark{
    private String name;
    public Animal(String name){
        this.name =name;
    }

    public void eat(){
        System.out.println("name = " + name);
    }
}


class Dog extends Animal{

    public Dog(){
        super("");
    }

    public Dog(String name){
        super(name);
    }

    @Override
    public void bark() {
        System.out.println(this.getName() + "bark");    
    }
}

interface Bark{
    void bark();
}

fun main() {
    // new 키워드 없이도 생성 
    val dog = Dog("해피")

    dog.eat()
    dog.bark()
}


interface Bark {
    fun bark()
}

abstract class Animal (
    // 코틀린에서는 생성자 부분이 바로 선언이 됨 
    private val name: String? = "") : Bark{

    // body - > 

    fun eat() {
        println("$name 식사 !")
    }
}

// 이렇게만 선언해도 됨 
//class Dog()


// Animal을 상속하는데 null을 받아도 되기 때문에 이렇게 호출이 가능함 
//class Dog(): Animal()


// 생성자를 받아서 슈퍼라는 키워드로 넘기는 것  표현 
class Dog(
    private val name:String?=null
) : Animal(name), 
    // Temp 인터페이스 다중 상속 -> 파라미터 없으므로 그냥 상속 
    Temp{
    override fun bark() {
        println("$name : 멍멍")
    }

    override fun hi() {
        println("hi")
    }
}

interface Temp{
    fun hi()
}

상속

Java

Java에서 상속을 사용할 때에는 extends 키워드를 사용한다. 생성자에서 부모 클래스의 생성자를 호출할 때에는 super 키워드를 사용하며, 이는 반드시 서브 클래스의 생성자 첫 라인에 와야한다.

public class Dog extends Animal {
    public Dog(String name){
        super(name);
    }
}

Kotlin

Kotlin에서는 : (콜론)을 사용하여 상속을 나타낸다. 여기서 super 키워드를 사용하여 부모 클래스의 생성자를 호출할 수 있다.

class Dog(
    name: String? = null
) : Animal(name)

인터페이스

Java

Java에서 인터페이스는 interface 키워드로 선언된다. 구현 클래스에서는 implements 키워드를 사용하여 인터페이스를 구현한다.

public class Dog extends Animal implements Bark {
    @Override
    public void bark() {
        System.out.println(this.getName() + "bark");
    }
}

Kotlin

Kotlin에서도 interface 키워드로 인터페이스를 선언한다. 구현은 상속과 동일하게 : (콜론)을 사용하여 나타낸다.

class Dog(
    name: String? = null
) : Animal(name), Bark, Temp

getter와 setter 차이

Java

Java에서는 getter와 setter 메서드를 명시적으로 작성해야 한다.

public class Animal {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Kotlin

Kotlin에서는 getter와 setter가 자동으로 생성된다. 이로 인해 코드가 더 간결해진다. 물론, 커스텀 getter나 setter를 정의할 수도 있다.

class Animal(var name: String) {
    // ...
}

코틀린에서 캡슐화를 하고 싶다면?

코틀린에서는 접근 제어자를 사용하여 캡슐화를 할 수 있다. 예를 들면,

  1. private: 해당 요소가 선언된 파일 또는 클래스 내에서만 접근 가능.
  2. protected: 해당 클래스와 그 하위 클래스에서만 접근 가능.
  3. internal: 같은 모듈 내에서 접근 가능.
  4. public: 어디서든 접근 가능. (기본값)

예를 들어, 클래스의 속성을 private로 설정하면 외부에서 직접 접근할 수 없고, getter나 setter 메서드를 통해서만 접근할 수 있다.

class Animal {
    private var name: String = "Unknown"

    fun setName(name: String) {
        this.name = name
    }

    fun getName(): String {
        return this.name
    }
}

코틀린에서 사용하는 property에 대해

코틀린에서는 프로퍼티(property)라는 개념이 중요하다. 자바에서는 일반적으로 필드와 이에 대응하는 getter, setter 메서드를 가지지만, 코틀린에서는 이런 것들을 한 번에 다루는 방식을 제공한다.

기본 프로퍼티 선언

기본적인 프로퍼티 선언은 다음과 같다.

var propertyName: Type = initialValue

또는 초기값이 없고, 나중에 할당할 것이라면:

var propertyName: Type

커스텀 getter와 setter

코틀린에서는 프로퍼티에 커스텀 getter와 setter를 쉽게 추가할 수 있다.

var myProperty: Int = 0
    get() = field
    set(value) {
        if (value >= 0) {
            field = value
        }
    }

lateinit과 lazy

lateinitby lazy 두 키워드를 통해 지연 초기화(lazy initialization)를 지원한다.

  • lateinit: 나중에 초기화할 것이라는 것을 알리는 키워드. 주로 의존성 주입(DI)에 사용된다.
  • by lazy: 이 키워드는 프로퍼티가 처음으로 접근될 때 초기화되게 한다.
lateinit var lateInitProperty: String
val lazyProperty: String by lazy { "I'm lazy!" }

Data 클래스

코틀린의 data 클래스는 자동으로 equals(), hashCode(), toString() 등을 구현해주며, var 또는 val로 선언된 프로퍼티에 대해 자동으로 getter와 setter를 제공한다.

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

 

코틀린의 프로퍼티 기능은 코드를 훨씬 간결하고 안전하게 만들어준다. 

 

 


reference:

반응형