interface Person {
var name: String
val age: Int
}
- 왜 이렇게 사용하지?
- 인터페이스를 사용하는 클래스에 공통 프로퍼티 명시적 정의
- 코드의 가독성을 높이고 사용성 향상-
- 구현체에 프로퍼티 구현
- 그닥 와닿지 않는다.
interface MyInterface {
fun doSomething()
}
interface MyInterface {
val data: String
fun doSomething()
}
class MyClass(override val data: String) : MyInterface {
override fun doSomething() {
println("Doing something with $data")
}
}
fun main() {
val obj = MyClass("Hello")
obj.doSomething() // Doing something with Hello
println(obj.data) // Hello
}
- 예시코드를 보면서 생각에 잠겼다.
- 결국 인터페이스에 프로퍼티를 선언하는건.
- 인터페이스를 구현할 클래스가 메소드를 사용할때 좀더 명확히 설명해주는 역할?
- 구현체에서 반드시 그 프로퍼티르 구현해야함을 명시적으로 나타내는거 같다.
- 프로퍼티를 선언하고 사용하지 않으면?? 컴파일 오류 발생!
- 결론적으로 인터페이스에 프로퍼티를 선언하면 구현체에 프로퍼티로 뭔가를 하는 명시적 역활
## 마지막 예시
interface Car {
val maxSpeed: Int // 최대 속도 프로퍼티
fun start()
fun stop()
}
class SportsCar : Car {
override val maxSpeed: Int = 300 // 명시적으로 구현
override fun start() { /* 구현 */ }
override fun stop() { /* 구현 */ }
}
코드의 안정성과 가독성을 높여 준다.
++ getter ,setter 자동으로 생성됨. 구현체는 자동으로 쓸 수 있다.
++ 간결해진다 코드가.
코틀린의 내부클래스는 기본적으로 자바에서 정적 중첩클래스로 선언되 외부클래스 참조X
이는 중첩 클래스가 외부 클래스의 인스턴스에 대한 참조를 갖지 않기 때문
ineer 키워드 사용하면 외부 클래스 인스턴트 참조를 가져서 클래스 멤버 접근가능
내부 클래스시 외부클래스의 참조를 가지고 있어서
이로인한 메모리 사용량 증가,.
객체지향 설계 -> 바깥 클래스와 결합도가 낮아
의존성 역전원칙을 적용하기 쉬워짐.
바 깥클래스의 인스턴스가 변경되어도 중첩 클래스에는 영향을 주지않음
class OuterClass(private val outerField: Int) {
class NestedClass(private val nestedField: Int) {
fun printFields(outer: OuterClass) {
println("outerField: ${outer.outerField}")
println("nestedField: $nestedField")
}
}
}
val outer = OuterClass(2)
nested.printFields(outer) // 출력: outerField: 2, nestedField: 1
이런식으로 전달 해야됨
자바 8과 비슷하게 구현 메소드 정의 가능.
구현체에서 사용할땐 : (인터페이스 명)
override 변경자 필수 - 상위 클래스 메소드와 같을경우 명시하지않으면 컴파일 오류
코틀린에선 인터페이스경우 final,open ,abstract 사용하지않음.
인터페이스는 항상열려있어 Final로 변경할 수없고 ,abstract 붙일 필요 없다.
코틀린에서 인터페이스는 자동으로 open으로 취급되기 때문에 final, open, abstract 키워드를 사용하지 않습니다.
인터페이스는 final 키워드를 사용할 수 없습니다
코드 기반에 선언에 대한 클래스 외부접근을 제어함.
클래스 구현에 대한 접근을 제한함으로서 클래스 의존하는 외부코드를 깨지않고 내부 구현 변경 가능
코틀린의 기본가시성은 Public
코틀린의 패키지는 네임스페이스에만 이용 (가시성제어 X)
대신 코틀린에선 최상위 선언부터 모든 클래스에대해 가시성 제한자 지정해 줘야됨.
이렇게하면? 클래스나 멤버에대해 접근을 명확히 정해 줌!
모듈 내부에서만 본다는 코틀린의 새로운 가시성
모듈 구현에 대해 캡슐화 제공 및 자세한 구현은 외부에 감출때 유용
정리하면 internal은 각 모듈별로 무슨 역할만 알고있고 내부구현은 몰라도되는 캡슐화가 됨.
internal class InternalClass {
fun someFunction() {
// ...
}
}
// file2.kt
import some.package.InternalClass
class OtherClass {
fun someFunction() {
val internalClass = InternalClass() // 컴파일 에러!
internalClass.someFunction() // 컴파일 에러!
}
}
1. 코틀린은 하나의 모듈로 구성되면 이 파일은 한번에 컴파일 되고 동시실행됨.
2. 이 파일들으 서로 내부 구현을 알수 있지만...
3. Internal 가시성 제한자 사용하면 같은 모듈 만 내부에서 접근가능
4. 다른 모듈은 몰라도되..
package com.example;
class PackagePrivateClass {
// 클래스 내용
}
위 예시 코드에서 PackagePrivateClass 클래스는 com.example 패키지 내에서만 접근 가능한 클래스입니다. 따라서 com.example 패키지 외부에서는 접근할 수 없음
interface State: Serializable
interface View {
fun getCurrentState : State
fun restoreState (state: State) ()
}
public class Button implements View {
@Override
public State getCurrentState() {
return new ButtonState();
}
@Override
public void restoreState(State state) {
// Implementation details go here
}
public static class ButtonState implements State {
// Implementation details go here
}
}
기본적으로 자바는 Inner class가 된다.
내부 클래스인 ButtonState는 Button 클래스를 묵시적으로 참조하고 있고.
Button을 참조하고 있어서 직렬화가 불가능하기 때문! (외부클래스도 직렬화가가능해야되니깐)
문제 해결을위해 Inner class 대신 static으로 ButtonState클래스를 선언해야 됨.
class Button : View {
override fun getCurrentState(): State {
return ButtonState()
}
override fun restoreState(state: State) {
// Implementation details go here
}
class ButtonState : State {
// Implementation details go here
}
}
코틀린은 기본적으로 중첩 클래스라 바깥클래스를 참조하지 않아 오류가 발생하지 않음
책에서는 코틀린은 Inner Class를 명시하거나 자바는 static을 명시해서 바꿔 주라.
클래스 계층을 만들때 계층 속 클래수 제한 할때 편리?
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int =
when (e) {
is Expr.Num -> e.value
is Expr.Sum -> eval(e.left) + eval(e.right)
}
fun main() {
val num1 = Expr.Num(3)
val num2 = Expr.Num(4)
val sum = Expr.Sum(num1, num2)
val result = eval(sum)
println("The result of the expression is: $result")
}
sealed 클래스는 상위 클래스에 sealed 변경자를 붙여 하위 클래스의 정의를 제한
그러므로 디폴트 분기가 필요가 없어짐.
만약 없다면 새로운 클래스 추가시 잊어버려 디폴트분기로 빠져서 버그가 발생할 수 있다.
글고 자동으로 open 이므로 별도로 붙일 필요없음
그리고 새로운 클래스 추가시 when절에서 분기를 추가하지 않을시 컴파일 에러가남
class User constructor(val nickname: String) {
init {
println("User initialized with nickname: $nickname")
}
}```
open class View constructor(ctx: Context) {
constructor ( c t : Context) {
}
constructor(ctx: Context, attr: AttributeSet) : this(ctx) {
// attr를 이용한 추가 작업 수행
}
}
class MyButton : View {
constructor(ctx: Context) : super(ctx) {
// Initialize view
}
constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
// Initialize view
}
}
부 생성자는 constructor로 시작함.
super() 키워드를 통해 자신에 대응하는 상위 클래스 생성자 호출!
constructor (ctx: Context): this (ctx, MYSTYLE) 이런식으로 자신의 다른생성자 호출.
부 생성자는 반드시 상위클래스를 초기화 하거나 다른 생성자에게 생성위임
클래스에주 생성자가없다면모든부 생성자는 반드시 상위 클래스를초기화하거나
다른생성자에게생성을위임해야한다.
부 생성자는 생성자 오버로딩을 구현하기 위해 사용되며, 다양한 매개변수 조합을 지원하기 위해 활용
class Person(val name: String, val age: Int) {
// 주 생성자로 프로퍼티를 정의하고 초기화
// 생략 가능한 경우 생략 가능
init {
println("Name: $name, Age: $age")
}
// 부 생성자
constructor(name: String) : this(name, 0) {
println("Name: $name")
}
}
fun main() {
val person1 = Person("John", 30) // Name: John, Age: 30
val person2 = Person("Kate") // Name: Kate, Age: 0
}
이런 식으로 예시도 들수 있다.
여러가지 예시가 있지만.. 디폴트 파라미터로 왠만한건 처리될거 같아 부생성자 사용에대해 의문이 생김.
인터페이스에는 선언된 추상프로퍼티는 하위클래스에 반드시 오버라이드 되야됨.
인터페이스는 상태를 가질수 없으므로 구현체는 오버라이드해서 프로퍼티를 직접 만들어 줘야 됨.
interface MyInterface {
val myProperty: String
fun myFunction(): Int
}
class MyClass : MyInterface {
override val myProperty: String = "Hello, World!"
override fun myFunction(): Int {
return myProperty.length
}
}
myClass에서 myProperty 구현 및 myfunction을 구현함.
뒷받침하는 필드가 없어 override val 사용해 값을 초기화 함
interface MyInterface {
val myProperty: String
}
class MyClass : MyInterface {
override var myProperty: String = "initial value"
set(value) { field = value.toUpperCase() }
}
interface MyInterface {
val myProperty: String
}
class MyClass : MyInterface {
override val myProperty: String = "initial value".toUpperCase()
}
두개는 동일한 코드고 이런식으로 field를 써서 직접 값에 접근해 읽거나 쓸수 있다.
뒷받침 필드란 프로퍼티 값을 저장하는 실제 변수를 말하며 코틀린의 프로퍼티 구현방식 이다.
프로퍼티 값을 변경할대마다 필드 값얼 업데이트 해준다, 기본적으로 코틀린은 Field 자동 생성
사용자는 직접 접근할 수없기에 Field라는 특별한 식별자를 제공한다.
해당 프로퍼티에 참조하기때문에 직접 값을 할당하거나 값을 읽을 수 있다.
설명만들어서는 이해가 잘되지 않았다,
interface MyInterface {
val myProperty: String
}
class MyClass : MyInterface {
private var _myProperty: String = ""
override var myProperty: String
get() = _myProperty
set(value) {
_myProperty = value.toUpperCase()
}
}
fun main() {
val obj = MyClass()
obj.myProperty = "hello"
println(obj.myProperty) // 출력 결과: HELLO
}
위 코드를 보고 결국 이해한것은 상위클래스에서 명시한 프로퍼티를
하위클래스에서 게터 세터를 통한 뒷받침 필드를 이용해 재정의 한것.
이게 뒷받침 필드의 정의 같다 프로퍼티값을 정의하는 구현 방식.
코틀린에서는 == 연산자는 참조 동일성이아니라 객체의 동등성이다.
equlas 는 서로 동등한가?(속성) 해시코드(객체의 고유하게 식별하는데 사용)
equals 메서드 사용시 hashcode도 함께 사용해야 한다.
왜냐하면 equals()- > 객체동등성이 True면 hashcode는 반드시 같은 값을 반환해야하는
JVM언어 제약 때문.(특성이기도 한다고 한다)
hashcode는 객체의 고유한 해시 코드를 반환함. (속성에 따라)
hashSet , hashMap 등 컬렉션은 해쉬코드를 사용해 객체 위치를 찾고,
equals 메서드이용해 동등성 확인 해쉬코드를 이용해 동일 객체인지 확인해
equlas가 true이면 Hashcode()메서드도 트루여야 한다.
만약 서로 값이 다르면 예기치 않는 동작 발생 한다.
주생성자 밖에 설정된 프로퍼티는 두 메소드의 고려대상이아님을 유의!
object 클래스 클래스에 hashcode()를 상속받게 됨.
상속받은 클래스는 객체의 메모리주소기반을 하기때문에 두개의 객체는 다를수 있다.
Person person1 = new Person("John", 30);
Person person2 = new Person("John", 30);
System.out.println(person1.equals(person2)); // true
System.out.println(person1.hashCode() == person2.hashCode()); // false
## Copy()
코틀린의 데이터 클래스는 프로퍼티 val 불변 클래스로 만드는것을 권장
hashmap등 컨테이너에 데이터 클래스 객체담을경우 불변성이 필수이기 때문
hashMap과 같은 해시 기반 컨테이너는 내부적으로 해시 테이블 사용하여
객체 저장하고 검색 -> 객체를 고유한 해시코드로 매핑 저장 이를 이용 검색.
따라서 객체의 해시코드는 변하면 안됨 만약 변경되면 찾을수가 없기 때문!
다중 스레드 프로그램인경우 이런 성질이 더 중요하다.
왜냐하면 불변 객체를 사용하면 스레드가 사용 중인 데이터를 다른스레드가 변경을 못하니 동기화
필요가 줄어드니깐!
불변 객체를 더 쉽게 활용? copy() 메서드 제공
객체 복사 -> 일부 프로퍼티 변경 가능한 메서드.
class Client(val name: String, val postalCode: Int) {
fun copy(name: String = this.name, postalCode: Int = this.postalCode) =
Client(name, postalCode)
}
fun copy (name: String = this.name,
postalCode: Int = this.postalCode) = Client (name, postalCode)
>>>val lee=Client("이계영", 4122)
>>> println (lee. copy (postalCode = 4000))
client(name=이계영, postalCode=4000)
// 허나 얕은 복사이기 때문에 서로 같은 참조를 공유하는 속성을 가지고 있어 변경시 주의해야 된다.
data class Person(val name: String, val address: Address)
data class Address(val city: String, val street: String)
fun main() {
val person1 = Person("John", Address("Seoul", "Gangnam-gu"))
val person2 = person1.copy()
person1.address.city = "Busan"
println(person1) // Person(name=John, address=Address(city=Busan, street=Gangnam-gu))
println(person2) // Person(name=John, address=Address(city=Busan, street=Gangnam-gu))
}
// 일부 속성을 변경해 새로운 객체 만들 때
data class Person(val name: String, val age: Int, val address: String)
fun main() {
val person1 = Person("John", 30, "Seoul")
val person2 = person1.copy()
println(person1) // Person(name=John, age=30, address=Seoul)
println(person2) // Person(name=John, age=30, address=Seoul)
val person3 = person1.copy(name = "Jane", age = 25)
println(person3) // Person(name=Jane, age=25, address=Seoul)
}
// 불변 리스트에서 요소를 추가하거나 제거할 때. (map에서도 활용가능)
fun main() {
val list1 = listOf("apple", "banana", "orange")
val list2 = list1.toMutableList()
val newList1 = list1 + "grape"
val newList2 = list2.apply { add("grape") }
println(newList1) // [apple, banana, orange, grape]
println(newList2) // [apple, banana, orange, grape]
}
종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가할시 사용하는 패턴.
핵심은 기존 클래스와 같은 인터페이스 제공, 기존 클래스 내부 필드를 유지하고
새로운 동작을 추가하는 새로운 클래스(데코레이터) 만듬.
class DelegatingCollection<T: Collection<> (
private val innerList = arrayListOf<T> ()
override val size: Int get () = innerList.size
override fun isEmpty () : Boolean = innerList.isEmpty ()
override fun contains (element: T) : Boolean =
위임 해야될 코드가 너무 많다..
그래서 by 등장. 인터페이스 구현을 다른 객체에 위임 가능함!
코드 중복을 줄이고 객체지향적 유연성을 높힘.
interface Shape {
fun calculateArea(): Double
}
class Circle(private val radius: Double) : Shape by Math {
// Shape 인터페이스에 정의된 메서드를 구현하지 않아도 된다
}
object Math : Shape {
override fun calculateArea(): Double {
// 원의 넓이를 계산하는 로직
return 3.14 * radius * radius
}
}
fun main() {
val circle = Circle(5.0)
val area = circle.calculateArea()
println(area)
}
// 위임을 사용하지않고 직접 구현 .
interface Shape {
fun calculateArea(): Double
}
class Circle(private val radius: Double) : Shape {
override fun calculateArea(): Double {
// 원의 넓이를 계산하는 로직
return 3.14 * radius * radius
}
}
fun main() {
val circle = Circle(5.0)
val area = circle.calculateArea()
println(area)
}
object Singleton {
// 싱글톤 객체의 프로퍼티와 메서드를 정의한다.
// 전역적으로 접근하는 단 하나의 객체
val name: String = "Singleton"
fun printName() {
println(name)
}
}
// 객체표현식 생성 무명클래스 정의 및 바로 인스턴스
val sum = object : Function2<Int, Int, Int> {
override fun invoke(a: Int, b: Int): Int = a + b
}
println(sum.invoke(1, 2)) // 3
// 동반객체 생성
// 동반 객체는 클래스의 인스턴스와 별도로 생성되서
동반 객체의 프로퍼티와 메서드에 접근할 때에는 MyClass.Companion 형태로 접근
// 해당 클래스와 밀접한 관련이 있는 정적 변수(static variable)와 메서드를 구현하는
데에 사용됨. 주로 팩토리 메서드(factory method)를 구현할 때 사용
class MyClass {
companion object {
// 동반 객체의 프로퍼티와 메서드를 정의한다.
val name: String = "MyClass"
fun printName() {
println(name)
}
}
}
// 동반 객체의 프로퍼티에 접근하여 값을 출력한다.
println(MyClass.Companion.name) // MyClass
// 동반 객체의 메서드를 호출하여 값을 출력한다.
MyClass.Companion.printName() // MyClass
// 싱글톤 예제
class MySingleton private constructor() {
companion object {
private val INSTANCE = MySingleton()
fun getInstance(): MySingleton {
return INSTANCE
}
}
fun doSomething() {
println("Hello, World!")
}
}
fun main() {
val singleton1 = MySingleton.getInstance()
val singleton2 = MySingleton.getInstance()
println(singleton1 === singleton2) // true
singleton1.doSomething() // Hello, World!
singleton2.doSomething() // Hello, World!
}
object 키워드로 시작, 클래스를 정의하고 인스턴스를만들어 변수에 저장하는 작업을 한문장으로 처리
대표 적인 싱글톤 예제 클래스 선언과 그 클래스의 속한 단일 인스턴스의 선언을 합침.
주로 한번만 초기화 되는 싱글턴 객체를 쉽게 만들기 위해 사용 된다.
음 아마도 어디서든지 공통적인 무언가를 수행할때 사용하는 전역 메서드 개념으로 이해했다.
그리고 싱글톤으로 만들어지기때문에 자원효율성도 좋다.
// 싱글톤
// getDatabase를 통해 전역으로 메서드를 사용 가능.
// DatabaseManager.getDatabase() 메소드를 통해 접근가능.
object DatabaseManager {
private val database = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java, "database-name"
).build()
fun getDatabase(): AppDatabase {
return database
}
}
----------
// 기존 클래스를 이용해 생성 <-> object를 이용해 생성
class Logger {
fun log(message: String) {
println("[$message]")
}
}
fun main() {
val logger = Logger()
logger.log("Hello, World!")
}
object Logger {
fun log(message: String) {
println("[$message]")
}
}
fun main() {
Logger.log("Hello, World!")
}
---------------------
class AppConfig private constructor() {
companion object {
private var instance: AppConfig? = null
fun getInstance(): AppConfig {
if (instance == null) {
instance = AppConfig()
}
return instance!!
}
}
var apiUrl: String = "https://api.example.com"
var apiKey: String = "1234567890"
}
fun main() {
val config = AppConfig.getInstance()
println(config.apiUrl)
println(config.apiKey)
}
object AppConfig {
var apiUrl: String = "https://api.example.com"
var apiKey: String = "1234567890"
}
fun main() {
println(AppConfig.apiUrl)
println(AppConfig.apiKey)
}
객체 선언
interface Animal {
fun speak()
}
class Dog : Animal {
override fun speak() {
println("Woof!")
}
}
class Cat : Animal {
override fun speak() {
println("Meow!")
}
}
object AnimalFactory {
fun createAnimal(type: String): Animal? {
return when (type.toLowerCase()) {
"dog" -> Dog()
"cat" -> Cat()
else -> null
}
}
}
fun main() {
val dog: Animal? = AnimalFactory.createAnimal("dog")
val cat: Animal? = AnimalFactory.createAnimal("cat")
dog?.speak()
cat?.speak()
}
// 동반객체
interface Animal {
fun speak()
}
class Dog : Animal {
override fun speak() {
println("Woof!")
}
}
class Cat : Animal {
override fun speak() {
println("Meow!")
}
}
object AnimalFactory {
fun createAnimal(type: String): Animal? {
return when (type.toLowerCase()) {
"dog" -> Dog()
"cat" -> Cat()
else -> null
}
}
}
fun main() {
val dog: Animal? = AnimalFactory.createAnimal("dog")
val cat: Animal? = AnimalFactory.createAnimal("cat")
dog?.speak()
cat?.speak()
}