재귀 함수란 무엇일까?

재귀 함수란 자기 자신을 호출하는 함수이다. 즉, 문제를 해결하기 위해 동일한 문제의 더 작은 버전을 반복해서 호출하는 방식이다.

 

재귀 함수의 구성 요소

  • 기본 조건(Base Case) : 재귀 호출을 멈추는 조건이다. 기본 조건이 없으면 함후가 무한히 자기 자신을 호출하게되는 무한루프에 빠지게 된다.
  • 재귀 호출(Recursive Case) : 문제를 더 작은 문제로 나누어 자기 자신을 호출하는 부분이다.

 

 

재귀 함수 예제

1. 팩토리얼 계산(n!)

fun factorial(n: Int): Int {
    // 기본 조건: n이 0이면 1 반환
    if (n == 0) {
        return 1
    }
    // 재귀 호출: n * (n-1)! 계산
    return n * factorial(n - 1)
}

fun main() {
    val number = 5
    println("$number! = ${factorial(number)}") // 출력: 5! = 120
}

 

 

2. 피보나치 수열 계산

피보나치 수열은 앞의 두 수의 합으로 다음 수를 만드는 수열이다.

재귀적으로 f(n) = f(n-1) + f(n-2)로 정의되며, f(0)=0, f(1)=1 이다.

fun fibonacci(n: Int): Int {
    // 기본 조건: n이 0 또는 1이면 n 반환
    if (n == 0 || n == 1) {
        return n
    }
    // 재귀 호출: 피보나치 수열의 합 계산
    return fibonacci(n - 1) + fibonacci(n - 2)
}

fun main() {
    val n = 10
    println("Fibonacci($n) = ${fibonacci(n)}") // 예시 출력: Fibonacci(10) = 55
}

 

 

 

3. 문자열 재귀적으로 뒤집기

 - 문제 설명 : 문자열을 재귀 함수를 사용하여 뒤집는 코드를 작성해보자

 - 힌트 : 문자열의 첫 번째 문자와 나머지 부분을 분리하여, 나머지 부분을 재귀적으로 뒤집고 첫 번째 문자를 맨 뒤에 붙이는 방식으로 접근해보자

더보기
fun reverseRecursively(str: String): String {
    // 기본 조건: 문자열이 비어있으면 그대로 반환
    if (str.isEmpty()) {
        return str
    }
    // 재귀 호출: 문자열의 첫 번째 문자를 마지막으로 보내고, 나머지 문자열을 뒤집음
    return reverseRecursively(str.substring(1)) + str[0]
}

fun main() {
    val text = "Hello"
    println("원래 문자열: $text")
    println("뒤집은 문자열: ${reverseRecursively(text)}")
    // 출력: 뒤집은 문자열: olleH
}

문자열이란?

문자열(String) 이란 여러 문자가 이어진 데이터이다. 예를 들어, "Hello, Kotlin!" 은 하나의 문자열이다.

 

문자열에서 인덱스 (Index)도 중요한데 인덱스란 문자열의 각 문자의 순서를 나타내며, 첫 번째 문자는 0번 인덱스이다.

val text = "Hello"
println(text[0]) // 출력: H
println(text[1]) // 출력: e

 

Kotlin에서는 문자열을 다루기 위한 다양한 내장 함수를 제공한다.

  • reversed() : 문자열을 뒤집는다.
  • length : 문자열의 길이를 반환한다.
  • contains() : 특정 문자가 포함되어 있는지 확인한다. 리턴값은  true / false
  • split() : 문자열을 특정 구분자로 나눈다.
fun main() {
    val text = "Kotlin Programming"
    println("원본 문자열: $text")
    println("뒤집은 문자열: ${text.reversed()}")   // 뒤집기
    println("문자열 길이: ${text.length}")          // 길이 출력
    println("포함 여부 (o): ${text.contains("o")}")   // 'o'가 포함되었는지
    println("단어 분리: ${text.split(" ")}")          // 공백을 기준으로 분리
}

 

 

 

문제 예시

1. 문자열 뒤집기

  - 문제 설명 : 사용자가 입력한 문자열을 뒤집어서 출력하는 코드를 작성해보자

  - 힌트 : Kotlin의 내장 함수를 사용할 수 있다.

더보기
fun reverseString(str: String): String {
    return str.reversed()
}

fun main() {
    val input = "Hello, Kotlin!"
    println("원본 문자열: $input")
    println("뒤집은 문자열: ${reverseString(input)}")
}

 

 

2. 문자열에서 특정 문자 개수 세기

 - 문제 설명 : 주어진 문자열에서 특정 문자가 몇 번 등장하는지 계산하는 코드를 작성해보자.

 - 힌트 : 반복문과 조건문을 사용하자

더보기
fun countChar(str: String, ch: Char): Int {
    var count = 0
    for (c in str) {
        if (c == ch) {
            count++
        }
    }
    return count
}

fun main() {
    val input = "banana"
    println("문자 'a'의 개수: ${countChar(input, 'a')}")
    // 출력: 문자 'a'의 개수: 3
}

 

 

3. 문자열에서 단어 개수 세기

 - 문제 설명 : 사용자가 입력한 문장에서 단어의 개수를 출력하는 코드를 작성해보자

 - 힌트 : Kotlin의 내장 함수를 사용하자

더보기
fun countWords(sentence: String): Int {
    // 공백을 기준으로 단어 분리 후, 분리된 리스트의 크기를 반환
    val words = sentence.trim().split("\\s+".toRegex())
    return words.size
}

fun main() {
    val sentence = "Kotlin is fun and powerful"
    println("단어 개수: ${countWords(sentence)}")
    // 출력: 단어 개수: 5
}



// val words = sentence.trim().split("\\s+".toRegex()) 동작 순서

// 1. sentence.trim()
// 동작: 입력된 문자열 sentence의 앞뒤(시작과 끝)에 있는 불필요한 공백(스페이스, 탭 등)을 제거합니다.
// 예시: " Hello Kotlin! " → "Hello Kotlin!"

// 2.  .split("\\s+".toRegex())
// 동작:
// trim()으로 정리된 문자열을 기준으로, 하나 이상의 공백 문자(정규표현식 \\s+)를 찾아서 해당 부분에서 문자열을 나눕니다.
// \\s+는 정규 표현식에서 "하나 이상의 공백 문자"를 의미합니다.
// .toRegex()는 문자열 "\\s+"를 정규식 객체로 변환해줍니다.
// 예시: "Hello Kotlin!" → ["Hello", "Kotlin!"]

 

 

 

 

 

 

 

 

 

 

Java 기반 프로젝트를 배포할 때 자주 사용하는 Jar 파일과 War 파일이 있다. 누군가는 War 파일로 달라고 하고, 누군가는 Jar 파일로 달라고 한다. 두 파일의 차이점은 무엇이고 어떨때 활용하면 좋을까?

 

Jar, War 모두 패키징이다.

 

여기서 패키징은 단순히 파일을 압축하는 것이 아니라, 실제 애플리케이션을 실행 가능한 형태로 만들어 주는 매우 중요한 단계이다.

 

연인에게 줄 선물을 박스째로 주는 것보다 예쁘게 포장해서 주면 더 좋은것과 마찬가지이다.

 

소프트웨어도 Jar 파일이나 War 파일과 같은 포맷으로 잘 포장되어 있어야, 다른 개발자나 운영자가 쉽게 사용하고 배포할 수 있다.

 

Jar 파일(Java Archive)

Jar 파일은 Java 애플리케이션을 패키징하는데 사용되는 파일 형식이다. 여러 개의 클래스 파일, 리소스 파일, 메타데이터를 하나의 압축 파일로 묶은 것이다. 특히 Spring Boot와 같은 프레임워크에서는 애플리케이션 전체가 Jar 파일 하나로 패키징된다.

 

 

Jar 파일의 특징

  • 독립적 실행 가능 : 내장된 Tomcat, Jetty 등 웹 서버를 함께 포함하여 별도의 웹 서버 설치 없이 실행할 수 있다.
  • 실행 방식 간편 : 명령어 한 줄로 간편하게 실행할 수 있다.
  • 클라우드 및 마이크로서비스 환경 적합 : 컨테이너화된 환경에서 효율적으로 관리된다.
  • 실행 가능한 Jar 파일은 main() 메서드를 포함하고 있어,  java - jar filename.jar   명령어로 실행할 수 있다.

 

활용법 

  • 애플리케이션 배포 : 독립 실행형 애플리케이션으로 배포할 때 사용한다.
  • 라이브러리 제공 : 다른 프로젝트에서 공통적으로 사용할 수 있는 기능을 포함한 라이브러리를 Jar 파일로 제공할 수 있다.

 

 

 

War 파일(Web Application Archive)

War 파일은 웹 애플리케이션을 Tomcat, JBoss, WebLogic 등 외부 웹 서버에 배포할 때 사용되는 형식이다.

 

War 파일의 특징

  • 외부 서버에 의존적 : 별도의 외부 웹 서버가 반드시 필요하다.
  • 복잡한 설정 지원 가능 : 다양한 서버에서 다수의 애플리케이션 관리 시 효율적이다.
  • 전통적인 배포 방식 : 기존 웹 서버 기반 인프라에 적합하다.

어떤 것을 선택할지?

  • Jar : 빠른 배포가 중요하거나 클라우드 환경에 배포한다면 Jar 파일이 좋다.
  • War : 복잡한 서버 환경을 관리하거나 기존 웹 서버를 활용해야 하는 환경이라면 War 파일이 유리하다.

 

두 파일의 차이점을 정리해보자 

 

Jar와 War 파일의 차이점

1. 구조와 구성 요소 비교

 

Jar 파일:

  • 주로 클래스 파일과 리소스 파일, 메타데이터가 포함됩니다.
  • 실행 가능한 Jar 파일은 main() 메서드를 포함하여 독립적으로 실행됩니다.

War 파일:

  • 웹 애플리케이션에 필요한 모든 파일(HTML, CSS, JS, 서블릿, JSP, WEB-INF 폴더 등)이 포함됩니다.
  • War 파일은 서블릿 컨테이너(예: Tomcat)에 배포되어 실행됩니다.

 

 

2. 실행 환경과 용도

 

Jar 파일:

  • 용도: 독립 실행형 애플리케이션, 라이브러리 제공
  • 실행 환경: JVM이 설치된 모든 환경에서 실행 가능
  • 실행 방법: java -jar myapp.jar

War 파일:

  • 용도: 웹 애플리케이션, 동적 웹 사이트
  • 실행 환경: 서블릿 컨테이너(예: Tomcat, Jetty)가 필요
  • 배포 방법: War 파일을 서버의 webapps 폴더에 복사하여 자동 배포

 

실제 개발 사례와 활용 전략

제가 백엔드 개발자로 일하면서 느낀 점은, Jar 파일과 War 파일을 어떻게 활용하느냐가 프로젝트의 성격에 따라 달라진다는 것이다.

 

1. 소규모 프로젝트 vs 대규모 웹 서비스

  • 소규모 프로젝트:
    독립 실행형 애플리케이션이나 명령줄 도구 등은 Jar 파일로 배포하는 것이 편리합니다.
  • 대규모 웹 서비스:
    웹 애플리케이션은 War 파일로 패키징하여 Tomcat 같은 서버에 배포합니다.

 

 

2. 마이크로서비스 아키텍처에서의 활용

요즘은 많은 기업들이 마이크로서비스 아키텍처를 채택하고 있다고 한다.
각 서비스는 독립적으로 개발되고 배포되는데, 이때

  • 각 서비스는 Jar 파일로 배포될 수도 있고,
  • 혹은 각각의 웹 애플리케이션으로 War 파일로 패키징되어 별도의 서블릿 컨테이너에서 실행될 수도 있습니다.

 

 

3. 실제 적용 예: Spring Boot 애플리케이션

Spring Boot는 기본적으로 Jar 파일로 배포할 수 있도록 설계되어 있지만, 설정을 통해 War 파일로도 패키징할 수 있습니다.

 

Jar 파일로 배포하는 경우

장점:

  • 독립 실행형으로 배포하기 쉽고, 내장 Tomcat을 포함하여 추가 설정이 필요 없습니다.

예제

   java -jar myapplication.jar    

 

 

War 파일로 배포하는 경우

Spring Boot 애플리케이션을 War 파일로 빌드하려면, 메인 클래스에 SpringBootServletInitializer 를 상속받는 설정이 필요하다.

 

예제 코드를 한번 살펴보자

package com.example.demo

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer

@SpringBootApplication
class DemoApplication

/**
 * War 파일로 패키징하기 위한 설정 클래스
 */
class ServletInitializer : SpringBootServletInitializer() {
    override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
        return application.sources(DemoApplication::class.java)
    }
}

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

 

빌드 후 생성된 War 파일을 Tomcat의 webapps 폴더에 복사하면 자동으로 배포된다.

 

 

Jar 파일 구조 다이어그램

 

Jar 파일은 Java 클래스, 리소스, 그리고 메타데이터가 하나의 압축 파일에 담겨 있는 구조이다.

 

 

War 파일 구조 다이어그램

 

War 파일은 웹 애플리케이션을 위한 모든 파일이 포함되어 있으며, WEB-INF 폴더 아래에 클래스와 라이브러리들이 존재한다.

+ Recent posts