[Kotlin in Action] Chap2. 코틀린 기초
현재 진행하고 있는 스터디에서 코틀린 인 액션을 가지고 공부를 진행하고 있습니다. Github에 Repository를 생성하여 내용을 정리하여 관리하고 있지만, 블로그에서도 확인할 수 있도록 마이그레이션 하고 있습니다.
코틀린 기초
1. 함수
1 | // 블록이 본문인 함수 |
- 함수 선언은
fun
키워드로 시작 - fun 다음에
함수명
을 명시 - 함수 이름 뒤에 괄호 안에 파라미터들 명시
- 변수 선언과 마찬가지로 파라미터 뒤에
:
을 통해 타입을 명시
- 변수 선언과 마찬가지로 파라미터 뒤에
- 본문
- 블록이 본문인 함수: 중괄호로 본문을 감싼 형태
- 식이 본문인 함수: 중괄호 대신 등호와 식을 이용한 형태
TODO - 알고가자!
문(statement)과 식(expression)의 차이
- 식은 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있으나 문은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값도 만들어내지 않는다
2. 변수
1 | // 타입 표기 생략 (feat. 타입 추론) |
- 코틀린은 변수를 선언할 때에 변수명 뒤에
:
을 통해 명시
TODO - 알고가자 !
변수명을 뒤에 명시하는 이유?
-
타입을 생략할 경우 식과 변수 선언이 구별을 할 수 없기 때문에 변수명 뒤에 명시하거나 생략하도록 설계되었다.
-
자바와 마찬가지로 부동소수점 사용시
Double
타입이 된다.
변경 가능한 변수와 변경 불가 함수
val
: 변경 불가능한 값을 저장하는 변수. 일단 초기화하면 재대입이 불가.- 딱 1번만 초기화가 가능
- 자바의
final 변수
에 해당 - val 참조 자체가 불변일지라도 그 참조가 가리키는 객체의 내부 값은 변경될 수 있다.
아래 코드는 올바른 코드이다.
1 | val languages = arrayListOf("java") // 불변 참조 선언 |
TODO - 의논해보자!
val 객체의 내부 값이 변경 가능한 이유는 무엇일까?
- 태형’s Think
- 코틀린은 자바와 동일하게 기본적으로
call by value
이며 객체 전달 시 메모리 주소를 전달하므로 객체 내부를 변경하여도 메모리 값이 변하는 것이 아니기 때문에 변경이 가능하다.
- 코틀린은 자바와 동일하게 기본적으로
TODO - 고민해보자!
호출되는 순서를 고민해보자.
1 | object Main { |
1 | object Main { |
callByValue정답: funA() -> callByValue()
callByName 정답: callByName() -> funA()
callByName의 이점?
1 | object Main { |
1 | object Main { |
-
callByValue의 경우 condition에 상관 없이 doSomething()이 실행되며 비효율적으로 동작하게 되지만, callByName을 사용하는 경우 condition 값에 따라 doSomething()이 실행되므로 효율적인 측면에서 더 뛰어나다.
-
var
: 변경 가능한 참조. 변수 타입은 고정.- 자바의 일반 변수에 해당
- 타입은 변환시킬 수 없음
1 | var answer = 42 |
- 문자열 템플릿
1 | val name ="TEAM-ASC" |
클래스
1 | // kotlin |
1 | // java |
- 코틀린 클래스의 기본 접근지정자가 public 으로 생략 가능
- getter/setter 메소드를 기본적으로 제공하여 생략 가능
프로퍼티
자바의 필드와 접근자 메소드(getter/setter 메소드)를 완전히 대신함
1 | class Person( |
1 | Person p = Person("Bob", false) |
- val 프로퍼티: 읽기 전용 프로퍼티로 private 필드와 필드를 읽는 pulbic getter() 를 생성함(backing 필드)
TODO - 찾아보자 !
프로퍼티에 접근지정자 (private)을 지정했을 경우에 private 필드 + private getter()가 되어 접근이 안되는 것인가? 아니면 접근 지정자 지정을 안했을 때에 default가 public 필드인가?
- var 프로퍼티: 읽고 쓰기가 가능한 프로퍼티로 private 필드와 public getter()/public setter() 를 생성함(backing 필드)
- 뿐만 아니라 생성자가 필드를 초기화 하는 구현이 내부적으로 구현되어 있음
TODO - 알고가자!
backing 필드 ?
- 프로퍼티의 값을 저장하기 위한 비공개 필드
프로퍼티 이름이 is로 시작할 경우 - 프로퍼티 이름과 동일한 getter() 생성: 예, isMarried()
커스텀 접근자
1 | class Rectangle(val height: Int, val width: Int) { |
- 위와 같이 사용 시 해당 프로퍼티에 접근할 때마다 getter가 프로퍼티 값을 매번 다시 계산함
소스코드 구조
- 파일의 맨 앞에 package 문 사용해서 패키지 지정
- 파일의 모든 선언 (클래스, 함수, 프로퍼티 등)이 해당 패키지에 속함
- 디렉토리 구조와 패키지 구조가 일치할 필요 없음
- 같은 패키지에 속해있다면 다른 파일에서 임포트 없이 정의한 선언 사용 가능
- 다른 패키지에서 사용하려면
import
키워드로 사용할 선언을 임포트해야 함
enum
enum
키워드를 사용하여 열거타입 지정
1 | enum class Color { |
- 자바는
enum
, 코틀린은enum class
- 코틀린에서의 enum은 소프트 키워드(soft keyword) 라고 부름
- class 앞에 붙여질 경우 특별한 의미를 지니지만 다른 곳에서는 이름에 사용할 수 있음(예약어처럼 사용이 불가하지 않음)
프로퍼티와 메소드 선언 가능 (메소드 선언시 마지막 열거 값 뒤에 세미콜론 필수)
1 | enum class Color(val r: Int, val g: Int, val b: Int) { |
when
자바의 switch
와 유사하며 코틀린의 when
은 if
와 마찬가지로 값을 만들어내는 식 임
1 | fun getColorString(color: Color) = |
- 각 분기에
break
키워드 필요 없음 ,
를 통해 여러 매치 패턴을 지정할 수 있음- 모든 분기 식에 만족하지 않으면 else 분기가 실행됨
when 식은 객체의 동등성 사용
1 | fun mix(c1: Color, c2: Color) = |
setOf()
: 자바로 치면 set을 만들어 주는 메소드로 HashSet과 비슷하다고 생각하면 됨- c1, c2가 들어오는 순서에 상관이 없음
인자 없는 when 식
1 | fun mixOptimized(c1: Color, c2: Color) = |
- when에 인자가 없으려면 각 분기의 조건이 Boolean 결과를 계산하는 식이어야 함
TODO - 생각해보자!
- 불필요한 인스턴스를 생성하지 않아 불필요한 가비지 객체가 늘어나지 않는 장점이 있으나 가독성이 매우 떨어지는 코드가 될 수 있어 주의하여 사용하는 것이 좋을 것 같다.
스마트 캐스트
Object의 타입 확인과 변환을 한번에 해주는 기능
1 | fun eval(e: Expr): Int { |
1 | fun eval(e: Expr): Int { |
is
연산자를 통해 변수 타입 검사- 검사한 이후에는 명시적인 캐스팅 없이 해당 타입으로 바로 사용 가능
- 블록으로 되어있는 경우 마지막 식이 반환값이 되어 반환됨
while 루프
자바의 while 루프
와 동일하게 사용된다.
1 | while (조건) { |
for 루프
- 범위(CloseRange 인터페이스): 두 값으로 이루어진 구간
- 수열(Progression): 범위에 속한 값을 일정한 순서로 이터레이션
- 예시
1 rangeTo 10 step 2
또는1..10 step 2
: 1~10 까지 2씩 증가하며 이터레이션100 downTo 1 step 2
: 100부터 1로 줄어들며 2씩 감소하며 이터레이션0 until 10
: 0부터 10까지 이터레이션(단, 10은 미포함)
자바의 for (int i = 0; i < length; i++)
에 해당하는 루프가 없다. 대신 범위
를 사용한다.
1 | // 범위 1~100 (100 포함) |
맵, 리스트에 대한 이터레이션
1 | val binaryReps = TreeMap<Char, String>() |
in
을 통해 값이 범위에 속하는지 검사하기
1 | fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' |
in
은contains
와 동일
익셉션(Exception)
자바나 다른 언어의 예외 처리와 비슷하다. 즉, 함수 실행 중 오류가 발생하면 예외를 던질(throw) 수 있고 함수를 호출하는 쪽에서는 그 예외를 잡아 처리(catch)할 수 있다. 예외에 대해 처리를 하지 않은 경우 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올 때까지 예외를 다시던진다(rethrow).
1 | if (percentage !in 0..100) { |
1 | fun readNumber(reader: BufferedReader): Int? { |
- 자바와의 가장 큰 차이점은 함수명 뒤에
throws
절이 없다는 것임- 자바에서 상기 코드는 함수 뒤에 throws IOException을 붙여야 함
- IOException이 체크 예외이기 때문에 자바는 명시적으로 표현해야 함
TODO - 알고가자!
Java에서의 체크 예외
- ClassNotFoundException
- CloneNotSupportedException
- InstantiationException
- IOException
Java에서의 언체크 예외
- RuntimeException을 상속받는 Exception
- ArithmeticException
- IllegalArgumentException
- IndexOutOfBoundsException
try는 식
1 | fun readNumber(reader: BufferedReader) { |