위 그림과 같이 부모 클래스의 멤버는 자식 클래스가 그대로 물려받게 된다.
자식 클래스의 멤버는 부모 클래스에 영향을 끼치지 못한다.
부모 클래스가 2개인데, 각 클래스에 메소드의 이름이 겹치는 상황을 생각해보자.
그러면 누구의 메소드를 상속받을 것인지 정해야 한다.
이러한 다중상속의 문제점으로 단일 상속만을 허용한다. (그런데 C++에서는 다중상속을 허용한다.)
위 그림과 같이 부모 클래스를 확장시킨다. 라는 뜻에서 자식 클래스에 extends
를 붙이면 된다.
class Child extends Parent {
...
}
"has a" 를 넣어서 문장이 성립하면 포함관계이다.
ex) Circle has a point. (원은 점을 가지고 있다.)
class Point {
int x;
int y;
}
class Circle {
Point c = new Point(); // 원점
int r; // 반지름
}
"is a" 를 넣어서 문장이 성립하면 상속관계이다.
ex) Circle is a shape. (원은 도형이다.)
class Shape {
String color = "black";
}
class Circle extends Shape {
...
}
"~위에 덮어쓰다." 라는 뜻이다.
말 그대로 조상 클래스로부터 상속받은 기존의 메소드의 내용을 변경하는 것을 말한다.
조상 클래스의 메소드와
=> 즉 선언부가 서로 일치해야 한다.
접근 제어자와 예외는 다음과 같은 조건에서는 변경이 가능하다.
public > protected > default(생략 가능) > private
static 메소드를 자손 클래스에서 똑같은 이름의 static메서드로 정의하는 건 오버라이딩인가요?
기존에 없는 새로운 메소드를 추가하는 것이다.
같은 메소드이름이여도 매개변수가 다르거나 반환타입이 다른 경우를 말한다.
자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다.
class Parent {
int x = 10;
}
class Child extends Parent{
int x = 20;
void method() {
System.out.println(super.x); // 10
System.out.println(this.x); // 20
}
}
만약 super()
이렇게 사용한다면 조상 클래스의 생성자를 부른다는 뜻이다.
Object 클래스를 제외한 클래스의 생성자 첫 줄에는 super();
가 붙어있다. 안 붙이면 컴파일러가 알아서 붙여준다.
그런데 만일 부모 클래스에 기본 생성자가 아닌 생성자가 들어가있다면 자식 클래스에 부모 클래스의 생성자를 붙여줘야 한다.
class Parent {
int x = 10;
public Parent(int x) {
this.x = x;
}
}
class Child extends Parent {
int x = 20;
public Child(int x) {
// super(10); 없으면 컴파일 에러!
this.x = x;
}
public void method() {
System.out.println(this.x);
System.out.println(super.x);
}
}
public class Test {
public static void main(String[] args) {
Child c = new Child(20);
c.method();
}
}
조상 클래스의 멤버변수는 이처럼 조상의 생성자에 의해 초기화되도록 해야 하는 것이다.
패키지란 클래스 또는 인터페이스의 묶음이다.
클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리인 것이다.
다른 패키지의 클래스를 사용할 때 사용한다.
static member를 호출할 때 클래스 이름을 생략할 수 있게 해준다.
import static java.lang.System.out;
out.println("print");
// System.out.println("print");
static, final, abstract, native, transient, synchronized, volatile, strictfp
class Singleton {
private static Singleton s = new Singleton();
private Singleton() {
...
}
public static Singleton getInstance() { // 직접 인스턴스를 생성 못 하니 static을 붙여야함.
return s
}
}
private를 붙이게 되면 같은 클래스내에서밖에 쓰지 못 하니 직접 인스턴스를 생성할 수 없다.
또한, 다른 클래스의 조상이 될 수 없다.
자식 클래스의 인스턴스를 생성할 때 조상 클래스의 생성자가 실행되어야 하는데 접근 제어자가 private이기 때문이다.
public 메소드를 통해 접근하게 만듬으로써 인스턴스의 개수를 제한할 수 있다.
인스턴스 멤버를 사용하지 않는 메소드는 static 메소드로 만들자.
그러면 인스턴스를 생성하지 않아도 되어 편리하고 속도도 더 빠르다.
메소드에 static과 abstract를 함께 사용할 수 없다.
static 메소드는 몸통이 있는 메소드에만 사용할 수 있기 때문이다.
클래스에 abstract와 final을 동시에 사용할 수 없다.
abstract는 상속을 통해서 완성이 되기 때문이다.
abstract메소드의 접근 제어자가 private일 수 없다.
위와 동일한 이유이다.
메소드의 private와 final을 같이 사용할 필요는 없다.
private자체로 오버라이딩이 될 수 없다는 전제이기 때문이다.
조상클래스 타입의 참조변수로 자식클래스의 인스턴스를 참조할 수 있도록 한 것이다.
class Parent {
...
}
class Child extends Parent{
...
}
class Test {
public static void main(String[] args) {
Parent p = new Child();
}
}
Parent p = new Child();
는 parent의 멤버에만 참조가 가능하다. child의 메소드, 멤버 변수를 참조할 수 없다는 뜻이다.Child c = new Parent();
이와 같이 반대로 하면 컴파일 에러가 발생한다.
인스턴스인 Parent보다 참조변수 c가 사용할 수 있는 멤버 개수가 더 많기 때문이다.
즉, 참조변수가 사용할 수 있는 멤버의 개수 <= 인스턴스 멤버의 개수
를 만족해야 한다.
Child c = new Child();
Parent p = c;
c = (Child) p;
이렇게 쓰면 가능하긴 하다.
바로 다운캐스팅을 하게되면 컴파일은 괜찮지만, 실행시 에러가 뜬다. 업캐스팅 -> 다운캐스팅을 해야한다.
컴파일 시에는 참조변수간의 타입만 체크하기 때문이다.
실행 시에는 인스턴스의 타입까지 고려하기 때문에 실행시에는 에러가 발생된다.
멤버변수는 참조변수 타입에 따른다.
추상클래스는 미완성 설계도에 비유할 수 있다.
추상클래스 자체로는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상 클래스로서 중요한 의미를 갖는다.
그러니깐 "자식클래스들은 이 메소드를 써야해!" 하고 틀을 만들어주는 것이다.
추상클래스에도 생성자가 있고, 멤버변수와 메소드를 가질 수 있다.
기존 클래스의 공통부분을 뽑아 조상클래스로 만드는 개념이다.
상속받는 클래스에 따라 메소드의 내용이 달라질 수 있다.
그렇기 때문에 선언부만을 작성해서 해당 기능의 목적을 알려주는 것이다.
자식클래스들은 해당 메소드를 받아 적절히 구현하는 것이다.
abstract class Player {
abstract void play(int post);
void stop();
}
메소드에 abstract
를 붙이면 자식 클래스에서 해당 메소드를 구현하도록 강제할 수 있다.
구현된 것은 아무것도 없고 밑그림만 그려져 있는 기본 설계도와 같다.
interface 인터페이스이름 {
public static final 타입 상수이름 = 값;
public abstract 메소드이름(매개변수목록);
}
public static final
이어야 하며, 이를 생략할 수 있다.public abstract
이어야 하며, 이를 생략할 수 있다.생략하면 컴파일러가 자동으로 추가해준다.
JDK1.8부터는 static
메소드와, default
메소드를 허용해준다.
interface Movable {
void move(int x, int y);
}
interface Attackable {
void attack(Unit u);
}
interface Fightable extends Movable, Attackable {}
클래스와 달리 다중상속이 가능하다.
interface는 Object클래스와 같은 최고 조상이 없다.
class 클래스이름 implements 인터페이스이름 {
...
}
extends
가 아닌 implements
를 사용한다.
public
을 꼭 붙여야 한다. 접근 제어자의 범위를 같거나 좁게해야하기 때문이다. 오버라이딩의 주의사항을 읽어보자.class Fighter extends Unit implements Fightable {
...
}
Fightable f = new Fighter();
void attack(Fightable f) {
...
}
그러면 1번에 의해서 다음과 같이 할 수 있다.
attack(new Fighter())
Fightable method() {
return new Fighter();
}
static메소드는 인스턴스와 관계가 없기 때문에 딱히 큰 상관이 없다.
인터페이스가 변경될 일이 생길 수도 있다.
이 때 메소드를 추가하면 이를 구현한 클래스들을 모두 변경해야 할 것이다.
이 때문에 default method라는 것을 고안해냈다.
interface MyInterface {
void method();
default void newMethod();
}
다음과 같이 default를 추가하면 굳이 구현하지 않아도 된다.
클래스 내부에 선언되는 클래스이다.
외부 클래스의 멤버변수 선언위치에 선언한다. 외부 클래스의 instance멤버처럼 다루어진다.
외부 클래스의 멤버변수 선언위치에 선언한다. 외부 클래스의 static멤버처럼 다루어진다.
외부 클래스의 메소드나 초기화블럭 안에 선언한다. 선언된 내부에서만 사용가능하다.
클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스이다.(일회용)
class Outer{
void method() {
int lv = 0;
final int LV = 0;
class LocalInner {
int liv1 = lv; // JDK1.8부터는 에러가 안난다.
int liv2 = LV;
}
}
}
다음과 같이 지역 클래스일 경우 생각해야한다.
지역 클래스는 상수만 접근이 가능하다.
하지만 JDK1.8부터는 final을 생략할 수 있다. 컴파일러가 알아서 붙여주기 때문이다.
상수만 접근이 가능한 이유