Backend/코틀린
[Kotlin] 클래스, 상속, 접근제어, object
지수쓰
2023. 3. 20. 17:10
반응형
출처 - 인프런 자바개발자를 위한 코틀린 입문 강의 ( 9~14강 )
https://www.inflearn.com/course/java-to-kotlin
CH9. 클래스를 다루는 방법
- 코틀린에서는 필드를 만들면 getter와 setter를 자동으로 만든다.
- 필드 하나를 프로퍼티라고 부름
- 코틀린에서는 주 생성자가 필수임
- custom getter, setter를 만들 수 있음
생성자
class Person {
val name: String,
var age: Int,
} {
init { // 가장먼저호출
if (age < 0) {
throw new Exception()
}
}
constructor(name: String): this(name, 1){
println("첫번째 부 생성자")
}
}
부생성자보다는 default parameter 또는 정적팩토리메서드를 권장함
class Person {
val name: String = "홍길동",
}
custom getter
class Person {
val name: String,
var age: Int,
} {
// 원래함수형태
fun isAdult(): Boolean {
return this.age >= 20
}
// Custom getter
val isAdult: Boolean {
get() = this.age >= 20
}
}
- 객체의 속성을 나타내는것 : custom getter
- 아니면 함수형태
예) 이름 대문자로 바꾸는 예제
class Person (
name: String = "jisu",
) {
val name = name
get() = field.uppercase()
//get() = name.uppsercase() 이렇게쓰면 무한루프 발생돼서 자기자신 가리키는 필드를 의미하는 field 예약어 씀 (backing field)
}
class Person (
name: String = "jisu",
) {
val uppercaseName: String
get() = this.name.uppercase()
}
backing field말고 this.name 으로 써도 됨
custom setter
setter 자체는 지향하기때문에 custom setter는 많이 안 씀
class Person (
name: String = "jisu",
) {
var name = name
set(value) {
field = value.uppercase()
}
}
CH10. 상속을 다루는 방법
- 상속 또는 구현을 할 때 : 사용
- 상위 클래스 상속을 구현할 때 생성자를 반드시 호출해야 한다
- override를 필수로 붙여야 한다
- 추상멤버가 아니면 기본적으로 오버라이드가 불가능 -> open 사용
추상클래스
abstract class Animal(
protected val species: String,
protected open val legCount: Int, // 프로퍼티 오버라이드할 때 open 붙여주어야함
) {
abstract fun move()
}
class Cat (
species: String
) : Animal(species, 4){ // :로 상속받으면서 바로 super를 불러줌
override fun move() { // 어노테이션 대신 override
println("고양이")
}
}
class Penguin(
val species: String
) : Animal(species, 2) {
private val wingCount: Int = 2
override fun move() {
println("펭귄")
}
override val legCount: Int
get() = super.legCount + this.wingCount
}
java의 extends 대신 : 를 사용해서 상속받음
- 변수 타입은 species: String한 칸 안띄고 : 씀
- 상속받을 때는 Cat () : Animal 한칸 띄고 씀
open 키워드
- 프로퍼티 오버라이드 할 때 open을 붙여줘야 함
인터페이스
interface Flyable {
fun act() { // default 쓰지 않아도 java default함수처럼 됨
println("파닥파닥")
}
fun fly() // 추상메서드
}
interface Swimable {
println("어푸 어푸")
}
class Penguin(
val 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() // 특정 상위 인터페이스 함수 오버라이드할 때 꺽쇠로 표현
super<Flyable>.act()
}
}
- 구현은 :
- 중복 인터페이스 특정할 때는 super<타입>
클래스 상속할 때 주의할 점
- 코틀린은 클래스를 상속받으려면 open 키워드를 써줘야 함. 기본은 불가능
- 상위 클래스를 설계할 때 생성자 또는 초기화블록에 사용되는 프로퍼티에는 open을 피해야 한다.
상속 관련 키워드
- final : override 할 수 없게 함. default값
- open : override를 열어준다
- abstract: override 강제
- override: 상위 타입을 오버라고 하고 있다는 의미로 사용하는 키워드
CH11. 접근제어를 다루는 방법
가시성 제어
javakotlin
public | 모든 곳에서 접근 가능 | 모든 곳에서 접근 가능 |
protected | 같은 패키지 또는 하위클래스 | 선언된 클래스 또는 하위클래스 |
default | 같은 패키지에서만 사용 가능 | |
internal | 같은 모듈에서만 접근 가능 | |
private | 선언된 클래스 내에서만 가능 | 선언된 클래스에서만 접근 가능 |
- 코틀린에서는 패키지를 가시성제어에는 사용하지 않음 -> 모듈
- kotlin의 기본 접근지시 : public ( java는 default )
파일의 접근제어(. kt)
- 파일에 변수, 함수, 클래스 여러 개 만들 수 있음
- protected는 클래스단위로 쓰이는 거라 파일에서는 사용할 수 없음
기타
- 생성자에 접근지시어를 붙이려면 생략된 constructor를 명시해서 써야 함
- class Cat internal constructor
- 파일최상단에 .kt 유틸용 파일을 만들면 편하다
- 자바에서 static 메서드 + private생성자 만들어 쓴 거랑 비슷
- 프로퍼티 가시성은 생성자에 지시어를 붙이거나, custom getter, setter 만들면서 get, set에다가 만 붙여줄 수 있음
- 자바와 코틀린이 protected, internal이 의미가 달라서 같이 쓸 때 주의해야 함
CH12. object 키워드
static 함수와 변수
class Person private constructor (
var name: String,
var age: Int,
) {
companion object Factory : Log { // Log 인터페이스 구현
private const val MIN_AGE = 0 // const: 진짜 상수
//@JvmStatic
fun newBaby(name: String): Person {
return Person(name, MIN_AGE)
}
override fun log() {
println("Person 클래스 동행객체 Factory")
}
}
}
interface Log {
fun log()
}
- 코틀린은 static 키워드 대신 companion object 사용
- static : 클래스가 인스턴스화될 때 새로운 값이 복제되는 게 아니라 정적으로 인스턴스끼리의 값 공유
- companion object: 클래스와 동행하는 유일한 오브젝트
- val과
- val : 런타임시 할당
- const val : 컴파일 시 할당, 진짜 상수. 기본값과 String에만 붙일 수 있다
- companion object는 하나의 객체로 간주되어 인터페이스 구현, 이름 짓기 모두 가능
- 자바에서 코틀린의 companion을 사용하고 싶을 때
- 이름이 없을 때
- Person.Companion.newBaby("ABC")
- @JvmStatic 어노테이션 붙여줬을 때
- Person.newBaby("ABC")
- 이름 있을 때
- Person.Factory.newBaby("ABC")
- 이름이 없을 때
싱글톤 : 단 하나의 인스턴스만 갖는 클래스
자바의 싱글톤
public class JavaSingleton {
private static final JavaSingleton INSTANCE = new JavaSingleton();
private JavaSingleton() { }
public static JavaSingleton getInstance() {
return INSTANCE;
}
}
코틀린의 싱글톤 -> object만 붙여주면 됨
//생성
object Singleton {
var a: Int = 0
}
//사용
fun main() {
println(Singleton.a)
}
익명 클래스
특정 인터페이스 클래스를 상속받은 구현체를 일회성으로 사용할 때 파라미터에 object : 타입이름 써서 구현
fun main() {
moveSomething(object : Movable {
override fun move() {
println("무브")
}
override fun fly() {
println("플라이")
}
})
}
private fun moveSomthing(movable: Movable) {
movable.move()
movable.fly()
}
CH13. 중첩 클래스
중첩 클래스 종류 (java 기준)
static을 사용하는 중첩클래스 : 코틀린 기본(권장)
- 클래스 안에 static을 붙인 클래스는 바깥 클래스 직접 참조 불가
-
class House( private val address: String, private val livingRoom: LivingRoom ) { class LivingRoom( private val area: Double ) }
static을 사용하지 않는 중첩클래스
- inner class : 코틀린에서 inner 키워드 붙여줘야함
- 바깥 클래스 참조 가능 this.@바깥클래스
-
class House( var address: String, ) { var livingRoom = this.LivingRoom(10.0) inner class LivingRoom( private val area: Double ) { val address: String get() = this@House.name // 바깥클래스 참조 } }
- local class
- anonymous class
class House(
private val address: String,
private val livingRoom: LivingRoom
) {
class LivingRoom(
private val area: Double
)
}
class House(
private val address: String,
private val livingRoom: LivingRoom
) {
class LivingRoom(
private val area: Double
)
}
CH14. 다양한 클래스 다루는 방법
Data Class
- getter, setter
- equals and hashCode
- toString
data class PersonDTO(
val name: String,
val age: Int,
)
// 자동으로 다 만들어줌
- java에서도 jdk16부터 비슷한 record class 도입했음
Enum Class
- 특징 : 상속 X , 인터페이스 구현O, 싱글톤
- kotlin에서 when 으로 분기처리를 쉽게 할 수 있음
- else 사용 X
- enum 변화 체크
enum class Country (
private val code: String,
) {
KOREA("KO"),
AMERICA("US")
;
}
fun handleCountry(country: Country) {
when (country) {
Country.KOREA -> TODO()
Country.AMERICA -> TODO()
// else 를 작성 할 필요가 없음
// enum에 다른 값이 추가되면 경고를 줌
}
}
Sealed Class, Sealed Interface
- 등장배경 ) 상속이 가능하도록 추상클래스를 만들지만 외부에서는 이 클래스를 상속받지 않았으면 좋겠음
- 컴파일 타임 때 하위 클래스의 타입을 모두 기억해서 런타임 때 클래스 타입이 추가될 수 없음
- 하위 클래스는 같은 패키지에 있어야 함
- Enum과 다른 점
- 클래스 상속 O
- 하위 클래스 멀티 인스턴스 가능
- 추상화가 필요한 Entity나 DTO에서 사용하고 JDK17에서도 sealed Class가 추가되었음
/// 선언
sealed class HyundaiCar(
val name: String,
val price: Long
)
// 같은 패키지 또는 같은 파일에 구현되어야함
class Avante : HyundaiCar(~~)
class Sonata : HyundaiCar(~~)
class Grandeur : HyundaiCar(~~)
/// 사용
fun main() {
handleCar(Avante())
}
private fun handleCar(car: HyundaiCar) {
when (car) {
is Avante -> TODO()
is Grandeur -> TODO()
is Sonata -> TODO()
}
}