Development/Kotlin&Android
Kotlin 문법 Part 3
우봉수
2023. 3. 16. 13:14
Class
class Animal(val name:String)
class Person(val firstName: String, tmpLastName:String){
var lastName:String = tmpLastName
get () = field.uppercase()
set (value){
field = "[$value]"
}
}
fun main(){
val a = Animal("Dog")
println("Animal: ${a.name}")
val p = Person("Sukung","Kim")
// getter 호출됨
println("Name: ${p.firstName} ${p.lastName}")
// setter 호출됨 []이 붙여짐
p.lastName = "KIM"
println("Name: ${p.firstName} ${p.lastName}")
}
- 단순한 클래스 정의
- class 클래스_이름(생성자 인자 리스트)
- 생성자 인자들 중 val/var이 붙은 것은 클래스의 속성이 됨
- 자바와 다른점
- 코틀린은 기본적으로 public class임
- 객체를 생성할 때 new 키워드를 쓰지 않음
- 속성 접근 메소드를 자동으로 만들어주며, 커스텀 접근 메소드를 만들 수도 있음
Interface
interface ClickListener{
fun onClick()
fun onTouch() = println("touched")
}
class View(private val id:Int):ClickListener{
override fun onClick() {
println("clicked $id")
}
}
fun main(){
val v = View(10)
v.onClick()
v.onTouch()
}
- 자바의 인터페이스와 비슷함
- 구현이 있는 메소드 정의가 가능 하지만 자바처럼 default를 붙이진 않음
- 인터페이스 구현 클래스의 메소드 구현에서 override를 반드시 적어주어야 함
- 클래스 상속 받을 때 메소드 오버라이드 할 때에도 override를 반드시 명시해 주어야 함
기본적으로 상속, 오버라이드 금지
class NoInherit(val name:String)
// 컴파일 에러
//class Child2(val name:String):NoInherit(name)
open class Inherit(val name:String){
fun first() = println("first")
open fun second() = println("second")
}
open class child(name:String):Inherit(name){
final override fun second() {
println("override second")
}
open fun third() = println("third")
}
class GrandChild:child(""){
override fun third() {
println("override third")
}
}
- open 이라는 키워드가 붙어야 가능
- 단 interface, abstract class는 기본적으로 open 키워드가 디폴트로 들어가 있음
- class 는 기본적으로 상속, 메소드 오버라이드 금지
- open class Test()
- 메소드 오버라이드 접근 변경
- final: 오버라이드 금지 기본적인 디폴트 설정임
- open: 오버라이드 가능
- abstract: 반드시 오버라이드 해야 함
- override: 오버라이드 한 것을 명시
기본적으로 공개(public)
- 코틀린은 클래스와 메소드 모두 기본적으로 public
- 접근 변경자
- public: 디폴트 옵션 자유롭게 접근 가능
- internal: 같은 모듈에서만 접근 가능
- protected: 상속 받은 클래스에서만 접근 가능
- private: 같은 클래스 에서만 접근 가능
중첩 클래스
class Outer(var m:Int=0){
// inner 키워드를 통해 내부 클래스 선언
inner class Inner{
fun doSomething(){
this@Outer.m++
println("Inner.doSomething ${this@Outer.m}")
}
}
// Outer 클래스의 속성에 접근이 불가능
// 기본적으로 정적(static) 중첩 클래스로 만들어짐
class NoInner{
fun doSomething(){
println("NoInner.doSomething")
}
}
}
fun main(){
// Outer클래스를 만들고 나서야 Inner 클래스를 만들 수 있음
// 대신 Outer 클래스의 속성에 접근이 가능함
val inner = Outer().Inner()
inner.doSomething()
// Outer클래스를 만들지 않고 바로 NoInner 클래스를 만들 수 있음
val noInner = Outer.NoInner()
noInner.doSomething()
}
- 코틀린에서 중첩 클래스는 자바에서 static 중첩 클래스와 같음
- 자바처럼 내부 클래스를 만들려면 중첩 클래스 앞에 inner 키워드 사용
봉인(sealed) 클래스
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.right)+eval(e.left)
}
fun main(){
val r = eval(Expr.Sum(Expr.Num(10),Expr.Num(10)))
println(r)
}
- 특정 클래스를 상속하는 클래스를 제한할 수 있다.
Constructor 생성자
class ConstEx1 constructor(tmp:Int){
val prop: Int
val prop2: Int
init {
prop = tmp
prop2= tmp
}
}
class ConstEx2 constructor(tmp:Int){
val prop = tmp
}
class ConstEx3 constructor(val prop:Int)
open class ConstEx4(val prop:Int,val prop2:Int=0){
constructor(_prop:Int,_prop2:Int,_prop3:Int): this(_prop+_prop3+_prop2){
}
}
class ConstEx5(_prop:Int) : ConstEx4(_prop)
fun main(){
ConstEx1(1)
ConstEx2(2)
ConstEx3(3)
ConstEx4(4)
ConstEx4(4,4)
ConstEx4(4,4,4)
ConstEx5(5)
}
- Primary constructor: 클래스 이름 옆에 정의하는 생성자
- Secondary constructor: 클래스 내부에 정의 하는 생성자
Property와 getter/setter
interface GetSetI{
var prop: String
}
class GetSet(tmp:String):GetSetI{
override var prop: String = tmp
set(value) {
field = value.substringBefore('@')
}
get() = field.uppercase()
var prop2:Int =0
private set
}
fun main(){
val getset = GetSet("test@removed")
println("getset.prop = ${getset.prop}")
getset.prop = "ok@remeved"
println("getset.prop = ${getset.prop}")
// 컴파일 오류 getset.prop2 = 10
}
- Property의 커스텀 setter와 getter를 정의할 수 있다.
- public 속성에 대해 setter를 private으로 하면 클래스 밖에서는 get만 가능함
- Interface에 property를 정의할 수 있으나, 이 property는 구현 클래스에서 반드시 오버라이드 해야 한다.
Data Class
data class MyClass(val a:Int,val b:String)
fun main(){
val str1 = "Hello, kotil"
val str2 = "Hello, kotil"
val class1 = MyClass(10,"class1")
val class2 = MyClass(10,"class1")
val class3 = MyClass(20,"class2")
println(str1 == str2)
println(class1==class2)
println(class1==class3)
println(class1==class2)
}
- class 정의 앞에 data 키워드 추가
- data class 는 equals/ hashCode/ toString / copy를 자동으로 만들어준다.
object 키워드
interface ClickListner{
fun onClick()
}
object ClickListenerImp : ClickListner{
override fun onClick() {
println("clicked")
}
}
fun setClickListner(listner: ClickListner) = listner.onClick()
class Touch(){
val objectNums : Int
get() = num
init{
num++
}
// Touch 클래스가 아무리 많이 만들어져도 해당 클래스는 한 개만 존재 싱글톤
companion object{
var num:Int = 0
}
}
fun main(){
setClickListner(ClickListenerImp)
setClickListner(object :ClickListner{
override fun onClick() {
println("click2")
}
})
Touch()
Touch()
println(Touch().objectNums)
}
- 클래스 정의 없이 바로 객체를 생성하는 방법
- 싱글톤을 만들거나
- companion object를 만들거나
- anonymous object를 만들때 사용 (한 번만 사용하는 객체를 만들경우)
- Companion object는 이 객체를 포함하는 클래스의 private 맴버에 접근 가능
Extension Method/Property
val String.lastChar:Char
get() = get(length-1)
fun main(){
println("Hello".lastChar)
}
fun<T> Collection<T>.join(separator:String = " "):String
{
val result = StringBuilder()
// this.withIndex (해당 컬렉션의 맴버)
for((index,element)in withIndex()){
if(index>0) result.append(separator)
result.append(element)
}
return result.toString()
}
fun main()
{
println(listOf("1","2","3").join())
println(arrayListOf(1,2,3).join(","))
}
- 이미 말들어진 클래스의 메소드와 속성을 클래스를 상속하거나 수정하지 않고도 추가 할 수 있음
- 이렇게 추가도니 메소드는 오버라이드 안됨
- 자바의 기존 Collection들에 다양한 유틸 함수들을 이 방법으로 추가
- String의 새로운 유틸 함수: substringBefore, substringAfter 등
연산자 오버로딩
data class Complex(val real:Double,val img:Double){
operator fun plus(other:Complex):Complex
= Complex(real + other.real,img+other.img)
override fun toString(): String = "$real+${img}i"
}
operator fun Complex.minus(other: Complex):Complex
= Complex(real-other.real,img-other.img)
fun main(){
val c1 = Complex(1.0,1.0)
val c2 = Complex(2.0,2.0)
val c3 = c1 + c2
println(c3)
println(c3==Complex(3.0,3.0))
}
- 이항 산술 연산자 +,-,*,/,% 오버로딩 가능
- 오버로딩할 때 연산자 이름 plus, minus, times, div, rem
- 단항연산자
- unaryPlus, unaryMinus, not, inc, dec
- 비교 연산자
- equals, compareTo
위임(Delegation)
interface Base{
fun print()
fun printHello()
}
class BaseImpl(val x :Int):Base{
override fun print(){print(x)}
override fun printHello() {
println("Hello")
}
}
class Derived(val b:Base):Base by b{
override fun print() {
b.print()
println("ok")
}
}
fun main(){
val b = BaseImpl(10)
val d = Derived(b)
d.print()
d.printHello()
}
- 상속과 유사하지만 다른 개념
- 상속은 강한 결합, 위임은 약한 결합
- 상속 북가여도 위임은 가능
- 위임(상속)할 클래스와 동일한 인터페이스를 구현해야 함
- 위임 패턴은 위임(상속)할 객체를 맴버로 만들고 특정 메소드 호출시 위임 객체의 메소드로 포워드(호출)하는 방식으로 만듦
- 위임 객체의 메소드 각각에 대해 메소드를 새로 만들어서 포워드 해야 함
- 코틀린은 by 키워드로 위임을 쉽게 지원함
위임 속성(Delegated Property)
class User(val map:MutableMap<String, Any?>){
val lazyValue: String by lazy{
// 처음 실행시 출력됨
println("computed!")
"hello"
}
var name:String by map
var age: Int by map
var nameOb: String by Delegates.observable("<no name>"){
prop,old,new ->
println("$old -> $new")
}
}
fun main(){
val user = User(mutableMapOf(
"name" to "SuKung",
"age" to 25
))
println(user.lazyValue)
println(user.lazyValue)
println(user.name)
println(user.age)
user.age = 30
println(user.map)
user.nameOb = "fisrt"
user.nameOb = "second"
}
class MyClass{
var newName: Int = 0
@Deprecated("Use 'newNaem' instead",ReplaceWith("newName"))
var oldName: Int by this::newName
}
- by 키워드를 써서 속성의 get/set을 위임 객체의 getValue/setValue로 위임함
- 이를 이용하여 특별한 성질을 갖는 속성을 만듦
- Lazy 특성: 값을 처음 접근할 때 생성/계산함
- Observable 특성: 값이 변경될 때 알려주는 리스너를 지정
- Map을 이용하여 객체 속성 값을 저장
- 다른 속성으로 위임할 수도 있음
Generic
fun <T:Number> myPlus(op1:T,op2:T):String = "$op1$op2"
class MyGeneric<T:Number>(private val prop:T){
fun test(arg:T):String{
println(arg)
return myPlus(prop,arg)
}
}
fun main(){
println(myPlus(1,1))
println(myPlus(1.0,1.0))
println(MyGeneric(10).test(10))
}
- 자바 제네릭과 비슷함