# 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()
// ...
}
변수 db에 MySQL의 인스턴스가 할당되든 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();