F-lab Java 1์ฃผ์ฐจ / Phase 2 / Unit 2.6 ๋ณธ๊ฒฉ ํ์ต ์๋ฃ
9-์น์ ๋ง์คํฐ ํ๋กฌํํธ ํ์์ผ๋ก ๊น์ด ํํค์น๋ค.์ ์ ์ง์: Unit 2.4 (๋คํ์ฑ)
๋ค์ Unit: Phase 3 โ SOLID 5์์น์ด Unit์ ์๋ฏธ: Phase 2์ ๋ง๋ฌด๋ฆฌ + ๋๋ค(3์ฃผ์ฐจ)์ ์ ์ .
ํด๋์ค ์์ ํด๋์ค๋ผ๋ ๊ฐ๋ ์ ์ดํดํด์ผ Spring ์ฝ๋, ์ฝ๋ฐฑ ํจํด, ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ๋ค๋ฃฐ ์ ์๋ค.
ํฐ ์ง์ ์์ํด๋ณด์ธ์. ์ง ์์๋ ๋ค์ํ ๋ฐฉ์ด ์์ต๋๋ค:
๊ฐ ๋ฐฉ์:
โ Nested ํด๋์ค๋ ๋ง์ฐฌ๊ฐ์ง โ ์ธ๋ถ ํด๋์ค ์์์๋ง ์๋ฏธ ์๋ ์์ ๊ณต๊ฐ.
ํ์ ์ค ์ ๊น ๋ฉ๋ชจํ ์ผ์ด ์๊ฒผ์ต๋๋ค:
ํน์ง:
โ Anonymous ํด๋์ค โ ์ด๋ฆ ์์ด ์ฆ์์์ ์ ์ํ๋ ์ผํ์ฉ ํด๋์ค.
์ค๋งํธํฐ ์๋ ์ฑ์์:
"์ด ์๋์ด ์ธ๋ฆฌ๋ฉด ์ด ๋์ ์ ํด์ค"
์๋: 7์ 30๋ถ โ ?
โ
๋น์ : ๊ทธ๋๋ง์ ๋์ ์ ์
"์์
๋๊ณ + ์ปคํผ ์ด๊ณ + ์ปคํผ ๋จธ์ ์ผ๊ธฐ"
์ด ๋์์:
โ ์ต๋ช ํด๋์ค์ ์ ์ . ์๋ฐ์์๋ ํด๋ฆญ ๋ฆฌ์ค๋, ์ฝ๋ฐฑ ๋ฑ์์ ์์ฃผ ์ฌ์ฉ.
์๋ฐ ์ด๊ธฐ (1.0, 1995) ์๋ ๋ชจ๋ ํด๋์ค๊ฐ ๋ณ๋ ํ์ผ ์ด์์ต๋๋ค. ๊ทธ๋ฌ๋ค Java 1.1 (1997) ์์ Nested ํด๋์ค๊ฐ ๋์ .
๊ทธ ์ ์ ๋ฌธ์ โ ์์ ๋์ฐ๋ฏธ ํด๋์ค์ ์ธ๋ก์:
// MyList.java
public class MyList {
private Object[] data;
public Iterator iterator() {
return new MyListIterator(this);
}
}
// MyListIterator.java โ ๋ณ๋ ํ์ผ
public class MyListIterator implements Iterator {
private MyList list;
private int index;
public MyListIterator(MyList list) {
this.list = list;
}
public boolean hasNext() { ... }
public Object next() { ... }
}
๋ฌธ์ :
MyListIterator ๋ MyList ๋ง์ ์ํ ํด๋์คMyList ์ ๋ด๋ถ ์ ๋ณด ์ ๊ทผ ์ด๋ ค์ (private ๋ชป ๋ด)MyListIterator ์๋ชป ์ฌ์ฉํ ์ํpublic class MyList {
private Object[] data;
// ํด๋์ค ์์ ํด๋์ค โญ
private class MyListIterator implements Iterator {
private int index;
public boolean hasNext() {
return index < data.length; // ์ธ๋ถ ํด๋์ค์ private ์ ๊ทผ ๊ฐ๋ฅ!
}
public Object next() { ... }
}
public Iterator iterator() {
return new MyListIterator();
}
}
ํจ๊ณผ:
MyListIterator ๊ฐ MyList ์ ์ผ๋ถ์์ ๋ช
ํํ๋ฌธ์ : ํ ๋ฒ๋ง ์ธ ํด๋์ค๋ฅผ ๋งค๋ฒ ์ ์ํ๊ธฐ ๋ฒ๊ฑฐ๋ก์
// Java 1.1 ์ด์ โ ๋ณ๋ ํด๋์ค ์ ์ ํ์
public class MyButtonClickListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("ํด๋ฆญ๋จ");
}
}
button.addActionListener(new MyButtonClickListener());
// ๋ฒํผ 1๊ฐ๋ฅผ ์ํด ๋ณ๋ ํ์ผ/ํด๋์ค ์ ์ โ
Java 1.1์ ์ต๋ช ํด๋์ค:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("ํด๋ฆญ๋จ");
}
});
// ํ ๋ฒ์ ๋ โ
โ GUI ํ๋ก๊ทธ๋๋ฐ, ์ด๋ฒคํธ ์ฒ๋ฆฌ, ์ฝ๋ฐฑ ํจํด ์ ํญ๋ฐ์ ํ์ฉ.
20๋ ๊ฐ๊น์ด ์ต๋ช ํด๋์ค๊ฐ ์ฝ๋ฐฑ์ ํ์ค์ด์์ง๋ง, ๋๋ฌด ์ฅํฉํ๋ค ๋ ๋ฌธ์ :
// ์ต๋ช
ํด๋์ค
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};
// Java 8 ๋๋ค โ ๊ฐ์ ์ผ์ ํ ์ค์
Comparator<String> comp = (a, b) -> a.length() - b.length();
๋๋ค์ ๋ณธ์ง:
โ Nested/Inner/Anonymous๋ ๋๋ค์ ์ ์ . ์งํ์ ์ญ์ฌ๋ฅผ ์์์ผ ๋๋ค๋ฅผ ์ ํํ ์ดํด.
"ํด๋์ค ์์ ํด๋์ค๋ '๊ฐํ ๊ด๊ณ' ๋ฅผ ํํํ๋ ๋๊ตฌ๋ค."
์ธ๋ถ ํด๋์ค์ ๋ผ๋ ค์ผ ๋ ์ ์๋ ์์ ํด๋์ค๋ฅผ ๋ง๋ค ๋, ๋ณ๋ ํ์ผ์ด ์๋ ์์ ๋๋ฉด ๊ด๊ณ๊ฐ ๋ช ํ ํด์ง๊ณ ์บก์ํ๊ฐ ๊ฐํด์ง๋ค.
์ต๋ช ํด๋์ค๋ ๋ ๋์๊ฐ ์ด๋ฆ์กฐ์ฐจ ํ์ ์๋ ์ผํ์ฉ ๊ฐ์ฒด ๋ฅผ ์ฆ์์์ ๋ง๋๋ ๋๊ตฌ. ์ด ์ฌ๊ณ ๋ฐฉ์์ด ๋๋ค๋ก ์งํ.
// โ ๋ณ๋ ํ์ผ๋ก ๋ถ๋ฆฌํ๋ค๋ฉด
public class PageRequest {
private int page;
private int size;
}
public class PageResult {
private List<?> data;
private int totalCount;
private int currentPage;
}
public class FareService {
public PageResult findByPage(PageRequest request) { ... }
}
๋ฌธ์ :
PageRequest, PageResult ๋ ํ์ด์ง๋ค์ด์
์์๋ง ์ฐ๋๋ฐ ๊ณต์ฉ ์์น์ ์์public class FareService {
// ๋ฉ์๋์ ์
๋ ฅ โ ์ธ๋ถ์์๋ ์ฌ์ฉ
public static class PageRequest {
private int page;
private int size;
}
// ๋ฉ์๋์ ๊ฒฐ๊ณผ โ ์ธ๋ถ์์๋ ์ฌ์ฉ
public static class PageResult<T> {
private List<T> data;
private int totalCount;
private int currentPage;
}
public PageResult<Fare> findByPage(PageRequest request) {
// ...
}
}
// ์ฌ์ฉ
FareService.PageRequest request = new FareService.PageRequest(0, 10);
FareService.PageResult<Fare> result = fareService.findByPage(request);
ํจ๊ณผ:
PageRequest ๊ฐ FareService ์ ๊ด๋ จ๋จ์ด ๋ช
ํ// Spring์ ํธ๋์ญ์
์ฝ๋ฐฑ
public class FareService {
public void processInTransaction() {
transactionTemplate.execute(new MyTransactionCallback());
}
}
// ๋ณ๋ ํด๋์ค ์ ์
public class MyTransactionCallback implements TransactionCallback<Void> {
@Override
public Void doInTransaction(TransactionStatus status) {
// ํธ๋์ญ์
์์์ ํ ์ผ
return null;
}
}
๋ฌธ์ :
public class FareService {
public void processInTransaction() {
transactionTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
// ํธ๋์ญ์
์์์ ํ ์ผ
return null;
}
});
}
}
ํจ๊ณผ:
public class FareService {
public void processInTransaction() {
transactionTemplate.execute(status -> {
// ํธ๋์ญ์
์์์ ํ ์ผ
return null;
});
}
}
โ ์ต๋ช ํด๋์ค โ ๋๋ค๋ก์ ์งํ. ๊ฐ์ ์ผ, ์งง์ ํํ.
1. Top-Level Class โ ์ผ๋ฐ ํด๋์ค (๋ณ๋ ํ์ผ)
2. Nested Class โ ํด๋์ค ์์ ํด๋์ค
โโโ Static Nested Class โ static ํค์๋ ์์
โโโ Inner Class โ static ์์ (= ๋ด๋ถ ํด๋์ค)
โโโ Member Inner โ ํด๋์ค ๋ณธ๋ฌธ์ ์ ์
โโโ Local Inner โ ๋ฉ์๋ ์์ ์ ์
โโโ Anonymous Inner โ ์ด๋ฆ ์๋ ์ฆ์ ์ ์
์ฉ์ด ์ ๋ฆฌ:
public class Outer {
private static String staticData = "static";
private String instanceData = "instance";
public static class StaticNested {
public void show() {
System.out.println(staticData); // โ
static ๋ฉค๋ฒ ์ ๊ทผ OK
// System.out.println(instanceData); // โ ์ธ์คํด์ค ๋ฉค๋ฒ ์ ๊ทผ X
}
}
}
// ์ฌ์ฉ โ ์ธ๋ถ ํด๋์ค ์ธ์คํด์ค ๋ถํ์
Outer.StaticNested nested = new Outer.StaticNested();
nested.show();
ํน์ง โญ :
์ธ์ ์ฌ์ฉ:
public class Outer {
private String instanceData = "instance";
public class Inner {
public void show() {
System.out.println(instanceData); // โ
์ธ๋ถ ์ธ์คํด์ค ๋ฉค๋ฒ ์ ๊ทผ OK
}
}
}
// ์ฌ์ฉ โ ์ธ๋ถ ํด๋์ค ์ธ์คํด์ค ๋จผ์ ํ์
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // โ ๏ธ ํน์ดํ ๋ฌธ๋ฒ
inner.show();
ํน์ง โญ :
์ธ์ ์ฌ์ฉ:
public class Outer {
public void doSomething() {
// ๋ฉ์๋ ์์์๋ง ์๋ฏธ ์๋ ํด๋์ค
class Helper {
public void help() {
System.out.println("๋์");
}
}
Helper h = new Helper();
h.help();
}
}
ํน์ง:
์ธ์ ์ฌ์ฉ:
์ด๋ฆ ์์ด ์ฆ์์์ ์ ์ + ์ธ์คํด์ค ์์ฑ:
public class FareService {
public void process() {
// ํ ์ค์ง๋ฆฌ ์ฝ๋ฐฑ
Runnable task = new Runnable() { // โ ์ต๋ช
ํด๋์ค
@Override
public void run() {
System.out.println("์์
์คํ");
}
};
task.run();
}
}
๋ฌธ๋ฒ โญ :
new ์ธํฐํ์ด์ค๋๋ํด๋์ค() {
// ๋ฉ์๋ ๊ตฌํ
};
ํน์ง:
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("์ค๋ ๋ ์คํ");
}
};
thread.start();
| Static Nested | Member Inner | Local Inner | Anonymous | |
|---|---|---|---|---|
| ์์น | ํด๋์ค ๋ณธ๋ฌธ | ํด๋์ค ๋ณธ๋ฌธ | ๋ฉ์๋ ์ | ์ฆ์ |
| ์ด๋ฆ | O | O | O | X |
| static ํค์๋ | O | X | X | X |
| ์ธ๋ถ ์ธ์คํด์ค ํ์ | X | O | (๋ฉ์๋ ํธ์ถ ์) | (๋ฉ์๋ ํธ์ถ ์) |
| ์ธ๋ถ ๋ฉค๋ฒ ์ ๊ทผ | static๋ง | ๋ชจ๋ | ๋ชจ๋ + ์ง์ญ๋ณ์ | ๋ชจ๋ + ์ง์ญ๋ณ์ |
| ์ฌ์ฉ ๋น๋ | ๋์ | ์ค๊ฐ | ๋งค์ฐ ๋ฎ์ | ๋์ (๋๋ค๋ก ๋์ฒด ์ค) |
์๋ฐ ์ปดํ์ผ๋ฌ๋ Nested ํด๋์ค๋ฅผ ๋ณ๋ .class ํ์ผ ๋ก ๋ถ๋ฆฌํฉ๋๋ค:
// Outer.java
public class Outer {
public static class StaticNested { ... }
public class Inner { ... }
public void method() {
class Local { ... }
Runnable r = new Runnable() { ... };
}
}
์ปดํ์ผ ๊ฒฐ๊ณผ:
Outer.class
Outer$StaticNested.class โ static nested
Outer$Inner.class โ inner
Outer$1Local.class โ local
Outer$1.class โ anonymous
$ ๊ธฐํธ โญ โ ์๋ฐ์ nested ํด๋์ค ๋ช
๋ช
๊ท์น.
โ JVM ์ ์ฅ์์๋ ๋ชจ๋ ๋ณ๋์ ํด๋์ค.
Inner Class๋ ์ธ๋ถ ํด๋์ค์ ์ธ์คํด์ค ์ฐธ์กฐ ๋ฅผ ์๋์ผ๋ก ๋ณด์ :
public class Outer {
private String data = "outer data";
public class Inner {
public void show() {
System.out.println(data); // ์ด๋ป๊ฒ ์ ๊ทผ?
}
}
}
์ปดํ์ผ๋ฌ๊ฐ ๋ณํ (์์ฌ ์ฝ๋):
public class Outer {
private String data = "outer data";
}
public class Outer$Inner {
private final Outer this$0; // โ ์จ๊ฒจ์ง ์ธ๋ถ ์ฐธ์กฐ
public Outer$Inner(Outer outer) {
this.this$0 = outer;
}
public void show() {
System.out.println(this$0.data); // ์ธ๋ถ ์ฐธ์กฐ๋ก ์ ๊ทผ
}
}
์ค์ํ ํจ์ โญโญ :
public class Outer {
private int count = 0;
public void process() {
Runnable r = new Runnable() {
@Override
public void run() {
count++; // ์ธ๋ถ ํ๋ ์ ๊ทผ
}
};
}
}
์ปดํ์ผ๋ฌ๊ฐ ์์ฑ:
class Outer$1 implements Runnable {
private final Outer this$0;
public Outer$1(Outer outer) {
this.this$0 = outer;
}
@Override
public void run() {
this$0.count++;
}
}
โ ์ต๋ช ํด๋์ค๋ ๊ฒฐ๊ตญ ๋ณ๋ .class ํ์ผ + ์ธ๋ถ ์ฐธ์กฐ ๋ณด์ .
Local/Anonymous ํด๋์ค๊ฐ ๋ฉ์๋ ์ง์ญ๋ณ์ ๋ฅผ ์ฌ์ฉํ ๋:
public void process() {
int count = 10; // ์ง์ญ๋ณ์
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(count); // โ
์ฌ์ฉ ๊ฐ๋ฅ
// count = 20; // โ ์ปดํ์ผ ์๋ฌ
}
};
}
๊ท์น โญ :
์?:
effectively final:
public void process() {
int count = 10; // ํ ๋ฒ๋ ์ฌํ ๋น ์ ๋จ โ effectively final
Runnable r = () -> System.out.println(count); // โ
}
// vs
public void process() {
int count = 10;
count = 20; // ์ฌํ ๋น โ effectively final ์๋
Runnable r = () -> System.out.println(count); // โ ์ปดํ์ผ ์๋ฌ
}
// ์ต๋ช
ํด๋์ค (Java 1.1+)
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("ํด๋ฆญ");
}
});
// ๋๋ค (Java 8+)
button.addActionListener(e -> System.out.println("ํด๋ฆญ"));
๋๋ค๊ฐ ๊ฐ๋ฅํ ์กฐ๊ฑด โ ํจ์ํ ์ธํฐํ์ด์ค (๋ฉ์๋ 1๊ฐ):
@FunctionalInterface
public interface ActionListener {
void actionPerformed(ActionEvent e); // ๋ฉ์๋ 1๊ฐ
}
๋๋ค์ ํ๊ณ:
โ 3์ฃผ์ฐจ์์ ๋ณธ๊ฒฉ ํ์ต.
public class Fare {
private final Long id;
private final int amount;
private final String currency;
private final Customer customer;
private Fare(Builder builder) {
this.id = builder.id;
this.amount = builder.amount;
this.currency = builder.currency;
this.customer = builder.customer;
}
public static Builder builder() {
return new Builder();
}
// Static Nested Class โ Builder
public static class Builder {
private Long id;
private int amount;
private String currency = "KRW"; // ๊ธฐ๋ณธ๊ฐ
private Customer customer;
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder amount(int amount) {
if (amount < 0) throw new IllegalArgumentException();
this.amount = amount;
return this;
}
public Builder currency(String currency) {
this.currency = currency;
return this;
}
public Builder customer(Customer customer) {
this.customer = customer;
return this;
}
public Fare build() {
return new Fare(this);
}
}
}
// ์ฌ์ฉ
Fare fare = Fare.builder()
.id(1L)
.amount(50000)
.currency("USD")
.customer(alice)
.build();
ํจ๊ณผ:
Builder ๊ฐ Fare ์ ๊ฐํ๊ฒ ์ฐ๊ด๋จ์ด ๋ช
ํโ Lombok์ @Builder ๋ ๋ด๋ถ์ ์ผ๋ก ์ด ํจํด.
public class FareController {
@PostMapping("/api/fares")
public ApiResponse<FareResponse> create(@RequestBody CreateFareRequest request) {
Fare fare = fareService.create(request);
return ApiResponse.success(FareResponse.from(fare));
}
// ์๋ต DTO โ Static Nested
public static class CreateFareRequest {
private Long customerId;
private int amount;
// getters/setters
}
public static class FareResponse {
private Long id;
private int amount;
private String status;
public static FareResponse from(Fare fare) {
FareResponse response = new FareResponse();
response.id = fare.getId();
response.amount = fare.getAmount();
response.status = fare.getStatus().name();
return response;
}
}
}
ํจ๊ณผ:
public class FareCollection {
private Fare[] fares;
private int size;
public Iterator<Fare> iterator() {
return new FareIterator(); // ์ธ๋ถ ์ธ์คํด์ค์ it
}
// Inner Class โ ์ธ๋ถ์ fares, size ์ง์ ์ ๊ทผ
private class FareIterator implements Iterator<Fare> {
private int index = 0;
@Override
public boolean hasNext() {
return index < size; // ์ธ๋ถ ํด๋์ค์ size ์ ๊ทผ
}
@Override
public Fare next() {
return fares[index++]; // ์ธ๋ถ ํด๋์ค์ fares ์ ๊ทผ
}
}
}
// ์ฌ์ฉ
FareCollection collection = new FareCollection();
for (Fare fare : collection) { // for-each ๊ฐ๋ฅ
// ...
}
ํจ๊ณผ:
FareIterator ๊ฐ FareCollection ์ ๋ด๋ถ์ ๊ฐํ๊ฒ ์์กดโ ArrayList, LinkedList ๋ฑ ์๋ฐ ์ปฌ๋ ์ ์ด ์ ํํ ์ด ํจํด.
@Service
public class FareService {
@Autowired
private TransactionTemplate transactionTemplate;
public void processInTransaction(Long fareId) {
// ์ต๋ช
ํด๋์ค๋ก ์ฝ๋ฐฑ ์ ์
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
Fare fare = fareRepository.findById(fareId).orElseThrow();
fare.process();
fareRepository.save(fare);
}
});
}
}
// ๋๋ค๋ก (Java 8+)
public void processInTransactionLambda(Long fareId) {
transactionTemplate.execute(status -> {
Fare fare = fareRepository.findById(fareId).orElseThrow();
fare.process();
fareRepository.save(fare);
return null;
});
}
โ ์ฝ๋ฐฑ์ ์ต๋ช ํด๋์ค์ ๋ํ ์ฌ์ฉ์ฒ. ๋๋ค๋ก ์งํ.
public class IlicEventBus {
private final List<EventListener> listeners = new ArrayList<>();
public void register(EventListener listener) {
listeners.add(listener);
}
}
public class IlicApplication {
public void init() {
// ์ด์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
eventBus.register(new EventListener() {
@Override
public void onEvent(Event event) {
if (event instanceof FareCreatedEvent fareEvent) {
System.out.println("์ด์ ์์ฑ๋จ: " + fareEvent.getFareId());
}
}
});
// ๊ฒฐ์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก
eventBus.register(new EventListener() {
@Override
public void onEvent(Event event) {
if (event instanceof PaymentCompletedEvent paymentEvent) {
System.out.println("๊ฒฐ์ ์๋ฃ: " + paymentEvent.getPaymentId());
}
}
});
}
}
// ๋๋ค๋ก (์ธํฐํ์ด์ค ๋ฉ์๋ 1๊ฐ์ผ ๋)
eventBus.register(event -> {
if (event instanceof FareCreatedEvent fareEvent) {
System.out.println("์ด์ ์์ฑ๋จ: " + fareEvent.getFareId());
}
});
List<Fare> fares = ...;
// ์ต๋ช
ํด๋์ค
fares.sort(new Comparator<Fare>() {
@Override
public int compare(Fare a, Fare b) {
return Integer.compare(a.getAmount(), b.getAmount());
}
});
// ๋๋ค (Java 8+)
fares.sort((a, b) -> Integer.compare(a.getAmount(), b.getAmount()));
// ๋ฉ์๋ ์ฐธ์กฐ (๋ ๊ฐ๊ฒฐ)
fares.sort(Comparator.comparingInt(Fare::getAmount));
โ ์ต๋ช ํด๋์ค โ ๋๋ค โ ๋ฉ์๋ ์ฐธ์กฐ ์ ์งํ ํ๋ฆ.
public class FareSearchCriteria {
private final Range<Integer> amountRange;
private final Range<LocalDate> dateRange;
private final List<FareStatus> statuses;
// Static Nested โ Range ํด๋์ค (FareSearchCriteria ์ ์ฉ)
public static class Range<T extends Comparable<T>> {
private final T from;
private final T to;
public Range(T from, T to) {
if (from != null && to != null && from.compareTo(to) > 0) {
throw new IllegalArgumentException("from > to");
}
this.from = from;
this.to = to;
}
public boolean contains(T value) {
if (from != null && value.compareTo(from) < 0) return false;
if (to != null && value.compareTo(to) > 0) return false;
return true;
}
}
}
// ์ฌ์ฉ
FareSearchCriteria.Range<Integer> amountRange = new FareSearchCriteria.Range<>(1000, 100000);
public class Outer {
private byte[] hugeData = new byte[100 * 1024 * 1024]; // 100MB
public class Inner {
// Outer ์ฐธ์กฐ ์๋ ๋ณด์
}
public Inner createInner() {
return new Inner();
}
}
// ์ฌ์ฉ
Outer outer = new Outer();
Inner inner = outer.createInner();
outer = null; // outer ์ฐธ์กฐ ํด์ ์๋
// ๊ทธ๋ฌ๋ inner๊ฐ ์ด์์์ผ๋ฉด outer๋ GC ์ ๋จ โ
// โ 100MB ๋์
ํด๊ฒฐ โ Static Nested ์ฌ์ฉ:
public class Outer {
private byte[] hugeData = new byte[100 * 1024 * 1024];
public static class StaticInner {
// Outer ์ฐธ์กฐ ์์ โ
}
}
์์น โญ :
"ํ์ ์์ผ๋ฉด Static Nested"
"Inner Class๋ ์ ๋ง ์ธ๋ถ ์ธ์คํด์ค์ ์ ๊ทผํด์ผ ํ ๋๋ง"
โ Joshua Bloch (Effective Java) ๊ฐ๋ ฅ ๊ถ์ฅ.
public class Outer {
public void process() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(this); // โ ๏ธ ์ด this๋ ๋๊ตฌ?
System.out.println(Outer.this); // โ ์ธ๋ถ ํด๋์ค์ this
}
};
}
}
๊ท์น:
this = ์ต๋ช
ํด๋์ค ์ธ์คํด์ค์ธ๋ถํด๋์ค๋ช
.this = ์ธ๋ถ ํด๋์ค ์ธ์คํด์คpublic void process() {
int count = 0;
Runnable r = new Runnable() {
@Override
public void run() {
count++; // โ ์ปดํ์ผ ์๋ฌ โ effectively final ์๋
}
};
}
ํด๊ฒฐ โ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ์ปจํ ์ด๋ ์ฌ์ฉ:
public void process() {
int[] count = {0}; // ๋ฐฐ์ด์ ์ฐธ์กฐ๊ฐ final์ด๋ฉด OK
// ๋๋
AtomicInteger count = new AtomicInteger(0);
Runnable r = new Runnable() {
@Override
public void run() {
count[0]++; // โ
// count.incrementAndGet(); // AtomicInteger
}
};
}
โ ์ฝ๊ฐ์ ์ฐํ. ์ง์ง ๊ถ์ฅ์ ์ํ๋ฅผ ์ธ๋ถ์์ ๊ด๋ฆฌ.
// โ ์๋ฐ 8+ ํ๊ฒฝ์์ ๋๋ฌด ์ฅํฉ
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doSomething();
}
});
// โ
๋๋ค๋ก ๊ฐ๊ฒฐํ๊ฒ
button.addActionListener(e -> doSomething());
์กฐ๊ฑด โญ :
// โ ๊ฐ๋
์ฑ ์ง์ฅ
service.process(new Runnable() {
@Override
public void run() {
helper.execute(new Callback() {
@Override
public void onComplete() {
another.handle(new Handler() {
@Override
public void handle() {
// ... ์ฝ๋ฐฑ ์ง์ฅ โ
}
});
}
});
}
});
ํด๊ฒฐ โ ๋๋ค + ๋ฉ์๋ ๋ถ๋ฆฌ:
service.process(this::doWork);
private void doWork() {
helper.execute(this::onComplete);
}
private void onComplete() {
another.handle(this::onHandle);
}
private void onHandle() { ... }
โ ์ฝ๋ฐฑ ์ง์ฅ์ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ์ ๊ณ ์ ์ ๋ฌธ์ . ํ๋์๋ CompletableFuture, Reactive ๋ก ํด๊ฒฐ.
public class Outer {
public class Helper { // โ ๏ธ static ๋น ์ง โ Inner Class ๋จ
// ...
}
}
// ์ฌ์ฉ ์
Outer.Helper h = new Outer.Helper(); // โ ์ปดํ์ผ ์๋ฌ
// Outer ์ธ์คํด์ค ํ์
Outer outer = new Outer();
Outer.Helper h = outer.new Helper(); // โ ๏ธ ํน์ดํ ๋ฌธ๋ฒ
๊ท์น:
"๊ธฐ๋ณธ์ static, ์ ๋ง ํ์ํ ๋๋ง ์ธ์คํด์ค ๋ฉค๋ฒ ์ ๊ทผ์ฉ inner"
Runnable r = new Runnable() {
@Override
public void run() { ... }
public void newMethod() { // โ ๏ธ ์ ์๋ ๊ฐ๋ฅ
// ...
}
};
r.newMethod(); // โ ์ปดํ์ผ ์๋ฌ โ Runnable์ ์์
โ ์ต๋ช ํด๋์ค์ ์ ๋ฉ์๋ ์ถ๊ฐํด๋ ์ธ๋ถ์์ ํธ์ถ ๋ถ๊ฐ (์ธํฐํ์ด์ค ํ์ ์ผ๋ก๋ง ๋ค๋ฃธ).
โ ์ ๋ฉ์๋๊ฐ ํ์ํ๋ฉด ์ด๋ฆ ์๋ ํด๋์ค ์ฌ์ฉ.
[Phase 2 ์๋ฃ]
โ
[Phase 3: SOLID 5์์น] โ ๊ฐ์ฒด ์ค๊ณ์ ํฉ๊ธ๋ฅ
โ
[Phase 4: JVM ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ]
โ
... ๊ณ์
[Anonymous Class (Java 1.1)]
โ
[Lambda (Java 8)]
โ
[Method Reference (Java 8)]
โ
[Stream API (Java 8)]
โ 3์ฃผ์ฐจ์์ ๋ณธ๊ฒฉ ํ์ต.
1์ฃผ์ฐจ ๋ด:
๋ฏธ๋ ์ฃผ์ฐจ:
"์ธ๋ถ ํด๋์ค์ ์ธ์คํด์ค ๋ฉค๋ฒ์ ์ ๊ทผํด์ผ ํ๋?"
โ
YES โโ Inner Class
| (Iterator, View ๋ฑ ๊ฐํ ๊ฒฐํฉ)
|
NO โโโ Static Nested Class โญ (๋๋ถ๋ถ์ ๊ฒฝ์ฐ)
(Builder, DTO, ๋ณด์กฐ ํด๋์ค)
์์น: ๊ธฐ๋ณธ์ static, ์ ๋ง ํ์ํ ๋๋ง inner.
| ์ง๋ฌธ | ์ด Unit์์์ ๋ต |
|---|---|
| "Inner Class์ Static Nested Class์ ์ฐจ์ด?" | static ์ ๋ฌด + ์ธ๋ถ ์ธ์คํด์ค ์ ๊ทผ ์ฐจ์ด |
| "Inner Class์ ๋ฉ๋ชจ๋ฆฌ ๋์ ์ํ?" | ์ธ๋ถ ์ฐธ์กฐ ์๋ ๋ณด์ โ ์ธ๋ถ GC ๋ฐฉํด |
| "์ต๋ช ํด๋์ค๋?" | ์ด๋ฆ ์์ด ์ฆ์์์ ์ ์ํ๋ ์ผํ์ฉ ํด๋์ค |
| "์ต๋ช ํด๋์ค์ ๋๋ค์ ์ฐจ์ด?" | ๋๋ค๋ ํจ์ํ ์ธํฐํ์ด์ค ํ์ , ๋ ๊ฐ๊ฒฐ |
| "Local Inner Class์ effectively final?" | ์ง์ญ๋ณ์๋ ๋ณ๊ฒฝ ๋ถ๊ฐ์ฌ์ผ ์ฌ์ฉ ๊ฐ๋ฅ |
1๏ธโฃ Nested ํด๋์ค๋ "๊ฐํ ๊ด๊ณ" ๋ฅผ ํํํ๋ ๋๊ตฌ๋ค.
์ธ๋ถ ํด๋์ค์ ๋ผ๋ ค์ผ ๋ ์ ์๋ ์์ ํด๋์ค๋ฅผ ์์ ๋๋ฉด ๊ด๊ณ๊ฐ ๋ช ํํด์ง๊ณ ์บก์ํ๊ฐ ๊ฐํด์ง๋ค. Static Nested (์ธ๋ถ ์ฐธ์กฐ ์์) ์ Inner (์ธ๋ถ ์ฐธ์กฐ ๋ณด์ ) ๋ก ๋๋๋๋ฐ, ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง๋ฅผ ์ํด ๊ฐ๋ฅํ๋ฉด Static Nested ๊ถ์ฅ.
2๏ธโฃ ์ต๋ช ํด๋์ค๋ "์ด๋ฆ ์๋ ์ผํ์ฉ ๊ฐ์ฒด" ๋ฅผ ์ฆ์์์ ๋ง๋ ๋ค.
ํ ๋ฒ๋ง ์ธ ์ฝ๋ฐฑ์ด๋ ๋ฆฌ์ค๋์ ์ ํฉ. ์ปดํ์ผ๋ฌ๊ฐ
Outer$1.class๊ฐ์ ๋ณ๋ .class ํ์ผ๋ก ๋ณํ. ํจ์ํ ์ธํฐํ์ด์ค์ธ ๊ฒฝ์ฐ Java 8+ ๋๋ค๋ก ๋ ๊ฐ๊ฒฐํ๊ฒ ํํ ๊ฐ๋ฅ โ ์ต๋ช ํด๋์ค๊ฐ ๋๋ค์ ์ ์ ์ด๋ค.3๏ธโฃ Local Inner์ ์ง์ญ๋ณ์๋ effectively final ์ด์ด์ผ ํ๋ค.
๋ฉ์๋ ์์ ํด๋์ค๊ฐ ์ธ๋ถ ๋ฉ์๋์ ์ง์ญ๋ณ์๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๊ทธ ๋ณ์๋ final ๋๋ ์ฌ์ค์ final (์ฌํ ๋น ์ ๋จ). ์ปดํ์ผ๋ฌ๊ฐ ์ง์ญ๋ณ์๋ฅผ ํด๋์ค ์์ ๋ณต์ฌํ๊ธฐ ๋๋ฌธ์, ์๋ณธ๊ณผ ๋ณต์ฌ๋ณธ์ ์ผ๊ด์ฑ์ ์ํ ๊ฐ์ . ์ด ๊ท์น์ ๋๋ค์๋ ๊ทธ๋๋ก ์ ์ฉ๋๋ค.
Q1: ์ต๋ช ํด๋์ค๋ ์ธ์ ๋๋ค๋ก ๋์ฒดํ ์ ์๋๊ฐ?
์กฐ๊ฑด: ํจ์ํ ์ธํฐํ์ด์ค (๋ฉ์๋ 1๊ฐ) ์ ์ต๋ช ํด๋์ค๋ง ๋๋ค๋ก ๋์ฒด ๊ฐ๋ฅ.
๊ฐ๋ฅํ ๊ฒฝ์ฐ:
// ์ธํฐํ์ด์ค๊ฐ ๋ฉ์๋ 1๊ฐ
@FunctionalInterface
public interface Runnable {
void run();
}
// ์ต๋ช
ํด๋์ค
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("์คํ");
}
};
// ๋๋ค๋ก ๋ณํ โ
Runnable r = () -> System.out.println("์คํ");
๋ํ ํจ์ํ ์ธํฐํ์ด์ค:
Runnable (run)Comparator<T> (compare)Consumer<T> (accept)Function<T, R> (apply)Supplier<T> (get)Predicate<T> (test)๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ:
public interface MultiInterface {
void methodA();
void methodB();
}
// ์ต๋ช
ํด๋์ค๋ง ๊ฐ๋ฅ โ ๋๋ค X
MultiInterface m = new MultiInterface() {
@Override public void methodA() { ... }
@Override public void methodB() { ... }
};
// ์ต๋ช
ํด๋์ค๋ก ํด๋์ค ์์
Thread t = new Thread() {
@Override
public void run() { ... }
};
// ๋๋ค๋ ์ธํฐํ์ด์ค ํ์ โ ํด๋์ค ์์ X
// ์ ์ฝ๋๋ฅผ ๋๋ค๋ก? ๊ฐ๋ฅ โ Thread๋ Runnable์ ๋ฐ์
Thread t = new Thread(() -> { ... });
Runnable r = new Runnable() {
private int count = 0; // ์ํ ๋ณด์
@Override
public void run() {
count++;
System.out.println(count);
}
};
// ๋๋ค๋ ์ํ ๋ณด์ X โ ์ธ๋ถ ๋ณ์ ํ์ฉํด์ผ
๋๋ค์ ํ๊ณ ์ ๋ฆฌ:
โ ๊ฐ๋จํ ์ฝ๋ฐฑ์ ๋๋ค, ๋ณต์กํ ๊ฐ์ฒด๋ ์ต๋ช ํด๋์ค ๋๋ ๋ช ๋ช ํด๋์ค.
Q2: ๋ด๋ถ ํด๋์ค๊ฐ ์ธ๋ถ ํด๋์ค์ private ํ๋์ ์ ๊ทผ ๊ฐ๋ฅํ ์ด์ ๋?
ํ ์ค ๋ต: ์ปดํ์ผ๋ฌ๊ฐ ์ธ๋ถ ํด๋์ค์ ์ธ์คํด์ค ์ฐธ์กฐ๋ฅผ ์๋์ผ๋ก ๋ด๋ถ ํด๋์ค์ ์ฃผ์ ํ๊ธฐ ๋๋ฌธ.
์์ธ ์ค๋ช :
public class Outer {
private String secret = "๋น๋ฐ";
public class Inner {
public void reveal() {
System.out.println(secret); // โ
private ์ ๊ทผ OK
}
}
}
์ปดํ์ผ๋ฌ๊ฐ ๋ณํ (์์ฌ ์ฝ๋):
public class Outer {
private String secret = "๋น๋ฐ";
// ํฉ์ฑ ๋ฉ์๋ ์๋ ์์ฑ โญ
static String access$000(Outer outer) {
return outer.secret;
}
}
public class Outer$Inner {
final Outer this$0; // ์ธ๋ถ ์ธ์คํด์ค ์ฐธ์กฐ ์๋ ๋ณด์
public Outer$Inner(Outer outer) {
this.this$0 = outer;
}
public void reveal() {
// ํฉ์ฑ ๋ฉ์๋๋ฅผ ํตํด ์ ๊ทผ
System.out.println(Outer.access$000(this$0));
}
}
ํต์ฌ ๋ฉ์ปค๋์ฆ โญ :
์ปดํ์ผ๋ฌ๊ฐ ์ธ๋ถ ์ธ์คํด์ค ์ฐธ์กฐ ์ฃผ์
this$0 ๋ผ๋ ์จ๊ฒจ์ง ํ๋์ ์ ์ฅํฉ์ฑ ๋ฉ์๋ (Synthetic Method) ์๋ ์์ฑ
access$000, access$100 ๊ฐ์ ์ด๋ฆJVM ์ ์ฅ์์๋ private ์ง์ ์ ๊ทผ X
์ ์ด๋ ๊ฒ ๋์? โ ์ธ์ด์ ํธ์์ฑ์ ์ํ ์ค๊ณ:
"๋ด๋ถ ํด๋์ค๋ ์ธ๋ถ ํด๋์ค์ ์ผ๋ถ" ๋ผ๋ ์ง๊ด์ ์ฝ๋์์๋ ์ ์งํ๊ธฐ ์ํด
private ๋ฉค๋ฒ์๋ ์์ฐ์ค๋ฝ๊ฒ ์ ๊ทผํ ์ ์๋๋ก ์ปดํ์ผ๋ฌ๊ฐ ๋์์ค
Static Nested์ ๋น๊ต:
public class Outer {
private static String staticSecret = "static ๋น๋ฐ";
private String instanceSecret = "instance ๋น๋ฐ";
public static class StaticNested {
public void show() {
System.out.println(staticSecret); // โ
static ์ ๊ทผ OK
// System.out.println(instanceSecret); // โ ์ธ๋ถ ์ธ์คํด์ค ์์
}
}
}
โ Static Nested๋ ์ธ๋ถ ์ธ์คํด์ค ์ฐธ์กฐ ์์ โ ์ธ์คํด์ค ๋ฉค๋ฒ ์ ๊ทผ X.
Inner์ ์ํ์ฑ โ ๏ธ :
์ด ์๋ ์ธ๋ถ ์ฐธ์กฐ ๋๋ฌธ์:
๊ฒฐ๋ก :
"์ธ๋ถ ์ธ์คํด์ค ๋ฉค๋ฒ์ ์ ๊ทผํด์ผ ํ๋ค๋ฉด Inner Class,
๊ทธ๋ ์ง ์๋ค๋ฉด Static Nested Class๋ฅผ ์ฌ์ฉํ๋ผ" โ Joshua Bloch (Effective Java)
| Unit | ์ฃผ์ | ํต์ฌ |
|---|---|---|
| 2.1 | ๋ฉ์๋์ ๊ตฌ์กฐ | ์๊ทธ๋์ฒ, ์ ๊ทผ ์ ์ด์, ์ค๋ฒ๋ก๋ฉ |
| 2.2 | ๊ฐ๋ณ์ธ์ | ํ์
... ๋ณ์๋ช
, ๋ฐฐ์ด๋ก ๋ณํ |
| 2.3 | ์์๊ณผ ์์ฑ์ ์ฒด์ด๋ | extends, super(), ๋จ์ผ ์์ |
| 2.4 | ๋คํ์ฑ โ โ โ | VMT, ๋์ ๋ฐ์ธ๋ฉ, OCP์ ํ ๋ |
| 2.5 | instanceof์ ํ๋ณํ | ์์ ํ ๋ค์ด์บ์คํ , ํจํด ๋งค์นญ |
| 2.6 | Nested/Inner/Anonymous | ๋๋ค์ ์ ์ , ๋ฉ๋ชจ๋ฆฌ ๋์ ์ฃผ์ |
"ํด๋์ค๋ฅผ ๋ง๋ค ์ค ์๋ค โ OOP๋ฅผ ์๋ค"
๋ฉ์๋, ์์, ๋คํ์ฑ, ํ๋ณํ, ๋ด๋ถ ํด๋์ค โ ์ด ๋ชจ๋๊ฐ ๊ฐ์ฒด๋ค์ด ํ๋ ฅํ๋ ๋ฐฉ์ ์ ํํํ๋ ๋๊ตฌ. ์ด ๋๊ตฌ๋ค์ ์ ์กฐํฉํด์ผ SOLID ์์น (Phase 3) ์ผ๋ก ์ข์ ์ค๊ณ๊ฐ ๊ฐ๋ฅํ๋ค.
Phase 2์ ๋๊ตฌ๋ค์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํ๋ 5๊ฐ์ง ์์น:
โ Phase 2์ OOP 3๋ ์ถ์ด SOLID๋ก ๊ฝํ๋ค.