Kotlin에 들어가며
코틀린은 자바와 완전히 호환 가능한 언어이며 자바의 몇가지 단점을 해결하고자 등장했다.
대표적인 자바의 문제는 NPE가 있으며 밑에서 알아 보겠다.
또한 자바는 완전히 oop언어임에 반해 코틀린은 oop + F/L(Functional Languge)의 특성을 함께 가지고 있다.
코틀린은 간결성을 중요시 하며 때문에 가독성이 약간 떨어지는 문제가 발생할 수 있다.
다른 특징으로는 자바는 대입식인데 반해 코틀린은 대입문이다.
x = y = a + 5 //불가능
마지막으로 코틀린은 모든 것을 객체로 취급한다.
타입들은 자동으로 박싱(boxing) 및 언박싱(unboxing)되어 객체로 다뤄진다.
연산자
코틀린은 자바의 대부분은 연산자를 지원
+,<= 같은 연산자를 말한다.
하지만 지원하지 않는 연산자가 있다.
자바의 삼항 연산자이다.
int result = (condition) ? 4 : 9;
위의 연산자를 코틀린에서 구현하기 위해서는
var result = if(a > 3) 3 else 6
위와 같이 구현해야 한다.
변수 선언
코틀린은 자바와 다르게 변수 선언 시 var, val을 사용한다.
//var로 선언한 변수는 바꿀 수 있지만 val로 선언한 변수는 불가능 하다.
var a : Int = 3
val b : Int = 3
a = 4
b = 4 //불가능
//타입 추론
var a = 4 // a의 값이 4임을 보고 컴파일러가 타입을 추론해준다.
Null safety
자바에서의 문제점인 NPE의 코틀린의 해결방법
보통 null 값이라고는 하지만 null이 값은 아님
null은 undefind로 아직 정의가 되지 않은 상태
//이 코드는 NPE에러 상태
Book b;
b.changePrice(2000);
개발자가 문제로 인식하지 않으면 에러가 생길 수 있다.
//Int | Int?
var b = null(잚못 된 사용)
var b : Int?(타입을 명시해 주어야 함!!)
null safety 연산자
?.!!.?:가 있다.
b!!.plus(3) // null이 아님을 명시
b?.plus(3) // null이 아니면 사용하고 null이면 null로 나옴 때문에 받는 타입도 null 타입을 받을 수 있어야 함
var a : Int? = b?.plus(3) //이런식으로 만들어야 함
b?.plus(3) ?: -1 // b가 널이 아니면 실행 널이면 -1을 반환
Array(배열)
배열은 기본적으로 선형구조임
랜덤 접근가능
val x = arrayOf(1,2,3,4,5) // 배열의 길이와 초기값을 정의
val y = emptyArrayOf()
val z = Array<Int>(3){0}
val w : Array<Int?> = ArrayOfNulls(3) // null값이 들어감 이럴 경우 타입을 명시해 주어야 함
Collection
배열과 달리 크기가 자유로움
List,Set,Map
List,Set은 색인번호로 접근하지만 Map은 key로 접근
List와 Set의 가장 큰 차이점은 List는 중복을 허용하지만 Set은 중복을 허용하지 않는다!
val a = listOf(1,2,3)
a[0] = 2 // 불가능
val a = mutableListOf(1,2,3) //이렇게 만들어야 변경 가능
간단한 예제
data class MusicSong(var title : String, var artist : String)
---
class PlayList(var title : String) {
fun add(musicSong : MusicSong) {
songs.add(musicSong)
}
fun show() {
for(song in songs) pringln(song)
val songIterator = songs.iterator() //iterator 사용 방식
while(songIterator.hasNext())
println(songIterator.next())
}
fun remove(name : String)) {
for(song in songs) {
if(song.title.equals(name)) {
songs.remove(song)
return
}
}
}
val songs = mutableListOf<MusicSong>() // 비어있는 변경가능 리스트 생성
}
fun main() {
val plist = PlayList("여름 노래집")
plist.add(MusicSong("여행", "사춘기")
plist.add(MusicSOng("바다안에서","사춘기")
plist.show()
plist.remove("여행")
plist.show()
}
Function
자바는 클래스 안에 모든 Method가 있어야 하지만 코틀린은 클래스가 없어도 필요에 따라 Function을 만들 수 있음
//함수의 기본 구조
fun 함수명(매개변수) {
...
}
fun power(a : Int, b : String) : Int { //매개변수는 무조건 val로 선언된다. 때문에 바꿀 수 없다.
result = a
result = if(b.eqals(se) 2 else 3
return result // 함수에 선언한 타입을 반환할 수 있다. 여기서는 Int로 선언됨
}
println(power(3,a))
코틀린은 함수 안에 또다른 함수를 정의하는 것이 가능하다
Class
class 클래스명 {
내용
...
}
내용에는 멤버변수(속성)와 멤버함수가(메소드)가 존재한다.
class Book constructor(title:String = "", price :Int = 0)(주 생성자) constructor *생략 가능*{
var title = price(주 생성자일 경우에 이렇게 사용)
var title = ""
get() = field // 자바의 get을 직접 구현
set(value) { // 자바의 set을 직접 구현
field = value //식을 통해 임의로 조정도 가능
}
var price = 0
val rating = 0
init { //주 생성자의 초기화를 좀 더 구체적으로(복잡하게) 해야 될 때 사용
if(price in 1 .. 100) h++
}
//생성자(부 생성자 Secondary Constructor)
constructor(title:String = "", price:Int = 0) : this(title,price) { //디폴드 값을 주면 기본 생성자를 굳이 만들지 않아도 됨
this.title = title
this.price = price
}
}
fun main() {
val book = Book() //자바의 Book book = new Book()과 같은
book.title = "Kotlin" //자바의 book.setTitle("Kotlin")와 같음
book.price = 1000
val x = book.price //자바의 book.getPrice()와 같음
book.rating = 199 // val이기 때문에 set이 존재하지 않아 값을 바꿀 수 없음
val book2 = Book("Java", 2000)
}
코틀린에서는 자바의 setter와 getter, 기본 생성자를 내부적으로 자체적으로 만들어주기 때문에 쉽게 사용할 수 있다.
코틀린은 주 생성자와 부 생성자가 있다.
주 생성자가 있고 부 생성자가 있을 경우 부 생성자는 반드시 주 생성자를 먼저 실행한다.
//constructor는 생략이 가능하기 때문에 다음과 같이 만들어도 주 생성자가 존재하는 것이다.
class Rect(var w : Int, var h : Int, color : String) {
}
//data class 는 멤버변수만 정의 가능하다.
//자동으로 toString 생성
data class Rect(var w : Int, var h : Int) {
}
data class Rect(var w : Int, var h : Int) {
fun size() = w*h
}
fun main() {
val r = Rect() //자바의 Book book = new Book()과 같은
print(r.size())
//결과 12가 나오는 걸 확인 가능하다.
}
class Rect(var w : Int, var h : Int) {
class r(var x : Int) : Int { //내포 클래스
return x * x * 3.14 //외부 클래스 변수 사용 불가
}
inner class r(var x : Int) : Int { //내부 클래스(inner 사용)
return x * x * 3.14 * w * h // 외부 클래스의 변수 사용가능
}
}
캡슐화 = 정보 은닉
public, private, protected
코틀린은 기본적으로 public이다.
//private로 선언하면 자동으로 set과 get이 만들어지지 않는다.
//때문에 set을 직접 만들어줘야 값을 바꿀수 있다.
class Rect(private var h : Int) {
fun setH(h : Int) {
this.h = h
}
var title = "" //객체 외부에서 color 변수를 변경하지 못하게 막음
private set(value)
}
open class Rect(var w : Int, var h : Int) { //자바와 다르게 open이라는 키워드를 적어조야 상속 가능
open fun size() = w*h //메소드도 마찬가지로 open을 사용해야만 가능
}
}
final class Rect(var w : Int, var h : Int) { //final이 붙으면 상속 불가능
}
//Rectt(w,h)와 같이 부모를 생성할 필요가 있음
class Ro(w:Int, h : Int, var fill :Int) : Rect(w,h) { `:` 는 자바의 extends와 같은 역할
override fun size() = w*h
}
//부모 타입의 객체참조변수가 자식 객체를 가리키는 것(참조하는 것) Up Casting
val woid : Rect = Ro(2,4,5)
}
object OneTime {
#### var x = 2
var y = 3
fun clear() x + y
}
fun main() {
val obj = OneTime
println(obj.clear())
val book = object { //바로 구현해도 됨
var title = "Kotlin"
var price = 1000
fun increase(a : Int) {
price += a
}
}
}
많이 사용되는 디자인 패턴을 하나 소개하겠다.
객체가 하나만 생성되어 사용하는 방법
class TicketManager private constructor() {
companion objcet {
private var manager : TicketManager? = null
fun getInstance() : TicketManager {
return manager ?: TicketManager().also { manager = it }
}
}
fun makeTicket() {
println("Ticket")
}
}
상속을 위한 클래스
자식 클래스에서 오버라이드를 통해 재 정의 해야만 정상적으로 동작
다형성과도 관련있음
abstract class shape(var length : Int) {
abstract var color : String //추상변수는 초기화 지정 않음
abstract fun size() : Double //추상 메소드
}
class Circle(a : Int) : shape(a) { //구현 클래스
override var color : String = "" //초기화 구현
get() = feild
set(value) { feild = value}
override fun size() {
length * length * 3.14
}
}
class Triangle(length : Int, var length2 : Int) : shape(length) { //구현 클래스
override var color : String = "Red" //직접 초기화
override fun size() {
length * length * 3.14
}
}
fun main() {
val shape : shape = Triangle(8,2)
when(shape) {
is Circle -> println("Circle size" + shape.size())
is Triangle -> println("Triangle size" + shape.size())
}
}
만약 자식 클래스의 변화를 컴파일이 확인하게 하고 싶으면 다음과 같이 바꾸면 된다.
abstract class shape
sealeㅇ class shape
interface Controller {
//추상변수, 추상함수, 구현함수를 담을 수 있음
val capacity : Int //초기화 하면 오류 생김 앞에 abstract가 생략임
fun on()
fun off()
fun up()
fun down()
fun pause() { //직점 구현도 가능, 오버라이드도 가능
println("divice is pause.")
}
}
class Deivce : Controller {
override val capacity : Int = 5
override fun on() {
}
}
fun main() {
//인터페이스 변수로 구현 객체를 참조
val device : Controller = Device() // Up casting
}
람다식
First-class,High-order function
First-class
함수를 값처럼 사용한다는 개념
High-order function
//변수에 함수 저장 가능
val add = fun(a : Int, b : Int) : Int {
return a * b
}
//람다식
val add2 = {a: Int, b: Int -> a+b}
val numbers = arrayOf(1,2,3,4)
//람다식은 it으로 가르킴
numbers.forEach{ it -> println(it) } //원소 값을 하나씩 출력
var value = number.all { it > 0 }
println(value) //결과 ture나옴 모든 원소의 조건을 확인
var value = number.any { it > 0 } //하나라도 만족하는게 있는지 확인
var value = number.count { it > 0 } //조건에 만족하는 개수 확인
var value = number.find { it > 0 } ?: 0 //조건에 맞는 첫 값
var value = number.filter { it > 0 } //조건에 맞는 값만 리스트로 만들어줌
val newNumber = number.map { it * it } //각 원소에 결과를 적용해서 리스트로 만들어줌
//High-order function
cun compute(a : Int, f : (Int) -> Int) {
println(f(a))
}
val s1 = { x : Int -> x * x}
compute(5,s1)
compute(4, { x : Int -> x * x}) //한번만 쓴다면 바로 넣어도 됨