Personal project/50Buttons

String과 CharSequence의 차이점?이 궁금했다.

닉네임도항상고민 2023. 5. 23. 11:05

https://github.com/hhyeok1026/50Buttons

 

GitHub - hhyeok1026/50Buttons: This is a development exercise project.

This is a development exercise project. Contribute to hhyeok1026/50Buttons development by creating an account on GitHub.

github.com

 

내 연습용 프로젝트에서 두 번째 버튼을 만들면서 궁금했던 점.

 

xml뷰의 Button에서, setText, getText는 파라미터로 CharSequence라는 인터페이스를 가지고 있다.

보통은 String을 많이 쓰니까 CharSequence가 뭔지 궁금해졌다.

 

 

 

블로그를 좀 찾아봤지만, 내 궁금증을 풀어줄 만큼 제대로 설명해 주는 블로그는 별로 보이지 않는 것 같고, 블로거들이 제대로 이해를 못 하고 있는 것 같았다.

사실, 나 같은 경우에는 String와 CharSequence의 그 자체의 설명이 아니라, 언어적으로 인터페이스, 클래스, 캐스팅에 대한 개념이 필요했다.

 

그래서 chatGPT와 함께 대화를 해보았다.

 

 

 

우선, chatGPT와 얘기한 것 중 중요한 것 3가지를 뽑자면,

 

1. 인터페이스와 인터페이스를 상속받아 구현체가 되는 클래스에 대한 이해가 필요하다.

1-1. ) 인터페이스는 구현체가 되는 클래스의 인스턴스를 가질 수 있다는 것을 알아야 한다.

 

2. 업캐스팅과 다운캐스팅에 대한 이해가 필요하다.


(chatGPT가 알려준 내용을 내가 이해한 것을 정리한 것이므로,

틀린 내용이 있을 수 있다는 것을 감안하여 보시길)

 

1. 인터페이스와 인터페이스를 상속받아 구현체가 되는 클래스에 대한 이해가 필요하다.

 

String은 Kotlin에서 문자열을 저장하고 조작하기 위해 가장 일반적으로 사용되는 클래스입니다. String은 불변(immutable)이며, 한 번 생성되면 변경할 수 없습니다. 따라서 String 객체를 수정하려면 새로운 String 객체를 생성해야 합니다. 예를 들어, 문자열을 연결하거나 분할하려면 새로운 String 객체를 생성합니다.

반면, CharSequence는 문자열의 읽기 전용 시퀀스를 나타내는 인터페이스입니다. CharSequence를 구현한 클래스들은 문자열을 저장하고 조작할 수 있습니다. 이 인터페이스를 구현한 클래스 중 가장 널리 사용되는 것은 String 클래스입니다. 그러나 CharSequence를 사용하면 String 이외의 다른 클래스(예: StringBuilder)도 사용할 수 있습니다. CharSequence를 사용하면 문자열을 수정하지 않고도 문자열 조작 메서드를 호출할 수 있으므로 일부 상황에서 더 효율적일 수 있습니다.

 

 

1-1. ) 인터페이스는 구현체가 되는 클래스의 인스턴스를 가질 수 있다는 것을 알아야 한다.

 

인터페이스는 스스로 인스턴스화되거나 값을 가지고 있지 않다.

CharSequence는 그 구현체인 String값을 대입받을 수 있게 되는데,

 

예시코드로,

val charSequence: CharSequence = "Hello, World!"

 

우측의 문자열 리터럴은 코틀린에서 String으로 인스턴스 처리된다.

그러면, CharSequence는 String값을 할당받게 되는데,

이때 CharSequence와 String은 상속 관계에 있고, 캐스팅된다. (업캐스팅)

그렇다면 캐스팅에 대한 이해가 필요하겠다.


 

 

2. 업캐스팅과 다운캐스팅에 대한 이해가 필요하다.

 

 

업캐스팅 (Upcasting):
업캐스팅은 하위 타입을 상위 타입으로 변환하는 것을 의미합니다.
상위 타입은 더 일반적인(더 추상적인) 개념을 나타내는 타입입니다.
업캐스팅은 자동으로 이루어지며, 별도의 캐스팅 연산자가 필요하지 않습니다.
업캐스팅은 데이터 손실이 없는 안전한 변환입니다.

 

다운캐스팅 (Downcasting):
다운캐스팅은 상위 타입을 하위 타입으로 변환하는 것을 의미합니다.
하위 타입은 더 구체적인(더 특수한) 개념을 나타내는 타입입니다.
다운캐스팅은 명시적인 캐스팅 연산자(예: as, is)를 사용하여야 합니다.
다운캐스팅은 데이터 손실이 발생할 수 있는 불안전한 변환입니다.
다운캐스팅을 수행하기 전에 원본 객체가 해당 타입으로 실제로 적합한지 확인해야 합니다. (예: is 연산자를 사용하여 타입을 확인한 후 다운캐스팅)

 

open class Animal {
    // ...
}

class Dog : Animal() {
    // ...
}


// 업캐스팅 예시
val animal: Animal = Dog()


// 다운캐스팅 예시
val animal: Animal = Dog()
if (animal is Dog) {
    val dog: Dog = animal
    // 다운캐스팅이 성공했으므로 dog 변수에 할당 가능
    // ...
}

 

익히 알고 있지만, 계속 헷갈렸던 업캐스팅과 다운캐스팅 개념이다.

저 설명에서 나는 의아한 점이 있었는데,

업캐스팅은 안전하고, 다운 캐스팅은 안전하지 않음으로 명시적으로 해야 한다는 것이다..

내 생각으로는 메모리구조적으로 보면, 하위타입이 상위타입보다 구현되는 게 더 많고 정보를 더 많이 가지고 있을 것이고,

그 하위타입을 상위타입으로 바꾸면, 데이터가 잃어서 업캐스팅은 안전하지 않은 거 아닌가? 생각했다.

 

하지만,

'캐스팅'은 데이터의 양이나 메모리 구조에는 영향을 주지 않는다고 한다.

 

그리고 바뀔 타입을 기준으로 , 그 바뀐 타입을 사용하는데 문제가 없음에 대해, 안전하다고 말하는 거였다.

dog에서, animal로 변경하게 되면,

animal의 있는 기능을 모두 안전하게 사용가능하다는 의미다.

 

그리고 데이터나 메모리에 영향을 주지 않으므로,

다시 다운캐스팅을 하게 되면, dog에 있던 기능을 다시 제대로 쓸 수 있게 되는 거라고 한다.

(테스트를 안 해봐서 gpt피셜을 완전히 믿을 수는 없다.)

 

하지만 원래 dog로 인스턴스 된 게 아니라,

애초에 animal로 인스턴스 된 것을 dog로 바꾸게 되면, dog의 기능을 완전히 사용하지 못할 수 있으므로,

다운캐스팅은 개발자가 주의해서 해야 하고 명시적으로 하게 되는 것인듯하다.


 

정리하자면,

String은 클래스,

CharSequence는 인터페이스

 

String은 CharSequence의 구현한 클래스 중 하나이다.

CharSequence에 대입되는 것은 CharSequence를 구현한 클래스의 인스턴스가 들어가는 것

 

String이 CharSequence에 대입되면, 자동으로 업캐스팅이 되고(업캐스팅은 안전하기 때문),

CharSequence의 기능을 사용할 수 있게 된다. (하지만, 업캐스팅 되면 String으로서는 사용 못함.)