상세 컨텐츠

본문 제목

코틀린 프로젝트01

JSP·자바·코틀린

by 김일국 2022. 3. 11. 15:57

본문

2017년 구글이 공식 안드로이드 앱 개발 언어로 코틀린을 추가했다는 발표와 더불어 스프링 프레임워크도 코틀린을 공식적으로 지원하게 된다.

지금까지는 자바로 안드로이드(그래들빌드) 앱과 스프링(메이븐빌드)과 스프링부트(그래들빌드)의 프로젝트를 진행해 왔다.

최근에 상용 신규 프로젝트에 코틀린이 등장하는 것을 종종 보게 된다.

따라서, 개발자로서 코틀린을 알아야 할 필요가 있고, 코틀린으로 쇼핑몰 앱 프로젝트를 진행 하려고 한다.

크게 스프링부트 API 웹서비스와 안드로드이 앱으로 구성되어 있다.(아래)

1. 스프링부트(인텔리 J 개발툴)로 구현한 쇼핑몰 API 서비스 웹 프로그램

2. 안드로이드(안드로이드 스튜디오 개발툴)로 구현현 쇼핑몰 Native앱

기술참조: 코틀린으로 쇼핑몰 앱 만들기(터닝포인트출판사:이현석 저자)

소스참조(스프링부트API): https://github.com/benimario/parayo-api

소스참조(안드로이드앱): https://github.com/benimario/parayo-android

신규작업소스(스프링부트API): https://github.com/kimilguk/shop-api

신규작업소스(안드로이드앱): https://github.com/kimilguk/shop-app

1장. 코틀린 언어 입문

- 변수, 함수, 클래스 등의 사용방법을 학습한다.

- 프로그램 흐름인 로직 제어구문에 대해 학습한다.

- 코틀린기본: 문장끝에 ; 세미콜론이 붙지 않는다. 모든 변수는 null 값을 허용하지 않는다. 메소드명칭 대신 함수를 사용한다. 클래스는 Kclass 라고 하고, 자바의 클래스와는 구분한다.

 

# 함수 생성 구문( 변수 : 타입 의  해석은 타입을 상속한 변수라고 표현해도 어색하지 않기 때문에 필요에 따라 상속 or 반환값 이라고 말한다.)

- 자바에서는 메소드 라고하고 반환타입, 변수타입이 메소드명과 변수명 앞에 오지만, 코틀린은 반대이다.

 fun 함수명(파리미터명: 변수타입,파라미터명: 변수타입): 함수의 반환값의 타입 {

   구현내용

 }

- 위 내용을 더 간략하게 표현도 가능하다.(아래-함수의반환타입과 {}중괄호 생략됨)

 fun sum(a: Int, b: Int) = a + b

 

# 변수 생성 구문

- 자바에서는 static 으로 변경불가능한 상수값을 코틀린에서는 val 키워드를 사용한다.(아래 2구문은 기능이 같다)

즉, 자바에는 static 키워드가 사용되지만, 코틀린에서는 static 키워드 자체가 없다.

 val a: Int = 3

 a = 1 //컴파일 에러(불변 변수에 값을 변경시키는 시도를 했기 때문에)

 val b = 3

- 변경가능한 가변변수는 var 키워드를 사용한다.(아래 2구문은 기능이 같다)

 var c: Int = 3

 c = 1 //가변변수에 값은 얼마든지 변경 가능하다. 위 val 로 선언한 변수와 비교한다.

 var d = 3

 

# 프로그램 로직의 흐름제어

- if~else, when(자바의 switch와 기능동일), for, while 구문에 대해서 자바와 비교한다.

- if~else 구문(자바)

Integer IQ = 180;

String result = "";

if(IQ>= 180) {

    result = "천재";

}else{

   result = "일반";

}

- if~else 구문(코틀린)

val IQ: Int = 180

val result: String

result = if(IQ >= 180) {

   "천재"

}else{

  "일반"

}

- 단축하면 다음처럼 표현도 가능하다, val result = if(IQ >= 180) "천재" else "일반"

 

# when 구문

- 자바의 switch와 기능동일하다.(아래)

val en = "blue"

val ko: String

when(en) {

   "red" -> ko = "빨간색" //화살표는 람다표현식으로 사용할 때 이용한다.

   "green" -> ko = "녹색"

   "blue" -> ko = "파란색"

   else -> ko = "없음"

}

println(ko) //파란색

 

# for 구문 for (item: Int in 배열) { println("변수선언부분만 다르고 자바와 같다.") }

for ( i in 1..3 ) {//1부터3까지 범위를 지정할 수 도 있다.

   println(i)

}

 

# while 구문(자바와 같다.아래)

do {

    x = 10

}

while (x>0) {

   x--

   println(x)

}

 

# 예외처리 (변수타입이 뒤에 배치된 다는 것만 다르고, 자바와 같다.아래)

val a: Int? = try { //변수 a 가 null 이 될수 있다는 것을 표시할 때 ? 물음표를 마지막에 붙인다.

  parseInt(input)

}catch(e: Exception) {

   null

}

finally { //생략가능 }

- 참고, 자바에서는 String, Integer, Boolean 등의 대문자로 시작하는 클래스(참조)형 데이터는 null이 허용 된다. 소문자로 시작하는 원시형인 char,int,boolean 등은 null이 허용된지 않는다.

 

# 객체의 null 예외처리

- 자바에서는 야래처럼 람다구문을 이용해서 null 포함 에러처리가 가능하다.
Users users = UsersRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 회원이 없습니다."));
- 위 구문을 코틀린으로 처리하면 아래와 같이 엘비스 연산자 ?: 를 이용해서 널 에러 처리를 할 수 있다.
val users: Users = UsersRepository.findById(id) ?: throw IllegalArgumentException("해당 회원이 없습니다."));
- 자바에서 자주 사용하는 다음과 같은 코딩은 if(users != null) { println(구현 코딩); } 아래처럼 코틀린으로 변경할 수 있다.
즉, users 객체가 null 값이 아닐 때만 구현할 때는 코틀린에서 users?.let { println("구현코딩") } 처럼 구현 할 수 있다.

usersRepository.findByEmail(email)?.let { //쿼리 결과가 null 인지 체크해서 값이 있다면 {구현}
    throw ShopException("이미 사용중인 이메일 입니다.")
}

 

# 클래스와 프로퍼티 (자바에서는 프로퍼티가 멤버변수이고, 객체의 멤버변수에 Get/Set메소드가 아닌 아래 코딩 처럼 직접 접근이 가능하다.)

class Adder {

   var a: Int = 0

   var b: Int = 0

   val sum: Int

      get() {

         return a + b

      }

}

val adder = Adder() // 객체 생성시 자바처럼 new 키워드가 필요하지 않다.

adder.a = 1

adder.b = 1

println(adder.sum) // sum의 get 함수를 호출해서 더하기 값 2를 출력한다.

 

# 클래스 참조 ( 자바에서는 클래스명.class 로 참조한다. )

코틀린에서 더블콜론(::)은 리플렉션(참조)을 위해 사용합니다. 

리플렉션이란 코드를 작성하는 시점에는 런타임상 컴파일된 바이트코드에서 내가 작성한 코드가 어디에 위치하는지 알 수 없기때문에 바이트코드를 이용해 내가 참조하려는 값을 찾기위해 사용합니다. 

참고로, 리플렉션이라는 단어가 반사,투영이라는 의미가 있기때문에 바이트코드를 통해서 투영된 코드를 참조한다는 의미이다.(사용법 아래)

//코틀린에서 더블콜론(::)은 리플렉션(참조)을 위해 사용합니다.  
//끝에 .java 를 사용한 이유는 JVM에서 실행시 Kclass 에서 자바클래스로 바꾸어 주기 위해서 이다.
private val loader = LoggerFactory.getLogger(this::class.java)
class ShopExceptionHandler {
	//중략...
	@ExceptionHandler(Exception::class) //일반적인 오류인 Exception 클래스를 참조한다.
	fun handleException(e: Exception): ApiResponse {
    	loader.error("API 에러", e)
    	return ApiResponse.error("알수 없는 오류가 발생했습니다.")
	}
}

 

# companion object (static 클래스 대신에 사용)

class StaticMember {

   companion object {

      val hello = "static 대신 클래스에 바로접근이 가능하다"

   }

}

println(StaticMember.hello) //클래스에 바로 접근이 가능하다.

 

# 싱글톤 객체사용 (자바에서는 싱글톤 객체를 만들 때 private 생성자 메소드와 static 변수로 객체를 사용할 때 new 키워드로 객체를 사용하지 못하고, 앱 실행시 생성된 객체를 앱 종료시 까지 1개만 사용가능하게 만들어서 메모리 낭비를 허용하지 않게 하는데, 코틀린에서는 object 로 선언한 클래스로 선언해서 앱 실행과 동시에 1개의 객체가 생성되게 해서 자바처럼 개발자가 별도의 객체를 만들지 못하도록 한다.)

object Singleton { println("싱글톤으로 구현할 코딩") }

참고, 자바의 싱글톤 객체 구조(아래)

public class Singleton {
    private static Singleton instance = new Singleton();//앱 실행시 객체가 1개 자동으로 생성된다. 앱 종료시까지 메모리에 유지된다. 그래서, getInstance() 로 사용만 할 수 있다.
    private Singleton() {
        // 생성자는 외부에서 호출(생성)못하게 private 으로 지정해야 한다.
    }
    public static Singleton getInstance() {
        return instance;
    }
    public void say() {
        System.out.println("싱글톤으로 구현할 코딩");
    }
}

 

# 함수타입 파라미터 매개변수에 함수와 람다구문으로 코딩 구현 (고차함수)

class ParamFunc {//고차함수 Higher Order Function 에서 아래 () -> 반환타입(값 변수 생략) 가 함수타입을 말한다.
    fun higherOrderFunction(stringParam: String, funcParam: () -> String) {
        println(stringParam + funcParam())
    }
}
//메인 실행함수(아래)
fun main() {
    val paramFunc = ParamFunc()//new 키워드없이 생성자함수만으로 객체생성
    paramFunc.higherOrderFunction("안녕 ") {
        "코틀린"
    }
}

//안녕 코틀린 이 출력된다.

 

# 클래스 상속 (자바는 부모클래스를 상속하여 오버라이드 메소드를 재정의해서 기능을 구현한다.  코틀린은 이 과정을 확장 함수라고 하고,  .() 처럼 .함수로 표시한다.)

코틀린에서 class 자식클래스명 : 부모클래스( ) 처럼 상속관계를 표시한다.(아래)

//자바클래스의 상속 코딩 예
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
//코트린클래스의 상속 코딩 예
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

- 아래 DSL 언어에서 확장 함수 사용방법을 확인 할 수 있다.

 

# 코틀린(GPL)언어를 DSL (Domain Specific Language 도메인 특화언어)로 만들 수 있다.

class Address {

   var first: String? = null

   var detail: String? = null

   var postalCode: String? = null

}

class Person {

   var name: String? = null

   var age: Int? = null

   var address: Address? = null //Address 는 클래스 타입을 말한다.

}

//Person클래스를 매개변수로 받는 person 초기화 함수(아래)

fun person(init: (Person) -> Unit): Person { //init 는 Person클래스를 매개변수로 받는 함수형 변수고 그냥 init 함수로 부른다.

   val person = Person()

   init(persion)

   return person

}

//클래스 값 등록(아래)

val pserson: Person = person {

   it.name = "김일국"

   it.age = "52"

}

//Person.()->확장 함수를 사용하면 아래 it 영역을 생략할 수 있다(아래)

fun person(init: Person.() -> Unit): Person {//init 는 함수이고, 매개변수로 받는 리시버타입을 Person클래스로 확장함수로 사용한다.

   val person = Person() //객체로 만들기

   init(persion)

   return person

}

//Address클래스를 매개변수로 받는 address 초기화 함수(아래)

fun Person.address(init: Address.() -> Unit) {

   val address = Address() //객체로 만들기

   init(address)

   this.address = address

}

//클래스 값 등록(아래)

val pserson: Person = person {

   name = "김일국"

   age = "52"

   address {

       first = "충남 천안시 OOO"

       detail = "OO 아파트 OO동 OO호"

       postalCode = "00000"

   }

}

 

# 문자열 처리

자바에서 합치기(아래)

val a = "코틀린"

println("안녕 " + a)

코틀린 에서 문자열 합치기(아래)

val a = "코틀린"

println("안녕 $a") //큰따옴표 안에 $변수명으로 변수값을 불러올 수 있다.

 

# 스레드 비동기 처리에 사용되는 코루틴(Coroutine) 모듈 사용

bluild.gradle 에서 implementation "org.jetbrains.kotlinx:kotlinex-coroutines-core:$coroutine_version"

import kotlinx.coroutines.*

suspend fun someNetworkCall(): String { //함수앞에 suspend 키워드를 추가해서 비동기 통신용 서스펜딩(백그라운드 대기) 함수임을 명시한다. 즉, 멀티스레드로 여러개의 함수가 실행이 가능하다.

   delay(1000)

   return "네트워크 전송 데이터 1초 대기후 완료"

}

suspend fun uiFunction() {

   println("UI 화면에서 실행되는 스레드명은 ${Thread.currentThread().name}")

   val data = someNetworkCall() //네트워크에서 1초 대기후 완료 데이터 저장

   println(data)

   println("UI 화면 실행 완료")

}

fun main() {

   val scope = CoroutineScope(Dispatchers.Default)

   scope.launch { //launch 로 새로운 비동기 작업을 시작한다.

      println("1번째 코루틴이 실행되는 스레드명은 ${Thread.currentThread().name}")

      uiFunction()

   }

   Thread.sleep(500)//메인 스레드를 0.5초 쉬었다가 다음 실행 여기서 쉬지 않으면 아래 2번 부터 출력된다.

   scope.launch {
      println("2번째 코루틴이 실행되는 스레드명 ${Thread.currentThread().name}")

   }

   println("메인 함수 종료. 현재 실행되는 main스레드명 ${Thread.currentThread().name}")

   Thread.sleep(2000)//메인 스레드를 2초 대기 후 다음 실행

}

출력결과 ( JVM 실행 옵션에 -Dkotlinx.coroutines.debug 룰 추가한 후 실행 한다)

1번째 코루틴이 실행되는 스레드명은 ~ @coroutine#1

UI 화면에서 실행되는 스레드명은 ~ @coroutine#1

메인 함수 종료. 현재 실행되는 main스레드명은 ~ main

2번째 코루틴이 실행되는 스레드명은 ~ @coroutine#2

네트워크 전송 데이터 1초 대기후 완료

UI 화면 실행 완료

 

# 학습후기

- 코틀린 언어의 사용방식이 개념은 자바와 비슷하지만, 코딩은 단순하고 직관적으로 사용할 수 있을 듯 합니다.

- 개인적으론 자바만 사용했으면 하지만, 현실적으로 상용 프로젝트에 코틀린이 사용되기 때문에 학습을 계속 이어가기로 한다. 자바와 비교하면서 학습하니 많이 도움이 되는 듯 합니다.^^

앞으로 2장은 웹 API 프로젝트(인텔리J)와 안드로드이 앱 프로젝트(안드로이드스튜디오) 으로 시작하게 됩니다.

관련글 더보기

댓글 영역