[Java] 객체지향 프로그래밍Ⅱ

📁 상속

클래스를 재사용해 새로운 클래스를 만드는 것
두개의 클래스에 조상-자손 관계를 맺어주는 것
 
클래스에서 다른 클래스를 상속할 때에는 확장한다는 뜻의 extends를 사용

class Child extends Parent {}

특징

  • 자손은 부모의 멤버를 모두 물려받지만 생성자와 초기화블럭은 상속되지 않는다.
  • 자손의 멤버 수는 조상과 같거나 많다.
  • 자손 멤버의 변경은 조상 멤버에 영향을 미치지 않는다.

장점

  • 코드의 재사용성 향상
  • 코드를 공통적으로 관리해 코드의 추가 및 변경 용이
  • 코드의 중복을 방지해 프로그램의 생산성과 유지보수에 기여

📁 클래스 사이의 관계

자바는 단일상속만을 지원한다.
다중상속이 필요한 경우 가장 비중이 높은 클래스를 상속하고 나머지는 포함관계로 만들어야 한다.

상속관계

한 클래스를 확장하여 다른 클래스를 만들 때 두 클래스의 관계
같은 조상을 상속하고 있다고 하더라도 자손클래스들 사이에는 어떠한 관계가 성립하지 않는다.

class Parent {}
class Child1 extends Parent {}
class Child2 extends Parent {}

포함관계

클래스 안에 다른 클래스를 포함시키는 것
 
'원은 점이다'라는 문장보다는 '원은 점을 가지고 있다'라는 문장이 더 자연스럽다.
이같은 경우 Point 클래스를 상속하기보다는 Point 인스턴스를 멤버변수로 선언하는 것이 바람직

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

📁 Object 클래스

모든 클래스의 조상
부모가 없는 클래스는 자동으로 Object를 상속한다.
 
toString()이나 equals()를 따로 정의하지 않고 사용할 수 있었던 것은 Object 클래스에 이미 정의되어 있기 때문
이외에도 Object 클래스에는 11개의 메서드가 정의되어 있어 모든 클래스에서 사용할 수 있다.

public class Object {
	public String toString() {}
    public boolean equals(Object obj) {}
    ...
}

📁 오버라이딩

조상으로부터 상속받은 메서드의 내용을 변경하는 것

@Override
public void parentMethod() {}

오버라이딩의 조건

  • 메서드의 선언부가 조상과 동일해야 한다.
  • 제어자의 범위는 조상보다 좁게 설정할 수 없다.
  • 조상보다 더 많은 예외를 선언할 수 없다.

 

🤔 static 메서드는 오버라이딩할 수 있을까?
static 메서드는 오버라이딩이 불가능하다.
static 메서드는 프로그램이 실행되면 메모리에 정적으로 할당되기 때문에 프로그램이 끝날 때까지 변경될 수 없다.

오버로딩 vs 오버라이딩

  • 오버로딩 : 클래스에 기존에 존재하지 않는 새로운 메서드를 과적하는 것
  • 오버라이딩 : 기존에 존재하는 메서드에 덮어쓰는 것

super

자손클래스에서 상속받은 조상의 멤버를 참조하는 데에 사용하는 참조변수

super.조상메서드명();
super.조상변수명;

super()

자손클래스에서 조상의 생성자를 호출하는 예약어

super();
super(조상멤버변수);

생성자 첫 줄에 생성자가 없는 경우 컴파일러가 조상의 기본 생성자를 자동 호출한다.
이때 조상에 기본 생성자가 없으면 에러가 발생한다.
클래스에서 당장 사용하지 않더라도 기본 생성자를 만들어줘야 하는 이유는 이 때문이다.

📁 package와 import

package

클래스의 묶음
서로 관련이 있는 클래스들을 한 패키지에 묶음으로써 효율적인 관리가 가능하다.

package 패키지명.하위패키지명;

모든 클래스는 반드시 하나 이상의 패키지에 속해야 한다.
.을 구분자로 하여 하위 패키지를 생성할 수 있다.

import

컴파일러에게 소스파일에 제공된 클래스의 정보를 제공해 다른 패키지의 클래스를 사용할 때 패키지명을 생략할 수 있다.
패키지명 뒤에 클래스명이 아닌 *를 붙이면 해당 패키지의 모든 클래스를 패키지명 없이 사용할 수 있다.

import 패키지명.클래스명;
import 패키지명.*;

static import

클래스의 static 멤버를 호출할 때 클래스명을 붙이지 않고 사용할 수 있다.
클래스명 뒤에 멤버명 대신 *를 쓰면 해당 클래스의 모든 static 멤버를 클래스명 없이 사용할 수 있다.

import static 패키지명.클래스명.클래스메서드명;
import static 패키지명.클래스명.클래스변수명;
import static 패키지명.클래스명.*;

📁 제어자

클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여

static

클래스의, 공통적인

  • 멤버변수 : 모든 인스턴스가 공유하는 클래스 변수
  • 메서드 : 객체 생성 없이 사용 가능한 클래스 메서드

final

마지막의, 변경될 수 없는

  • 클래스 : 변경과 확장이 불가능해 다른 클래스의 조상이 될 수 없음
  • 메서드 : 자손클래스에서 오버라이딩이 불가능한 메서드
  • 변수 : 값을 변경할 수 없는 상수

abstract

추상의, 미완성의

  • 클래스 : 추상메서드를 가지고 있는 클래스
  • 메서드 : 구현부가 없는 메서드

📁 접근제어자

클래스와 멤버에 접근 가능한 범위를 설정하는 제어자.

  같은 클래스 같은 패키지 자손 클래스 전체
public O O O O
protected O O O  
default O O    
private O      

접근제어자와 캡슐화

접근제어자를 사용하면 외부에서 데이터에 직접적으로 접근하지 못하게 해 데이터를 보호할 수 있다.
외부에서 불필요한, 내부에서만 사용되는 부분을 감추는 캡슐화의 효과이다.

생성자의 접근제어자

생성자에 접근제어자를 사용해 인스턴스 생성을 제한할 수 있다.
싱글톤 클래스를 만들 때 사용되는 방법이다.

class PrivateConstructor {
	private PrivateConstructor() {}
    
    public PrivateConstructor getInstance() {
    	return new PrivateConstructor();
	}
}

생성자의 접근제어자를 private으로 지정하고, 클래스 내부에서 인스턴스를 생성해 반환하는 메서드를 제공해 외부에서 인스턴스를 사용할 수 있도록 한다.
생성자의 접근제어자가 private일 경우 상속이 불가능하다. 자손의 생성자에서 조상 생성자를 호출할 수 없기 때문이다.

📁 제어자의 조합

필요에 따라 제어자를 조합해 사용할 수 있다.
 

  • abstract가 붙은 클래스와 메서드에 static, final은 사용할 수 없다.
  • 메서드에서 pirvatefinal은 같은 의미를 가지므로 하나만 써도 된다.
  사용 가능한 제어자
클래스 static, final, abstract, public, default
메서드 static, final, abstract, public, protected, default, private
멤버변수 static, final, public, protected, default, private
지역변수 final

📁 다형성

여러가지 형태를 가질 수 있는 능력

자바는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 구현했다.
 

Parent p1 = new Parent();
Parent p2 = new Child();

이처럼 조상타입 참조변수로 자손의 인스턴스를 참조할 수 있는 것은 자바의 다형성 때문이다.
조상타입 참조변수로는 조상클래스에 선언된 멤버만 사용할 수 있다.
 

Child c = new Parent();	// 에러

반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조하려고 하면 에러가 발생한다.
참조변수가 사용할 수 있는 멤버의 갯수는 인스턴스의 멤버 갯수보다 적거나 같아야 하기 때문이다.

참조변수의 형변환

참조변수는 다형성에 의해 조상타입 혹은 자손타입으로 형변환 가능하다.
참조변수의 형변환은 단지 참조변수의 타입을 변경하여 사용할 수 있는 멤버의 범위를 조절하는 것에 불과하다.
따라서 실제 인스턴스의 타입에는 영향을 끼치지 않는다.

업캐스팅 Up-Casting

  • 자손타입→조상타입
  • 형변환 생략 가능

참조변수를 통해 다룰 수 있는 멤버의 갯수는 실제 멤버의 갯수보다 적을 것이 분명하다.
따라서 형변환을 생략해도 문제가 되지 않는다.

다운캐스팅 Down-Casting

  • 조상타입→자손타입
  • 형변환 생략 불가

참조변수를 통해 다룰 수 있는 멤버의 수가 실제 인스턴스의 멤버보다 많아질 수 있어 형변환을 생략할 수 없다.
형변환 전에 instanceof 연산자를 통해 실제 인스턴스 타입을 확인하는 것이 안전하다.

instanceof

참조변수가 참조하고 있는 인스턴스의 실제 타입을 검사하는 연산자. 참조변수가 클래스나 그 자손 타입의 인스턴스인지 검사하여 boolean값을 반환한다.

참조변수 instatnceof 클래스명

결과값이 true라는 것은 참조변수가 해당 클래스 타입으로 형변환이 가능하다는 것을 의미한다.

매개변수의 다형성

다형성은 메서드의 매개변수에도 적용된다.
매개변수는 명시된 타입 뿐만 아니라 그 자손 타입의 인스턴스도 인자로 받을 수 있다.

객체 배열

객체를 저장하는 배열
다형성에 의해 조상 타입으로 배열을 선언하고 자손 타입 객체들을 저장할 수 있다.

Fruit f = new Fruit[2];
f[0] = new Apple();
f[1] = new Banana();

📁 추상클래스

미완성 설계도, 추상메서드를 가지고 있는 클래스

  • 인스턴스 생성 불가
  • 일반 클래스와 동일하게 멤버변수와 메서드, 생성자를 가질 수 있다.
abstract class 추상클래스명 {}

추상메서드

선언부만 있고 구현부가 없는 메서드

abstract 리턴타입 추상메서드명(매개변수);

추상화와 구체화

  • 추상화
    상속의 반대 개념
    기존 클래스에서 공통적인 부분을 뽑아내 조상클래스로 만드는 것
  • 구체화
    상속을 통해 클래스를 구현하고 확장하는 작업
    추상클래스를 사용하기 위해서는 반드시 구체화 작업을 통해 모든 추상메서드를 구현해야 한다.

특징

  • 자손클래스에서 공통적으로 필요한 기능을 추상메서드로 선언함으로써 구현을 강제한다.
  • 구현부 작성 권한을 각 자손에 일임해 같은 기능을 구현하더라도 클래스 성격에 따라 내용이 달라질 수 있음을 고려한다.

📁 인터페이스 Interface

일종의 추상클래스로 추상화 정도가 매우 높은 것
interface 인터페이스명 {}

구현

추상클래스와 마찬가지로 인스턴스를 생성할 수 없으며 하위클래스에서 구현해야 사용 가능
클래스와 달리 extends 대신 implements를 사용한다.

class 클래스명 implements 인터페이스명 {}

다중상속

  • 한 클래스에서 다수의 인터페이스, 클래스와 인터페이스를 함께 상속 가능
  • 인터페이스 역시 다른 인터페이스를 상속할 수 있으며 extends를 사용

작성

인터페이스는 추상클래스보다 추상화 정도가 더 높아서 원칙적으로 추상메서드와 상수만을 멤버로 가질 수 있다.

  • 멤버변수의 제어자 : public static final
  • 메서드의 제어자 : public abstract

제어자를 생략하더라도 묵시적으로 제어자가 붙는다.

public static final 타입 상수명 = 값;
public abstract 리턴타입 메서드명(매개변수);

static 메서드와 디폴트 메서드

JDK1.8부터는 스태틱 메서드와 디폴트 메서드를 가질 수 있게 되었다.

static 메서드

public static 리턴타입 메서드명(매개변수) {}

static 메서드는 애초에 인스턴스와 관계가 없기 때문에 인터페이스에서 사용을 가능하도록 해도 무방했지만 자바 규칙을 쉽게 배우도록 하기 위해 금지되었다.
static 메서드를 사용할 수 없었던 기존의 인터페이스들은 static 메서드만 모아둔 클래스를 따로 만들어야만 했는데, 대표적으로 컬렉션을 다루는 Collections 클래스가 있다.
스태틱 메서드 역시 인터페이스의 멤버이므로 접근제어자가 public이며, 생략하더라도 컴파일러가 자동 추가한다.

디폴트 메서드

public default 리턴타입 메서드명(매개변수) {}

인터페이스에 새로운 메서드를 추가할 때마다 자손 클래스에서 일일히 내용을 구현해줘야 하는 불편함을 해결하기 위해 고안되었다.
이름은 default이지만 접근제어자는 public이며, 생략하더라도 컴파일러에 의해 자동으로 추가된다.

인터페이스의 다형성

  • 인터페이스 타입 매개변수는 인터페이스를 구현한 자손 객체가 인자로 들어올 수 있다.
  • 인터페이스 타입 참조변수는 인터페이스를 구현한 자손 객체를 참조할 수 있다.
  • 인터페이스 타입 리턴타입인 메서드는 인터페이스를 구현한 자손 객체를 반환한다.

중간역할

클래스는 클래스를 제공하는 쪽(Provider)과 사용하는 쪽(User) 두가지가 있다.
여기서 인터페이스는 이 두 클래스들을 느슨하게 연결해 대화, 소통을 돕는 중간역할을 수행한다.

(1) 강한 결합

제공하는 쪽과 사용하는 쪽이 직접적인 관계를 맺는 것

사용하는 쪽은 제공하는 쪽 타입의 참조변수를 통해 멤버에 직접적으로 접근한다.
제공하는 쪽에 변경사항이 생기거나 다른 클래스로부터 제공을 받으려는 경우, 사용하는 쪽 클래스의 변경이 불가피하다.

class User {
	public use(Provider p) {
		p.provide();
	}
}

(2) 느슨한 결합

제공하는 쪽과 사용하는 쪽이 간접적인 관계를 맺는 것
인터페이스를 활용하면 두 클래스를 느슨하게 결합할 수 있다.

제공하는 쪽을 인터페이스로 감싸고 사용하는 쪽은 인터페이스를 매개로 멤버에 접근하도록 한다.
제공하는 쪽에 변경사항이 생기거나 다른 클래스로부터 제공을 받을 때, 사용하는 쪽 코드 수정이 필요하지 않다.

class User {
	public use(Interface i) {
		i.provide();
	}
}

장점

  • 개발시간 단축
  • 작업의 표준화 가능
  • 서로 관계없는 클래스들에 관계를 맺어줌
  • 클래스의 선언과 구현을 분리해 독립적인 프로그래밍 가능

📁 내부클래스

클래스의 내부에 선언되는 클래스
내부클래스의 종류와 사용 가능한 제어자는 멤버변수의 그것과 동일하다.

인스턴스 클래스

외부클래스의 필드에 선언되어 인스턴스 멤버로 취급

  • 내부적으로 static 멤버를 가질 수 없다. 인스턴스 클래스는 외부 객체가 생성되어야 사용할 수 있기 때문
  • static과 final이 함께 붙은 상수는 사용 가능

스태틱 클래스

외부클래스의 필드에 선언되어 클래스 멤버로 취급

  • static 멤버를 가질 수 있음
  • 외부 인스턴스 멤버에 접근할 수 없다. 인스턴스 멤버는 객체를 생성해야 사용할 수 있기 때문

지역클래스

외부클래스의 메서드나 초기화블럭 내에 선언되어 선언된 내부에서만 사용 가능

  • 외부클래스의 static 멤버와 인스턴스 멤버, 지역클래스가 선언된 메서드의 지역변수 중 final이 붙은 변수 사용 가능

익명클래스

일회성으로 사용하기 위해 클래스의 선언과 객체 생성을 동시에 하는 클래스

new 조상클래스명() {}
new 인터페이스명() {}