개발자입니다
[비트캠프] 58일차(12주차3일) - Java(람다, 메서드 레퍼런스, 제네릭), myapp-24, 25 본문
[비트캠프] 58일차(12주차3일) - Java(람다, 메서드 레퍼런스, 제네릭), myapp-24, 25
끈기JK 2023. 1. 27. 12:30
중첩 클래스의 종류
클래스 안에 static 클래스가 있으면 static nested class 라 한다.
클래스 안에 non-static 클래스가 있으면 non-static nested class 라 한다.
메서드 안에 클래스가 있으면 local class 라 한다.
인스턴스를 생성하고 상속받은 클래스를 바로 정의한 것을 anonymous class 라 한다.
중첩 클래스와 .class 파일
클래스 안의 static class X { } 를 컴파일 하면 A$X.class 파일이 만들어진다.
클래스 안의 class Y { } 를 컴파일 하면 A$Y.class 파일이 만들어진다.
메서드 안의 class Z { } 를 컴파일 하면 A$1Z.class 파일이 만들어진다.
익명 클래스를 컴파일 하면 A$1.class 파일이 만들어진다.
.class 파일에 중첩 클래스 정보와 Nest Member 정보가 나온다.
// Compiled from Exam0210.java (version 17 : 61.0, super bit)
public class com.eomcs.oop.ex11.a.Exam0210 {
Inner classes:
[inner class info: #21 com/eomcs/oop/ex11/a/Exam0210$1, outer class info: #0
inner name: #0, accessflags: 0 default],
[inner class info: #31 com/eomcs/oop/ex11/a/Exam0210$1C, outer class info: #0
inner name: #33 C, accessflags: 0 default],
[inner class info: #34 com/eomcs/oop/ex11/a/Exam0210$A, outer class info: #1 com/eomcs/oop/ex11/a/Exam0210
inner name: #36 A, accessflags: 8 static],
[inner class info: #37 com/eomcs/oop/ex11/a/Exam0210$B, outer class info: #1 com/eomcs/oop/ex11/a/Exam0210
inner name: #39 B, accessflags: 0 default]
Nest Members:
#21 com/eomcs/oop/ex11/a/Exam0210$1,
#31 com/eomcs/oop/ex11/a/Exam0210$1C,
#34 com/eomcs/oop/ex11/a/Exam0210$A,
#37 com/eomcs/oop/ex11/a/Exam0210$B
static nested class 와 non-static nested class 의 차이
static class X { } 는 컴파일러가 X() { - } 기본 생성자 추가한다.
class Y { } 는 컴파일러가 바깥 클래스의 객체 주소를 담은 레퍼런스 A this$0 추가한다. 생성자에서 바깥 클래스의 인스턴스를 받을 수 있도록 Y(A arg) { this$0 = arg; } 파라미터 추가한다.
Y obj = new Y(); 는 new Y(this); 로 컴파일러가 바깥 클래스의 객체 주소를 넘기는 코드로 자동 변경한다.
예제 소스
com.eomcs.oop.ex11.d
local class : 클래스 정의와 인스턴스 생성
package com.eomcs.oop.ex11.d;
class A {
void m1() {
// 메서드 안에 정의하는 클래스를 "local class"라 한다.
// - 특정 메서드 안에서만 사용되는 경우 로컬 클래스로 정의한다.
// - 쓸데없이 외부로 노출하지 않기 위함.
// - 노출을 줄이면 유지보수에 좋다.
// - 로컬 클래스에서 로컬 이라는 말은 '이 메서드 안에서만 사용할 수 있다'는 뜻이다.
// - 그냥 사용 범위에 대한 제한을 가리키는 말이다.
// - 메서드를 호출할 때 클래스가 정의된다는 뜻이 아니다.
class X {
}
X obj = new X();
}
static void m2() {
// 메서드 안에 정의하는 클래스를 "local class"라 한다.
class X {
}
X obj = new X();
}
}
public class Exam0110 {
public static void main(String[] args) {
A outer = new A();
outer.m1();
}
}
로컬 클래스는 1부터 숫자가 붙는다. 같은 이름을 가지면 숫자를 증가시키며 저장한다.
local class : 사용 범위
package com.eomcs.oop.ex11.d;
class B {
void m1() {
class X {
}
X obj = new X();
}
static void m2() {
// 다른 메서드에 정의된 클래스는 사용할 수 없다.
// X obj; // 컴파일 오류!
}
}
public class Exam0120 {
public static void main(String[] args) {
}
}
local class : .class 파일명
package com.eomcs.oop.ex11.d;
class B2 {
void m1() {
class X {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$1X.class
}
class Y {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$1Y.class
}
}
static void m2() {
class Y {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$2Y.class
}
class X {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$2X.class
}
class Z {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$1Z.class
}
}
}
public class Exam0130 {
public static void main(String[] args) {
}
}
local class : 인스턴스 메서드와 로컬 클래스
package com.eomcs.oop.ex11.d;
class C {
int v1 = 1;
void m1() {
// 인스턴스 메서드는 C 인스턴스 주소를 this 변수에 저장한다.
class X {
// 그래서 인스턴스 메서드 안에 정의된 로컬 클래스는
// 바깥 클래스의 인스턴스를 사용할 수 있다.
// .class 파일을 확인해보면 바깥 클래스이 인스턴스 주소를 저장하는 필드가 선언되어 있다.
// 또한 생성자에도 바깥 클래스의 인스턴스 주소를 받는 파라미터가 선언되어 있다.
//
// class com.eomcs.oop.ex11.d.C$1X {
// final synthetic com.eomcs.oop.ex11.d.C this$0;
//
// C$1X(com.eomcs.oop.ex11.d.C arg0);
// 0 aload_0 [this]
// 1 aload_1 [arg0]
// 2 putfield com.eomcs.oop.ex11.d.C$1X.this$0 : com.eomcs.oop.ex11.d.C [10]
//
void f() {
System.out.printf("v1 = %d\n", C.this.v1);
}
}
X obj = new X(); // 컴파일 ==> new X(this);
obj.f();
}
}
public class Exam0210 {
public static void main(String[] args) {
C c = new C();
c.m1();
}
}
local class : 스태틱 메서드와 로컬 클래스
package com.eomcs.oop.ex11.d;
class C2 {
int v1 = 1;
static void m1() {
// 스태틱 메서드는 C2 인스턴스 주소를 저장할 this라는 변수가 없다.
class X {
// 그래서 바깥 클래스의 인스턴스를 사용할 수 없다.
// .class 파일을 확인해보면 바깥 클래스의 인스턴스를 보관할 필드가 선언되어 있지 않다.
// 또한 생성자에도 바깥 클래스의 인스턴스를 받는 파라미터가 선언되어 있지 않다.
//
// class com.eomcs.oop.ex11.d.C2$1X {
//
// C2$1X();
// 0 aload_0 [this]
// 1 invokespecial java.lang.Object() [8]
//
void f() {
// System.out.printf("v1 = %d\n", C2.this.v1); // 컴파일 오류!
}
}
X obj = new X(); // 컴파일 해도 같다. ===> new X();
obj.f();
}
}
public class Exam0220 {
public static void main(String[] args) {
C2 c = new C2();
c.m1();
}
}
local class에서 바깥 메서드의 로컬 변수 접근
package com.eomcs.oop.ex11.d;
// 계산기 사용법을 정의한다.
interface Calculator {
double compute(int money);
}
class CalculatorFactory {
static Calculator create(float interest) {
class CalculatorImpl implements Calculator {
@Override
public double compute(int money) {
return money + (money * interest);
// interest는 create() 함수의 로컬 변수이다.
// CalculatorImpl 객체를 생성하여 리턴한 후에는 interest 로컬 변수는 스택에서 사라진 상태일 것이다.
// 나중에 compute()를 호출할 때 interest 변수는 없을텐데, 어떻게 된 것인가?
// => 로컬 클래스에서 메서드의 로컬 변수를 사용한다면
// 컴파일러는 로컬 클래스에 바깥 메서드의 로컬 변수 값을 저장할 필드를 추가한다.
// 또한 로컬 클래스의 객체를 생성할 때 생성자에 로컬 변수의 값을 넘겨 줄 것이다.
}
}
return new CalculatorImpl();
}
}
public class Exam0310 {
public static void main(String[] args) {
Calculator c1 = CalculatorFactory.create(0.02f);
Calculator c2 = CalculatorFactory.create(0.08f);
System.out.printf("%.2f\n", c1.compute(1235_0000));
System.out.printf("%.2f\n", c2.compute(1235_0000));
}
}
로컬 클래스의 .class 파일을 보면 바깥 메서드의 필드를 저장할 변수를 선언하는 것을 알 수 있다.
// Compiled from Exam0310.java (version 17 : 61.0, super bit)
class com.eomcs.oop.ex11.d.CalculatorFactory$1CalculatorImpl implements com.eomcs.oop.ex11.d.Calculator {
// Field descriptor #8 F
private final synthetic float val$interest;
local class에서 바깥 메서드의 로컬 변수 접근
package com.eomcs.oop.ex11.d;
class D {
void m1() {
int a = 100;
int b = 200;
class X {
// 로컬 클래스에서 바깥 메서드(m1())의 로컬 변수(a,b) 값을 사용한다면
// 1) 다음과 같이 컴파일 할 때 그 값을 저장할 필드(a)를 자동으로 추가한다.
// => 사용하는 변수에 대해서만 필드를 생성한다.
// => 로컬 변수 b는 사용하지 않기 때문에 b 값을 받을 필드는 생성하지 않는다.
// int a;
// D outer;
//
// 2) 또한 로컬 클래스의 객체를 생성할 때 그 값을 사용할 수 있도록
// 생성자에 파라미터를 추가한다.
// public X(D outer, int a) {
// this.a = a;
// this.outer = outer;
// }
//
// .class 파일의 코드:
// class com.eomcs.oop.ex11.d.D$1X {
//
// final synthetic com.eomcs.oop.ex11.d.D this$0;
//
// private final synthetic int val$a;
//
// D$1X(com.eomcs.oop.ex11.d.D arg0, int arg1);
// ...
//
void f1() {
// 그래서 다음과 같이
// 로컬 클래스에서는 바깥 메서드의 로컬 변수를 자기것인양 사용할 수 있는 것이다.
System.out.println(a); // m1() 에 선언된 로컬 변수를 가리킨다.
}
}
X obj = new X();
obj.f1();
}
static void m2() {
int a = 100;
int b = 200;
// 스태틱 메서드의 로컬 클래스도 인스턴스 메서드의 로컬 클래스와 같다.
// 단, 스태틱이기 때문에 바깥 클래스의 인스턴스 주소를 받는 필드는
// 자동으로 생성되지 않는다.
//
// .class 파일의 코드:
// class com.eomcs.oop.ex11.d.D$2X {
//
// private final synthetic int val$b;
//
// D$2X(int arg0);
// ..
class X {
void f1() {
System.out.println(b);
}
}
X obj = new X();
obj.f1();
}
}
public class Exam0311 {
public static void main(String[] args) {
D obj = new D();
obj.m1();
obj.m2();
}
}
local class에서 바깥 메서드의 로컬 변수 접근 I
package com.eomcs.oop.ex11.d;
class D2 {
int v1 = 1;
void m1() {
int v2 = 2;
class X {
int v3 = 3;
void f() {
int v4 = 4;
System.out.printf("v4 = %d\n", v4); // f()의 로컬 변수
System.out.printf("v3 = %d\n", this.v3); // X 인스턴스의 변수
System.out.printf("v1 = %d\n", D2.this.v1); // D2의 인스턴스 변수
// 바깥 메서드의 로컬 변수를 사용할 때는
// 자신의 메서드인양 사용하면 된다.
System.out.printf("v2 = %d\n", v2); // D2.m1()의 로컬 변수
}
}
X obj = new X();
obj.f();
}
}
public class Exam0321 {
public static void main(String[] args) {
D2 obj = new D2();
obj.m1();
}
}
local class에서 바깥 메서드의 로컬 변수 접근 II
package com.eomcs.oop.ex11.d;
class D22 {
int v1 = 1;
void m1() {
// int v1 = 10;
class X {
// int v1 = 100;
void f() {
// int v1 = 1000;
System.out.printf("v1 = %d\n", v1);
// this 를 생략하면 다음 순서로 변수를 찾는다.
// 1) 로컬 변수
// 2) 인스턴스 변수
// 3) 메서드에 선언된 로컬 변수
// 4) 바깥 클래스의 인스턴스 변수 또는 스태틱 변수
}
}
X obj = new X();
obj.f();
}
}
public class Exam0322 {
public static void main(String[] args) {
D22 obj = new D22();
obj.m1();
}
}
로컬 클래스에서 메서드에 선언된 로컬 변수 접근하기
package com.eomcs.oop.ex11.d;
class D3 {
void m1() {
final int v1 = 1;
int v2 = 2;
int v3 = 3;
// v3 = 30;
class X {
void f() {
// 로컬 클래스에서는 바깥 메서드의 로컬 변수를 사용할 수 있다.
// 1) final 로 선언된 경우
System.out.printf("v1 = %d\n", v1);
// 2) final 로 선언된 것은 아니지만 값을 한 번만 할당한 경우.
System.out.printf("v2 = %d\n", v2);
// => 값을 여러 번 할당한 경우에는 접근할 수 없다.
System.out.printf("v3 = %d\n", v3); // 컴파일 오류!
// 결론!
// - 상수 값이거나 상수에 준하는 경우(값을 한 번만 할당한 경우)
// 로컬 클래스에서 메서드의 로컬 변수를 사용할 수 있다.
// - 즉 로컬 클래스에서 바깥 메서드의 로컬 변수를 사용하는 상황?
// 값을 조회하는 용도할 때.
//- 왜?
// 로컬 객체가 사용하는 로컬 변수는
// 메서드 호출이 끝났을 때 제거되기 때문이다.
}
}
X obj = new X();
obj.f();
}
}
public class Exam0330 {
public static void main(String[] args) {
D3 obj = new D3();
obj.m1();
}
}
로컬 클래스가 로컬 변수를 조회용으로만 사용하는 이유?
package com.eomcs.oop.ex11.d;
public class Exam0340 {
public static void main(final String[] args) {
final Exam0340 obj = new Exam0340();
final Runner r = obj.createRunner("홍길동");
// createRunner()의 리턴 값은
// 이 메서드에 선언된 로컬 클래스인 A의 객체이다.
// A 객체는 Runner 규칙에 따라 만들었기 때문에
// Runner 레퍼런스에 저장할 수 있다.
// 이 레퍼런스를 사용하여 A 객체의 run() 메서드를 호출해 보자!
r.run();
// 로컬 클래스의 객체가 로컬 변수의 값을 조회용으로만 사용하는 이유?
// => 위의 코드에서 createRunner() 호출이 끝나면 이 메서드가 실행되는 동안
// 생성되었던 모든 로컬 변수는 스택 메모리에서 제거된다.
// 즉 createRunner()의 name 파라미터가 제거된다는 것이다.
}
Runner createRunner(String name) {
// Exam0340 객체 주소를 받는 내장 변수 this 가 있다.
//
class A implements Runner {
// 컴파일러는 바깥 클래스의 객체 주소를 받을 필드는 추가한다.
// 또한 바깥 클래스의 객체 주소를 받는 생성자를 추가한다.
/*
* Exam0340 outer; // 바깥 클래스의 주소를 받을 필드
*
* String paramName; // run() 메서드에서 사용할 로컬 변수을 값을 받을 필드
*
* public A(Exam0365 obj, String str) { outer = obj; paramName = str;}
*
*/
@Override
public void run() {
// 바깥 메서드의 로컬 변수는 메서드 호출이 완료되는 순간
// 스택 메모리에서 제거되기 때문에
// 로컬 클래스의 객체에서 사용할 수 없는 상황이 발생할 것이다.
// 그래서 컴파일러는 바깥 메서드의 로컬 변수 값을 저장할
// 필드를 클래스에 추가한다.
// 또한 로컬 변수의 값을 받는 생성자를 만든다.
// 따라서 run() 메서드를 호출하는 시점에는
// A 로컬 객체에 name 변수의 값이 들어 있기 때문에
// 그래도 사용할 수 있는 것이다.
// => 다음 코드의 name은 createRunner() 메서드의 파라미터가 아니다.
// => A 인스턴스가 생성될 때 값을 따로 복제한 필드를 가리키는 것이다.
//
System.out.printf("%s님이 달립니다!", name);
// 위 문장은 다음과 같이
// 컴파일러가 로컬 클래스의 필드(예: paramName)를 사용하는 문장으로 바꾼다.
// 그래서 createRunner() 메서드 호출이 끝나더라도
// name 값을 사용할 수 있는 것이다.
//
// System.out.printf("%s님이 달립니다!", this.paramName);
}
}
return new A();
// 컴파일러는 로컬 클래스의 객체를 생성할 때
// 바깥 클래스의 객체 주소와 로컬 객체가 사용하는
// 메서드의 로컬 변수 값을 전달하기 위해
// 다음과 같이 컴파일러가 만든 생성자를 호출한다.
//
// return new A(this, name);
}
}
interface Runner {
void run();
}
anonymous class : 인터페이스를 구현한 익명 클래스 정의
package com.eomcs.oop.ex11.e;
public class Exam0110 {
// 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
// => 인터페이스는 규칙을 정의한 것이기 때문에 인스턴스 멤버라는 개념이 존재하지 않는다.
interface A {
void print();
}
class X {}
static class Y {}
public static void main(final String[] args) {
// 1) 로컬 클래스로 인터페이스 구현하기
class My implements A {
String name = "홍길동";
@Override
public void print() {
System.out.printf("Hello, %s!\n", this.name);
}
}
A obj = new My();
obj.print();
}
}
package com.eomcs.oop.ex11.e;
public class Exam0111 {
// 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
// => 인터페이스는 규칙을 정의한 것이기 때문에 인스턴스 멤버라는 개념이 존재하지 않는다.
interface A {
void print();
}
public static void main(final String[] args) {
// 2) 익명 클래스로 인터페이스 구현하기
// => 인스턴스를 한 번 만 생성할 것이라면,
// 로컬 클래스로 정의하는 것 보다 익명 클래스로 정의하는 것이 더 낫다.
// => 특히 객체를 사용하려는 곳에 바로 익명 클래스를 정의하면
// 읽기 쉽기 때문에 소스 코드를 유지보수 하기가 더 좋다.
//
// 익명 클래스 = 클래스 정의 문법 + 인스턴스 생성 문법
// new 수퍼클래스명() {클래스 정의}
// new 인터페이스명() {클래스 정의}
//
// 익명 클래스로 인터페이스 구현하기
// 문법:
// => 인터페이스명 레퍼런스 = new 인터페이스명() {};
// - 수퍼 클래스에 존재하는 생성자를 호출한다.
//
A obj2 = new A() {
@Override
public void print() {
System.out.println("Hello!");
}
};
obj2.print();
new A() {
@Override
public void print() {
System.out.println("Hello!");
}
}.print();
}
}
// 익명 클래스를 정의하는 과정
// 1단계 : 로컬 클래스
/*
class X implements A {
@Override
public void print() {
System.out.println("Hello!");
}
};
*/
// 2단계 : 클래스의 이름을 지운다.
/*
class implements A {
@Override
public void print() {
System.out.println("Hello!");
}
};
=> 그러나 클래스의 이름이 없기 때문에 인스턴스를 생성할 수가 없다.
A obj = new ();
*/
// 3단계 : 클래스 이름이 없기 때문에 'class', 'implements' 키워드는 없앤다.
/*
A {
@Override
public void print() {
System.out.println("Hello!");
}
};
*/
// 4단계 : 클래스 이름이 없기 때문에 따로 인스턴스를 생성할 수 가 없다.
// 바로 생성해야 한다.
// => 인터페이스 이름 바로 앞에 'new' 키워드를 둔다.
/*
new A {
@Override
public void print() {
System.out.println("Hello!");
}
};
*/
// 5단계 : 익명 클래스의 생성자가 없기 때문에 수퍼 클래스의 생성자를 호출한다.
// => 객체 생성할 때 항상 생성자를 호출해야 하는데,
// 클래스에 이름이 없으면 생성자를 만들 수 없다.
// 따라서 호출할 익명 클래스의 생성자가 없다.
// (실제는 내부에 익명 클래스의 기본 생성자가 만들어진다.)
// 그래서 수퍼 클래스의 생성자를 호출해야 한다.
// 자바의 모든 클래스는 따로 수퍼 클래스를 지정하지 않으면
// java.lang.Object 클래스가 수퍼 클래스로 자동 설정된다.
// 바로 그 Object 클래스의 생성자를 호출하도록 지정해야 한다.
// Object 클래스의 생성자는 기본 생성자 하나 뿐이다.
// 인터페이스 이름 뒤에 기본 생성자를 호출하는 괄호를 추가한다.
/*
new A() {
@Override
public void print() {
System.out.println("Hello!");
}
};
*/
// 6단계 : 익명 클래스의 레퍼런스 선언
// => 익명 클래스는 이름이 없기 때문에
// 익명 클래스로 레퍼런스를 선언할 수 없다.
// 그래서 익명 클래스가 구현한 인터페이스나
// 상속 받는 수퍼 클래스로 레퍼런스를 선언해야 한다.
/*
A obj = new A() {
@Override
public void print() {
System.out.println("Hello!");
}
};
*/
anonymous class : 클래스를 상속 받은 익명 클래스 정의
package com.eomcs.oop.ex11.e;
public class Exam0120 {
// 클래스는 static과 non-static이 구분된다.
static abstract class A {
abstract void print();
}
public static void main(final String[] args) {
// 1) 로컬 클래스로 서브 클래스 만들기
class My extends A {
@Override
public void print() {
System.out.println("Hello!");
}
}
A obj = new My();
obj.print();
}
}
package com.eomcs.oop.ex11.e;
public class Exam0121 {
// 클래스는 static과 non-static이 구분된다.
static abstract class A {
abstract void print();
}
public static void main(final String[] args) {
// 2) 익명 클래스로 서브 클래스 만들기
// => 인스턴스를 한 번 만 생성할 것이라면,
// 로컬 클래스로 정의하는 것 보다 익명 클래스로 정의하는 것이 더 낫다.
// => 특히 객체를 사용하려는 곳에 바로 익명 클래스를 정의하면
// 읽기 쉽기 때문에 소스 코드를 유지보수에 더 좋다.
//
// 익명 클래스로 인터페이스 구현하기
// 문법:
// => 수퍼클래스 레퍼런스 = new 수퍼클래스() {};
// - 수퍼 클래스에 존재하는 생성자를 호출한다.
//
A obj2 = new A() {
@Override
void print() {
System.out.println("Hello!");
}
void m() {
System.out.println("이 메서드를 어떻게 호출할까?");
}
};
obj2.print();
// obj2.m(); // obj2는 A 타입이다. A에는 m() 이 정의되어 있지 않다. 컴파일 오류!
}
}
anonymous class : 클래스를 상속 받은 익명 클래스 정의 - 생성자
package com.eomcs.oop.ex11.e;
public class Exam0122 {
// default constructor 가 없는 클래스
static class A {
String message;
int value;
A(String message) {
this.message = message;
}
A(String message, int value) {
this.message = message;
this.value = value;
}
void print() {
System.out.printf("A의 print(): %s, %d\n", this.message, this.value);
}
}
public static void main(final String[] args) {
// 3) 익명 클래스의 생성자
// => 익명 클래스의 객체를 만들 때 호출하는 생성자는
// 수퍼 클래스에 존재하는 생성자여야 한다.
//
// 문법:
// => 수퍼클래스 레퍼런스 = new 수퍼클래스(파라미터, ...) {};
// - 컴파일러는 호출하는 수퍼 클래스의 생성자와 동일한 파라미터를 갖는 생성자를
// 익명 클래스에 추가한다.
//
A obj2 = new A("오호라", 100) {
// bytecode를 확인해보라!
};
obj2.print();
}
}
anonymous class - 수퍼 클래스와 인터페이스를 동시에 지정할 수 있을까?
package com.eomcs.oop.ex11.e;
public class Exam0130 {
static abstract class A {
public abstract void print();
}
interface B {
String getName();
}
public static void main(final String[] args) {
// 클래스도 상속 받고 인터페이스도 구현하는 익명 클래스를 만들 수 있을까?
// => 안된다.
// => 둘 중 하나만 상속 받거나 구현해야지, 동시에 다 할 수 없다.
//
A obj = new A implements B() { // 컴파일 오류!
@Override
public void print() {
System.out.println("Hello!");
}
public String getName() {
return "부질없는 짓";
}
};
obj.print();
}
}
anonymous class - 여러 개의 인터페이스를 구현할 수 있을까?
package com.eomcs.oop.ex11.e;
public class Exam0140 {
interface A {
void m1();
}
interface B {
void m2();
}
class X implements A, B {
public void m1() {};
public void m2() {}
}
public static void main(final String[] args) {
// 여러 개의 인터페이스를 구현할 수 있을까?
// => 안된다. 익명 클래스에는 그런 문법은 없다.
//
Object obj = new A, B() { // 컴파일 오류!
@Override
public void print() {
System.out.println("Hello!");
}
};
obj.print();
}
}
anonymous class - 생성자
package com.eomcs.oop.ex11.e;
public class Exam0210 {
interface A {
void print();
}
public static void main(final String[] args) {
A obj = new A() {
// 1) 익명 클래스는 생성자를 직접 정의할 수 없다.
// - 그러나 컴파일러가 컴파일할 때 익명 클래스의 생성자를 만든다.
//
// 2) 대신 인스턴스 블록으로 생성자를 대신한다.
// - 인스턴스 블록에 작성한 코드는
// 결국 컴파일러가 자동 생성한 생성자에 들어간다.
// - 그래서 인스턴스 블록에 작성한 코드가 실행될 것이다.
{
System.out.println("익명 클래스의 인스턴스 블록 실행!");
}
@Override
public void print() {
System.out.println("Hello!");
}
};
}
}
// 수퍼 클래스 생성자 호출
// - 모든 클래스의 생성자는 항상 수퍼 클래스의 생성자를 먼저 호출한다.
// - 인터페이스를 구현한 익명 클래스는 java.lang.Object 클래스를 상속 받는다.
// - 따라서 익명 클래스의 생성자는 Object 클래스의 생성자를 먼저 호출한다.
anonymous class - 생성자 II
package com.eomcs.oop.ex11.e;
public class Exam0220 {
static class A {
public A() {
System.out.println("A() 호출!");
}
}
public static void main(final String[] args) {
A obj = new A() {
// 1) 익명 클래스는 생성자를 직접 정의할 수 없다.
// - 그러나 컴파일러가 컴파일할 때 익명 클래스의 생성자를 만든다.
//
// 2) 대신 인스턴스 블록으로 생성자를 대신한다.
// - 인스턴스 블록에 작성한 코드는
// 결국 컴파일러가 자동 생성한 생성자에 들어간다.
// - 그래서 인스턴스 블록에 작성한 코드가 실행될 것이다.
//
// 3) 물론 그전에 먼저 수퍼 클래스의 생성자가 실행될 것이다.
{
System.out.println("익명 클래스의 인스턴스 블록 실행!");
}
// 컴파일러는 익명 클래스의 생성자를 다음과 같이 추가할 것이다.
// A$1() {
// super();
// System.out.println("익명 클래스의 인스턴스 블록 실행!");
// }
};
}
}
// 수퍼 클래스 생성자 호출
// - 모든 클래스의 생성자는 항상 수퍼 클래스의 생성자를 먼저 호출한다.
anonymous class - 호출할 수퍼 클래스의 생성자 지정하기
package com.eomcs.oop.ex11.e;
public class Exam0230 {
static class A {
String name;
public A() {
System.out.println("A() 호출됨!");
name = "이름 없음";
}
public A(String name) {
System.out.println("A(String) 호출됨!");
this.name = name;
}
public void print() {
System.out.println(name);
}
}
public static void main(String[] args) {
// 익명 클래스를 정의할 때 호출할 수퍼 클래스의 생성자를 지정할 수 있다.
// - 문법:
// new 수퍼클래스명(파라미터,...) {}
// - 즉 생성자에 넘겨주는 파라미터로 호출될 생성자를 지정한다.
// 1) 수퍼 클래스의 기본 생성자 호출하기
// - 컴파일러는 익명 클래스에 기본 생성자를 추가한다.
//
A obj = new A() {};
obj.print();
System.out.println("-----------------------------");
// 2) 수퍼 클래스의 다른 생성자 호출하기
// - 다음과 같이 익명 클래스의 인스턴스를 만들 때 값을 지정하면
// 그 타입의 값을 받는 수퍼 클래스의 생성자가 호출된다.
// - 컴파일러는 익명 클래스에 String 파라미터를 가진 생성자를 추가한다.
//
obj = new A("홍길동") {};
obj.print();
// 물론 다음과 같이 해당 타입의 값을 받을 생성자가 없으면
// 컴파일 오류가 발생한다.
// obj = new A(100) {}; // 컴파일 오류!
}
}
anonymous class - 수퍼 클래스의 메서드 오버라이딩 하기
package com.eomcs.oop.ex11.e;
public class Exam0310 {
static class A {
public void print() {
System.out.println("반갑습니다!");
}
}
public static void main(final String[] args) {
A obj = new A() {
@Override
public void print() {
System.out.println("정말 반갑습니다!");
}
};
obj.print();
}
}
anonymous class - 익명 클래스가 놓이는 장소: 스태틱 필드
package com.eomcs.oop.ex11.e;
public class Exam0410 {
// 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
interface A {
void print();
}
// 스태틱 필드의 값을 준비할 때 익명 클래스를 사용할 수 있다.
static A obj = new A() {
@Override
public void print() {
System.out.println("Hello!");
}
};
}
anonymous class - 익명 클래스가 놓이는 장소: 인스턴스 필드
package com.eomcs.oop.ex11.e;
public class Exam0420 {
// 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
interface A {
void print();
}
// 인스턴스 필드의 값을 준비할 때 익명 클래스를 사용할 수 있다.
A obj = new A() {
@Override
public void print() {
System.out.println("Hello!");
}
};
}
anonymous class - 익명 클래스가 놓이는 장소: 로컬 클래스
package com.eomcs.oop.ex11.e;
public class Exam0430 {
// 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
interface A {
void print();
}
void m1() {
// 로컬 변수의 값을 준비할 때 익명 클래스를 사용할 수 있다.
A obj = new A() {
@Override
public void print() {
System.out.println("Hello!");
}
};
obj.print();
}
public static void main(String[] args) {
Exam0430 r = new Exam0430();
r.m1();
}
}
anonymous class - 익명 클래스가 놓이는 장소: 파라미터
package com.eomcs.oop.ex11.e;
public class Exam0440 {
// 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
interface A {
void print();
}
static void m1(A obj) {
obj.print();
}
public static void main(String[] args) {
// 1) 로컬 클래스 만들기
class X implements A {
@Override
public void print() {
System.out.println("XXXXX");
}
}
m1(new X());
// 2) 익명 클래스 만들기
A obj = new A() {
@Override
public void print() {
System.out.println("익명 클래스!!!");
}
};
m1(obj);
// 3) 익명 클래스를 파라미터 자리에 바로 삽입
m1(new A() {
@Override
public void print() {
System.out.println("안녕!!!");
}
});
}
}
anonymous class - 익명 클래스가 놓이는 장소: 리턴
// anonymous class - 익명 클래스가 놓이는 장소: 리턴
package com.eomcs.oop.ex11.e;
class My {
static void m1() {
System.out.println("오호라!!!!");
}
void m2() {
System.out.println("와우~~~~!");
}
}
public class Exam0450 {
// 인터페이스의 경우 static으로 선언하지 않아도 스태틱 멤버에서 사용할 수 있다.
interface A {
void print();
}
static A create0() {
class X implements A {
@Override
public void print() {
System.out.println("Hello0!");
}
}
return new X();
}
static A create1() {
A a = new A() {
@Override
public void print() {
System.out.println("Hello1!");
}
};
return a;
}
static A create2() {
return new A() {
@Override
public void print() {
System.out.println("Hello2!");
}
};
}
static A create3() {
return () -> System.out.println("Hello3!");
}
static A create4() {
return My::m1; // 메서드 레퍼런스
// return () -> My.m1();
}
static A create5() {
return new My()::m2;
}
public static void main(String[] args) {
A obj0 = create0();
obj0.print();
A obj1 = create1();
obj1.print();
A obj2 = create2();
obj2.print();
A obj3 = create3();
obj3.print();
A obj4 = create4();
obj4.print();
A obj5 = create5();
obj5.print();
}
}
com.eomcs.oop.ex12
Lambda 문법 - 익명 클래스 vs 람다
package com.eomcs.oop.ex12;
public class Exam0110 {
// 다음과 같이 추상 메서드가 한 개 있는 인터페이스를 "functional interface"라고 부른다.
// => 이런 경우에 람다 문법을 사용할 수 있다.
// => 인터페이스는 static 을 붙이지 않아도 static 멤버가 사용할 수 있다.
interface Player {
void play();
}
public static void main(String[] args) {
// 익명 클래스로 인터페이스 구현하기
Player p1 = new Player() {
@Override
public void play() {
System.out.println("익명 클래스");
}
};
p1.play();
// 람다 문법으로 인터페이스 구현하기
// => 메서드 한 개짜리 인터페이스를 좀 더 간결하게 구현하기 위해 만든 문법이다.
// => 뻔한 코드 생략!
Player p2 = () -> System.out.println("익명 클래스");
p2.play();
}
}
// 익명 클래스와 .class 파일
// => 자바의 nested class 는 모두 별도의 .class 파일을 갖는다.
// => 위의 main()에 정의된 로컬 익명 클래스는 다음과 같은 이름의 .class 파일로 컴파일 된다.
// Exam0110$1.class
//
// 람다와 .class 파일
// => 람다는 별도의 .class 파일을 생성하지 않는다.
// => 컴파일러는 람다 코드를 해당 클래스의 스태틱 메서드로 정의한다.
// => 그리고 리플렉션 API를 사용하여 이 메서드를 호출하도록 변경한다.
// => 람다 문법이 초기에 등장했을 때는 익명 클래스로 변환되었다.
// => 그러나 최근에는 메서드로 변환한 후 호출하는 방식으로 바뀌었다.
// => 예)
// 원래의 자바코드:
// Player p2 = () -> {
// System.out.println("람다");
// };
//
// 컴파일러가 변환한 코드:
// private static synthetic void lambda$0();
// 0 getstatic java.lang.System.out : java.io.PrintStream [33]
// 3 ldc <String "람다"> [39]
// 5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [41]
// 8 return
// Line numbers:
// [pc: 0, line: 27]
// [pc: 8, line: 28]
//
// => 람다를 호출하는 코드는 자동 생성된 메서드를 호출하는 코드로 변환된다.
Lambda 문법 - 람다 body
package com.eomcs.oop.ex12;
public class Exam0120 {
// "functional interface" = 추상 메서드가 한 개만 있는 인터페이스
interface Player {
void play();
}
public static void main(String[] args) {
// 1) 한 문장일 때는 중괄호를 생략할 수 있다.
Player p1 = () -> System.out.println("테스트1");
p1.play();
// 2) 물론 중괄호를 명확히 적어도 된다.
Player p2 = () -> {
System.out.println("테스트2");
};
p2.play();
// => 파라미터가 없다고 괄호를 생략할 수는 없다.
// Player p3 = -> System.out.println("테스트3"); // 컴파일 오류!
}
}
Lambda 문법 - 람다 파라미터 I
package com.eomcs.oop.ex12;
public class Exam0130 {
interface Player {
void play(String name);
}
public static void main(String[] args) {
// 1) 파라미터는 괄호() 안에 선언한다.
Player p1 = (String name) -> System.out.println(name + " 님 환영합니다.");
p1.play("홍길동");
// 2) 파라미터 타입을 생략할 수 있다.
Player p2 = (name) -> System.out.println(name + " 님 환영합니다.");
p2.play("홍길동");
// 3) 파라미터가 한 개일 때는 괄호도 생략할 수 있다.
Player p3 = name -> System.out.println(name + " 님 환영합니다.");
p3.play("홍길동");
}
}
Lambda 문법 - 람다 파라미터 II
package com.eomcs.oop.ex12;
public class Exam0140 {
interface Player {
void play(String name, int age);
}
public static void main(String[] args) {
// 1) 파라미터는 괄호() 안에 선언한다.
Player p1 = (String name, int age) -> System.out.printf("%s(%d)님 환영합니다.\n", name, age);
p1.play("홍길동", 20);
// 2) 파라미터 타입을 생략할 수 있다.
Player p2 = (name, age) -> System.out.printf("%s(%d)님 환영합니다.\n", name, age);
p2.play("임꺽정", 30);
// 3) 파라미터가 여러 개일 때는 괄호를 생략할 수 없다.
// Player p3 = name, age -> System.out.printf("%s(%d)님 환영합니다.\n", name, age);
// p3.play("임꺽정", 30);
}
}
Lambda 문법 - 람다 리턴
package com.eomcs.oop.ex12;
public class Exam0150 {
interface Calculator {
int compute(int a, int b);
}
public static void main(String[] args) {
// 1) 리턴 값은 return 명령을 사용하여 처리한다.
Calculator c1 = (a, b) -> {
return a + b;
};
System.out.println(c1.compute(10, 20));
// 2) 한 문장으로 된 표현식(=값을 리턴하는 한 문장의 코드)인 경우 괄호 생략할 수 있다.
// => 문장은 문장인데 값을 리턴하는 문장을 '표현식(expression)' 이라 부른다.
// => 단 괄호를 생략할 때 return 키워드도 생략해야 한다. 있으면 컴파일 오류!
Calculator c2 = (a, b) -> a + b;
System.out.println(c2.compute(10, 20));
// Math.max()는 int 값을 리턴한다. 그래서 이 메서드를 호출하는 문장은 표현식이다.
Calculator c3 = (a, b) -> Math.max(a, b);
System.out.println(c3.compute(10, 20));
// 값을 리턴해야 하는데 람다 문장에서 값을 리턴하지 않으면 컴파일 오류!
// Calculator c4 = (a, b) -> System.out.println(a + ",", b); // 컴파일 오류!
// System.out.println(c4.compute(10, 20));
}
}
lambda 문법 : 익명 클래스를 사용할 수 있는 곳에는 모두 람다 사용 가능
package com.eomcs.oop.ex12;
public class Exam0160 {
interface A {
void print();
}
// 스태틱 필드
static A obj1 = () -> System.out.println("스태틱 필드");
//인스턴스 필드
A obj2 = () -> System.out.println("인스턴스 필드");
public static void main(final String[] args) {
// 로컬 변수
A obj3 = () -> System.out.println("로컬 변수!");
// 파라미터
m1(() -> System.out.println("파라미터"));
// 리턴 값
A obj4 = m2();
}
static void m1(final A obj) {
obj.print();
}
static A m2() {
// 리턴 문장
return () -> System.out.println("리턴 문장");
}
}
Lambda 문법 - functional interface의 자격
package com.eomcs.oop.ex12;
public class Exam0210 {
// 추상 메서드가 한 개짜리 인터페이스여야 한다.
interface Player {
void play();
}
public static void main(String[] args) {
// 추상 메서드를 한 개만 갖고 있는 인터페이스에 대해
// 람다 문법으로 익명 클래스를 만들 수 있다.
Player p = () -> System.out.println("Player...");
p.play();
}
}
package com.eomcs.oop.ex12;
public class Exam0220 {
// 추상 메서드가 두 개 이상이면 람다 문법으로 구현할 수 없다.
interface Player {
void play();
void stop();
}
public static void main(String[] args) {
// 추상 메서드가 두 개 이상인 경우 람다 문법을 사용할 수 없다.
// Player p = () -> System.out.println("Player..."); // 컴파일 오류!
}
}
package com.eomcs.oop.ex12;
public class Exam0230 {
// 여러 개의 메서드가 있다 하더라도 추상 메서드가 한 개이면 된다.
interface Player {
static String info() {
return "Player입니다.";
}
default void stop() {}
void play();
}
public static void main(String[] args) {
// static 메서드나 default 메서드가 몇개이든 그 개수는 중요하지 않다.
// 추상 메서드가 한 개이면 람다 문법을 사용할 수 있다.
Player p = () -> System.out.println("Player...");
p.play();
System.out.println(Player.info());
}
}
package com.eomcs.oop.ex12;
public class Exam0240 {
static abstract class Player {
public abstract void play();
}
public static void main(String[] args) {
// 인터페이스가 아닌 추상 클래스는 람다 구현의 대상이 아니다!
// Player p = () -> System.out.println("Player..."); // 컴파일 오류!
}
}
아규먼트에 람다(lambda) 활용
package com.eomcs.oop.ex12;
public class Exam0310 {
static interface Player {
void play();
}
static void testPlayer(Player p) {
p.play();
}
public static void main(String[] args) {
// 로컬 클래스
class MyPlayer implements Player {
@Override
public void play() {
System.out.println("실행!");
}
}
testPlayer(new MyPlayer());
}
}
package com.eomcs.oop.ex12;
public class Exam0311 {
static interface Player {
void play();
}
static void testPlayer(Player player) {
player.play();
}
public static void main(String[] args) {
// 간단한 인터페이스 구현체 조차
// 다음과 같이 따로 정의하여 사용하면 매우 불편하다.
Player player = new Player() {
@Override
public void play() {
System.out.println("실행~~~~");
}
};
testPlayer(player);
}
}
package com.eomcs.oop.ex12;
public class Exam0312 {
static interface Player {
void play();
}
static void testPlayer(Player player) {
player.play();
}
public static void main(String[] args) {
// 아규먼트 부분에 바로 익명 클래스를 정의하는 코드를 두면
// 코드를 해석하기가 편하다.
testPlayer(new Player() {
@Override
public void play() {
System.out.println("실행~~~~");
}
});
}
}
package com.eomcs.oop.ex12;
public class Exam0313 {
static interface Player {
void play();
}
static void testPlayer(Player player) {
player.play();
}
public static void main(String[] args) {
// 아규먼트 자리에 lambda 문법을 사용하면 더 편하다!
testPlayer(() -> System.out.println("실행~~~~"));
}
}
아규먼트에 람다(lambda) 활용 II - 파라미터와 리턴 값이 있는 람다 만들기
package com.eomcs.oop.ex12;
public class Exam0320 {
static interface Calculator {
int compute(int a, int b);
}
static void test(Calculator c) {
System.out.println(c.compute(100, 200));
}
public static void main(String[] args) {
// 람다
// 파라미터와 리턴 값이 있는 메서드 구현하기
test((a, b) -> a + b);
}
}
package com.eomcs.oop.ex12;
public class Exam0321 {
static interface Calculator {
int compute(int a, int b);
}
static void test(Calculator c) {
System.out.println(c.compute(100, 200));
}
public static void main(String[] args) {
// 익명 클래스로 정의한다면?
test(new Calculator() {
@Override
public int compute(int a, int b) {
return a + b;
}
});
}
}
아규먼트에 람다(lambda) 활용 III - 여러 개의 문장이 있는 경우
package com.eomcs.oop.ex12;
public class Exam0330 {
interface Calculator {
int compute(int a, int b);
}
static void test(Calculator c) {
System.out.println(c.compute(100, 200));
}
public static void main(String[] args) {
// 여러 문장을 실행하는 경우 블록 {}으로 감싸라! (블록을 생략할 수 없다.)
test((a, b) -> {
int sum = 0;
for (int i = a; i <= b; i++) {
sum += i;
}
return sum;
});
}
}
package com.eomcs.oop.ex12;
public class Exam0331 {
interface Calculator {
int compute(int a, int b);
}
static void test(Calculator c) {
System.out.println(c.compute(100, 200));
}
public static void main(String[] args) {
// 익명 클래스로 정의한다면?
test(new Calculator() {
@Override
public int compute(int a, int b) {
int sum = 0;
for (int i = a; i <= b; i++) {
sum += i;
}
return sum;
}
});
}
}
리턴 문장에 람다(lambda) 활용
package com.eomcs.oop.ex12;
public class Exam0410 {
static interface Interest {
double compute(int money);
}
// 팩토리 메서드
// => Interest 구현체를 생성하여 리턴하는 메서드
//
static Interest getInterest(final double rate) {
// 로컬 클래스로 인터페이스 구현한 후 객체 리턴하기
class InterestImpl implements Interest {
double rate;
public InterestImpl(double rate) {
this.rate = rate;
}
@Override
public double compute(int money) {
return money + (money * rate / 100);
}
}
return new InterestImpl(rate);
}
public static void main(String[] args) {
Interest i1 = getInterest(1.5);
System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));
Interest i2 = getInterest(2.5);
System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
}
}
package com.eomcs.oop.ex12;
public class Exam0411 {
static interface Interest {
double compute(int money);
}
static Interest getInterest(final double rate) {
// 로컬 클래스로 인터페이스 구현한 후 객체 리턴하기
class InterestImpl implements Interest {
// 로컬(또는 익명 로컬) 클래스 안에서 바깥 메서드의 로컬 변수를 사용하면
// 컴파일러는 자동으로 그 값을 보관하기 위해
// 필드를 추가한다.
// 또한 그 값을 받을 수 있도록 생성자를 변경한다.
// 따라서 개발자가 직접 필드나 생성자를 정의할 필요가 없다.
//
@Override
public double compute(int money) {
return money + (money * rate / 100);
}
}
// 또한 개발자는 메서드의 로컬 변수 값을
// 로컬 클래스의 생성자에 전달하기 위해 직접 작성할 필요가 없다.
// 컴파일러가 자동으로 추가한다.
//
return new InterestImpl();
}
public static void main(String[] args) {
Interest i1 = getInterest(1.5);
System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));
Interest i2 = getInterest(2.5);
System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
}
}
package com.eomcs.oop.ex12;
public class Exam0412 {
static interface Interest {
double compute(int money);
}
static Interest getInterest(final double rate) {
// 익명 클래스로 인터페이스 구현한 후 객체 리턴하기
// => 객체를 한 개만 생성할 것이라면 익명 클래스로 정의하라.
Interest i = new Interest() {
@Override
public double compute(int money) {
return money + (money * rate / 100);
}
};
return i;
}
public static void main(String[] args) {
Interest i1 = getInterest(1.5);
System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));
Interest i2 = getInterest(2.5);
System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
}
}
package com.eomcs.oop.ex12;
public class Exam0413 {
static interface Interest {
double compute(int money);
}
static Interest getInterest(final double rate) {
// 익명 클래스로 인터페이스 구현한 후 객체 리턴하기
return new Interest() {
@Override
public double compute(int money) {
return money + (money * rate / 100);
}
};
}
public static void main(String[] args) {
Interest i1 = getInterest(1.5);
System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));
Interest i2 = getInterest(2.5);
System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
}
}
package com.eomcs.oop.ex12;
public class Exam0414 {
static interface Interest {
double compute(int money);
}
static Interest getInterest(final double rate) {
// 람다 문법으로 인터페이스 구현한 후 객체 리턴하기
return money -> money + (money * rate / 100);
}
public static void main(String[] args) {
Interest i1 = getInterest(1.5);
System.out.printf("금액: %.2f\n", i1.compute(1_0000_0000));
Interest i2 = getInterest(2.5);
System.out.printf("금액: %.2f\n", i2.compute(1_0000_0000));
}
}
메서드 레퍼런스 - 스태틱 메서드 레퍼런스
package com.eomcs.oop.ex12;
public class Exam0510 {
static class MyCalculator {
public static int plus(int a, int b) {return a + b;}
public static int minus(int a, int b) {return a - b;}
public static int multiple(int a, int b) {return a * b;}
public static int divide(int a, int b) {return a / b;}
}
interface Calculator {
int compute(int x, int y);
}
public static void main(String[] args) {
// 메서드 한 개짜리 인터페이스의 구현체를 만들 때,
// 1) 익명 클래스 활용
Calculator obj1 = new Calculator() {
@Override
public int compute(int x, int y) {
return x * y;
}
};
// 2) 람바 문법 활용
Calculator obj2 = (x, y) -> x * y;
// 3) 기존에 작성한 클래스의 스태틱 메서드를 재활용하기
// => 인터페이스의 메서드 규격과 일치하는 메서드가 있다면,
// 그 메서드를 람다 구현체로 대체할 수 있다.
// => 새로 코드를 작성할 필요가 없어 매우 편리하다.
// => 규격? 메서드의 파라미터 타입/개수/순서, 리턴 타입
// => 문법:
// 클래스명::메서드명
Calculator c1 = MyCalculator::plus; // MyCalculator의 스태틱 메서드인 plus()를 가지고 구현체를 자동 생성!
Calculator c2 = MyCalculator::minus;
Calculator c3 = MyCalculator::multiple;
Calculator c4 = MyCalculator::divide;
System.out.println(c1.compute(200, 17)); // compute() ==> plus()
System.out.println(c2.compute(200, 17)); // compute() ==> minus()
System.out.println(c3.compute(200, 17)); // compute() ==> multiple()
System.out.println(c4.compute(200, 17)); // compute() ==> divide()
}
}
메서드 레퍼런스 - 스태틱 메서드 레퍼런스 구현 원리
package com.eomcs.oop.ex12;
public class Exam0520 {
static class MyCalculator {
public static int plus(int a, int b) {
return a + b;
}
public static int minus(int a, int b) {
return a - b;
}
public static int multiple(int a, int b) {
return a * b;
}
public static int divide(int a, int b) {
return a / b;
}
public static int power(int a) {
return a * 2;
}
}
interface Calculator {
int compute(int a, int b);
}
public static void main(String[] args) {
// 스태틱 메서드 레퍼런스로 Calculator 구현체를 만드는 방법
//
Calculator c01 = MyCalculator::plus;
Calculator c02 = MyCalculator::minus;
Calculator c03 = MyCalculator::multiple;
Calculator c04 = MyCalculator::divide;
// Calculator c05 = MyCalculator::power; // 해당 메서드를 가지고 구현체를 만들 수 없다.
// 위의 코드는 내부적으로 다음과 같다.
//
Calculator c1 = new Calculator() {
@Override
public int compute(int a, int b) {
// 기존 메서드가 메서드 레퍼런스로 전달 가능한지 여부는
// 다음 코드를 참고하라.
// 인터페이스에 정의된 메서드(예: compute())가 호출되었을 때,
//
// 그 파라미터 값은 메서드 레퍼런스로 지정된
// 스태틱 메서드(예: plus())에게 전달될 것이다.
// => 그래서 스태틱 메서드의 파라미터는 항상
// 인터페이스 메서드에 정의된 파라미터 값을 받을 수 있어야 한다.
//
// 스태틱 메서드의 리턴 값은
// 인터페이스 메서드에 정의된 대로 리턴할 수 있어야 한다.
// => 그래서 스태틱 메서드의 리턴 타입은
// 인터페이스 메서드의 리턴 타입과 일치하거나
// 그 타입으로 바꿀 수 있어야 한다.
//
return MyCalculator.plus(a, b);
}
};
System.out.println(c1.compute(200, 17));
}
}
메서드 레퍼런스 - 스태틱 메서드 레퍼런스
package com.eomcs.oop.ex12;
public class Exam0530 {
static class MyCalculator {
public static int plus(int a, int b) {
return a + b;
}
public static int minus(int a, int b) {
return a - b;
}
public static int multiple(int a, int b) {
return a * b;
}
public static int divide(int a, int b) {
return a / b;
}
}
interface Calculator1 {
double compute(int a, int b);
}
interface Calculator2 {
float compute(int a, int b);
}
interface Calculator3 {
short compute(int a, int b);
}
interface Calculator4 {
void compute(int a, int b);
}
interface Calculator5 {
Object compute(int a, int b);
}
interface Calculator6 {
String compute(int a, int b);
}
public static void main(String[] args) {
// 리턴 타입 int ===> double
Calculator1 c1 = MyCalculator::plus; // OK!
// 위 문장은 다음 문장과 같다.
// Calculator1 c1 = new Calculator1() {
// @Override
// public double compute(int a, int b) {
// return MyCalculator.plus(a, b);
// }
// };
System.out.println(c1.compute(100, 200));
// 리턴 타입 int ===> float
Calculator2 c2 = MyCalculator::plus; // OK!
System.out.println(c2.compute(100, 200));
// 리턴 타입 int ===> short
// Calculator3 c3 = MyCalculator::plus; // 컴파일 오류!
// 위 문장은 다음과 같다.
// Calculator3 c3 = new Calculator3() {
// @Override
// public short compute(int a, int b) {
// return MyCalculator.plus(a, b); // 컴파일 오류!
// }
// };
// 리턴 타입 int ===> void
Calculator4 c4 = MyCalculator::plus; // OK!
// 위 문장은 다음과 같다.
// Calculator4 c4 = new Calculator4() {
// @Override
// public void compute(int a, int b) {
// MyCalculator.plus(a, b); // OK!
// }
// };
c4.compute(100, 200); // plus() 메서드의 리턴 값은 무시한다.
// 리턴 타입 int ===> Object
Calculator5 c5 = MyCalculator::plus; // OK!
// 위 문장은 다음과 같다.
// Calculator5 c5 = new Calculator5() {
// @Override
// public Object compute(int a, int b) {
// return MyCalculator.plus(a, b); // OK!
// //이유? plus()가 리턴한 int 값이 오토박싱 되기 때문이다.
// }
// };
System.out.println(c5.compute(100, 200));
// 리턴 타입 int ===> String
// Calculator6 c6 = MyCalculator::plus; // 컴파일 오류!
// 위 문장은 다음과 같다.
// Calculator6 c6 = new Calculator6() {
// @Override
// public String compute(int a, int b) {
// return MyCalculator.plus(a, b); // 컴파일 오류!
// }
// };
// => 메서드 레퍼런스를 지정할 때 리턴 타입의 규칙:
// 1) 같은 리턴 타입
// 2) 암시적 형변환 가능한 타입
// 3) auto-boxing 가능한 타입
// 4) void
// 결론,
// 메서드 레퍼런스가 가리키는 실제 메서드를 호출한 후
// 그 메서드가 리턴한 값이
// 인터페이스에 정의된 메서드의 리턴 값으로 사용할 수 있다면
// 문제가 없다.
//
}
}
package com.eomcs.oop.ex12;
public class Exam0540 {
static class MyCalculator {
public static int plus(int a, int b) {
return a + b;
}
public static int minus(int a, int b) {
return a - b;
}
public static int multiple(int a, int b) {
return a * b;
}
public static int divide(int a, int b) {
return a / b;
}
}
static interface Calculator1 {
int compute(byte a, byte b);
}
static interface Calculator2 {
int compute(short a, short b);
}
static interface Calculator3 {
int compute(long a, long b);
}
static interface Calculator4 {
int compute(float a, float b);
}
static interface Calculator5 {
int compute(Object a, Object b);
}
static interface Calculator6 {
int compute(String a, String b);
}
static interface Calculator7 {
int compute(Integer a, Integer b);
}
static interface Calculator8 {
int compute(int a);
}
static interface Calculator9 {
int compute(int a, int b, int c);
}
public static void main(String[] args) {
// 파라미터 타입: byte, byte ===> int, int
Calculator1 c1 = MyCalculator::plus; // OK!
//
// Calculator1 xx = new Calculator1() {
// @Override
// public int compute(byte a, byte b) {
// return MyCalculator.plus(a, b); // OK!
// }
// };
// 파라미터 타입: short, short ===> int, int
Calculator2 c2 = MyCalculator::plus; // OK!
//
// Calculator2 c2 = new Calculator2() {
// @Override
// public int compute(short a, short b) {
// return MyCalculator.plus(a, b); // OK!
// }
// };
// 파라미터 타입: long, long ===> int, int
// Calculator3 c3 = MyCalculator::plus; // 컴파일 오류!
//
// Calculator3 c3 = new Calculator3() {
// @Override
// public int compute(long a, long b) {
// return MyCalculator.plus(a, b); // 컴파일 오류!
// }
// };
// 파라미터 타입: float, float ===> int, int
// Calculator4 c4 = MyCalculator::plus; // 컴파일 오류!
// 파라미터 타입: Object, Object ===> int, int
// Calculator5 c5 = MyCalculator::plus; // 컴파일 오류!
// 파라미터 타입: String, String ===> int, int
// Calculator6 c6 = MyCalculator::plus; // 컴파일 오류!
// 파라미터 타입: Integer, Integer ===> int, int
Calculator7 c7 = MyCalculator::plus; // OK
//
// Calculator7 c7 = new Calculator7() {
// @Override
// public int compute(Integer a, Integer b) {
// return MyCalculator.plus(a, b); // OK!
// // 이유? 오토언박싱 때문이다.
// // MyCalculator.plus(a.intValue(), b.intValue()) 코드로 변경된다.
// }
// };
// 파라미터 타입: int ===> int, int
// Calculator8 c8 = MyCalculator::plus; // 컴파일 오류!
//
// Calculator8 c8 = new Calculator8() {
// @Override
// public int compute(int a) {
// return MyCalculator.plus(a, ?); // 컴파일 오류!
// // compute()는 int 값 한 개만 받는데, plus()는 int 값 두 개를 요구한다.
// }
// };
// 파라미터 타입: int, int, int ===> int, int
//Calculator9 c9 = MyCalculator::plus; // 컴파일 오류!
//
// Calculator9 c9 = new Calculator9() {
// @Override
// public int compute(int a, int b, int c) {
// return MyCalculator.plus(a, b, c); // 컴파일 오류!
// // compute()는 int 값 세 개를 받아서 plus()에 세 개 모두 전달한다.
// // 그러나 plus()는 int 파라미터가 두 개만 있다.
// }
// };
// 메서드 레퍼런스를 지정할 때 파라미터 타입 규칙:
// => 인터페이스 규칙에 따라 받은 값을
// 실제 메서드에 그대로 전달할 수 있다면 가능하다.
}
}
메서드 레퍼런스 - 인스턴스 메서드 레퍼런스
package com.eomcs.oop.ex12;
public class Exam0610 {
static class Calculator {
double rate;
public Calculator(double rate) {
this.rate = rate;
}
public double year(int money) {
return money * rate / 100;
}
public double month(int money) {
return money * rate / 100 / 12;
}
public double day(int money) {
return money * rate / 100 / 365;
}
}
static interface Interest {
double compute(int money);
}
public static void main(String[] args) {
// 메서드 한 개짜리 인터페이스의 구현체를 만들 때
// 기존 인스턴스 메서드를 람다 구현체로 사용할 수 있다.
// => 단 인터페이스에 선언된 메서드의 규격과 일치해야 한다.
// => 보통 특정 인스턴스 값을 가지고 작업해야 할 경우에 이 방식을 사용한다.
// => 규격? 파라미터 타입 및 개수, 리턴 타입
// => 문법:
// 인스턴스::메서드명
//
Calculator 보통예금 = new Calculator(0.5);
Calculator 정기예금 = new Calculator(1.5);
Calculator 청년행복예금 = new Calculator(10);
System.out.println("[보통예금]");
Interest i1 = 보통예금::year; // 인스턴스 메서드를 메서드 레퍼런스로 지정한다.
//
// 람다 문법으로 표현하면:
// Interest i1 = money -> 보통예금.year(money);
System.out.printf("년 이자: %.1f\n", i1.compute(10_0000_0000));
i1 = 보통예금::month;
System.out.printf("월 이자: %.1f\n", i1.compute(10_0000_0000));
i1 = 보통예금::day;
System.out.printf("일 이자: %.1f\n", i1.compute(10_0000_0000));
System.out.println("--------------------------");
System.out.println("[정기예금]");
Interest i2 = 정기예금::year;
System.out.printf("년 이자: %.1f\n", i2.compute(10_0000_0000));
i2 = 정기예금::month;
System.out.printf("월 이자: %.1f\n", i2.compute(10_0000_0000));
i2 = 정기예금::day;
System.out.printf("일 이자: %.1f\n", i2.compute(10_0000_0000));
}
}
메서드 레퍼런스 - 인스턴스 메서드 레퍼런스 구현 원리
package com.eomcs.oop.ex12;
public class Exam0620 {
static class Calculator {
double rate;
public Calculator(double rate) {
this.rate = rate;
}
public double year(int money) {
return money * rate / 100;
}
public double month(int money) {
return money * rate / 100 / 12;
}
public double day(int money) {
return money * rate / 100 / 365;
}
}
static interface Interest {
double compute(int money);
}
public static void main(String[] args) {
Calculator 보통예금 = new Calculator(0.5);
// 인스턴스 메서드 레퍼런스로 Calculator 구현체를 만드는 방법
//
// Iterest i1 = 보통예금::year;
// 위의 코드는 내부적으로 다음과 같다.
// 1) 람다 표현
// Interest i1 = money -> 보통예금.year(money);
//
// 2) 익명 클래스
Interest i1 = new Interest() {
@Override
public double compute(int money) {
// 인스턴스 메서드 레퍼런스는 실제
// 인터페이스 구현체에서 다음과 같이 메서드로 호출된다.
return 보통예금.year(money);
}
};
System.out.printf("년 이자: %.1f\n", i1.compute(10_0000_0000));
}
}
메서드 레퍼런스 - 활용예
package com.eomcs.oop.ex12;
import java.util.function.Predicate;
public class Exam0630 {
public static void main(String[] args) {
// Predicate<String> 인터페이스 구현체 준비하기
// 1) 로컬 클래스로 인터페이스 구현체 만들기
class MyPredicate<T> implements Predicate<T> {
@Override
public boolean test(T value) {
return ((String)value).isEmpty();
}
}
Predicate<String> p1 = new MyPredicate<>();
// 2) 익명 클래스로 인터페이스 구현체 만들기
Predicate<String> p2 = new Predicate<>() {
@Override
public boolean test(String value) {
return value.isEmpty();
}
};
// 3) 람다로 인터페이스 구현체 만들기
Predicate<String> p3 = value -> value.isEmpty();
// 4) 메서드 레퍼런스를 사용하여 기존 클래스의 메서드를 인터페이스 구현체로 사용하기
// => 의미: "Predicate 인터페이스 구현체로서 String의 isEmpty()를 사용하겠다"
Predicate<String> p4 = String::isEmpty;
}
}
메서드 레퍼런스 - 생성자 레퍼런스
package com.eomcs.oop.ex12;
import java.util.ArrayList;
import java.util.List;
public class Exam0710 {
interface ListFactory {
List create();
}
public static void main(String[] args) {
// 인터페이스에 정의된 메서드가
// 생성자의 형식과 일치하다면
// 메서드 레퍼런스로 생성자를 지정할 수 있다.
//
// 1) 익명 클래스로 인터페이스 구현
// ListFactory f1 = new ListFactory() {
// public List create() {
// return new ArrayList();
// }
// };
// 2) 람다 문법으로 인터페이스 구현
// ListFactory f1 = () -> new ArrayList();
// 3) 메서드 레퍼런스로 인터페이스 구현
ListFactory f1 = ArrayList::new;
// 인터페이스의 메서드를 호출하면
// 지정된 클래스의 인스턴스를 만든 후 생성자를 호출한다.
List list = f1.create(); // new ArrayList();
System.out.println(list instanceof ArrayList);
System.out.println(list.getClass().getName());
}
}
메서드 레퍼런스 - 생성자 레퍼런스 구현 원리
package com.eomcs.oop.ex12;
import java.util.ArrayList;
import java.util.List;
public class Exam0720 {
static interface ListFactory {
List create();
}
public static void main(String[] args) {
// 생성자 레퍼런스를 지정하는 것은
// 다음과 같이 익명 클래스를 만드는 것과 같다.
// => ListFactory f1 = ArrayList::new;
ListFactory f1 = new ListFactory() {
@Override
public List create() {
return new ArrayList();
}
};
List list = f1.create();
System.out.println(list instanceof ArrayList);
System.out.println(list.getClass().getName());
}
}
메서드 레퍼런스 - 생성자 레퍼런스
package com.eomcs.oop.ex12;
public class Exam0730 {
static class Message {
String name;
public Message() {
this.name = "이름없음";
}
public Message(String name) {
this.name = name;
}
public void print() {
System.out.printf("%s님 반갑습니다!\n", name);
}
}
static interface Factory1 {
Message get();
}
static interface Factory2 {
Message get(String name);
}
public static void main(String[] args) {
// 생성자 레퍼런스를 지정할 때,
// 인터페이스 메서드의 파라미터에 따라 호출할 생성자가 결정된다.
Factory1 f1 = Message::new; // Message() 생성자를 가리킨다.
// Factory1 f1 = () -> new Message(); // 위의 문장과 같다.
Factory2 f2 = Message::new; // Message(String) 생성자를 가리킨다.
// Factory2 f2 = n -> new Message(n); // 위의 문장과 같다.
Message msg = f1.get(); // ==> new Message()
msg.print();
msg = f2.get("홍길동"); // ==> new Message("홍길동")
msg.print();
}
}
package com.eomcs.oop.ex12;
public class Exam0740 {
static class Message {
String name;
public Message() {
this.name = "이름없음";
}
public Message(String name) {
this.name = name;
}
public void print() {
System.out.printf("%s님 반갑습니다!\n", name);
}
}
static interface Factory1 {
Message get();
}
static interface Factory2 {
Message get(String name);
}
static interface Factory3 {
Message get(String name, int age);
}
public static void main(String[] args) {
Factory1 f1 = Message::new; // Message() 생성자를 가리킨다.
//=> 즉 컴파일러는 Message 의 기본 생성자를 호출하는 Factory 구현체를 만들어 리턴한다.
//
// Factory1 f1 = new Factory1() {
// @Override
// public Message get() {
// return new Message();
// }
// };
Factory2 f2 = Message::new; // Message(String) 생성자를 가리킨다.
//=> 즉 컴파일러는 Message 의 생성자 중에서
// String을 파라미터 받는 생성자를 호출하는 Factory 구현체를 만들어 리턴한다.
//
// Factory2 f2 = new Factory2() {
// @Override
// public Message get(String name) {
// return new Message(name);
// }
// };
// Factory(String,int) 생성자가 없기 때문에 컴파일 오류!
// Factory3 f3 = Message::new; // 컴파일 오류!
//=> 즉 컴파일러는 Message 의 생성자 중에서
// String과 int를 파라미터 받는 생성자를 호출하는 Factory 구현체를 만들어야 하는데,
// Message 클래스에는 String과 int를 파라미터로 받는 생성자가 없기 때문에
// Factory 구현체를 만들 수 없다.
// 그래서 컴파일 오류가 발생한다!
}
}
메서드 레퍼런스 - 생성자 레퍼런스 활용
package com.eomcs.oop.ex12;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;
public class Exam0750 {
@SuppressWarnings("unchecked")
static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
Collection<T> list = factory.get(); // => new ArrayList<String>()
for (T value : values) {
list.add(value);
}
return list;
}
static <T> void print(Iterator<T> i) {
while (i.hasNext()) {
System.out.print(i.next() + ",");
}
System.out.println();
}
public static void main(String[] args) {
// '람다'는 새로 메서드를 구현해야 하지만,
// '메서드 레퍼런스'는 기존 클래스의 메서드를 재활용 할 수 있다.
//
// 생성자 레퍼런스
// 문법:
// => 클래스명::new
//
// 인터페이스 구현체를 직접 만들지 않고 기존에 존재하는 메서드를 구현체로 사용하는 문법이
// "메서드 레퍼런스" 이다.
// => 스태틱 메서드 레퍼런스, 인스턴스 메서드 레퍼런스, 생성자 레퍼런스
Collection<String> c1 = prepareNames(ArrayList<String>::new,
"홍길동", "임꺽정", "유관순", "임꺽정");
System.out.println("------------------------");
Collection<String> c2 = prepareNames(HashSet<String>::new,
"홍길동", "임꺽정", "유관순", "임꺽정");
print(c2.iterator());
}
}
package com.eomcs.oop.ex12;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;
public class Exam0751 {
@SuppressWarnings("unchecked")
static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
Collection<T> list = factory.get(); // => new ArrayList<String>()
for (T value : values) {
list.add(value);
}
return list;
}
static <T> void print(Iterator<T> i) {
while (i.hasNext()) {
System.out.print(i.next() + ",");
}
System.out.println();
}
public static void main(String[] args) {
// 1) 로컬 클래스 사용하여 Supplier 구현체 만들기
class MySupplier<T> implements Supplier<T> {
@Override
public T get() {
return (T) new ArrayList<>();
}
};
Supplier<Collection<String>> supplier = new MySupplier<Collection<String>>();
Collection<String> list = prepareNames(supplier, "홍길동", "임꺽정", "유관순", "임꺽정");
print(list.iterator());
}
}
package com.eomcs.oop.ex12;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;
public class Exam0752 {
@SuppressWarnings("unchecked")
static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
Collection<T> list = factory.get(); // => new ArrayList<String>()
for (T value : values) {
list.add(value);
}
return list;
}
static <T> void print(Iterator<T> i) {
while (i.hasNext()) {
System.out.print(i.next() + ",");
}
System.out.println();
}
public static void main(String[] args) {
// 2) 익명 클래스 사용하여 Supplier 구현체 만들기
Supplier<Collection<String>> supplier = new Supplier<Collection<String>>() {
@Override
public Collection<String> get() {
return new ArrayList<>();
}
};
Collection<String> list = prepareNames(supplier, "홍길동", "임꺽정", "유관순", "임꺽정");
print(list.iterator());
}
}
package com.eomcs.oop.ex12;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;
public class Exam0753 {
@SuppressWarnings("unchecked")
static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
Collection<T> list = factory.get(); // => new ArrayList<String>()
for (T value : values) {
list.add(value);
}
return list;
}
static <T> void print(Iterator<T> i) {
while (i.hasNext()) {
System.out.print(i.next() + ",");
}
System.out.println();
}
public static void main(String[] args) {
// 3) 익명 클래스 사용하여 Supplier 구현체 만들기 II
Collection<String> list = prepareNames(
new Supplier<Collection<String>>() {
@Override
public Collection<String> get() {
return new ArrayList<>();
}
},
"홍길동", "임꺽정", "유관순", "임꺽정");
print(list.iterator());
}
}
package com.eomcs.oop.ex12;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Supplier;
public class Exam0754 {
@SuppressWarnings("unchecked")
static <T> Collection<T> prepareNames(Supplier<Collection<T>> factory, T... values) {
Collection<T> list = factory.get(); // => new ArrayList<String>()
for (T value : values) {
list.add(value);
}
return list;
}
static <T> void print(Iterator<T> i) {
while (i.hasNext()) {
System.out.print(i.next() + ",");
}
System.out.println();
}
public static void main(String[] args) {
// 4) 람다 문법으로 Supplier 구현체 만들기
Collection<String> list = prepareNames(
() -> new ArrayList<String>(),
"홍길동", "임꺽정", "유관순", "임꺽정");
print(list.iterator());
}
}
인터페이스 구현체를 만드는 다양한 방법
package com.eomcs.oop.ex12;
public class Exam0810 {
interface Factory {
Object create();
}
static class Car {}
public static void main(String[] args) {
// 1) 로컬 클래스로 인터페이스 구현체를 만든다.
class CarFactory implements Factory {
@Override
public Object create() {
return new Car();
}
}
printCar(new CarFactory());
// 2) 익명 클래스로 인터페이스 구현체를 만든다.
printCar(new Factory() {
@Override
public Object create() {
return new Car();
}
});
// 3) 람다로 인터페이스 구현체를 만든다.
printCar(() -> new Car());
// 4) 기존에 존재하는 메서드를 인터페이스 구현체로 사용한다.
printCar(Exam0810::createCar);
// 5) 기존 클래스의 생성자를 인터페이스 구현체로 사용한다.
printCar(Car::new);
System.out.println("완료!");
}
public static Car createCar() {
return new Car();
}
public static void printCar(Factory factory) {
Object obj = factory.create();
System.out.printf("만든 객체: %s\n", obj);
}
}
List 와 forEach() - forEach() 사용 전
package com.eomcs.oop.ex12;
import java.util.ArrayList;
public class Exam0910 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("홍길동");
names.add("임꺽정");
names.add("유관순");
names.add("김구");
names.add("안중근");
for (int i = 0; i < names.size(); i++) {
System.out.println(names.get(i));
}
}
}
List 와 forEach() - forEach() 사용 전 II
package com.eomcs.oop.ex12;
import java.util.ArrayList;
public class Exam0911 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("홍길동");
names.add("임꺽정");
names.add("유관순");
names.add("김구");
names.add("안중근");
for (String name : names) {
System.out.println(name);
}
}
}
List 와 forEach() - forEach() 사용 후
package com.eomcs.oop.ex12;
import java.util.ArrayList;
import java.util.function.Consumer;
public class Exam0920 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("홍길동");
names.add("임꺽정");
names.add("유관순");
names.add("김구");
names.add("안중근");
class MyConsumer<T> implements Consumer<T> {
@Override
public void accept(T item) {
System.out.println(item);
}
}
names.forEach(new MyConsumer<String>());
}
}
package com.eomcs.oop.ex12;
import java.util.ArrayList;
import java.util.function.Consumer;
public class Exam0921 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("홍길동");
names.add("임꺽정");
names.add("유관순");
names.add("김구");
names.add("안중근");
names.forEach(new Consumer<String>() {
@Override
public void accept(String item) {
System.out.println(item);
}
});
}
}
package com.eomcs.oop.ex12;
import java.util.ArrayList;
public class Exam0923 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("홍길동");
names.add("임꺽정");
names.add("유관순");
names.add("김구");
names.add("안중근");
names.forEach(System.out::println);
//
// names.forEach(new Consumer<String>() {
// @Override
// public void accept(String t) {
// System.out.println(t);
// };
// });
}
}
myapp
24. List가 특정 타입의 목록만 다룰 수 있게 맞춤 설정하는 방법: 제네릭 문법 적용
### 24. List가 특정 타입의 목록만 다룰 수 있게 맞춤 설정하는 방법: 제네릭 문법 적용
- 한 개의 클래스를 정의하여 타입 별로 클래스가 있는 것처럼 사용한다.
- 즉 타입 파라미터를 이용하여 목록에서 특정 타입의 값만 다루도록 제한한다.
- 형변환 없이 목록에 들어 있는 데이터를 꺼낸다.
- 이를 통해 각 타입 별로 전용 클래스가 있는 효과를 누린다.
package bitcamp.util;
// 사용하는 시점에서 타입을 고정하기
// => Iterator를 사용하는 시점에 어떤 타입의 데이터를 다룰 것인지 지정한다.
// 예) Iterator<Board>
public interface Iterator<T> {
boolean hasNext(); // 꺼낼 값이 있는지 검사할 때
T next(); // 값을 꺼낼 때
}
package bitcamp.util;
// iterator()가 리턴하는 객체(Iterator)가 어떤 타입의 데이터를 다룰 것인지 지정한다.
//
public interface Iterable<T> {
Iterator<T> iterator();
}
package bitcamp.util;
// List 인터페이스를 구현하는 시점에서 다룰 데이터의 타입을 지정한다.
// => 예) List<Board>
public interface List<E> extends Iterable<E> {
void add(E value);
Object[] toArray();
E get(int index);
E set(int index, E value);
boolean remove(E value);
int indexOf(E value);
int size();
}
package bitcamp.util;
// 이 클래스를 상속 받는 쪽에서
// 이 클래스가 다룰 데이터의 타입을 지정한다.
//
public abstract class AbstractList<E> implements List<E> {
protected int size;
@Override
public E get(int index) {
if (index < 0 || index >= this.size) {
throw new IndexOutOfBoundsException("인덱스가 무효합니다.");
}
return null;
}
@Override
public int size() {
return this.size;
}
@Override
public Iterator<E> iterator() {
// => return 문 + anonymous class
return new Iterator<E>() {
int cursor;
@Override
public boolean hasNext() {
return cursor >= 0 && cursor < AbstractList.this.size();
}
@Override
public E next() {
return AbstractList.this.get(cursor++);
}
};
}
}
package bitcamp.util;
// LinkedList 를 생성할 때 어떤 데이터를 다룰지 타입을 지정한다.
// 예) new LinkedList<Student>();
//
public class LinkedList<E> extends AbstractList<E> {
private Node<E> head;
private Node<E> tail;
@Override
public void add(E value) {
Node<E> node = new Node<>(value);
if (this.tail == null) { // size == 0, head == null
this.head = this.tail = node;
} else {
this.tail.next = node;
this.tail = node;
}
this.size++;
}
@Override
public Object[] toArray() {
Object[] values = new Object[this.size];
int index = 0;
Node<E> cursor = this.head;
while (cursor != null) {
values[index++] = cursor.value;
cursor = cursor.next;
}
return values;
}
@Override
public E set(int index, E value) {
if (index < 0 || index >= this.size) {
throw new IndexOutOfBoundsException("인덱스가 유효하지 않습니다.");
}
Node<E> cursor = head;
int i = 0;
while (cursor != null) {
if (i == index) {
E old = cursor.value;
cursor.value = value;
return old;
}
cursor = cursor.next;
i++;
}
return null;
}
@Override
public boolean remove(E value) {
Node<E> prevNode = null;
Node<E> deletedNode = null;
Node<E> cursor = this.head;
while (cursor != null) {
if (cursor.value.equals(value)) {
deletedNode = cursor;
break;
}
prevNode = cursor;
cursor = cursor.next;
}
if (deletedNode == null) {
return false;
}
if (prevNode == null) {
this.head = this.head.next;
deletedNode.next = null;
if (this.head == null) {
this.tail = null;
}
} else {
prevNode.next = deletedNode.next;
deletedNode.next = null;
if (prevNode.next == null) {
this.tail = prevNode;
}
}
this.size--;
return true;
}
@Override
public int indexOf(E b) {
Node<E> cursor = head;
int i = 0;
while (cursor != null) {
if (cursor.value.equals(b)) {
return i;
}
cursor = cursor.next;
i++;
}
return -1;
}
@Override
public E get(int index) {
super.get(index);
Node<E> cursor = head;
int i = 0;
while (i < index) {
cursor = cursor.next;
i++;
}
return cursor.value;
}
// Node 객체에 담을 데이터의 타입이 무엇인지 지정한다.
// 예) new Node<Student>();
//
static class Node<T> {
T value;
Node<T> next;
public Node() {}
public Node(T value) {
this.value = value;
}
}
}
package bitcamp.util;
import java.util.Arrays;
// 이 클래스의 인스턴스를 생성하는 쪽에서
// 이 클래스가 다룰 데이터의 타입을 지정한다.
//
public class ArrayList<E> extends AbstractList<E> {
private static final int SIZE = 3;
protected Object[] objects = new Object[SIZE];
@Override
public void add(E object) {
if (size == objects.length) {
objects = Arrays.copyOf(objects, objects.length + (objects.length >> 1));
}
this.objects[this.size++] = object;
}
@Override
public Object[] toArray() {
return Arrays.copyOf(objects, size);
}
@SuppressWarnings("unchecked")
@Override
public E get(int index) {
super.get(index);
return (E)this.objects[index];
}
@SuppressWarnings("unchecked")
@Override
public E set(int index, E object) {
E old = (E) this.objects[index];
this.objects[index] = object;
return old;
}
@Override
public boolean remove(E object) {
int index = indexOf(object);
if (index == -1) {
return false;
}
for (int i = index + 1; i < this.size; i++) {
this.objects[i - 1] = this.objects[i];
}
this.objects[--this.size] = null; // 레퍼런스 카운트를 줄인다.
return true;
}
@Override
public int indexOf(E object) {
for (int i = 0; i < this.size; i++) {
if (objects[i].equals(object)) {
return i;
}
}
return -1;
}
}
package bitcamp.myapp.dao;
import java.sql.Date;
import bitcamp.myapp.vo.Board;
import bitcamp.util.Iterator;
import bitcamp.util.List;
public class BoardDao {
List<Board> list;
public BoardDao(List<Board> list) {
this.list = list;
}
int lastNo;
public void insert(Board board) {
board.setNo(++lastNo);
board.setCreatedDate(new Date(System.currentTimeMillis()).toString());
list.add(board);
}
public Board[] findAll() {
Board[] boards = new Board[list.size()];
Iterator<Board> i = list.iterator();
int index = 0;
while (i.hasNext()) {
boards[index++] = i.next();
}
return boards;
}
public Board findByNo(int no) {
Board b = new Board();
b.setNo(no);
int index = list.indexOf(b);
if (index == -1) {
return null;
}
return list.get(index);
}
public void update(Board b) {
int index = list.indexOf(b);
list.set(index, b);
}
public boolean delete(Board b) {
return list.remove(b);
}
}
package bitcamp.myapp.dao;
import java.sql.Date;
import bitcamp.myapp.vo.Student;
import bitcamp.util.Iterator;
import bitcamp.util.List;
public class StudentDao {
List<Student> list;
int lastNo;
public StudentDao(List<Student> list) {
this.list = list;
}
public void insert(Student s) {
s.setNo(++lastNo);
s.setCreatedDate(new Date(System.currentTimeMillis()).toString());
list.add(s);
}
public Student[] findAll() {
Student[] students = new Student[list.size()];
Iterator<Student> i = list.iterator();
int index = 0;
while (i.hasNext()) {
students[index++] = i.next();
}
return students;
}
public Student findByNo(int no) {
Student s = new Student();
s.setNo(no);
int index = list.indexOf(s);
if (index == -1) {
return null;
}
return list.get(index);
}
public void update(Student s) {
int index = list.indexOf(s);
list.set(index, s);
}
public boolean delete(Student s) {
return list.remove(s);
}
}
package bitcamp.myapp.dao;
import java.sql.Date;
import bitcamp.myapp.vo.Teacher;
import bitcamp.util.Iterator;
import bitcamp.util.List;
public class TeacherDao {
List<Teacher> list;
int lastNo;
public TeacherDao(List<Teacher> list) {
this.list = list;
}
public void insert(Teacher t) {
t.setNo(++lastNo);
t.setCreatedDate(new Date(System.currentTimeMillis()).toString());
list.add(t);
}
public Teacher[] findAll() {
Teacher[] teachers = new Teacher[list.size()];
Iterator<Teacher> i = list.iterator();
int index = 0;
while (i.hasNext()) {
teachers[index++] = i.next();
}
return teachers;
}
public Teacher findByNo(int no) {
Teacher t = new Teacher();
t.setNo(no);
int index = list.indexOf(t);
if (index == -1) {
return null;
}
return list.get(index);
}
public void update(Teacher t) {
int index = list.indexOf(t);
list.set(index, t);
}
public boolean delete(Teacher t) {
return list.remove(t);
}
}
Handler 앞부분에 제네릭 적용한다.
package bitcamp.myapp.handler;
import bitcamp.myapp.dao.BoardDao;
import bitcamp.myapp.vo.Board;
import bitcamp.util.LinkedList;
import bitcamp.util.Prompt;
public class BoardHandler {
private BoardDao boardDao = new BoardDao(new LinkedList<Board>());
package bitcamp.myapp.handler;
import bitcamp.myapp.dao.StudentDao;
import bitcamp.myapp.vo.Student;
import bitcamp.util.ArrayList;
import bitcamp.util.Prompt;
public class StudentHandler {
private StudentDao memberDao = new StudentDao(new ArrayList<Student>());
package bitcamp.myapp.handler;
import bitcamp.myapp.dao.TeacherDao;
import bitcamp.myapp.vo.Teacher;
import bitcamp.util.ArrayList;
import bitcamp.util.Prompt;
public class TeacherHandler {
private TeacherDao teacherDao = new TeacherDao(new ArrayList<Teacher>());
25. 기존 List 구현체를 자바 컬렉션 API로 교체하기: java.util 패키지의 클래스 사용
### 25. 기존 List 구현체를 자바 컬렉션 API로 교체하기: java.util 패키지의 클래스 사용
- 기존의 List 관련 클래스를 모두 자바 컬렉션 API로 변환한다
우리가 만든 List, AbstractList, LinkedList, LinkedListTest, ArrayList, ArrayListTest, Iterable, Iterator 삭제한다.
Dao 3개에 아래 import로 바꾼다.
import java.util.Iterator;
import java.util.List;
BoardHandler는 아래 import로 바꾼다.
import java.util.LinkedList;
StudentHandler, TeacherHandler는 아래 import로 바꾼다.
import java.util.ArrayList;
조언
*
과제
예제 소스 학습
com/eomcs/oop/ex11/overview
com/eomcs/algorithm/data_structure
com/eomcs/basic/ex03~08
com/eomcs/generic
'네이버클라우드 AIaaS 개발자 양성과정 1기 > Java' 카테고리의 다른 글
[Java] 예제 소스 정리 - 파일 입출력 (0) | 2023.01.30 |
---|---|
[비트캠프] 59일차(13주차1일) - Java(컬렉션 API, 파일 입출력), myapp-26 (0) | 2023.01.30 |
[비트캠프] 57일차(12주차2일) - Java(중첩 클래스: static, non-static, local, 익명), myapp-23, backend-app 익명 클래스 적용 (0) | 2023.01.26 |
[비트캠프] 56일차(12주차1일) - Java: myapp-22, 인터페이스(인터페이스 필드, default, static 메서드, 다중 상속) (0) | 2023.01.25 |
[비트캠프] 55일차(11주차5일) - Java(인터페이스), myapp-19~21 (0) | 2023.01.20 |