A. 중첩 클래스/인터페이스 용어정리
A-1. 용어 정리
- 🍯Tip : 들어가기 전에.. (변수 선언)
- 필드 : 클래스의 멤버로 선언하는 변수. 접근 수식어(public, private, …)를 사용할 수 있음
- 인스턴스 필드 : static 이 아닌 필드
- 객체를 생성한 후에, 그 참조 변수로 접근해서 사용하는 필드
- 객체 생성자 생성 시, 메모리에 생성됨
- 클래스 필드 : static으로 선언된 필드
- 프로그램이 시작될 때, 클래스 로더에 의해서 메모리에 미리 로딩되는 변수
- 객체를 생성하지 않고, 클래스 이름을 접두사로 사용함.
- 인스턴스 필드 : static 이 아닌 필드
- 지역 변수 : 메서드 안에서 선언하는 변수. 접근 수식어를 사용할 수 없음.
- 선언된 위치에서부터 변수가 포함된 블록이 끝날 때까지 사용 가능
- 파라미터도 지역 변수의 한 종류
- 지역 변수는 static을 사용할 수 없음 </aside>
- 필드 : 클래스의 멤버로 선언하는 변수. 접근 수식어(public, private, …)를 사용할 수 있음
- <aside> 💡 🚨🚨🚨변수 선언🚨🚨🚨 (클래스와 비슷)
외부 클래스/인터페이스
outer, enclosing class/interface
- 내부 클래스/인터페이스를 감싸고 있는 클래스/인터페이스
내부 클래스/인터페이스
inner class/interface
- 다른 클래스/인터페이스 안에서 선언된 클래스/인터페이스
- 멤버 내부 클래스
- 인스턴스 내부 클래스 : static이 아닌 내부 클래스
- 외부 클래스의 객체를 우선 생성한 후에, 그 참조 변수를 사용해서 객체를 생성할 수 있는 내부 클래스.
- 외부인스턴스.생성자 호출()
- static 내부 클래스 : static으로 선언된 내부 클래스
- 외부 클래스의 객체 생성 여부와 상관 없이 사용할 수 있는 클래스
- 중첩 클래스(nested class) 교육자에 따라 중첩 클래스가 전체를 포괄하는 경우가 있고, static 내부 클래스만 중첩 클래스로 분류하는 경우가 있다.
- 인스턴스 내부 클래스 : static이 아닌 내부 클래스
- 지역 (내부) 클래스 (local class)
- 메서드 안에서 선언된 내부 클래스
- 선언된 블록 안에서만 객체를 생성하고 사용할 수 있는 클래스
- 익명 (내부) 클래스(anonymous class)
- 보통 지역 클래스인 경우가 많음.
- 클래스 선언과 동시에 객체 생성까지 해야만 하는 클래스
- → 람다 표현식.
- 자바에서 가장 많이 사용하고 있는 내부 클래스로 잘 알아놓는 것이 좋음.
A-2. 인스턴스 내부 클래스
내부 클래스 내에서 외부 클래스 멤버에 접근하기
package com.itwill.inner01;
public class Outer {
// field
private int x;
private int y;
private String s;
// constructor
public Outer(int x, int y, String s) {
this.x = x;
this.y = y;
this.s = s;
}
public int getX() {
return x;
}
// method
@Override
public String toString() {
return String.format("Outer(x=%d, y=%d, s=%s)", this.x, this.y, this.s);
}
// static이 아닌 내부 클래스 선언
public class Inner {
// field
private int y;
// constructor
public Inner(int y) {
this.y = y;
}
// method
public void info() {
System.out.println("--- Inner Class ---");
System.out.println("y = " + y);
System.out.println("Outer x = " + x); // (1)
System.out.println("Outer y = " + Outer.this.y); // (2)
System.out.println("Outer s = " + s);
System.out.println(Outer.this.toString()); // 외부 클래스의 메서드를 사용할 수 있다.
}
}
}
- 외부 클래스와 내부 클래스의 필드명(y)이 겹친다.
- 내부 클래스는 외부 클래스의 멤버를 접근할 수 있음
- 멤버의 이름이 겹치는 경우, 외부 클래스의 필드에 접근하기 위해 Outer.this. 접두사를 붙인다.
- 2와 동일하게 이름이 겹치는 메서드를 호출할 시에도, Outer.this. 접두사를 이용하여 외부 메서드를 접근할 수 있다.
메인에서 객체 생성
public class InnerMain01 {
public static void main(String[] args) {
// Outer 클래스 타입 객체 생성
Outer outer1 = new Outer(1, 2, "java");
System.out.println(outer1);
// Outer.Inner 클래스 타입 객체 생성
Outer.Inner inner1 = outer1.new Inner(100); // (1)
inner1.info();
Outer.Inner inner2 = outer1.new Inner(123);
inner2.info();
System.out.println(inner1.getX()); // (2)
}
}
- 내부 클래스의 인스턴스와 그를 생성하기 위해 사용된 outer1 의 변수의 객체는 어떠한 관계도 없다. 그렇기 때문에 클래스 밖에서 내부 클래스의 인스턴스로 outer1의 변수나 메서드에 접근할 수 없다.
- 다만 클래스 멤버 내에서는 외부 클래스의 변수나 메서드에 접근할 수 있으므로 해당 기능이 필요한 경우에는 내부 클래스의 메서드 등으로 이를 활용하면 된다.
- 내부 클래스 생성자 호출 문법
- 상속과 내부 클래스의 차이
- 클래스 외부에서 외부 클래스의 메서드나 필드에 접근할 수 없다.
- inner 인스턴스의 변수 Type을 Outer.Inner 처럼 길게 사용하지 않고, import를 사용하여 더 짧게 사용할 수 있다.
package com.itwill.inner01;
import com.itwill.inner01.Outer.Inner;
public class InnerMain01 {
Inner inner4 = outer1.new Inner(3);
}
}
A-3. static 내부 클래스 (a.k.a 중첩 클래스)
package com.itwill.inner01;
public class Enclosing {
public static int var = 1; // static field
private int x; // instance field
// constructor
public Enclosing(int x) {
this.x = x;
}
// static method
public static void test() {
System.out.println("var = " + var);
// System.out.println("x = " + x); // -> (1)
}
// instance method
@Override
public String toString() {
return String.format("Enclosing(var = %d, x = %d)", var, x); // (2)
}
public static class Nested {
private int x;
public Nested(int x) {
this.x = x;
}
public void info() {
System.out.println("---- Nested Class ----");
System.out.println("x = " + x);
// System.out.println(Enclosing.this.x; // (3)
}
}
} // class Enclosing
- 컴파일 에러
- static 멤버는 다른 static 멤버들만 사용가능, non-static 멤버는 사용 불가
- static이 아닌 인스턴스 메서드는 static과 non-static 멤버들을 모두 사용할 수 있음.
- 컴파일 에서
- static 내부 클래스에서는 외부 클래스의 non-static 멤버를 사용할 수 없음!
중첩 클래스 생성하기
Enclosing.Nested nested = new Enclosing.Nested(1);
- Enclosing Class와 Nested Class는 문법 상으로 관련이 없는 두 개의 클래스를 겹쳐놓은 것이라고 생각하면 된다.
- import하여 타입 더 짧게 작성하여 중첩 클래스 인스턴스 사용하기
import com.itwill.inner01.Enclosing.Nested;
------------------------------------------------------------------------
public static void main (String[] args) {
Enclosing.Nested nested = new Enclosing.Nested(1);
Nested nested2 = new Nested(0);
}
빌더(Builder, Factory) Design 패턴
**package com.itwill.inner02;
public class Book {
// field
private String title; // 책 제목
private String author; // 책 저자
private String publisher; // 출판사
public Book() {
}
public Book(String title) {
// this.title = title;
this(title, null, null);
}
public Book(String title, String author) {
this(title, author, null);
}
public Book(String title, String author, String publisher) {
this.title = title;
this.author = author;
this.publisher = publisher;
}
}**
- 생성자 오버로딩
- 파라미터 (개수 타입)이 다른 생성자들을 여러 개 작성
- Book(String title, String author) 생성자가 있는 경우. Book(String titls, String publisher) 생성자는 오버로딩할 수 없음.
- 동일 타입 필드가 여러 개 있을 경우, 아규먼트 수가 똑같다면 만들 수 있는 생성자의 종류는 제한 적임
- 또한 개발자가 개발 시 타입으로만 구분하기에 혼동할 수 있는 여지가 충분
public class Book {
// field
private String title; // 책 제목
private String author; // 책 저자
private String publisher; // 출판사
// ------------ Builder(Factory) 디자인 패턴 ---------------------- //
public static BookBuilder builder() {
return new BookBuilder();
}
public static class BookBuilder { // (1)
private String title;
private String author;
private String publisher;
private BookBuilder() {
}
public BookBuilder title(String title) {
this.title = title;
return this;
}
public BookBuilder author(String author) {
this.author = author;
return this;
}
public BookBuilder publisher(String publisher) {
this.publisher = publisher;
return this;
} // (2)
public Book build() {
return new Book(title, author, publisher);
}
} // class BookBuilder
- Book을 만들기 위해 BookBuilder를 사용할 수 있어야 한다.
- BookBuilder는 Book을 생성하기 전에 접근할 수 있어야 한다.
- 그렇기 때문에 BookBuilder 클래스와 Book 내부에 선언된 BookBuiler의 인스턴스를 반환하는 메서드는 static 메서드여야 한다.
- 연쇄적으로 메서드를 사용할 수 있게 하기 위해서 return this; 를 하여 실제 BookBuilder를 사용하여 Book의 인스턴스를 만들기 위해서든 다음과 같이 사용된다.
package com.itwill.inner02;
public class InnerMain02 {
public static void main(String[] args) {
Book book1 = new Book("일론 머스크", "월터 아이작슨", "20세기북스");
System.out.println(book1);
Book book2 = new Book("일론 머스크", "월터 아이작슨");
System.out.println(book2);
Book book3 = Book.builder()
.author("윌터아이작슨").title("일론머스크").publisher("20세기북스")
.build();
System.out.println(book3);
Book book4 = Book.builder().author("윌터아이작슨").build();
System.out.println(book4);
}
}
- 생성자 오버로딩이 불가능하여 사용하지 못했던 author 정보만 가지고 있는 Book의 인스턴스를 Builder Class를 사용하여 만들 수 있다.
- 또한 순서에 상관없이 메서드를 이용하여 생성 필드들을 필요한 순서대로 입력할 수 있다.
A-4. 지역 클래스
- 내부 인터페이스는 항상 public static 임
package com.itwill.inner03;
public class Button {
// public static 내부 인터페이스 - static은 생략 가능
interface OnClickListener {
void onClick(); // public abstract 메서드
}
// field
private String btnName;
private OnClickListener listener;
// constructor
public Button(String btnName) {
this.btnName = btnName;
}
// setter
public void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
// method (기능)
public void click() {
System.out.println(btnName + "버튼 : ");
listener.onClick(); // 버튼을 클릭
}
}
package com.itwill.inner03;
import com.itwill.inner03.Button.OnClickListener;
public class OpenButtonListener implements OnClickListener {
@Override
public void onClick() {
System.out.println("파일 열기 실행");
}
}
package com.itwill.inner03;
public class InnerMain03 {
public static void main(String[] args) {
// field
Button btnOpen = new Button("열기");
// 버튼이 할일을 가지고 있는 객체를 생성
// 리스너 객체를 생성
OpenButtonListener listener = new OpenButtonListener();
// 리스너 객체를 버튼에 등록
btnOpen.setOnClickListener(listener);
btnOpen.click(); // 버튼을 클릭
}
}
- 위 코드는 중복해서 사용하지 않고, 단 세 줄만 추가하기 위해 별도의 클래스를 구성하여 효율적이지 않음
- 한 번만 사용할 이 OpenButtonListener 타입의 클래스를 따로 구성하지 않고, 다음과 같이 Main 메서드 내에서 지역 클래스를 구현하여 해당 지역 내에서만 사용할 클래스를 생성할 수 있음.
- 메서드 내에 선언된 클래스의 경우, 지역 클래스가 생성된 코드 위에 생성자를 부를 수 없다.
- 메서드는 위에서 아래로 시작된다..
- 메서드 내에 선언된 클래스의 경우, 지역 클래스가 생성된 코드 위에 생성자를 부를 수 없다.
- package com.itwill.inner03; import com.itwill.inner03.Button.OnClickListener; public class InnerMain03 { // 1. Button 타입 객체 생성 Button btnClose = new Button("닫기"); // 2. OnClickListener를 구현하는 클래스 선언 -> 지역 클래스 class CloseButtonListener implements OnClickListener { @Override public void onClick() { System.out.println("파일 닫기를 실행합니다.."); } } // 3. 리스너 객체 생성 OnClickListener closeListener = new CloseButtonListener(); // 4. 버튼에 리스너를 설정 btnClose.setOnClickListener(closeListener); btnClose.click(); }
- 클래스에 이름을 붙이는 이유는 생성자 호출이나 타입 지정을 위한 것인데, 하나의 객체를 생성할 목적으로 생성하는 클래스는 따로 이름을 붙일 필요가 없지 않을까라는 고민에서 익명 클래스가 나왔다.
A-5. 익명 클래스
package com.itwill.inner03;
import com.itwill.inner03.Button.OnClickListener;
public class InnerMain03 {
public static void main(String[] args) {
System.out.println("--- 익명 클래스 ---");
// 1. 버튼 타입 객체 생성
Button saveButton = new Button("저장");
// 2. 리스너 객체 생성
**OnClickListener saveListener = new OnClickListener() {
@Override
public void onClick() {
System.out.println("파일을 저장합니다..");
}
};**
saveButton.setOnClickListener(saveListener);
saveButton.click();
}
}
- 클래스 선언과 동시에 클래스 생성을 할 수 있는 익명 클래스
A-6. Lambda와 익명 클래스 구현
중첩 인터페이스는 static이 없어도 항상 static
함수형 인터페이스
- 추상 메서드를 항상 1개만 갖는 인터페이스
- @FunctionalInterface라는 annotation을 붙일 수 있음 :
- 선택사항이지만, 컴파일러가 확인해줄 수 있으므로 쓰는게 좋지 않을까?!
<aside> 💡 🚨🚨함수형 인터페이스 구현 방법 4가지🚨🚨
package com.itwill.lambda01;
public class Calculator {
@FunctionalInterface
public interface Calculable {
double calculate(double x, double y);
}
private double x;
private double y;
public Calculator(double x, double y) {
this.x = x;
this.y = y;
}
public double calculate(Calculable calc) {
return calc.calculate(x, y);
}
}
- Calculator 클래스 내의 calculate 메서드를 사용하기 위해서는 함수형 인터페이스 Calculable을 구현한 클래스의 객체를 인자로 넘겨야 한다.
- 함수형 인터페이스를 구현하여 객체를 넘기는 4가지 방법에 대해서 알아보자
- 람다 표현식 예시는 아래에서 확인하세요~
Calculator calc = new Calculator(2, 1);
Calculable adder = new Adder();
double result = calc.calculate(adder);
System.out.println("result = " + result);
Calculator calc = new Calculator(2, 1);
class Subtractor implements Calculable {
@Override
public double calculate(double x, double y) {
return x - y;
}
}
// Subtractor 타입 객체를 calculate 메서드 아규먼트로 전달
result = calc.calculate(new Subtractor());
Calculator calc = new Calculator(2, 1);
result = calc.calculate(new Calculable() {
@Override
public double calculate(double x, double y) {
return x * y;
}
});
</aside>
람다 표현식(Lambda expression) - 함수형 표기법
- 클래스 선언과 객체 생성을 동시에 하는 익명 클래스를 간단히 작성하기 위한 문법
- 함수형 인터페이스 타입 객체만 람다 표현식으로 작성할 수 있음.
문법
(파라미터 선언) -> { 메서드 코드; } // (1)
(double x, double y) -> { return x / y; }
(파라미터 선언) -> 메서드 코드
- 기본 문법
- 생략 표현 (조건 : 실행 문장이 하나만 있는 경우)
- 람다 표현식의 파라미터 선언에서는 변수 타입을 생략할 수 있음 (명시해줘도 됨)
- 람다 표현식에서 메서드 본체의 문장이 하나만 있는 경우에는 {}와 ;를 생략할 수 있음.
- 람다표현식이 return 문장만 있는 경우에는 {}, return, ; 을 모두 생략하고 리턴 값만 표기
- EX) (x, y) → x + y
- 람다 표현식에서 파라미터가 1개만 있는 경우에는 ()도 생략 가능
- EX) x → 2 * x + 3
- FunctionalInterface를 구현할 때만 사용할 수 있음
- 구현해야 할 메서드가 1개만 있는 인터페이스
- 호출할 수 있는 메서드가 1개밖에 없기 때문에 메서드 이름은 중요하지 않음
Calculator calc = new Calculator(2, 1);
result = calc.calculate((x, y) -> x / y);
System.out.println("result = " + result);
<aside> 💡 람다에
</aside>
Filter 함수 구현
package com.itwill.lambda02;
@FunctionalInterface
public interface MyFilter {
boolean test(Object x);
}
package com.itwill.lambda02;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LambdaMain02 {
public List**<? extends Object>** filter(List<Object> list, MyFilter filter) { // (1)
List<Object> result = new ArrayList<Object>();
for (Object x : list) {
if (filter.test(x))
result.add(x); // ㅋㅋ result를 넣고 있었음;;;
}
return result;
}
public static void main(String[] args) {
// LambdaMain02 타입 객체 생성
LambdaMain02 app = new LambdaMain02();
List<Object> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
System.out.println(numbers);
List<? extends Object> evens = app.filter(numbers, new MyFilter() {
@Override
public boolean test(Object x) {
return ((Integer) x) % 2 == 0;
}
});
System.out.println(evens);
System.out.println(evens.get(0) instanceof Integer);
// (2)
List<? extends Object> odds = app.filter(numbers, x -> (Integer) x % 2 != 0);
System.out.println(odds);
List<Object> languages = Arrays.asList("Java", "SQL", "JavaScript", "HTML", "Servlet");
System.out.println(languages);
// languages의 원소들 중 5글자 이상인 문자열들로 이루어진 리스트를 만들고 출력
List<Object> result = app.filter(languages, (x) -> x instanceof String ? ((String) x).length() >= 5 : false);
System.out.println(result);
}
}
- 메인 함수 내에 구현된 filter 함수는 Filter 기준을 반환해주는 MyFilter 타입의 아규먼트가 필요함.
- Bold로 작성된 <? extends Object> 를 사용하면 Object를 상속받은 하위 타입 인스턴스면 어느 것이든지 반환값으로 올 수 있음.
- <?> 는 어떤 타입의 객체든 반환값으로 올 수 있음
- 하지만 List과 같은 Collection Framework 내의 원소타입으로만 해당 문법을 사용할 수 있고(Maybe Generic), int main () {} 과 같이 ? main () {} 으로는 사용할 수 없다.
- numbers의 원소들 중에서 홀수들로만 이루어진 리스트를 만들고 출력
B. Stream
- 통로의 역할
메서드 참조
List<String> languages = Arrays.asList("Java", "JavaScript", "Python", "Kotlin", "C");
System.out.println(languages);
List<String> results
= languages.stream().filter(x -> x.length() >= 5)**.map(x -> x.toLowerCase())**.toList();
= languages.stream().filter(x -> x.length() >= 5)**.map(String::toLowerCase)**.toList();
System.out.println(results);
- 볼드의 내용은 String::toLowerCase 라고도 사용하고, 메서드 참조라고 한다.
- 람다 표현식이 아규먼트를 1개만 갖고, 람다의 리턴값이 그 아규먼트에서 파라미터가 없는 메서드 호출 결과인 경우 메서드 참조 방식으로 람다 표현식을 작성할 수 있다.
- 람다의 아규먼트에서 메서드를 호출한 값이 람다의 리턴값인 경우
- x -> x.toLowerCase() 와 String::toLowerCase은 같은 람다 표현식.
- x → x.length() 와 Strign::length
- 람다 표현식이 아규먼트를 1개만 전달 받고, 람다의 구현부가 메서드 1개 호출하여 그 메서드가 람다의 아규먼트를 전달받는 경우
- 메서드 참조 방식을 사용할 수 있음
- (x) → System.out.println(x)
- System.out::println
- ex)
- languages.forEach(x -> System.out.println(x)); languages.forEach(System.out::println);
'programming > Java [notion 정리본 업로드]' 카테고리의 다른 글
Oracle (1) | 2024.04.15 |
---|---|
입출력 스트림 (0) | 2024.04.15 |
JSP/SERVLET 프로젝트 생성 시 Dependencies (0) | 2024.04.13 |
Collection (0) | 2024.04.13 |
예외처리 (Exception) (0) | 2024.04.13 |