인터페이스 내에는 원래 추상 메서드만 선언 가능했지만..
JDK 1.8이후로 default 메서드와 static 메서드도 추가할 수 있게됨.
왜 추가되었을까?
조상클래스에 새로운 메서드를 추가하는 것은 별 일이 아니지만.. 인터페이스의 경우에는 좀 큰일임.
인터페이스에 메서드를 추가한다는 것은, 추상메서드를 추가한다는 것.
그럼 결국 해당 인터페이스를 구현한 모든 클래스 내에서도 추상메서드 새로 구현해야 함.
인터페이스가 변경되지 않으면 좋겠지만.. 아무리 설계를 잘 하더라도 언젠간 변경이 필요할 수 있음.
▶ 디폴트 메서드(default method) 등장
▶ 새로 추가되어도 인터페이스를 구현한 클래스들을 변경하지 않아도 됨.
디폴트메서드는 앞에 default 키워드가 필요, 추상메서드와 달리 일반 메서드처럼 몸통이 필요하다.
접근제어자는 public이며 생략 가능함.
interface MyInterface {
void method();
default void newMethod() { }
}
새로 추가된 디폴트 메서드가 기존 메서드와 이름이 중복되어 충돌하는 경우가 발생.
이 충돌을 해결하기 위한 규칙은 다음과 같다.
1) 여러 인터페이스의 디폴트 메서드 간 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야한다.
2) 디폴트 메서드와 조상 클래스의 메서드 간 충돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
interface MyInterface{
default void method1() {
System.out.println("method1() in MyInterface");
}
default void method2() {
System.out.println("method2() in MyInterface");
}
static void staticMethod() {
System.out.println("staticMethod() in MyInterface");
}
}
interface MyInterface2{
default void method1() {
System.out.println("method1() in MyInterface2");
}
static void staticMethod() {
System.out.println("staticMethod() in MyInterface2");
}
}
class Parent3 {
public void method2() {
System.out.println("method2() in Parent2");
}
}
class Child3 extends Parent3 implements MyInterface, MyInterface2 {
public void method1() {
System.out.println("method1() in Child3");
}
}
public class Ex7_11 {
public static void main(String[] args) {
Child3 c = new Child3();
c.method1();
c.method2();
MyInterface.staticMethod();
MyInterface2.staticMethod();
}
}
뭔가 있어보이지만 당연한 결과고 당연한 코드임.
내부클래스(inner class)
클래스 내에 선언된 클래스
클래스에 다른 클래스를 선언하는 이유는.. 두 클래스가 서로 긴밀한 관계에 있기 때문이다.
- 한 클래스를 다른 클래스의 내부 클래스로 선언하면 두 클래스의 멤버들 간 서로 쉽게 접근할 수 있다.
- 또한 외부에 불필요한 클래스를 감춤으로써 코드 복잡성을 줄일 수 있다. (캡슐화)
class A {
...
class B {
...
}
}
이 때 내부클래스인 B는 외부클래스 A를 제외하고는 다른 클래스에서 잘 사용되지 않는 것이어야 함.
[내부클래스 종류와 특징]
내부클래스의 종류는 선언위치에 따라 나뉜다. (마치 선언위치에 따른 변수 종류처럼.)
내부 클래스 | 특징 |
인스턴스 클래스 (instance class) |
외부클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스 멤버처럼 다뤄진다. 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언. |
스태틱 클래스 (static class) |
외부 클래스의 멤버변수 선언위치에 선언. 외부 클래스의 static 멤버처럼 다뤄진다. 주로 외부 클래스의 static 멤버, 특히 static 메서드에서 사용될 목적으로 선언. |
지역 클래스 (local class) |
외부 클래스의 메서드나 초기화블럭 안에 선언, 선언된 영역 내부에서만 사용 가능 |
익명 클래스 (annoymous class) |
클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스 (일회용) |
class Outer {
int iv = 0; //인스턴스변수
static int cv = 0; //static변수
void myMethod() {
int lv = 0; //지역변수
}
}
class Outer {
class InstanceInner { .. } //인스턴스 클래스
static class StaticInner { ... } //스태틱 클래스
void myMethod(){
class LocalInner { ... } //지역 클래스
}
}
아무튼 각 내부 클래스는 위치에 따라 동일한 유효범위(scope) 및 접근성(accessibility)을 갖는다.
내부클래스의 제어자 및 접근성
class Outer {
private int iv = 0; //인스턴스변수
protected static int cv = 0; //static변수
void myMethod() {
int lv = 0; //지역변수
}
}
class Outer {
private class InstanceInner { .. } //인스턴스 클래스
protected static class StaticInner { ... } //스태틱 클래스
void myMethod(){
class LocalInner { ... } //지역 클래스
}
}
내부 클래스도 멤버변수와 같은 성질을 가지기에,
외부 클래스의 멤버처럼 간주되고 인스턴스 멤버와 static멤버 간의 규칙이 내부클래스에도 똑같이 적용된다.
내부클래스도 클래스이기에... abstract이나 final과 같은 제어자 사용 가능, 멤버변수처럼 private, protected 가능
[예제 1]
public class Ex7_12 {
class InstanceInner { // 내부 인스턴스 클래스
int iv = 100;
// static int cv = 100; 스태틱변수 선언 불가능. 에러
final static int CONST = 100;
}
static class StaticInner { //내부 스태틱 클래스
int iv = 200;
static int cv = 200; // static 클래스만 static 멤버 선언 가능
}
void myMethod() {
class LocalInner {
int iv = 100;
// static int cv = 100; 스태틱변수 선언 불가능. 에러
final static int CONST = 100;
}
}
public static void main(String[] args) {
System.out.println(InstanceInner.CONST);
System.out.println(StaticInner.cv);
}
}
- 내부클래스 중 static 클래스만 static 멤버를 가질 수 있다.
- 내부 클래스에 static 변수를 선언해야 한다면.. static 클래스로 선언해야 함.
- 다만 final과 static이 동시에 붙은 변수는 상수이므로, 모든 내부 클래스에서 정의 가능.
[예제 2]
public class Ex7_13 {
//멤버처럼 사용되는 내부 클래스들
class InstanceInner { } // 인스턴스 변수. 모든 멤버에 접근 가능.
static class StaticInner { } // static 변수. 인스턴스 멤버 뱨고 다 접근 가능.
//인스턴스 멤버 간에는 서로 직접 접근이 가능하다.
InstanceInner iv = new InstanceInner();
// static 멤버 간에는 서로 직접 접근이 가능하다
static StaticInner cv = new StaticInner();
static void staticMethod() {
//static멤버는 인스턴스 멤버에 직접 접근할 수 없다.
//InstanceInner obj1 = new InstanceInner();
StaticInner obj2 = new StaticInner();
// 굳이 접근하려면 아래와 같이 객체를 생성해야 함.
// 인스턴스 클래스는 외부 클래스를 먼저 생성해야 생성 가능
Ex7_13 outer = new Ex7_13();
InstanceInner obj1 = outer.new InstanceInner();
}
void instanceMethod() { //외부클래스의 인스턴스 멤버
//인스턴스 메서드에서는 인스턴스 멤버와 static 멤버 모두 접근 가능하다.
InstanceInner obj1 = new InstanceInner();
StaticInner onj2 = new StaticInner();
//메서드 내 지역적으로 선언된 내부 클래스는 외부에서 접근할 수 없음.
//LocalInner lv = new LocalInner();
}
void myMethod() {
class LocalInner { } //지역 내부클래스
LocalInner lv = new LocalInner();
}
}
코드가 좀 난잡하다.
정리하자면..
1) 인스턴스 멤버: 같은 클래스 내 모든 멤버 호출 가능 (인스턴스 멤버 <= 인스턴스 클래스)
2) static 멤버: 인스턴스 멤버 뺴고 다 가능 (static 멤버 <= static 클래스)
(직접접근이 불가능하다는 것임)
[예제 3]
class Outer {
private int outerIv = 0;
static int outerCv = 0;
//내부 인스턴스 클래스
class InstanceInner {
int iiv = outerIv; //외부클래스의 private 멤버도 접근 가능!
int iiv2 = outerCv;
}
//내부 스태틱 클래스(static member)
static class StaticInner {
//int siv = outerIv; 불가능
static int scv = outerCv;
}
void myMethod() {
int lv = 0;
final int LV = 0; //JDK1.8부터 final 생략 가능
// 내부 클래스(로컬)
class LocalInner{
int liv=outerIv;
int ilv2=outerCv;
//외부클래스의 지역변수는 final 붙은 변수(상수)만 접근 가능하다.
//int liv3=lv; 에러, 그러나 JDK1.8부터는 에러 아님
int liv4=LV;
}
}
}
내부 클래스에서 외부 클래스의 변수들에 대한 접근성을 보여주는 예제.
인스턴스 클래스(Instance class): 외부 클래스인 Outer의 인스턴스멤버이기 때문에.. 인스턴스 변수인 outerlv와 static 변수 outerCv를 모두 사용할 수 있다. outerlv의 접근 제어자가 private임에도 사용 가능.
스태틱 클래스(static class): 외부 클래스 Outer의 스태틱 멤버이기 때문에... 외부클래스의 인스턴스멤버인 outerlv와 InstanceInner(클래스) 를 사용할 수 없다. static 멤버인 outerCv만 사용 가능.
지역 클래스(LocalInner): 외부 클래스의 인스턴스멤버와 static 멤버를 모두 사용할 수 있음. 지역 클래스가 포함된 메서드에 정의된 지역변수도 사용 가능. 그러나 final이 붙은 지역변수만 접근 가능(상수)
(이유) 지역 클래스의 인스턴스 → 소멸된 지역변수를 참조하려는 경우가 발생할 수 있기 때문.
맨 아래의 myMethod() 메서드 내부 클래스를 보자 (지역클래스)
"외부클래스의 지역변수는 final 붙은 변수(상수)만 접근 가능하다"
맞다. 그래서 [ int liv3 = lv ] 와 같이 초기화 시 에러가 발생한다. 그러나 JDK 1.8부터는 에러가 발생하지 않는데..
그 이유는 JDK1.8부터 final 키워드가 생략되기 때문이다.
그러므로 liv3 = lv는 liv3 = 0이라는 상수로 취급됨.
[예제 4]
class Outer2 {
class InstanceInner {
int iv=100;
}
static class StaticInner {
int iv = 200;
static int cv=300;
}
void myMethod() {
class LocalInner {
int iv = 400;
}
}
}
public class Ex7_15 {
public static void main(String[] args) {
//인스턴스 클래스의 인스턴스를 생성하려면, 외부 클래스의 인스턴스 먼저 생성해야 함.
Outer2 oc = new Outer2();
Outer2.InstanceInner ii = oc.new InstanceInner();
System.out.println("ii.iv: " + ii.iv);
System.out.println("Outer2.StaticInner.cv: "+Outer2.StaticInner.cv);
//스태틱 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 된다.
Outer2.StaticInner si = new Outer2.StaticInner();
System.out.println("si.iv: " + si.iv);
}
}
외부 클래스가 아닌, 다른 클래스에서 내부클랫를 생성하고 내부 클래스의 멤버에 접근하는 것을 보여주는 예제.
사실 ㅋㅋ 내부클래스는 딱 외부클래스에서만 사용하도록 만들어진 것. 위 예제처럼 사용하는 경우, 내부클래스로 선언해서는 안 되는 클래스를 내부 클래스로 선언한 건데.. 걍 참고로만 봐두자.
위 예제를 컴파일 했을 때 생성되는 클래스 파일
- Ex7_15.class
- Outer2.class
- Outer2$InstanceInner.class
- Outer2$StaticInner.class
- Outer2$1LocalInner
[예제 5]
class Outer3 {
int value = 10;
class Inner {
int value = 20;
void method1() {
int value= 30;
System.out.println(" value : " + value);
System.out.println(" this.value : "+this.value);
System.out.println("Outer3.this.value : "+Outer3.this.value);
}
}
}
public class Ex7_16 {
public static void main(String[] args) {
Outer3 outer = new Outer3();
Outer3.Inner inner = outer.new Inner();
inner.method1();
}
}
내부 클래스와 외부 클래스에 선언된 변수의 이름이 같을 때 변수 앞에 "this" 또는 "외부클래스명.this"를 붙여서 서로 구별할 수 있다는 것을 보여준다.
this.value => method1()을 사용하려면 Inner 클래스로 인스턴스를 생성해야 함. 즉, this는 Inner의 인스턴스를 의미
익명 클래스(annoymous class)
이름이 없는, 일회용 클래스를 의미한다.
new 조상클래스이름(){
// 멤버 선언
}
new 구현인터페이스이름() {
//멤버선언
}
이름이 없기 때문에 생성자 없음.
조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의한다.
▶ 하나의 클래스로 상속받는 동시에 인터페이스를 구현 불가능
▶ 둘 이상의 인터페이스 구현 불가능
[예제 1]
public class Ex7_17 {
Object iv = new Object() { void method() { } }; //익명클래스
static Object cv = new Object() { void method() { } }; //익명클래스
void myMethod() {
Object lv = new Object() { void method() { } }; //익명클래스
}
}
컴파일 시 생성되는 클래스
- Ex7_17.class
- Ex7_17$1.class < 익명클래스
- Ex7_17$2.class < 익명클래스
- Ex7_17$3.class < 익명클래스
[예제 2]
//awt : 자바 윈도우 프로그래밍
import java.awt.*;
import java.awt.event.*;
public class Ex7_18 {
public static void main(String[] args) {
Button b = new Button("Start");
b.addActionListener(new EventHandler());
}
}
class EventHandler implements ActionListener{
public void actionPerformed(ActionEvent e) {
System.out.println("ActionEvent occurred!!!");
}
}
//실행 결과 아무것도 뜨지 않는 것이 정상
[예제 3]
//awt : 자바 윈도우 프로그래밍
import java.awt.*;
import java.awt.event.*;
public class Ex7_19 {
public static void main(String[] args) {
Button b = new Button("Start");
b.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("ActionEvent occurred!!!");
}
});
}
}
[예제 2]를 익명클래스로 변환한 것이 [예제 3]이다.
먼저 두 개의 독립된 클래스를 작성한 뒤, 익명클래스를 이용하여 변경하면 보다 쉽게 코드를 작성할 수 있음.
'프로그래밍 > java' 카테고리의 다른 글
[JAVA] 자바의정석 기초편 7장 연습문제 (0) | 2022.09.03 |
---|---|
[JAVA] 인터페이스, 상속, 구현 (0) | 2022.08.31 |
[JAVA] 추상 클래스와 추상 메서드 (abstract) (0) | 2022.08.31 |
[JAVA] 매개변수의 다형성, 객체 배열과 벡터 (0) | 2022.08.31 |
[JAVA] 참조변수의 형변환 (0) | 2022.08.29 |