내부 클래스(inner class)는 말 그대로 '클래스 내부에 선언한 클래스'임
내부에 클래스를 선언하는 이유는 대개 이 클래스와 외부 클래스가 밀접한 관련이 있기 때문임
다른 클래스와 협력할 일이 없는 경우에 내부 클래스로 선언해서 사용함
내부 클래스는 선언하는 위치나 예약어에 따라 크게 네 가지 유형으로 나눌 수 있음
1) 인스턴스 내부 클래스
2) 정적(static) 내부 클래스
3) 지역(local) 내부 클래스
위 세 유형은 클래스 내부에 선언하는 변수의 유형(인스턴스 변수, 정적 변수, 지역 변수)과 유사함
4) 익명(anonymous) 내부 클래스 : 클래스 이름 없이 선언하고 바로 생성하여 사용할 수 있음
내부 클래스를 보면 가장 바깥에 선언한 ABC 클래스를 외부 클래스, ABC 내부에 선언한 클래스는 내부 클래스 또는 중첩된 클래스라고도 함
내부 클래스는 멤버 변수처럼 클래스 내부에 정의하는 인스턴스 내부 클래스, static 키워드를 사용하는 정적 내부 클래스, 그리고 메서드 내부에 정의하는 지역 내부 클래스로 나눌 수 있음
내부 클래스는 유형에 따라 만드는 방법이 다를뿐더러 클래스 내부에 선언할 수 있는 변수 유형과 사용할 수 있는 외부 클래스 변수 유형도 다름
외부 클래스 안에 private 예약어로 변수 num과 sNum을 선언함
두 변수는 private로 선언했지만 외부 클래스 안에 있기 때문에 내부 클래스에서도 사용할 수 있음
인스턴스 내부 클래스는 외부 클래스를 생성한 이후에 사용해야 하기 때문에 정적 변수 부분에서는 오류가 남
클래스의 생성과 상관없이 사용할 수 있는 정적 변수는 인스턴스 내부 클래스에서 선언할 수 없음
정적 메서드에서는 인스턴스 변수를 사용할 수 없음
정적 내부 클래스에서도 외부 클래스의 인스턴스 변수는 사용할 수 없음
예제와 표에서 알 수 있듯이 정적 내부 클레스에서 사용하는 메서드가 정적 메서드인 경우에는 외부 클래스와 정적 내부 클래스에 선언된 변수 중 정적 변수만 사용할 수 있음
람다식을 구현하는 방법은 지금까지 배운 프로그래밍 방식과 조금 다름
람다식은 간단히 설명하면 함수 이름이 없는 익명 함수를 만드는 것임
메서드에서 사용하는 매개변수가 있고, 이 메서드가 매개변수를 사용하여 실행할 구현 내용, 즉 메서드의 구현부를 { } 내부에 작성함
메서드 이름 add와 반환형 int를 없애고 -> 기호를 사용하여 구현함
람다식의 의미를 살펴보면 두 입력 매개변수 (x, y)를 사용하여 {return x + y;} 문장을 실행해 반환하라는 의미임
두 매개변수 s, v를 사용해 연결된 문자열이 출력되도록 함
위 구현부분을 StringConcat 인터페이스 자료형인 concat2 변수에 대입하고, 이 변수를 사용하여 makeString( ) 메서드를 호출함
두 구현 방법을 비교해 보면, 람다식으로 구현하는 경우에 코드가 더 간결해지는 것을 알 수 있음
람다식으로 구현하려면 메서드를 하나만 포함하는 함수형 인터페이스만 가능하다는 점 강조함
- i는 main( ) 함수의 지역 변수임
- 람다식 내부에서 변수 i값을 변경하면 오류가 발생하지만, 변수 값을 변경하지 않고 출력만하면 오류가 발생하지 않음
- 지역 변수는 메서드 호출이 끝나면 메모리에서 사라지기 때문에 익명 내부 클래스에서 사용하는 경우에는 지역 변수가 상수로 변함
- 람다식 역시 익명 내부 클래스가 생성되므로 외부 메서드의 지역 변수를 사용하면 변수는 final 변수, 즉 상수가 됨
따라서 이 변수를 변경하면 상수를 변경하는 것이기에 오류가 발생하는 것임
람다식을 변수에 대입하면 이를 매개변수로 전달할 수 있음
전달되는 매개변수의 자료형은 인터페이스형임
TestLambda 클래스에 정적 메서드 showMyString( )을 메서드를 호출할 떄 구현된 람다식을 대입한 labmdaStr 변수를 매개변수로 전달함을 하나 추가함
showMyString( )을 메서드를 호출할 때 구현된 람다식을 대입한 labmdaStr 변수를 매개변수로 전달함
매개변수의 자료형은 인터페이스형인 PrintString이고 변수는 p임
p.showString("hello lamda_2");라고 호출하면 람다식의 구현부인 출력문이 호출됨
아래와 같이 메서드의 반환형을 람다식의 인터페이스형으로 선언하면 구현한 람다식을 반환할 수 있음
위 람다식은 매개변수로 전달된 문자열에 "world"를 더하여 반환하도록 구현함
반환형은 인터페이스형인 PrintString임
람다식은 함수의 구현부를 변수에 대입하고, 매개변수로 전달하고, 함수의 반환 값으로 사용할 수 있음
- 마치 변수처럼 사용할 수 있는 것
- 함수형 프로그래밍의 특징 중 하나
map( )은 클래스가 가진 자료 중 이름만 출력하는 경우에 사용함
예로 고객 클래스가 있다면 고객 이름만 가져와서 출력할 수 있음
mpa( )은 요소들을 순회하여 다른 형식으로 변환하기도 함
filter( )와 map( ) 둘 다 함수를 수행하면서 해당 조건이나 함수에 맞는 결과를 추출해 내는 중간 역할을 함
최종 연산으로 중간 연산 결과를 출력함
Collection 인터페이스의 스트림 메서드
Collection 인터페이스를 구현한 클래스 중 가장 많이 사용하는 ArrayList에 스트림을 생성하고 활용해 보겠음
아래와 같이 문자열을 요소로 가지는 ArrayList가 있음
Collection에서 stream( ) 메서드를 사용하면 위 클래스는 제네릭형을 사용해 아래와 같이 자료형을 명시할 수 있음
이렇게 생성된 스트림은 내부적으로 ArrayList의 모든 요소를 가지고 있음
모든 요소를 하나씩 가져와서 처리할 때 스트림의 forEach 메서드를 활용함
forEach( ) 메서드는 내부적으로 반복문이 수행됨
forEach( ) 괄호 안에 구현되는 람다식의 의미는 forEach( ) 메서드가 수행되면 요소가 하나씩 차례로 변수 s에 대입되고 이를 매개변수로 받아 출력문이 호출됨
ArrayList에 저장된 이름을 정렬하여 결과를 호출해보자
앞에서 stream 변수에 스트림을 생성했지만 forEach( ) 메서드가 수행되면서 자료가 소모됨
- 따라서 스트림을 새로 생성해야 함
중간 연산으로 정렬을 위한 sorted( ) 메서드를 호출하고, 최종 연산으로 출력을 위해 forEach( ) 메서드를 사용함
sorted( ) 메서드를 사용하려면 정렬 방식에 대한 정의가 필요함
- 따라서 사용하는 자료 클래스가 Comparable 인터페이스를 구현해야 함
- 만약 구현되어 있지 않다면 sorted( ) 메서드의 매개 변수로 Comparator 인터페이를 구현한 클래스를 지정할 수 있음
ArrayList 이외에 다른 Collection의 자료도 같은 방식으로 정렬하고 출력할 수 있음
첫 번째 매개변수 T identify는 초깃값을 의미함
두 번째 매개변수 BinaryOperator&tT> accumulator는 수행해야 할 기능임
BinaryOperator 인터페이스는 두 매개변수로 람다식을 구현하며 이 람다식이 각 요소가 수행해야 할 기능이 됨
- 이때 BinaryOperator 인터페이스를 구현한 람다식을 직접 써도 되고, 람다식이 길면 인터페이스를 구현한 클래스를 생성하여 대입해도 됨
apply( ) 메서드는 두 개의 매개변수와 한 개의 반환 값을 가지는데, 세 개 모두 같은 자료형임
reduce( ) 메서드가 호출될 때 BinaryOperator의 apply( ) 메서드가 호출됨
reduce( ) 메서드를 사용해 모든 요소의 합을 구할 때, 두 번째 매개변수에 람다식을 직접 쓰는 경우는 아래와 같음
초깃값은 0이고 스트림 요소가 매개변수로 전달되면서 합을 구함
내부적으로는 반복문이 호출되면서 람다식에 해당하는 부분이 리스트 요소만큼 호출되는 것임
- 따라서 reduce( ) 메서드에 어떤 람다식이 전달되느냐에 따라 다양한 연산을 수행할 수 있음
reduce( ) 는 처음부터 마지막까지 모든 요소를 소모하면서 람다식을 반복해서 수행하므로 최종 연산임
고객 명단을 출력하는 코드를 살펴보면 19행에서는 map( ) 메서드를 사용하여 고객의 이름을 가져오고 forEach( ) 메서드로 이름을 출력하고 있음
21행에서는 각 고객이 지불한 비용을 가져와서 mapToInt( ) 메서드로 그 값을 정수로 변환한 후 sum( )으로 합을 구함
최종 연산 sum( )의 반환 값이 int형이므로 int형 변수 total 변수에 결과를 대입함
25행의 20세 이상 고객을 가져와서 이름을 정렬하는 부분은 3개의 중간 연산을 사용함
- filter( )를 사용하여 20세 이상만 추출한 후 map( )으로 이들의 이름을 가져오고, sorted( )를 사용하여 이름을 정렬함
- 윗 부분까지가 중간 연산이고 최종 연산 forEach( )를 활용하여 출력함
데이터베이스의 쿼리문이 스트림과 비슷한 느낌을 줌