Kotlin 자바 개발자라면 코틀린은 쉽게 배울 수 있다

자바를 경험해본 적이 있으면 코틀린을 배우기에 한결 쉬운 언어인거 같습니다.

저도 재미삼아 코틀린을 공부하고 있으며, 개인적으로 공부하면서 작성한 내용이기 때문에 잘못된 부분이 있으면 코멘트 남겨주시면 감사하겠습니다.

안드로이드의 공식 언어인 코들린(kotlin)에 대해 사용법과 예제를 보도록 하겠습니다.

 

Kotlin 이란?

코들린은 JetBrains사가 만든 JVM 환경에서 동작하는 언어로 자바와 호환 운용이 가능한 언어입니다.

예를 들어 어떠한 클래스 A를 Java로 작성하고 그것을 코들린에서 상속을 B라는 클래스가 있습니다.

다시 자바에서 C라는 클래스가 B를 상속하여 사용을 할 수 있는 것입니다.

Java로 만든 프로젝트에 추가로 기능을 구현할 때 코틀린을 사용하여 새로운 기능을 쉽게 추가할 수 있습니다.

코틀린은 안드로이드에서 Java 대신에 많이 사용된 언어입니다.

Android Studio는 InelliJ IDE라는 IDE를 바탕으로 만들어졌지만 JetBrains는 InelliJ IDEA의 바탕이 됩니다.

코틀린은 Android Studio에서도 작동이 잘 되도록 되어있기 때문에, 많은 개발자들이 사용을 하고 있습니다.

또한 Google이 코틀린을 안드로이드 공식 언어로 발표도 했습니다.

코틀린은 더 좋은 Java를 사용하기 위해 경량화 시킨 언어입니다.

 

코틀린 사용 환경

코틀린 인스톨 방법은 몇 가지가 있습니다.

인스톨 방법은 간략하게 설명하겠습니다.

・인스톨을 하지 않아도 브루아저에서 실행 가능

・Android Studio 3.0
  코틀린을 지원하는 버전을 다운로드 및 인스톨

・Android Studio 2.x
  Plugins > Install JetBrains plugin에서 Kotlin을 검색하여 플러그인 인스톨

・커맨드 라인
  Mac의 경우에는 brew install kotlin 을 실행

 

 

코들린과 자바 비교

코틀린과 자바는 많은 점들이 비슷합니다.

하지만 작성 방법은 조금 차이가 있기 때문에 주의해야 합니다.

샘플을 보면서 확인해보겠습니다.

// Java
public class Main {
	public static void main(String[] args) {
		System.out.println("Hello, world!");
	}
}

 

// Kotlin
fun main(args: Array<String>) {
	println("Hello, world!")
}

 

코틀린에서는 main 함수를 만들 필요가 없습니다.

코틀린에서는 println 함수가 아니기 때문에 System.out을 작성하지 않아도 됩니다.

그리고 함수는 static 함수로 인식하기 때문에 static도 작성하지 않아도 됩니다.

기본은 public이기 때문에 public도 작성하지 않았습니다.

코틀린에서는 소스가 끝나는 부분에 ;도 필요 없습니다.

fun과 Array<String>에 대해서는 뒤에서 설명하겠습니다.

 

연산자, 리터랄, 코멘트

기본적으로 자바와 동일합니다. 0x0F나 42L 등 리터랄도 사용할 수 있습니다.

그리고 1_000 처럼 언더바를 넣어서 사용할 수도 있습니다.

변수 선언

// Java
String s = "abc";
final String t = "xyz";

 

// Kotlin
var s: String = "abc"
val t: String = "xyz"

 

코틀린에서는 자바와 다르게 변수명 뒤에 타입을 작성합니다.

final 변수를 선언할 때에는 val, 변경 가능한 변수를 선언할 때에는 var을 사용합니다.

var이나 val을 작성하는 것이 자바와 비교하면 조금은 번거롭지만, 코틀린에서는 형 추론(Type Inference)에 의해 변수를 선언할때 타입을 생략할 수도 있습니다.

// Kotlin
var s = "abc"
val t = "xyz"

 

변수를 선언할 때에 타입을 생략하는 경우, 우측의 값을 보고 타입을 추론하여 적용됩니다.

위 샘플에서는 “abc” 또는 “xyz”를 보고 타입이 정해집니다.

 

프리미티브 타입

코틀린에서는 자바에서 사용되는 프리미티브 타입이 대문자로 시작합니다.

Int, Double, Boolean 등등

// Java
int a = 42;
final boolean b = true;

 

// Kotlin
var a: Int = 42
val b: Boolean = true

 

제어문

if

코틀린의 if는 식으로 사용이 됩니다.

식으로 사용되는 만큼 블록 안에서 처리한 결과를 변수에 바로 대입할 수 있습니다.

// Java
final String foo;
if (bar < 42) {
	foo = "abc";
} else {
	foo = "xyz";
}

 

// Kotlin
val foo = if (bar < 42) {
	"abc"
} else {
	"xyz"
}

 

코틀린에서 if가 식으로 사용되는 이유는 Java의 삼항연사자 처럼 사용하기 위해서입니다.

코틀린에는 삼항연산자가 없습니다.

코틀린에서 삼항연사자를 사용하고 싶은 경우에는 if를 다음과 같이 사용할 수 있습니다.

// Java
final String foo = bar < 42 ? "abc" : "xyz";

 

// Kotlin
val foo = if (bar < 42) "abc" else "xyz"

 

for

코틀린에는 자바의 확장 for문 밖에 없습니다.

문법은 거의 비슷하지만 : 대신에 in을 사용합니다.

또한 첫 번째 항목에 타입도 생략해서 작성할 수 있습니다.

// Java
for (int number : numbers) {
	System.out.println(number);
}
// Kotlin
for (number in numbers) {
	println(number)
}

 

// Java
for (int i = 0; i< 100; i++) {
	System.out.println(i);
}
// Kotlin
for (i in 0 until 100) {
	println(i)
}

 

until은 함수입니다.

코틀린에서는 +, *등의 연산자를 함수를 사용해서도 작성할 수 있습니다.

0 until 100 의 반환값은 IntRange로, IntRange는 Iterable 이기때문에 for – in으로 반복이 됩니다.

여러 함수를 이용하여 for문을 사용한 예제 입니다.

// Kotlin

// for (int i = 99; i >= 0; i--)
for (i in 99 downTo 0) println(i)

// for (int i = 0; i < 100; i += 2)
for (i in 0 until 100 step 2) println(i) 

// for (int i = 1; i <= 100; i++)
for (i in 1..100) println(i) 

 

switch 

switch문은 코틀린에서 when으로 사용합니다.

when도 식으로 사용할 수 있습니다.

그리고 case를 사용하지 않기 때문에 break도 사용하지 않습니다.

// Java
final String s;
	switch (a) {
		case 0:
			s = "abc";
			break;
		case 1:
		case 2:
			s = "def";
			break;
		default:
			s = "xyz"
			break;
}

 

// Kotlin
val s = when (a) {
	0 -> "abc"
	1, 2 -> "def"
	else -> "xyz"
}

 

 

new 

코틀린 에서는 자바와 다르게 생성자를 호출할 경우 new는 필요 없습니다.

// Java
final Foo foo = new Foo();

 

// Kotlin
val foo = Foo()

 

클래스

코틀린에서 클래스를 사용하는 방법을 보도록 하겠습니다.

// Java
public class Person {
	private final String firstName;
	private final String lastName;
	private int age;

	public Person(String firstName, String lastName, int age) {
		this.firstName = firstName;
		this.lastName = lastName;
		this.age = age;
	}

	public String getFirstName() {
		return firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age)) {
		this.age = age;
	}

	public String getFullName() {
		return firstName + " " + lastName;
	}
}

 

// Kotlin
class Person {
	val firstName: String
	val lastName: String
	var age: Int

	constructor(firstName: String, lastName: String, age: Int) {
		this.firstName = firstName
		this.lastName = lastName
		this.age = age
	}

	fun getFullName(): String {
		return firstName + " " + lastName
	}
}

 

코틀린에서는 디폴트로 public이 적용되기 때문에 생략이 가능합니다.

그 외에도 private, protected, internal가 있습니다.

private는 자바와 같습니다.

protected도 자바와 거의 동일하지만 다른 점으로는 같은 패키지 안이라도 서브 클래스에서만 접근이 가능합니다.

internal은 자바의 기본 접근 레벨인 패키지 private와 다르게 동일 모듈 안에서만 접근할 수 있습니다.

 

 

프로퍼티 (Property)

프로퍼티는 필드와 getter, setter를 한 번에 사용할 수 있는 형식으로 되어있습니다.

즉, 코틀린에서는 getter, setter를 만들지 않아도 됩니다.

// Java
final int age = person.getAge();
person.setAge(age + 1);

 

// Kotlin
val age = person.age
person.age = age + 1

 

자바에서 필드를 public으로 만든 것과 같다고 생각을 할 수 있습니다.

하지만 프로퍼티는 필드와 다르게 함수처럼 결과를 반환할 수 있습니다.

샘플을 보겠습니다.

// Java
public class Person {
	...

	public String getFullName() {
		return firstName + " " + lastName;
	}
}

 

// Kotlin
class Person {
	...

	val fullName: String
	get() {
		return firstName + " " + lastName
	}
}

 

샘플처럼 프로퍼티는 보통 사용하는 프로퍼티처럼 person.fullName로 호출하여 사용할 수 있습니다.

그리고 val가 아닌 var로 지정하면 set을 사용해서 setter처럼 사용할 수도 있습니다.

또한 프로퍼티는 다음과 같이 생략하여 작성할 수 있습니다.

// Kotlin
val fullName: String
get() = firstName + " " + lastName

 

자바에서 필드를 public 하지 않고 getter나 setter를 만들면, 필드를 서브 클래스에서 오버라이드 해서 변경을 하거나, 인터페이스로 추상화 할 수 없습니다.

코틀린의 프로퍼티는 함수와 동등하기 때문에 서브 클래스에서 오버라이드 하거나, 인터페이스에 프로퍼티를 선언하고 그것을 오버라이드해서 사용하는 것도 가능합니다.

 

생성자 (Constructor)

코틀린에서는 프로퍼티 선언과 생성자 선언을 동시에 할 수 있습니다.

// Kotlin
class Person(val firstName: String, val lastName: String, var age: Int) {
	...
}

 

자바에서 필드 선언과 생성자와 getter, setter 작성은 IDE에서 자동으로 생성해주는 경우가 많이 있지만 코들린에서는 위의 샘플처럼 동시에 작성할 수 있습니다.

이와 같은 생성자를 Primary 생성자 (Primary Constructor)라고 합니다.

Primary 생성자는 편리하지만, 초기화하는 것 이외의 처리는 작성할 수 없습니다. Primary 생성자를 초기화 하는 처리 외에도 작성을 하고 싶은 경우에는 init를 사용합니다.

// Java
public class Person {
	private final String firstName;
	private final String lastName;
	private int age;

	public Person(String firstName, String lastName) {
		this.firstName = firstName;
		this.lastName = lastName;
		age = 0;
	}

	...
}

 

// Kotlin
class Person(val firstName: String, val lastName: String) {
	var age: Int

	init {
		age = 0
	}

...
}

 

반면, constructor로 작성한 생성자는 세컨더리 생성자( Secondary Constructor)라고 부릅니다.

Primary 생성자는 하나밖에 만들 수 없지만, 세컨더리 생성자는 여러개 만들 수 있습니다.

// Java
public class Person {
	private final String firstName;
	private final String lastName;
	private int age;

	public Person(String firstName, String lastName, int age) {
		this.firstName = firstName;
		this.lastName = lastName;
		this.age = age;
	}

	public Person(String firstName, String lastName) {
		this(firstName, lastName, 0);
	}

	...
}

 

// Kotlin
class Person(val firstName: String, val lastName: String, var age: Int) {
	constructor(firstName: String, lastName: String) : this(firstName, lastName, 0) {
	}

...
}

 

생성자를 생략해서 작성할 수도 있습니다.

예를 들어 age를 생략하여 생성자를 작성해보겠습니다.

// Kotlin
class Person(val firstName: String, val lastName: String, var age: Int = 0) {
	...
}

 

방법은 Primary 생성자에 기본값을 설정 하면 됩니다.

생성자를 호출할때에 age에 0을 설정하게 됩니다.

// Kotlin
// Person("Albert", "Einstein", 0) 과 같음
val person = Person("Albert", "Einstein")

 

 

함수

// Java
public class Person {
	...

	// 자바 예제 함수
	public int elapse(int years) {
		age += years;
		return age;
	}
}

 

// Kotlin
class Person(val firstName: String, val lastName: String) {
	...

	// 코틀린 예제 함수
	fun elapse(years: Int): Int {
		age += years
		return age
	}
}

 

자바와 코틀린의 함수 선언부가 fun으로 시작한다는 것 외에는 차이가 거의 없습니다.

파라미터도 설정도 코틀린에서 변수 선언을 하듯이 작성하면 됩니다.

반환값의 타입은 파라미터 위에 : Int 라고 지정하면 됩니다.

반환값이 없는 경우에는 : Unit라고 작성하면 됩니다.

또는 생략을 해도 됩니다.

함수를 식처럼 사용하고 싶은 경우에는 아래와 같이 작성할 수도 있습니다.

// Kotlin
fun getFullName(): String = firstName + " " + lastName

 

상속

코틀린에서의 함수는 기본적으로 final이 적용됩니다.

이유는 요즘 프로그램을 만들 때에 상속은 좋지 않은 작성방법이라는 인식이 생겼기 때문입니다.

그래서 최소한으로 필요한 만큼만 상속을 사용하고 있습니다.

코틀린에서 상속을 하기 위해서는 open을 설정해줘야 합니다.

// Java
public class Person {
	...

	public String getFullName() {
		return firstName + " " + lastName;
		}
}

public final class EasternPerson extends Person {
	public EasternPerson(String firstName, String lastName, int age) {
		super(firstName, lastName, age);
	}

	@Override
	public String getFullName() {
		return lastName + " " + firstName;
	}
}

 

// Kotlin
// class앞에 open을 설정
open class Person(val firstName: String, val lastName: String, var age: Int) {
	...

	open val fullName: String // open 작성
	get() = firstName + " " + lastName
}

class EasternPerson(firstName: String, lastName: String, age: Int) : Person(firstName, lastName, age) {
	override val fullName: String
	get() = lastName + " " + firstName
}

 

extends 대신으로는 :를 사용합니다.

EasternPerson Primary 생성자의 파라미터에는 vaㅣ또는 var을 사용하고 있지 않습니다.

val 이나 var을 사용하게 되면 프로퍼티를 선언한다는 의미가 되지만, 아무것도 사용하지 않으면 단순한 파라미터로 사용을 할 수 있습니다.

super 클래스의 생성자에 파라미터를 전달하는 경우에도 var과 val을 필요 없습니다.

: Person(firstName, lastName, age) 의(firstName, lastName, age) 부분이 Java의 super(firstName, lastName, age)에 해당됩니다.

그리고 자바에서는 @Override는 필수가 아니지만 코틀린에서는 필수입니다.

@Override를 붙이는 것을 잊어버리지 않게 주의해야 합니다.

 

 

인터페이스

작성방법은 다르지만 개념은 자바와 동일합니다.

// Java
public interface Foo {
	int getBar();
	void baz(String qux);
}

 

// Kotlin
interface Foo {
	val bar: Int
	fun baz(qux: String)
}

 

코틀린에서 선언한 변수 bar처럼 인터페이스에서 프로퍼티를 선언하고 클래스에서 사용할 수도 있습니다.

즉 코틀린에서는 프로퍼티를 함수처럼 사용할 수 있습니다.

// Kotlin
class ConcreteFoo(override val bar: Int): Foo {
	override fun baz(qux: String) {
		println(qux)
	}
}

 

제네릭

코틀린에서도 자바처럼 제네릭을 사용할 수 있습니다.

// Java
public final class Box<T> {
	private T value;
	
	public Box(T value) {
		this.value = value;
	}
	
	public T getValue() {
		return value;
	}
	
	public void setValue(T value) {
		this.value = value;
	}
}

 

// Kotlin
class Box<T>(var value: T)

 

컬렉션

List

코틀린에서도 List나 Map, Set이 존재합니다.

List와 MutableList가 있습니다.

List 인스턴스는 listOf 함수이고, MutableList 인스턴스는 MutableListOf 함수로 작성합니다.

코틀린에서도 ArrayList와 HashMap 등을 사용할 수 있지만 특별히 꼭 사용해야 하는 경우가 아니라면 listOf 등의 함수를 사용을 해도 됩니다.

// Java
final List<Integer> a = Collections.unmodifiableList(Arrays.asList(2, 3, 5));

final List<Integer> b = new ArrayList();
b.add(2);
b.add(3);
b.add(5);

 

// Kotlin
val a: List<Int> = listOf(2, 3, 5)

val b: MutableList<Int> = mutableListOf()
b.add(2)
b.add(3)
b.add(5)

 

MutableList는 List의 서브 타입이기 때문에 다음의 예제처럼도 사용할 수 있습니다.

// Kotlin
val a = mutableListOf(2, 3, 5)
val b: List<Int> = a

println(b) // [2, 3, 5]

a.add(7)

println(b) // [2, 3, 5, 7]

 

배열

코틀린에서 배열 사용은 다음 샘플과 같습니다.

// Java
final int[] a = new int[] { 2, 3, 5};

 

// Kotlin
val a = intArrayOf(2, 3, 5)

 

람다식

코틀린에서도 자바8 이후의 버전처럼 람다식을 사용할 수 있습니다.

코틀린에서는 람다식 전체를 {}로 감싸줘야 합니다.

// Java
final Stream<Integer> squared = numbers.map(x -> x * x);

 

// Kotlin
val squared = numbers.map({ x -> x * x })

그리고 컬렉션은 map이나 filter 등에서 바로 사용할 수 있도록 되어있습니다.

 

 

try ~ catch ~ finally

try ~ catch ~ finally도 자바처럼 사용 할 수 있습니다.

코틀린에는 자바의 try-with-resources가 없습니다.

비슷한 기능 만들고 싶은 경우에는 use 함수를 사용하면 됩니다..

사용방법은 샘플처럼 사용할 수 있습니다.

// Java
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
	...
} catch (IOException e) {
	...
}

 

// Kotlin
try {
	file.bufferedReader().use { reader ->
	...
	}
} catch (e: IOException) {
	...
}

 

package와 import

package나 import를 작성할 때에 ;가 없을 뿐 자바와 동일합니다.

코틀린에서 자바 패키지를 import 하고 그것을 사용하거나 자바 클래스를 상속할 수 있습니다.

자바에서는 클래스명이 중복되면 사용할 수 없거나 또는 완전한 클래스 명을 작성해주지 않으면 안 되지만, 코틀린에서는 as를 사용하여 별칭을 붙여서 사용할 수 있습니다.

// Kotlin
import java.util.Date
import java.sql.Date as SqlDate

 

코틀린은 문법 작성방법이 조금 다르기만 할 뿐 자바와 기본 개념을 거의 비슷합니다.

그리고 자바와 비교해 코드가 깔끔한 부분도 상당히 있습니다.

저도 코틀린을 아직 공부 중에 있지만 자바와 비슷하다는 점에서 어느 정도는 다루기 편한 프로그램 언어가 아닌가 생각이 듭니다.

댓글