Android Kotlin 객체지향 프로그래밍
업데이트:
카테고리: Android
/태그: Kotlin, 객체지향 프로그래밍
객체 지향 프로그래밍(Object Oriented Programming)
5가지의 기본 정의가 있다.
- 변수와 타입
- 저장 공간에서의 위치: 저장 공간을 나타내기 위해서 고유의 이름을 정해야 한다.
- 왜냐면 고유의 이름으로 데이터에 접근할 수 있기 때문에
fun main(args: Array<String>){ var age = 15 println(age) // 15 }
- 흐름 제어
- 조건이 맞는 경우에만 코드를 실행
- 조건이 맞으면 코드를 여러 번 반복 실행한다.
fun main(args:Array<String>){ var age = 17 // 21보다 크거나 같을 때 투표할 수 있다는 출력을 한다. if(age >= 21) println("You may vote now") }
- 함수
- 코드를 나눠주고 필요시 코드 블록을 실행하게 한다.
- 코드를 쉽게 나누고 정리해준다.
fun main(args:Array<String>){ // 함수 호출 myFunction() } fun myFunction(){ println("my function was called") }
- 컬렉션
- 많은 요소를 한 군데에 저장해준다.
- 흐름 제어의 도움을 받아 여러 요소를 반복 실행하게 해준다.
- 클래스
- 직접 데이터 타입을 만든다.
- 데이터 멤버와 메서드를 한 곳에 있게 해준다.
- 메서드는 쉽고 유지 가능한 코드를 만들게 해준다.
객체
차
라는 청사진, 클래스가 존재한다.
차의 props는 최고 속도
, 바퀴 개수
, 창문과 문의 갯수
… 이 있다.
차의 method은 drive
, break
가 있다.
차의 최고속도는 322km
차의 바퀴는 4개, 문은 2개
drive, break, turbo buster가 있다.
이렇게 생산된 차는 객체라고 불린다.
클래스
코틀린의 MainActivity는 AppCompatActivity로 부터 상속받은 것이다.
fun main() {
// 이런식으로 클래스를 이용하여 객체를 생성한다.
var sohee = Person(Cheon, Sohee)
}
// 메서드나 속성이 없어도 클래스가 된다.
// 매개변수에 기본값을 지정해줄 수 있다.
class Person(firstName: String = "John", lastName: String = "Doe"){
init {
println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
}
}
constructor
: 보조 생성자
- 객체 생성 시 값을 추가해준다.
- 클래스 안에 어떤 속성이 들어가는 지 미리 정할 수 있다.
- 구체적으로 정하고 싶지 않다면 필수는 아니다.
init
: 이니셜라이저
- 객체가 생성되는 순간 호출된다.
스코프
함수의 파라미터에는 값을 지정할 수 없다.
변수와 파라미터는 다르다.
// 이런식으로 제일 바깥에 변수를 지정해주면 어느 곳에서든 사용가능하다.
var b = 3
fun main () {
myFcuntion(5)
// 함수 내의 변수는 전역으로 확인 할 수 없다.
b = 3
}
fun myFunction(a: Int){
// 변수 a와 파라미터 a는 다르다.
b = a
println("a = $a") // 4
}
함수 내에서 변수로 값을 지정하고 호출을 하면 파라미터가 아닌 변수가 호출된다.
코틀린에서는 왜 메서드에서 변수와 매개변수 이름을 같게 정할까?
이것을 shadowing
이라 부르고, 시스템 내 다른 곳으로부터 코드를 디커플링 하는 것에 유용하다.
유의사항
- 다른 2개의 변수나 파라미터가 있을 때 이름이 같다면 읽기가 힘들다.
- 한번 쉐도잉을 하면, 스코프 안에서 더는 원래의 변수에 접근할 수가 없다.
멤버
fun main() {
var sohee = Person(Cheon, Sohee)
// 속성 초기화
sohee.hobby = "스케이트보드 타기"
// 메서드 출력
sohee.stateHobby()
}
class Person(firstName: String = "John", lastName: String = "Doe"){
// 변수 = 프로퍼티
var age : Int? = null
var hobby : String = "watch Nexflix"
var firstName : String? = null
// 함수 = 메서드
fun stateHobby(){
// 객체의 프로퍼티에 파라미터값을 저장한다.
this.firstName = firstName
println("$firstName\'s hobby is $hobby")
}
// 2번째 생성자
constructor(firstName: String, lastName: String, age: Int)
: this(firstName, lastName){
// 나이 재정의
this.age = age
}
}
늦은 초기화
변수를 만들면 항상 초기화를 해주어야 하는데 lateinit
을 앞에 붙어주면 나중에 초기화 예정을 의미한다.
fun main(){
var myCar = Car()
myCar.owner // 오류가 발생
}
class Car(){
lateinit var owner : String
init {
// 이니셜라이저를 하게되면 더이상 오류가 발생하지 않는다.
this.owner = "Frank"
}
}
lateinit
을 사용한 변수를 호출하게 되면 오류가 발생한다.
Uninitalized Property Access Exception
이 발생한다.
프로퍼티 소유주가 초기화되지 않아서 비어있는 데이터가 접근했기 때문이다.
해결하기 위해서는 이니셜라이저를 init
안에 넣어줘야 한다.
세터 & 게터
세터
는 변수를 생성할 때 클래스 내에서 속성을 어떻게 지정할 건가이다.
게터
는 변수를 생성하고 객체에서 속성을 호출하였을 때 발생한다.
fun main(){
var myCar = Car()
println("brand is: ${myCar.myBrand}") // bwm
myCar.maxSpeed = -5 // 에러가 발생하게 된다.
}
class Car(){
val myBrand: String = "BMW"
// myBrand에 접근하게 되면 myBrand의 소문자를 받게 된다.
get() {
return field.toLowerCase()
}
var maxSpeed: Int = 250
// 변수가 생성이 하단의 코드가 자동으로 생성된다.
// get() = field
// 여기서 value는 변수를 뜻한다.
set(value){
field = if(value > 0) value else throw IllegalArgumentException("최고 속도를 0 이상으로 설정하세요")
}
var myModel : String = "M5"
// 다른곳에서 지정하려면 불가능하다.
private set
init(){
this.myModel = "M3"
}
}
private
는 해당 클래스 내에서만 사용할 수 있도록 하는 접두사?이다.
데이터 클래스
클래스 생성자 앞에 data
를 작성하면 데이터 클래스를 만들 수 있다.
반드시 주 생성자에 매개변수 하나는 꼭 있어야 한다.
매개변수는 var
또는 val
로 넣어야 한다.
데이터 클래스는 추상, 오픈, 봉인, 내부 클래스일 수 없다.
data class User(val id: Long, var name: String)
fun main(){
val user1 = User(1, "Sohee")
val name = user1.name
println(name) // Sohee
// val로 정해져 있어서 재 정의가 불가하다.
// user1.id = 2
val user2 = User(1, "Sohee")
println(user1.equals(user2)) // ture
// 데이터 클래스는 toString을 내장하고 있다.
println("User Details: $user1")
// 데이터 클래스는 copy를 내장하고 있다.
// 매개변수에 따로 변경할 값을 넣으면 그 값으로 바뀌게 된다.
val updatedUser = user1.copy(name="Cheon Sohee")
println(updatedUser) // User(id=1, name="Cheon Sohee")
// 컴포넌트(속성) 별로 호출할 수 있다.
println(updatedUser.component1()) // 1
println(updatedUser.component2()) // Cheon Sohee
// 구조분해 할당
val (id, name) = updatedUser
}
상속
부모 클래스의 프로퍼티와 메소드를 모두 이어 받아 사용할 수 있다.
코드 재사용성이 높아진다.
// Main Class of Car
open class Car(val name: String, val: brand: String){
open var range: Double = 0.0
open fun extendRange(amount: Double){
if(amount > 0)
range += amount
}
fun drive(distance: Double){
println("Drove for $distance KM")
}
}
// Sub Class of Car
// 상속받은 매개변수도 입력해줘야 한다.
class ElectricCar(name: String, brand: String, batteryLife: Double) : Car(name, brand){
override var range = batteryLife * 6
var chargerType = "Type1"
override fun drive(distance: Double){
println("Drove for $distance KM on electricity")
}
fun drive(){
println("Drove for $range KM on electricity")
}
}
fun main(){
var myCar = Car("A3", "Audi")
var myECar = ElectricCar("S-Model", "Tesla", 85.0)
myCar.drive(200.0)
// 매개변수의 갯수에 따라 다른 결과를 출력할 수 있다.
myECar.drive(200.0)
myECar.drive()
}
open
: 상속을 받을려면 부모클래스가 오픈키워드를 가지고 있어야한다.
- 또한 변수나 함수도 오버라이딩 하기 위해서는 open 키워드를 가지고 있어야한다.
sealed
: 해당 키워드를 가지고 있으면 상속할 수 없다.
- 해당 인자를 가지고 있으면 클래스가 객체로 인식된다.
인터페이스
인터페이스는 클래스가 서명할 수도 있는 계약서입니다.
모든 프로퍼니와 함수를 구현해줄 수 있지만, 꼭 할 필요는 없다.
클래스 내에서 사용하는 프로퍼티와 메서드를 미리 지정하는 타입이다.
interface Drivable{
val maxSpeed: Double
fun drive(): String
fun brake(){
println("The drivable is braking")
}
}
open class Car(override val maxSpeed: Double, val name: String, val: brand: String): Drivable{
open var range: Double = 0.0
open fun extendRange(amount: Double){
if(amount > 0)
range += amount
}
// override fun drive(): String = return "driving the interface drive"
override fun drive(): String {
return "driving the interface drive"
}
open fun drive(distance: Double){
println("Drove for $distance KM")
}
}
fun main(){
var audiA3 = Car(200.0, "A3", "Audi")
// 따로 함수를 생성하지 않았지만 interface에 있는 값이 나온다.
audiA3.brake()
}
구체적인 클래스를 제작하지 않고 구현하고 싶을 때 사용한다.
추상
키워드 abstract
을 사용하지 않으면 추상 클래스가 아니다.
추상 클래스와 메서드는 따로 구현하지 않는다.
abstract fun run()
구현하지 않고 추후 상속 받을 때 오버라이딩 한다.
추상클래스는 객체를 만들 수 없다.
오로지 상속을 통해 객체를 만들 수 있다.
타입 캐스팅
val stringList: List<String> = listOf("Denis","Frank", "Michael", "Garry")
val mixedTypeList: List<Any> = listOf("Denis", 31, 5, "BDay", 70.5)
listOf()
: 이걸 사용하면 리스트를 만들 수 있다.
Smart Cast: 자동으로 타입을 할당한다.
val obj1: Any = "I have a dream"
if (obj1 !is String) {
println("Not a String")
} else {
println("Found a String of length ${obj1.length}")
}
Explicit: as
키워드를 사용해서 타입 캐스팅을 한다.
val obj1: Any = "I have a dream"
val str1: String = obj1 as String
println(str1.length)
val obj2: Any = 1337
val str2: String = obj2 as String
println(str2) // Error
// str3을 nullable한 타입으로 지정하여 String이 아니면 null이 들어오도록 설정한다.
val obj3: Any = 1337
val str3: String? = obj3 as? String
println(str3)