본문 바로가기
Java

익명객체와 람다식(Stream)

by 임혁진 2023. 11. 30.

익명객체 (람다식의 전제조건)

 

 

인터페이스 Car, 익명객체를 사용

interface Car {

public void run();

}

 

 

public class Garage {

 

//Car인터페이스를 상속받는 클래스(딱 한번만 사용하고, 더이상 사용하지 않을 클래스는 익명객체로 생성할수 있습니다)

public Car car = new Car() {

 

@Override

public void run() {

System.out.println("this is pride");

}

};

}

public class MainClass {

 

public static void main(String[] args) {

 

Garage g = new Garage();

 

g.car.run();

 

}

}

 

 

익명객체가 없을 때 (Pride로 구현)

public class Pride implements Car {

 

@Override

public void run() {

System.out.println("this is pride");

}

 

}

 

public class Tv {

 

private int volume;

private RemoteControl remote;

 

 

public Tv() {

 

String name = "LG TV"; //지역변수

 

remote = new RemoteControl() {

 

//익명객체 안에서는 this키워드 사용이 불가함

//익명객체 안에서는 지역변수의 값을 변경이 불가능(사용은 가능)

@Override

public void volumeUp() {

 

//name = "이렇게 바꿀래";

 

Tv.this.volume++; //Tv의 멤버변수에 접근이 가능

System.out.println(volume);

}

 

@Override

public void volumeDown() {

volume--;

System.out.println(volume);

}

 

@Override

public void turnOn() {

System.out.println("Tv를 켭니다");

}

 

@Override

public void turnOff() {

System.out.println("Tv를 끕니다");

}

};

 

 

}

 

 

//remote을 외부에서 받으려면

//setter

public void setRemote(RemoteControl remote) {

this.remote = remote;

}

 

//getter

public RemoteControl getRemote() {

return remote;

}

}

public interface RemoteControl {

 

public void turnOn();

public void turnOff();

public void volumeUp();

public void volumeDown();

 

}

public class MainClass {

 

public static void main(String[] args) {

 

Tv tv = new Tv();

 

tv.getRemote().turnOn();

tv.getRemote().volumeUp();

tv.getRemote().turnOff();

}

}

람다식의 정의

=> 함수적 프로그래밍 y=f(x); 형태의 함수

 

함수적 프로그래밍이 객체지향 프로그래밍보다 효율적인 경우

1. 대용량 데이터의 처리

2. 이벤트 지향 프로그램

 

에서 좋은 효율을 보인다

 

요새는 굳이 람다식(함수식)과 객체지향을 구분하지않고 추가하여 쓴다

 

객체지향 프로그래밍 + 함수식 프로그래밍 ;

 

람다식의 사용방법

익명함수 생성식

(매개변수,매개변수) -> {실행문}

 

람다식의 장점

- 코드가 간결해진다

- 컬렉션 요소(Map,Set,List) 처리가 쉬워진다

 

람다식의 기본 사용

=>자바의 람다식은 함수적 인터페이의 익명 구현 '객체'를 대신합니다.

 

=>람다식은 코드블럭을 메소드에 넣을 때 사용하는 기술.

//추상메서드가 1개인 인터페이스를 함수적인터페이스 라고 부릅니다.

public interface Say01 {

public void talking();

}

 

public interface Say02 {

public String talking();

}

public interface Say03 {

 

public int talking(String a, int b);

}

public class Person {

 

public void greeting(Say01 say) {

say.talking();

}

 

//say02를 받는 인사법

public String greeting(Say02 say) {

String result = say.talking();

return result;

}

 

//say03을 받는 인사법

public int greeting(Say03 say) {

 

int result = say.talking("hello", 10);

return result;

}

}

public class MainClass {

 

public static void main(String[] args) {

 

Person p = new Person();

 

p.greeting( new Say01() {

@Override

public void talking() {

System.out.println("hello");

}

});

 

p.greeting( new Say01() {

@Override

public void talking() {

System.out.println("안녕하세요");

}

});

 

p.greeting( new Say01() {

 

@Override

public void talking() {

System.out.println("니쉬팔로마");

}

});

 

System.out.println("---------------------------------");

 

//함수적인터페이스를 구현하는 익명객체를 -> 람다식으로 표현이 가능

p.greeting( () -> { System.out.println("hello"); });

p.greeting( () -> System.out.println("안녕하세요") ); //표현할 코드가 한줄이면 {}생략이 됩니다

 

 

System.out.println("---------------------------------");

// String r = p.greeting( new Say02() {

// @Override

// public String talking() {

// return "hello";

// }

// } );

 

String r = p.greeting( () -> "hello" ); //한줄이면서, return이 생략되면 자동으로 return됩니다

System.out.println(r);

 

 

System.out.println("---------------------------------");

 

// int r2 = p.greeting( new Say03() {

// @Override

// public int talking(String a, int b) {

// //처리할 코드

// int sum = 0;

// for(int i = 1; i <= b; i++) {

// sum += i;

// }

// return sum;

// }

// });

 

 

int r2 = p.greeting( (a, b) -> { //자동타입추론 ()안에는 타입을 생략합니다. 매개변수가 1개라면 ()도 생략가능합니다

 

int sum = 0;

for(int i = 1; i <= b; i++) {

sum += i;

}

return sum;

});

 

System.out.println(r2);

 

}

}

람다식 적용을 위한 스트림(Stream())

반복자 스트림-collection 저장 요소를 하나씩 참조하도록 도와주는 반복자 

파일입출력(Stream)이랑은 다른 것이다.

 

스트림의 특징 

Iterator와 비슷한 역할을 하는 반복자이다.

대부분 메소드는 함수적 인터페이스 타입(1개의 추상 메소드)이다.

속도가 빠르다.

 

구조

Stream의 실행 예시

Stream 의 진행 예시

 

중간처리 메소드

 

자주 쓰는 메소드 

distinct() -중복 제거 메소드

filter() - 매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링

map , mapTo~~ - 요소를 대체하는 요소로 구성된 새로운 Stream 리턴

sorted() - 요소를 정렬 

 

최종처리 메소드

자주 쓰는 메소드

forEach() 메소드 - 결과를 출력할 때 사용

Collect(0 메소드 - 컬렉션 객체를 List,Set,Map으로 변환처리 할 때 사용한다

.count() 메소드 - 갯수를 세릴 때 사용한다.

sum() 메소드 - 수를 합칠 때 사용

reduce() 메소드 - 사용자가 정의한 합계를 구할 수 있는 최종메소

람다식 예제 

public class MainClass01 {

 

public static void main(String[] args) {

 

 

List<String> list = new ArrayList<>();

list.add("홍길동");

list.add("피카츄");

list.add("라이츄");

list.add("꼬북이");

 

for(String s : list) {

System.out.println(s);

}

 

System.out.println("-------------------------------------------");

 

//Stream<String> stream = list.stream();

//stream.forEach( a -> System.out.println(a) );

 

// list.stream().forEach( new Consumer<String>() {

// @Override

// public void accept(String a) {

// System.out.println(a);

// }

//

// });

 

list.stream().forEach( a -> System.out.println(a) );

 

}

}

 

람다식 예제2

public static void main(String[] args) {

 

/*

* 펑셔널인터페이스

* Consumer - 매개변수가 1개 이상이고, 반환은 void

* Predicate - 매개변수가 1개 이상이고, 반환이 boolean

* Function - 매개변수가 1개 이상이고, 반환은 임의의 타입

*/

 

//100개의 랜덤한 값을 가지고 있는 리스트

List<Integer> list = new ArrayList<>();

 

for(int i = 0; i < 100; i++ ) {

list.add( new Random().nextInt(100) + 1 );

}

System.out.println(list.toString());

 

System.out.println("-----------------------------------");

 

//중복제거

list.stream().distinct().forEach( a -> System.out.print(a + " ") );

 

System.out.println("\n-----------------------------------");

 

//return에 true인 값들만 필터링해서 저장

list.stream().filter( a -> a > 50 ).forEach(a -> System.out.print(a + " ") );

 

System.out.println("\n-----------------------------------");

 

//정렬

list.stream().sorted().forEach(a -> System.out.print(a + " "));

 

System.out.println("\n-----------------------------------");

 

//map -> 메서드 안에 정의된 내용을 기준으로 새로운 리스트를 만듬

//a는 리스트의 요소, 반환은 임의의 새로운값

list.stream().map( a -> a % 2 == 0 ? true : false).forEach(a -> System.out.print(a + " ") );

 

 

System.out.println("\n-----------------------------------");

 

list.stream()

.distinct()

.map( a -> a > 50 ? a : 0 )

.filter( a -> a != 0 )

.sorted()

.forEach( a -> System.out.print(a + " ") );

 

System.out.println("\n-----------------------------------");

 

//collect = 최종수집함수(새로운 list, set, map) 반환을 받을 수 있음

List<Integer> newList = list.stream()

.map( a -> a < 0 ? -a : a)

.sorted()

.collect( Collectors.toList() );

 

System.out.println(newList);

 

 

System.out.println("--------------------------------------");

 

List<String> list2 = Arrays.asList("hong", "LEE", "Park", "Choi");

 

//각 이름의 최초 0번째 글자를 얻어서, 전부 대문자로 치환하고, 알파벳순으로 정렬한 결과를 리스트로 반환받는다

List<String> newList2 = list2.stream()

.map( a -> a.toUpperCase().charAt(0) + "" )

.sorted()

.collect(Collectors.toList());

 

System.out.println(newList2);

 

 

Map<String, Integer> map = list2.stream().collect(Collectors.toMap(a -> a, a -> a.length() ));

System.out.println(map.toString());

}

}

 

람다식 예제3

public class MainClass03 {

 

public static void main(String[] args) {

 

/*

* Stream의 타입

*

* Stream - 오리지널스트림

* IntStream - 정수저장스트림

* DoubleStream - 실수저장스트림

* LongStream - 롱타입 저장스트림

* Optional - 이외의 것들을 저장스트림

*

*/

List<Integer> list = new ArrayList<>();

 

for(int i = 1; i <= 20; i++) {

list.add( new Random().nextInt(100) + 1 );

}

 

System.out.println(list.toString());

 

//평균, 합계, 개수 등 집계기능을 사용하려면 정수형스트림으로 변경

//mapToXXX()

long cnt = list.stream().mapToInt( a -> a).count(); //count -최종함수

System.out.println("데이터개수:" + cnt);

 

int sum = list.stream().mapToInt(a -> a).sum(); //sum - 최종함수

System.out.println("데이터의합계:" + sum);

 

//optionalXX은 특정한 값을 저장하고 있는 객체, 값을 얻을때는 get메서드를 사용함

double avg = list.stream().mapToInt(a -> a).average().getAsDouble();

System.out.println("데이터의평균:" + avg);

 

int min = list.stream().mapToInt(a -> a).min().getAsInt();

System.out.println("최소값:" + min);

 

//boxed() -> 정수스트림을 오리지널스트림으로 형변환

Stream<Integer> s = list.stream().mapToInt(a -> a).boxed();

 

//Reduce() - 사용자가 정의한 합계를 구할 수 있는 최종메서드

List<String> list2 = Arrays.asList("a", "b", "c", "d", "e");

//a가 초기값, b가 요소

String result = list2.stream().reduce((a, b) -> a+b).get();

System.out.println(result);

 

System.out.println("---------------------------------------------------");

 

//정수스트림을 빠르게 만드는법

//0~10미만 값을 인트스트림으로 생성

IntStream.range(0, 10).forEach( a -> System.out.print(a + " ") );

 

System.out.println();

 

//0~10까지 값을 인트스트림으로 생성

IntStream.rangeClosed(0, 10).forEach( a -> System.out.print(a + " "));

 

System.out.println();

 

//1~100까지 값을 저장하는 리스트

List<Integer> list3 = IntStream.rangeClosed(1, 100).boxed().collect( Collectors.toList() );

System.out.println(list3);

}

}

 

 

람다는 함수를 많이 이해하고 외워서 많이 써보자.