들어가기
Kotlin을 공부하면서 간단하게 정리해보았다. 기본에서 시작해서 나름대로 심화까지 다룰려고 한다.
프로그래밍 언어에 어느정도 익숙한 개발자를 위한 내용으로 Java을 사용했다면 이해하기 더 쉬울거라 생각한다.
가급적 자잘한 설명은 생략하고 압축해서 정리했다.
작성자: ospace114@empal.com, http://ospace.tistory.com/
HelloWorld
시작은 HelloWorld이다.
fun main() {
println("Hello world!")
}코드에서 알 수 있듯이 main()이 있어야 실행된다. 그리고 println()을 통해서 문자열을 출력되고 있음을 알 수 있다.
구문 마지막에는 세미콜론(;)은 없다. 물론 사용할 수도 있지만 없어도 문제가 없기에 생략해서 사용했다.
변수와 상수
변수
"var 변수명:자료형" 형태로 변수를 정의한다.
fun main() {
var str:String
str = "Hello world!"
println("str[$str]")
}자료형을 지정한 변수를 선언해서 값을 할당해서 쓸 수 있다. 참고로 println()에서 문자열 포멧팅도 가능하다. 또한 포매팅에서 중괄호를 사용할 수 있다.(예. ${str})
만약 변수를 초기화하는 경우 자료형 추론에 의해 자료형 생략해서 변수 선언할 수 있다.
fun main() {
var str = "Hello world!"
println("str[${str}]")
}기본 변수는 non-null 타입으로 null 할당시 에러가 발생한다.
null을 할당하려면 물음표(?) 기호를 사용해서 nullabe하게 선언해야한다.
var str1:String = null // 컴파일 에러
var str2:String? = null // nullable 지정상수
상수는 "val 변수명:자료형"으로 정의할 수 있다.
val str = "Hello world!"
str = "Hello!" // Val cannot be reassigned자료형
제공되는 기본 자료형은 다음과 같다.
- Byte, UByte: 1바이트 (-2^7 ~ 2^7-1, 0 ~ 2^8-1)
- Short, UShort: 2바이트 (-2^15 ~ 2^15-1, 0 ~ 2^16-1)
- Int, UInt: 4바이트 (-2^31 ~ 2^31-1, 0 ~ 2^32-1)
- Long, ULong: 8바이트 (-2^63 ~ 2^63-1, 0 ~ 2^64-1)
- Float: 4바이트
- Double: 8바이트
- Boolean
- Char: 1바이트, 2바이트(Unicode)
초기화 값의 타입을 추론해서 변수 타입 결정한다.
val one = 1 // Int
val onePositive = 1u // UInt
val threeBillion = 3000000000 // Long
val oneLone = 1L // Long
val oneLonePositive = 1uL // ULong
val pi = 3.14 // Double
val piFloat = 3.14f // Float
val oneMillion = 1_000_000 // undersocre more readable조건문
조건문에는 if와 when이 있다.
if
먼저 if문 사용예이다.
fun main() {
var str:String? = "";
if (null == str) {
println("str is null.")
} else if (str.isEmpty()) {
println("str is empty.")
} else {
println("str is $str.")
}
}if 문을 조건 할당하는 형태로 사용할 수 있다.
fun main() {
var str:String? = null;
var msg = if (null == str) {
"str is null."
} else if (str.isEmpty()) {
"str is empty."
} else {
"str is $str."
}
println(msg)
}
var res = if (str == "foo") {
"It is Foo."
} else if (str == null) {
"It is Bar."
} else {
"Unknown."
}when
if문의 조건 할당을 when 사용해서 처리할 수 있다. when은 switch라고 보면 된다.
fun main() {
val num = 1
when(num) {
1 -> println("one")
2,3,5,7,11 -> println("prime")
in 10..19 -> println("teen")
else -> println("normal")
}
var str:String? = "";
var msg = when {
null == str -> "str is null."
str.isEmpty() -> "str is empty."
else -> "str is $str."
}
println(msg)
}반복문
반복문에는 while과 for가 있다.
while
while문 예제를 보자.
fun main() {
var i = 0
while (i < 10) {
print("$i, ")
++i
}
println()
var j = 0
do {
print("$j, ")
++j
} while(j < 10)
println()
}for
for 예제를 보자.
fun main() {
for (i in 0..10) print("$i, ")
println()
for (i in 0 until 10) print("$i, ")
println()
for (i in 10 downTo 1) print("$i, ")
println()
for (i in 10 downTo 1 step 2) print("$i, ")
println()
var strs = arrayListOf("Hello", "World")
for(s in strs) print("$s ")
println()
for((i, v) in strs.withIndex()) print("[$i] $v, ")
println()
}..연산자는 범위 생성하며 "1..10"은 1에서 10까지 범위를 의미한다.
in은 범위 조건에 맞는지 확인한다.
함수
기본형
"fun 함수명(인자): 리턴형 { 구문 }" 형태로 정의된다.
간단한 함수 예를 보자.
fun say(name:String): String {
return "Hello $name!"
}
fun main() {
println(say("Foo"))
}인자는 "인자명:자료형"으로 선언한다. 만약 함수에 리턴이 없다면 리턴형을 생략할 수 있다.
인자 기본값을 지정할수 있다.
fun say(name:String = "world"): String {
return "Hello $name!"
}
fun main() {
println(say())
}함수 표현을 더 단순화할 수 있다.
fun say(name:String = "world"): String = "Hello $name!"
fun main() {
println(say())
}람다식
람다식은 "{ 인자1: 자료형, 인자2: 자료형 -> 구문 }"으로 정의한다.
별도 return 을 사용할 수 없고 마지막 값이 리턴된다.
val say1: (String)->String = { arg:String -> "Hello $arg!" }
val say2: (String)->String = { arg -> "Hello $arg!" }
val say3: (String)->String = { "Hello $it!" }
fun main() {
println(say1("Foo1"))
println(say2("Foo2"))
println(say3("Foo3"))
}자료형은 자료형 추론에 의해 생략할 수 도 있다.
또한 람다식 인자가 1개 인 경우는 생략하고 it으로 대체해서 사용할 수 있다.
고차함수
고차함수는 함수를 인자로 사용하는 함수이다.
fun say(name:String = "world", handler:(String)->String) {
println(handler("Hello " + name))
}
fun main() {
say("Foo1", { "\"$it!\"" })
say("Foo2") { "\"$it!\"" }
say { "\"$it!\"" }
}마지막 함수 객체 인자는 함수 호출 외부로 뺄 수 있다.
또한 함수 호출시 인수가 없다면 괄호를 생략해서 사용할 수 있다.
:: 키워드를 사용해서 일반 함수를 함수 객체로 사용할 수 있다.
fun say(name:String = "world", handler:(String)->String) {
println(handler("Hello " + name))
}
fun doublemark(str:String): String = "\"$str\""
fun main() {
say("Foo", ::doublemark)
}일반함수를 함수 객체로 사용하는 경우 호출 외부로 뺄 수 없다.
클래스
기본형
"class 클래스명 { }" 형태로 기본 클래스 정의하며 속성은 일반 변수 처럼 정의한다. 기본 가시성은 public이다.
class Greeting {
var msg = "Hello world!"
fun say() = println(msg)
}
fun main() {
var greeting = Greeting()
println(greeting.msg)
greeting.say()
}msg라는 속성 한 개가 정의되고 외부에서 접근할 수 있다. 메소드도 일반 함수 처럼 정의하면 된다.
생성자를 사용해서 내부 속성을 초기하해보자.
class Greeting {
var msg = "Hello"
var name:String = ""
constructor(name: String) {
this.name = name
}
fun say() = println("$msg $name!")
}
fun main() {
var greeting = Greeting("Foo")
greeting.say()
}속성을 다른 방식으로 정의할 수 있다.
class Greeting(var name: String) {
var msg = "Hello"
fun say() = println("$msg $name!")
}
fun main() {
var greeting = Greeting("Foo")
println(greeting.name)
greeting.say()
}클래스명에서 괄호 안에 변수 선언하듯이 속성을 정의할 수 있다.
그리고 인스탄스할 때에 값을 받아서 초기화도 된다.
init로 블럭 정의해서도 초기화활 수 있다.
class Greeting(val name: String) {
var msg = "Hello"
private var result:String
init {
result = msg + " " + name + "!"
}
fun say() = println(result)
}
fun main() {
var greeting = Greeting("Foo")
greeting.say()
}getter/setter
속성 정의시 get()과 set()을 사용해서 getter/setter을 정의할 수 있다.
별도 getter/setter가 없으면 자동으로 생성한다.
class Greeting() {
var msg = "Hello"
var name = "world"
get() = field
set(value) {
result = msg + " " + value + "!"
field = value
}
private var result = "Hello world!"
fun say() = println(result)
}
fun main() {
var greeting = Greeting()
greeting.name = "Foo"
greeting.say()
}result 속성의 가시성을 private을 해서 외부에서 접근하지 못하도록 했다.
사용할 수 있는 가시성 종류는 아래와 같다.
- public (기본): 공개
- internal: 같은 모듈
- protected: 하위 클래스
- private: 같은 클래스
setter/getter와 같이 활용해서 속성을 외부에서 읽기 전용으로 만들 수 있다.
class Greeting {
var msg = "Hello"
var name = "world"
private set
}
fun main() {
var greeting = Greeting()
greeting.name = "Foo" // cannot assign to 'name'
}직접 값을 할당하려면 에러가 발생한다. 별도 setter 함수를 만들어서 사용하면 된다.
class Greeting {
var msg = "Hello"
var name = "world"
private set
fun setName(name:String) { this.name = name }
fun say() = println("$msg $name!")
}
fun main() {
var greeting = Greeting()
greeting.setName("Foo")
println(greeting.name)
greeting.say()
}상속
“class 자식클래스 : 부모클래스” 형태로 상속은 “:”를 사용해서 표현한다.
open class Greeting(var msg:String) {
fun say() = println("$msg!")
}
class MorningGreeting : Greeting {
constructor() : super("Good morning") {
}
}
fun main() {
var greeting = MorningGreeting()
greeting.say()
}클래스는 기본 final로 되어 있기 때문에 바로 상속할 수 없다. open으로 선언해줘야 상속가능한 클래스가 된다.
사용할 수 있는 상속 제어 변경자는 다음과 같다.
- final(기본): 상속 불가
- open: 상속 가능
- abstract: 반드시 상속 해야함
- override: 부모 메소드/속성 오버라이드
부모 생성자를 호출할 때에 super()를 사용할수도 있고, 아래 처럼 부모 클래스를 바로 호출할 수도 있다.
open class Greeting(var msg:String) {
fun say() = println("$msg!")
}
class MorningGreeting : Greeting("Good morning") {
}
fun main() {
var greeting = MorningGreeting()
greeting.say()
}특수클래스
Any
클래스 계층구조에서 루트로 모든 클래스의 슈퍼클래스이다. 명시적으로 지정하지 않지만, Any을 상속하고 있다.
public open class Any {
public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String
}Nothing
Nothing은 인스탄스가 안되는 클래스로 값이 존재하지 않다는 의미로 사용된다. 예를 들어 함수 리턴타입이 Nothing이라면 절대 리턴하지 않는다라는 의미로 리턴하는 경우 예외가 발생한다.
public class Nothing private constructor()인터페이스
“interface 인터페이스명 { }” 형태로 표현한다.
interface Sayable {
fun say()
}
class Greeting(var name:String) : Sayable {
override fun say() = println("Hello $name!")
}
fun main() {
var greeting = Greeting("Foo")
greeting.say()
}인터페이스는 기본 메소드 정의와 프로퍼티를 추가할 수 있다.
interface Sayable {
var msg:String
fun say() = println(msg)
}
class Greeting(var name:String) : Sayable {
override var msg = "Hello " + name
}
fun main() {
var greeting = Greeting("Foo")
greeting.say()
}인터페이스는 다중 구현이 가능하다.
interface Sayable {
fun say()
}
interface Hello : Sayable {
override fun say() = print("Hello")
}
interface World : Sayable {
override fun say() = print("World")
}
class Message : Hello, World {
override fun say() {
super<Hello>.say()
print(" ")
super<World>.say()
println("!")
}
}
fun main() {
var message = Message()
message.say()
}다중 구현된 인터페이스 메소드가 동일할 경우 super<인터페이스명>.메소드() 형태로 해당 인터페이스의 메소드를 호출할 수 있다.
가시성
클래스, 객체, 인터페이스, 함수 등에 가시성 수정자(visibility modifier)를 지정할 수 있다. 사용할 수 있는 가시성 종류는 아래와 같다.
- public (기본): 공개
- internal: 같은 모듈
- protected: 하위 클래스
- private: 같은 클래스, 같은 파일
아무것도 지정하지 않았다면 기본으로 public으로 적용된다. private으로 정의되었다면 선언된 범위 영역 내에서만 접근할 수 있다. 이 말은 클래스에 필드나 메소드에 private이라면 해당 클래스내에서만 사용할 수 있고, 일반 함수가 private이라면 선언된 파일 내에서만 접근할 수 있다. internal은 같은 모듈에서만 사용가능하다고 하는데 모듈이 뭘까? 모듈이 패키지와 혼용할 수 있는데 서브프로젝트라고 보면 된다. 즉, 외부 라이브러리로 되어 있어서 이를 사용할 경우라고 보면 된다. protected은 최상위 층에서는 쓸 수 없고 클래스 내에서만 사용할 수 있다. 지정된 영역의 범위가 클래스 < 파일 < 모듈 < 전체 순으로 보면 된다.
참고
[1] online kotlin builder, https://play.kotlinlang.org/
'3.구현 > Java or Kotlin' 카테고리의 다른 글
| [spring] Spring Framework에서 DB연동 테스트 (0) | 2023.10.26 |
|---|---|
| Kotlin 배우기2 - 심화 (0) | 2023.10.18 |
| Reactive Programming 맛보기 (0) | 2023.09.14 |
| [Java] RTP 서버 간단히 구현하기 (0) | 2021.01.12 |
| [java] enum 사용하기 (0) | 2016.11.11 |