자바 8버전에서 람다가 나왔는데 그 이후로 자바에서 람다는 없어선 안될 중요한 요소가 됐음

람다는 자바에서 처음 나온게 아니지만 자바 8 이후로 자바에서 람다를 활용하면 코드가 줄어들 뿐만 아니라 가독성까지 향상되는 장점이 있음

 

전 글 ( https://codingaja.tistory.com/144 ) 의 마지막에 본 익명 클래스에서 사용했던 코드를 람다를 사용한 코드로 바꿔보자

<< 코드 설명 >>

점선의 위에 있는 Printable 인터페이스는 동일함

- 점선 밑 왼쪽에 있는 코드는 전 글의 마지막에 본 익명 클래스를 사용한 인터페이스 구현

- 점선 밑 오른쪽에 있는 코드는 이번에 우리가 배울 람다를 사용한 인터페이스 구현임

단, 여기서 익명 클래스와 람다를 비교 했다 그렇다고 해서 익명 클래스와 람다의 내용이 같다거나 비슷하지는 않음

단지 두 요소의 코드가 우연히 비슷하기 때문에 비교해서 본 것

 

람다를 사용한 코드를 좀 더 분석해보자

파란색 영역을 람다 표현식 ( Lambda Expression ) 이라고 함

람다 표현식은 줄여서 람다식 이라고 함

 

람다식 내 -> 를 람다 연산자 ( Lambda Operator ) 라고 함

 

익명 클래스는 불필요한 클래스 선언을 줄여주고

람다는 익명 클래스 보다 더나아가 불필요한 인스턴스 생성까지도 줄여줌

앞서 언급했듯 익명 클래스와 람다의 내용이 같진 않지만 코드의 형태가 우연히 비슷하기 때문에 배울 때는 연계해서 배우면 람다가 좀 더 친숙하게 다가옴


람다식은 메서드의 인자로 전달할 수 있음

무슨 뜻인지 단계별로 살펴보자

 

다음과 같이 Person 클래스가 있는 상황에서

package jpaStudy;

public class Person {
	private String name;
	private int age;
	private double height;
	
	// Getter & Setter 생략
}

1. 메서드의 인자로 값을 전달하는 상황

2. 메서드의 인자로 객체를 전달하는 상황

3. 메서드의 인자로 람다식을 전달하는 상황

을 보자

 

 

1. 메서드의 인자로 값 전달

package jpaStudy;

public class Sample {
	public static void main(String[] args) {
		Person p1 = new Person();
		Person p2 = new Person();
		
		p1.setName("홍길동"); p1.setAge(17); p1.setHeight(176.1);
		p2.setName("고영희"); p2.setAge(17); p2.setHeight(164.3);
	}
}

코드 줄 수를 줄이기 위해 코드를 가로로 나열 했는데 람다를 공부하는 수준이라면 충분히 이해할 수 있을 것

setter ( 좀 더 추상적으로 생각하면 메서드 ) 의 인자로 값을 넣어 메서드의 매개변수로 값을 전달했음

문자열은 값이 아닌 객체지만 값이라고 생각하고 넘어가자 ^^;;

 

 

2. 메서드의 인자로 객체 전달

package jpaStudy;

public class Sample {
	public static void main(String[] args) {
		Person p1 = new Person();
		Person p2 = new Person();
		
		p1.setName("홍길동"); p1.setAge(17); p1.setHeight(176.1);
		p2.setName("고영희"); p2.setAge(17); p2.setHeight(164.3);
		
		if(p1.equals(p2)) {
			System.out.println("두 사람의 정보가 같습니다.");
		} else {
			System.out.println("두 사람의 정보가 다릅니다.");
		}
	}
}

if문의 조건식에 equals 메서드를 사용했는데 이와 같이 메서드의 인자로 객체를 넣어 메서드의 매개변수로 객체를 전달할 수 있음

 

 

3. 메서드의 인자로 메서드 전달

 

우선 메서드가 메서드를 인자로 전달 받기 위해 인터페이스를 하나 선언하자

package jpaStudy;

public interface Printable {
	void print(Person p);
}

여기서 주의할 점은 print 메서드의 매개변수는 Person 타입임

즉, 메서드 이름으로 생각해보면 print 메서드는 전달 받은 객체를 출력하는 메서드임

아직 여기까지는 전혀 특별할 게 없는 인터페이스임

 

일반적이라면 [ Printable 인터페이스를 구현하는 클래스를 선언하고 그 클래스가 Printable 인터페이스를 구현 ] 해 print 메서드가 정확하게 어떤 동작을 하는지 정의할 것

그러나 람다식을 사용하면 [ Printable 인터페이스를 구현하는 클래스를 선언하고 그 클래스가 Printable 인터페이스를 구현 ] 하는 과정을 생략할 수 있음

 

아래 코드가 람다식을 사용한 코드이고 코드의 형태만 놓고 보면 메서드의 인자로 메서드를 넣은 것으로 보임

package jpaStudy;

public class Sample {
	public static void main(String[] args) {
		Person p1 = new Person();
		Person p2 = new Person();
		
		p1.setName("홍길동"); p1.setAge(17); p1.setHeight(176.1);
		p2.setName("고영희"); p2.setAge(17); p2.setHeight(164.3);
		
		if(p1.equals(p2)) {
			System.out.println("두 사람의 정보가 같습니다.");
		} else {
			System.out.println("두 사람의 정보가 다릅니다.");
		}
		
		printPerson((p) -> {
			System.out.println("이름 : " + p1.getName());
			System.out.println("나이 : " + p1.getAge());
			System.out.println("키 : " + p1.getHeight());
		}, p1);
		
		printPerson((p) -> {
			System.out.println("이름은 " + p2.getName() + " 입니다.");
			System.out.println("나이는 " + p2.getAge() + "살 입니다.");
			System.out.println("키는 " + p2.getHeight() + "cm 입니다.");
		}, p2);
	}
	
	public static void printPerson(Printable printer, Person person) {
		printer.print(person);
	}
}

<< 코드 설명 >>

이 코드를 이해하기 위해서는 다시 [ 메서드의 인자로 값 전달 ], [ 메서드의 인자로 객체 전달 ] 부터 살펴봐야함

 

1. 메서드의 인자로 값 전달의 이해

더보기

setName 메서드를 호출하기 위해서 필요한건? 문자열

그래서 setName 메서드를 호출 하면서 인자로 문자열을 넣었음

메서드의 인자로 메서드를 전달 한다 를 이해하기 위해서는 반드시 기억하고 있어야함


 

2. 메서드의 인자로 객체 전달의 이해

더보기

equals 메서드를 호출하기 위해서 필요한건? 비교할 "객체"

그래서 equals 메서드를 호출 하면서 인자로 객체를 넣었음

메서드의 인자로 메서드를 전달 한다 를 이해하기 위해서는 반드시 기억하고 있어야함


 

3. 메서드의 인자로 메서드 전달

아래 설명을 보기 전 먼저 여러분이 직접 코드를 충분히 천천히 살펴보면서 printPerson 메서드를 이해하길 바람

충분히 천천히가 1분일 수도 있고 10분일 수도 있고 1시간일 수도 있음

본인의 실력에 따라 충분히 천천히가 달라지므로 꼭 printPerson 메서드를 이해하고 아래 설명을 보자

printPerson 메서드를 호출하기 위해서 와야하는게 무엇인지 를 이해해야함

더보기

printPerson 메서드를 호출하기 위해 필요한 첫 번째 인자는?

Printable 인터페이스를 구현한 클래스의 인스턴스

printPerson 메서드의 매개변수 타입이 Printable 이므로 Printable 인터페이스의 인스턴스는 안될까?

너무도 잘 알고 있듯이 인터페이스는 추상 메서드를 갖고 있으므로 인터페이스로 인스턴스를 만들 수 없음

 

Printable 인터페이스를 구현한 Printer 클래스를 만들었다고 상상해보자

그럼 이제 위 main의 17 ~ 28번째 줄까지 코드는 없다고 생각한 상태에서 상상 속의 Printer 클래스를 사용해 printPerson 메서드를 호출하는 코드를 상상해보자

 

<< 상상 속의 Printer 클래스를 사용해 printPerson 메서드를 호출하는 코드 >>

그럼 이와 같이 될 것, 상상이므로 컴파일 오류는 없다고 하자

17번째 줄 코드를 보면 printPerson 메서드는 Printer 클래스의 인스턴스가 갖고 있는 print 메서드를 사용해 전달 받은 p1(홍길동의 정보) 객체를 출력할 것

여기서 출력된 결과가 어떻게 될까 까지는 상상하지 않아도 됨

18번째 줄 코드를 보면 역시나 printPerson 메서드는 Printer 클래스의 인스턴스가 갖고 있는 print메서드를 사용해 전달 받은 p2(고영희의 정보) 객체를 출력할 것

 

이때! printPerson 메서드의 매개변수 타입이 Printable 인 의미는 이 메서드(printPerson 메서드)에게 필요한건 Printable 인터페이스에 적어둔 구현된 print 메서드라는 것

표면적으로는 Printable 인터페이스를 구현한 클래스의 인스턴스지만 코드를 곰곰히 곱씹어보면 Printable 인터페이스를 구현한 클래스의 인스턴스가 아니라 Printable 인터페이스에 적어둔 구현된 print 메서드임

 

그래서 번거롭게 Printable 인터페이스를 구현한 Printer 클래스를 선언하고 그러면서 Printer 클래스 안에 print 메서드를 정의하고 또~ Printer 클래스의 인스턴스를 생성한 후 printerPerson 메서드를 호출하는 과정이 아니라 간단하게 람다식을 사용해서 printerPerson 메서드를 호출하면서 print 메서드를 구현해 print 메서드를 매개변수로 전달하는 것

1. 람다식을 적용하기 전

  1-1. Printable 인터페이스를 구현한 Printer 클래스 선언

  1-2. Printer 클래스 내 print 메서드 정의

  1-3. Printer 클래스의 인스턴스 생성

  1-4. printPerson 메서드를 호출하며 인자로 Printer 클래스의 인스턴스 전달

 

2. 람다식을 적용한 후

  2-1. printPerson 메서드를 호출하며 인자로 print 메서드를 구현해 전달


 

728x90
LIST

외부 클래스 ( Outer Class )

  -> 내부 클래스를 감싸는 클래스

 

내부 클래스 ( Inner Class )

  -> 클래스 내 선언 되어있는 클래스

  -> 네스티드 클래스 ( Nested Class ) 라고도 부름

 

아래 코드로 외부, 내부 클래스를 이해하자

(1). 외부 클래스

(2). 내부 클래스

 

외부 클래스는 우리가 지금까지 사용했던 그냥 클래스임

그래서 외부 클래스를 두고 굳이 외부 클래스라고 부르지 않고 그냥 클래스 라고 부름


내부 클래스는 두 가지 기준에 따라 종류가 나뉨

 

Static 키워드 여부에 따라 나눈 내부 클래스

  -> Non-Static Inner Class

  -> Static Inner Class

내부 클래스가 선언된 위치에 따라 나눈 내부 클래스

  -> Member Inner Class

  -> Local Inner Class

  -> Anonymous Inner Class

 

< Static 키워드 여부에 따라 나눈 내부 클래스 >

1. Non-Static Inner Class : static 키워드가 붙지 않은 내부 클래스

더보기

package jpaStudy;

public class Outer {
	// Outer 클래스의 멤버 변수, 메서드, Getter, Setter 등
	
	class NonStaticNestedClass {
		// static nested class의 멤버 변수, 메서드, Getter, Setter 등
		
	}
}

 

2. Static Inner Class : static 키워드가 붙은 내부 클래스

더보기

package jpaStudy;

public class Outer {
	// Outer 클래스의 멤버 변수, 메서드, Getter, Setter 등
	
	static class StaticNestedClass {
		// static nested class의 멤버 변수, 메서드, Getter, Setter 등
		
	}
}

 

 

 

< 내부 클래스가 선언된 위치에 따라 나눈 내부 클래스 >

1. Member Inner Class : 멤버 변수, 메서드를 선언하는 위치인 클래스 내에 선언된 내부 클래스

더보기

아래 Outer 클래스와 Inner 클래스가 있음

Inner 클래스는 Outer 클래스 내에 있고 Outer 클래스의 구성 요소인 멤버 변수, 메서드와 같은 위치에 선언되어있음

이러한 내부 클래스를 Member Inner Class 라 함

package jpaStudy;

public class Outer {
	private int outerField;
	
	public void outerMethod() {
		// 어떤 코드1
		// ...
		// 어떤 코드n
	}
	
	public int getOuterField() { return outerField; }

	public void setOuterField(int outerField) { this.outerField = outerField; }

	class Inner {
		private int innerField;
		
		public void increseOuterField(int n) {
			outerField = outerField + n;
		}
		
		public int getOuterField() {
			return outerField;
		}
	}
}

특히 이 Member Inner Class의 경우 외부 클래스의 멤버 변수에 접근할 수 있는 점이 특징임

 

이제 내부 클래스를 사용해 객체를 생성해보자

이때 내부 클래스에 static 키워드가 붙지 않았기 때문에 내부 클래스를 사용해 객체를 생성하려면 우선 외부 클래스로 객체를 생성해야함

그 후 외부 클래스로 생성한 객체를 사용해 내부 클래스를 사용해 객체를 생성할 수 있음

 

package jpaStudy;

public class Example {
	public static void main(String[] args) {
		Outer o1 = new Outer();
		Outer o2 = new Outer();
		
		Outer.Inner o1i1 = o1.new Inner();
		Outer.Inner o1i2 = o1.new Inner();
		
		Outer.Inner o2i1 = o2.new Inner();
		Outer.Inner o2i2 = o2.new Inner();
		
		o1i1.increseOuterField(3);
		System.out.println(o1i2.getOuterField());
		
		o2i1.increseOuterField(5);
		System.out.println(o2i2.getOuterField());
	}
}

< 코드 설명 >

(1). 위에서 언급한대로 Outer 클래스가 갖고 있는 Inner 클래스를 사용해 객체를 생성하거나 인스턴스를 생성하기 위해서는 반드시 Outer 클래스의 객체가 필요함
  Outer 클래스의 객체 없이 Inner 클래스를 사용해 객체를 생성하려면 Inner 클래스를 Static Inner Class로 만들어주면 됨
  단, Static Inner Class가 되면 멤버 변수에 접근하거나 메서드를 호출 할 때 제약이 생김

(2). o1 객체를 사용해 Outer 클래스가 갖고 있는 Inner 클래스 객체를 두 개 생성했음

  이럴 경우 o1i1, o1i2 두 객체는 한 객체(o1)을 통해 만들었기 때문에 o1 객체가 갖고 있는 멤버 변수를 공유해서 사용함

  즉, o1i1 객체를 통해서 o1 객체가 갖고 있는 outerField 멤버 변수에 접근할 수 있고 o1i2 객체를 통해서 o1 객체가 갖고 있는 outerField 멤버 변수에 접근할 수 있음

  당연한 얘기지만 o1 객체를 통해서도 같은 outerField 멤버 변수에 접근할 수 있음

(3). o1i1 객체를 통해서 outerField 멤버 변수의 값을 3 증가 시켰음 그 후 o1i2 객체를 통해서 outerField 멤버 변수의 값을 가져와 출력하고 있음

  이를 통해 o1i1, o1i2 두 객체가 같은 outerField 멤버 변수에 접근 하고 있다는걸 알 수 있음


Member Inner Class 는 클래스의 정의를 감추고 싶을 때 사용함

개발자가 직접 Member Inner Class 를 선언하고 활용하는 경우는 드물지만 자바의 Iterator에서 Member Inner Class를 활용함

 

2. Local Inner Class : if, while, 메서드 등 코드 블록 내에 선언된 내부 클래스

  내부 클래스 자체가 사용 빈도가 떨어지는데 Local Inner Class 의 경우 사용 빈도가 더 떨어짐

  따라서 Local Inner Class 가 있다 정도로만 생각하고 넘어가자

 

3. Anonymous Inner Class : 인터페이스 또는 추상클래스의 구현과 동시에 인스턴스를 생성할 때 사용하는 인스턴스의 타입으로 사용되는 클래스

더보기

Interface는 당연히 인스턴스를 생성할 수 없다는 것을 잘 알 것

다음과 같은 Interface가 있을 경우 당연히 이 Interface를 구현하는 클래스가 별도로 있을 것

package jpaStudy;

public interface Printable {
	void print();
}

 

그러나 Anonymous Inner Class 를 사용하면 Interface 를 구현하는 클래스 없이 Interface 구현과 동시에 인스턴스를 생성할 수 있음

package jpaStudy;

public class Sample {
	public static void main(String[] args) {
		Printable p = new Printable() {
			@Override
			public void print() {
				System.out.println("익명 클래스를 활용해");
				System.out.println("인터페이스 구현과 동시에 객체 생성");
			}
		};
		
		p.print();
	}
}

< 코드 설명 >

위 main 코드에서 익명 내부 클래스는 파란색 네모 쳐진 부분임
잘 알고 있듯 원래 인터페이스는 인스턴스를 생성할 수 없음
지금이 설명에 사용된 코드도 인터페이스로 인스턴스를 생성한 게 아님

코드만 보기에는 인터페이스로 인스턴스를 생성한 것처럼 보이지만 익명 클래스를 사용해 인터페이스를 구현하고 그 익명 클래스를 사용해 구현된 print 메서드를 갖고 있는 인스턴스가 생성 된 것

 

이런 형태의 코드는 특히 안드로이드를 개발할 때 많이 활용됨

즉, 익명 클래스가 특별한게 아닌 일반적이라는 것

 

또 다른 예시를 보고 익명 클래스를 마무리 하자

package jpaStudy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Sample {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("ROBOT");
		list.add("APPLE");
		list.add("BOX");
		
		System.out.println(list);
		
		Collections.sort(list, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.length() - o2.length();
			}
		});
		
		System.out.println(list);
	}
}


여기까지 외부 클래스와 내부 클래스에 대해서 배웠음

여기서 배운 것 중 특히, 익명 클래스가 중요함

 

자바로 안드로이드 개발하면 많이 보게 될 코드이기도 하고

자바로 웹 개발을 할 때는 익명 클래스를 활발히 사용하진 않지만 이제부터 배울, 그리고 자바에서는 없어선 안될 람다(Lambda)의 형태와 익명 클래스의 형태가 비슷하게 생겼기 때문에 람다를 친숙하게 만들어줌

 

여기서 배운 것들 중 익명 클래스가 람다 코드를 친숙하게 만들어 준다 외에 크게 중요한 점은 없음

728x90
LIST

'자바 > Chap10. 내장 클래스와 람다' 카테고리의 다른 글

Chapter10. 람다(Lambda) - 기초  (0) 2023.08.21