1. Member inner class(멤버 내부 클래스) : 다른 클래스 내부에서 선언된 클래스
2. Static inner class(static 내부 클래스) : 다른 클래스의 내부에서 static으로 선언된 클래스
3. Local class(지역 클래스) > 메소드 안에서 정의
1) Local inner class(지역 내부 클래스) : 메소드 내부에서 선언된 클래스
2) Anonymous inner class(익명 내부 클래스): 이름이 없는 local class
package com.lec.java.inner01;
public class Inner01Main {
public static void main(String[] args) {
System.out.println("Member Inner Class(멤버 내부 클래스)");
// 외부 클래스의 인스턴스 생성
TestOuter out = new TestOuter(100);
// 멤버 내부 클래스의 인스턴스 생성
// 멤버 내부 클래스의 이름: [외부클래스 이름].[멤버 내부클래스 이름]
// [외부클래스 이름].[내부클래스 이름] 참조변수 =
// [외부클래스 인스턴스].new 내부클래스 생성자();
TestOuter.TestInner in = out.new TestInner(111);
in.printOuterValue();
in.printInnerValue();
// 하나의 외부 클래스 인스턴스를 이용해서
// 멤버 내부 클래스의 인스턴스는 여러개를 생성할 수 있다.
TestOuter.TestInner in2 = out.new TestInner(123);
in2.printOuterValue();
in2.printInnerValue();
TestOuter.TestInner in3 = out.new TestInner(999);
in3.printOuterValue();
in3.printInnerValue();
TestOuter.TestInner in4 = new TestOuter(30).new TestInner(330);
in4.printOuterValue();
in4.printInnerValue();
} // end main()
} // end class Inner01Main
package com.lec.java.inner01;
/*
Member inner class(멤버 내부 클래스)
TestOuter 클래스 내부에서 TestInner 클래스를 정의
TestOuter: 외부 클래스(Outer Class, Enclosing Class)
TestInner: 멤버 내부 클래스(Member Inner Class)
1) 멤버 내부 클래스는 외부 클래스의 인스턴스가 생성된 이후에야
인스턴스 생성이 가능함.
2) 멤버 내부 클래스는 외부 클래스의 모든 멤버들(private 포함)을 사용 가능
*/
// 클래스: 멤버 변수들 (+ 생성자들) + 멤버 메소드들 = 데이터 타입
public class TestOuter {
// 멤버 변수:
private int value;
// 생성자:
public TestOuter() {}
public TestOuter(int value) {
this.value = value;
}
// 메소드:
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
// Member Inner Class 정의:
public class TestInner {
// 멤버변수
private int innerValue;
// 생성자
public TestInner() {}
public TestInner(int val) {
this.innerValue = val;
}
// 멤버 메소드
public void printOuterValue() {
System.out.println("value = " + value);
// 멤버 내부 클래스는 외부 클래스의 멤버를 직접 접근 가능
}
public void printInnerValue() {
System.out.println("innerValue = " + innerValue);
}
}
} // end class TestOuter
[멤부 내부 클래스 활용 코드]
package com.lec.java.inner02;
/*
언제 내부 클래스로 설계?
상속 관계로 묶을 수는 없지만,
A라는 객체가 생성된 이후에야 존재할 수 있는 B라는 객체가 있다고 한다면,
B를 A의 멤버 '내부 클래스'로 선언한다.
(예) 컴퓨터-CPU/메모리, 자동차-타이어
반면
'햄버거 세트 메뉴' 객체의 경우
햄버거 객체와 콜라 객체는 별개의 객체로도 존재 가능하니까
'햄버거' 와 '콜라' 는 '세트메뉴' 객체의 '멤버변수'로 붙도록 하는게 낳다
is-a : 상속관계
has-a (종속) : 멤버내부클래스
has-a (독립) : 멤버변수
*/
public class Inner02Main {
public static void main(String[] args) {
System.out.println("멤버 내부 클래스 활용");
Car myCar = new Car("Indigo Blue");
Car.Tire myTire1 = myCar.new Tire(17);
Car.Tire myTire2 = myCar.new Tire(19);
myTire1.displayInfo();
System.out.println();
myTire2.displayInfo();
} // end main()
} // end class Inner02Main
package com.lec.java.inner02;
public class Car {
// 멤버 변수( outer )
private String color;
// 생성자
public Car(String color) {
this.color = color;
}
// 메소드
public void displayCarInfo() {
System.out.println("color: " + color);
}
// 멤버 내부 클래스
public class Tire {
private int radius;
public Tire(int radius) {
this.radius = radius;
}
public void displayInfo() {
System.out.println("--- 타이어 정보 ---");
System.out.println("차량 color: " + color);
System.out.println("tire : " + radius);
}
}
} // end class Car
[외부/내부 클래스의 this 코드 예시]
package com.lec.java.inner03;
public class Inner03Main {
public static void main(String[] args) {
System.out.println("외부/내부 클래스의 this");
TestOuter out = new TestOuter(100); // 1.
TestOuter.TestInner in1 = out.new TestInner(111); // 2.
in1.printValue(10); // 3.
} // end main()
} // end class Inner03Main
package com.lec.java.inner03;
public class TestOuter {
private int value; // 1.
public TestOuter(int value) {
this.value = value;
}
public int getValue() {
return value;
}
// 멤버 내부 클래스
public class TestInner {
private int value; // 2.
public TestInner(int value) {
this.value = value;
}
public void printValue(int value) { // 3.
System.out.println("value = " + value); // 3.
System.out.println("this.value = " + this.value); // 2.
System.out.println("TestOuter.this = " + TestOuter.this.value);
}
}
} // end class TestOuter
Nested Class(중첩 클래스):
- 다른 클래스의 내부에서 멤버로 정의된 클래스인데, static 키워드가 사용된 내부 클래스 (static inner class)
- static : 클래스의 인스턴스가 생성되지 않아도 사용될 수 있는 멤버(변수, 메소드)에 사용
따라서, nested class는 외부 클래스의 인스턴스를 생성하지 않고, 내부 클래스의 인스턴스를 생성할 수 있다.
nested(static) class는 (외부 클래스에서) static으로 선언된 변수와 메소드만 사용 가능
중첩 클래스의 인스턴스 생성:
타입 참조변수 = new 생성자()
중첩 클래스의 이름(타입): [외부클래스 이름].[내부클래스 이름]
중첩 클래스의 생성자: new [외부클래스 이름].생성자()
package com.lec.java.inner04;
public class Nested01Main {
public static void main(String[] args) {
System.out.println("Nested Class(중첩 클래스): static inner class");
TestOuter.TestNested nest1 = new TestOuter.TestNested();
nest1.displayInfo();
TestOuter.TestNested.println();
} // end main()
} // end class Nested01Main
package com.lec.java.inner04;
public class TestOuter {
// 멤버변수
private int value; // 인스턴스 변수
private static int count = 100; // 클래스 변수 (static)
// 생성자
public TestOuter(int value) {
this.value = value;
}
public static class TestNested {
public void displayInfo() {
// System.out.println(value); static 클래스에서 외부의 non-static 사용 못함
System.out.println("count = " + count);
}
public static void println() {}
}
} // end class TestOuter
// TestOuter: 외부 클래스(outer class, enclosing class)
// TestNested: 중첩 클래스(nested class, static inner class)
Local Inner Class: 블록({ ... }) 내부에서 정의된 클래스
1. 정의가 된 블록(메소드) 내부에서만 사용 가능 - 참조변수 선언, 인스턴스 생성
2. 접근 수식어(public, protected, private)는 쓸 수 없다.
3. 외부 클래스의 멤버 변수(private 포함)는 모두 사용 가능
4. effectively final인 지역변수나 매개변수만 사용 가능
effectively final 변수란?
1) final로 선언된 변수, 또는
2) 한 번 초기화가 된 이후로 값이 변경되지 않은 변수(Java 8에서 도입)
package com.lec.java.inner05;
public class Local01Main {
public static void main(String[] args) {
System.out.println("Local Inner Class(지역 내부 클래스)");
// 외부 클래스 생성
TestOuter out = new TestOuter();
out.localMethod();
System.out.println();
TestOuter2 out2 = new TestOuter2();
out2.localMethod();
} // end main()
} // end class Local01Main
package com.lec.java.inner05;
public class TestOuter {
// TestOuter 클래스의 멤버 변수
private int num1 = 100;
// TestOuter 클래스의 멤버 메소드
public void localMethod() {
int num2 = 200;
// localMethod() 내부에서 정의된 Local inner class
class TestLocal {
private int num3 = 300;
public void showNumbers() {
// 외부 클래스의 멤버변수 출력
System.out.println("num1 = " + num1);
// 지역클래스의 동일 scope 의 지역변수 출력
System.out.println("num2 = " + num2);
// 로컬 내부 클래스 (자신)의 멤버변수 출력
System.out.println("num3 = " + num3);
}
} // end local inner class
// 지역 내부 클래스 인스턴스 생성은 클래스가 정의된 메소드(블럭) 안에서만 가능
TestLocal local = new TestLocal();
// num2 = 400; // num2 값을 변경하면.. 아래 showNumbers()에선
// 200 이 찍혀야 하나? 400이 찍혀야 하나?
// 그래서 로컬내부클래스에서 사용 가능한 지역의 변수는
// 반드시 effectively final 이어야 한다
// 즉 한번 초기화 된 후 값이 변경되지 않거나, final 이어야 한다
local.showNumbers();
} // end localMethod()
} // end class TestOuter
package com.lec.java.inner05;
public class TestOuter2 {
//TestOuter 클래스의 멤버 변수
private int num = 100; // ①
// TestOuter 클래스의 멤버 메소드
public void localMethod() {
int num = 200; // ②
class TestLocal {
private int num = 300; // ③
public void showNumber() {
int num = 400; // ④
// ①, ②, ③, ④ 출력 가능?
System.out.println("TestOuter2.this.num = " + TestOuter2.this.num); // ①
System.out.println("this.num = " + this.num); // ③
System.out.println("num = " + num); // ④
// ②는 사용 불가!
}
} // end local inner class
TestLocal local = new TestLocal();
local.showNumber();
} // end localMethod()
} // end class TestOuter
[Local 내부 클래스의 활용 코드]
package com.lec.java.inner06;
public class Local02Main {
public static void main(String[] args) {
System.out.println("Local 내부 클래스의 활용");
Person person = new Person("ABC");
person.readAge(10);
MyReadable r = person.createInstance(16);
r.readInfo(); // 다른 클래스에서도 로컬클래스가 정의한 메소드를 사용 가능
} // end main()
} // end class Local02Main
package com.lec.java.inner06;
public class Person {
// Person 외부 클래스의 멤버 변수
private String name;
// Person 외부 클래스의 생성자
public Person(String name) {
this.name = name;
}
public void readAge(final int age) {
// age = 10;
// local inner class
class PersonWithAge {
public void readInfo() {
System.out.println("이름: " + name);
System.out.println("나이: " + age);
}
}
PersonWithAge p = new PersonWithAge();
p.readInfo();
}
/*
public PersonWithAge createInstance(int age) {
class PersonWithAge {
public void readInfo() {
System.out.println("이름: " + name);
System.out.println("나이: " + age);
}
}
PersonWithAge p = new PersonWithAge();
return p;
}
*/
/*
지역 클래스는 메소드 실행이 끝나게 되면 자체가 사라지게 되는 클래스임.
메소드 내부에 정의된 지역 클래스 타입을 리턴하는 메소드는 만들 수 없다.
경우에 따라서는, 지역 클래스에 정의된 메소드를
외부에서 직접 사용하고자 하는 경우가 발생할 수도 있습니다.
그걸 가능하게 하는 방법이
인터페이스(interface) + 다형성(polymorphism):
1. 외부에서 사용하고 싶은 메소드를 선언한 인터페이스를 작성
2. 메소드의 리턴타입은 정의한 인터페이스 타입으로 정의
3. 로컬 클래스는 인터페이스를 구현(implements)하도록 정의
4. 로컬 클래스의 인스턴스를 생성하고 리턴해줌
*/
// 2. 메소드의 리턴타입은 정의한 인터페이스 타입으로 정의
public MyReadable createInstance(int age) {
// 3. 로컬 클래스는 인터페이스를 구현(implements)하도록 정의
class PersonWithAge implements MyReadable {
@Override
public void readInfo() {
System.out.println("이름: " + name);
System.out.println("나이: " + age);
}
}
// 4. 로컬 클래스의 인스턴스를 생성하고 리턴해줌
MyReadable person = new PersonWithAge(); // 다형성
return person;
}
} // end class Person
// 1. 외부에서 사용하고 싶은 메소드를 선언한 인터페이스를 작성
interface MyReadable {
public abstract void readInfo();
}
Anonymous inner class(익명 내부 클래스):
- 이름이 없는 local inner class
- 이름이 없기 때문에 생성자로 만들 수가 없습니다.
- 클래스의 정의와 동시에 인스턴스를 생성합니다.
익명 내부 클래스:
- 인스턴스 생성과 동시에 이름없는 클래스가 정의됨.
- new 인터페이스() { 익명 클래스 내부 작성 };
- new 부모클래스() { 익명 클래스 내부 작성 };
- 익명 내부 클래스 (Anonymous Inner class) 는 인터페이스 뿐 아니라, 일반 클래스, 추상클래스 등도 가능하다.
- 상속 받은 (이름없는) 클래스의 인스턴스를 생성.
- 멤버 변수/ 메소드 선언
- 메소드 오버라이딩
package com.lec.java.inner07;
public class Anonymous01Main {
public static void main(String[] args) {
System.out.println("Anonymous Inner Class(익명 내부 클래스)");
Person p = new Person("QWERTY");
MyReadable r = p.createInstance(20);
r.readInfo();
//------------------------------------------------
System.out.println();
MyReadable r2 = new MyReadable() {
int a = 10; // 익명클래스의 멤버변수 추가
@Override
public void readInfo() {
System.out.println("readInfo()");
System.out.println("a = " + a);
}
};
r2.readInfo();
System.out.println(r2);
new MyReadable() {
int a = 200;
@Override
public void readInfo() {
System.out.println(a + 100);
}
}.readInfo();
//------------------------------------------------
System.out.println();
System.out.println(new MyClass().methodA(30));
System.out.println(new MyClass() {
int d = 400;
@Override
int methodA(int c) {
return a + b + c + d;
}
}.methodA(30));
int k = 300;
// k= 150; // 익명클래스는 기본적으로 local inner class 이기에
// 동일 scope 의 지역변수를 사용할때는
// 그 지역변수는 effective final 이어야 한다
int result = new MyAbstract() {
@Override
int methodA(int a) {
return a + n + d + k;
}
}.methodA(100);
System.out.println("result = " + result);
System.out.println("\n프로그램 종료");
} // end main()
} // end class
//익명 내부 클래스 (Anonymous Inner class) 는
//인터페이스 뿐 아니라, 일반 클래스, 추상클래스 등도 가능하다.
//상속 받은 (이름없는) 클래스의 인스턴스를 생성.
// - 멤버 변수/ 메소드 선언
// - 메소드 오버라이딩
abstract class MyAbstract{
int n = 10;
int d = 2;
abstract int methodA(int a);
} // end class
class MyClass {
int a = 10;
int b = 20;
int methodA(int c) {
return a + b + c;
}
} // end class
package com.lec.java.inner07;
public class Person {
// 외부 클래스 멤버변수
private String name;
// 외부 클래스 생성자
public Person(String name) {
this.name = name;
}
public MyReadable createInstance(int age) {
// 익명 내부 클래스:
// 인스턴스 생성과 동시에 클래스가 정의됨.
// new 인터페이스() { 익명 클래스 내부 작성 };
// new 부모클래스() { 익명 클래스 내부 작성 };
// MyReadable 을 implement 한 이름없는 익명 class 를 정의하고 그 instance 생성
// 익명클래스 구문 사용!!
return new MyReadable() {
@Override
public void readInfo() {
System.out.println("이름: " + name);
System.out.println("나이: " + age);
}
};
}
} // class Person
interface MyReadable {
public abstract void readInfo();
}
[익명 내부 클래스 활용 코드]
package com.lec.java.inner08;
public class Anonymous02Main {
public static void main(String[] args) {
System.out.println("익명 내부 클래스 활용");
System.out.println();
System.out.println("1. 이름있는 클래스를 사용하여 구현");
Calculable tc1 = new TestMyMath();
double result = tc1.operate(1.0, 2.0);
System.out.println("result = " + result);
System.out.println();
System.out.println("2. 익명 클래스 사용");
Calculable tc2 = new Calculable() {
@Override
public double operate(double x, double y) {
return x - y;
}
};
result = tc2.operate(1.0, 2.0);
System.out.println("result = " + result);
} // end main()
} // end class Anonymous02Main
interface Calculable {
public abstract double operate(double x, double y);
}
/*
위와 같이 특정 추상 메소드만 implement 하는 목적으로 설계되는 인터페이스의 이름은
보통 ~ able 로 작명 경우가 많다.
자바에서 제공하는 대표적으로 많이 사용하는 이러한 인터페이스들.
Serializable, Cloneable, Readable, Appendable, Closeable,
AutoCloseable, Observable, Iterable, Comparable, Runnable,
Callable, Repeatable,
*/
class TestMyMath implements Calculable {
@Override
public double operate(double x, double y) {
return x + y;
}
}