1. if, if-else
if 예시)
fun validateScore(number: Int) {
if (number < 0) {
throw IllegalArgumentException("${number}는 0보다 작을 수 없습니다.")
}
}
- 반환값을 생략하면 Unit(==void)로 적용된다.
- new를 사용해서 객체를 생성하지 않는다.
- fun을 이용하여 함수를 생성한다.
- 세미콜론을 사용하지 않는다.
- ${}를 이용하여 변수를 문자열에 넣는다.
if문은 자바와 동일하다.
if-else 예시)
fun getPassOrFail(score: Int): String {
if (score > 50) {
return "P"
} else {
return "F"
}
}
if-else 또한 자바와 동일하게 생겼지만 차이점이 있다.
Java에서 if-else는 Statement이지만,
Kotlin에서는 Expression이다.
2. Expression과 Statement
Statement : 프로그램의 문장, 하나의 값으로 도출되지 않는다
예시)
int x = 5; // 변수 선언과 값 할당 - Statement
System.out.println("Hello"); // 출력 작업 - Statement
Expression : 하나의 값으로 도출되는 문장
예시)
int y = 2 + 3; // '2 + 3'은 Expression이고, y에 할당하는 전체는 Statement
int z = y * 10; // 'y * 10'은 Expression
Statement중에 하나의 값을 반환하는것을 Expression이므로 위와같이 표현할 수 있다.
Java에서 if-else는 Statement이지만, Kotlin에서는 Expression이다.
즉 Kotlin에서 if-else는 반환값이 존재한다는것이다.
그래서 다음과 같이 작성할 수 있다.
before)
fun getPassOrFail(score: Int): String {
if (score > 50) {
return "P"
} else {
return "F"
}
}
after)
fun getPassOrFail(score: Int): String {
return if (score > 50) {
"P"
} else {
"F"
}
}
return이 if 밖으로 나간 모습을 확인할 수 있다.
after 2)
fun getPassOrFail(score: Int): String {
return if (score > 50)
"P"
else
"F"
}
Expression이므로 중괄호가 필요하지 않다.
Kotlin에서는 if-else를 expression으로 사용할 수 있기 때문에 3항 연산자가 없다.
private String getPassOrFail(int score) {
return score >= 50 ? "P" : "F";
}
자바였으면 이런식으로 삼항연산자를 사용했겠지만
코틀린에서는
fun getPassOrFail(score: Int): String {
return if (score > 50) "P" else "F"
}
이런식으로 if-else문이 Expression임을 이용하여 삼항 연산자를 대체할 수 있다.
[ Expression이므로 중괄호가 필요하지 않다. ]
간단한 TIP
어떠한 값이 특정 범위에 포함되어 있는지, 포함되어 있지 않은지를 자바에서는 다음과 같이 확인했다.
if (0 <= score && score <= 100) {
}
하지만 코틀린에서는 in과 a..b 를 이용하여 다음과 같이 쓸 수 있다.
fun getPassOrFail(score: Int): String {
if (score in 1..100){
return "P"
}
return "F"
}
fun getPassOrFail(score: Int): String {
return if (score in 1..100) "P" else "F"
}
3. switch와 when
코틀린에서는 switch-case 문이 사라지고
그것을 대체할 when문이 생겼다.
기존 자바 switch 문
코틀린으로 변환하게 되면
fun switchTest(score: Int): String {
return when (score / 10) {
9 -> "A"
8 -> "B"
else -> "D"
}
}
when 역시 하나의 Expression이기 때문에 return이 가능하다.
case 대신에 ->를 이용하는 점, default 대신에 else를 이용하는점 등등의 차이가 있다.
다른 예시)
fun switchTest(score: Int): String {
return when (score / 10) {
9 -> {
println("a")
"A" // 마지막 구문이 반환값
}
8 -> "B"
else -> "C"
}
}
중괄호를 이용하여 여러줄 작성가능
결과를 반환할 필요없이 단순히 작업만 처리한다면 Return 없이 Statement처럼 사용가능
fun switchTest(score: Int) {
when (score / 10) {
9 -> {
println("a")
}
8 -> {
println("b")
}
else -> {
println("c")
}
}
}
또는 in을 이용하여 다음과 같이 작성가능하다.
fun switchTest(score: Int): String {
return when (score) {
in 90..99 -> "A"
in 80..89 -> "B"
in 70..79 -> "C"
else -> "D"
}
}
조건부에 is를 사용한 예시)
fun switchTest(obj: Any): Boolean {
return when (obj) {
is String -> obj.startsWith("A")
else -> false
}
}
한번에 여러 조건을 확인하는 예시)
fun switchTest(number: Int): Boolean {
return when (number) {
1,0,-1 -> true
else -> false
}
}
when에 값을 주지않고 사용가능하다.
예시)
fun earlyReturn(number: Int) {
val message = when {
number == 0 -> "0"
number % 2 == 0 -> "짝수"
else -> "홀수"
}
println(message) // 조건에 맞는 메시지를 출력
println("?") // 언제나 실행
}
when은 Enum Class 혹은 Sealed Class와 함께 사용할 경우, 더욱더 진가를 발휘한다
Enum 예시)
enum class Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
fun getDayType(day: Day): String {
return when (day) {
Day.SATURDAY, Day.SUNDAY -> "Weekend" // 주말
else -> "Weekday" // 평일
}
}
sealed 예시)
sealed class Result {
object Success : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
fun handleResult(result: Result): String {
return when (result) {
is Result.Success -> "Operation was successful"
is Result.Error -> "Error occurred: ${result.message}"
is Result.Loading -> "Loading..."
}
}
Sealed Class는 상속 가능한 클래스의 종류를 제한합니다.
이를 활용하면 when으로 모든 가능성을 컴파일 타임에 확인할 수 있습니다
- Result는 Success, Error, Loading 세 가지 타입만 가질 수 있습니다.
- when에서 모든 경우를 처리하지 않으면 컴파일 오류가 발생합니다. 이를 통해 누락된 처리를 방지할 수 있습니다.
4. for-each문
자바코드 예시)
List<Long> numbers = List.of(1L, 2L, 3L);
for(Long number : numbers){
System.out.println("number = " + number);
}
코틀린 예시)
val numbers = listOf(1L,2L,3L)
for (number in numbers) {
println(number)
}
: 대신 in을 사용하는 차이가 있다.
5. 전통적인 for문 , Progression과 Range
자바 코드예시)
for (int i = 1; i <= 5; i += 2) {
System.out.println(i);
}
코틀린 예시)
for (i in 1..5 step 2) {
println("i = $i")
}
println("------------")
for (i in 5 downTo 1 step 2) {
println("i = $i")
}
.. 연산자는 범위(Range)를 만들어 내는 연산자이다.
Range는 Progression(등차수열,진행의 의미)를 상속받는다.
..를 타고 들어가보면
public operator fun rangeTo(other: Int): IntRange
다음과 같이 IntRange를 반환하고, IntRange 클래스를 들어가보면
public class IntRange(start: Int, endInclusive: Int) : IntProgression(start, endInclusive, 1), ClosedRange<Int> {
IntProgression을 상속받고 있고 기본값으로 step값인 1을 받고있다.
그래서 1..5 이라는 의미는
1에서 5까지 1씩 상승하는 등차수열을 만들어달라는 의미가 된다.
3 downTo 1은 시작값 3, 끝값 1, 공차가 -1인 등차수열이 만들어지고
1..5 step 2는 시작값 1, 끝값 5, 공차가 2인 등차수열이 만들어진다.
downTo, step 둘다 함수이다. ( 중위 함수 )
중위 함수(Infix Function)란
중위 함수는 일반적인 함수 호출 방식인 점(.)과 괄호를 사용하지 않고, 연산자처럼 사용하는 함수를 의미한다.
즉, 피연산자 사이에 함수 이름을 위치시켜 호출할 수 있는 함수이다.
public infix fun Int.downTo(to: Int): IntProgression {
3 downTo 1 이라는 코드에서는
3이 downTo라는 함수를 호출했고 그 매개변수가 1인것이다.
6. while 문
while, do-while문은 자바와 동일하다
var i = 0
while (i<10) {
println(i)
i++
}
7. try catch finally 구문
코틀린 코드 예시)
fun parseIntOrThrow(str: String): Int {
try {
return str.toInt()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("${str}는 숫자가아님")
}
finally {
println("abc")
}
}
그리고 try catch 문 또한 Expression 처럼 사용 가능하다.
fun parseIntOrThrow(str: String): Int {
return try {
str.toInt()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("${str}는 숫자가아님")
}
finally {
println("abc")
}
}
8. Checked Exception과 Unchecked Exception
자바 코드 예시)
public void readFile() throws IOException {
File currentFile = new File(".");
File file = new File(currentFile.getAbsoluteFile() + "/a.txt");
BufferedReader reader = new BufferedReader(new FileReader(file));
System.out.println(reader.readLine());
reader.close();
}
코틀린으로 변환시
fun readFile() {
val currentFile = File(".")
val file = File(currentFile.absolutePath + "/a.txt")
val reader = BufferedReader(FileReader(file))
println(reader.readLine())
reader.close()
}
두 코드의 차이점은 throws IOException 부분이다.
자바 코드에서는 BufferedReader 부분에서 IOException (체크예외)가 발생하므로 throws를 붙여줘야했다.
하지만 코틀린에서는 throws 구문을 붙여주지않아도 컴파일 에러가 발생하지않았다.
코틀린은 Checked Exception Unchecked Exception을 구분하지않고 전부 Unchecked Exception으로 간주한다.
8. try with resources
자바코드 예시)
public void readFile(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
System.out.println(reader.readLine());
}
}
try () 구문안에 외부 리소스를 자동으로 닫아주는 기능이 있다.
코틀린 예시)
코틀린에는 try with resources 구문이 없다.
fun readFile(path: String) {
BufferedReader(FileReader(path)).use { bufferedReader ->
println(bufferedReader.readLine())
}
}
대신 이런식으로 사용한다.
use라는 함수안에 사용이 끝나면 close()해주는 코드가 들어가있다.
[use는 inline 확장함수이다. 나중에 배운다. ]
9. 함수 선언 문법
fun을 이용하여 함수를 선언한다. 반환타입은 Unit(void)일때 생략가능하다
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
여기서 중괄호와 return을 없앨 수 있다.
중괄호을 이용해서 이 함수는 어떤 return을 준다라는것을 의미했다면
중괄호,return을 없애고 =를 사용하여
fun max(a: Int, b: Int): Int =
if (a > b) a else b
max란 함수는
if (a > b) a else b
의 결과다 라는 의미이다.
접근제어자는 public은 생략가능하고 fun 앞에 붙이면 된다.
private fun max(a: Int, b: Int): Int =
if (a > b) a else b
또한 =를 사용하게되면 코틀린은 반환타입을 추론할 수 있기 때문에 반환타입을 생략할 수 있다.
private fun max(a: Int, b: Int) =
if (a > b) a else b
- 중괄호을 사용하는 경우: 중괄호를 사용하면 return을 사용해서 반환 타입을 명시해야 하며, 그렇지 않으면 컴파일러가 오류를 발생시킵니다.
- =를 사용하는 경우: 단일 표현식으로 함수 본문을 작성하면 Kotlin이 자동으로 반환 타입을 추론합니다. 이때, 반환 타입을 명시하지 않아도 됩니다.
함수는 클래스 안에 있을 수도, 파일 최상단에 있을 수도 있습니다.
또한, 한 파일 안에 여러 함수들이 있을 수도 있습니다.
이런 클래스파일이 있다고해보자.
KoPrac.kt
package lannstark.lec01;
class KoPrac {
val name = "ABC"
}
fun printPrac() {
println("print")
}
printPrac() 메소드를 외부에서 사용하고 싶다면,
KoPrac 객체를 만들어서 사용하고 싶다면
Lec01Main.kt
import lannstark.lec01.KoPrac
import lannstark.lec01.printPrac
fun main() {
val koprac = KoPrac()
val name = koprac.name
printPrac()
}
printPrac()를 쓰고싶다면 해당 파일의 상위 패키지를 import하면 되고
Koprac 객체를 생성하고 싶으면 해당 파일을 import하면 된다.
이유
Kotlin에서는 클래스와 최상위 함수는 독립적인 개념으로 동작합니다.
즉, KoPrac 클래스와 printPrac() 함수가 같은 파일에 정의되어 있더라도,
서로 독립적으로 존재하며 다른 파일에서 각각 명시적으로 import해야 합니다.
이유 1: 파일에 정의된 클래스와 최상위 함수는 별개로 동작
- KoPrac은 클래스이고, printPrac()은 최상위 함수입니다.
- Kotlin에서 최상위 함수는 해당 파일의 클래스나 다른 요소와 종속적 관계가 없습니다. 따라서 KoPrac을 import했다고 해서 자동으로 printPrac()도 가져오는 것은 아닙니다.
이유 2: Kotlin의 명시적 import 철학
- Kotlin은 명시적으로 어떤 클래스나 함수를 사용하는지 드러내는 것을 중요시합니다.
- 이를 통해 불필요한 import나 의도치 않은 충돌을 방지합니다.
- 따라서, import lannstark.lec01.KoPrac을 했더라도 동일 파일의 다른 최상위 함수(printPrac)는 별도로 import해야 합니다.
최상위 함수란?
최상위 함수는 Kotlin에서 클래스나 객체 내부가 아닌, **파일의 최상위 레벨(바깥)**에 직접 정의된 함수를 말합니다.
Java와는 다르게 Kotlin에서는 함수가 반드시 클래스 내부에 정의될 필요가 없으며, 독립적으로 파일에 작성될 수 있습니다.
최상위 함수의 특징
- 클래스와 독립적
- 최상위 함수는 특정 클래스나 객체와 연결되지 않습니다.
- 클래스 내부에 멤버 함수로 정의하지 않아도 됩니다.
- 정적 함수처럼 동작
- 컴파일 시 JVM에서는 최상위 함수가 정적(static) 함수처럼 처리됩니다.
- 즉, 최상위 함수는 객체를 생성하지 않고도 호출할 수 있습니다.
- 가시성 제어
- 기본적으로 public 가시성을 가지며, 패키지를 통해 다른 파일에서 접근 가능합니다.
- private으로 선언하면 해당 파일 내에서만 사용할 수 있습니다.
- 간단한 유틸리티 함수 작성에 적합
- 최상위 함수는 파일 스코프에서 독립적으로 작동하기 때문에, 유틸리티 함수 작성에 유용합니다.
10. Default Parameter , Named Argument
함수의 매개변수에 기본값을 넣고싶을때
자바에서는 오버로딩을 이용하였다.
기존의 repeat코드를 오버로딩하여
useNewLine을 받지않는경우, num까지 받지않는경우의 대한 정의를 내려줘서 기본값 처리를 하였다.
코틀린에서는
fun main() {
repeat("Hello World")
}
fun repeat(
str: String,
num: Int =3,
useNewLine: Boolean = true
) {
for (i in 1..num) {
if (useNewLine) {
println(str)
} else {
print(str)
}
}
}
위와 같이 기본값을 설정할 수 있다.
repeat함수를 사용할떄 num, useNewLine에 값을 안넣어주면 기본값이 들어가진다.
[ 물론 코틀린에도 Java와 동일하게 오버로드 기능은 있다. ]
fun main() {
repeat("Hello World",3,true)
}
기본값으로 설정되어있는값 또한 다시 넣을 수 있다.
만약에 num은 3을 쓸거고 useNewLine을 false로 하고싶을때는
fun main() {
repeat("Hello World", useNewLine = false)
}
이런식으로 매개변수 이름을 이용하여 지정할 수 있다.
Named Argument로 인해 builder를 직접 만들지 않고 builder의 장점을 가지게 된다
즉 자바에서 builder를 이용하면
Student.Builder()
.name("gil")
.age(11)
.build();
이런식으로 파라미터를 넣을때 헷갈리지않게 된다.
그저 매개변수 순서대로 값을 넣어주게 되면 실수가 발생할 수 있다.
Named Argument를 사용하면 빌더처럼 매개변수 이름을 확인해가며 값을 넣어 줄 수 있다.
Kotlin에서 Java 함수를 가져다 사용할 때는 named argument를 사용할 수 없다.
자바 함수를 가져다 쓸때는 Named Argument 기능 사용불가하다.
[ 자바코드가 byte 코드로 변환될떄 파라미터 이름이 보존되지않아서 사용불가능하다.]
11. 같은 타입의 여러 파라미터 받기 (가변인자)
문자열을 N개 받아 출력하는 자바 예제)
public static void printAll(String... strings) {
for (String str : strings) {
System.out.println(str);
}
}
...은 Java의 가변 인자(varargs)를 의미
가변 인자란?
- 가변 인자는 개수에 상관없이 메서드 호출 시 여러 개의 값을 전달할 수 있도록 합니다.
- 메서드의 파라미터로 여러 값을 전달하거나, 전달하지 않을 수도 있습니다.
- 가변 인자는 배열로 처리되며, 내부적으로는 전달된 값들이 배열로 변환됩니다.
호출하는쪽에서는 다음과 같이 사용했다.
코틀린에서는 다음과 같이 작성된다.
fun main() {
printAll("A", "B", "C")
val array = arrayOf("A", "B", "C")
printAll(*array) // 배열을 가변인자에 넣어줄때는 *를 붙여준다.
// *는 스프레드 연산자로 배열을 풀어주는 기능
}
// ...을 타입 뒤에 쓰는 대신 제일 앞에 vararg를 적어주어야 한다!
fun printAll(vararg strings: String) {
for (str in strings) {
println(str)
}
}
vararg ( 발알지 => var argument)
vararg 키워드
- Kotlin에서는 Java의 ...(가변 인자) 대신 vararg 키워드를 사용합니다.
- vararg는 메서드나 함수가 0개 이상의 인자를 받을 수 있도록 합니다.
- 함수 내부에서는 전달받은 인자들을 배열(Array)처럼 처리할 수 있습니다.
스프레드 연산자(*)
- 배열을 가변 인자 함수에 전달할 때 배열의 각 요소를 풀어서 전달해야 합니다.
- 이를 위해 Kotlin은 * 연산자(스프레드 연산자)를 제공합니다.
'인프런 > 자바 개발자를 위한 코틀린 입' 카테고리의 다른 글
5) 코틀린에서 상속을 다루는 방법 (0) | 2025.01.08 |
---|---|
4) 코틀린에서 클래스를 다루는 방법 (0) | 2025.01.07 |
2) 코틀린에서 Type, 연산자를 다루는 방법 (0) | 2025.01.06 |
1) 코틀린에서 변수,null을 다루는 방법 (0) | 2025.01.06 |
댓글