데이터 처리속도와 처리량의 한계로 인해 전통적인 RDBMS 데이터베이스와 다른 새로운 데이터베이스가 요구되었습니다. 이로인해 NoSQL(Not only SQL)과 NewSQL과 같은 데이터 베이스들이 등장하였습니다.

RocksDB의 특징

  • LSM-tree를 기반으로하여 대용량의 데이터를 저장하는데(write intensive workload)에 적합합니다
  • InnoDB와 비교하였을때 RocksDB는 쓰기성능에서의 압도적인 성능차이를 보였고, 읽기성능에서는 조금 뒤떨어지는 성능을 보여주었지만 쓰기성능에서의 이점을 상쇄할 만큼의 큰 차이는 보이지 않았습니다.
    image
    (출처 : https://smalldatum.blogspot.com/2017/11/insert-benchmark-io-bound-intel-nuc.html)
  • RocksDB는 다양한 parameter를 제공하여 여러 하드웨어 configuration에서도 좋은 성능을 내도록 할 수 있습니다.

Facebook은 방대한 MySQL클러스터의 스토리지 엔진을 InnoDB에서 RocksDB로 변경하는 작업을 2018년에 완료하였는데, 덕분에 50%에 가까운 저장용량을 아낄 수 있었습니다.

Who Uses RocksDB

RocksDB는 점점더 많이 활용되고 있지만 그중 대표적인 활용사례를 몇가지 추려 보았습니다.

분산데이터 베이스에서의 스토리지 엔진으로써의 활용

  • Apache Cassandra
  • CockroachDB
  • MySQL(MyRocks)
  • Rockset

분산데이터 베이스 외에서의 활용

  • Kafka Streams
  • Apache Samza
  • Netflix
  • Santander UK
  • Uber

Netflix

넷플릭스는 유저데이터를 제공하는데 지연시간을 단축시키기 위해 EVCache를 사용하는데, 이때 RocksDB를 사용합니다.

image


먼저 유저가 넷플릭스에 로그인 하면 넷플릭스 서버는 데이터베이스로부터 유저의 시청기록, 평가기록, 개인 데이터들을 기반으로한 추천 영상등과 같은 유저데이트를 데이터베이스 서버로부터 가져오게됩니다. 하지만 이러한 유저데이터는 너무 방대하기때문에 클라우드 DB만을 사용하기에는 지연시간이 길어집니다. 이러한 지연시간을 단축시키기 위해 넷플릭스는 EVCache라는 캐시를 설계하였습니다.

image


유저데이터가 요청되면, 먼저 EVCache를 확인하여 유저데이터가 있는지 확인하고, 없다면(Cache miss) Similar Service를 통해 아마존 SimpleDB와 같은 클라우드 데이터베이스에 접근하여 유저데이터를 가져옵니다. 가져온 유저데이터는 EVCache에 쓰여지고, 사용자에게 제공됩니다.

image


넷플릭스의 EVCache 클러스터는 기존에는 메모리만을 활용하였지만, 이는 메모리의 비용적 문제로 확장성(scalable)이 떨어지고 hardware fault에 취약하였습니다.

 

넷플릭스의 EVCache cluster는 지역별로 존재하는데 한국에 존재하는 사용자는 주로 한국의 EVCache에 접근하고 미국이나 영국의 EVCache에는 접근하지 않습니다. 이러한 이유로 한국의 EVCache입장에서 한국의 유저데이터는 Hot한 유저데이터가 되지만, 미국, 영국의 EVCache입장에서 한국의 유저데이터는 Cold한 유저데이터가 됩니다.

 

이러한 차이로 모든 유저데이터를 메모리상에 저장하는것은 비효율적입니다.

image


넷플릭스는 효율적으로 유저데이터를 관리하기 위해 캐시를 메모리를 활용하는 L1 Cache, SSD를 활용하는 L2 Cache로 나누었습니다. 그중 L2 Cache에서 RocksDB가 사용됩니다.

 

L1,L2 캐시는 각각 AWS EC2 인스턴스의 r3, i2인스턴스를 사용합니다. SSD를 사용함으로써 저장할 데이터의 용량은 25배 이상 늘렸지만, 시간당 가격은 2.5배 밖에 차이가 나지 않습니다.

Kafka Streams

Kafka에서는 스트림 프로세싱(stream processing)의 기본 상태저장소로(state store) rocksDB를 사용합니다.

스트림 프로세싱은 이동 중인 데이터, 즉 데이터가 생성되거나 수신되는 즉시 데이터 위에 컴퓨팅되는 데이터를 처리하는 것입니다.

대부분의 데이터는 센서 이벤트, 웹 사이트의 사용자 활동, 금융 거래 등과 같은 연속적인 스트림으로 생성됩니다. 이 모든 데이터는 시간이 지남에 따라 일련의 이벤트로 생성됩니다.

image

스트림프로세싱이 도입되기 전, 데이터는 데이터베이스, 파일 시스템 또는 다른 형태의 대용량 저장소에 저장되었고 애플리케이션은 필요에 따라 데이터를 쿼리하거나 각종 분석작업을 진행하는 배치 처리의 형태로 진행되었습니다.

image

스트림 프로세싱은 각 구간의 스트림 프로세서(Stream processor)를 통해 데이터를 실시간으로 처리합니다.

데이터 스트림을 수신 및 전송하고 애플리케이션 또는 분석 로직을 실행하는 시스템을 스트림 프로세서(Stream processor)라고 합니다.

image

스트림을 처리 하다보면 이전 스트림프로세서가 처리한 결과(state)를 참조해야 하는 경우가 있습니다. 이런 류의 처리 방식을 상태 기반처리라고 합니다. 상태 기반 스트림 처리를 하기 위해서는 애플리케이션의 처리 결과를 저장할 상태 저장소(state store)가 필요합니다. 이때 스트림 프로세싱 애플리케이션이 이 저장소를 관리하면 내부 상태 저장소라고 하고, Database와 같은 별도의 상태 저장소를 사용하게 되면 외부 상태 저장소라고 합니다.

 

Kafk Streams에서는 RocksDB를 기본 상태저장소로 사용합니다. 여기서 RocksDB는 in-memory key-value store와 달리 디스크를 활용할 수 있어 가용메모리보다 큰 state를 저장할 수 있게 해줍니다.

Uber

우버에서는 Cherami라는 MessageQueue System에 RocksDB를 사용합니다.

image

Uber는 기존에는 redis라는 in-memory key-value데이터베이스를 사용하였는데 Uber가 필요로하는 확정성과 지속성을 제공해 주지못해 RcoksDB로 데이터베이스를 변경하였다 라고 설명합니다.

 

Uber Message Queue system에서 RocksDB는 메시지를 저장하는데 사용되는데 다음과 같이 Key를 높은 비트에 delivery time, 낮은 bit에 sequnce number로 하여 메시지를 저장하는 형태입니다.

image

Reference

'Infra' 카테고리의 다른 글

[Husky] 커밋메시지 JIRA 티켓번호 자동화  (0) 2022.08.22
[YCSB] Introduction  (0) 2022.08.22
[RocksDB] Introduction  (0) 2022.08.22
[RocksDB] RocksDB Install  (0) 2022.08.22
[FPGA]Introduction of FPGA  (0) 2022.08.22

What is RocksDB?

RocksDB는 Facebook사에서 개발한 LSM(Log Structured Merge)기반의 No SQL데이터 베이스 저장 엔진입니다.

정보 통신 기술의 발전으로 전 세계의 데이터 생산량이 기하 급수적으로 증가하였고, 이에따라 기존의 관계형 데이터베이스(RDBMS)보다 대용량의 데이터를 효율적으로 저장하고 관리할 수 있는 기술이 중요해 졌고 이에따라 NoSQL데이터 베이스가 등장하게 되었습니다.

NoSQL데이터 베이스는 다음의 몇가지 특징을 가집니다.

  • Dynamic Schema
    • RDBMS에서는 데이터베이스에 데이터를 저장하기 전에 사전에 스키마를 정의하여야 합니다. 반면 NoSQL의 경우 사전 정의된(pre-defined) 스키마를 요구하지 않아 데이터 및 요구 사항이 변경됨에 따라 NoSQL 데이터베이스를 훨씬 쉽게 업데이트할 수 있습니다.
  • Data structure
    • RDBMS가 테이블 기반인 반면 NoSQL데이터베이스의 경우 document based, graph databases, key-value pairs, 또는 wide-column stores가 될 수 있습니다. 때문에 더 자유롭게 비정형 데이터를 처리할 수 있습니다
  • Scaling
    • 수직적 확장이 필요한 RDBMS의 경우 더 비싼 단일 서버를 요구하여 확장 비용이 크지만, NoSQL 데이터베이스의 경우 범용의 값싼 서버를 추가하는것으로 수평적 확장이 가능하여 확장비용이 저렴합니다.

NoSQL데이터 베이스에는 대표적으로 Amazon의 DynamoDB, MongoDB, Github의 Redis, Google의 LevelDB 그리고 Facebook의 RocksDB가 있습니다. 그중 RocksDB는 Google에서 개발한 LevelDB를 fork하여 Facebook에서 개발한 오픈소스 프로젝트로 고성능 저장장치에 최적화된 Key-Value 데이터베이스입니다.

Key-value Store?

Key-Value store에서는 데이터의 각 조각들을 Key-Value쌍으로 구성하여 키를 사용하여 데이터베이스에 값을 저장합니다. 애플리케이션이 데이터를 검색할때 Key를 Key-Value Store에 제공하면, Key-Value Store는 Key를 해시하여 저장소에서 Value를 가져옵니다.

RocksDB Architecture

RocksDB는 크게 Memtable, sstfile, logfile 3가지로 구성되어 있습니다.

쓰기요청이 발생하면 메모리의 임시버퍼인 Memtable에 쓰기작업을 진행합니다. Memtable이 꽉 차게되면 읽기 전용의 immutable memtable로 전환(switch)되고 새로운 Memtable을 생성하여 새로 들어오는 쓰기 요청을 진행합니다.

이후 Immutable memtable이 특정 개수에 도달하면 저장장치로 옮겨 sstfile로 저장하게 되는데 이러한 작업을 flush라고 합니다.

sstfile은 키를 쉽게 찾아보기 위해 모든 key-value 데이터들을 정렬아여 저장하고, 로그 구조의 병합 트리 구조를 가지고 저장 장치 내에서 컴팩션을 통해 업데이트 됩니다.

Appendix

Rocks DB vs LevelDB

LevelDB RocksDB
Developed by Google Developed by Facebook
Open Source Open Source
Key-Value Store Key-Value Store
github.com/­google/­leveldb rocksdb.org
Built in C++ Built in C++
No Transactions Transactions are supported.
Concurrency supported Concurrency supported.
NodeJs,Python,Java C++,Go Api available to access the LevelDB C++ and Java Api available.

Reference

'Infra' 카테고리의 다른 글

[YCSB] Introduction  (0) 2022.08.22
[RocksDB] RocksDB 활용사례  (0) 2022.08.22
[RocksDB] RocksDB Install  (0) 2022.08.22
[FPGA]Introduction of FPGA  (0) 2022.08.22
[FPGA]Introduction of FPGA acceleration  (0) 2022.08.22

 

깃허브의 facebook/rocksdb repo의 install.md를 참고하였습니다.

Dependecies

먼저 의존성 패키지를 설치합니다.

sudo apt-get install -y \
  libgflags-dev \
  libsnappy-dev \
  zlib1g-dev \
  libbz2-dev \
  libzstd-dev \
  liblz4-dev

Install

깃에서 RocksDB를 클론합니다.

git clone https://github.com/facebook/rocksdb.git

다음으로 make를 해주어야하는데 makemake all은 디버그 모드로 컴파일하고, make static_lib는 rocksdb를 릴리즈 모드로 컴파일 합니다.
RocksDB 깃허브의 설치 가이드에는 릴리즈 모드인 make static_lib로 설치하기를 권장하고 있습니다.

디버그 모드(Debug Mode) vs 릴리즈 모드(Release Mode)

디버그 모드의 경우 실행파일에 디버깅 정보를 포함하여 실행파일의 상태정보를 확인 가능합니다. 다만 이러한 디버깅 정보를 포함하기 때문에 속도가 릴리즈 모드에 비해 다소 느립니다

반면 릴리즈 모드의 경우 이러한 디버깅 정보를 포함하지않기 때문에 속도가 디버그 모드에 비해 빠르고 파일의 크기가 비교적 작습니다.

릴리즈모드로 컴파일합니다(db_bench도 같이 컴파일하고싶으면 뒤에 추가해줍니다)

cd rocksdb
make static_lib db_bench

컴파일이 완료되었으면 다음의 간단한 예제프로그램을 컴파일 해봅니다.

#include <assert.h>
#include "rocksdb/db.h"

rocksdb::DB* db;
rocksdb::Options options;
int main() {
    options.create_if_missing = true;
    rocksdb::Status status =
        rocksdb::DB::Open(options, "/tmp/testdb", &db);
    assert(status.ok());
}

g++ 컴파일 옵션에는 다음과 같이 rocksdb를 포함해 줍니다.

g++ test.cpp -lrocksdb -ldl -lpthread

'Infra' 카테고리의 다른 글

[RocksDB] RocksDB 활용사례  (0) 2022.08.22
[RocksDB] Introduction  (0) 2022.08.22
[FPGA]Introduction of FPGA  (0) 2022.08.22
[FPGA]Introduction of FPGA acceleration  (0) 2022.08.22
[CUDA]Programming Model  (0) 2022.08.22

Goal

StringBuilder와 StringBuffer의 차이

StringBuilder, StringBuffer

StringBuilder, StringBuffer 둘다 mutable한 객체이며 데이터 변경이 빈번한 경우에 성능면에서 유리합니다. 하지만 둘사이에는 약간의 차이가 존재합니다.

StringBuffer

StringBuffer는 데이터 변경시 스레드간 동기화를 진행하기 때문에 때문에 thread-safe합니다.

@Override @HotSpotIntrinsicCandidate 
public synchronized StringBuffer append(String str) { 
    toStringCache = null; 
    super.append(str); 
    return this; 
}

Synchronized Method

인스턴스 단위로 lock을 설정하며, 메서드가 시작될 때 부터 종료될 때 까지 동기화가 발생합니다. 동일 인스턴스 내의 다른 synchronized method에 대해서도 동일한 lock을 설정합니다.

StringBuilder

반면 StringBuilder는 스레드간 동기화를 지원하지 않기 때문에 thread-safe하지 않으나, 단일 스레드에서의 성능은 StringBuffer보다 우수합니다.

@Override @HotSpotIntrinsicCandidate 
public StringBuilder append(String str) { 
    super.append(str); 
    return this; 
}

이러한 차이때문에 StringBuilder의 주석에서는 다음과 같이 멀티 스레드 환경에서는 StringBuilder를 사용할것을 권장하고 있습니다.

      * Instances of {@code StringBuilder} are not safe for
      * use by multiple threads. If such synchronization is required then it is
      * recommended that {@link java.lang.StringBuffer} be used.

1. 상속

1.1 상속의 정의

  • 조상 클래스 : 부모(parent), 상위(super), 기반(base) 클래스
  • 자손 클래스 : 자식(child), 하위(sub), 파생(derived) 클래스

자바에서 상속은 다음과 같이 키워드 extends를 통해 구현한다.

class Parent {
    int age;
    ...
}
class Child extends Parent { 
    void play();
}

자손 클래스는 조상 클래스의 모든 멤버를 상속받기 때문에 Child클래스는 Parent클래스의 멤버들을 포함한다.

따라서 위의 소스코드에서 Child 클래스는 age멤버 변수를 Parent클래스로부터 상속받아 추가된 것과 같은 효과를 얻는다.

반면 자손 클래스의 변경은 조상 클래스에 아무런 영향을 주지 못한다.

Child 클래스의 play()함수는 Parent 클래스에서는 사용할 수 없다.

자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.

1.2 클래스간의 관계

원을 표현하기 위해 Circle이라는 클래스를 다음과 같이 작성하였다 가정하자.

class Circle {
    int x;
    int y;
    int r;
}

포함관계 로 표현

class Point {
    int x;
    int y;
}

class Circle {
    Point c = new Point();
    int r;
}

상속관계로 표현

class Circle extends Point {
    int r;
}

이렇게 Point클래스를 상속관계로 두느냐, 포함관계로 두느냐는 큰 차이가 있어보이지는 않는다.

클래스간의 관계를 결정하기 위해서는 다음과 같은 기준을 활용하면 보다 명확해진다.

  • 상속관계 : ~은 ~이다(원은 점이다)
  • 포함관계 : ~은 ~을 가지고 있다(원은 점을 가지고 있다)

원은 점을 가지고 있다가 조금더 매끄러운 문장이다. 따라서 Circle클래스와 Point클래스는 포함관계를 맺어주는것이 더 좋다.

1.3 단일 상속(single inheritance)

C++에서는 다중상속을 허용하지만 자바에서는 단일 상속만을 허용한다.
단일상속은 하나의 조상 클래스만을 가질 수 있기 때문에 클래스 간의 관계가 보다 명확해지고 코드를 더 신뢰할수 ㅣㅇㅆ게 만들어준다.

1.4 Object클래스

Object클래스는 모든 클래스 상속계층의 최상위 조상클래스이다. class Tv{}라는 클래스를 생성하면 컴파일러는 자동적으로 extends Object를 추가하여 Tv클래스가 Object클래스로부터 상속받도록 한다.

이미 클래스가 다른 클래스를 상속한다면 단일 상속 규칙을 위해 Object클래스를 상속하지 않는다.

2. 오버라이딩(Overriding)

class Point {
    int x;
    int y;

    String getLocation() {
        return "x :" + x + ", y :" + y;
    }
}

class Point3D extends Point{
    int z;
    String getLocation() {
        return "x :" + x + ", y :" + y + ", z :" + z;
    }
}

2.1 오버라이딩 조건

  • 이름이 같아야 한다.
  • 매개변수가 같아야 한다
  • 반환타입이 같아야 한다
  1. 접근제어자는 조상클래스의 메서드보다 좁은 범위로 변경할 수 없다.
    • protected <= public
  2. 조상클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
    • 아래의 경우 부모클래스가 더 많은 예외를 가지고 있는것 처럼 보이지만 Exception은 모든 예외의 최고 조상이므로 가장 많은 개수의 예외를 던질 수 있도록 선언한 것이다. 따라서 잘못된 오버라이딩이다.
    • class Parent { void pMethod() throws IOException, SQLException { ... } void cMethod() throws Excetpion { ... } }
  3. 인스턴스 메서드를 static메서드 도는 그 반대로 변경할 수 없다.
    • static메서드 호출시에는 참조변수.메서드() 보다는 클래스이름.메서드()의 방식으로 호출하는 것이 바람직하다.

2.2 오버로딩 vs 오버라이딩

오버로딩 - 기존에 없던 새로운 메서드 정의
오버라이딩 - 상속받은 메서드의 내용을 변경하는것

class Parent {
    void parentMethod() {
        ...
    }
}

class Child {
    void parentMethod() {}      // 오버라이딩
    void parentMethod(int i) {} // 오버로딩
}

2.3 super

super는 조상클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다.

class Parent {
    int x = 10;
    void print() {
        System.out.println(x);
    }
}

class Child extends Parent {
    int x = 20;

    void method() {
        System.out.println(x);          // 20
        System.out.println(this.x);     // 20
        System.out.println(super.x);    // 10
    }

    void print_child() {
        super.print();                  // 10
    }
}

this()와 마찬가지로 super()또한 생성자이지만, 조상 클래스의 생성자를 호출하는데 사용된다.

class Point {
    int x;
    int y;
    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

class Point3D {
    int x;
    int y;
    int z;

    Point3D(int x, int y, int z) {
        super(x, y);
        int this.z = z;

    }
}

3. Package & import

3.1 Package

  • 하나의 소스파일은 첫 번째 문장으로 단 한 번의 패키지 선언만을 허용
  • 모든 클래스는 반드시 하나의 패키지에 속해야한다.
  • 패키지는 점(.)을 구분자로 계층구조로 구성할 수 있다.
  • 패키지는 물리적으로 클래스 파일을 포함하는 하나의 디렉토리이다.

3.2 import

일반적으로 소스파일은

  1. package
  2. import
  3. 클래스 선언

순으로이루어 진다.

한 패키지에서 여러 클래스를 사용하는 경우 클래스의 이름을 일일이 지정해주는 것보다 패키지명.*으로 하는것이 편리하다

import java.util.*

3.3 static import

import문을 사용하면 클래스의 패키지 명을 생략할 수 있는 것과 같이 static import문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다.

System.out.println(Math.random());
import static java.lang.Math.random;
import static java.lang.System.out;

out.println(random());

4. 제어자

접근 제어자

  • public, protected, default, private

그 외

  • static, final, abstract, native, transient, synchronized, volatile, strictfp

4.1 static

static은 클래스의, 공통적인이라는 의미를 갖고 잇다.

static 멤버변수(클래스변수)는 인스턴스를 생성하지 않고도 사용할 수 있으며, 모든인스턴스가 값을 공유한다.

4.2 final

c++의 const와 같이 변수에 사용하게되면 상수가 되며, 메서드에 사용되면 오버라이딩 할 수없게 되며, 클래스에 사용하면 자손클래스를 정의하지 못하게 된다.

final class FinalTest {
    final int MAX_SIZE = 10;

    final void getMaxSze() {
        final int LV = MAX_SIZE;
        return MAX_SIZE;
    }
}

4.3 abstract

클래스에서의 abstract는 클래스 내에 추상 메서드가 선언되어 있음을 의미하며, 메서드에서 abstract는 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다

abstract class AbstractTest {
    abstract void move();
}

추상클래스는 아직 완성되지 않은 미완성 설계도이므로 인스턴스를 생성할 수 없다.

4.4 접근제어자

  • private
    • 같은 클래스 내에서만 접근 가능
  • default
    • 같은 패키지 내에서만 접근 가능
  • protected
    • 같은 패키지, 다른 패키지의 자손클래스에서 접근 가능
  • public
    • 접근 제한이 없다

이러한 접근 제어자는 클래스 내부의 데이터를 캡슐화 하여 보호하기 위해서 사용한다.

생성자의 접근 제어자

생성자에 접근 제어자를 사용하면 인스턴스 생성을 제한하여 클래스 내부에서만 인스턴스를 생성할 수 있다.

class Singleton {
    private Sigleton() {
        ...
    }
    private static instanceNum = 0;
    private static Singleton s = new singleton();
    // static 으로 선언하여 인스턴스를 미리생성, getInstance()에서 사용할 수 있도록 한다
    public static Singleton getInstance() {
        if (instanceNum > 5) return -1;
        else {
            instanceNum++;
            return s;
        }
    }
}

이와 같이 public 메서드를 통해 인턴스에 접근하게 하여 사용할 수 있는 인스턴스의 개수를 제한할 수 있다.

4.5 제어자 조합

대상 사용가능한 제어자
클래스 public, default, final, abstract
메서드 모든 접근제어자, final, abstract, static
멤버변수 final, static
지역변수 static
  1. 메서드에 static과 abstract를 함께 사용할 수 없다.
  2. 클래스에 abstract와 final을 동시에 사용할 수 없다.
  3. abstract메서드의 접근 제어자가 private일 수 없다.
  4. 메서드에 private과 final을 같이 사용할 필요는 없다.

5. 다형성

다형성이란 여러가지 형태를 가질 수 있는 능력을 의미하며, 자바에서는 한 타입(조상클래스)의 참조변수로 여러 타입(자손클래스)의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.

주의할 점은 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수는 있지만, 자손타입의 참조변수로 조상 타입의 인스턴스를 참조하는것은 존재하지 않는 멤버변수를 참조할 가능성이 있으므로 허용하지 않는다.

자손타입 -> 조상타입 : 형변환 생략가능
조상타입 -> 자손타입 : 형변환 생략불가능

5.1 instanceof연산자

상속관계에 있는 타입간 형변환은 자유롭게 수행될 수있지만, 참조변수가 가리키는 인스턴스의 자손타입으로의 형변환은 허용되지 않는다.
따라서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알기 위해 instanceof연산자를 사용할 수 있다.

void doWork(Car c) {
    if (c instanceof FireEngine) {
        FireEngine fe = (FireEngine)c;
        ...
    } else if (c instanceof Ambulance) {
        Ambulance ac = (Ambulance)c;
        ...
    }
}

어떤 타입에 대한 instanceof연산의 결과가 true라는것은 검사한 타입으로의 형변환이 가능하다는 것이다.

c instanceof FireEngine == true

  • c는 FireEngine클래스 타입으로 형변환이 가능하다.

5.2 참조변수와 인스턴스의 연결

메서드의 경우 조상클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.

class BindingTest {
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();

        System.out.println(p.x); // 100
        System.out.println(c.x); // 200

        p.method(); // Child Method
        c.method(); // Child Method

    }
}

class Parent {
    int x = 100;

    void method() {
        System.out.println("Parent Method");
    }
}

class Child extends Parent {
    int x = 200;

    void method() {
        System.out.println("Child Method");
    }
}

6. 추상클래스

추상클래스는 클래스로서의 역할은 못하지만 새로운 클래스를 작성하는데 바탕이 되는 조상클래스로서 중요한 의미를 갖는다.

추상메서드는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기에 조상클래스에서는 선언부만을 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려 주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워두는 것이다.

/* 주석을 통해 메서드의 목적 설명*/
abstract 리턴타입 메서드이름(); // 구현부가 없으므로 {} -> ;

여러 클래스에 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고, 기존 클래스의 공통적인 부분을 뽑아 추상클래스로 만든후 상속하도록 하는 경우도 있다.

7. 인터페이스

인터페이스는 일종의 추상클래스이나, 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다.
오직 추상 메서드와 상수만을 멤버로 가질 수 있으며, 어떠한 다른 요소도 허용하지 않는다.

interface Name {
    public static final 타입 상수이름 = 값;
    public abstract 메서드이름(매개변수 목록);
}
  • 모든 멤버변수는 pbulic static final이어야 하며, 생략할 수 있다.
  • 모든 메서드는 public abstract이어야 하며 생략할 수 있다.

7.1 인터페이스 상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며 클래스와 달리 다중상속이 가능하다

interface Movable {
    void move(int x, int y);
}

interface Attackable {
    void attack(Unit u);
}

interface Fightable extends Movable, Attackable {}

7.2 인터페이스 구현

인터페이스는 상속을 통해 구현하지만, 확장한다는 의미에서 extends를 사용하는 일반 클래스와 달리 구현한다는 의미의 implements를 사용한다.

class 클래스이름 implements 인터페이스 이름 {
    /*추상 메서드 구현*/
    ...
}

class Fighter implements Fightable {
    public void move(int x, int y) {
        ...
    }
    public void attack(Unit u) {
        ...
    }
}

7.3 인터페이스에서의 다중상속?

인터페이스는 stataic상수만 정의할 수 있으므로 조상클래스의 멤버변수와 충돌하는 경우는 거의 없으며, 충돌한다 하더라도 클래스의 이름을 붙여 구분이 가능하다.

7.4 인터페이스의 장점

  1. 개발시간 단축
    • 인터페이스를 구현하는 클래스, 메소드 호출하여 사용하는 쪽을 동시에 개발을 진행할 수 있다.
  2. 표준화 가능
    • 기본틀을 인터페이스로 작성한 후, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화 된 프로그램의 개발이 가능하다.
  3. 관계없는 클래스들에게 관계를 맺어 줄 수 있다
    • 서로 관계없는 클래스들에게 하나의 인터페이스를 공톡적으로 구현하도록 하여 관계를 맺어줄 수 있다.
  4. 독립적인 프로그래밍이 가능하다
    • 클래스의 선언과 구현을 분리시켜 구현에 독립적인 프로그램을 작성할 수 있다.

8. 내부 클래스

8.1 내부클래스

내부클래스는 클래스 내에 선언된 클래스로 두 클래스가 긴밀한 관계에 있을때 사용한다.

장점

  • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
  • 코드의 복잡성을 줄일 수 있다.
class Outer {
    private int outerIv = 0;
    static int outerCv = 0;
    // instance 클래스
    private class InstanceIneer {
        final static int CONST = 100; // final static은 상수이므로 허용
    }

    // static 클래스
    protected static class StaticInner{
        static int cv = 200; // static class만 static 변수를 선언할 수 있다.
    }

    void method() {
        int lv         = outerLv;  // 외부클래스의 지역변수
        final int LV   = 0;
        // local 클래스
        class LocalInner {
            int liv1 = outerIv;
            int liv2 = outerCv;
            int liv3 = lv;  // 에러가 발생하거나 JDK 1.8이상일 경우 lv는 final lv가 된다.
            int liv4 = LV;
        }
    }
}

8.2 익명클래스

익명클래스는 다른 내부클래스와는 달리 선언과 객체 생성을 동시에 하기 때문에 이름이 없고, 단한번 객체를 생성하는데 사용할 수 있는 일회용 클래스이다.

class Inner {
    Object iv = new object() {
        void method() {...}
    };
}

익명클래스는 이름이 없기 때문에 외부클래스명$숫자.class의 형식으로 클래스파일 명이 결정된다.

1. 객체지향 이론, 언어

객체지향 이론

객체 지향 이론은 '실제 세계는 사물(객체)로 이루어져 있으며 발생하는 모든 사건들은 사물간의 상호작용이다.'를 기본개념으로 한다.
객체 지향 이론은 상속, 캡슐화, 추상화 개념을 중심으로 발전하였다.

객체지향 언어

  1. 코드의 재사용성이 높다.
    • 새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.
  2. 코드의 관리가 용이하다.
    • 코드간 관계를 이용하여 적은 노력으로 쉽게 코드를 변경할 수 있다.
  3. 신뢰성 높은 프로그래밍을 가능하게 한다.
    • 제어자와 메서드를 이용하여 데이터를 보호하고 올바른 값을 유지하도록 하며, 코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다.

2. 클래스, 객체

  • 클래스 : 객체를 정의해 놓은것, 객체를 생성할 때 사용된다.
  • 객체(인스턴스) : 실제로 존재하는 것, 사물 또는 개념

2.1 객체의 구성요소

  • 속성 : member variable, attribute, field, state
  • 기능 : method, function, behavior
// class
class Tv {
   // member variable
   String color;
   boolean power;
   int channel;

   // method
   void power() {power = !power; }
   void channelUp (){ ++this.channel; }
   void channelDown() { --this.channel; }
}

class TvTest {
   public static void main(String args[]) {
      Tv t;          // t : Tv인스턴스를 참조하기위한 변수 t
      t = new Tv();  // 인스턴스 생성
      t.channel = 7;
      t.channelDown();
      ...
   }
}

📋 자바 명명 규칙 가이드

  1. Class, Interface : 명사여야 하며 대문자로 시작하여야 한다.
  2. Method : 동사여야하며, 소문자로 시작한다. 붙이는 단어의 경우 붙이는 단어 첫문자를 대문자로 쓴다. ex) isFunny(){}
  3. 변수 : 소문자로 시작하며, 두글자 이상을 권장한다.
  4. 상수 : 모든 글자에 대문자를 사용한다.
  5. 패키지 : 모든 글자를 소문자로 사용한다.

2.2 인스턴스 생성시 메모리 구조

| color         | <-지역변수-> <--0x100-->
| power         |
| channel       | 
| power()       | <--메서드-->
| channelUp()   |
| channelDown() |

2.3 인스턴스 생성 및 사용

  1. Tv t;
    • 참조 변수 t를 위한 공간 생성
  2. t = new Tv();
    • 연산자 new에 의해 인스턴스가 메모리의 빈공간에 할당.
    • 멤버변수는 각 자료형의 기본값으로 초기화
    • 생성된 인스턴스의 주소값은 참조변수 t에 저장(t를 통해 객체에 접근 가능)
Tv t1 = new Tv();
Tv t2 = new Tv();
  1. t2 = t1
    • t1는 참조변수이므로 인스턴스의 주소를 저장하고있다.
    • t2는 대입연산자에 의해 t1의 주소를 t2에 저장한다.
    • 기존 t2가 가리키고 있던 인스턴스(객체)는 참조변수가 하나도 없게 된다.
      • 이러한 인스턴스는 Garbage Collector에 의해 자동적으로 메모리에서 제거된다.

2.4 프로그래밍 관점에서의 클래스에대한 정의

객체지향이론의 관점에서 클래스는 객체를 생성하기 위한 틀로 속성과 기능으로 정의되어있다.

프로그래밍적 관점에서의 클래스는 다음과 같은 발전과정을 거쳤다.

변수 -> 배열 -> 구조체(자료형이 다른 변수) -> 클래스(구조체 + 함수)

함수는 많은 경우에 있어데이터를 가지고 작업을 하기 때문에 데이터와 함수는 관계가 깊다. 따라서 자바와 같은 객체지향 언어에서는 변수와 함수를 하나의 클래스에 정의하여 서로 관계가 깊은 변수와 함수들을 함께 다룰 수 있게 하였다.

다음과 같이 클래스의 멤버 변수를 직접 접근하지 않고 get(), set()메서드를 통해 접근하게 하면, 보다 정확한 데이터를 유지하는데 도움이 된다.

public class Time() {
   private hour;
   ...
   public int getHour();
   public int setHour();
   ...
}

3. 변수와 메서드

3.1 선언 위치에 따른 변수의 종류

class Variable {
   static int cv;    // 클래스 변수
   int iv;           // 인스턴스 변수

   void method() {
      int lv = 0;    // 지역변수 
   }
}
종류 위치 생성 시기
클래스변수 클래스 영역 클래스가 메모리에 올라갈때
인스턴스 변수 클래스 영역 인스턴스가 생성되었을때
지역변수 클래스 영역 이외의 영역 변수 선언문이 수행되었을 때

클래스 변수

  • 인스턴스를 생성하지 않고도 언제든지 사용할 수 있다.
  • 메모리에 로딩되어 프로그램이 종료될 때 까지 유지된다.
  • 클래스 변수를 통해 모든 인스턴스가 하나의 저장공간을 공유하게 하여 공통된 값을 갖도록 할 수 있다.

인스턴스 변수

  • 인스턴스가 생성될 때 마다 생성되므로 각기 다른 값을 유지할 수 있다.

3.2 메서드

메서드를 사용하는 이유

  1. 높은 재사용성
  2. 중복된 코드의 제거
  3. 프로그램의 구조화
  • 같은 클래스 내의 메서드 끼리는 참조변수를 사용하지 않고도 서로 호출이 가능하지만, static메서드는 같은 클래스 내의 인스턴스 메서드를 호출할 수 없다.
    • 인스턴스 변수는 인스턴스가 생성된 후에나 사용할 수 있음.

3.3 JVM 메모리 구조

  1. Method Area(메서드 영역)
    • 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스 파일을 읽어서 분석하여 클래스에 대한 정보를 이곳에 저장한다. 이때 클래스 변수도 이영역에 함께 생성된다.
  2. Call Stack(호출 스택)
    • 메서드의 작업에 필요한 메모리 공간을 제공함. 메서드가 작업을 수행하는 동안 지역 변수, 연산의 중간 결과를 저장하는데 사용되며, 메서드가 반환되면, 메모리공간도 반환되어 비워진다.
    • 참조변수 또한 이영역에 저장되어 힙영역의 인스턴스를 가리키는 역할을 한다.
  3. Heap(힙)
    • 인스턴스가 생성되는 공간으로 인스턴스 변수가 이곳에 생성된다.

3.4 매개변수 : 참조형, 기본형

static void change(int x)              // x는 기본형 매개변수
static void changeClass(Data d)        // d(클래스 인스턴스)는 참조형 매개변수
static void changeArray(int add[] arr) // arr(배열)은 참조형 매개변수

메서드 호출시 매개변수의 타입이 기본형(primitive type)이면 매개변수의 값이 복사되지만, 참조형(reference type)이면 인스턴스의 주소가 복사된다.

기본형 매개변수는 변수의 값을 읽기만 할 수 있지만, 참조형 매개변수는 변수의 값을 읽고 변경할 수 있다.

4. 오버로딩

4.1 오버로딩 조건

  1. 메서드 이름이 같아야 한다
  2. 매개변수의 개수 또는 타입이 달라야 한다.
void println();
void println(boolean x);
void println(char x);
void println(char[] x);
void println(double x);
void println(float x);
...

4.2 가변인자 오버로딩

<타입>... 변수명의 형식으로 선언한다.

public PrintStream printf(String format, Object... args);

가변인자는 매개변수들 중 가장 마지막에 선언되어야 한다.

가변인자는 내부적으로 배열을 이용한다. 하지만 매개변수 타입을 배열로 하는것과 달리 인자를 생략할 수도있고, null이나 길이가 0인 배열도 인자로 포함할 수 있다.

인자로 배열을 사용한 경우

String concatenate(String[] str);
String result = concatenate(new String[0]);
String result = concatencate(null);
String result = concatenate();

가변인자를 사용한 메서드는 가능하면 오버로딩 하지 않는게 좋다. 다음과 같은 경우에 에러가 발생할 수 있다.

static String concatenate(String delim, String... args) {...}
static String concatenate(String... args) {...}

컴파일시 두 오버로딩된 메서드가 구분되지 않아 에러가 발생할 수 있다. 따라서 가능하면 가변인자를 사용한 메서드는 오버로딩 하지 않는것이 좋다.

5. 생성자

5.1 생성자 기본

  1. 생성자의 이름은 클래스의 이름과 같아야 한다.
  2. 생성자는 반환 값이 없다.
class Card {
   Card() { // class name = method(constructor) name
      ...
   }
   Card(String k, int num) {
      ...
   }
}

모든 클래스에는 하나 이상의 생성자가 정의되어있어야 한다. 하지만 실제로 인스턴스 생성시 생성자 없이도 인스턴스를 생성할 수 있었는데 이는 기본 생성자 덕분이다.

기본생성자는 컴파일러가 자동적으로 추가하는 생성자로, 특별히 인스턴스 초기화 작업이 필요하지 않다면, 기본생성자를 사용하는 것도 좋다.

class Data {
   int val;

   Data(int x) {
      this.val = x;
   }
}

class Test {
   public static void main(String argsp[]) {
      Data d1 = new Data();      // 컴파일 에러 발생 
      Data d2 = new Data(5);
   }
}

위의 Data클래스에 대한 인스턴스를 매개변수 없이 생성할때 컴파일 에러가 발생한다. 매개변수가 있는 생성자가 존재하므로 컴파일러는 기본 생성자를 추가하지 않아 매개변수가 없는 생성자는 정의되지 않는다.

즉 기본 생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐이다.

5.2 다른 생성자 호출

  • 생성자의 이름으로 클래스이름 대신 this를 사용한다.
  • 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.

에러가 발생하는 경우

Car(String color) {
   door = 5;
   Car(color, "auto", 4);
}

수정

Car(String color) {
   this(color, "auto", 4);
   door = 5;
}

this는 참조변수로 인스턴스 자신을 가리킨다. 참조변수를 통해 인스턴스의 멤버에 접근할 수 있듯, this를 통해 인스턴스 변수에 접근할 수 있다.

5.3 생성자를 통한 인스턴스 복사

class Car {
   int a;
   String b;
   float c;
   Car() {
      this(10, "kk", "5.1");
   }
   Car(Car ca) {
      /*
      a = ca.a;
      b = ca.b;
      c = ca.c;
      */
      this(ca.a, ca.b, ca.c); // 기존의 코드를 재활용하는것이 바람직 하다
   }
}

6. 변수의 초기화

6.1 초기화 블럭

클래스 초기화 블럭

  • 클래스 변수의 복잡한 초기화에 사용된다
  • 클래스 로드시 한번만 수행
  • 인스턴스 초기화 블럭*
  • 인스턴스 변수의 복잡한 초기화에 사용된다
  • 인스턴스가 생성될때 마다 수행된다.
    // 클래스 초기화 블럭
    static {
     ...
    }
    

// 인스턴스 초기화 블럭
{
...
}


```java
class BlockTest {
   int a;
   static int[] arr = new int[10];
   static {
      for(int i = 0; i < 10; i++) {
         arr[i] = (int)(Math.random() * 10 + 1);
      }
   }

   {
      a = (int)(Math.random() * 10 + 1);
   }
}
class Car {
    static int num = 0;
    static { 
        System.out.println("Class loaded");
    }

    {
        System.out.println(num++ + "번째 차");

    }
}
public class App {
    public static void main(String[] args) {
        Car c1 = new Car();
        Car c2 = new Car();

    }
}

image

6.2 초기화 시기와 순서

- 클래스 변수 인스턴스 변수
시점 클래스가 처음 로딩될때 단 한번 인스턴스가 생성될때마다 인스턴스 별로
순서 기본값 -> 명시적 초기화 -> 클래스 초기화 블럭 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자

+ Recent posts