마크다운을 이용한 타이핑 필기는 편하긴 하지만 개행 등을 잘 못하면 가독성이 극악이 된다는 단점이 있었는데, 이런식으로 PPT 양식으로 만들면 한 칸 내에 정보가 제한되는(?) 효과를 발휘하면서 굉장히 읽기 편한 자료를 만들 수 있었다.
다만 아직 개발중인 건지, 불완전한 모습이 간간히 보이고 있다. 어느 정도는 자동 맞춤을 해주지만, 글이 너무 많아지면 내용이 아예 잘려버린다던가(축소에 한계치가 있는걸까?) PPT 변환을 해봤더니 첫페이지 내용으로만 변환되는 문제도 있었고, 아쉽게도 이미지 파일 역시 첫 페이지만 변환이 가능하다. 어서 기능개선을 통해 불편한 점이 해결되었으면 좋을 듯.
제네릭 : 자료형을 다루는 메소드나 클래스를 컴파일 시점에서 검사하여 적절한 자료형을 선택
앵글 브라켓(<>) 사이에 자료형을 선언
자료형을 대표하는 형식 매개변수로 T를 사용
classBox(a : Int) //원래의 클래스 선언///////////classBox<T>(a:T) //제네릭 클래스 선언 - 모든 자료형을 받을 수 있음funmain()
{
val box1 = Box(1) //Box<Int>로 초기화val box2 = Box("kim") //Box<String>으로 초기화
}
제네릭 함수
제네릭 함수 : 함수나 메소드 앞에 지정
fun<T>genericFun(arg : T) : T? { ... } //T는 순서대로 형식 매개변수, 매개변수 자료형, 반환 자료형fun<K, V>genericFun(key : K, value : V) : Unit {...} //형식 매개변수가 여러개인 경우//배열의 인덱스 찾기fun<T>find(a:Array<T>, Target : T) : Int
{
for (i in a.indices) //a.indice : a의 인덱스 반환 (0..n)
{
if(a[i] == Target) return i
}
return -1
}
funmain()
{
val arr1: Array<Int> = arrayOf(1,2,3,4)
println(find(arr1, 2)) //1 반환됨
}
//람다식을 이용한 반환형 연산식 결정fun<T>add(a:T, b:T, op : (T,T)->T):T
{
return op(a,b)
}
funmain()
{
val result = add(2, 3, {a, b -> a+b}) //람다식으로 2+3 반환됨
println(result) //5 출력
}
함수의 형식 매개변수 제한하기 : <T:자료형>으로 선언하여 제한 가능
함수 호출 시 해당 자료형으로 선언해주어야 함(<자료형>) - 만족하지 않을 경우 오류 발생
가변성
형식 매개변수가 클래스 계층에 미치는 영향 정의
상위 클래스가 하위 클래스를 수용할 수 있음
ex. Number(정수, 실수를 모두 포함) > Int, Int?(Nullable) > Int
가변성의 유형
무변성 : C와 C<T'>는 무관
코틀린에서는 별도 지정이 없으면 형식 매개변수는 무변성
공변성 : T'이 T의 하위 자료형이면, C<T'>는 C의 하위 자료형
<Out T`>로 선언
하위 자료형이 상위 자료형으로 캐스팅됨
반공변성 : T'이 T의 하위 자료형이면, C<T'>는 C의 상위 자료형
<in T`>로 선언
상위 자료형이 하위 자료형으로 캐스팅됨
자료형 프로텍션
선언 지점 변성 : 클래스 자체에 가변성 지정 / 클래스 사용 시 자료형 지정 필요가 없음
사용 지점 변성 : 클래스 생성 등 사용 위치에서 가변성을 지정
classBox<T>(var : item : T)
{
fun<T>printObj(box : Box<outAnimal>)// Box를 사용할 때 가변성을 결정
{
val obj : Animal = box.item //out이므로 getter가 동작
box.item = Animal() //out이므로 setter는 동작하지 않음//자료형 프로텍션(out으로 선언)에 의해 안전성 보장
}
}
스타 프로젝션 : in/out 선언 없이 추후 겱정
<*>으로 선언
자료형이 결정된 후에는 그 자료형만 사용 가능하도록 제한
배열
데이터의 연속적 나열
arrayOf(), Array() 생성자로 생성
arrayOfNulls() : 빈 배열
배열은 객체이므로 Heap에 저장됨
val numbers = arrayOf(1, 2, 3, 4)
for (element in numbers) println(element)
ex. abstract class Vehicle : 하위 클래스로 차량의 클래스인 차량, 오토바이 등을 구현
open 키워드 없이 상속 가능
abstractclassVehicle(val name : String, val color : String, val weight : Double)
//primary constructor 속 프로퍼티는 abstract가 아님
{
abstractval maxSpd : Double//추상 프로퍼티 : 하위 클래스에서 override해야 함abstractfunstart()abstractfunstop()// 추상 메소드 : 하위 클래스에서 구현해야 함fundisplay()
{
println("$name / $color / $weight")
}//비 추상 메소드 : 하위 클래스 모두가 해당 구현물을 갖게 됨
}
classCar(name : String, color : String, weight : Double, overrideval maxSpd : Double) : Vehicle(name, color, weight)
//상속 선언(maxsPd override 포함)
{
overridefunstart()
{
println("car start")
}
overridefunstop()
{
println("car stop")
}
}
object를 이용한 추상 메소드 구현
abstractclassPrinter
{
abstractfunprint()
}
val myPrinter = object : Printer()
{
overridefunprint() println("출력!")//하위 클래스 선언 없이 임시로 추상 메소드 구현 가능
}
인터페이스
해야 하는 작업에 대한 추상적 명세
클래스가 아니므로 다중 상속 가능
강한 연관을 갖지 않음
interfacePet //인터페이스 상속 시 ': 인터페이스명' 추가
{
//abstract 없이도 프로퍼티/메소드 선언됨var category : String
funfeeding()//다만 메소드는 내용 작성 시 일반 메소드로 동작funpatting()
{
println("Keep patting!")
}
}
classCat(overridevar category : String) : Pet
{
overridefunfeeding()
{
println("Feed cat tuna!")
}
//patting은 인터페이스에서 선언되었으므로 오버라이드가 없어도 됨
}
데이터 클래스
데이터 전달을 위한 객체 DTO(Data Transfer Object)
자바에서는 POJO(Plain Old Java Object)
구현 없이 데이터를 표현
데이터를 접근하는 게터/세터 포함
코틀린의 데이터 클래스 : 프로퍼티 = 필드 + 게터, 세터
자동 생성 메소드
게터, 세터
비교를 위한 equals(), 키를 위한 hashCode()
프로퍼티를 문자열로 보여주는 toString()
객체 복사를 위한 copy()
프로퍼티에 상응하는 component1(), component2(), ...
선언 방식 : data class 이름(프로퍼티)
주 생성자는 최소 하나의 매개변수를 가져야 함
주 생성자의 모든 매개변수는 val, var 프로퍼티여야 한다
데이터 클래스는 abstract, open, sealed, inner 키워드를 쓸 수 없음
dataclassCustomer(var name : String, var email : String)
{
var job : String = "unknown"constructor(name : String, email : String, __job : String) : this(name, email)
{
job = __job
} // 부 생성자 : 프로퍼티 선언init
{
// init : 간단한 로직 선언 가능
}
}
val cus1 = Customer("kim", "kim@mail.com")
val cus2 = Customer("kim", "kim@mail.com")
println(cus1 == cus2) //동등성 비교, true 반환
println(cus1.equals(cus2)) //위와 동일
println("${cus1.hashCode(), ${cus2.hashCode()}}")
// 고유값 동일 - 두 객체의 값이 동일하므로 참조하는 위치도 동일함val cus3 = cus1.copy(name = "alice") // cus1 내용을 복사하면서 name만 새 값을 대입
디스트럭처링(destructuring) : 객체의 프로퍼티를 개별 변수로 분해
val (name, email) = cus1
val (_, email) = cus1 // 특정 프로퍼티가 필요 없는 경우val name = cus1.component1() //cus1의 1번째 component(name) 반환val customers = listOf(cus1, cus2) //DTO가 많은 경우 리스트화 후 반복문 사용for((name, email) in customers)
내부 클래스와 중첩 클래스
코틀린의 내부 클래스
중첩(Nested) 클래스 : 객체 생성 없이 이용 가능
외부 클래스 생성 없이 내부의 nested 객체를 생성할 수 있음
외부 클래스에 companion object를 갖고 있을 경우 해당 객체에 접근 가능
이너(inner) 클래스 : 필드, 메소드와 연동하는 클래스(inner 키워드 필요) - 외부 클래스 프로퍼티에 접근 가능
클래스 내에 키워드 없이 선언 시 nested class로 선언됨, 내부 클래스는 외부에 접근이 불가능하게 됨
바깥 클래스의 private 멤버도 접근 가능
*익명 객체 : 일회용 객체 선언을 위해 object 키워드 사용
지역 클래스와 익명 클래스
지역 클래스(Local Class)
특정 메서드 블록이나 init블록과 같이 그 안에서만 유효한 클래스
블록 범위 내에서는 사용되지 않음
classSmartphone(val model:String)
{
funpowerOn() : String
classLed(val color : String)
{
funblink() : String = "Blink $color on $model"
}//지역 클래스 - 외부 프로퍼티에 접근 가능val powerStatus = Led("Red")
return powerStatus.blink()
}
익명 클래스(Anonymous Class)
일회적으로 객체를 생성해 사용
메모리 효율 혹은 개발 속도(오버라이딩 구현) 면에서 사용
실드 클래스와 열거형 클래스
실드(Sealed) 클래스
sealed 키워드를 사용
그 자체로는 추상 키워드와 같이 객체 생성은 불가
기본 생성자는 private, 그 외에는 불허
같은 파일 내에서만 상속 가능
블록 내의 클래스는 필요한 경우 open 키워드로 선언하여 상속 가능
주로 상태의 정의와 관리에 사용
sealedclassResult
{
openclassSuccess(val message : String) : Result() //해당 클래스는 상속 가능classError(val code : Int, val message : String) : Result()
}
classStatus : Result() // 같은 파일에서만 상속 가능classInside : Result.Success("Success") // 내부 클래스 상속
열거형 클래스(enum class)
여러 상수를 선언하고 조건에 따라 값을 선택 가능
동일한 자료형의 상수 나열 가능
enumclass 이름(생성자)
{
상수1(값), 상수2(값), 상수3(값), ...
//값은 선택 가능
}
위에서 설치한 markdown paste는 복사한 이미지를 간단하게 붙여넣을 수 있다.
ctrl+alt+v로 붙여넣으면 된다(다만 시간이 좀 걸릴 수 있다)
markdown all in one의 기능 중 md파일을 html로 바꾸는 기능이 있다. 이 기능을 이용하면 티스토리 글쓰기에 html 기능으로 작성한 글을 옮길 수 있다(테마에 따라 지원이 완전히 되지 않을 수 있다.)
링크는 대괄호에 [표시할 이름]을, 소괄호에 (링크)를 넣는다. [ ] ( )이런 식으로(사이 공백은 붙인다.) : 이 글로 다시 돌아오는 링크
이외에도 많은 문법이 있는데, 구글에 검색하면 많으나 컴퓨터로 기록할때는 이정도를 주로 쓰고 있다. 너무 악필이다 보니 컴퓨터 기록 방법을 고뇌하다 찾았는데, 생각보다 너무 유용하게 쓰이는 듯.
md 파일은 Github 웹사이트를 만들 때도 쓸 수 있는데, Github의 경우 티스토리보다 손이 좀 많이 가다 보니(웹사이트 구축에서 막히기 시작하니까 흥미가 훅 떨어지더라...) 그냥 티스토리에 아예 정착한 상황이다. md파일이 있으니 원할 때 그대로 옮겨갈 순 있을 것 같긴 한데, 티스토리가 섭종하지 않는 한은 그냥 이대로 갈 것 같다.
classPerson(var name: String, var age: Int) // 클래스 선언funmain()
{
val user = User("Kim", 20)
val name = user.name //getter에 의한 프로퍼티 반환
user.age = 25//setter에 의한 프로퍼티 값 변경
}
게터와 세터의 설정 원리 : 변수 설정 시 get(), set()이 각각 자동으로 선언됨
아래 양식처럼 암묵적으로 선언되며 같은 양식대로 직접 커스텀할 수 있음
Class bar(...)
{
val A : Int//불변형은 getter만 선언됨get() = field
/*
프로퍼티를 get에 대입 시 무한 재귀 호출이 되기 때문에,
프로퍼티를 가리키는 변수인 field로 별도로 선언됨
*/var B : Int//가변형은 get, set 모두 선언됨get() = field
set(value)
{
field = value
}
}
지연 초기화
클래스의 프로퍼티는 null일 수 없으므로 객체 생성 시 초기화가 필요한데, 객체 정보가 나중에 나타나는 경우 나중에 초기화할 방법이 필요
lateinit, lazy 키워드로 지연된 초기화 가능
lateinit
클래스 간의 의존성이 있는 경우 (특정 클래스가 생성되어야 초기화할 수 있는 경우)
테스트를 위해 임시로 객체를 생성해야 하는 경우
var형 프로퍼티만 가능
초기화 전에는 프로퍼티의 getter, setter 사용이 불가
classPerson
{
lateinitvar name : String
funtest()
{
if(~::name.isInitialized) println("not initialized") //초기화 여부 확인else println("initialized")
}
}
funmain()
{
val kildong = Person()
kildong.test() //"not initialized"
kildong.name() = "Kildong"//isInitialized = true
kildong.test() //"initialized"
}
/////////////////////////////////////////////////////////// lateinit을 이용한 클래스 생성의 지연 초기화dataclassPerson(var name : String, var age : Int)
lateinitvar person1 : Person //클래스 지연 초기화funmain()
{
person1 = Person("Kildong", 30) //생성자 호출 시점에 초기화
}
lazy : 호출 시 by lazy로 지연 초기화
val에서만 사용 가능, 초기화 후값 변경 불가
lazy 키워드의 모드
by lazy(mode = SYNCHRONIZED) : 단일 스레드의 사용 보장
by lazy(mode = PUBLICATION) : 여러 곳에서 호출될 수 있으나 처음 초기화된 값 사용
by lazy(mode = NONE) : 락이 되지 않으므로 여러 스레드의 접근이 가능하나 값이 일관되지 않을 수 있음
class Lazytest
{
init {println("init block")}
val subject by lazy
{
println("lazy init")
"kotlin programming"//subject 접근 시 대입됨
}
fun flow()
{
println("not init")
println("subject : $subject") // 이 시점에서 subject가 초기화됨
}
}
fun main()
{
val test = Lazytest()
test.flow()
}
by를 이용한 위임
람다식을 사용
한 클래스가 다른 클래스에 위임
위임된 클래스 멤버는 참조 없이 호출
<var|val|class> 이름 : 자료형 by 위임자
위임을 쓰는 이유
코틀린 라이브러리는 open되지 않은 클래스(상속 불가)
표준 라이브러리의 복잡한 상속을 방지하나, 기능 확장이 어려움
위임 사용 시 : 클래스 기능에 추가 기능을 구현 가능
interfaceAnimal()
{
funeat() {...}
}
classCat = Animal()
val cat = Cat()
classRobot = Animalbycat //Robot은 Cat의 멤버를 사용 가능
위임의 특수 키워드
observable : 프로퍼티를 보다가 변경 발생 시 호출
vetoable : 반환값에 따른 변경 허용 및 취소
import kotlin.properties.Delegates
classUser
{
var name : String by Delegates.observable("NONAME")//observable 매개변수로 name 초기값 설정
{
prop, old, new -> //프로퍼티, 기존값, 새로운 값을 매개로 받음
println("$old->$new") //이벤트 발생(name값의 변화) 시 실행
}
}
정적 변수와 메서드
코틀린의 클래스는 동적으로 생성
동적 초기화 없이 사용 가능한 변수 = 정적 변수 : 코틀린에서는 컴패니언 객체라고 칭함
사용하지 않아도 메모리에 상주하므로 자주 사용하는 객체에 대해 사용
classPerson
{
var id : Int = 0var name : String = "kim"companionobject// 컴패니언 객체 : 객체 생성 없이 접근 가능
{
var lang : String = "Kor"funwork()
{
println("working")
}
}
}
funmain()
{
println(Person.lang) //인스턴스 생성 없이 출력 가능
Person.lang = "Eng"//변경 가능
Person.work() //메소드 실행 가능
println(Person.name) //name은 companion이 아니므로 에러
}
주 생성자(Primary Constructor) : 클래스명과 함께 기재, 보통 constructor 키워드는 생략 가능
부 생성자 (Secondary Constructor) : 클래스명 본문에 기재, 하나 이상의 부생성자를 정의 가능
classBird
{
//프로퍼티var name : String = "noname"var wing : Int//생성자constructor(name:String, wing : Int)
{
this.name = name //생성자, 메소드에서 클래스 프로퍼티 참조this.wing = wing
}
//메소드funfly()
{
println("Fly!")
}
}
funmain()
{
val coco = Bird("coco", 2)
coco.fly()
}
상속(Inheritance)
자식 클래스가 상위(부모) 클래스 내용을 계승
상위 클래스의 프로퍼티와 메소드가 하위로 적용됨
open class (class명) : 클래스를 상속 가능한 상태로
class (클래스명) : (상위 클래스명) : 클래스 상속
코틀린 클래스는 묵시적으로 Any 클래스로부터 상속됨
openclassBird(var name : String, var wing : Int)
//pri. Constructor, 프로퍼티 선언한 상위 클래스
{
funfly()
{
println("Fly!")
}
}
classLark(name : String, wing : Int) : Bird()
//클래스 상속 : 상위 클래스의 프로퍼티와 동일하게 Construct
{
fun singalong
{
print("sing together")
}
}
classParrot : Bird()
{
var language : String
//만약 하위 클래스에 새 프로퍼티 추가 시 별도로 선언constructor(name : String, wing : Int, language : String) : super(name, wing)
//부생성자로 클래스 상속 시 super 키워드 사용
{
this.language = "..."
}
funspeak()
{
println("hello?")
}
}
funmain()
{
var lark = Lark("mylark", 2)
var parrot = Parrot("myparrot", 2, "Eng")
}
다형성(Polymorphism)
같은 이름을 사용하지만 매개변수가 다른 함수
하나의 이름으로 여러 기능 수행
static polymorphism : 컴파일 시점의 다형성, 메소드 오버로딩
dynamic polymorphism : 런타임 다형정, 메소드 오버라이딩
오버라이딩
기능을 바꾸어 재설계
ex. push() - 확인 / 취소 로 서로 다른 기능을 수행하게 할 수 있음
클래스 상속과 같이 open으로 상위 메소드에 선언해주고, override로 상속
final override 사용 시 오버라이드를 선언한 클래스의 하위 클래스는 재정의 금지
오버로딩
같은 기능을 구현하지만 인자 수나 타입이 다름
ex. print("hello") (string), print(1)(int)와 같이 같은 출력이지만 받는 인자가 다름
funadd(x:Int):Int
{
return x+1
}
funadd(x:Double) : Double
{
return x+1.0//오버로딩 : 다른 type
}
funadd(x:Int, y:Int) : Int
{
return x+y //오버로딩 : 다른 인자 개수
}
val sum : (Int, Int) -> Int = {x, y -> x+y}
val mul = {x:Int, y:Int -> x * y}
val add : (Int) -> Int = {it + 1} //it은 전달받은 인자를 의미 (함수 인자가 하나인 경우)
고차 함수 : 리턴값이나 인자가 함수인 함수
funhigh(name : String, body : (Int)->Int) : Int
{
prinln("name : $name")
val x=0return body(x)
}
//함수 호출val res1 = high("TxT", {x->inc(x+3)})
//인자로 전달되는함수 인자가 하나인 경우 : it으로 대체 가능val res2 = high("TxT") {inc(it+3)}
//매개변수가 없을 때 함수 이름만 사용val res3 = high("TxT", ::inc) //inc는 람다식으로 변환됨// 매개변수가 하나인 경우 + 람다식 전달val res4 = high("TxT") {it+3}
클로저
람다식 함수에서 외부 변수에 접근할 수 있는 방법
외부 변수를 capture하여 사용
funmain() {
val calc = Calc()
var result = 0// 외부의 변수
calc.addNum(2,3) { x, y -> result = x + y } // 클로저
println(result) // 값을 유지하여 5가 출력
}
classCalc{
funaddNum(a: Int, b: Int, add: (Int, Int) -> Unit) { // 람다식 add에는 반환값이 없음
add(a, b)
}
}
코틀린 표준 라이브러리
코틀린 표준 함수는 확장 함수 형태의 람다식으로 제공
let() : 객체를 인자로 넘기고 결과값 반환
//main문 내val score : Int?=32// 기존 null검사funchkScore()
{
if (score != null) println("Score : $Score")
}
//let을 이용한 null검사 - null이면 {...}이 실행되지 않음funletChkScore()
{
score?.let{println("Score:$it)}
}
also() : let은 결과값 반환, also는 함수에 전달한 객체를 반환
함수 블록 {...}의 마지막 문장을 let은 결과로 반환
also를 사용 시 마지막 문장을 반환하지 않음 (=대입이 안됨)
var m=1
m = m.also{it+3} //m은 1 - 연산한 결과가 m에 대입되지 않음
apply(): also와 같이 객체를 전달하고 반환받음
also와 달리 확장 함수로 처리되므로 클래스 인자 접근 시 it을 사용하지 않아도 됨
val person = Person("", 0)
//applyval result = person.apply {
name = "James"
age = 56
}
//alsoval result = person.also {
it.name = "James"
it.age = 56
}
run() : 익명 함수 / 객체 호출 두가지로 활용
block 내용 수행 후 결과값을 반환
조건부 메소드 사용 가능
apply와 다르게 객체에 접근하면서 함수 블록{...}의 마지막 결과가 실행됨
run
{
if (firstTimeView) introView else normalView
}.show()
if(...)
{
...
}
elseif (...)
{
...
}
else
{
...
}
val bar = if(...) a else b //1줄로 축약 가능//if문을 이용한 대입val a = 1val b = 2val bar = if(...)
{
println("a")
a
}
else
{
println("b")
b
}
in 연산자 : 범위 내에 값이 있는지 확인
if (score >= 80 && score <= 89>) //score이 80 ~ 89 내인지?if (score in80..89) { ... } // 위와 동일한 동작
조건문을 통한 분기 (2)
When문 : Switch - Case문의 간략화
when(x)
{
/*(일치하는 값 or 표현식) -> 수행할 명령문*/0, 1 -> printf("x is 0 or 1")
returnvalue(x) -> print("return is correct") // 함수 리턴값과 비교isInt -> println("x is int")
/*(일치하는 범위) -> 수행할 명령문*/in1..10 -> println("x is in 1 ~ 10")
!in10..20 - > println("x is not in 10 ~ 20")
...
/*else -> 수행할 명령문*/else -> println("??")
}