코틀린은 이미 정의된 클래스를 확장 시킬 수 있는 기능이 있다. 예를 들어서 아래와 같이 String 클래스에 원하는 메서드를 추가 할 수 있다.
fun String.getFirst() = this[0]
println("hello".getFirst())
//print 'h'
원리
원리는 생각보다 간단하다. 자바에서 static을 사용하여 기존 클래스를 입력받아 처리하는 메서드를 생성함으로써 사용자 입장에서는 기존의 클래스를 확장시킨 것처럼 보이게 하는 것이다.
public static String getFisrt(String string){ return string[0];}
Question?
그렇다면 만약 상속 관계에 있는 두 개의 클래스를 이용하여 아래와 같이 코드를 작성하면 어떤 문자열이 출력될까?
open class Parent
class Child: Parent()
fun Parent.foo() = "parent"
fun Child.foo() = "child"
fun main(args: Array<String>) {
val parent: Parent = Child()
println(parent.foo())
// 여기서 출력되는건 무엇일까?
}
Answer & Why
원리를 알고있다면 추론하는 것은 간단하다. 위 코드가 자바로 변환 되었다고 생각해보자. static을 이용하므로 아래와 같이 변환 되었을 것이다.
//... 클래스 정의는 생략
public static String foo(Parent parent){return "parent";)
public static String foo(Child child){return "child";)
public static void main(String[] args){
Parent parent=new Child();
System.out.println(foo(parent));
}
자바 공부를 열심히 했다면 결과는 "parent"가 출력되었을 것이라고 바로 알았을 것이다. 자바는 오버로딩을 통해서 어규먼트와 파라미터가 일치하는 함수를 호출하기 때문이다. 위 코드를 보면 오버라이딩을 통해서 "child"가 출력될수 있다고 착각할 수있다. 그러나 확장함수는 인스턴스의 실제 값과는 관련없이 클래스에만 선언되기 때문에 함수를 호출할 때 인스턴스의 클래스 종류만을 따른다.
Question2 : Member vs Extension
자 그럼 만약 확장함수를 기존 클래스의 멤버 메소드를 오버라이딩 하는 것처럼 사용하면 어떻게 될까?
class A{
fun foo()="member"
}
fun A.foo()="extension"
println(A().foo()) //??
Answer & Why
정답은 "member"를 출력한다. 왜냐하면 코틀린은 확장함수가 멤버 함수를 오버라이딩 하는 것을 허용하지 않는다. 왜 그럴까? 만약 반대로 허용한다고 생각해보자, 코드를 작성 할때 전역에서 확장함수로 Array.get()을 새로 정의 했다고 하자. 추후 다른 사람이 이 파일에 새로운 코드를 추가한다고 했을 때, get()을 본래의 의도로 사용하여 작성한다면 코드는 버그 투성이가 될 것이고 이를 인지하고 본래의 함수를 사용하고자 해도 확장함수로 정의된 이상 불가능하다.
하지만 코틀린은 확장함수로 오버로딩을 하는 것은 허용한다. 이는 기존의 멤버 메소드에 영향을 끼치지 않기 때문이다.
class A{
fun foo()="member"
}
fun A.foo(i:Int)="extension $i"
println(A().foo(2)) //extension 2
이번 글에서는 처음으로 코틀린을 공부해보는 만큼 코틀린에 쓰이는 기본타입(numbers, characters, booleans, arrays, and strings)에 대해서 알아보도록 하겠습니다.
1. Numbers
1. 정수
1. 파이썬, javascript처럼 변수에 따로 타입을 선언하지 않음
2. 일반적인 정수를 저장하면 Int로 설정
3. Int의 범위 밖이나 뒤에 L을 추가하면 Long으로 설정
4. 변수이름 : 타입 = ? 구문을 통해서 직접 타입 설정 가능
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1
2. 실수
val pi = 3.14 // Double
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817
1. 일반 실수 입력시 Double로 설정
2. 뒤에 F또는 f 추가시 float으로 설정되고 범위 밖의 데이터는 소멸
3. 독립성
코틀린은 일반적인 언어처럼 숫자 파라미터가 포괄적인게 아니라 해당 파라미터만 수용가능, 그렇지 않을 경우 에러 발생
fun main() {
fun printDouble(d: Double) { print(d) }
val i = 1
val d = 1.1
val f = 1.1f
printDouble(d)
// printDouble(i) // Error: Type mismatch
// printDouble(f) // Error: Type mismatch
}
4.. 리터럴 상수(Literal constants)
10진수, Decimals:123
Longs are tagged by a capitalL:123L
8진수, Hexadecimals:0x0F
2진수, Binaries:0b00001011
NOTE: Octal literals are not supported.
Doubles by default:123.5,123.5e10
Floats are tagged byforF:123.5f
5. 가독성을 위한 언더스코어 사용가능
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
6. 변환
val b: Byte = 1 // OK, literals are checked statically
val i: Int = b // ERROR
타입은 명백히 독립적이므로 바로 대입하는것은 불가(java와 다름)
변환하기 위해선 toInt,toLong등의 메서드 사용필요
val i: Int = b.toInt() // OK: explicitly widened
print(i)
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
*오퍼레이터 오버로딩
val l = 1L + 3 // Long + Int => Long
서로 다른 두 데이터의 수학적 계산 결과의 타입은 자동으로 최적화됨
*나눗셈으로 인한 타입 결정은 java와 같음
7.비교(Floating point numbers comparison)
The operations on floating point numbers discussed in this section are:
Equality checks:a == banda != b
Comparison operators:a < b,a > b,a <= b,a >= b
Range instantiation and range checks:a..b,x in a..b,x !in a..b
2.Characters
Characters are represented by the typeChar. They can not be treated directly as number
fun check(c: Char) {
if (c == 1) { // ERROR: incompatible types
// ...
}
}
특수문자사용 :\t,\b,\n,\r,\',\",\\and\$
유니코드 인코딩 : '\uFF00'
Convert a character to anIntnumber
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // Explicit conversions to numbers
}
3.Booleans
The typeBooleanrepresents booleans, and has two values:trueandfalse.
Built-in operations on booleans include
||– lazy disjunction
&&– lazy conjunction
!- negation
4.Arrays
Arrays in Kotlin are represented by theArrayclass
메서드 : set,get( that turn into[] by operator overloading conventions )
특성 : size
사용법
1. arrayOf(1, 2, 3)creates an array[1, 2, 3]
2. arrayOfNulls()library function can be used to create an array of a given size filled with null elements.
3. Arrayconstructor
it takes array size and the function that can return the initial value of each array element given its index
// Creates an Array<String> with values ["0", "1", "4", "9", "16"]
val asc = Array(5) { i -> (i * i).toString() }
asc.forEach { println(it) }
Primitive type arrays
ByteArray,ShortArray,IntArray등 Kotlin에는 특수한 배열들이 있습니다. 각 배열은 Array에 상속되지 않지만 동일한 메서드를 사용하며 []를 통해 접근이 가능합니다.
// Array of int of size 5 with values [0, 0, 0, 0, 0]
val arr = IntArray(5)
// e.g. initialise the values in the array with a constant
// Array of int of size 5 with values [42, 42, 42, 42, 42]
val arr = IntArray(5) { 42 }
// e.g. initialise the values in the array using a lambda
// Array of int of size 5 with values [0, 1, 2, 3, 4] (values initialised to their index value)
var arr = IntArray(5) { it * 1 }
5.Strings
Strings are represented by the typeString
특성
1.Strings are immutable
2.Elements of a string are characters that can be accessed by the indexing operation:s[i]
3.string can be iterated over with afor-loop:
for (c in str) {
println(c)
}
4. concatenate strings using the+operator,as long as the first element in the expression is a string
val s = "abc" + 1
println(s + "def")
String literals
1.특수 문자사용
val s = "Hello, world!\n"
2. """ 사용
val text = """
for (c in "foo")
print(c)
"""
String templates
$기호를 사용해서 템플릿으로 사용할 수 있음
val i = 10
println("i = $i") // prints "i = 10"
중괄호를 사용해서 임의의 표현까지 템플릿으로 사용 가능
val s = "abc"
println("$s.length is ${s.length}") // prints "abc.length is 3"