상속 받을 클래스에서 구현해야 할 프로퍼티 및 메서드를 기술한 클래스
abstract 키워드와 함께 선언하며 추상 클래스는 객체 생성 불가
상속을 주기 위해서는 open 키워드를 선언해야 하는데 abstract class는 open이 기본적으로 적용됨
java와 달리 추상 프로퍼티를 가질 수 있음
예제 코드
// 추상 클래스, 주 생성자에는 비추상 프로퍼티 선언의 매개변수 3개가 있음
abstract class Vehicle(val name: String, val color: String, val weight: Double) {
// 추상 프로퍼티 (반드시 하위 클래스에서 재정의해 초기화해야 함) -> 자바와 차이점
abstract var maxSpeed: Double
// 일반 프로퍼티 (초기 값인 상태를 저장할 수 있음)
var year = "2018"
// 추상 메서드 (반드시 하위 클래스에서 구현해야 함)
abstract fun start()
abstract fun stop()
// 일반 메서드
fun displaySpecs() {
println("Name: $name, Color: $color, Weight: $weight, Year: $year, Max Speed: $maxSpeed") }
}
class Car(name: String, color: String, weight: Double, override var maxSpeed: Double) : Vehicle(name, color, weight) {
override fun start() {
println("Car Started") // 코드의 구현
}
override fun stop() {
println("Car Stopped") // 코드의 구현
}
}
class Motorcycle(name: String, color: String, weight: Double, override var maxSpeed: Double) : Vehicle(name, color, weight) {
override fun start() {
println("Bike Started") // 코드의 구현
}
override fun stop() {
println("Bike Stopped") // 코드의 구현
}
}
fun main() {
val car = Car("SuperMatiz", "yellow", 1110.0, 270.0)
val motor = Motorcycle("DreamBike", "red", 173.0, 100.0)
car.year = "2013"
car.displaySpecs()
car.start()
motor.displaySpecs()
motor.start()
}
자바와 동일하게 구현부가 없는 method + default method의 집합
상속을 주기 위해 만들어진 클래스로 별도의 open 키워드는 필요 없음
상속 받는 interface에서 동일한 function이 있다면 반드시 구현해야 함
예제 코드
interface Clickable {
fun click()
fun showOff(){
println("Clickable.showOff")
}
}
interface Focusable{
fun showOff(){
println("Focusable.showOff")
}
}
class Button : Clickable, Focusable {
override fun click() {
println("Button.click")
}
//showOff는 두 슈퍼에 모두 존재하므로, 동일하게 애매함을 없애기 위해서 반드시 선언.
//super에 선언된 fun을 호출할때는 super<Clickable>.showOff()
//java에서는 Clickable.super.showOff();
override fun showOff() {
println("Button.showOff")
}
}
fun main() {
var button = Button()
button.click()
button.showOff()
}
data class 키워드로 선언해서 사용
변수나 상수를 선언할 수 있음 (구분은 쉼표( , ) 로)
프로퍼티를 일반 클래스 내의 프로퍼티를 기준으로 생성자가 만들어짐
DTO(Data Transfer Object)를 다룰 때 유용
자바와 다르게 getter, setter 만들 필요가 없고 초기화도 필요 없이 선언하기만 하면 끝
일반 클래스와 달리 소괄호로 프로퍼티를 선언하면 끝
자동으로 파라미터 생성자, toString( ), equals( ), hashCoode 등 제공
{ } 열어서 function 추가도 가능
예시 코드
//일반 클래스
class Person20(var name: String) {
private var type: String = ""
private var age: Int = 0
constructor(name: String, type: String, age: Int) : this(name) {
this.type = type
this.age = age
}
fun myMbTi() {
println("my name is $name and type is $type")
}
}
//data clas
data class PersonData(val name: String, val type: String, var num: Int, var own: Boolean)
fun main(args: Array<String>) {
val person20 = Person20("김싸피", "ENFP", 23)
person20.myMbTi()
val person = PersonData("김싸피", "ENFP", 23, true)
println(person)
if (person.own)
println("my name is ${person.name}")
else
println("i'm not human being")
}
object 키워드로 선언
생성자를 갖지 않음
어느 클래스, 함수에서든 별도의 객체화 과정 없이 접근 가능
프로그램이 실행되는 동안 저장된 데이터는 손실되지 않고, 프로그램이 종료되면 소멸
위 특징 덕분에 안드로이드에서는 액티비티, 프래그먼트 구분하지 않고 데이터 전달 가능
anonymous nested class를 생성할 때 사용
예제 코드
object PersonObject {
var name: String = ""
var type: String = ""
var age: Int = 0
fun myType() {
println("my name is $name and type is $type age is $age")
}
}
fun main() {
val name = "samsung"
val type = "ENFP"
val age = 23
PersonObject.name = name
PersonObject.type = type
PersonObject.age = age
PersonObject.myType()
println("${PersonObject.name} and ${PersonObject.type} and ${PersonObject.age}")
}
java와 동일한 열거형
[클래스 이름.name] 으로 이름 값 접근 가능
[클래스 이름.ordinal] 으로 해당 값이 몇 번째에 기록되어 있는지 순서 값 접근 가능
인자가 있는 열거형 클래스인 경우 열거 값들을 표현하는 방식을 다채롭게 할 수 있음
enum class ExHandsome {
EX, LOVE, PEACE
}
fun main() {
val exEnum: ExHandsome = ExHandsome.LOVE
println("${exEnum.name} ... ${exEnum.ordinal}")
val exEnum2: Array<ExHandsome> = ExHandsome.values()
for (i in exEnum2.indices) {
println(exEnum2[i].name)
}
}
enum class Human(val age: Int) {
KIM(25), CHOI(21)
}
fun main() {
val human: Human = Human.KIM
println("${human.name}, ${human.age}, ${human.ordinal}")
}
부모 클래스의 상속을 받는 자식 클래스의 종류를 제한하는 클래스
sealed class에 정의된 하위 클래스 외의 다른 하위 클래스는 존재하지 않는다는 것을 컴파일러에게 알려줌
sealed class 키워드를 선언해서 사용
sealed class도 추상 클래스로 객체 생성 불가
sealed class와 그 하위 클래스는 동일한 파일에 정의되어야 함.
하위 클래스는 class, data class, object class로 정의 가능
예시 코드
sealed class Color {
object Red : Color()
object Green : Color()
object Blue : Color()
}
fun main() {
val color: Color = Color.Red
val font = when (color) {
is Color.Red -> "Noto Sans"
is Color.Green -> "Open Sans"
is Color.Blue -> "sans-serif"
// No error!
}
println("결정된 font는 : $font")
val font2 = when (color) {
is Color.Red -> "Noto Sans"
is Color.Green -> "Open Sans"
is Color.Blue -> "sans-serif"
}
println("결정된 font는 : $font")
}
공통점
차이점
enum은 하나의 객체만 생성 가능
sealed class는 여러 개의 객체 생성 가능
예시 코드
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> {
expr.number
}
is Sum -> {
eval(expr.e1) + eval(expr.e2)
}
is NotANumber -> {
Double.NaN
}
}
fun main() {
val num1 = Const(10.0)
val num2 = Const(20.0)
val sum = Sum(num1, num2)
val result = eval(sum)
println("result : $result") //30.0
}
자바에서는 클래스 안에 클래스를 정의하면 자동으로 내부 클래스가 되었지만 코틀린에서는 클래스 안에 클래스 선언하면 그냥 중첩 클래스가 됨
내부 클래스로 만드려면 inner 키워드로 선언해야 함
내부 클래스는 기본적으로 외부 클래스를 참조 할 수 있지만, 중첩 클래스는 선언이 클래스 내부에 있을 뿐 외부 클래스의 member에 접근 불가
// nested class중첩 클래스
classOuter {
private valbar: Int = 1
classNested {
// nested class는 외부 클래스 멤버 참조 불가 (자바에선 가능했다.)
//그냥 안에 있는 클래스 정도라 생각해라
// fun foo() = bar
funfoo() = 2
}
}
// inner class내부 클래스
classOuterInner {
private valbar: Int = 1
inner classInner {
funfoo() = bar
// fun foo() = this@OuterInner.bar
}
}
funmain() {
valdemo = Outer.Nested().foo()
valdemo2 = OuterInner().Inner().foo()
println(demo)// 2
println(demo2)// 1
}
가시성 지시자(Visibility modifiers)를 통해서 외부 접근 범위를 정할 수 있음
private
protected
internal
public