# Table of Contents

# 객체지향 프로그래밍

자바는 모든 대상을 객체(Object)로 바라본다. 이러한 점에서 자바는 객체지향 프로그래밍(Object-oriented Programming)언어다. 객체지향 프로그래밍을 이해하려면 클래스와 인스턴스에 대해 알아야한다.

# 클래스와 인스턴스

클래스와 인스턴스를 설명할 때 와플 기계와 와플을 예로 든다.

클래스(Class)인스턴스를 만드는 틀, 인스턴스(Instance)클래스로 만든 무언가다. 즉 클래스는 와플 기계, 인스턴스 와플이다. 클래스를 코드로 표현하면 다음과 같다.

class Waffle {
    // ...
}

인스턴스는 다음과 같이 생성한다. 키워드 new를 사용하고 클래스 이름 뒤에 ()를 붙여주면 된다.

Waffle waffle = new Waffle()

이렇게 만든 인스턴스는 객체(Object)라고도 한다.

# 생성자

생성자(Constructor)는 클래스의 인스턴스를 생성할 때 호출되는 구문이다. 보통 생성자에서는 초기화 작업을 수행한다.

class Person {

    // 멤버변수
    String name;

    // 생성자
    Person() {
        // 멤버변수 초기화
        this.name = "Paul";
        System.out.println("Constructor has occurred.");
    }
}

생성자는 인스턴스를 생성할 때 호출된다.

Person person = new Person();   // Constructor has occurred.

생성자에 매개변수를 전달할 수 있다.

class Person {

    // 멤버변수
    String name;

    // 매개변수가 없는 생성자
    Person() {
        this.name = "";
    }

    // 매개변수가 있는 생성자
    Person(String name) {
        this.name = name;
    }
}

# 멤버변수와 메소드

멤버변수메소드는 클래스 내부에 다음과 같이 선언한다.

class Person {

    // 멤버변수
    String name;

    // 멤버변수
    String nation;

    // 생성자
    Person(String name) {
        this.name = name;
    }

    // 메소드
    void printName() {
        System.out.println(this.name);
    }
}

멤버변수와 메소드는 다음과 같이 사용할 수 있다.

Person person = new Person("Paul");

person.printName();

String name = person.name;

# 상속

실생활에서 상속은 자식이 부모의 재산을 물려받는 행위를 뜻한다. 자바에서도 상속은 비슷한 의미로 사용된다. 상속(Inheritance)자식 클래스가 부모 클래스의 멤버변수나 메소드를 그대로 물려받는 것을 의미한다. 이를 통해 코드의 중복을 제거할 수 있다.

다음과 같이 부모 클래스가 있다고 가정해보자.

// 부모 클래스
class Person {

    protected String name;

    Person(String name) {
        this.name = name;
    }

    void printInformation() {
        System.out.println(this.name);
    }
}

부모 클래스를 상속할 땐 키워드extends를 사용한다. 자식 클래스에서는 @Override를 붙여서 부모 클래스의 메소드를 재정의할 수 있다.

// 자식 클래스
class Player extends Person {

    private String team;

    Player(String name, String team) {
        super(name);
        this.team = team;
    }

    // 부모 클래스의 메소드를 재정의
    @Override
    void printInformation() {
        System.out.println(name + " plays in " + team);
    }
}

주의할 점은 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출해야 한다는 것이다. 이때는 키워드 super를 사용합니다.

// 자식 클래스
class Player extends Person {

    // 자식 클래스의 생성자
    Player(String name, String team) {
        // 부모 클래스의 생성자를 호출
        super(name);
        // ...
    }
}

이렇게 정의한 자식 클래스는 다음과 같이 사용할 수 있다.

Player player = new Player("Heung-min Son", "Tottenham");
player.printInformation();	// Heung-min Son plays in Tottenham

# 추상 클래스

미구현된 메소드를 가지고 있는 클래스추상 클래스라고 한다. 추상 클래스를 선언할 땐 키워드 abstract를 붙인다.

// 부모 클래스
abstract class Person {

    String name;

    Person(String name) {
        this.name = name;
    }

    // 구현된 메소드
    void printName() {
        System.out.println(name);
    }

    // 미구현된 메소드
    abstract void printInformation();
}

추상 클래스는 인스턴스를 생성할 수 없다.

Person person = new Person("Paul"); // 아래와 같은 에러가 발생
// 'Person' is abstract; cannot be instantiated

따라서 이를 상속하는 자식 클래스에서 미구현된 메소드를 구현해야한다.

class Player extends Person {

    String team;

    Player(String name, String team) {
        super(name);
        this.team = team;
    }

    // 메소드 구현
    @Override
    void printInformation() {
        System.out.println(name + " plays in " +team);
    }
}

이제 클래스의 인스턴스를 생성할 수 있다.

Player player = new Player("Paul", "Real Madrid")

# 인터페이스

모든 메소드가 선언만 있고 구현부가 없는 클래스인터페이스라고 한다. 인터페이스는 특정 규격을 준수하도록 강제하는데 사용한다.

인터페이스를 선언할 때는 키워드 interface를 사용한다. 인터페이스에는 메서드를 선언만 하고 구현은 하지 않는다.

interface Database {
    void save()
    void delete()
}

인터페이스는 인스턴스 생성할 수 없다.

Database db = new Database();   // 아래와 같은 에러가 발생합니다.
// 'Database' is abstract; cannot be instantiated

따라서 인터페이스를 구현해야한다. 인터페이스를 구현한 클래스를 구현체(Imlementation)라고 한다. 인터페이스를 구현할 때는 키워드 implements를 사용한다.

인터페이스 Database 규격을 따르는 클래스 두 개를 구현해보자.

class MySQL implements Database {

    @Override
    void save() {
        OracleClient client = new OracleClient()
        client.save()
    }

    @Override
    void delete() {
        OracleClient client = new OracleClient()
        client.delete()
    }
}
class Realm implements Database {

    @Override
    void save() {
        Realm realm = new Realm()
        realm.save()
    }

    @Override
    void delete() {
        Realm realm = new Realm()
        realm.delete()
    }
}

위 예제에서는 Database라는 규격을 따르도록 강제하고있다. 이렇게 하면 Database를 사용하는 입장에서 실제 구현체가 무엇인지 신경쓰지 않고 사용할 수 있다. 이를 이처럼 인터페이스에 다양한 구현체를 할당할 수 있는 것을 어려운 용어로 다형성(Polymorphism)이라고 한다.

다형성을 예제를 통해 살펴보자.

class LoginViewModel implements ViewModel {

    Database db = new MySQL()
    db.save()
    
    db = new Realm()
    db.save()

    // ...
}

변수 dbMySQL의 인스턴스가 할당되든 Realm의 인스턴스가 할당되는 save()메소드를 호출할 수 있다. MySql클래스와 Realm클래스가 Database인터페이스의 표준을 따르고 있기 때문이다.

이처럼 인터페이스와 다형성을 사용하면 유지보수성을 향상시킬 수 있다.

# 내부 클래스

자바에서는 클래스 안에 클래스를 정의할 수 있다. 이를 내부 클래스라고 한다. 내부 클래스는 다음과 같이 선언한다.

class Outer {

    class Inner {
        // ...
    }
}

내부 클래스는 외부 클래스의 인스턴스를 먼저 생성해야 내부 클래스를 생성할 수 있다.

// (1) 외부 클래스의 인스턴스 생성
Outer outer = new Outer();

// (2) 외부 클래스의 인스턴스를 사용하여 내부 클래스의 인스턴스 생성
Outer.Inner inner = outer.new Inner();

Static 내부 클래스도 다음과 같이 선언할 수 있다.

class Outer {
    // Static Inner Class
    static class StaticInner {
    }
}

Static 내부 클래스는 외부 클래스의 인스턴스를 생성하지 않고도 내부 클래스의 인스턴스를 생성할 수 있다.

// 외부 클래스의 인스턴스를 생성하지 않고도 내부 클래스의 인스턴스 생성
Outer.StaticInner staticInner = new Outer.StaticInner();

# 익명 클래스

다음과 같은 부모 클래스가 있다고 가정하자.

// 부모 클래스
abstract class Person {
    abstract void doSomething();
}

이를 상속한 자식 클래스를 한번만 사용하는 경우, 매번 자식 클래스를 정의하기에는 번거로운 경우가 있다.

// 자식 클래스를 정의
class Designer extends Person {

    @Override
    void doSomething() {
        System.out.println("Do something.");
    }
}

// 자식 클래스 사용
Person person = new Designer();
person.doSomething();

이러한 경우 익명 클래스를 유용하게 사용할 수 있다.

abstract class Person {
    abstract void doSomething();
}

// 익명 클래스 사용
Person person = new Person() {
    @Override
    public void doSomething() {
        System.out.println("Do something.");
    }
};

person.doSomething();

익명 클래스는 안드로이드 어플리케이션 개발에서 이벤트를 처리할 때 많이 사용한다.

Button loginButton = findViewById(R.id.loginButton);


// 버튼을 눌렀을 때 실행할 코드를 익명 클래스로 등록
loginButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // log in
    }
});

# 열거형

열거형(Enum)서로 관련 있는 상수들을 모아 심볼릭한 명칭의 집합으로 정의하는 것이다.

예를 들어 방향은 동, 서, 남, 북 네 가지로 값의 범위가 한정되어있다. 이러한 경우 열거형을 유용하게 사용할 수 있다. 열거형은 다음과 같이 키워드enum을 사용하여 정의한다.

enum Direction {
    EAST, WEST, SOUTH, NORTH
}

열거형의 인스턴스는 다음과 같이 생성할 수 있다.

Direction direction = Direction.EAST;

열거형은 switch 구문과 함께 유용하게 사용할 수 있다.

switch (direction) {
    case EAST: {
        System.out.println("Go to east.");
        break;
    }
    case WEST: {
        System.out.println("Go to west.");
        break;
    }
    case NORTH: {
        System.out.println("Go to north.");
        break;
    }
    case SOUTH: {
        System.out.println("Go to south.");
        break;
    }
}

# java.lang 패키지

java.lang 패키지는 자바에서 가장 기본적인 동작을 수행하는 클래스들의 집합이다. 이 패키지에 있는 클래스는 import하지 않아도 바로 사용할 수 있다.

# Object 클래스

Object클래스는 모든 자바 클래스의 최고 조상 클래스다. 자바의 모든 클래스는 암시적으로든 명시적으로든 이 클래스를 상속하고 있다. 이 클래스는 java.lang패키지에 포함되며, 유용한 여러 메소드를 제공한다.

# hashCode()

hashcode()는 객체에 대한 해시코드를 반환한다.

Person p1 = new Person("Paul");
Person p2 = p1;
Person p3 = new Person("John");

System.out.println(p1.hashCode());  // 214520509
System.out.println(p2.hashCode());  // 214520509
System.out.println(p3.hashCode());  // 68944050

이 해시코드는 객체를 고유하게 구분하기 위해 사용된다. HashMap의 경우 데이터를 삽입할 때 hashCode()메소드의 반환값을 해시 테이블의 인덱스 주소값으로 사용한다. 따라서 충돌을 낮추고 성능을 향상시키기 위해 hashCode()를 잘 정의하는 것이 중요하다.

# equals()

메소드를 호출하는 객체와 인자로 전달받은 객체의 동등성을 판단한다. 다만 Object클래스를 상속하는 클래스에서는 equals()을 직접 구현해야한다.

package com.yologger.example;

class Person {

    String name;

    Person(String name) {
        this.name = name;
    }

    // equal()메소드를 직접 구현
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Person) {
            Person _person = (Person) obj
            // name 속성 값이 같으면 같은 객체로 취급
            return this.name == _person.name;
        } else {
            return false;
        }
    }
}

다음과 같이 인스턴스를 비교할 수 있다.

Person p1 = new Person("Paul");
Person p2 = new Person("Paul");

boolean result = p1.equals(p2); // true

# getClass()

인스턴스의 클래스를 반환한다.

Person person = new Person("Paul");
Class class1 = person.getClass()
System.out.println(class1);  // class com.yologger.app.Person

Phone phone = new Phone();
Class class2 = phone.getClass();
System.out.println(phone);   // class com.yologger.app.Phone

# toString() 메소드

해당 객체의 정보를 문자열로 반환한다.

toString()을 구현하지 않은 상태에서 호출하면 아래와 같이 호출된다.

Person person = new Person("Paul", "London");

System.out.println(person.toString());  // com.yologger.app.Person@cc952bd

보통 toString()를 개발자가 원하는대로 구현하고 호출한다.

class Person {

    String name;
    String address;

    Person(String name, String address) {
        this.name = name;
        this.address = address;
    }

    // toString()을 직접 구현
    @Override
    public String toString() {
        return name + " lives in " + address;
    }
}

Person person = new Person("Paul", "London");

System.out.println(person.toString());  // Paul lives in London

# 그 외

그 외에도 Object 클래스는 finalize(), notify(), notifyAll(), wait() 메소드 등을 제공한다.

# static

키워드 static은 클래스의 멤버변수와 메소드 앞에 붙일 수 있다. 이 키워드가 붙어있는 멤버변수와 메소드를 정적 멤버변수정적 메소드라고 한다.

정적 멤버변수와 정적 메소드는 다음과 같은 특징이 있다.

  • 클래스의 인스턴스를 생성하지 않고도 정적 메소드를 호출할 수 있다.
  • 클래스의 인스턴스를 생성하지 않고도 정적 멤버변수에 접근할 수 있다.
  • 클래스의 모든 인스턴스가 정적 멤버변수를 공유한다.

예제를 살펴보자. 정적 멤버변수와 정적 메소드는 키워드 static을 붙여서 선언한다.

class Counter {

    // 정적 멤버변수
    static int count = 0;

    // 생성자
    Counter() {

    }

    // 정적 메소드
    static void increment() {
        count++;
    }

    // 정적 메소드
    static void decrement() {
        count--;
    }

    // 정적 메소드
    static void printCount() {
        System.out.println(count);
    }
}

정적 멤버변수와 정적 메소드는 다음과 같이 인스턴스를 생성하지 않고도 사용할 수 있다.

Counter.printCount();   // 0

Counter.increment();
Counter.increment();
Counter.increment();

Counter.printCount();   // 3

# 초기화 블록

# 인스턴스 초기화 블록

인스턴스 초기화 블록은 클래스의 인스턴스가 생성될 때 호출되며, 생성자보다도 먼저 호출된다.





 
 
 
 












class Person {

    String name;

    // 초기화 블록
    {
        System.out.println("This is initialization block");
    }

    // 생성자
    Person(String name) {
        System.out.println("This is constructor");
        this.name = name;
    }

    void printName() {
        System.out.println(name);
    }
}

클래스의 인스턴스를 생성하면 다음과 같이 화면에 출력된다.

Person person = new Person("Paul");
// This is initialization block
// This is constructor

person.printName();
// Paul

# static 초기화 블록

static 초기화 블록은 클래스가 메모리에 로드될 때 호출되며, 다음과 같이 선언한다.




 
 
 
 














class Counter {
    static int count = 0;

    // static 초기화 블록
    static {
        System.out.println("Static Initialization Block");
    }

    static void increment() {
        count++;
    }

    static void decrement() {
        count--;
    }

    static void printCount() {
        System.out.println(count);
    }
}

static 초기화 블록은 클래스가 로드될 때 호출된다.

Counter.printCount();
// Static Initialization Block
// 0

Counter.increment();
Counter.increment();
Counter.printCount();
// 2

# 접근 제한자

접근 제한자는 외부에서 클래스 내부의 메소드나 멤버변수에 접근하는 것을 제한하는 것이다. Java는 private, protected, default, public이라는 네 개의 접근 제한자를 지원한다. 접근 제한자는 클래스, 생성자, 메소드, 멤버변수 앞에 붙일 수 있습니다.

# private

private가 붙은 멤버변수, 메소드, 생성자는 같은 클래스 안에서만 접근할 수 있다. 클래스에는 붙일 수 없다.

class Person {

    // private 변수 name
    private String name;

    Person(String name) {
        this.name = name;
    }

    void printName() {
        // 같은 클래스에서는 name에 접근할 수 있다.
        System.out.println(name);
    }
}

Person person = new Person("Paul");

person.name;    // 클래스 외부에서는 접근할 수 없다. 다음과 같은 에러가 발생한다.
// 'name' has private access in Person

# protected

protected가 붙은 멤버변수, 메소드, 생성자는 같은 패키지 내에서 상속 관계에 있는 자식 클래스에서만 접근할 수 있다. 클래스에는 붙일 수 없다.

package com.yologger.person;

// 부모 클래스
class Person {

    // protected 변수
    protected String name;

    Person(String name) {
        this.name = name;
    }
}
package com.yologger.person;

// 자식 클래스
class Programmer extends Person {

    Programmer(String name) {
        super(name);
    }

    void printName() {
        // 자식 클래스에서 부모 클래스의 protected 멤버변수에 접근 가능
        System.out.println(name);
    }
}

# default

접근 제한자를 생략하면 기본적으로 default 접근 제한자가 적용된다. default가 붙은 멤버변수, 메소드, 생성자는 동일한 패키지 내 어디서든 접근할 수 있다. default는 클래스에도 붙일 수 있다.

Person클래스는 com.yologger.person패키지에 정의되어있고 접근 제한자는 default다.

package com.yologger.person;

class Person {
    // ...
}

따라서 다른 패키지인 com.yologger.app에서 접근할 수 없다.

package com.yologger.app;

Person person = new Person();   // 다음과 같은 에러가 발생한다. 
// 'com.yologger.person.Person' is not public in 'com.yologger.person'. Cannot be accessed from outside package

# public

public가 붙은 멤버변수, 메소드, 생성자는 다른 패키지라도 어디에서든 접근이 가능하다.

package com.yologger.person;

public class Person {
    // ...
}

다른 패키지인 com.yologger.app에서도 접근할 수 있다.

package com.yologger.app;

Person person = new Person();