코틀린 기본 정리

구교석·2024년 7월 23일
post-thumbnail

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을 사용한다.

  • val : Value의 약자로, 값이 불변(immutable)을 나타냄
  • var : Variable의 약자로, 값이 가변(mutable)을 나타냄
//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);

개발자가 문제로 인식하지 않으면 에러가 생길 수 있다.

코틀린에서의 null 사용법

//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을 반환

코틀린의 null safety 방법 정리

  1. null가능 연산자와 불가 연산자로 나누어짐
  2. null 연산자가 있음

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가 나오는 걸 확인 가능하다.
}

내포 클래스 vs 내부 클래스

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 = 공개 안함
  • 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
함수를 값처럼 사용한다는 개념

  • function = value

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})  //한번만 쓴다면 바로 넣어도 됨
profile
끊임없이 노력하는 개발자

0개의 댓글