1) Activity Lifecycle

  • 앱의 완성도안정성을 높이기 위해 반드시 알아야 함
    • 다른 앱으로 전환 시, 비정상 종료되는 문제
    • 사용자가 앱을 사용하지 않는데, 시스템 리소스가 소비되는 문제
    • 사용자가 앱을 나갔다가 돌아왔을 때, 진행상태가 저장되지 않는 문제
    • 화면이 가로 ↔ 세로 전환될 때, 비정상 종료되거나, 진행상태가 저장되지 않는 문제

콜백

1) onCreate

  • 필수적으로 구현해야 함
  • Activity 의 생명주기 중 한 번만 발생해야하는 로직을 실행
    • 멤버 변수 정의
    • UI 구성 ( setContentView, xml 레이아웃 파일 정의 )
  • saveInstanceState 매개 변수 수신 → Activity 이전 저장 상태가 포함된 Bundle 객체

 

 

2) onStart

  • Activity 가 사용자에게 표시됨
  • 앱은 Activity 를 포그라운드로 보내 상호작용할 수 있도록 준비

 

3) onResume

  • Activity 가 포그라운드에 표시되어, 사용자와 상호 작용 할 수 있는 상태
  • 앱에서 포커스가 떠날 때 까지 onResume 상태에 머무름

 

4) onPause

  • 사용자가 활동을 떠나는 첫 번째 신호
  • 매우 짧음
  • 활동이 포그라운드에 있지 않지만, 잠시 후 다시 시작할 작업을 일시중지 하거나 조정
  • ex) 반투명 Activity 가 띄워져 포커스는 없지만 화면에 보이는 상태
  • 이 상태를 통해서, 실행중이지 않을 때 필요하지 않은 리소스를 해지할 수 있음
  • 이 상태에서, 데이터를 저장하거나, 네트워크 호출, DB의 IO 작업을 하면 안 됨
    • 매우 짧은 시간이라 메서드가 끝나기 전에 Activity 가 종료될 수 있음

 

5) onStop

  • Activity 가 사용자에게 더 이상 표시 되지 않는 상태
  • CPU 를 비교적 많이 소모하는 종료 작업을 실행해야 함
    • DB 저장
  • Activity 가 중단되면, Android OS 에서 리소스 관리를 위해, 해당 Activity 가 포함된 프로세스를 소멸시킬 수 있음

 

6) onDestroy

  • Activity 가 완전히 종료되기 전에 실행
  • 호출되는 케이스
    • finish 메서드가 호출 되어 Activity 가 종료될 때
    • configurationChange (ex 기기 회전, 멀티 윈도우) 로 인해, 시스템이 Activity 를 일시적으로 소멸시킬 때

 

 

2) Activity LifeCycle Image

출처: https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko

 

활동 수명 주기  |  Android Developers

활동은 사용자가 전화 걸기, 사진 찍기, 이메일 보내기 또는 지도 보기와 같은 작업을 하기 위해 상호작용할 수 있는 화면을 제공하는 애플리케이션 구성요소입니다. 각 활동에는 사용자 인터페

developer.android.com

 

 


[ 생명주기 추가 설명]

 

[1] OnCreate

onCreate, onStart 는 콜백이 호출된 다음

그 상태에 머무는 것이 아니라

바로 그 다음 상태로 전환이 된다.

 

onCreate 가 불리고 멈춰있는 상태는 없고

onStart 가 불리고 멈춰있는 상태도 없다.

 

onCreate가 불린 후에는

바로 onStart 가 불린다.

 

 

UI 구성

Activity 생명주기 중 한 번만 그리면 됨

 

SetContentView

화면을 그리기 위한 내용

 

SaveInstanceState

이전 저장 상태를 포함한다고 했으므로

 

1) Activity 가 최초로 한 번만 실행되었을 때는 값은 NULL 임

이전 저장상태가 없기 때문

 

2) 이전 저장 상태를 저장할 필요가 없다고 하면

매번 다시 실행되므로 값은 늘 NULL

 

 

 

[3] OnResume

onResume 같은 경우는 이 상태에 머물러있다.

onCreate, onStart 는 스쳐지나가는 상태라면

onResume 는 머무르는 상태라는 특징이 있다.

 

사용자와 상호작용 할 수 있는 상태

화면이 눈 앞에 보이는 상태 화면에서 클릭할 수 있고,

키보드 칠 수 있고,

Activity 에서 표현되는 데이터도 볼 수 있는 상태

 

 

[4] OnPause

onPause 상태에 머무는 경우는 거의 없고

skip 해서 지나가는 상태 중 하나

 

 

사용자가 활동을 떠나는 첫 번째 신호

사용자가 이 Activity를 통해서

뭔가 할 수 있는 상호작용이 없는 상태가 되는

트리거 같은 콜백

 

리스닝을 하거나

리소스를 사용할 필요가 없으므로

리소스를 해지하는 작업을 하면 됨

 
활동이 포그라운드에 있지 않지만,
잠시 후 다시 시작할 작업을 일시중지 하거나 조정
 

Activity 가 지금 당장 사용자 눈 앞에 보이지 않을지언정

재시작할 수 있는 상태

 

Activity 가 있는데, 그 위에 반투명한 Activity가 띄워지게 되면

화면이 아예 없어지는 것은 아님

 

위는 onPause 상태로 머무르는 케이스이다.

 

 

매우 짧은 시간 이라 메서드가 끝나기 전에 

Activity가 종료될 수 있음

매우 짧은 시간이기 때문에

이때 호출된 메서드가

반드시 끝까지 호출될 수 있음을 보장할 수 없음

 

 

[5] OnStop

앱을 실행하고 있다가

홈 버튼을 눌러서

해당 앱을 종료하지 않고 잠깐 백스택에 두는 경우

 

해당 Activity는 사용자 눈앞에 보이지도 않고

상호작용하는 상태도 아님

Activity 가 Running 되는 상태도 아님

 

onPause 상태를 거쳐서

onStop 상태가 된다.

 

 

Activity 가 중단되면, Android OS 에서 리소스 관리를 위해, 
해당 Activity 가 포함된 프로세스를 소멸시킬 수 있음

안드로이드 OS 입장에서는

지금 사용자가 당장 하고자 하는 동작

지금 당장 사용하고자 하는 앱이

우선순위가 높다.

 

1) 실행한 지 시간이 오래 지났거나

2) 리소스를 많이 사용하고 있는 상태에서는

사용자와 인터랙션이 없는 Activity 를 유지할 이유가 없다.

 

그래서 onStop 상태일 때

안드로이드 OS 관점에서

리소스를 계속 사용할 필요가 없다고 판단되는 시점이 오면

프로세스를 kill 한다.

 

onStop 상태에서

1) 프로세스를 kill 하지 않은 상태에서

다시 실행하게 되면

onRestart, onStart, onResume 가 호출이 됨

 

2) 프로세스를 kill 했다면

다시 onCreate 부터 실행

 

 

[6] OnDestroy

멀티 윈도우

원래 Android OS 에서 멀티윈도우를 지원했던 것은 아님

 

나중에 폴더블 폰이나 화면이 큰 것이 나오니까

여러개의 Activity와 여러 개의 앱을

동시에 실행할 수 있는 케이스가 나오면서

멀티 윈도우를 고려한 Life Cycle 이 추가가 되었음

 

처음에 Activity 의 Life Cycle 을 공부할 때는

멀티 윈도우의 생명주기는 제외하고

학습하는 것을 권장함


 

 

[ 생명주기 부연설명 ]

  • Activity가 최초 실행되었을 때,

onCreate(), onStart(), onResume() 가 호출됨.

 

  • onResume() 가 실행되면

실제로 사용자가 앱이 상호작용할 수 있는

Activity running 상태가 된다.

 

 

  • onPause() 상태는 잠깐 일시정지 상태로

다른 Activity가 사용자에게 보여지려고 하는 시점에

원래 보고 있던 것이 onPause() 상태가 되고,

다른 Activity 가 아예 안 보이게 되는 시점에는

onStop() 까지 호출된다.

 

 

  • onStop() 상태에서도

해당 Activity 를 종료하겠다고 명시적으로 표현을 하거나,

configuration change 처럼 화면이 전환되거나,

앱이 아예 꺼져버린다면

onDestroy() 가 호출 되어서 모두 종료가 된다.

 

 

  • 대신 onStop() 상태에서 다시 실행을 했을 때,

OS 에 의해서 해당 Activity 를 포함한 프로세스가 kill 되지 않았다면,

onRestart() 가 불린 다음에,

onStart(), onResume() 가 호출이 되어서

다시 Activity 가 실행된다.

 

 

  • OS 에 의해서 리소스 최적화를 위해서

Activity 를 가지고 있는 프로세스가 kill 이 되었다면,

다시 처음부터 onCreate() 부터 실행된다.

콜백이 언제 이루어지고, 언제 어떤 동작을 해야하는지 살펴보자.

 

1) 앱 구성요소 ( 4가지 )

Android 앱의 필수적인 요소로, 각각은 시스템이나 사용자가 앱에 들어올 수 있는 진입점

Activity, Service, BroadcastReceiver, ContentProvider 이렇게 4가지가 있다.

 

1-1) Activity

  • 앱과 사용자가 상호작용을 하기 위한 진입점
    • 앱을 실행할 때는 앱을 전체적으로 호출하는 것이 아니라 앱의 액티비티를 호출
  • 모든 앱에 반드시 1개 이상 존재
  • Activity 는 사용자와 상호작용을 위한 UI 가 있음
    • 앱이 실행되면, 화면이 표시됨
    • 사용자의 입력값을 받음 ( 화면 클릭, 더블 클릭, 롱클릭, 스와이프, 드래그 앤 드랍 등 )
    • 사용자에게 제공하고자 하는 내용을 화면에 표시함
  • Lifecycle 이 있음

출처: https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko

 

활동 수명 주기  |  Android Developers

활동은 사용자가 전화 걸기, 사진 찍기, 이메일 보내기 또는 지도 보기와 같은 작업을 하기 위해 상호작용할 수 있는 화면을 제공하는 애플리케이션 구성요소입니다. 각 활동에는 사용자 인터페

developer.android.com

 

 

1-2) Service

  • 백그라운드에서 오래 실행되는 작업 수행을 위한 컴포넌트
  • 사용자가 다른 앱으로 전환하더라도 백그라운드에서 계속 실행
  • UI 없음
  • 종류: 포그라운드 서비스, 백그라운드 서비스, 바인드 서비스
  • 포그라운드 서비스 : 사용자에게 잘 보이는 작업. 포그라운드 서비스의 경우, 반드시 알림을 표시해야하며, 사용자가 앱과 상호작용하지 않을 때도 계속 실행됨
  • 백그라운드 서비스: 사용자에게 직접 보이지 않는 작업
    • ex) 사용자에게 보이지 않는 작업 ( 저장소 압축, 게임 업데이트, 파일 압축 등 )
    • 앱이 API 레벨 26 이상을 대상으로 할 경우
      • 즉시 실행해야 하는 작업 : Work Manager
      • 지연 작업 : Alarm Manager
  • 바인드 서비스 : 앱 컴포넌트가 bindService 를 호출해 서비스를 호출하면, 서비스가 바인딩 된 서비스는 클라이언트-서버 인터페이스를 제공해 서비스와 상호작용 함. 여러 개가 한꺼번에 바인딩 될 수 있고, 바인딩 된 컴포넌트가 모두 종료되면, 서비스도 종료됨.

 

출처: https://developer.android.com/develop/background-work/services?hl=ko

 

서비스 개요  |  Background work  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 서비스 개요 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Service는 백그라운드에서 장기 실행 작업

developer.android.com

 

 

 

foreground/backgroundService 서비스를 실행할 때,

startService() 로 실행 서비스를 종료할 때,

onDestroy() 를 명시적으로 호출해야 함

 

bindService 서비스를 실행할 때,

bindService() 로 실행 서비스를 종료할 때,

바인딩을 요청한 클라이언트들이 모두 사라지면

unbinding 이 되는 상태이므로 서비스는 종료됨

 

예) Activity A 와 Acitivity B 가 서비스 C를 바인드 했을 때

Activity A 와 Activity B 가 종료되면, 더 이상 바인딩할 서비스가 없어진다.

클라이언트 자체가 없어지기 때문.

 

 

1-3) BroadcastReceiver

  • 안드로이드 OS에서 발생하는 이벤트와 정보를 앱에서 수신할 수 있도록 하는 구성요소
  • UI 없음
  • 예시
    • 화면이 꺼졌거나
    • 배터리가 부족하거나
    • 사진을 캡처했거나

 

[부연설명]

Broadcast ( 방송 ) + Receiver ( 수신자 ) 두 개 단어의 합성어 뜻 그대로 가지고 있다.

방송을 듣는 사람.

안드로이드 OS 에서는 다양한 이벤트가 발생하는데, 이때 이 정보를 특정 앱에서 알아야하는 케이스들이 있다.

음악 앱이라면 이어폰이 꽂혔다가 이어폰이 해제가 된 경우, 음악재생이 중지되는 것이 기대됨

이런 이벤트를 캐치해서 처리

 

 

1-4) ContentProvider

  • 파일 시스템, SQLite 데이터베이스, 웹상이나 앱이 액세스할 수 있는 다른 모든 영구 저장장치에 저장 가능한 앱 데이터의 공유형 집합을 관리
  • 다른 앱은 콘텐츠 프로바이더를 통해 해당 데이터를 질의하거나 수정할 수 있음
  • 예시
    • 연락처 정보
    • 갤러리 이미지 / 비디오

[부연설명]

Content ( 내용 ) + Provider ( 제공자 ) 내용을 제공하는 사람

디바이스에 저장되어있는 모든 데이터에 접근할 수 있는 것을 관리한다.

연락처 정보를 가져온다거나,

갤러리에 저장된 이미지를 가져오는 기능은,

앱 내가 아닌 디바이스에 저장된 정보를 가져오는 것

 

2) Manifest

  • 앱의 필수적인 정보를 담고 있는 파일
    • 앱의 패키지 이름
    • 앱의 구성요소
    • 권한
    • 필요한 기능

[부연설명]

Manifest 의 사전적 의미는 명사로는 화물 목록, 승객 목록 동사로는 나타내다.

두 가지를 섞어서 생각해보면, Manifest 는 앱의 필수적인 정보를 담고 있다.

정보를 나열한다는 관점에서는 ”화물 목록”으로 볼 수 있고

앱의 성격이나 특징을 나타낸다는 관점에서는 동사 뜻인 “나타내다”를 잘 반영한다.

앱의 패키지명이라고 하면, ID 같은 고유한 값

앱의 구성요소는 4가지 컴포넌트들이다. 반드시 Manifest 파일에 선언이 되어야 사용이 가능하다.

예) Activity 파일을 만들어 놓고, Manifest 파일에 Activity 를 선언하지 않으면, 해당 Activity를 호출할 수 없다.

4대 컴포넌트에 대해서는 Manifest 에 해당 내용이 적혀 있어야 한다.

특정 앱애서는 앱을 사용하기 위해서는 이런 권한이 필요합니다. 알림이 뜨는 것을 볼 수 있다. 지도 앱이면, 위치정보 접근 배달 앱이면, 리뷰 작성 시, 사진 접근 등의 권한이 필요하다.

Manifest 파일만 보고도 대략적으로 이 앱이 어떤 앱인지 알 수 있다.

 

 

3) Intent

  • 구성요소 간의 통신을 할 수 있게 하는 역할
  • 앱에 포함된 구성요소 이외에, 다른 앱의 구성요소와도 통신할 수 있음
    • 명시적 인텐트 : 특정 컴포넌트, 액티비티를 명확히 특정해 실행할 경우
      • 예시) A_Activity 에서 B_Activity 실행을 호출할 경우
    • 암시적 인텐트: 동작을 특정하긴 했지만, 실행될 대상이 달라질 수 있는 경우
      • 예시) 특정 URL 을 실행이라는 액션을 요청한 경우, 웹브라우저 기능을 가진 다수의 앱이 호출될 수 있는 경우

[부연설명]

Intent 는 “의도”라는 뜻을 가지고 있다.

’어떤 액션을 취하겠다’는 “액션 정보”를 담고 있기도 하고, ”데이터” 를 담고 있어서

각 구성요소 간에 이런 데이터를 주고 받아야 한다. 특정 액션을 취해야 한다.

Intent에 위와 같은 내용들이 담겨있다.

 

 

1) Android OS

  • Linux 기반의 운영체제
  • 오픈소스 기반
  • 스마트폰 OS 의 약 70%를 차지함
  • 스마트폰 뿐만 아니라 웨어러블 디바이스, TV, 자동차 등 다양한 플랫폼에서 동작할 수 있는 OS (중요)
  • 버저닝이 디저트 이름으로 되어 있음

 

1-1) 세부 설명

  • 스마트폰 OS 의 약 70%를 차지함

스튜디오 설치 시, SDK (Software Development Kit) 를 설치하는데 개발자가 개발하기를 쉽게할 수 있는 기능을 제공함

Provider 가 만드는 것을 쉽게 함으로써 좀 더 많은 Provider 를 이끌어 왔다.

 

 

  • Android OS 는 이식성이 좋다.

iOS 는 애플 제품에만 사용 가능하지만 Android 는 화웨이, 샤오미, 삼성, 엘지 에 사용할 수 있으므로 범용적이다.

Android OS = 앱 개발 은 아니다. 앱을 사용하는 모바일 폰 뿐만 아니라 다양한 플랫폼에서 동작 가능하다.

큰 화면에 대응할 수 있다. 자동차, 웨어러블 디바이스, TV, Chrome OS 에 사용 가능.

 

 

  • Google

처음에 구글에서 Android OS 를 만든 것은 아니고, Android OS 를 만드는 회사를 Google 이 인수했다.

 

 

  • iOS 와 Android OS

iOS 공부할까, Android OS 를 공부할까 고민을 한다면 위 지표들을 참고하는 게 좋다.

 

1) Data Class

데이터를 담기 위한 클래스

  • toString(), hashCode(), equals(), copy() 메서드를 자동으로 생성
  • override 하면, 직접 구현한 코드를 사용
  • 1개 이상의 property가 있어야 함
  • 데이터 클래스는 abstract, open, sealed, inner 를 붙일 수 없음
  • 상속이 불가능

 

1-1) 코틀린/자바 코드 비교

fun main() {

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

class Cat() // 클래스는 property가 없어도 괜찮음

코틀린 코드를 자바 코드로 바꿔보기 Tools > Kotlin > Show Kotlin Bytecode > Decompile

코드의 일부를 살펴보면,

Person 은 그냥 클래스로 구현되었으므로 Get 메서드, 생성자가 있다.

// Person.java
public final class Person {
   @NotNull
   private final String name;
   private final int age;
   public static final int $stable;

   public Person(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }
}

 

 

Dog 데이터 클래스는,

copy(), toString(), hashCode(), equals() 4개의 함수가 자동생성된 것을 확인할 수 있음

  • copy() 값 복사할 때
  • toString() Dog 클래스 내부 값 확인을 할 때, 그냥 Class에서 toString() 하면 해당 객체의 주소값을 가져옴
  • toString() : 클래스 내부 데이터를 볼 수 있도록 구현됨
  • hashCode()
  • equals() 동등성 비교할 때
// Dog.java
public final class Dog {
   @NotNull
   public final Dog copy(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new Dog(name, age);
   }

   @NotNull
   public String toString() {
      return "Dog(name=" + this.name + ", age=" + this.age + ')';
   }

   public int hashCode() {
      int result = this.name.hashCode();
      result = result * 31 + Integer.hashCode(this.age);
      return result;
   }

   public boolean equals(@Nullable Object other) {
      if (this == other) {
         return true;
      } else if (!(other instanceof Dog)) {
         return false;
      } else {
         Dog var2 = (Dog)other;
         if (!Intrinsics.areEqual(this.name, var2.name)) {
            return false;
         } else {
            return this.age == var2.age;
         }
      }
   }
}

1-2) toString() 출력

fun main() {
    val person = Person("주연", 11)
    val dog = Dog("삽살개", 2)

    println(person.toString())
    println(dog.toString())

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

Person 에는 아무 것도 하지 않아서 참조 주소값이 출력됨

Dog 는 데이터 클래스로 내부 데이터를 볼 수 있는 함수가 생성되었으므로, 내부 데이터가 출력됨


1-3) toString 메서드 override 해서 직접 구현

fun main() {
    val person = Person("주연", 11)
    val dog = Dog("삽살개", 2)

    println(person.toString())
    println(dog.toString())

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
) {
    override fun toString(): String  {
        return "Dog 직접 구현: $name $age"
    }
}

 


1-4) copy 메서드로 특정 값만 변경

fun main() {
    val dog = Dog("삽살개", 2)
    println(dog.copy(age = 3)) // age 만 변경된 객체를 얻을 수 있음

}

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

1-5) 데이터 클래스는 상속 불가능

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

data class Corgi (
    val cute : Boolean = true
) : Dog()

데이터 클래스가 다른 클래스를 상속받았을 때 4가지 자동 생성된 메서드를 어떻게 생성해야할 지 알 수 없기 때문에

데이터 클래스는 상속이 불가능하다.


2) Sealed Class

추상 클래스로, 상속받은 자식 클래스의 종류를 제한

  • 컴파일러가 sealed 클래스의 자식 클래스가 어떤 것인지 앎
  • when과 함께 쓰일 때, 장점을 느낄 수 있음

Cat 추상 클래스를 상속받은 클래스는 BlueCat, RedCat, GreenCat 3개 뿐이므로 when 절 안에서 else 에 들어갈 case가 없다.

컴파일러는 Cat 을 상속받은 애들이 누군지 모른다.

fun main() {
    val cat : Cat = BlueCat()

    val result = when(cat) {
        is BlueCat -> "blue"
        is RedCat -> "red"
        is GreenCat -> "green"
        else -> "none"
    }

    // 상속받은 Cat 의 색깔을 출력
    println(result)
}

// sealed class
abstract class Cat
class BlueCat : Cat()
class RedCat : Cat()
class GreenCat : Cat()

여기서 abstract class 를 sealed class 로 변경하면, sealed class Cat

else 구문이 redundant 하다고 알려준다. 컴파일러가 Cat 이 어떤 자식을 가지고 있는지 알게된다. else branch 가 필요없어지는 것

  1. Recycler View 에서 View 타입 체크하거나
  2. 에러 체크할 때 유용함
fun main() {
    val cat : Cat = BlueCat()

    val result = when(cat) {
        is BlueCat -> "blue"
        is RedCat -> "red"
        is GreenCat -> "green"
        else -> "none"
    }

    // 상속받은 Cat 의 색깔을 출력
    println(result)
}

// sealed class
sealed class Cat
class BlueCat : Cat()
class RedCat : Cat()
class GreenCat : Cat()

예를 들어, 위 코드에서 class WhiteCat : Cat() 을 추가한다면 (흰 고양이 추가)

컴파일러가 Cat이 어떤 자식을 가지고 있는지 아므로, is WhiteCat → {} 구문이 빠졌다는 것을 알려줌

컴파일러 단계에서 컴파일 에러를 뱉어주기 때문에 실수를 줄일 수 있다.

else 문으로 모호하게 WhiteCat을 처리하는 것이 아니라 명시적으로 처리할 수 있음


3) Class 배경지식

Java 에서도 데이터를 holding 하기 위해 클래스를 많이 만든다.

네트워크 통신을 통해서 서버의 내용을 받는다면,

서버의 response를 그냥 가지고 있는게 아니고, parsing 해서

원하는 데이터를 담을 수 있는 클래스에 넣는 경우가 많음

[목차]

[내용]

1) Data Class

데이터를 담기 위한 클래스

  • toString(), hashCode(), equals(), copy() 메서드를 자동으로 생성
  • override 하면, 직접 구현한 코드를 사용
  • 1개 이상의 property가 있어야 함
  • 데이터 클래스는 abstract, open, sealed, inner 를 붙일 수 없음
  • 상속이 불가능

1-1) 코틀린/자바 코드 비교

fun main() {

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

class Cat() // 클래스는 property가 없어도 괜찮음

코틀린 코드를 자바 코드로 바꿔보기 Tools > Kotlin > Show Kotlin Bytecode > Decompile

코드의 일부를 살펴보면,

Person 은 그냥 클래스로 구현되었으므로 Get 메서드, 생성자가 있다.

// Person.java
public final class Person {
   @NotNull
   private final String name;
   private final int age;
   public static final int $stable;

   public Person(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }
}

Dog 데이터 클래스는,

copy(), toString(), hashCode(), equals() 4개의 함수가 자동생성된 것을 확인할 수 있음

  • copy() 값 복사할 때
  • toString() Dog 클래스 내부 값 확인을 할 때, 그냥 Class에서 toString() 하면 해당 객체의 주소값을 가져옴 toString() : 클래스 내부 데이터를 볼 수 있도록 구현됨
  • hashCode()
  • equals() 동등성 비교할 때
// Dog.java
public final class Dog {
   @NotNull
   public final Dog copy(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new Dog(name, age);
   }

   @NotNull
   public String toString() {
      return "Dog(name=" + this.name + ", age=" + this.age + ')';
   }

   public int hashCode() {
      int result = this.name.hashCode();
      result = result * 31 + Integer.hashCode(this.age);
      return result;
   }

   public boolean equals(@Nullable Object other) {
      if (this == other) {
         return true;
      } else if (!(other instanceof Dog)) {
         return false;
      } else {
         Dog var2 = (Dog)other;
         if (!Intrinsics.areEqual(this.name, var2.name)) {
            return false;
         } else {
            return this.age == var2.age;
         }
      }
   }
}

1-2) toString() 출력

fun main() {
    val person = Person("주연", 11)
    val dog = Dog("삽살개", 2)

    println(person.toString())
    println(dog.toString())

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

 

 

Person 에는 아무 것도 하지 않아서 참조 주소값이 출력됨

Dog 는 데이터 클래스로 내부 데이터를 볼 수 있는 함수가 생성되었으므로, 내부 데이터가 출력됨


1-3) toString 메서드 override 해서 직접 구현

fun main() {
    val person = Person("주연", 11)
    val dog = Dog("삽살개", 2)

    println(person.toString())
    println(dog.toString())

}

class Person (
    val name: String,
    val age: Int,
)

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
) {
    override fun toString(): String  {
        return "Dog 직접 구현: $name $age"
    }
}

 


1-4) copy 메서드로 특정 값만 변경

fun main() {
    val dog = Dog("삽살개", 2)
    println(dog.copy(age = 3)) // age 만 변경된 객체를 얻을 수 있음

}

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)


1-5) 데이터 클래스는 상속 불가능

data class Dog (
    // 반드시 하나 이상의 property 필요
    val name: String,
    val age: Int,
)

data class Corgi (
    val cute : Boolean = true
) : Dog()

데이터 클래스가 다른 클래스를 상속받았을 때

4가지 자동 생성된 메서드를 어떻게 생성해야할 지 알 수 없기 때문에

데이터 클래스는 상속이 불가능하다.


2) Sealed Class

추상 클래스로, 상속받은 자식 클래스의 종류를 제한

  • 컴파일러가 sealed 클래스의 자식 클래스가 어떤 것인지 앎
  • when과 함께 쓰일 때, 장점을 느낄 수 있음

Cat 추상 클래스를 상속받은 클래스는 BlueCat, RedCat, GreenCat 3개 뿐이므로 when 절 안에서 else 에 들어갈 case가 없다.

컴파일러는 Cat 을 상속받은 애들이 누군지 모른다.

fun main() {
    val cat : Cat = BlueCat()

    val result = when(cat) {
        is BlueCat -> "blue"
        is RedCat -> "red"
        is GreenCat -> "green"
        else -> "none"
    }

    // 상속받은 Cat 의 색깔을 출력
    println(result)
}

// sealed class
abstract class Cat
class BlueCat : Cat()
class RedCat : Cat()
class GreenCat : Cat()

여기서 abstract class 를 sealed class 로 변경하면, sealed class Cat

else 구문이 redundant 하다고 알려준다. 컴파일러가 Cat 이 어떤 자식을 가지고 있는지 알게된다. else branch 가 필요없어지는 것

  1. Recycler View 에서 View 타입 체크하거나
  2. 에러 체크할 때 유용함

fun main() {
    val cat : Cat = BlueCat()

    val result = when(cat) {
        is BlueCat -> "blue"
        is RedCat -> "red"
        is GreenCat -> "green"
        else -> "none"
    }

    // 상속받은 Cat 의 색깔을 출력
    println(result)
}

// sealed class
sealed class Cat
class BlueCat : Cat()
class RedCat : Cat()
class GreenCat : Cat()

예를 들어, 위 코드에서 class WhiteCat : Cat() 을 추가한다면 (흰 고양이 추가)

 

컴파일러가 Cat이 어떤 자식을 가지고 있는지 아므로,

is WhiteCat → {} 구문이 빠졌다는 것을 알려줌

컴파일러 단계에서 컴파일 에러를 뱉어주기 때문에 실수를 줄일 수 있다.

else 문으로 모호하게 WhiteCat을 처리하는 것이 아니라 명시적으로 처리할 수 있음


3) Class 배경지식

Java 에서도 데이터를 holding 하기 위해 클래스를 많이 만든다.

네트워크 통신을 통해서 서버의 내용을 받는다면,

서버의 response를 그냥 가지고 있는게 아니고, parsing 해서

원하는 데이터를 담을 수 있는 클래스에 넣는 경우가 많음

 

 

1) 앱 구성요소 ( 4가지 )

Android 앱의 필수적인 요소로, 각각은 시스템이나 사용자가 앱에 들어올 수 있는 진입점

Activity, Service, BroadcastReceiver, ContentProvider 이렇게 4가지가 있다.

 

 

1-1) Activity

  • 앱과 사용자가 상호작용을 하기 위한 진입점
    • 앱을 실행할 때는 앱을 전체적으로 호출하는 것이 아니라 앱의 액티비티를 호출
  • 모든 앱에 반드시 1개 이상 존재
  • Activity 는 사용자와 상호작용을 위한 UI 가 있음
    • 앱이 실행되면, 화면이 표시됨
    • 사용자의 입력값을 받음 ( 화면 클릭, 더블 클릭, 롱클릭, 스와이프, 드래그 앤 드랍 등 )
    • 사용자에게 제공하고자 하는 내용을 화면에 표시함
  • Lifecycle 이 있음

출처: https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko

 

 

1-2) Service

  • 백그라운드에서 오래 실행되는 작업 수행을 위한 컴포넌트
  • 사용자가 다른 앱으로 전환하더라도 백그라운드에서 계속 실행
  • UI 없음
  • 종류: 포그라운드 서비스, 백그라운드 서비스, 바인드 서비스
  • 포그라운드 서비스 : 사용자에게 잘 보이는 작업. 포그라운드 서비스의 경우, 반드시 알림을 표시해야하며, 사용자가 앱과 상호작용하지 않을 때도 계속 실행됨
  • 백그라운드 서비스: 사용자에게 직접 보이지 않는 작업
    • ex) 사용자에게 보이지 않는 작업 ( 저장소 압축, 게임 업데이트, 파일 압축 등 )
    • 앱이 API 레벨 26 이상을 대상으로 할 경우
      • 즉시 실행해야 하는 작업 : Work Manager
      • 지연 작업 : Alarm Manager
  • 바인드 서비스 : 앱 컴포넌트가 bindService 를 호출해 서비스를 호출하면,
  • 서비스가 바인딩 된 서비스는 클라이언트-서버 인터페이스를 제공해 서비스와 상호작용 함.
  • 여러 개가 한꺼번에 바인딩 될 수 있고, 바인딩 된 컴포넌트가 모두 종료되면, 서비스도 종료됨.
  •  

 

출처: https://developer.android.com/develop/background-work/services?hl=ko

foreground/backgroundService 서비스를 실행할 때, startService() 로 실행 서비스를 종료할 때, onDestroy() 를 명시적으로 호출해야 함

bindService 서비스를 실행할 때, bindService() 로 실행 서비스를 종료할 때, 바인딩을 요청한 클라이언트들이 모두 사라지면 unbinding 이 되는 상태이므로 서비스는 종료됨

예) Activity A 와 Acitivity B 가 서비스 C를 바인드 했을 때 Activity A 와 Activity B 가 종료되면, 더 이상 바인딩할 서비스가 없어진다. 클라이언트 자체가 없어지기 때문.


1-3) BroadcastReceiver

  • 안드로이드 OS에서 발생하는 이벤트와 정보를 앱에서 수신할 수 있도록 하는 구성요소
  • UI 없음
  • 예시
    • 화면이 꺼졌거나
    • 배터리가 부족하거나
    • 사진을 캡처했거나

[부연설명]

Broadcast ( 방송 ) + Receiver ( 수신자 ) 두 개 단어의 합성어 뜻 그대로 가지고 있다.

방송을 듣는 사람.

안드로이드 OS 에서는 다양한 이벤트가 발생하는데, 이때 이 정보를 특정 앱에서 알아야하는 케이스들이 있다.

음악 앱이라면 이어폰이 꽂혔다가 이어폰이 해제가 된 경우, 음악재생이 중지되는 것이 기대됨

이런 이벤트를 캐치해서 처리


1-4) ContentProvider

  • 파일 시스템, SQLite 데이터베이스, 웹상이나 앱이 액세스할 수 있는 다른 모든 영구 저장장치에 저장 가능한 앱 데이터의 공유형 집합을 관리
  • 다른 앱은 콘텐츠 프로바이더를 통해 해당 데이터를 질의하거나 수정할 수 있음
  • 예시
    • 연락처 정보
    • 갤러리 이미지 / 비디오

[부연설명]

Content ( 내용 ) + Provider ( 제공자 ) 내용을 제공하는 사람

디바이스에 저장되어있는 모든 데이터에 접근할 수 있는 것을 관리한다.

연락처 정보를 가져온다거나,

갤러리에 저장된 이미지를 가져오는 기능은,

앱 내가 아닌 디바이스에 저장된 정보를 가져오는 것


2) Manifest

  • 앱의 필수적인 정보를 담고 있는 파일
    • 앱의 패키지 이름
    • 앱의 구성요소
    • 권한
    • 필요한 기능

[부연설명]

Manifest 의 사전적 의미는 명사로는 화물 목록, 승객 목록 동사로는 나타내다.

두 가지를 섞어서 생각해보면, Manifest 는 앱의 필수적인 정보를 담고 있다.

정보를 나열한다는 관점에서는 ”화물 목록”으로 볼 수 있고

앱의 성격이나 특징을 나타낸다는 관점에서는 동사 뜻인 “나타내다”를 잘 반영한다.

앱의 패키지명이라고 하면, ID 같은 고유한 값

앱의 구성요소는 4가지 컴포넌트들이다. 반드시 Manifest 파일에 선언이 되어야 사용이 가능하다.

예) Activity 파일을 만들어 놓고, Manifest 파일에 Activity 를 선언하지 않으면, 해당 Activity를 호출할 수 없다.

4대 컴포넌트에 대해서는 Manifest 에 해당 내용이 적혀 있어야 한다.

특정 앱애서는 앱을 사용하기 위해서는 이런 권한이 필요합니다. 알림이 뜨는 것을 볼 수 있다. 지도 앱이면, 위치정보 접근 배달 앱이면, 리뷰 작성 시, 사진 접근 등의 권한이 필요하다.

Manifest 파일만 보고도 대략적으로 이 앱이 어떤 앱인지 알 수 있다.


3) Intent

  • 구성요소 간의 통신을 할 수 있게 하는 역할
  • 앱에 포함된 구성요소 이외에, 다른 앱의 구성요소와도 통신할 수 있음
    • 명시적 인텐트 : 특정 컴포넌트, 액티비티를 명확히 특정해 실행할 경우
      • 예시) A_Activity 에서 B_Activity 실행을 호출할 경우
    • 암시적 인텐트: 동작을 특정하긴 했지만, 실행될 대상이 달라질 수 있는 경우
      • 예시) 특정 URL 을 실행이라는 액션을 요청한 경우, 웹브라우저 기능을 가진 다수의 앱이 호출될 수 있는 경우

[부연설명]

Intent 는 “의도”라는 뜻을 가지고 있다.

’어떤 액션을 취하겠다’는 “액션 정보”를 담고 있기도 하고, ”데이터” 를 담고 있어서

각 구성요소 간에 이런 데이터를 주고 받아야 한다. 특정 액션을 취해야 한다. Intent에 위와 같은 내용들이 담겨있다.

[목차]

[내용]

1) 앱 구성요소 ( 4가지 )

Android 앱의 필수적인 요소로, 각각은 시스템이나 사용자가 앱에 들어올 수 있는 진입점

Activity, Service, BroadcastReceiver, ContentProvider 이렇게 4가지가 있다.


1-1) Activity

  • 앱과 사용자가 상호작용을 하기 위한 진입점
    • 앱을 실행할 때는 앱을 전체적으로 호출하는 것이 아니라 앱의 액티비티를 호출
  • 모든 앱에 반드시 1개 이상 존재
  • Activity 는 사용자와 상호작용을 위한 UI 가 있음
    • 앱이 실행되면, 화면이 표시됨
    • 사용자의 입력값을 받음 ( 화면 클릭, 더블 클릭, 롱클릭, 스와이프, 드래그 앤 드랍 등 )
    • 사용자에게 제공하고자 하는 내용을 화면에 표시함
  • Lifecycle 이 있음

출처: https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko


1-2) Service

  • 백그라운드에서 오래 실행되는 작업 수행을 위한 컴포넌트
  • 사용자가 다른 앱으로 전환하더라도 백그라운드에서 계속 실행
  • UI 없음
  • 종류: 포그라운드 서비스, 백그라운드 서비스, 바인드 서비스
  • 포그라운드 서비스 : 사용자에게 잘 보이는 작업. 포그라운드 서비스의 경우, 반드시 알림을 표시해야하며, 사용자가 앱과 상호작용하지 않을 때도 계속 실행됨
  • 백그라운드 서비스: 사용자에게 직접 보이지 않는 작업
    • ex) 사용자에게 보이지 않는 작업 ( 저장소 압축, 게임 업데이트, 파일 압축 등 )
    • 앱이 API 레벨 26 이상을 대상으로 할 경우
      • 즉시 실행해야 하는 작업 : Work Manager
      • 지연 작업 : Alarm Manager
  • 바인드 서비스 : 앱 컴포넌트가 bindService 를 호출해 서비스를 호출하면, 서비스가 바인딩 된 서비스는 클라이언트-서버 인터페이스를 제공해 서비스와 상호작용 함. 여러 개가 한꺼번에 바인딩 될 수 있고, 바인딩 된 컴포넌트가 모두 종료되면, 서비스도 종료됨.

출처: https://developer.android.com/develop/background-work/services?hl=ko

foreground/backgroundService 서비스를 실행할 때, startService() 로 실행 서비스를 종료할 때, onDestroy() 를 명시적으로 호출해야 함

bindService 서비스를 실행할 때, bindService() 로 실행 서비스를 종료할 때, 바인딩을 요청한 클라이언트들이 모두 사라지면 unbinding 이 되는 상태이므로 서비스는 종료됨

예) Activity A 와 Acitivity B 가 서비스 C를 바인드 했을 때 Activity A 와 Activity B 가 종료되면, 더 이상 바인딩할 서비스가 없어진다. 클라이언트 자체가 없어지기 때문.


1-3) BroadcastReceiver

  • 안드로이드 OS에서 발생하는 이벤트와 정보를 앱에서 수신할 수 있도록 하는 구성요소
  • UI 없음
  • 예시
    • 화면이 꺼졌거나
    • 배터리가 부족하거나
    • 사진을 캡처했거나

[부연설명]

Broadcast ( 방송 ) + Receiver ( 수신자 ) 두 개 단어의 합성어 뜻 그대로 가지고 있다.

방송을 듣는 사람.

안드로이드 OS 에서는 다양한 이벤트가 발생하는데, 이때 이 정보를 특정 앱에서 알아야하는 케이스들이 있다.

음악 앱이라면 이어폰이 꽂혔다가 이어폰이 해제가 된 경우, 음악재생이 중지되는 것이 기대됨

이런 이벤트를 캐치해서 처리


1-4) ContentProvider

  • 파일 시스템, SQLite 데이터베이스, 웹상이나 앱이 액세스할 수 있는 다른 모든 영구 저장장치에 저장 가능한 앱 데이터의 공유형 집합을 관리
  • 다른 앱은 콘텐츠 프로바이더를 통해 해당 데이터를 질의하거나 수정할 수 있음
  • 예시
    • 연락처 정보
    • 갤러리 이미지 / 비디오

[부연설명]

Content ( 내용 ) + Provider ( 제공자 ) 내용을 제공하는 사람

디바이스에 저장되어있는 모든 데이터에 접근할 수 있는 것을 관리한다.

연락처 정보를 가져온다거나,

갤러리에 저장된 이미지를 가져오는 기능은,

앱 내가 아닌 디바이스에 저장된 정보를 가져오는 것


2) Manifest

  • 앱의 필수적인 정보를 담고 있는 파일
    • 앱의 패키지 이름
    • 앱의 구성요소
    • 권한
    • 필요한 기능

[부연설명]

Manifest 의 사전적 의미는 명사로는 화물 목록, 승객 목록 동사로는 나타내다.

두 가지를 섞어서 생각해보면, Manifest 는 앱의 필수적인 정보를 담고 있다.

정보를 나열한다는 관점에서는 ”화물 목록”으로 볼 수 있고

앱의 성격이나 특징을 나타낸다는 관점에서는 동사 뜻인 “나타내다”를 잘 반영한다.

앱의 패키지명이라고 하면, ID 같은 고유한 값

앱의 구성요소는 4가지 컴포넌트들이다. 반드시 Manifest 파일에 선언이 되어야 사용이 가능하다.

예) Activity 파일을 만들어 놓고, Manifest 파일에 Activity 를 선언하지 않으면, 해당 Activity를 호출할 수 없다.

4대 컴포넌트에 대해서는 Manifest 에 해당 내용이 적혀 있어야 한다.

특정 앱애서는 앱을 사용하기 위해서는 이런 권한이 필요합니다. 알림이 뜨는 것을 볼 수 있다. 지도 앱이면, 위치정보 접근 배달 앱이면, 리뷰 작성 시, 사진 접근 등의 권한이 필요하다.

Manifest 파일만 보고도 대략적으로 이 앱이 어떤 앱인지 알 수 있다.


3) Intent

  • 구성요소 간의 통신을 할 수 있게 하는 역할
  • 앱에 포함된 구성요소 이외에, 다른 앱의 구성요소와도 통신할 수 있음
    • 명시적 인텐트 : 특정 컴포넌트, 액티비티를 명확히 특정해 실행할 경우
      • 예시) A_Activity 에서 B_Activity 실행을 호출할 경우
    • 암시적 인텐트: 동작을 특정하긴 했지만, 실행될 대상이 달라질 수 있는 경우
      • 예시) 특정 URL 을 실행이라는 액션을 요청한 경우, 웹브라우저 기능을 가진 다수의 앱이 호출될 수 있는 경우

[부연설명]

Intent 는 “의도”라는 뜻을 가지고 있다.

’어떤 액션을 취하겠다’는 “액션 정보”를 담고 있기도 하고, ”데이터” 를 담고 있어서

각 구성요소 간에 이런 데이터를 주고 받아야 한다. 특정 액션을 취해야 한다. Intent에 위와 같은 내용들이 담겨있다.

 

1) Android OS

  • Linux 기반의 운영체제
  • 오픈소스 기반
  • 스마트폰 OS 의 약 70%를 차지함
  • 스마트폰 뿐만 아니라 웨어러블 디바이스, TV, 자동차 등 다양한 플랫폼에서 동작할 수 있는 OS (중요)
  • 버저닝이 디저트 이름으로 되어 있음

 

1-1) 세부 설명

  • 스마트폰 OS 의 약 70%를 차지함

스튜디오 설치 시, SDK (Software Development Kit) 를 설치하는데 개발자가 개발하기를 쉽게할 수 있는 기능을 제공함

Provider 가 만드는 것을 쉽게 함으로써 좀 더 많은 Provider 를 이끌어 왔다.

 

 

  • Android OS 는 이식성이 좋다.

iOS 는 애플 제품에만 사용 가능하지만 Android 는 화웨이, 샤오미, 삼성, 엘지 에 사용할 수 있으므로 범용적이다.

Android OS = 앱 개발 은 아니다. 앱을 사용하는 모바일 폰 뿐만 아니라 다양한 플랫폼에서 동작 가능하다.

큰 화면에 대응할 수 있다. 자동차, 웨어러블 디바이스, TV, Chrome OS 에 사용 가능.

 

 

  • Google

처음에 구글에서 Android OS 를 만든 것은 아니고, Android OS 를 만드는 회사를 Google 이 인수했다.

 

 

  • iOS 와 Android OS

iOS 공부할까, Android OS 를 공부할까 고민을 한다면 위 지표들을 참고하는 게 좋다.

 

1) Object

Object : 클래스를 정의함과 동시에 객체를 생성

  • 싱글톤을 쉽게 만들 수 있는 키워드
  • 생성자 사용 불가
  • 프로퍼티, 메서드, 초기화 블록은 사용 가능
  • 다른 클래스나 인터페이스를 상속받을 수 있음

 

1-1) 생성자 사용 불가

오브젝트에는 생성자 사용 불가

 

 

1-2) 싱글톤 / 생성과 동시에 초기화 / 프로퍼티, 메서드, 초기화 블록 가능

fun main() {
    // 클래스였다면 : Counter()
    // 오브젝트이면 생성과 동시에 초기화됨
    println(Counter.count)

    Counter.countUp()
    Counter.countUp()

    println(Counter.count) // 2 ( 싱글톤 )

}

object Counter {
    init {
        println("카운터 초기화")
    }

    var count = 0 // 초기화
    fun countUp() {
        count++
    }
    fun countDown() {
        count--
    }

}

 

 

1-3) 클래스 / 인터페이스 상속 가능

fun main() {
    Counter.hello()

}

object Counter : Hello() {
    init {
        println("카운터 초기화")
    }

    var count = 0 // 초기화
    fun countUp() {
        count++
    }
    fun countDown() {
        count--
    }

}

open class Hello() {
    fun hello() = println("Hello")

}

 

 

1-4) 사용

에러 코드 정의할 때

해당 인스턴스를 매번 새로 만들 필요가 없으므로


2) Companion Object

Companion Object: 동반객체

  • 클래스 내에 하나만 생성할 수 있음
  • Java 의 static 과 동일한 역할

 

 

2-1) Java 의 static 과 동일한 역할

// Companion 을 통해 import
// Companion 을 통해서 가져왔다.
import com.example.partzero.Book.Companion.NAME

fun main() {
    Book.create() // 인스턴스를 만들지 않아도
                  // 클래스 메소드처럼 사용 가능
    NAME // Companion 을 통해 import
}

class Book {
    companion object { // 실제로는 object Companion 임
        // 코틀린은 자바와 다르게
        // static final (constant value 로 선언)이 없는 대신
        // companion object 사용
        
        const val NAME = "hello" // constant value 는
        // 카멜 규칙이 아닌, capital letter 및 snake rule

        // factory pattern
        fun create() = Book()
    }
}

 

 

2-2) companion object 의 이름 ( Novel )

import com.example.partzero.Book.Novel.NAME

fun main() {
    NAME
}

class Book {
    companion object Novel{ 
        const val NAME = "hello"
        fun create() = Book()
    }
}

import com.example.partzero.Book.Novel.NAME

Novel 이라는 Object 를 통해서 NAME 에 접근함

static 과 완전히 동일하지는 않고 Object 의 개념을 가져옴.

해당 Companion object를 포함한 클래스를 선언할 때 Object 도 함께 선언이 된다.

 

 

2-3) 클래스 내에 하나만 생성할 수 있음

한 클래스 내에 이름이 같은 Object 를 여러 개 만들 수 없는 것과 동일

Only one companion object is allowed per class

 

1) 초기화 지연

초기화 지연 : lateinit, lazy

정의: 변수를 선언할 때 값을 지정하지 않고, 나중에 지정할 수 있는 방법

목적: 메모리를 효율적으로 사용하기 위해서, null safe 한 value 를 사용하기 위해서

 

2) lateinit, var

  • 변수 타입을 지정해줘야 함
  • Primitive 타입은 사용할 수 없음
  • 선언 후, 나중에 초기화 해줘도 됨
lateinit var text :String // 선언 ( 타입 지정해주어야 함)
// lateinit var age : Integer // Int ( primitive type 는 lateinit 불가능 )
                              // 참조타입은 lateinit 가능

fun main() {
    // text 초기화 되기 전에, text 에 접근하면
    // lateinit property text has not been initialized 오류 발생
    
    text = "name" // 초기화
    // age = Integer(10)

    print(text)

}

3) lazy, val

  • 선언과 동시에 초기화를 해야 함
  • 호출시점에 초기화가 이루어짐
val test : Int by lazy {
    println("초기화 중")
    100 // test 를 호출하는 시점에 초기화
}

fun main() {

    println("메인 함수 실행")
    println("초기화 한 값 $test")
    println("두번째 호출 $test")

}

 

lazyinit 은 lateinit과 달리 선언과 함께 초기화를 해주어야한다는 차이점이 있다.

+ Recent posts