2024.05.14(화) 슈퍼코딩 신입연수원 7주차 Day 2 후기 - 유틸리티 클래스, 내부 클래스, 람다식, stream
곰돌이볼2024. 5. 14. 08:21
강의
55강(내부 클래스와 유틸클래스) ~ 57강(stream 사용하여 컬렉션 우아하게 사용하기)
유틸리티 클래스 & 내부 클래스
Java 클래스의 아쉬움 점
클래스를 이용하려면 인스턴스화 해야함 → static 활용
여러 클래스/메서드와 유사한걸 표현하려면 상속을 해야함 → 내부 클래스 이용해서 개념적 영역으로 그룹화
한 번만 사용할 클래스를 새로 정의 → 내부 클래스 문법 이용
유틸리티 클래스
불필요한 인스턴스화를 할 필요가 없는 클래스
클래스의 메서드는 전부 static으로 선언해서 인스턴스 없이 사용 가능
public class MathUtils {
public static final double PI = 3.14;
public static int sum(int a, int b);
return a - b;
}
}
내부 클래스
내부 클래스 종류
일반 중첩 클래스(inner class) : 클래스 내부에 선언된 클래스
정적 중첩 클래스(static nested class) : static이 붙은 클래스 내부에 선언된 클래스
지역 중첩 클래스(method local inner class) : 메서드 내에 선언된 내부클래스
익명 중첩 클래스(anoymous inner class)
class Outer {
class InstanceInner{}{} // 일반 중첩 클래스
static class StaticInner(){} // 정적 중첩 클래스
void method() {
class LocalInner(){} // 지역 중첩 클래스
}
}
선언
// 내부 클래스 정의
class OuterClass {
class InstanceInner{}{} // 일반 중첩 클래스
static class StaticInner(){} // 정적 중첩 클래스
void method() {
class LocalInner(){} // 지역 중첩 클래스
}
}
interface Flyable {
void fly();
}
// 선언
public class Main {
public static void main(String args[]) {
OuterClass outer = new OuterClass();
Outer.InnerClass inner = outer.new InnerClass(); // 내부 중첩 클래스 선언
Outer.StaticInner staticInner = new OuterClass.InnerClass(); // 정적 중첩 클래스 선언
// 지역 내부 클래스 선언
class LocalFlyalbe implements Flyable {
@Override
public void fly() { System.out.println("LocalFlyable.fly()"); }
}
// 지역 내부 클래스 사용
LocalFlyable localFlyable = new LocalFlyable();
localFlyable.fly();
// 익명 내부 클래스 선언
Flyable anonymousFlyable = new Flyable() {
@Override
public void fly() { System.out.println("AnonymousFlyable.fly()"); }
};
// 익명 내부 클래스 사용
anonymousFlyable.fly();
}
}
일반 중첩 클래스(inner class, 인스턴스 클래스)
외부 클래스 인스턴스와 연관되어 있음
외부 클래스의 멤버변수 위치에 선언
인스턴스 멤버처럼 사용
정적 중첩 클래스(static nested class)
외부 클래스 인스턴스와 독립적임
외부 클래스의 멤버변수 위치에 선언
static 멤버처럼 사용
지역 중첩 클래스(method local inner class)
보통 인터페이스를 상속받아서 클래스 내부에 선언
여러 번 인스턴스화 가능
외부 클래스의 메서드나 초기화 블록 안에서 선언
선언된 영역 내부에서만사용 가능
익명 중첩 클래스(anoymous inner class)
보통 인터페이스를 상속받아서 구현 후 인스턴스화
한 번만 인스턴스화 가능
클래스의 선언과 객체의 생성을 동시에 하는 클래스(1회성)
함수형 프로그래밍
함수형 프로그래밍(Function programming)
모든 프로그램을 함수의 연속으로 보는 방식
대표적인 함수형 프로그래밍 언어
클로저(Clojure)
스칼라(Scala)
하스켈(Haskell)
java에서 함수형 프로그래밍 일부 수용 → 람다식 문법
람다식
람다식 문법
// 원래 문법
int sum(int a, int b) {
return a + b;
}
// 람다식 : 메서드 이름과 반환타입 제거 & -> 붙이기
(int a, int b) -> {
return a + b;
}
// 선택사항1 : return문이나 {} 생략 가능
(int a, int b) -> a + b
// 선택사항2 : 매개변수 타입 추론 가능 시 생략 가능
(a, b) -> a + b
람다식 구현
람다 인터페이스에 @FunctionalInterface 어노테이션 사용해서 선언하기
@FunctionalInterface는 1개의 메서드만 선언 가능
람다식 구현 시 익명 내부클래스가 자동으로 구현됨
외부에 선언된 변수 변경 X(참조 가능) → 아래의 이유로 인해서 참조하는 외부 변수 final이어야함
동시성 문제 방지 : 람다식이 병렬로 실행되더라도 외부 변수가 안전하게 유지
스택과 힙의 차이로 인한 문제 방지 : 람다식이 로컬 변수의 값을 안전하게 참조할 수 있도록 함
자바의 불변성 원칙 유지 : 람다식의 동작을 예측 가능, 코드의 이해와 유지 보수 용이
// 람다 인터페이스 정의
@FunctionalInterface
interface CalculateNum {
int add(int str);
}
@FunctionalInterface
interface StringNum {
void printString(String str, int count);
}
// 람다 구현 및 실행
public class Main {
public static void main(String[] args) {
// 매개변수가 1개인 경우
CalculateNum calculateNum = (x) -> x + 1;
calculateNum.add(10);
/*
CalculateNum calculateNum = new CalculateNum() {
@Override
public String add(String str) {
return str + 1;
}
}
*/
// 매개변수가 2개인 경우
StringNum stringNum1 = (str, count) -> System.out.println(str + " " + count);
StringNum stringNum2 = (str, count) -> {
for(int i=0; i<count; i++) System.out.println(i+"번째 : " + str);
};
stringNum1.printString("hello1", 2);
stringNum1.printString("hello2", 5);
}
}
활용
메서드의 매개변수에 람다식 이용
Genric을 람다식의 매개변수에 이용
실전
직접적인 사용 X → 다른 실전 API의 매개변수에 사용(orElseGet, stream 등...)
Stream
stream : 함수형 프로그래밍을 도입하여 collection, array 등의 처리를 간단하고 효율적으로 처리하는 API
count(), max(), min() : 개수, 최고값, 최소값 반환, 숫자 stream만 사용 가능
reduce() : (초기값 설정 및 연산)을 통해서 규칙 설정 가능
stream을 이용한 for-loop 개선
public class Main {
public static void main(String[] args) {
// for-loop
List<String> fruitsList = Arrays.asList("apple", "pear", "banana");
for(String fruit : fruitsList) {
System.out.println(fruit);
}
// stream을 이용한 for-loop 개선
Stream<String> stream = fruitsList.stream();
stream.forEach(System.out::println);
}
}
예시 코드
public class Main {
public static void main(String[] args) {
// stream 값
List<String> fruitsList = Arrays.asList("apple", "pear", "banana");
// 1. forEach() : 출력
fruitsList.stream().forEach(fruit -> System.out.println("for-each : " + fruit));
// 2. collect : List, Set, Map, Collector 등 반환
Set<String> set = fruitsList.stream().collect(Collectors.toSet());
System.out.println("set : " + set);
// 3. findFirst() : stream의 첫번째 값 산출(return값 : Optional<T>)
Optional<String> optional = fruitsList.stream().findFirst();
System.out.println("findFirst : " + optional.orElseGet(() -> "not found"));
// 숫자 stream 값
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 4. sum, average
System.out.println("sum : " + numbers.stream().mapToInt(i -> i).sum());
System.out.println("average : " + numbers.stream().mapToInt(i -> i).average().getAsDouble());
// 5. max, min, count
System.out.println("max : " + numbers.stream().mapToInt(i -> i).max().getAsInt());
System.out.println("min : " + numbers.stream().mapToInt(i -> i).min().getAsInt());
System.out.println("count : " + numbers.stream().count());
// 6. reduce() 로 소모값 구하기
// reduce로 규칙 설정 가능
int result = numbers.stream().reduce(0, (a, b) -> a + b);
System.out.println("reduce : " + result);
}
}
for-each : apple
for-each : pear
for-each : banana
set : [banana, apple, pear]
findFirst : apple
sum : 55
average : 5.5
max : 10
min : 1
count : 10
reduce : 55