강의 자료
https://gitlab.com/easyspubjava/javacoursework/-/tree/master/Chapter6
여러 내부클래스(inner class)의 정의와 유형
내부 클래스(inner class)
- 클래스 내부에 선언한 클래스
- 외부 클래스와 밀접한 연관이 있는 경우가 많다.
- 다른 외부 클래스에서 사용할 일이 거의 없는 경우에 내부 클래스로 선언해서 사용한다.
- 중첩 클래스라고도 한다.
- 내부 클래스 종류
- 인스턴스 내부 클래스
- 정적(static) 내부 클래스
- 지역(local) 내부 클래스
- 익명(anonymous) 내부 클래스
인스턴스 내부 클래스
- 내부적으로 사용할 클래스를 선언 (private 권장)
- 외부 클래스가 생성된 후 생성됨 (정적 내부 클래스와 다름)
- private이 아닌 내부 클래스는 다른 외부 클래스에서 생성할 수 있음
package Chapter6.ch01;
class OutClass{
private int num=10;
private static int sNum=20;
private InClass inClass;
public OutClass(){
inClass = new InClass(); //외부 클래스 생성시 내부 클래스도 생성됨
}
class InClass{ //내부 클래스
int iNum = 100;
void inTest(){
System.out.println("num = " + num); //외부 클래스의 인스턴스 변수
System.out.println("iNum = " + iNum); //내부 클래스의 인스턴스 변수
System.out.println("sNum = " + sNum); //외부 클래스의 스태틱 변수
// static int stNum = 20; static 변수는 생성X : 내부 클래스는 외부 클래스 만들어진 후 만들어지므로
}
// static void sTest(){} 에러남
}
public void usingClass(){ //내부 클래스 사용하기
inClass.inTest();
}
}
public class InnerTest {
public static void main(String[] args) {
OutClass outClass = new OutClass();
outClass.usingClass();
System.out.println();
OutClass.InClass inner = outClass.new InClass(); //외부에서 내부 클래스 객체 생성
inner.inTest();
}
}
내부 클래스에서는 static 변수와 메서드를 선언할 수 없다.
(외부 클래스가 만들어진 후 내부 클래스가 만들어지므로 static처럼 이미 컴파일 시간때부터 만들어지는 특성과 맞지 않다.)
외부 클래스 생성시 내부 클래스도 생성되기 위해 외부 클래스의 생성자에 내부 클래스 생성자 포함시키기!
정적 내부 클래스
- 외부 클래스 생성과 무관하게 사용할 수 있다.
- 정적 변수, 정적 메서드 사용
package Chapter6.ch01;
class OutClass{
private int num=10;
private static int sNum=20;
private InClass inClass;
public OutClass(){
inClass = new InClass(); //외부 클래스 생성시 내부 클래스도 생성됨
}
class InClass{ //내부 클래스
int iNum = 100;
void inTest(){
// static int stNum = 20; static 변수는 생성X : 내부 클래스는 외부 클래스 만들어진 후 만들어지므로
System.out.println("num = " + num); //외부 클래스의 인스턴스 변수
System.out.println("iNum = " + iNum); //내부 클래스의 인스턴스 변수
System.out.println("sNum = " + sNum); //외부 클래스의 스태틱 변수
}
// static void sTest(){} 에러남
}
public void usingClass(){ //내부 클래스 사용하기
inClass.inTest();
}
// 여기서부터
static class InStaticClass{
int inNum=100;
static int sInNum = 200;
void inTest(){ //static 클래스의 일반 메서드(static 클래스 생성 후에 나중에 호출)
// num+=10; 외부 클래스의 인스턴스 사용 불가, static 클래스는 외부 클래스 생성하지 않아도 사용될 수 있으므로
// System.out.println("iNum = " + iNum); //InClass의 인스턴스 변수 사용X
System.out.println("inNum = " + inNum); //staticClass 자신의 인스턴스 변수는 가능
System.out.println("sInNum = " + sInNum); //자신의 인스턴스 변수는 가능
System.out.println("sNum = " + sNum); //외부 클래스의 스태틱 변수는 가능-이미 만들어져있음
}
static void sTest(){ //static 메서드
// num +=10; 외부 클래스의 인스턴스 변수는 X
// inNum += 10; static 클래스와 상관없이 사용가능, 인스턴스 변수는X
System.out.println("sNum = " + sNum); //외부 클래스의 static 변수는 가능
System.out.println("sInNum = " + sInNum); //내부 클래스의 static 변수는 가능
}
}
}
public class InnerTest {
public static void main(String[] args) {
//외부 클래스 생성하지 않고 바로 정적 내부 클래스 생성
OutClass.InStaticClass sInClass = new OutClass.InStaticClass();
System.out.println("정적 내부 클래스 일반 메서드 호출");
sInClass.inTest(); //static 클래스 생성 후 일반 메서드 호출
System.out.println();
//static 메서드는 바로 호출 가능
OutClass.InStaticClass.sTest();
}
}
- static 클래스의 일반 메서드
- 사용하려면 static 클래스가 생성된 후 호출될 수 있다.
- 외부 클래스의 인스턴스 변수는 호출할 수 없다 - static 클래스이므로 외부 클래스의 인스턴스 변수가 정의되기 전에 호출가능
- 외부 클래스의 static 변수는 호출할 수 있다. -static 변수이므로 이미 생성되어 접근 가능
- 내부 클래스의 인스턴스 변수는 호출될 수 있다. - static 클래스이므로 이미 인스턴스 변수도 생성되었다.
- static 클래스의 static 메서드
- static 클래스의 생성과 상관없이 사용이 가능하다.
- 외부 클래스의 인스턴스 변수는 호출할 수 없다. - 인스턴스 변수는 생성되지 않았을 수 있음
- 내부 클래스의 인스턴스 변수는 호출할 수 없다. - 내부 클래스의 생성과 상관없이 메서드가 호출될 수 있다.
- 외부 클래스의 static 변수는 호출될 수 있다.
- 내부 클래스의 static 변수는 호출될 수 있다.
- static 클래스의 생성과 상관없이 사용이 가능하다.
지역 내부 클래스
- 지역 변수와 같이 메서드 내부에서 정의하여 사용하는 클래스
- 메서드의 호출이 끝나면 메서드에 사용된 지역변수의 유효성이 사라짐
- 메서드 호출 이후에도 사용해야하는 경우가 있을수있으므로 지역 내부 클래스에서 사용하는 메서드의 지역변수나 매개변수는 final로 선언됨
package Chapter6.ch01;
class Outer{
int outNum=100;
static int sNum=200;
Runnable getRunnable(int i) { //메서드-호출 후에 사라짐
int num = 100; //final로 생성됨
class MyRunnable implements Runnable { //내부 클래스
int localNum=10; //final 아님 (변경가능)
@Override
public void run() { //getRunnable 존재와 상관없이 호출 가능
// num=200; 에러 발생, 상수는 못바꿈
// i = 100; 에러 발생, 상수는 못바꿈
System.out.println("i = " + i); //접근만 가능
System.out.println("num = " + num);
System.out.println("localNum = " + localNum);
System.out.println("outNum = " + outNum); //클래스 생성된 후 getRunnable 생성
System.out.println("Outer.sNum = " + Outer.sNum);
}
}
return new MyRunnable();
}
}
public class AnonymousInnerTest {
public static void main(String[] args) {
Outer outer = new Outer();
Runnable runner = outer.getRunnable(100); //메서드는 호출 후 종료됨
runner.run(); //클래스의 메서드는 호출가능
}
}
getRunnable 메서드와 MyRunnable 클래스의 생성주기가 다르다.
getRunnable 메서드 : 호출 후에 사라진다.
MyRunnable 클래스의 run 메서드 : getRunnable 메서드와 상관없이(메서드 생성과 상관없이) 호출이 가능하다.
-> MyRunnable 클래스의 run 메서드에서 getRunnable 메서드의 인스턴스 변수에 접근할 수 있도록 하려면 스택(지역변수)이 아닌 final 상수(데이터 영역)에 저장되게 했다.
✅ final : 접근은 가능하지만 변경을 못한다.
=> 그러므로 getRunnable 메서드의 지역변수와 매개변수는 final로 설정된다.
익명 내부 클래스
이름이 없는 클래스
MyRunnable 클래스를 보면 클래스 이름은 호출되지 않는다.
=> 이름 생략하고 인터페이스나 추상 클래스를 구현하여 반환
인터페이스나 추상 클래스 자료형의 변수에 직접 대입하여 클래스를 생성하거나 지역 내부 클래스의 메서드 내부에서 생성하여 반환할 수 있다.
package Chapter6.ch01;
class Outer{
int outNum=100;
static int sNum=200;
Runnable getRunnable(int i) { //메서드-호출 후에 사라짐
int num = 100; //final로 생성됨
return new Runnable() { //익명 클래스
// i=20 못바꿈
int localNum = 10; //final 아님 (변경가능)
@Override
public void run() { //getRunnable 존재와 상관없이 호출 가능
// num=200; 에러 발생, 상수는 못바꿈
// i = 100; 에러 발생, 상수는 못바꿈
System.out.println("i = " + i); //접근만 가능
System.out.println("num = " + num);
System.out.println("localNum = " + localNum);
System.out.println("outNum = " + outNum); //클래스 생성된 후 getRunnable 생성
System.out.println("Outer.sNum = " + Outer.sNum);
}
};
}
Runnable runnable = new Runnable() { // 익명 클래스
@Override
public void run() {
System.out.println("Runnable class");
}
};
}
public class AnonymousInnerTest {
public static void main(String[] args) {
Outer outer = new Outer();
Runnable runner = outer.getRunnable(100); //메서드는 호출 후 종료됨
runner.run(); //클래스의 메서드는 호출가능
outer.runnable.run();
}
}
람다식(Lambda expression)
함수형 프로그래밍과 람다식
자바: 객체 지향 프로그래밍
자바8부터 함수형 프로그래밍 방식 = 람다식을 지원한다.
함수형 프로그래밍(FP)
- 순수 함수를 구현하고 호출함으로써 외부 자료에 부수적인 영향을 주지 않도록 구현하는 방식
- 순수함수: 매개변수만을 사용하여 만드는 함수, 함수 내부에서 함수 외부에 있는 변수를 사용하지 않아 함수가 수행되더라도 외부에는 영향을 주지 않는다.
- 동일 자료에 대해 동일한 결과를 보장한다. 즉 병렬처리가 가능하다.
- 다양한 자료에 대해 같은 기능을 수행할 수 있다.
인터페이스
package Chapter6.ch02;
public interface Add {
public int add(int x, int y);
}
코드
package Chapter6.ch02;
public class AddTest {
public static void main(String[] args) {
Add addL = (x, y)-> {return x+y;}; //반환문일 경우 {} 생략X
Add addL2 = (x, y)-> x+y; //반환문일 경우 {}와 return 둘 다 생략하면 가능
}
}
- 람다식 표현
- (int x, int y) -> {return x+y;}
- 매개변수가 하나일경우 자료형과 괄호 생략가능
- str->{System.out.println(x+y);}
- 매개변수가 두개 이상일 경우 괄호를 생략할 수 없음
- x,y -> System.out.println(x+y);} (X)
- 실행문이 한문장일 경우 중괄호 생략 가능
- str->System.out.println(x+y);
함수형 인터페이스와 람다식 구현하여 사용하기
함수형 인터페이스 선언
람다식을 선언하기 위한 인터페이스
@FunctionalInterface 애노테이션을 사용한다.
익명 함수와 매개변수만으로 구현되므로 인터페이스는 단 하나의 메서드만을 선언해야한다.
(내부에 매개변수 개수와 타입이 같은 여러개의 메서드를 선언하면 오류발생)
어노테이션(@): 컴파일러에게 정보를 준다.
예제
함수형 인터페이스
package Chapter6.ch03;
@FunctionalInterface
public interface MyNumber {
int getMax(int num1, int num2);
}
Test
package Chapter6.ch03;
public class MyNumberTest {
public static void main(String[] args) {
MyNumber myNumber = (x,y)-> x>y? x : y;
System.out.println("myNumber.getMax() = " + myNumber.getMax(10,20));
}
}
MyNumber 함수형 인터페이스에서 매개변수와 타입이 같은 추상 메서드를 찾아서 리턴한다.
=> 입력된 람다식으로 구현된다. 내부적으로는 익명 클래스가 생성된다.
사용시에는 인터페이스의 함수명으로 호출한다.
객체지향 프로그래밍 vs 람다식 구현
package Chapter6.ch04;
public class StringConcatTest {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "World";
//객체 지향
StringConcatImpl strImpl = new StringConcatImpl();
strImpl.makeString(s1, s2);
//lambda
StringConcat strconcat = (s,v)->System.out.println(s+","+v);
strconcat.makeString(s1, s2);
}
}
👍 람다식은 내부적으로 익명 클래스로 구현된다.
함수를 변수처럼 사용하는 람다식
인터페이스형 변수에 람다식 대입하기
interface PrintString{
void showString(String str);
}
PrintString lambdaStr = s->System.out.println(s); //람다식을 변수에 대입
lambda.showString("hi");
매개변수로 전달하는 람다식
showMyString(lambdaStr);
public static void showMyString(PrintString p){
p.showString("hi2");
}
반환값으로 쓰이는 람다식
public static PrintString returnString(){
return s->System.out.println(s+"world");
}
PrintString reStr = returnString();
reStr.showString("hello");
스트림(Stream)
- 자료의 대상과 관계없이 동일한 연산 수행
- 배열, 컬렉션을 대상으로 연산 수행
- 일관성 있는 연산으로 자료의 처리를 쉽고 간단하게 함
- 자료 처리에 대한 추상화가 구현되었다고 함
- 한번 생성하고 사용한 스트림은 재사용 할수 없음
- 자료에 대한 스트림을 생성하여 연산을 수행하면 스트림은 소모됨
- 다른 연산을 수행하기 위해서는 스트림을 다시 생성해야함
- 스트림 연산은 기존 자료를 변경하지 않음
- 스트림 생성시 스트림이 사용하는 메모리 공간은 별도로 생성되므로 연산 수행시에도 기존 자료에 대한 변경은 발생X
스트림 사용 예제
package Chapter6.ch06;
import java.util.Arrays;
import java.util.stream.IntStream;
public class InArrayStreamTest {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
for (int num : arr) {
System.out.println("num = " + num);
}
System.out.println();
//스트림 사용
IntStream is = Arrays.stream(arr);
is.forEach(n -> System.out.println("n = " + n));
//스트림 계산을 또 하고싶다면 다시 한번 스트림 만들기(한번 사용되면 소모됨)
System.out.println();
int sum = Arrays.stream(arr).sum(); //합 구하기 - 15
System.out.println("sum = " + sum);
long count = Arrays.stream(arr).count();//개수 구하기 - 5
System.out.println("count = " + count);
}
}
- 스트림 연산은 중간 연산과 최종 연산으로 구분됨
- 중간 연산: 여러개의 연산이 적용
- filter()-추출, map()-변환, sorted()
- 최종 연산: 마지막 한번만 적용됨
- forEach() - 요소를 하나씩 꺼내옴, count() - 요소의 개수, sum() - 요소들의 합
- 스트림이 관리하는 자료를 하나씩 소모해가며 연산이 수행됨
- 최종 연산 후에 스트림은 더이상 다른 연산을 적용할 수 없음
- 최종 연산이 호출되어야 중간 연산에 대한 수행이 이루어지고 결과가 만들어짐
- 중간 연산에 대한 결과를 연산 중에 알 수 없음 = 지연 연산
- 중간 연산: 여러개의 연산이 적용
문자열 리스트에서 문자열의 길이가 5이상인 요소만 출력하기
sList.stream().filter(s->s.length()>=5).forEach(s->System.out.println(s));
filter() : 중간 연산, forEach(): 최종 연산
고객 클래스 배열에서 고객 이름만 가져오기
customerList.stream().map(c->c.getName()).forEach(s->System.out.println(s));
map():중간 연산, forEach(): 최종 연산
스트림을 사용하는 이유
- 데이터 Collection(순서나 집합적인 저장공간 ex) List,Set)의 반복을 잘 처리하는 기능
- 멀티스테드 코드를 구현하지않아도 데이터를 투명하게 병렬로 처리 가능
- 스트림 : 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
- 컬렉션의 주제: 데이터 / 스트림의 주제: 계산
출처: https://zangzangs.tistory.com/171
연산 예시
package Chapter6.ch06;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class ArrayListStreamTest {
public static void main(String[] args) {
//list 만들기
List<String> sList = new ArrayList<String>();
sList.add("Tomas");
sList.add("Edward");
sList.add("Jack");
//출력하기
Stream<String> stream = sList.stream();
stream.forEach(s -> System.out.println("s = " + s));
System.out.println();
//정렬하기
sList.stream().sorted().forEach(s-> System.out.println("s = " + s));
System.out.println();
//문자열 길이 출력
sList.stream().map(s-> s.length()).forEach(n-> System.out.println("n = " + n));
System.out.println();
//문자열 길이 5 이상인 문자열 추출
sList.stream().filter(s -> s.length() >= 5).forEach(s -> System.out.println("s = " + s));
}
}
정수 자료 연산 예시
package Chapter6.ch06;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class InArrayStreamTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
int sum = list.stream().mapToInt(n -> n.intValue()).sum();
System.out.println("sum = " + sum);
}
}
mapToInt : 스트림을 IntStream으로 변환 (원시형 타입 int) => sum 함수 사용하기 위함
reduce() 연산: 직접 구현한 연산 적용
프로그래머가 직접 연산을 구현할 수 있다.
T reduce(T identify, BinaryOperator<T> accumulator)
identify: 초기값
accumulator: 람다식, BinaryOperator를 구현한 클래스
배열의 모든 요소의 합을 구하는 reduce() 연산 구현 예 - 람다식
Arrays.stream(arr).reduce(0, (a,b)->a+b));
BinaryOperator를 구현하여 배열에 여러 문자열이 있을 때 길이가 가장 긴 문자열 찾기 예
package Chapter6.ch06;
import java.util.Arrays;
import java.util.function.BinaryOperator;
class CompareString implements BinaryOperator<String>{
@Override
public String apply(String s1, String s2) {
if(s1.getBytes().length>=s2.getBytes().length) return s1;
else return s2;
}
}
public class ReduceTest {
public static void main(String[] args) {
String[] greetings = {"안녕하세요~~~", "hello", "Good morning", "반갑습니다^^"};
//람다식으로 구현
System.out.println(Arrays.stream(greetings).reduce("", (s1,s2)->
{
if (s1.getBytes().length>= s2.getBytes().length) return s1;
else return s2;
}));
//클래스로 구현
String str = Arrays.stream(greetings).reduce(new CompareString()).get();//구현한 클래스 이용
System.out.println("str = " + str);
}
}
reduce 메서드는 스트림의 모든 요소를 처리하면서 계산된 결과값과 다음 요소를 비교하여 새로운 결과값을 계산한다.
스트림을 활용하여 패키지 여행 비용 계산하기
여행사에 패키지 여행 상품이 있습니다. 여행 비용은 15세 이상은 100만원, 그 미만은 50만원 입니다.
고객 세 명이 패키지 여행을 떠난다고 했을 때 비용 계산과 고객 명단 검색등에 대한 연산을 스트림을 활용하여 구현해 봅니다.
고객에 대한 클래스를 만들고 ArrayList로 고객을 관리 합니다.
고객 정보는 다음과 같습니다.
CustomerLee
이름 : 이순신
나이 : 40
비용 : 100
CustomerKim
이름 : 김유신
나이 : 20
비용 : 100
CustomerHong
이름 : 홍길동
나이 :13
비용 : 50
고객 클래스
package Chapter6.ch07;
public class TravelCustomer {
private String name;
private int age;
private int price;
public TravelCustomer(String name, int age, int price) {
this.name = name;
this.age = age;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "TravelCustomer{" +
"name='" + name + '\'' +
", age=" + age +
", price=" + price +
'}';
}
}
스트림을 활용한 연산 수행
package Chapter6.ch07;
import java.util.ArrayList;
import java.util.List;
public class TravelTest {
public static void main(String[] args) {
TravelCustomer customerLee = new TravelCustomer("이순신", 40, 100);
TravelCustomer customerKim = new TravelCustomer("김유신", 20, 90);
TravelCustomer customerHong = new TravelCustomer("홍길동", 35, 123);
List<TravelCustomer> customerList = new ArrayList<>();
customerList.add(customerLee);
customerList.add(customerKim);
customerList.add(customerHong);
System.out.println("명단 추가 순서대로 출력");
customerList.stream().map(c -> c.getName()).forEach(s -> System.out.println("s = " + s));
int total = customerList.stream().mapToInt(c -> c.getPrice()).sum();
System.out.println("total = " + total);
System.out.println("20세 이상 고객 명단 정렬하여 출력");
customerList.stream().filter(c -> c.getAge() >= 20).map(c -> c.getName()).forEach(s -> System.out.println("s = " + s));
}
}
'Spring > 카테캠 - TIL' 카테고리의 다른 글
TIL [0511- 0514] : Spring Basic + Spring MVC 1 (2) | 2023.05.14 |
---|---|
TIL [0504-0505] : 객체지향 프로그래밍(예외처리, 로그, IO 스트림, 직렬화) (0) | 2023.05.06 |
TIL (0424 - 0429) : 객체지향 프로그래밍 (0) | 2023.04.29 |
TIL 0417 - 0423 (4) | 2023.04.23 |
TIL (230410 - 230416) (2) | 2023.04.22 |