2011년부터 오픈소스로 JetBrains에서 개발 시작, 2016년 1.0 정식 버전 발표. 현재도 진화중.
- JVM에서 실행되므로 자바와 완전히 호환, 크로스 플랫폼 지원
- 정적 타입 언어. type inference(타입 추론) 기능이 있음
- 객체지향 프로그래밍을 지원. extension 함수와 확장 속성을 사용할 수 있음.(objective-c의 그 기능..)
- 함수형 프로그래밍 지원.
- 문법과 코드가 간결
- NPE(NullPointerException) 예외가 생기지 않도록 언어 자체에서 배려하고 있어 자바보다 안전
- OSS이므로 무상 사용 가능
- 자바 코드를 kotlin코드로 변환해주는 변환기 제공(Intellij, Android Studio, Eclipse)
kotlin hello.kt -include-runtime -d hello.jar
// 함수는 fun 으로 시작한다.
// 파라메터는 (파라메터명: 타입) 으로 쓴다
fun main(args: Array<String>) {
// ;은 사용하지 않는다.
// println은 System.out.println을 래핑한 함수이다.
println(makeMessage1(1))
println(makeMessage1(2))
println(makeMessage2(1))
println(makeMessage2(2))
}
// 리턴 타입은 : Type 형태로 쓴다
fun makeMessage1(msgType: Int) : String {
return if (msgType == 1) "안녕하세요?" else "또 만났군요!"
}
// 아래와 같이 함수 선언과 동시에 반환값을 생략하고 대입문에 정의할 수 있다.
fun makeMessage2(msgType: Int) = if (msgType == 1) "날씨 좋죠?" else "참 맑군요!"
val a = 100
// 자동으로 변환되지 않는다.
val b: Long = a.toLong()
// toByte(), toShort(), toInt(), toLong(), toFloat(), toDouble(), toChar()
val s = """
삶이 그대를 속일지라도
슬퍼하거나 노하지 말라
슬픈 날엔 참고 견디라
즐거운 날이 오고야 말리니
"""
// trimMargin() 여백 제거 문자를 전달하면 해당 문자까지 여백을 제거, default 문자는 "|"
val s = """
>삶이 그대를 속일지라도
>슬퍼하거나 노하지 말라
>슬픈 날엔 참고 견디라
>즐거운 날이 오고야 말리니
""".trimMargin(">")
// 정수, 실수 타입 변환
val c = "77".toInt()
val d = "123.4".toDouble()
// 문자열 템플릿을 만들어 사용
println("$c, $d")
val count = 77
val s1 = "Count = $count"
// 배열과 표현식의 경우는 중괄호({})를 앞뒤로 붙인다.
val s2 = "$s1 의 길이는 ${s1.length}"
println(s1)
println(s2)
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456
val socialSecurityNumber = 999_99_9999
val hexBytes = 0xff_fe_ad_de
val bytes = 0b00000000_11111111_10101010_01011100
println("$oneMillion, $creditCardNumber, $socialSecurityNumber, $hexBytes, $bytes")
// arrayOf()를 사용하여 배열 생성
val item = arrayOf("사과", "바나나", "키위")
for (fruit in item) {
println(fruit)
}
// Array클래스 생성자를 사용
val num = Array<String>(5, { i -> (i * i).toString() })
// 위와 동일한 코드
val num2 = Array<String>(5) { i -> (i * i).toString() }
// <String>은 생략 가능. 컴파일러가 추론할 수 있음
for (item in num) {
println(item)
}
// 기본 타입의 요소를 저장하는 별도의 클래스
// IntArray, ByteArray, ShortArray, LongArray, FloatArray, DoubleArray, CharArray, BooleanArray
// 위 클래스들은 코틀린 컴파일러가 컴파일할 때 자바의 기본 타입 배열로 변환한다.
val intItem: IntArray = intArrayOf(1, 2, 3, 4, 5)
val intItem2 = IntArray(5, { i -> (i * i) })
val intItem3 = IntArray(5) { i -> (i * i) }
// index를 사용하여 읽거나 쓸수 있다
intItem[0] = intItem[1] + intItem[2]
fun main(args: Array<String>) {
val m1 = Score(100, 200)
val m2 = Score(300, 400)
// 객체간 + 연산을 사용하면 plus 함수를 사용한다. plus함수를 오버로딩 하지 않으면 컴파일에러가 발생.
println(m1 + m2)
println(m1 * m2)
}
// data 키워드는 나중에 자세히 알아보자
data class Score(val a: Int, val b: Int) {
// 연산자를 오버로딩 하는 함수는 앞에 operator 키워드를 붙여준다.
operator fun plus(other: Score): Score {
return Score(a + other.a, b + other.b)
}
}
// 확장 함수를 사용하여 아래와 같이 정의할 수도 있다.
operator fun Score.times(other: Score): Score {
return Score(a * other.a, b * other.b)
}
operator fun Score.unaryMinus(): Score {
return Score(-a, -b)
}
operator fun Score.inc(): Score {
return Score(a + 1, b + 1)
}
fun main(args: Array<String>) {
val p1 = Customer("홍길동", "010-1111-2222")
val p2 = Customer("김선달", "010-1111-3333")
println(p1 < p2)
println(p1 > p2)
}
class Customer(val name: String, val phone: String) : Comparable<Customer> {
override fun compareTo(other: Customer): Int {
return compareValuesBy(this, other, Customer::name, Customer::phone)
}
}
val start = LocalDate.now()
val end = start..start.plusDays(15)
println(start.plusWeeks(1) in end)
println(end)
// 결과
// true
// 2017-09-14..2017-09-29
fun main(args: Array<String>) {
val instance = InvokeOperator("코틀린을")
instance("배우자")
}
class InvokeOperator(val message1: String) {
operator fun invoke(message2: String) {
println("$message1 $message2")
}
}
val b: String = "코틀린을 배우자"
if(b is String) {
println("String 타입")
} else {
println("String 타입아님")
}
open class A{}
class B : A() {}
val x = B()
if(x is A) println("A type") else println("not A type")
if(x is B) println("B type") else println("not B type")
// 결과
// String 타입
// A type
// B type
val s1: String? = null
val s2: String = null // Error: Kotlin: Null can not be a value of a non-null type String
val s3: String = s1 // Error: Kotlin: Type mismatch: inferred type is String? but String was expected
var a: String? = "코틀린을 배우자"
// a가 널이 아닌 경우 substring을 호출하고 호출한 결과가 널이 아닌 경우 length를 호출
println(a?.substring(5)?.length)
var a: String? = "코틀린을 배우자"
val b = a?.length ?: 0 // val b: Int = if (a != null) a.length else 0
println(b)
// s2를 nullable string으로 캐스팅. s2가 String타입에 부적합한경우 ClassCastException이 발생
val s1: String? = s2 as String?
// s2가 String타입에 부적합하더라도 ClassCastException이 발생하지 않고 null 값이 반환
val s1: String? = s2 as? String?
val result = if (param == 1) {
"one"
} else if (param == 2) {
"two"
} else {
"three"
}
fun main(args: Array<String>) {
whenUsage(2, 50, "서울시")
}
fun whenUsage(inputType: Int, score: Int, city: String) {
// -------------- #1
when (inputType) {
// 값 -> 실행 코드 블록 형태로 작성
1 -> println("type-1")
// , 를 사용하여 여러 조건을 검사할 수 있다
2, 3 -> println("type-2,3")
// 모두 일치하지 않는 경우 else의 -> 코드를 실행한다.
// 자바의 break는 필요 없다
else -> {
println("미확인")
}
}
// --------------- #2
when (inputType) {
// 함수를 실행하여 그 결과를 검사할 수도 있다
checkInputType(inputType) -> {
println("타입정상")
}
else -> println("타입 비정상")
}
// --------------- #3
val start = 0
val end = 100
when (score) {
// in연산자와 범위 연산자 ..을 사용하여 검사할 수 있다.
in start..(end / 4) -> println("우수함")
50 -> println("평균임")
in start..end -> println("범위에 있음")
else -> println("범위를 벗어남")
}
// ---------------- #4
// 결과를 변수에 대입할 수 있다.
val isSeoul = when (city) {
// is 연산자를 사용하여 타입을 확인할 수 있다.
is String -> city.startsWith("서울")
// 값을 대입하는 경우 else가 없으면 컴파일 에러가 발생한다
else -> false
}
println(isSeoul)
// ----------------- #5
// if-else 문처럼도 사용가능하다.
when {
city.length == 0 -> println("도시명을 입력하세요")
city.substring(0, 2).equals("서울") -> println("서울이군요")
else -> println(city)
}
}
fun checkInputType(inputType: Int): Int {
// 아래 식은 inputType in 1..2 로 변경할 수 있다.
if (inputType >= 1 && inputType < 3) {
return inputType
}
return -1
}
// in 연산자를 사용하여 반복을 처리
val item1 = listOf("사과", "바나나", "키위")
for (item in item1) {
println(item)
}
// index를 사용하여 반복
val item2 = listOf("사과", "바나나", "키위")
for (index in item2.indices) {
println("item at $index is ${item2[index]}")
}
// array도 동일하게 사용 가능
val item3 = arrayOf("사과", "바나나", "키위")
for (index in item3.indices) {
println("item at $index is ${item3[index]}")
}
// 1부터 100까지 반복
for (i in 1..100) {
print(i)
}
// 1부터 100까지 반복. 100은 제외
for (i in 1 until 100) {
print(i)
}
// 2부터 10까지 반복. i는 2씩 증가
for (i in 2..10 step 2) {
print(i)
}
// 10부터 1까지 1씩 감소
for (i in 10 downTo 1) {
print(i)
}
val items = listOf("사과", "바나나", "키위")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
index = 0
do {
println("item at $index is ${items[index]}")
index++
} while (index < items.size)
fun min(valueLeft: Int, valueRight: Int): Int {
return if(valueLeft < valueRight) valueLeft else valueRight
}
// 코틀린에서 if는 표현식으로 간주하므로 대입문에 바로 추가 가능
// min 함수의 리턴 타입이 생략되어 있지만 컴파일러가 추론하므로, 에러가 생기지 않는다.
fun min(valueLeft: Int, valueRight: Int) = if(valueLeft < valueRight) valueLeft else valueRight
// 매개변수의 기본값을 정의할 수도 있다.
fun min(valueLeft: Int = 0, valueRight: Int = 0) = if(valueLeft < valueRight) valueLeft else valueRight
// 사용 예시
min(100,50)
min(100) // valueRight는 기본값인 0이됨
min() // valueLeft, valueRight 모두 0
min(valueLeft=50, valueRight=100) // 지명인자(named argument)를 사용하여 값을 전달
min(valueRight=100, valueLeft=50) // 지명인자를 사용하야 정의된 순서와 무관하게 전달
min(valueLeft=50) // 지명인자와 기본값을 사용하여 호출
// 가변인자를 배열로 받아 arrayList를 생성하여 저장한 후 반환
fun <T> newList(vararg ts: T): List<T> {
val result = ArrayList<T>()
// 아래 for문은 아래 연산자 오버로딩 함수로 변경 가능하다
// result += ts
for (t in ts) {
result.add(t)
}
return result
}
val list = newList(1, 2, 3)
val e = arrayOf(7, 8, 9) // Int 타입의 요소 3개를 저장한 배열 생성
val list2 = newList(1, 2, 3, *e, 5) // 1,2,3,7,8,9,5 를 가진 ArrayList 생성
// *e는 배열의 요소를 하나씩 가져와서 인자로 전달하라는 의미. *를 확산(spread)연산자라고함
ex) const val ERROR_CODE = 1 ==> public final static Int ERROR_CODE = 1;
class Customer() {
fun checkId() {..}
}
// 인스턴스를 생성(Customer())하여 호출
Customer().checkId()
fun main(args: Array<String>) {
println(calcCombination(45, 6)) // 로또 복권이 모든 조합 가능한 번호 개수 출력
}
fun calcCombination(whole:Int, selected: Int): Double {
if (selected > whole || selected <= 0 || whole <= 0) {
return -1.0
} else if (selected == whole) {
return 1.0
}
// 함수 내부에 factorial을 계산하는 지역함수를 정의하여 사용
// 지역 함수에서는 자신을 포함하는 외부 함수의 인자나 변수를 그냥 사용할 수 있다.(여기서는 안썻지만...)
fun calcFactorial(num: Int): Double {
var total: Double = 1.0
for (i in num downTo 1) {
total *= i
}
return total
}
return calcFactorial(whole) / (calcFactorial(whole - selected) * calcFactorial(selected))
}
위 예에서
// 코틀린 MutableList 클래스를 상속받지 않고 추가로 swap 기능을 하는 함수를 확장
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // this는 MutableList의 객체를 나타냄
this[index1] = this[index2]
this[index2] = tmp
}
// 사용예시
val l = mutableListOf(1, 2, 3)
l.swap(0, 2)
// Int 클래스에 정의되어 있는 shl 함수 예시
infix fun shl(bitCount: Int): Int {
...
}
val a = 8
// a+8은 (a+8)로 괄호를 씌우지 않아도 됨. shr()함수 호출보다 +연산자의 우선순위가 더 높음.
println(a+8 shr 2)
- 클래스의 멤버 함수이거나 확장 함수이어야함
- 매개변수가 한 개여야 함
- infix 키워드로 함수가 정의되어야함
fun main(args: Array<String>) {
println(calcFactorial(10000))
println(getFibonacciNumber(10000, BigInteger("1"), BigInteger("0")))
}
// 아래 함수는 tailrec와 무관하게 컴파일 후 재귀함수 형태로 보여진다.
tailrec fun calcFactorial(num: Int): Double {
if (num == 1) {
return 1.0
}
else {
return (num * calcFactorial(num -1)).toDouble()
}
}
// 아래 함수는 tailrec 키워드 여부에 따라 컴파일 후 재귀함수형태 or while루프(tailrec키워드가 있는 경우) 형태로 변경된다.
tailrec fun getFibonacciNumber(n: Int, a: BigInteger, b:BigInteger): BigInteger {
if(n==0) {
return b
} else {
return getFibonacciNumber(n - 1, a + b, a)
}
}
// 클래스 이름의 첫 자는 대문자
class Friend {}
// 본체가 없는 경우 {}도 생략이 가능하나.. 이렇게 쓸일이 없을듯.
class Friend
fun main(args: Array<String>) {
val f1 = Friend1()
f1.name = "박문수"
f1.tel = "010-123-4567"
f1.type = 5
println(f1.name + "," + f1.tel + "," + f1.type)
val f2 = Friend2()
f2.name = "홍길동"
f2.tel = "010-456-4567"
f2.type = 5 // set() 함수가 실행되므로 4값이 들어간다.
println(f2.name + "," + f2.tel + "," + f2.type)
// named argument를 사용하여 생성자의 인자를 전달
val f3 = Friend3(tel = "010-678-1234", name = "김선달", type = 5)
println(f3.name + "," + f3.tel + "," + f3.type)
val f4 = Friend4("전신주", "010-1111-1234", 3)
println(f4.name + "," + f4.tel + "," + f4.type)
// 실행 결과
박문수,010-123-4567,5
홍길동,010-456-4567,4
김선달,010-678-1234,4
전신주,010-1111-1234,3
}
class Friend1 {
// 생성자 정의가 없어 기본 생성자가 자동으로 생성된다
// 아래의 속성들은 자동으로 getter/setter가 생성된다.
var name: String = ""
var tel: String = ""
var type: Int = 4
}
class Friend2 {
var name: String = ""
var tel: String = ""
// 아래와 같이 커스텀 세터를 정의할 수 있다
var type: Int = 4
set(value: Int) {
// set 함수에서 해당 속성을 액세스 하려면 field 키워드를 사용한다
if(value < 4) field = value else field = 4
}
}
// 생성자를 아래와 같의 정의 할 수 있다. 아래 코드와 동일하다
// class Friend3 constructor(var name: String, var tel: String, var type: Int)
// 기본 생성자에서는 constructor 키워드를 생략할 수 있다
// private 와 같은 접근 제한자를 사용하는 경우 constructor 키워드를 생략할 수 없다.
class Friend3 (var name: String, var tel: String, var type: Int)
{
// 초기화 블록을 사용하여 속성의 값을 초기화 할 수 있다.
init {
type = if(type < 4) type else 4
}
}
class Friend4 {
var name: String
var tel: String
var type: Int
// java 처럼 보조 생성자를 정의할 수 있다.
constructor(name: String, tel: String, type: Int) {
this.name = name
this.tel = tel
this.type = if(type < 4) type else 4
}
}
class Friend2 {
...
// 멤버 함수 정의
fun printName(): Unit = println(this.name)
}
fun main(args: Array<String>) {
val f2 = Friend2()
// 인스턴스.함수명() 으로 호출
f2.printName()
}
접근 제한자 | 클래스 멤버일 때 | 최상위 수준으로 선언되었을 때 |
---|---|---|
public(기본값) | 어디서든 사용 가능 | 어디서든 사용 가능 |
internal | 같은 모듈에서만 사용 가능 | 같은 모듈에서만 사용 가능 |
protected | 서브 클래스에서만 사용 가능 | 해당 없음 |
private | 클래스 내무에서만 사용 가능 | 코틀린 파일 내부에서만 사용 가능 |
internal의 모듈 : Intellij의 모듈, Gradle의 Project
클래스와 클래스 멤버에만 사용 가능한 접근 제한자
접근 제한자 | 클래스 | 클래스 멤버 |
---|---|---|
final(기본값) | 서브 클래스를 만들 수 없음 | 슈퍼 클래스의 멤버를 오버라이딩 할 수 없음 |
open | 서브 클래스를 만들 수 있음 | 슈퍼 클래스의 멤버를 오버라이딩할 수 있음 |
abstract | 추상 클래스를 의미 | 함수에만 해당되며 몸체(실행코드)가 없음 |
override | 해당 없음 | 슈퍼 클래스의 멤버를 오버라이딩함 |
open class Father(nameF: String, ageF: Int)
// 상속관계는 콜론(:)을 사용한다
// 서브 클래스에서는 슈퍼클래스의 속성도 초기화해주어야 한다.
class Child(nameC: String, ageC: Int) : Father(nameC, ageC)
// 서브 클래스에 기본 생성자가 정의되지 않고 보조 생성자가 정의된 경우에는 : 과 super키워드를
// 사용하여 슈퍼 클래스의 속성을 초기화 한다.
class Child : Father {
constructor(nameC: String, ageC: Int) : super(nameC, ageC)
}
open class Father(nameF: String, ageF: Int) {
// 속성의 경우도 상속이 가능하다. open 키워드를 사용해야 한다.
open val height: Int = 170
// 속성의 경우도 상속이 가능하다. open 키워드를 사용해야 한다.
open var weight: Int = 70
// 서브 클래스에서 오버라이딩을 하게 하려면 open 키워드를 사용해야 한다
open fun sleep() {}
fun looseWeight() {}
}
class Child : Father {
constructor(nameC: String, ageC: Int) : super(nameC, ageC)
// 서브 클래스에서는 override 키워드를 사용해야 한다.
// val로 정의된 속성은 var로 override 가능하다.
override var height: Int = 180
// var로 정의된 속성은 val로 override 할 수 없다.
// 슈퍼 클래스에 var 속성으로 정의되면 setter가 추가로 생성되어 있게된다. 서브 클래스에서 val로 override하게 되면 슈퍼클래스의
// setter가 무의미해진다.
// override val weight: Int = 65
// 서브 클래스에서는 override 키워드를 사용해야한다
override fun sleep() {}
}
interface PlayMusic {
var musicalInstrument: String
fun play()
fun stop() {
println("stop")
}
}
// 클래스를 상속할때처럼 : 을 사용한다. 클래스 상속처럼 ()를 붙이지는 않는다.
class Professional(override var musicalInstrument: String) : PlayMusic {
override fun play() {
}
}
자바 코드로 변환해보면, interface의 속성은 getter, setter(var)로 표현됨.
java8의 default method 와 비슷한 구현이 있는 메서드는 DefaultImpl static class가 생성되어 표현된다.
// 변환된 java 코드.
public interface PlayMusic {
@NotNull
String getMusicalInstrument();
void setMusicalInstrument(@NotNull String var1);
void play();
void stop();
public static final class DefaultImpls {
public static void stop(PlayMusic $this) {
String var1 = "stop";
System.out.println(var1);
}
}
}
public final class Professional implements PlayMusic {
@NotNull
private String musicalInstrument;
public void play() {
}
@NotNull
public String getMusicalInstrument() {
return this.musicalInstrument;
}
public void setMusicalInstrument(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.musicalInstrument = var1;
}
public Professional(@NotNull String musicalInstrument) {
Intrinsics.checkParameterIsNotNull(musicalInstrument, "musicalInstrument");
super();
this.musicalInstrument = musicalInstrument;
}
public void stop() {
PlayMusic.DefaultImpls.stop(this);
}
}
open class MusicType {
open fun sing() {}
open fun play() {
println("play MusicType")
}
}
interface PlayMusic2 {
var musicalInstrument: String
fun play() {
println("play PlayMusic2")
}
}
// ,을 사용하여 상속과 구현을 나열하면 된다.
class Professional2(override var musicalInstrument: String) : MusicType(), PlayMusic2 {
override fun sing() {}
override fun play() {
// 인터페이스와 슈퍼클래스에 동일 이름의 메서드가 정의되어 있는 경우 super<타입> 키워드를 사용해서 참조하면 된다.
super<MusicType>.play()
}
}
이쯤되면 속성과 함수 구현을 가질 수 있는 interface와 뭐가 다른지 생각하게 된다.
abstract class PlayMusic3 {
val musicalInstrument: String = "피아노"
abstract fun play()
fun sing() {}
}
class Professional3() : PlayMusic3() {
// abstract 멤버 함수를 반드시 오버라이딩 해야 한다.
override fun play(){}
}
객체 선언(object declaration), 동반 객체(companion object), 객체 표현식(object expression)
object StateManager {
var msgNumber: Int = 0
var msgContent: String = ""
fun storeMessage() {
// 데이터를 저장하는 코드
println("메시지 번호 = $msgNumber, 내용 = $msgContent")
}
}
fun main(args: Array<String>) {
for (i in 1..10){
StateManager.msgNumber += 1
StateManager.msgContent = StateManager.msgNumber.toString() + "번째 메시지"
StateManager.storeMessage()
}
}
// java code
public final class StateManager {
private static int msgNumber;
@NotNull
private static String msgContent;
public static final StateManager INSTANCE;
public final int getMsgNumber() {
return msgNumber;
}
public final void setMsgNumber(int var1) {
msgNumber = var1;
}
@NotNull
public final String getMsgContent() {
return msgContent;
}
public final void setMsgContent(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
msgContent = var1;
}
public final void storeMessage() {
String var1 = "메시지 번호 = " + msgNumber + ", 내용 = " + msgContent;
System.out.println(var1);
}
private StateManager() {
INSTANCE = (StateManager)this;
msgContent = "";
}
static {
new StateManager();
}
}
class Outer() {
object InnerObject {
var count: Int = 0
fun printCount() {
println(count)
}
}
}
// 아래와 같이 사용 가능하다.
Outer.InnerObject.printCount()
fun main(args: Array<String>) {
OuterClass.printMsg()
val m = OuterClass.create()
m.printMyClass()
}
class OuterClass {
private constructor()
companion object ComObj { // ComObj는 companion object의 이름이다. 생략할 수 있다.
fun printMsg() {
println("동반 객체의 함수가 호출됨")
}
// 동반 객체에서는 private 멤버도 액세스 할 수 있다.
fun create(): OuterClass = OuterClass()
}
fun printMyClass() {
println("팩토리 객체의 함수가 호출됨")
}
}
// java code..
public final class OuterClass {
public static final OuterClass.ComObj ComObj = new OuterClass.ComObj((DefaultConstructorMarker)null);
public final void printMyClass() {
String var1 = "팩토리 객체의 함수가 호출됨";
System.out.println(var1);
}
private OuterClass() {
}
// $FF: synthetic method
public OuterClass(DefaultConstructorMarker $constructor_marker) {
this();
}
@Metadata(
mv = {1, 1, 7},
bv = {1, 0, 2},
k = 1,
d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004J\u0006\u0010\u0005\u001a\u00020\u0006¨\u0006\u0007"},
d2 = {"LOuterClass$ComObj;", "", "()V", "create", "LOuterClass;", "printMsg", "", "production sources for module Kotlin01"}
)
public static final class ComObj {
public final void printMsg() {
String var1 = "동반 객체의 함수가 호출됨";
System.out.println(var1);
}
@NotNull
public final OuterClass create() {
return new OuterClass((DefaultConstructorMarker)null);
}
private ComObj() {
}
// $FF: synthetic method
public ComObj(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
객체 표현식 : 익명 객체를 생성하는 방법
코틀린에서 좀 아쉬운 부분이다. 더 간결하게 할 수 있지 않았을까..?
fun countClicks(window: Window) {
var count = 0
window.addMouseListener(
// 매번 새로운 객체가 생성된다. 싱글톤이 아니다.
object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
count++
}
override fun mouseEntered(e: MouseEvent) {
..
}
}
}
}
class ClassOuter {
private val bar: Int = 1
class Nested {
fun funcNested() = 2 // fun funcNested() = bar 는 에러이다.
}
inner class Inner {
fun funcInner() = bar
}
}
fun main(args: Array<String>) {
val result1 = ClassOuter.Nested().funcNested()
println(result1) // 2가 출력
val result2 = ClassOuter().Inner().funcInner()
println(result2) // 1이 출력
}
// java code
public final class ClassOuter {
private final int bar = 1;
public static final class Nested {
public final int funcNested() {
return 2;
}
}
public final class Inner {
public final int funcInner() {
return ClassOuter.this.bar;
}
}
}
data class Friend7(val name: String, val age: Int, val tel: String)
fun main(args: Array<String>) {
val test = Friend7("이름", 10, "010-1111-2222")
println(test)
// 아래 java로 변환된 코드로 이해하자.
// test객체의 속성을 추출하여 좌측의 변수에 대입한다.
val (fname, fage, ftel) = test
println("$fname, $fage, $ftel")
}
public final class Friend7 {
@NotNull
private final String name;
private final int age;
@NotNull
private final String tel;
@NotNull
public final String getName() {
return this.name;
}
public final int getAge() {
return this.age;
}
@NotNull
public final String getTel() {
return this.tel;
}
public Friend7(@NotNull String name, int age, @NotNull String tel) {
Intrinsics.checkParameterIsNotNull(name, "name");
Intrinsics.checkParameterIsNotNull(tel, "tel");
super();
this.name = name;
this.age = age;
this.tel = tel;
}
// 각각의 속성에 대해 componentN 형태의 메소드를 생성한다.
@NotNull
public final String component1() {
return this.name;
}
public final int component2() {
return this.age;
}
@NotNull
public final String component3() {
return this.tel;
}
@NotNull
public final Friend7 copy(@NotNull String name, int age, @NotNull String tel) {
Intrinsics.checkParameterIsNotNull(name, "name");
Intrinsics.checkParameterIsNotNull(tel, "tel");
return new Friend7(name, age, tel);
}
// $FF: synthetic method
// $FF: bridge method
@NotNull
public static Friend7 copy$default(Friend7 var0, String var1, int var2, String var3, int var4, Object var5) {
if ((var4 & 1) != 0) {
var1 = var0.name;
}
if ((var4 & 2) != 0) {
var2 = var0.age;
}
if ((var4 & 4) != 0) {
var3 = var0.tel;
}
return var0.copy(var1, var2, var3);
}
public String toString() {
return "Friend7(name=" + this.name + ", age=" + this.age + ", tel=" + this.tel + ")";
}
public int hashCode() {
return ((this.name != null ? this.name.hashCode() : 0) * 31 + this.age) * 31 + (this.tel != null ? this.tel.hashCode() : 0);
}
public boolean equals(Object var1) {
if (this != var1) {
if (var1 instanceof Friend7) {
Friend7 var2 = (Friend7)var1;
if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age && Intrinsics.areEqual(this.tel, var2.tel)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
public static final void main(@NotNull String[] args) {
Friend7 test = new Friend7("이름", 10, "010-1111-2222");
System.out.println(test);
// val (fname, fage, ftel) = test 가 아래와 같이 동작한다.
String fname = test.component1();
int fage = test.component2();
String ftel = test.component3();
String var7 = null;
var7 = "" + fname + ", " + fage + ", " + ftel;
System.out.println(var7);
}
open class Figure() {
open fun draw() {}
open fun fill() {}
}
class Rectangle() : Figure() {
override fun draw() {
println("Draw rectangle")
}
override fun fill() = println("Fill rectangle")
}
class Circle() : Figure() {
override fun draw() = println("Draw circle")
override fun fill() = println("Fill circle")
}
class Window(val figure: Figure) {
fun drawFigure() {
figure.draw()
}
fun fillFigure() {
figure.fill()
}
}
fun main(args: Array<String>) {
val r = Rectangle()
val c = Circle()
// 클래스의 상속과 다형성으로 위임 구현
Window(r).drawFigure()
Window(r).fillFigure()
Window(c).drawFigure()
Window(c).fillFigure()
}
interface Figure1 {
fun draw() {}
fun fill() {}
}
class Rectangle1() : Figure1 {
override fun draw() {
println("Draw rectangle")
}
override fun fill() = println("Fill rectangle")
}
class Circle1() : Figure1 {
override fun draw() = println("Draw circle")
override fun fill() = println("Fill circle")
}
// Window클래스(delegate)에서는 위임을 받아 실행되는 인스턴스의 함수를 호출하는 코드가 필요 없다.
// 함수를 오버라이딩 할 수도 있다.
class Window1(val figure: Figure1) : Figure1 by figure {}
fun main(args: Array<String>) {
val r = Rectangle1()
val c = Circle1()
Window1(r).draw()
Window1(r).fill()
Window1(c).draw()
Window1(c).fill()
}
// 위 Windows1 클래스의 변환 코드
// 아래 코드가 by 키워드 하나로 정리됐다.
public final class Window1 implements Figure1 {
@NotNull
private final Figure1 figure;
@NotNull
public final Figure1 getFigure() {
return this.figure;
}
public Window1(@NotNull Figure1 figure) {
Intrinsics.checkParameterIsNotNull(figure, "figure");
super();
this.figure = figure;
}
public void draw() {
this.figure.draw();
}
public void fill() {
this.figure.fill();
}
}
// enum 키워드를 사용한다
enum class Friendtype {
SCHOOL, COMPANY, SNS, OTHERS
}
fun getFriendTypeName(friend: Friendtype) =
when (friend) {
Friendtype.SCHOOL -> "학교 친구"
Friendtype.COMPANY -> "회사 친구"
Friendtype.SNS -> "SNS 친구"
Friendtype.OTHERS -> "기타 친구"
}
fun main(args: Array<String>) {
println(Friendtype.SCHOOL)
// 0부터 시작한다.
println(Friendtype.SCHOOL.ordinal)
println(Friendtype.COMPANY.ordinal)
// name이라는 기본 속성이 있다.
println(Friendtype.COMPANY.name)
println(Friendtype.valueOf("COMPANY"))
// values() : 모든 항목의 이름을 배열로 반환한다.
val friends = Friendtype.values()
for (item in friends) {
println(item)
}
println(getFriendTypeName(Friendtype.OTHERS))
}
// 아래와 같이 enum 클래스 항목의 속성을 우리가 정의할 수도 있다.
enum class RGBColor(val r: Int, val g: Int, val b: Int) {
RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255),
INDIGO(75, 0, 130), VIOLET(238, 130, 238); // enum클래스의 멤버 함수를 정의하는 경우 세미콜론(;)을 써야한다.
fun rgbValue() = (r * 256 + g) * 256 + b
}
언제, 어떻게 써먹지..?
// 코틀린 1.1 이전에는 하위 클래스가 중첩되어야 했으나, 현재는 아래와 같이 사용할 수 있다.
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
// when에 else가 필요 없다.
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
// 기존 형태의 덧셈 함수
fun sum1(a: Int, b: Int): Int{
return a + b
}
// 코틀린의 대입문 형태로 정의된 뎃셈 함수
fun sum2(a: Int, b: Int): Int = a + b
fun main(args: Array<String>) {
println("합계는 ${sum1(10, 20)} 입니다.")
println("합계는 ${sum2(10, 20)} 입니다.")
// 람다식으로 작성된 덧셈 함수 #1
val sum3: (Int, Int) -> Int = {x, y -> x + y}
println("합계는 ${sum3(10, 20)} 입니다.")
// 람다식으로 작성된 뎃셈함수 #2
val sum4 = {a:Int, b:Int -> a + b}
println("합계는 ${sum3(10, 20)} 입니다.")
}
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
/* 버튼 클랙시 실행될 코드 */
}
}
button.setOnClickListener { view -> /* 버튼 클랙시 실행될 코드 */ }
val list = listOf(1, 2, 3, 4, 5)
// 하나의 매개변수만 갖는 람다식의 경우 기본으로 생성되는 it으로 매개 변수를 대체할 수 있다.
println(list.filter { it % 2 == 0})
// 각각의 요소에 10을 더한 값을 갖는 새로운 list를 생성하고 출력
println(list.map {it + 10})
data class Friend (val name: String, val age: Int, val tel: String)
val fr = listOf(Friend("김선달", 30, "010-1111-2222"), Friend("홍길동", 25, "010-2222-3333"))
// list에서 30세 이상인 친구의 이름만 출력
// 객체의 멤버(속성이나 함수)는 클래스이름::멤버이름의 형태로 참조가 가능하다.
println(fr.filter {it.age >= 30}.map(Friend::name))
// friend 필터링 및 출력 관련 변환된 java code
fr = CollectionsKt.listOf(new Friend[]{new Friend("김선달", 30, "010-1111-2222"), new Friend("홍길동", 25, "010-2222-3333")});
Iterable $receiver$iv = (Iterable)fr;
Collection destination$iv$iv = (Collection)(new ArrayList());
Iterator var22 = $receiver$iv.iterator();
Object item$iv$iv;
while(var22.hasNext()) {
item$iv$iv = var22.next();
Friend it = (Friend)item$iv$iv;
if (it.getAge() >= 30) {
destination$iv$iv.add(item$iv$iv);
}
}
$receiver$iv = (Iterable)((List)destination$iv$iv);
destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv, 10)));
var22 = $receiver$iv.iterator();
while(var22.hasNext()) {
item$iv$iv = var22.next();
String var24 = ((Friend)item$iv$iv).getName();
destination$iv$iv.add(var24);
}
List var20 = (List)destination$iv$iv;
System.out.println(var20);
println(fr.filter { it.age >= 30})
// 위 코드를 익명함수를 사용하면 다음과 같이 작성할 수 있다.
println(fr.filter(fun (friend) = friend.age >= 30))
println(
fr.filter(
fun (friend): Boolean {
return friend.age >= 30
)
)
}
val value: Int = 100
val list2 = listOf(1, 2, 3, 4, 5)
println(list2.map {it + value})
println(list2.map(fun (it): Int { return it + value}))
val sum3: (Int, Int) -> Int = {x, y -> x + y}
fun calc(value1: Int, value2: Int, execCode: (Int, Int) -> Int): Int {
return execCode(value1, value2)
}
fun main(args: Array<String>) {
val v1 = calc(2, 7, {a, b -> a * b})
val v2 = calc(2, 7, {a, b -> a + b})
val v3 = calc(2, 7, {a, b -> a - b})
println(v1)
println(v2)
println(v3)
}
inline fun calc(value1: Int, value2: Int, execCode: (Int, Int) -> Int): Int {
return execCode(value1, value2)
}
inline fun makeMoney(lambda1: () -> Int, noinline lambda2: (Int) -> Unit) {
// 실행될 코드
}
throw MyException("예외 발생!")
try {
// 실행중 예외가 발생할 수 있는 코드
} catch (e: SomeException) {
// SomeException이 발생한 경우 처리하는 코드
} finally {
// finally 블록은 생략 가능하다.
}
val err: Int? = try { parseInt(vale)}
catch (e: NumberFormatException) { null }
val s = fr.name ?: throw IllegalArgumentException("이름을 입력하세요")
val err = fr.name ?: fail("이름을 입력하세요)
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
끝.
// "data" class
data class Person(val name: String,
val age: Int? = null) // Nullable type(Int?)
// top-level function
fun main(args: Array<String>) {
val persons = listOf(Person("Alice"), Person("Bob", age = 29),
Person("Tom", 35), Person("Kkumot", 18))
val oldset = persons.maxBy { it.age ?: 0 } // Lambda expression, Elvis operator
println("The oldset is: $oldset") // string template
}
// The oldset is: Person(name=Tom, age=35) <-- autogenerated toString
val x = 1
자바 개발자는 OOP에는 익숙하나, functional programing에는 익숙하지 않을 수 있다. 주요 컨셉은 아래와 같다.
함수형 스타일로 얻는 이점?
fun findAlice() = findPerson { it.name == "Alice" }
fun findBob() = findPerson { it.name == "Bob" }
일반적으로 대부분의 프로그래밍 언어는 함수형 스타일로 사용할 수 있다. 코틀린은 아래의 함수형 스타일을 지원하기 위해 아래 기능을 지원한다.
함수형 프로그래밍을 강요하지는 않는다. 기존 스타일대로 코딩을 해도 된다. 알아서 잘 적절하게 사용해라.
컴파일러, 라이브러리, 관련된 툴 모두다 오픈소스다.(Apache 2) IntelliJ, Android Studio, Eclipse를 지원한다.
if (value is String) // 여기서 타입을 체크하면
println(value.toUpperCase()) // 따로 캐스팅 하지 않아도 된다.
1장 끝.
Kotlin is named after an island near St. Petersburg, Russia, where most of the Kotlin development team is located.