이번 포스팅에서는 코틀린
언어에 대해 포스팅해볼까 합니다. 안드로이드 앱 개발을 코틀린으로 할 것이기 때문에 코틀린 문법 정리 용도 입니다.
코틀린
은 요즘 많이 쓰는 프로그래밍 언어
이자, 안드로이드 공식 언어입니다. 쉽고 간결하고, JVM
기반 언어라는 특성 때문에 Java
언어를 대체하고자 많이 사용됩니다. 백엔드 분야에서는 일명 코프링(Kotlin
+ Spring
)도 있지요.
코틀린(kotlin)
은 IntelliJ, PyCharm 등 다양한 IDE를 선보인 것으로 유명한 JetBrains
사에서 오픈소스 그룹을 만들어 개발한 프로그래밍 언어입니다.
기존에는 안드로이드 앱을 자바로 개발했지만, 코틀린의 등장으로 점점 대체 되어 가는 추세입니다.
코틀린
은 어떻게 Java
를 대체할 수 있었을 까요? 바로 JVM 기반 언어
라는 특성 때문입니다.
🌟 JVM 기반 언어
를 다르게 얘기해보자면, 어떠한 언어도 자바 바이트 코드(.class)
로 컴파일할 수 있다면 동작할 수 있는 것입니다.
JVM이란?
JVM (Java Virtual Machine): 자바 프로그램을 실행하기 위한 실행 환경을 만들어 주는 소프트웨어입니다.
이러한 JVM은 흔히 자바를 설치할 때 설치하는 JDK (Java Development Kit)의 JRE(Java Runtime Environment)에 포함되어 있습니다.
JVM 너란 애는 참?
JVM이란 게 얼마나 멋진 친구냐 한마디로 정의하면, "JVM은 운영체제에 종속적이지 않습니다." 참으로 식상한 말이죠?
이것을 다르게 해석하면, 우리가 만든 JVM 기반 프로그램을 Linux, Windows, macOS 등 "어디에서나 실행 가능하다" 는 말입니다. 이게 얼마나 대단한 일인지는 겪어보지 않고서는 잘 와닿지가 않습니다.
그럼 우리는 언제 깨달을 수 있을까요? 애석하게도 인간은 문제가 발생하기 전까지 깨닫지 못합니다... 우리가 깨달음을 얻는 순간은 바로 이슈가 터졌을 때입니다. 이슈가 터지기 전에 방지할 수 있다면 좋겠죠?
운영체제(Windows, MacOS, Linux, ...) 별로 애플리케이션 확장자는 exe, deb, rpm 등 굉장히 다양한 종류의 애플리케이션이 많은데요. 각 애플리케이션은 실행 환경이 달라지면, 실행할 수 없습니다. ⇒ 호환성이 없기 때문이죠.
JVM 기반 언어
사실 JVM 기반 언어라는 특성은 호환성이 매우 좋아 특별하기 때문에 코틀린 외에도 굉장히 많습니다. 대표적인 언어로,
스칼라(scala)
,그루비(Groovy)
,Jython(Python)
가 있습니다.
코틀린 언어의 특징은 다음과 같습니다.
Kotlin의 기본 문법에 대해 살펴보도록하겠습니다.
코틀린에서 변수는 val
과 var
키워드로 선언합니다. 각 키워드를 정리해 보면,
val
(value): 불변성 변수var
(variable): 가변성 변수val data1 = 10
var data2 = 10
fun main(){
data1 = 20 // 오류!
data2 = 20 // 성공!
}
코틀린은 일반적으로 정적 타이핑 언어
이지만, 타입 추론
이 가능한 경우에는 위와 같이 생략할 수 있습니다. ⇒ 동적 타이핑
기능(타입 추론) 제공.
또한 명시적으로 타입을 선언할 수 있습니다.
val data1:Int = 10
lateinit
키워드를 사용하여 변수를 선언하면 변수의 초기화를 미룰 수 있습니다.
단, 2가지 조건이 있습니다.
lateinit var data1: Int // 오류!
lateinit val data2: String // 오류!
lateinit var data3: String // 성공!
lazy 키워드는 변수 선언문 뒤에 by lazy {}
형식으로 선언하며, 변수가 최초로 이용되는 순간 중괄호로 묶은 부분이 자동으로 실행되어 그 결과 값이 변수의 초기 값으로 할당됩니다.
val data4: Int by lazy {
println("in lazy .....")
10
}
fun main(){
println("in main .....")
println(data4 + 10)
println(data4 + 10)
}
/* 결과
in main .....
in lazy .....
20
20
*/
코틀린의 모든 변수는 객체
입니다. 자바에서 제공하는 원시 타입 개념 존재하지 않으며, 코틀린에서는 해당 타입들은 모두 객체로 제공됩니다.
기초 타입 객체: Int
, Short
, Long
, Double
, Float
, Byte
, Boolean
문자와 문자열: Char
, String
모든 타입: Any
(Java로 치면 Object
)
반환문이 없는 함수: Unit
fun some(): Unit {
println(10+20)
}
// 반환 타입이 없는 경우에는 Unit이 생략된 것
fun some() {
...
}
null이나 예외를 반환하는 함수: Nothing
⇒ Noting으로 선언한 변수는 null만 대입할 수 있습니다.
val data1: Nothing? = null
컬렉션 타입(collection type) 이란 여러 개의 데이터를 표현하는 방법이며 Array
, List
, Set
, Map
이 있습니다.
Array
는 배열을 표현하는 클래스입니다. 배열에 접근할 때는 대괄호([])를 이용하거나, set()
, get()
함수를 이용합니다.
배열의 타입은 제네릭(generic) 으로 표현됩니다.
Array
fun main(){
val data1: Array<Int> = Array(3, {0});
data1[0] = 10
data1[1] = 20
data1.set(2, 30)
}
배열의 타입이 기초 타입이라면 Array를 이용하지 않고 각 기초 타입의 배열을 나타내는 클래스를 이용할 수 있습니다.
BooleanArray
, ByteArray
, CharArray
, IntArray
, ShortArray
, LongArray
, FloatArray
, DoubleArray
등
val data4: IntArray = IntArray(3, {0})
arrayOf()
함수를 이용하면 선언과 동시에 할당할 수 있습니다.
val data4: IntArray = arrayOf<Int>(1,2,3)
List
, Set
, Map
은 Collection 인터페이스
를 타입으로 표현한 클래스이며, 컬렉션 타입 클래스 라고 합니다. 각 클래스의 특징은 다음과 같습니다.
List
- 순서가 있는 데이터 집합. 데이터의 중복 허용Set
- 순서가 없으며 데이터의 중복을 허용하지 않음Map
- 키와 값으로 이루어진 데이터 집합으로 순서가 없으며, 키의 중복을 허용하지 않음Collection 타입의 클래스는 가변(mutable)
클래스와 불변(immutable)
클래스로 나뉩니다. 불변 클래스는 초기에 데이터를 대입하면 더이상 변경(add, set)이 불가능한 클래스 입니다.
구분 | 타입 | 함수 | 특징 |
---|---|---|---|
List | List | listOf() | 불변 |
MutableList | mutableListOf() | 가변 | |
Set | Set | setOf() | 불변 |
MutableSet | mutableSetOf() | 가변 | |
Map | Map | mapOf() | 불변 |
MutableMap | mutableMapOf() | 가변 |
코틀린의 모든 변수는 객체
이기 때문에 null
값이 들어갈 수 있습니다. null
은 값이 할당되지 않은 상황을 의미합니다.
코틀린에서는 변수를 선언할 때 null을 대입할 수 있는 변수인지 아닌지 명확하게 구분해서 선언해야합니다.
널 허용으로 선언하려면 타입 뒤에 물음표(?)를 추가해야합니다.
var data1: Int = 10
data1 = null // 오류!
var data2: Int? = 10
data2 = null // 성공!
함수를 선언할 때는 fun
키워드를 사용합니다. 아래는 함수 선언 형식입니다.
fun 함수명(매개변수명: 타입): 반환 타입 { ... }
다음은 함수 선언의 특징입니다.
val
이나 var
키워드를 사용할 수 없으며, val
로 자동 적용됨. (즉, 불변 변수, 대입 불가)fun main(){
fun plus(data1: Int, data2: Int = 10){
return data1 + data2
}
println(plus(10))
println(plus(10, 20))
println(plus(data2 = 30, data1 = 10))
}
fun main(){
var data1 = 30
if(data1 > 10){
println("status: 0, data1: $data1")
}
else if(data1 > 0 && data1 <= 10){
println("status: 1, data1: $data1")
}
else {
println("status: -1, data1: $data1")
}
}
Kotlin에서는 if~else는 표현식으로도 사용할 수 있습니다.
fun main(){
var data1 = 30
val result: Int = if(data1 > 10){
0
}
else if(data1 > 0 && data1 <= 10){
1
}
else {
-1
}
}
fun main(){
var data1 = 30
when(data1) {
10 -> println("data is 10")
20 -> println("data is 20")
else -> {
println("data is unhandled!")
}
}
}
fun main(){
var sum:Int = 0
for(i in 1..10){
sum += i
}
println(sum)
}
for(i in 1..10)
for(i in 1 until 10)
for(i in 2..10 step 2)
for(i in 10 downTo 1)
fun main(){
var arr: IntArray = arrayOf(10, 20, 30)
for(i in arr.indices){
print(data[i])
}
for((index, value) in arr.withIndex()){
print(value)
}
}
fun main(){
var x: Int = 0
var sum: Int = 0
while(x < 10){
sum += ++x
}
println(sum)
}
다음으로는 코틀린의 객체 지향 프로그래밍에 대해 살펴보겠습니다.
코틀린의 클래스는 class
키워드로 선언합니다.
class User {}
클래스의 멤버는 생성자
, 변수
, 함수
, 클래스
로 구성됩니다. 이 중에서 생성자는 constructor
키워드로 선언하는 함수 입니다.
class User {
var name = "Heejeong Kim"
constructor(name: String){
this.name = name
}
fun getName(): String{
return this.name
}
}
코틀린의 생성자는 주 생성자
와 보조 생성자
로 나뉩니다. 주 생성자는 constructor
키워드로 클래스 선언부에 선언합니다.
// 1. 주 생성자 선언
class User constructor(){}
// 2. constructor 키워드 생략
class User (){}
// 3. 매개변수 없는 주 생성자 자동 생성
class User {}
// 4. 주 생성자의 매개 변수
class User (name: String, count: Int){}
보조 생성자는 클래스의 본문에 construtor 키워드로 선언하는 함수 입니다.
class User{
constructor(name: String){
// do somethine
}
constructor(name: String, age: Int){
// do somethine
}
}
init
영역 - 주 생성자의 본문입니다.
class User (name: String, age: Int){
// 클래스 멤버 변수 선언
var name: String
var age: Int
init {
this.name = name
this.age = age
}
fun some(){
println("My Name is $name}. age is $age}")
}
}
클래스 선언시 val 키워드를 이용하여 선언하면, init 영역 없이 클래스의 멤버 변수로 만들 수 있습니다.
class User (val name: String, val age: Int){
fun some(){
println("My Name is $name}. age is $age}")
}
}
fun main(){
val user = User("Heejeong", "27")
user.some()
}
클래스를 선언할 때 다른 클래스를 참조해서 선언하는 것을 상속(inheritance)
이라고 합니다.
코틀린에서는 클래스를 상속받으려면 선언부에 콜론(:)과 함께 상속받을 클래스 이름을 입력합니다
open class Parent {}
class Child: Parent(){
...
}
상위 클래스에 매개 변수가 있을 경우 하위 클래스에서도 상위 클래스를 호출할 때 매개변수 구성에 맞게 인자를 전달해야 합니다.
open class Parent(name: String) {}
class Child(name: String): Parent(name){
...
}
상속이 주는 최고의 이점은 상위 클래스에 정의된 멤버(변수, 함수)를 하위 클래스에서 자신의 멤버처럼 사용할 수 있다는 것입니다.
그런데, 때때로 하위 클래스에서 상위 클래스에서 정의된 멤버를 재정의 해야 할 수도 있습니다. 즉, "상위 클래스에서 선언된 변수나 함수를 같은 이름으로 하위 클래스에서 재정의 하는 것"을 오버라이딩
이라고 합니다.
open class Person() {
open var name = "hjkim"
open fun someFun(){
println("Hi~ I'm $name} (Person)")
}
}
class Developer: Person {
override var name = "developer_khj"
open fun someFun(){
println("Hi~ I'm $name} (Person)")
}
}
fun main(){
val obj = Developer()
obj.someFun()
}
접근 제한자
란 클래스의 멤버를 외부의 어느 범위까지 이용하게 할 것 인지를 결정하는 키워드 입니다.
코틀린이 제공하는 접근 제한자에는 public
, internal
, protected
, private
가 있으며, 각 접근 제한자의 접근 범위는 다음 표와 같습니다.
접근 제한자 | 최상위에서 이용 | 클래스 멤버에서 이용 |
---|---|---|
public | 모든 파일에서 가능 | 모든 클래스에서 가능 |
internal | 같은 모듈 내에서 가능 | 같은 모듈 내에서 가능 |
protected | 사용 불가 | 상속 관계의 하위 클래스에서만 가능 |
private | 파일 내부에서만 이용 | 클래스 내부에서만 이용 |
데이터 클래스
는 data
키워드로 선언하며, 자주 사용하는 데이터를 객체로 묶어 줍니다. 데이터 클래스는 VO 클래스를 편리하게 이용할 수 있게 해줍니다.
data class Student(val name: String, val email: String, val age: Int)
데이터 클래스는 주로 데이터를 다루는 것이 주 목적이기 때문에 equals() 메소드는 객체의 주요 데이터가 같은 비교합니다.
data class Student(val name: String, val email: String, val age: Int){
lateinit var address: String
constructor(name: String, email: String, age: Int, address: String): this(name, email, age){
this.address = address
}
}
fun main(){
val obj1 = Student("홍길동", "gildong@gmail.com", 10, "busan")
val obj2 = Student("홍길동", "gildong@gmail.com", 10, "seoul")
print(obj1.equals(obj2)) // => true
}
오브젝트 클래스
는 익명 클래스
를 만들 목적으로 사용합니다. 클래스의 이름이 없기 때문에 선언과 동시에 객체를 생성해야 합니다.
val obj = object {
var data = 10
fun some(){
println("data: $data")
}
}
fun main(){
obj.data = 30 // 오류!
obj.some() // 오류!
}
익명 클래스긴 하지만 익명 클래스를 선언한 거만으로 클래스의 구성요소에 접근할 수 없습니다.
사실상 자바의 인터페이스를 구현하는 원리와 유사합니다. 익명 클래스의 멤버 변수 및 메소드에 접근하기 위해서는 상속받아야합니다.
open class Super {
open var data = 10
open fun some(){
println("I'm Super. data: $data")
}
}
val obj = object: Super() {
var data = 10
fun some(){
println("data: $data")
}
}
fun main(){
obj.data = 30 // 성공!
obj.some() // 성공!
}
컴패니언 클래스는 멤버 변수나 함수를 클래스 이름으로 접근하고자 할 때 사용합니다. Java에서는 static
키워드와 유사하다고 볼수 있습니다.
class Myclass {
companion object {
var data = 10
fun some(){
println(data)
}
}
}
fun main(){
MyClass.data = 20 // 성공!
MyClass.some() // 성공!
}
람다 함수
란? 많은 프로그래밍 언어에서 제공하는 익명 함수
기법입니다.
람다 함수의 특징은 다음과 같습니다.
fun
키워드로 선언하지만 람다 함수는 fun
키워드를 이용하지 않으며 함수 이름이 없습니다.// 1. 형식
{ 매개변수 -> 함수 본문}
// 2. 예시
val sum = {no1: Int, no2: Int -> no1 + no2}
// 3. 매개 변수 없는 람다 함수 (화살표 생략 가능)
{ -> println("Hello, Kotlin!")}
{println("Hello, Kotlin!")}
typealias 를 이용해 변수 타입은 물론 함수 타입을 선언할 수 있습니다.
typealias MyInt = Int
fun main(){
val data2:MyInt = 10
}
typealias MyFunType = (Int, Int) -> Boolean
fun main(){
val someFun: MyFunType = {no1, no2 -> no1 > no2}
}
고차함수
란 함수를 매개변수로 전달받거나 반환하는 함수를 의미합니다.
fun hotFun(arg: (Int -> Boolean): () -> String){
val result = if(arg(10)) {"valid"} else {"invalid"}
return {"hotFun result: $result"}
}
fun main(){
val result = hotFun({no -> no > 0})
print(result())
}
널(null)
이란? 객체가 선언되었지만 초기화되지 않은 상태를 의미합니다.
객체는 흔히 데이터가 저장된 주소를 참조하므로 흔히 참조 변수라고 합니다. 데이터가 메모리에 저장되면 어디에 저장됐는지 알아야 이용할 수 있는데, 이 때 메모리 위치를 식별하는 것이 주소입니다.
그런데 널은 이 주소를 가지지 못한 상태를 나타냅니다.
객체가 널인 상태에서 객체에 접근하게 되면 널 포인트 예외(NullPointException)
이 발생합니다.
이 때 널 안정성
이란 널 포인트 예외가 발생하지 않도록 코드를 작성하는 것을 말합니다.
?
연산자: 변수 타입을 null 허용/불허용 구분?.
연산자: 널 허용으로 선언한 변수는 접근 시에 반드시 ?.
연산자로 접근?:
연산자: 변수가 null이면 null 반환!!
연산자: 객체가 null일때 예외를 일으키는 연산자. 일부로 예외를 던짐.이번 포스팅에서는 본격적인 안드로이드 앱 개발에 앞서, 앱 개발시 사용할 언어인 코틀린
의 특징과 문법을 정리해보았습니다.
다음 포스팅에서는 안드로이드 앱 프로젝트 구성에 대해 알아보겠습니다. 😍