1. 클래스 선언방법-1 : 클래스 헤더가 존재하지 않는 형태 - 클래스 헤더는 매개변수나 기본 생성자를 의미합니다. 이를 사용하지 않은 클래스를 선언하는 예시입니다.
2. 클래스 선언 방법-2: 클래스 헤더(매개변수)가 존재하는 경우 - 클래스 헤더로 매개변수를 받는 경우를 의미합니다. 즉, 인스턴스화 할때, 필수로 매개변수로 id, name 값을 받는 클래스를 선언하는 예시입니다. 3. 클래스 선언 방법-3: 본문이 없는 경우 - 클래스로 선언하였지만, 본문에 내용이 없는 경우를 의미합니다. 이는 향후 확장을 위한 자리 표시자로 사용될 수 있습니다.
// 클래스 선언 방법-1: 클래스 헤더가 존재하지 않는 형태
class Person {
// 클래스 본문
var name: String = ""
var age: Int = 0
}
// 클래스 선언 방법-2: 클래스 헤더(매개변수)가 존재하는 경우
class Student(private val id: Int, private var name: String) {
// 클래스 본문
fun introduce() {
println("My name is $name and my student ID is $id")
}
}
// 클래스 선언 방법-3: 본문이 없는 경우
class EmptyClass;
2. 생성자(Constructor)
💡 생성자(Constructor)
- Kotlin 클래스는 주 생성자와 하나 이상의 보조 생성자를 가질 수 있습니다. - 기본 생성자는 클래스 헤더에 선언되며 클래스 이름과 매개변수 뒤에 옵니다.
💡 생성자 선언 방법 1. 생성자 선언 방법-1: 클래스 이름과 매개변수 뒤에 constructor 선언하는 경우 - 클래스 구성 시 ‘constructor’를 통해서 생성자도 함께 생성하는 경우를 의미합니다. 2. 생성자 선언 방법-2: constructor를 사용하지 않는 경우 - 해당 경우에서는 매개변수들은 자동으로 클래스의 주 생성자가 됩니다.이 방식으로 선언된 매개변수들은 클래스 내부에서 사용할 수 있지만, 자동으로 프로퍼티가 되지는 않습니다. 3. 생성자 선언 방법-3 : 주 생성자와 보조 생성자가 존재하는 경우 - 해당 경우에서는 name, age라는 값을 이용하여 주 생성자를 구성하였습니다. - 클래스 본문 내에서 constructor를 통해서 name의 값은 필수 매개변수로 받으면서, age라는 매개변수에 대해서는 0으로 초기화를 하여 선택적으로 인스턴스화 할 수 있도록 구성합니다.
package com.blog.kotlinspringbootform.component
// 생성자 선언 방법-1: 클래스 이름과 매개변수 뒤에 constructor 선언하는 경우
class ConstructorComponent constructor(firstName: String) {
init {
// 생성자 초기화 수행
}
}
// 생성자 선언 방법-2: constructor를 사용하지 않는 경우
class ConstructorComponent(firstName: String, lastName: String, age: Int) {
init {
// 생성자 초기화 수행
}
}
// 생성자 선언 방법-3 : 주 생성자와 보조 생성자가 존재하는 경우
class Person(val name: String, var age: Int) {
// 주 생성자
init {
// 생성자 초기화 수행
}
constructor(name: String) : this(name, 0) {
// 보조 생성자
}
}
// 아래와 같은 구조를 통해 객체 생성 시 유연성을 제공합니다.
val person1 = Person("Alice", 30) // 주 생성자 사용
val person2 = Person("Bob") // 보조 생성자 사용, age는 0으로 초기화됨
3. 클래스 인스턴스 생성(Creating instances of classes)
💡 클래스 인스턴스 생성(Creating instances of classes)
- Kotlin에서 클래스의 인스턴스를 생성할 때는 new 키워드를 사용하지 않습니다. - 클래스 이름 뒤에 괄호를 붙여 생성자를 호출합니다. - 생성자에 매개변수가 있는 경우, 해당 매개변수를 괄호 안에 전달합니다.
// 매개변수가 없는 생성자
val invoice = Invoice()
// 매개변수가 있는 생성자
val customer = Customer("Joe Smith")
// 여러 매개변수를 가진 생성자
val person = Person("Alice", 30)
// 명명된 매개변수를 사용한 생성자 호출
val employee = Employee(name = "Bob", age = 35, department = "IT")
4. init block
💡 init block
- 클래스의 초기화 코드를 포함하는 특별한 블록을 의미합니다. - 주 생성자의 일부로 실행되며, 클래스가 인스턴스화될 때 호출됩니다. - 클래스 내에 여러 init 블록을 가질 수 있으며, 이들은 클래스 본문에 나타나는 순서대로 실행됩니다.
특징
설명
초기화 순서
클래스 본문에 나타나는 순서대로 실행됩니다.
주 생성자와의 관계
주 생성자의 일부로 실행되며, 주 생성자 매개변수에 접근할 수 있습니다.
다중 사용
하나의 클래스에 여러 init 블록을 정의할 수 있습니다.
프로퍼티 초기화
프로퍼티 초기화와 교차적으로 실행됩니다.
예외 처리
초기화 중 예외를 던질 수 있어 객체 생성 실패를 명시적으로 처리할 수 있습니다.
class Person(val name: String) {
var age: Int = 0
init {
println("Person instance created with name: $name")
}
init {
println("This is the second init block")
}
}
// 사용 예
val person = Person("Alice")
// 출력:
// Person instance created with name: Alice
// This is the second init block
5. 프로퍼티(Properties)
💡 프로퍼티(Properties)
- 클래스는 프로퍼티를 가질 수 있으며, 이는 주 생성자에서 선언하거나 클래스 본문에서 선언할 수 있습니다. - 해당 프로퍼티는 Java의 멤버 변수와 다르게 getter, setter를 자동으로 생성합니다. - val로 생성한 프로퍼티는 읽기전용으로 gettter가 되지만, var로 생성한 프로퍼티는 getter/setter로 읽기 쓰기가 가능합니다.
💡 프로퍼티 예시
- Person 클래스 내에 email 이라는 프로퍼티를 클래스 내에 선언하였습니다.
class Person(val name: String, var age: Int) {
var email: String = ""
}
- 프로퍼티는 Kotlin의 개념으로, getter와 setter를 자동으로 생성합니다. - 멤버 변수는 Java의 개념으로, 단순히 클래스 내부의 변수를 의미합니다. - 따라서, Kotlin의 프로퍼티는 멤버 변수의 개념을 포함하면서도 더 확장된 기능을 제공한다고 볼 수 있습니다.
5. 함수(Functions)
💡 함수(Functions)
- 클래스 내에 함수를 정의하여 메서드를 만들 수 있습니다
class Person(val name: String, var age: Int) {
// 메서드 선언부
fun introduce() {
println("My name is $name and I'm $age years old.")
}
}
// Person 클래스의 인스턴스 생성
val person = Person("Alice", 30)
// introduce 메서드 호출 : My name is Alice and I'm 30 years old. 출력
person.introduce()
- Animal 클래스 내에서 ‘open’ 키워드를 두어서 외부에서 상속하는것에 대해서 허용합니다. 즉, Dog 클래스 기준으로 Animal 클래스로 부터 상속을 받습니다. - 이를 상속받은 Dog 클래스 내에서는 Anmal 클래스의 makeSound() 메서드를 오버라이딩 합니다
open class Animal(val name: String) {
open fun makeSound() {
println("The animal makes a sound")
}
}
class Dog(name: String) : Animal(name) {
override fun makeSound() {
println("The dog barks")
}
}
2) Kotlin 클래스(Class) 종류
1. 추상 클래스(Abstract Class)
💡 추상 클래스(Abstract Class)
- 'abstract' 키워드를 사용하여 선언합니다. 이는 직접 인스턴스화 할 수 없으며 다른 클래스가 상속받아 구현해야 합니다. - 추상 클래스는 추상 메서드(구현이 없는 메서드)를 포함할 수 있고 일반 메서드와 프로퍼티도 포함할 수 있습니다.
특징
설명
부분적인 구현
일부 메서드는 구현하고, 일부는 추상으로 남겨둘 수 있습니다.
상속
다른 클래스가 추상 클래스를 상속받아 추상 메서드를 구현해야 합니다.
다형성
추상 클래스 타입의 변수로 하위 클래스의 인스턴스를 참조할 수 있습니다.
공통 기능 정의
여러 하위 클래스에서 공유할 수 있는 공통 기능을 정의할 수 있습니다.
abstract class Shape {
abstract fun draw() // 추상 메서드
fun moveTo(x: Int, y: Int) { // 일반 메서드
println("Moving to ($x, $y)")
}
}
class Circle : Shape() {
override fun draw() {
println("Drawing a circle")
}
}
// 사용 예
val circle = Circle()
circle.draw() // 출력: Drawing a circle
circle.moveTo(10, 20) // 출력: Moving to (10, 20)
- Kotlin은 데이터를 보유하는 목적의 클래스를 위한 특별한 'data class'를 제공합니다. - 일반적으로 구성하는 VO, DTO에 대한 정의를 위함입니다.
특징
설명
자동 생성 메서드
컴파일러가 자동으로 equals(), hashCode(), toString(), copy() 메서드를 생성. 코드 작성 시간 절약 및 오류 가능성 감소.
불변성 지원
주 생성자에서 프로퍼티를 val로 선언하여 불변 객체를 쉽게 생성 가능.
구조 분해
데이터 클래스 객체를 쉽게 구조 분해하여 여러 변수에 객체의 프로퍼티 할당 가능.
간결한 문법
최소한의 코드로 데이터 모델을 정의하여 코드의 가독성 향상.
💡 data class 사용예시
- 아래와 같이 data 클래스로 구성을 하는 경우, 별도의 getter/setter/toString 구성 없이도 접근이 가능합니다.
package com.blog.kotlinspringbootform.dto
data class UserDto(
val userSq: Int, // 사용자 시퀀스
val userId: String, // 사용자 아이디
val userPw: String, // 사용자 패스워드
val userNm: String, // 사용자 이름
val userSt: String, // 사용자 상태
)
💡 data class 호출 예시
- 타입을 UserDto로 지정한 user를 생성하여 data 클래스를 호출합니다. - UserDto의 파라미터를 통해서 각각의 값에 대해서 setter를 구성합니다. - userDto 객체의 프로퍼티에 접근하여 호출을 합니다. - userDto.toString() 메서드를 통해서 객체를 직렬화하여 호출합니다.
[더 알아보기] 💡 Kotlin에서는 Builder 패턴을 사용하지 않는가? - Kotlin에서는 일반적으로 Builder 패턴을 직접적으로 사용하지 않습니다. - Kotlin의 언어 기능들이 Builder 패턴의 장점을 대체할 수 있기 때문입니다. - 그러나 복잡한 객체 생성 로직이 필요한 경우는 Builder 패턴을 이용하여 구현합니다.
💡 [참고] 아래와 같은 형태로 Builder 패턴을 대체하여 사용합니다.
data class Person(
val name: String,
val age: Int = 0,
val email: String? = null
)
// 사용 예
val person = Person(
name = "John",
age = 30,
email = "john@example.com"
)
3. Enum 클래스(Enum Class)
💡 Enum 클래스(Enum Class)
- Kotlin에서 Enum 클래스는 'enum class' 키워드를 사용하여 선언합니다. - Enum 클래스는 미리 정의된 상수들의 집합을 나타내는 특별한 종류의 클래스입니다.
- 아래와 같이 EnumDirection, EnumDayOfWeek 두 개의 enum class가 있습니다.
1. EnumDirection의 경우는 각각 값에 대해서 관리를 합니다. 2. EnumDayOfWeek의 경우는 코드 형태로 SUNDAY 값을 호출하는 경우 숫자로 반환합니다.
/**
* 상수로 값을 지정한 ENUM CLASS
*/
enum class EnumDirection {
NORTH, SOUTH, EAST, WEST
}
/**
* 일자를 숫자로 가지고 있습니다.
*/
enum class EnumDayOfWeek(val value: Int) {
SUNDAY(1),
MONDAY(2),
TUESDAY(3),
WEDNESDAY(4),
THURSDAY(5),
FRIDAY(6),
SATURDAY(7);
fun isWeekend(): Boolean {
return this == SATURDAY || this == SUNDAY
}
}
fun enumClassCall() {
// EnumDirection 사용 예시
val direction = EnumDirection.NORTH
println("Selected direction: $direction") // Selected direction: NORTH
// EnumDayOfWeek 사용 예시
val today = EnumDayOfWeek.WEDNESDAY
println("Today is: $today") // Today is: WEDNESDAY
println("Today's value: ${today.value}") // Today's value: 4
println("Is today a weekend? ${today.isWeekend()}") // Is today a weekend? false
val saturday = EnumDayOfWeek.SATURDAY
println("Is Saturday a weekend? ${saturday.isWeekend()}") // Is Saturday a weekend? true
}
4. 중첩 클래스(Nested Class)
💡 중첩 클래스(Nested Class)
- Kotlin에서 중첩 클래스는 다른 클래스 내부에 선언된 클래스입니다. - 기본적으로 정적(static)이며, 외부 클래스의 인스턴스에 대한 참조를 갖지 않습니다. - 외부 클래스의 private 멤버를 포함한 모든 멤버에 접근할 수 없습니다.
- 아래 예시에서 NestedClass는 OuterClass 내부에 선언되었지만, OuterClass의 멤버에 직접 접근할 수 없습니다.
class OuterClass {
private val outerProperty = "Outer Property"
class NestedClass {
fun nestedMethod() {
println("This is a nested class method")
// 오류: outerProperty에 직접 접근 불가
// println(outerProperty)
}
}
fun outerMethod() {
val nested = NestedClass()
nested.nestedMethod()
}
}
fun main() {
val nestedInstance = OuterClass.NestedClass()
nestedInstance.nestedMethod()
val outerInstance = OuterClass()
outerInstance.outerMethod()
}
5. 내부 클래스(Inner Class)
💡 내부 클래스(Inner Class)
- Kotlin에서 inner class는 외부 클래스의 멤버에 접근할 수 있는 중첩 클래스입니다. - 'inner' 키워드를 사용하여 선언합니다. - 외부 클래스의 인스턴스에 대한 참조를 가집니다.
Inner Class의 특징
설명
외부 클래스의 멤버에 접근 가능
Inner class는 외부 클래스의 private 멤버를 포함한 모든 멤버에 접근할 수 있습니다.
외부 클래스의 인스턴스와 연결됨
Inner class의 인스턴스는 항상 외부 클래스의 인스턴스와 연관되어 있습니다.
메모리 누수에 주의해야 함
외부 클래스의 인스턴스에 대한 참조를 유지하므로, 부주의하게 사용하면 메모리 누수의 원인이 될 수 있습니다.
- ClassComponent 라는 클래스 내에서 내부 클래스로 InnerClass를 생성했습니다. 이 생성된 클래스는 함수내에서 호출이 가능합니다. - outer라는 변수에 인스턴스를 생성하고, inner라는 변수에 내부 클래스를 호출하도록 구성하였습니다. - 최종적으로 내부 클래스의 메서드에 접근하여서 호출을 하여 “Hello from Outer” 라는 콘솔에 출력을 하였습니다.
@Component
class ClassComponent() {
private val message: String = "Hello from Outer"
inner class InnerClass {
fun greet() = println(message)
}
/**
* 내부 클래스 호출 사용예시
*/
fun innerClassCall() {
val outer = ClassComponent()
val inner = outer.InnerClass()
inner.greet() // 출력: Hello from Outer
}
}
3) Kotlin 인터페이스
1. Kotlin 인터페이스
💡 Kotlin 인터페이스
- Kotlin에서 인터페이스는 ‘interface’ 키워드를 사용하여 선언합니다. - 인터페이스의 경우는 추상 메서드와 디폴트 메서드를 포함할 수 있습니다.
- 아래와 같이 서비스 컴포넌트임을 나태는 @Service 어노테이션을 선언하고 class [파일명] : [인터페이스 명] 형태로 구성을 합니다. - 메서드는 인터페이스에서 추상 메서드로 구현한 내용에 대해 실제 비즈니스 로직을 처리합니다.
package com.blog.kotlinspringbootform.service.impl
import com.blog.kotlinspringbootform.model.dto.UserDto
import com.blog.kotlinspringbootform.service.UserService
import org.springframework.stereotype.Service
@Service
class UserServiceImpl : UserService {
override fun selectUserList(userDto: UserDto): List<UserDto> {
TODO("Not yet implemented")
}
override fun insertUser(userDto: UserDto): Int {
TODO("Not yet implemented")
}
override fun updateUser(userDto: UserDto): Int {
TODO("Not yet implemented")
}
override fun deleteUserList(userDto: List<UserDto>) {
TODO("Not yet implemented")
}
override fun selectBoardList(any: Any): Any {
TODO("Not yet implemented")
}
}
1.3. 다중인터페이스 구현
💡 다중인터페이스 구현
- 추가적으로 BoardService를 구성하였습니다. - 이전 UserService 인터페이스와 함께 다중인터페이스로 구현체를 구현하는 방법입니다.
package com.blog.kotlinspringbootform.service
import org.springframework.stereotype.Service
@Service
interface BoardService {
fun selectBoardList(any: Any): Any
fun insertBoard(any: Any): Int
fun updateBoard(any: Any): Int
fun deleteBoard(any: Any): Int
}
💡 다중인터페이스 구현
- 구현체에서는 : UserService, BoardService를 통해서 두개의 인터페이스로 부터 구현체를 구성합니다.
package com.blog.kotlinspringbootform.service.impl
import com.blog.kotlinspringbootform.dto.UserDto
import com.blog.kotlinspringbootform.service.BoardService
import com.blog.kotlinspringbootform.service.UserService
class UserServiceImpl : UserService, BoardService {
override fun selectUserList(userDto: UserDto): List<UserDto> {
TODO("Not yet implemented")
}
override fun insertUser(userDto: UserDto): Int {
TODO("Not yet implemented")
}
override fun updateUser(userDto: UserDto): Int {
TODO("Not yet implemented")
}
override fun deleteUserList(userDto: List<UserDto>) {
TODO("Not yet implemented")
}
override fun selectBoardList(any: Any): Any {
TODO("Not yet implemented")
}
override fun insertBoard(any: Any): Int {
TODO("Not yet implemented")
}
override fun updateBoard(any: Any): Int {
TODO("Not yet implemented")
}
override fun deleteBoard(any: Any): Int {
TODO("Not yet implemented")
}
}
4) Kotlin 인터페이스의 구현체
💡 Kotlin 인터페이스 구현체
- 구현체는 'class' 키워드와 함께 콜론(:)을 사용하여 인터페이스를 구현합니다.
package com.blog.kotlinspringbootform.service.impl
import com.blog.kotlinspringbootform.dto.UserDto
import com.blog.kotlinspringbootform.service.UserService
class UserServiceImpl : UserService {
override fun selectUserList(userDto: UserDto): List<UserDto> {
TODO("Not yet implemented")
}
override fun insertUser(userDto: UserDto): Int {
TODO("Not yet implemented")
}
override fun updateUser(userDto: UserDto): Int {
TODO("Not yet implemented")
}
override fun deleteUserList(userDto: List<UserDto>) {
TODO("Not yet implemented")
}
}
- 'class' 키워드로 선언 - 데이터와 코드를 하나의 단위로 묶는 틀 - 클래스 이름, 헤더, 본문으로 구성
인터페이스(Interface)
- 'interface' 키워드로 선언 - 추상 메서드와 디폴트 메서드 포함 가능 - @Service 어노테이션과 함께 사용
추상 메서드
- 구현부가 없는 메서드 - 구현체에서 반드시 오버라이드 필요 - 예: selectUserList(), insertUser() 등
디폴트 메서드
- 인터페이스에서 기본 구현을 제공 - 구현체에서 선택적으로 오버라이드 가능
구현체(Implementation)
- 클래스에서 콜론(:)을 사용하여 인터페이스 구현 - override 키워드로 메서드 구현 - 다중 인터페이스 구현 가능
// 클래스 선언
class User {
var name: String = ""
var age: Int = 0
}
// 인터페이스 선언
interface UserService {
// 추상 메서드
fun getUser(id: Long): User
// 디폴트 메서드
fun printUserInfo(user: User) {
println("User: ${user.name}, Age: ${user.age}")
}
}
// 단일 인터페이스 구현
class UserServiceImpl : UserService {
override fun getUser(id: Long): User {
// 구현 로직
return User()
}
}
// 다중 인터페이스 구현
interface LogService {
fun logInfo(message: String)
}
class UserServiceWithLogging : UserService, LogService {
override fun getUser(id: Long): User {
return User()
}
override fun logInfo(message: String) {
println("Log: $message")
}
}