- 다형성
- 추상클래스
- 인터페이스
- 내부클래스
다형성이란 여러 가지 형태를 가질 수 있는 능력
을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.
조상클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하였다.
밑의 예제를 통해 설명한다.
class Tv{
boolean power;
int channel;
void power(){ power = !power;}
void channelUp(){channel++;}
void channelDown(){channel--;}
}
class CaptionTv extends Tv{
String text;
void caption(){ //생략}
}
Tv클래스와 CaptionTv클래스가 위와 같이 정의되어 있을 때 두 클래스의 인스턴스를 생성하여 사용하기 위해서는
Tv t = new Tv();
CaptionTv ct = new CaptionTv();
위와 같이 작성해야 한다. 지금까지 우리는 생성된 인스턴스를 다루기 위해서, 인스턴스 타입과 일치하는 타입의 참조변수만을 사용했다. (Tv인스턴스를 다루기위해서는 Tv타입의 참조변수를 사용..)
만약 두 클래스가 상속관계에 있을 경우, 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.
Tv t = new CaptionTv(); // 참조타입 - 조상 클래스 타입
// 실제 인스턴스 - 자손 클래스
인스턴스를 같은 타입의 참조변수로 참조하는 것과 조상타입의 참조변수로 참조하는 것은 어떤 차이가 있는지에 대해 알아본다.
CaptionTv c = new CaptionTv();
Tv t = new CaptionTv();
위의 코드에서 CaptionTv 인스턴스를 2개 생성하고, 참조변수 c와 t가 생성된 인스턴스를 하나씩 참조하도록 하였다. 이 경우 실제 인스턴스가 CaptionTv라고 하여도 t 참조변수는 CaptionTv의 모든 멤버를 사용할 수 없다. Tv타입의 참조변수로는 CaptionTv 인스턴스 중에서 Tv클래스 멤버들(상속받은 멤버포함)만 사용할 수 있다. ( 생성된 멤버변수중 CaptionTv에만 속해 있는 멤버-text,caption() 들은 사용할 수 없다. )
둘 다 같은 타입의 인스턴스이지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.
자손타입의 참조변수로 조상클래스의 인스턴스를 참조하는 것은 불가능하다.
실제 인스턴스인 조상클래스의 멤버 개수보다 참조변수(자손타입)가 사용할 수 있는 멤버 개수가 더 많기 때문이다. --> 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 찹조할 수는 없다.
기본형과 마찬가지로 참조변수도 형변환이 가능하다. 단, 서로 상속관계에 있는 클래스 사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손탕비의 참조변수로의 형변환만 가능하다.
기본형변수에서 작은 자료형에서 큰 자료형으로의 변환은 생략이 가능하듯이, 참조형 변수의 형변환에서는 자손타입의 참조변수를 조상타입의 형변환 하는 경우에 형변환을 생략할 수 있다.
자손타입 -> 조상타입(Up-casting) : 형변환 생략가능
조상타입 -> 자손타입(Down-casting) : 형변환 생략불가
참조변수간의 형변환 역시 캐스트 연산자를 사용하며, 괄호()안에 변환하고자 하는 타입의 이름(클래스명)을 적어주면 된다. (자바에서는 자손과 조상 관계만 존재하므로 형제 관계는 존재하지 않는다. - 형변환 불가 )
예로 조상클래스Car와 자손클래스FireEngine이 존재한다고 하자.
Car car = null;
FireEngine f = new FireEngine();
FireEngine f2 = null;
car = f; //upCasting - 형변환 생략 가능
f2 = (FireEngine)car; //downCasting - 형변환 생략 불가
위의 예제와 같이 자손타입을 조상타입으로 변경하는 경우에는 형변환이 생략가능하지만 조상타입을 자손타입으로 변경하는 경우에는 형변환의 생략이 불가하다.
자손타입의 참조변수를 조상타입의 참조변수로 형변환 하는 것은 조상타입의 참조변수가 다룰 수 있는 멤버의 개수가 실제 인스턴스가 갖고 있는 멤버의 개수보다 적을 것이 분명하므로 문제가 되지 않는다. 그래서 형변환을 생략할 수 있는 것이다.
하지만, 조상타입의 참조변수를 자손타입의 찹조변수로 형변환하는 것은 멤버개수가 늘어나는 것이므로, 실제 인스턴스의 개수보다 참조변수가 사용할 수 있는 멤버개수가 더 많아지므로 문제가 발생할 가능성이 있다.
그래서, 자손타입으로는 형변환을 생략할 수 없으며, 형변환을 수행하기 전에 instanceof
연산자를 사용해서 참조변수가 참조하고 있는 실제 인스턴스의 타입을 확인하는 것이 안전하다.
형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것이 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다. 단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에 사용할 수 있는 멤버의 범위(개수)를 조절하는 것 뿐이다.
public class CastingTest2 {
public static void main(String[] args) {
Car car =new Car();
Car car2 = null;
FireEngine fe = null;
car.drive();
fe = (FireEngine)car;
fe.drive();
car2 = fe;
car2.drive();
}
}
위의 예제는 사진과 같은 에러를 발생한다. 에러는 fe = (FireEngine)car;
에서 발생하며 겉으로 보기에는 조상타입의 참조변수를 자손타입으로 형변환을 제대로 수행할 것 처럼 생각이 들지만, 조상 타입의 참조변수인 car가 이미 조상 클래스의 인스턴스를 참조하고 있으므로 멤버개수가 더 많은 자손 타입으로 변경이 불가 한 것이다. (조상타입의 인스턴스를 자손타입의 참조변수로 참조하는 것은 허용되지 않는다. )
서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다. 그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.
참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자를 사용한다. 주로 조건문에 사요되며, 왼쪽에는 참조변수를 오른쪽에는 타입이 피연산자로 위치한다. 연산의 결과로는 boolean값인 true나 false가 반환된다.
instanceof의 결과로 true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻한다. 조상타입의 참조변수로 자손타입의 인스턴스를 찹조할 수 있기 때문에, 참조변수의 타입과 인스턴스의 타입이 항상 일치 하지 않을 수 있다. 조상타입의 참조변수는 실제 인스턴스의 모든 멤버를 사용할 수 없기 때문에 자손타입으로의 형변환(downCasting)을 해야만 모든 멤버를 사용할 수 있다.
package CH07_02;
public class InstanceOfTest {
public static void main(String[] args) {
FireEngine fe = new FireEngine();
if(fe instanceof FireEngine) {
System.out.println("This is insatnceof FireEngine");
}
if(fe instanceof Car) {
System.out.println("This is insatceof Car");
}
if(fe instanceof Object) {
System.out.println("This is instanceof Object");
}
System.out.println(fe.getClass().getName());
}
}
위의 예제를 보면 생성된 인스턴스는 FireEngine타입임에도 Object타입과 Car타입의 instanceof 연산에서도 true를 결과로 얻었다. 그 이유는 FireEngine클래스는 Object클래스와 Car클래스의 자손 클래스이므로 조상의 멤버들을 상속받았기 때문에, 결국엔 조상 클래스의 인스턴스를 모두 포함하고 있는 셈이기 때문이다.
실제 인스턴스와 같은 타입의 instanceof 연산 이외에 조상타입의 instanceof 연산에도 true를 결과로 얻으며, 검사한 타입으로 형변환을 해도 아무런 문제가 없다는 뜻이다.
어떤 타입에 대한 instanceof 연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
조상클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자손클래스에 중복으로 정의했을 때, 조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우는 서로 다른 결과를 얻는다.
메서드의 경우 조상클래스의 메서드를 자손 클래스에서 오버라이딩 한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.
멤버변수가 조상클래스와 자손클래스에 중복으로 정의된 경우 조상타입의 참조변수를 사용했을 때는 조상클래스의 멤버변수를, 자손타입의 참조변수를 사용했을 때는 자손클래스의 멤버변수를 사용한다.
중복정의되지 않은 경우에는 조상타입의 참조변수를 사용했을 때와 자손타입의 참조변수를 사용했을 때의 차이가 없다. 중복되지 않은 경우에는 하나뿐이므로 선택의 여지가 없기 때문이다.
참조변수의 다형적인 특성은 메서드의 매개변수에서도 적용된다. 메서드의 매개변수에 다형성을 적용하면 하나의 메서드로 간단한 처리가 가능하다.
void buy(Product p){
money = money - p.price;
bonusPoint = bonusPoint + p.bonusPoint;
}
매개변수가 Product 타입의 인스턴스라는 것은, 메서드의 매개변수로 Product 클래스의 자손타입의 참조변수면 어느 것이나 매개변수로 받아드릴 수 있다는 것이다. 이렇게 하면 다른 클래스를 매개변수로 받고 싶은 경우 Product 클래스를 상속받기만 하면 매개변수로 받아들여질 수 있다.
다른 예 print(Object obj) : Object타입의 변수를 매개변수로 받을 수 있고 모든 클래스의 조상이므로 어떤 타입의 인스턴스든 매개변수로 가능하다. 이 하나의 메서드로 모든 타입의 인스턴스를 처리할 수 있는 것이다. 이 메서드는 매개변수에 toString()을 호출하여 문자열을 얻어서 출력한다.
조상타입의 참조변수로 자손타입의 인스턴스를 참조하는 것이 가능하다. 즉, 다른 자손타입 객체들의 배열을 조상타입의 참조변수배열로 생성하는 것이 가능하다.
Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();
이처럼 조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다. (또는, 묶어서 다루고싶은 객체들의 상속관계를 따져서 가장 가까운 공통조상 클래스 타입의 참조변수 배열을 생성해서 객체들을 저장하면 된다.)
Vector 클래스
내부적으로 Object타입의 배열을 가지고 있어서, 이 배열에 객체를 추가하거나 제거할 수 있게 작성되어 있다. 배열의 크기를 알아서 관리해주므로 저장할 인스턴스의 개수에 신경 쓰지 않아도 된다. 동적으로 크기가 관리되는 객체배열일 뿐이다.
메서드/생성자 | 설명 |
---|---|
Vector() | 10개의 객체를 저장할 수 있는 Vector 인스턴스를 생성한다. 10개 이상의 인스턴스가 저장되면, 자동적으로 크기가 증가된다. |
boolean add(Object o) | Vector에 객체를 추가한다. 추가에 성공하면 true,실패하면 fasle를 반환한다. |
boolean remove(Object o) | Vector에 저장되어있는 객체를 제거한다. 성공시 true, 실패시 fasle |
boolean isEmpty() | Vector 가 비어있는지 검사한다. 비어있으면 true, 아니면 fasle |
Object get(int index) | 지정된 위치의 객체를 반환한다. 적절한 타입으로의 형변환이 필요하다. |
int size() | Vector에 저장된 객체의 개수를 반환한다. |
클래스를 설계도에 비유한다면, 추상클래스는 미완성 설계도에 비유할 수 있다. 클래스가 미완성이라는 것은 멤버의 개수에 관계된 것이 아닌, 단지 미완성 메서드(추상메서드)를 포함하고 있다는 의미이다.
추상클래스로는 인스턴스를 생성할 수 없다. 추상 클래스는 상속을 통해 자손클래스에 의해서만 완성될 수 있다. 추상 클래스는 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 갖는다. 즉, 공통부분만을 따로 미완성 설계도로 만들어두고 해당 설계도를 이용하여 각각의 설게도를 완성하는 것이 효율적일 것이다.
추상 클래스는 키워드 abstract
만 붙이기만 하면된다. 해당 키워드르 보고 이 클래스는 상속을 받아 완성을 시켜야 된다는 것을 확인할 수 있다. 추상 클래스에도 생성자,멤버변수,메서드를 가질 수 있다.
abstract class 클래스이름{
...
}
선언부만 작성하고 구현부는 작성하지 않은 채로 남겨두는 것이 추상메서드이다. 즉, 설계만 해놓고 실제 수행될 내용은 작성하지 않았기 때문에 미완성 메서드인 것이다. 미완성으로 남겨두는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서는 선언부만 작성하고 , 주석으로 어떤 기능 수행해야 하는 메서드인지만을 표시한다. 실제 내용은 상속받은 클래스에서 구현한다. -- 자손클래스가 상황에 맞게 구현해주어야 한다.
키워드 abstract
를 앞에 붙여주고 구현부가 없으므로 괄호{} 대신 문장의 끝을 알리는 ;
을 적어준다.
abstract 리턴타입 메서드이름();
추상 클래스를 상속받은 자손 클래스에서는 추상 클래스의 모든 추상메서드를 구현해주어야 하며 하나라도 구현되지 않을 경우 자손클래스도 추상클래스로 지정해주어야 한다.
여러 클래스에서 공통적으로 사용될 수 있는 클래스를 바로 작성하기도 하고, 기존의 클래스의 공통적인 부분을 뽑아서 추상클래스로 만들어 상속하도록 하는 경우도 있다.
추상의 사전적 의미
낱낱의 구체적 표상이나 개념에서 공통된 성질을 뽑아 이를 일반적인 개념으로 파악하는 정신 작용
상속이 자손 클래스를 만드는데 조상 클래스르 사용하는 것이라면, 이와 반대로 추상화는 기존의 클래스에서 공통 부분을 뽑아내서 조상 클래스를 만들어 내는 것이라고 할 수 있다. 상속 계층도를 따라 내려갈 수록 클래스는 점점 기능이 추가되어 구체화의 정도가 심해지며, 상속 계층도를 따라 올라갈 수록 클래스는 추상화의 정도가 심해진다.
추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만들어내는 작업
구체화 : 상속을 통해 클래스를 구현,확장하는 작업
굳이 abstract을 붙여 추상메서드로 만들어주는 이유는 자손클래스에서 오구현을 강제하고 있는 것이다. 자손 클래스에서 이 추상메서드 내용을 구현해주어야 한다는 사실을 인식하고 자신의 클래스에 알맞게 구현할 것이다.
인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 가지만 추상클래스보다 추상화정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수 만을 멤버로 가질 수 있다.
즉, 인터페이스는 구현된 것은 아무것도 없고 밑그림만 그려져있는 기본 설계도
라 할 수 있다. 다른 클래스를 작성하는데 도움을 줄 목적으로 작성한다.
키워드로 class대신 interface
를 사용한다. 인터페이스도 접근제어자로 public 또는 default를 선택할 수 있다.
interface 인터페이스이름 {
public static final 타입 상수이름 = 값;
public abstract 메서드이름(매개변수이름);
}
인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속, 여러개의 인터페이스로부터 상속을 받는 것이 가능하다.
클래스의 상속과 마찬가지로 자손 인터페이스는 조상 인터페이스에 정의된 멤버를 모두 상속받는다.
인터페이스도 추상클래스 처럼 그 자체로는 인스턴스를 만들 수 없으며, 인터페이스는 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 하는데 , 추상클래스가 자신을 상속받는 클래스를 정의하는 것과 같이 implements
키워드를 사용하여 인터페이스를 구현하는 클래스를 정의해주면 된다.
class 클래스이름 implements 인터페이스이름{
//추상메서드 구현
}
만일 구현해야 하는 메서드중 일부만 구현한다면 abstract
를 붙여 추상 클래스로 선언해야 한다.
상속과 구현을 동시에 할 수도 있다.
오버라이딩 시에는 조상메서드보다 넓은 범위의 접근 제어자를 지정해야 한다. 인터페이스에서 메서드는
public abtstact
로 선언이 되고 그러므로 해당 인터페이스를 구현하는 클래스에서는 메서드 구현시 public 접근제어자를 사용해야 한다.
두 조상으로 부터 상속받는 멤버중에서 멤버변수의 이름이 같거나 메서드의 선언부가 일치하고 구현내용이 다르다면 이 두 조상으로부터 어떤 조상의 것을 상속받게 되는 것인지 알수 없다. 그래서 자바에서는 다중 상속을 허용하지 않는다. 그러나 C++에서는 다중상속을 허용하기 때문에 자바는 '자바도 인터페이스를 이용하면 다중 상속이 가능하다'라고 하는 것일 뿐 자바에서 인터페이스로 다중 상속을 구현하는 경우는 거의 없다.
인터페이스는 static상수만 정의할 수 있으므로 조상클래스의 멤버변수와 충돌하는 경우는 거의 없고 충돌한다 하더라도 클래스이름으로 구분이 가능하다. 추상메서드는 구현내용이 전혀 없으므로 조상클래스의 메서드의 선언부가 일치하는 경우에는 당연히 조상 클래스 쪽의 메서드를 상속받으면 되므로 문제가 되지 않는다.
자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다. 인터페이스 역시 이를 구현한 클래스의 조상이라고 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다.
예로, Fightable 인터페이스를 구현한 Fighter 클래스의 인스턴스는 해당 인터페이스 참조변수로 참조가 가능하다.
Fightable f = (Fightable)new Fighter();
Fightable f2 = new Fighter();
따라서 인터페이스는 메서드의 매개변수 타입으로도 사용될 수 있따.
void attack(Fightable f){
...
}
인터페이스 타입의 매개변수를 가진다는 의미는 매개변수로 해당 인터페이스를 구현한 클래스의 인스턴스를 제공해야 한다는 의미이다.
메서드의 리턴타입으로 인터페이스 타입을 지정하는 것 역시 가능하다. 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
package CH07_02;
public class ParserTest {
public static void main(String[] args) {
Parseable parser = ParserManager.getParser("XML");
parser.parse("document.xml");
parser = ParserManager.getParser("HTML");
parser.parse("document2.html");
}
}
interface Parseable{
public abstract void parse(String fileName); //구문 분석작업을 수행한다.
}
class ParserManager{
public static Parseable getParser(String type) {
if(type.equals("XML")) {
return new XMLParser();
}else {
Parseable p = new HTMLParser();
return p;
}
}
}
class XMLParser implements Parseable{
public void parse(String fileName) {
System.out.println(fileName + " XML parsing completed");
}
}
class HTMLParser implements Parseable{
public void parse(String fileName) {
System.out.println(fileName + " HTML parsing completed");
}
}
Parseable 인터페이스는 구문 분석을 수행하는 기능을 구현할 목적으로 추상메서드 parse(String fileName)
을 정의했다. 그리고 XMLParser클래스와 HTMLParser 클래스는 Parseable 인터페이스를 구현하였다. ParserManager클래스의 getParser메서드는 매개변수로 넘겨받는 type의 값에 따라 XMLParser인스턴스 또는 HTMLParser 인스턴스를 반환한다.
getParser 의 수행결과로 참조변수 parser는 인터페이스 Praseable을 구현한 클래스의 인스턴스를 받는다. 각 클래스에 따라 해당하는 메서드가 수행된다.
일단, 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다. 동시에 다른 쪽에 인터페이스를 구현하는 클래스를 작성하게 되면, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있다.
기본 틀을 인터페이스로 작성한 다음, 개발자에게 인터페이스를 구현하게 하여 일관되고 정형화된 프로그램 개발이 가능하다.
서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있다.
인터페이스를 이용하면 클래스의 선언과 구현을 분리시킬 수 있기 때문에 실제 구현에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스와 클래스간의 관계를 인터페이스를 이용하여 간접관계로 변경하면 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.
- 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
- 메서드를 사용(호출)하는 쪽(user)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다. (내용은 몰라도 된다.)
class A{
public void methodA(B b){
b.methodB();
}
}
class B{
public void methodB(){
System.out.println("methodB()");
}
}
class InterfaceTest{
public static void main(String[] args){
A a = new A();
a.methodA(new B());
}
}
위와 같이 클래스 A와 클래스 B가 있다고 하자. 클래스 A(User)는 클래스 B(Provider)의 인스턴스를 생성하고 메서드를 호출한다. 이 두 클래스는 직접적인 관계가 있다. 이럴 경우 클래스 A를 작성하려면 이미 클래스 B가 작성되어 있어야 하며 클래스 B의 메소드의 선언부가 변경될 경우 클래스 A도 변경되어야 한다.
인터페이스를 사용한다면 위의 문제를 해결 할 수 있다. 먼저 인터페이스를 이용해서 클래스 B(Provider)의 선언과 구현을 분리해 주어야 한다.
interface I {
// 클래스 B의 메서드를 정의하는 인터페이스
public abstract void methodB();
}
class B implements I{
// 클래스 B가 I를 구현하도록 한다.
public void methodB(){
System.out.println("methodB");
}
}
위와 같이 인터페이스로 선언과 구현부를 분리해주면 클래스 A(User) 에서는 인터페이스를 사용하여 작성할 수 있다.
class A{
void methodA(I i){
i.methodB();
}
}
클래스 A는 여전히 클래스 B의 메서드를 호출하지만, 클래스 A는 인터페이스 I 하고만 직접적인 관계에 있기 때문에 클래스 B의 변경에 영향을 받지 않는다. 또한, 실제로 사용되는 클래스의 이름을 몰라도 된다.
인터페이스 I 는 실제 구현 내용을 감싸고 있는 껍데기 이며, 클래스 A는 껍데기 안에 어떤 알맹이가 들었는지 몰라도 된다.
원래는 인터페이스에 추상 메서드만 선언이 가능하였는데 , JDK1.8부터 디폴트메서드와 static메서드의 추가가 가능해졌다.
인터페이스에 메서드를 추가한다는 것은 이 인터페이스를 구현한 모든 클래스들이 새로 추가된 메서드를 구현해야 한다. 인터페이스는 아무리 설계를 잘해도 언젠가 변경은 발생하기 마련이다. 이러한 문제를 해결하기 위해 디폴드 메서드 를 사용한다.
디폴트 메서드 는 추상메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.
앞에 default
키워드를 붙이며, 추상 메서드와 달리 일반 메서드처럼 몸통{}이 있어야한다. (접근제어자 - public )
새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우 해결 규칙
1. 여러 인터페이스의 디폴트 메서드 간의 충돌 : 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 한다.
2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌 : 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
클래스 내에 선언된 클래스이다. 내부클래스의 장점은 내부 클래스에서 외부클래스의 멤버들을 쉽게 접근할 수 있으며, 코드의 복잡성을 줄일 수 있다.(캡슐화)
내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다. 내부 클래스는 마치 변수를 선언하는 것과 같은 위치에 선언할 수 있으며, 선언 위치에 따라 다르게 구분된다.
내부 클래스 | 특징 |
---|---|
인스턴스 클래스 | 외부 클래스의 멤버변수 선언 위치에 선언하며, 외부 클래스의 인스턴스 멤버 처럼 다뤄진다. 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언된다. |
static 클래스 | 외부 클래스의 멤버변수 선언 위치에 선언하며, 외부 클래스의 static 멤버처럼 다뤄진다. 주로 static메서드에서 사용될 목적으로 선언된다. |
지역 클래스 | 외부 클래스의 메서드나 초기화 블럭안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다. |
익명 클래스 | 클래스의 선언과 객체의 생성을 동시에 하는 이름 없는 클래스(일회용) |
class Outer{
class InstanceInner {}
static class StaticInner {}
void myMethod(){
class LocalInner{}
}
}
다른 내부 클래스들과는 달리 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.
new 조상클래스이름() {
// 멤버 선언
}
new 구현인터페이스이름(){
//멤버 선언
}
이름이 없기 때문에 생성자도 가질수 없으며, 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다. 오로지 단하나의 클래스만 상속받거나 하나의 인터페이스만을 구현할 수 있다.