OOP: 객체 지향 프로그래밍
1. 변수, 타입
2. 흐름 제어
3. 함수
4. 컬렉션
5. 클래스와 객체(상속 포함)
클래스: 데이터 타입 정의, 데이터 멤버와 메서드를 한곳에 저장, 가독성 좋은 코드
클래스: 차
properties: 최고속도, 바퀴, 사이즈, 색상 .. -> 변수
skills: drive, break .. -> 메서드
클래스: 속성과 기술을 정의해주는 청사진
클래스를 만든다 = 객체의 청사진을 만든다
객체: 생산된 차 한대
class Person constructor(_firstName: String, _lastName: String) { // or class Person constructor (_firstName: String, _lastName: String)
// Member Variables (Properties) of the class
var firstName: String
var lastName: String
// Initializer Block
init {
this.firstName = _firstName
this.lastName = _lastName
println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
}
}
// create an object like so:
// val denis = Person("Denis", "Panjuta")
// Alternatively:
class Person (_firstName: String, _lastName: String) {
// Member Variables (Properties) of the class
var firstName: String = _firstName
var lastName: String = _lastName
// Initializer Block
init {
println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
}
}
// Alternatively:
class Person(var firstName: String, var lastName: String) {
// Initializer Block
init {
println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
}
}
// Or even:
// whereby John and Doe will be default values
class Person(var firstName: String = "John", var lastName: String= "Doe") {
// Initializer Block
init {
println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
}
}
// Create an object:
/*
val john = Person()
val johnPeterson = Person(lastname: "Peterson")
*/
class Person(var firstName: String, var lastName: String) {
var age: Int? = null
var hobby: String = "Watch Netflix"
var myFirstName = firstName
// Secondary Constructor
constructor(firstName: String, lastName: String, age: Int): this(firstName, lastName) {
this.age = if(age > 0) age else throw IllegalArgumentException("Age must be greater than zero")
}
fun stateHobby(){
println("$firstname \'s Hobby is: $hobby'" )
}
}
// You can use primary or secondary Constructor to create an object
// Calls the primary constructor (Age will be null in this case)
val person1 = Person("Denis", "Panjuta")
// Calls the secondary constructor
val person2 = Person("Elon", "Musk", 48)
// Having multiple overloads:
class Person(var firstName: String, var lastName: String) {
var age: Int? = null
var eyeColor: String? = null
// Secondary Constructor
constructor(firstName: String, lastName: String, age: Int): this(firstName, lastName) {
this.age = if(age > 0) age else throw IllegalArgumentException("Age must be greater than zero")
}
// Secondary Constructor
constructor(firstName: String, lastName: String, age: Int, eyeColor: String):
this(firstName, lastName, age) {
this.eyeColor = eyeColor
}
}
// SETTERS AND GETTERS
// User class with a Primary constructor that accepts
// three parameters
class Car(_brand: String, _model: String, _maxSpeed: Int) {
// Properties of User class
val brand: String = _brand // Immutable (Read only)
var model: String = _model // Mutable
var maxSpeed: Int = _maxSpeed // Mutable
}
// Kotlin internally generates a default getter and setter for mutable properties, and a getter (only) for read-only properties.
It calls these getters and setters internally whenever
// you access or modify a property using the dot(.) notation.
This is how it would look like internally
class Car(_brand: String, _model: String, _maxSpeed: Int) {
val brand: String = _brand
get() = field
var model: String = _model
get() = field
set(value) {
field = value
}
var maxSpeed: Int = _maxSpeed
get() = field
set(value) {
field = value
}
}
// value
// We use value as the name of the setter parameter. This is the default convention in Kotlin but you’re free to use any other name if you want.
// The value parameter contains the value that a property is assigned to. For example, when you write user.name = "Elon Musk",
// the value parameter contains the assigned value "Elon Musk".
// 2. Backing Field (field)
// Backing field helps you refer to the property
// inside the getter and setter methods.
// This is required because if you use the property
// directly inside the getter or setter then you’ll
// run into a recursive call which will generate
// a StackOverflowError.
class Car() {
lateinit var owner : String
val myBrand: String = "BMW"
// Custom getter
get() {
return field.toLowerCase()
}
// default setter and getter
var myModel: String = "M5"
private set
var myMaxSpeed: Int = maxSpeed
get() = field
// Custom Setter
set(value) {
field = if(value > 0) value else throw IllegalArgumentException("_maxSpeed must be greater than zero")
}
init{
this.myModel = "M3"
this.owner = "Frank"
}
}
fun main() {
// soomin은 Person type의 변수
var soomin = Person("Soomin", "Kim", 25)
// 기본값으로 생성
var john = Person()
var johnPeterson = Person(lastName="Lee")
// MobilePhone 객체 생성
MobilePhone("Android", "Samsung", "Galaxy S20 Ultra")
MobilePhone()
MobilePhone("ios", "Apple", "iPhone 13")
soomin.hobby = "drink"
soomin.age = 26
println("I'm ${soomin.age} years old")
soomin.stateHobby()
john.hobby = "play video games"
john.stateHobby()
var myCar = Car()
println("brand is : ${myCar.myBrand}")
myCar.maxSpeed = 230
println("Maxspeed is ${myCar.maxSpeed}")
}
//constructor: 객체 생성 시, 값을 추가하게 해줌.
//변수를 만든다고도 볼 수 있음.
class Person constructor(_firstName: String = "John", _lastName: String = "Doe"){
// Properties & Methods
// 코드 실행하면 자동으로 실행되는 코드
// Member Variables - Properties
var age: Int? = null
var hobby: String? = "watch Netflix"
var firstName : String? = null
init {
this.firstName = _firstName
println("Person created" + "firstName=$firstName and lastName=$lastName")
}
// Member secondary Constructor
constructor(firstName: String, lastName: String, age: Int): this(firstName,lastName){
// constructor에서 넘어온 매개변수 age가, 이 클래스의 변수 age에 할당된
this.age = age
println("Person created" + "firstName=$firstName and lastName=$lastName and age=$age")
}
// Member functions - Methods. 클래스 안의 함수
fun stateHobby(){
println("$firstName's hobby is $hobby")
}
}
class MobilePhone constructor(osName: String = "Android", brand: String = "Samsung", model: String = "Galaxy S"){
init{
println("osName is $osName, brand is $brand, model is $model")
}
}
// 함수 안에서 변수가 선언됐으면, 함수 스코프 안에서만 변수 사용 가능.
fun myFunction(a: Int) { // a는 매개변수
// var a는 변수
var a = a
println("a is $a") // 여기서 print되는 a는 변수 a
}
class Car() {
// 나중에 초기화.
lateinit var owner: String
val myBrand: String = "BMW"
// Custom getter
get(){
return field.toLowerCase()
}
var maxSpeed: Int = 250
//아래 코드들 자동 실행
get() = field
set(value){
field= if(value>0) value else throw IllegalArgumentException("Max speed cannot be less than 0")
}
var myModel: String = "M5"
// private: Only available in class
private set // 이 클래스 안에서만 set을 할수있다.
init {
this.owner = "Frank"
}
//필드는 게터와 세터 메서드 안에서 프로퍼티 참조를 도와주며 뒷받침하는 필드
// 필수임. 만약 게터나 세터 안에 프로퍼티를 입력하면 반복되는 호출이 되어 스택 오버플로우 현상이 나타날 것.
// 그래서 위의 형태로 getter, setter 사용해야 되는 것.
// value는 필드에 할당되는 값.
}
package eu.tutorials.kotlinbasics
// 프로퍼티나 메서드 같은 특성을 다른 클래스에서 상속받거나 줄이게 해줌
// 다른 클래스 속성 상속받는 클래스: Sub class or Child class or Derived class
// 다른 클래스에 속성을 상속하는 클래스: Super class Parent Class or Base Class
//open class여야 상속할 수 있음.
// 모든 클래스는 자동으로 최종값. 즉 자동으로 상속할 수 없음.
// Super Class, Parent Class, Base Class
open class Vehicle{
//properties
//methods
}
//
// sealed class 로 하면 상속을 막을 수 있음.
// Sub Class, Child class or Derived class of Vehicle.
// Super Class, Parent Class, Base Class of ElectricCar
open class Car2(override val maxSpeed: Double, val name: String, val brand: String): Drivable{
open var range: Double = 0.0
fun extendRange(amount: Double) {
if (amount > 0) {
range += amount
}
}
// interface에서의 DRIVe
override fun drive(): String{
return "driving the interface drive"
}
open fun drive(distance: Double){
println("Drove for $distance km")
}
}
// Sub Class, Child class or Derived class of Car.
class ElectricCar(maxSpeed: Double, name: String, brand: String, batteryLife: Double): Car2(maxSpeed, name, brand){
var chargerType = "Type1"
// override range, drive functionality
override var range = batteryLife * 6
override fun drive(distance: Double){
println("Drove for $distance KM on electricity")
}
override fun drive(): String{
return "Drove for $range KM on electricity"
}
override fun brake(){
// super: 슈퍼 클래스의 brake 함수를 호출. (자동차 클래스에서는 brake 기능을 구현안했으니까)
super.brake()
println("brake inside of electronic car")
}
}
// Any class inherits from the Any Class -> 기본적인 함수들 이용할 수 있는 이
fun main(){
var audiA3 = Car2(200.0, "A3", "Audi")
var teslaS = ElectricCar(240.0,"S-Model", "Tesla", 85.0)
teslaS.chargerType = "Type2"
teslaS.drive(200.0)
// override drive를 호출할 것.(매개변수 없으니까)
teslaS.drive()
// Car, ElctricCar에는 구현 안되어있지만, 인터페이스에서 상속 받으니까 사용 가능
teslaS.brake()
audiA3.brake()
// Polymorphism(다형성): 비슷한 특성을 가진 객체들이 공통된 방법으로 여겨지는
// 전기차가 자동차의 서브 클래스이고, 자동차가 인터페이스가 되어 가능
audiA3.drive(200.0)
//ElectricCar는 drive 메소드가 없는데도, 상속받아 쓸 수 있음.
teslaS.extendRange(200.0)
}
//인터페이스는 클래스 기능을 확장하게 해줌.
interface Drivable{
val maxSpeed: Double
//함수 바디는 없고 함수 헤드만 있는 것.
fun drive(): String
fun brake() {
println("The drivable is braking")
}
//인터페이스는 모든 프로퍼티와 함수를 구현할 수 있지만, 그럴 필요는 없음.
//함수의 프로퍼티가 default implementation이 있으면 그 인터페이스를 이용하는 클래스가 오버라이딩 할수도 있음.
}
인터페이스: 기본적으로 클래스가 서명할 수도 있는 계약서
서명하면, 클래스는 인터페이스의 프로퍼티와 함수 구현을 제공할 의무를 가짐.
인터페이스는 다른 인터페이스에서 상속받을 수 있음.
즉 다른 인터페이스에서 상속 받는 고유 인터페이스를 만들 수 있음.
왜 인터페이스 사용?
나중에 구현하고 싶은 특정 함수와 클래스 프로퍼티가 있는데 바로 구현하고 싶지 않은 경우. 준비할 수 있음. 구체적인 함수 바디를 아직 안만들고 싶을 때.
package eu.tutorials.kotlinbasics
// Astract class는 예를 들 수 없음.(객체를 만들 수 없음)
// 하지만 Sub Class는 Abstract Class에서 상속할 수 있음.
// 추상 클래스의 멤버(프로퍼티, 메소드)는 직접 abstract 키워드를 쓰지 않는 이상, Non-abstract임.
abstract class Mammal(private val name: String, private val origin: String, private val weight: Double) { // Concrete(Non Abstract) Properties
//Abstract property (Must be overridden by Subclasses)
abstract var maxSpeed: Double
// Abstract Methods (Must be implemented by Subclasses)
// 즉 어떤 클래스가 Mammal을 상속받으면, 이 메서드를 구현해야 되는 것.
abstract fun run()
abstract fun breath()
//Concrete (Non Abstract)Method
fun displayDetails(){
println("Name: $name, Origin: $origin, Weight: $weight, " + "Max Spped: $maxSpeed")
}
}
//Mammal의 sub class
class Human(name: String, origin: String, weight: Double, override var maxSpeed: Double): Mammal(name, origin, weight){
override fun run(){
println("Runs on two legs")
}
override fun breath(){
println("Breath through mouth or nose")
}
}
fun main(){
// 상속 받은 클래스에서는 예시를 만들 수 있음.
val human = Human("Denis", "Russia", 70.0, 28.0)
human.run()
human.breath()
// 아래는 불가능 (추상 클래스의 예시를 만들 수 없으니까)
// val mammal = Mammal("Denis", "Russia", 70.0, 28.0)
}
인터페이스와 차이
인터페이스는 state를 저장할 수 없고, 여러 인터페이스를 구현할 수 있지만 클래스는 한개만 구현 가능. 생성자, 필드도 X
추상 클래스는 인터페이스가 추가할 수 있는건 다 할 수 있고, 추가적으로 필드와 생성자도 추가할 수 있음. state가 잘 저장될 수 있음.
package eu.tutorials.kotlinbasics
// Astract class는 예를 들 수 없음.(객체를 만들 수 없음)
// 하지만 Sub Class는 Abstract Class에서 상속할 수 있음.
// 추상 클래스의 멤버(프로퍼티, 메소드)는 직접 abstract 키워드를 쓰지 않는 이상, Non-abstract임.
abstract class Mammal(private val name: String, private val origin: String, private val weight: Double) { // Concrete(Non Abstract) Properties
//Abstract property (Must be overridden by Subclasses)
abstract var maxSpeed: Double
// Abstract Methods (Must be implemented by Subclasses)
// 즉 어떤 클래스가 Mammal을 상속받으면, 이 메서드를 구현해야 되는 것.
abstract fun run()
abstract fun breath()
//Concrete (Non Abstract)Method
fun displayDetails(){
println("Name: $name, Origin: $origin, Weight: $weight, " + "Max Spped: $maxSpeed")
}
}
//Mammal의 sub class
class Human(name: String, origin: String, weight: Double, override var maxSpeed: Double): Mammal(name, origin, weight){
override fun run(){
println("Runs on two legs")
}
override fun breath(){
println("Breath through mouth or nose")
}
}
fun main(){
// 상속 받은 클래스에서는 예시를 만들 수 있음.
val human = Human("Denis", "Russia", 70.0, 28.0)
human.run()
human.breath()
// 아래는 불가능 (추상 클래스의 예시를 만들 수 없으니까)
// val mammal = Mammal("Denis", "Russia", 70.0, 28.0)
}