스트림의 연산

스트림이 제공하는 기능은 ? 중간 연산최종 연산이 있다.

 

중간 연산 : 연산결과가 스트림인 연산. 반복적으로 적용가능

최종 연산 : 연산결과가 스트림이 아닌 연산. 단 한번만 적용가능 (스트림의 요소를 소모)


중간 연산

Stream<T> distinct() - 중복 제거

Stream<T> filter(Predicate<T> predicate) - 조건에 안 맞는 요소 제외

Stream<T> limit(long maxSize) - 스트림의 일부를 잘라낸다

Stream<T> skip(long n) - 스트림의 일부를 건너 뛴다

Stream<T> peek(Consumer<T> action) - 스트림의 요소에 작업수행

 

Stream<T> sorted()

Stream<T> sorted(Comparator<T> comparator) 스트림의 요소를 정렬한다.

 

스트림의 요소를 변환

Stream<R> map(Function<T,R> mapper)

DoubleStream mapToDouble(ToDoubleFunction<T> mapper)

IntStream mapToInt(ToIntFunction<T> mapper)

LongStream mapToLong(ToLongFunction<T> mapper)

Stream<R> flatMap(Function<T,Stream<R>> mapper)

DoubleStream flatMapToDouble(Function<T.DoubleStream> m)

IntStream flatMapToInt(Function<T, IntStream> m)

LongStream flatMapToLong(Function<T,LongStream> m)

 


최종 연산

void forEach(Consumer<? super T> action>

void forEachOrdered(Consumer<? super T>action) - 각 요소에 지정된 작업 수행

(Ordered 붙으면 순서 유지됨, 병렬 스트림이여도)

 

long count() - 스트림의 요소의 개수 반환

 

Optional<T> max (Comparator<? super T> comparator)        

Optional<T> min (Comparator<? super T> comparator)

스트림의 최대값/최소값을 반환

스트림의 요소 하나를 반환

 

Optional<T> findAny() //아무거나 하나

Optional<T> findFirst() //첫번째 요소

 

주어진 조건을 모든 요소가 만족시키는지, 만족시키지 않는지 확인

boolean allMatch(Predicate<T> p) // 모두 만족 하는지

boolean anyMatch(PredicaAte<T> p ) // 하나라도 만족하는지

boolean nonMatch(Predicate<> p) // 모두 만족하지 않는지

 

스트림의 모든 요소를 배열로 반환

Object[] toArray()    

A[] toArray(IntFunction<A[]> generator)

 

스트림의 요소를 하나씩 줄여가면서(리듀싱) 계산한다.

Optional<T> reduce(BinaryOperator<T> accumulator)

T reduce (T identity, BinaryOperator<T> accumulator)

U reduce (U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)

 

스트림의 요소를 수집한다. 주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용된다.

R collect(Collector<T,A,R> collector)

R collect(Supplier<R> supplier, BiConsumer<R,T> accumulator, BiConsumer<R,R> combiner)


자세히 살펴보자😀

스트림 자르기 - skip(), limit()

Stream<T> skip (long n) // 앞에서부터 n개 건너뛰기

Stream<T> limit(long maxSize) // maxSize 이후의 요소는 잘라냄

 

예시 )

IntStream intStream = IntStream.rangeClosed(1, 10); // 12345678910

intStream.skip(3).limit(5).forEach(System.out::print); // 45678


스트림의 요소 걸러내기 - filter(), distinct()

Stream<T> filter(Predicate<? super T> predicate) // 조건에 맞지 않는 요소 제거

Stream<T> distinct() // 중복제거

 

예시 )

IntStream intStream = IntStream.of(1,2,2,3,3,3,4,5,5,6);

intStream.distinct().forEach(System.out::println); // 123456

 

IntStream intStream = IntStream.rangeClosed(1, 10); // 12345678910

intStream.filter(i->i%2==0).forEach(System.out::print); // 246810

                2의 배수 제거

intStream.filter(i->i%2!=0 && i%3!=0).forEach(System.out::print);

intStream.filter(i->i%2!=0).filter(i->i%3!=0).forEach(System.out::print);

                   중간 연산

2의 배수 빼고, 3의 배수빼면 1,5,7만 남음.


스트림 정렬하기 - sorted()

Stream<T> sorted() // 스트림 요소의 기본 정렬(Comparable)로 정렬

Stream<T> sorted(Comparator<? super T> compartor) // 지정된 Comparator로 정렬

 

strStream.sorted() 기본 정렬

strStream.sorted(Comparator.naturalOrder()) 같은 기본 정렬임 안에 생략되어 있는 것

strStream.sorted((s1,s2) -> s1.compareTo(s2)); // 람다식도 가능

strStream.sorted(String::compareTo); // 위와 동일함

출력 결과 대문자 먼저 나온다
CCaaabccdd

::는 뭐지? 어떻게 쓰는걸까? 궁금해서 검색 후 메소드 참조에 정리할 것임.

 

strStream.sorted(Comparator.reverseOrder()) // 기본 정렬의 역순

strStream.sorted(Comparator.<String>naturalOrder().reversed())

ddccbaaaCC

strStream.sorted(String.CASE_INSENSITIVE_ORDER) // 대소문자 구분안함

aaabCCccdd      대문자가 먼저 나온다. CCcc reverse 여서 ccCC가 나올것 같지만..😑

strStream.sorted(String.CASE_INSENSITIVE_ORDER.reversed()) 

ddCCccbaaa

strStream.sorted(Comparator.comparing(String::length)) // 길이 순 정렬

strStream.sorted(Comparator.comparingInt(String::length)) // no 오토박싱

bddCCccaaa

strStream.sorted(Comparator.comparing(String::length).reversed()) 

aaaddCCccb

- Comparator의 comparing()으로 정렬 기준을 제공

comparing(Function<T, U> keyExtractor)

comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)

 

studentStream.sorted(Comparator.comparing Student::getBan)) // 반별로 정렬

                   .forEach(System.out::println);

 

- 추가 정렬 기준을 제공할 때는 thenComparing()을 사용

thenComparing(Comparator<T> other)

thenComparing(Function<T, U> keyExtractor)

thenComparing(Function<T, U> keyExtractor, Comparator<U> keyComp)

 

studentStream.sorted(Comparator.comparing(Student::getBean) // 반별로 정리

                            .thenComparing(Student::getTotalScore)   // 총점별로 정렬

                            .thenComparing(Student::getName)) // 이름별로 정렬

                            .forEach(System.out::println);

 


실 습 코 드

package Stream;

import java.util.Comparator;
import java.util.stream.Stream;

public class StreamSort {

	public static void main(String[] args) {
		Stream<Student> studentStream = Stream.of(
				new Student("이자바", 3, 300),
				new Student("김자바", 1, 200),
				new Student("안자바", 2, 100),
				new Student("박자바", 2, 150),
				new Student("소자바", 1, 200),
				new Student("나자바", 3, 290),
				new Student("감자바", 3, 180)
				);
		
		studentStream.sorted(Comparator.comparing(Student::getBan)
				.thenComparing(Comparator.naturalOrder()))
				.forEach(System.out::println);
	}

}

class Student implements Comparable<Student>{
	String name;
	int ban;
	int totalScore;
	
	Student(String name,int ban,int totalScore){
		this.name = name;
		this.ban = ban;
		this.totalScore = totalScore;
	}
	
	public String toString() {
		return String.format("[%s, %d, %d]",name, ban, totalScore);
	}

	public String getName() {return name;}
	public int getBan() {return ban;}
	public int getTotalScore() {return totalScore;}

	// 총점 내림차순을 기본 정렬로 한다.
	public int compareTo(Student s) {
		return s.totalScore - this.totalScore;
	}
	
}

Student는 기본 정렬을 Comparable로 구현하고 있고,

Comparable을 구현하면 compareTo 메서드를 오버라이딩 해야함.

그 오버라이딩 부분을 살펴보니 총점 내림차순을 기본 정렬로 한다.

		studentStream.sorted(Comparator.comparing(Student::getBan)
//				.thenComparing(Comparator.naturalOrder()))
				.forEach(System.out::println);

주석 처리하면 기본정렬이 됨.

 

람다식으로 바꾸는 연습

//		studentStream.sorted(Comparator.comparing(Student::getBan)
		studentStream.sorted(Comparator.comparing((Student s) -> s.getBan())
				.thenComparing(Comparator.naturalOrder()))
				.forEach(System.out::println);

 

스트림(Stream) 🌊

- 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것

※ 데이터 소스 : 컬렉션, 배열

 

컬렉션 프레임워크는 List, Set, Map 하고 있는 데 사용 방법이 다 같지는 않았다.

스트림은 JDK 1.8부터 통일됨.

 

List, Set, Map으로 Stream을 만들 수 있다.

Stream을 만들고 나면 다 똑같다.

Stream -> 중간 연산 -> 최종 연산 -> 결과

 

  • List <Integer> list = Arrays.asList(1,2,3,4,5);
  • Stream <Integer> intStream = list.stream(); // 컬렉션
  • Stream <String> strStream = Stream.of(new String []{"a", "b", "c"}); // 배열
  • Stream <Integer> evenStream = Stream.iterate(0, n->n+2); // 0,2,4,6...
  • Stream <Double> randomStream = Stream.generate(Math::random); // 람다식
  • IntStream intStream = new Random(). ints(5); // 난수 스트림 (크기가 5)
스트림이 제공하는 기능 

- 중간 연산 : 연산 결과가 스트림인 연산, 반복적으로 적용 가능

- 최종 연산 : 연산 결과가 스트림이 아닌 연산. 단 한 번만 적용 가능(스트림의 요소를 소모)

 

1. 스트림 만들기

2. 중간 연산(0~n번)

3. 최종 연산(0~1번)

 

stream.distinct(). limit(5). sorted(). forEach(System.out::println)

            중간 연산 3번                    최종 연산 1번


스트림(Stream)의 특징

- 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지 않는다.

List<Integer> list = Arrays.asList(3,1,5,4,2);
List<Integer> sortedList = list.stream().sorted()   // list를 정렬해서
		.collect(Collectors.toList());    // 새로운 List에 저장

list = [3 , 1 , 5 , 4 , 2]

sortedList = [1 , 2 , 3 , 4 , 5]

변경되지 않았음.

 

- 스트림은 Iterator처럼 일회용이다. (필요하면 다시 스트림을 생성해야 함)

strStream.forEach(System.out::println); // 모든 요소를 화면에 출력 (최종연산)
int numOfStr = strStream.count();       // 에러. 스트림이 이미 닫혔음.

 

- 최종 연산 전까지 중간 연산이 수행되지 않는다. - 지연된 연산

IntStream intStream = new Random().ints(1,46); // 1~45범위의 무한 스트림
intStream.distinct().limit(6).sorted()         // 중간 연산
   .forEach(i->System.out.print(i+","));       // 최종 연산

로또 번호 생성하는 코드임

로또 번호 중복 들어가면 안 되니깐 distinct() 그리고 숫자는 6개 잘라줘야 하므로 limit() 정렬 sorted()

중복을 제거하고 


스트림(Stream)의 특징 2

- 스트림은 작업을 내부 반복으로 처리한다.

// 반복문
for(String str : strList)
	System.out.println(str);

// 스트림 사용
stream.forEach(System.out::println);

forEach(최종 연산) 문을 열어보면 for문이 안에 들어가 있음

성능은 좋지 않지만 코드가 간결해진다는 장점이 있음.

void forEach(Consumer<? super T> action) {
	Objects.requireNonNull(action); // 매개변수의 널 체크
    
    for(T t : src) // 내부 반복 (for문을 메서드 안으로 넣음)
    	action.accept(T);
}

- 스트림의 작업을 병렬로 처리 - 병렬 스트림

Stream<String> strStream = Stream.of("dd","aaa","CC","CC","b");
int num = strStream.parallel() // 병렬 스트림으로 전환 (속성만 변경)
		.mapToInt(s -> s.length()).sum(); // 모든 문자열의 길이의 합

멀티 스레드로 병렬 처리한 것.

빅데이터는 한 개의 스레드보다 여러 개의 스레드가 처리하는 것이 좋음.

병렬로 처리하는 것이 parallel() 메서드이다.

 

- 기본형 스트림 - IntStream, LongStream, DoubleStream

 - 오토 박싱&언박싱의 비효율이 제거됨(Stream <Integer> 대신 IntStream 사용)

 - 숫자와 관련된 유용한 메서드를 Stream <T>보다 더 많이 제공


스트림 만들기 - 컬렉션

- Collection인터페이스의 stream()으로 컬렉션을 스트림으로 변환

	public static void main(String[] args) {
		List<Integer> list = Arrays.asList(1,2,3,4,5);
		Stream<Integer> intStream = list.stream(); // list를 Stream으로 변환
		intStream.forEach(System.out::print);
		intStream.forEach(System.out::print);
	}

출력 결과 에러 났음

에러난 이유는 스트림은 일회용인데 두 번 사용했기 때문!

		List<Integer> list = Arrays.asList(1,2,3,4,5);
		Stream<Integer> intStream = list.stream(); // list를 Stream으로 변환
		intStream.forEach(System.out::print);
		
		intStream = list.stream();
		intStream.forEach(System.out::print);

이렇게 하나를 더 만들어 줘야 한다. (완전 iterator 하고 똑같네..)

 

스트림을 배열로 만들어보자

- 객체 배열로부터 스트림 생성하기

Stream <T> stream.of(T..values) // 가변 인자

Stream<T> stream.of(T [])

Stream <T> Arrays.stream(T [])

Stream <T> Arrays.stream(T [] array, int startInclusive, int endExclusive) 배열의 일부로 만드는 것

사용 예

Stream<String> strStream = Stream.of("a","b',"c");
Stream<String> strStream = Stream.of(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"}, 0, 3);

한번 사용해보자.

		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
		Stream<Integer> intStream2 = list.stream(); // list를 Stream으로 변환
		intStream2.forEach(System.out::print); // forEach()최종연산

		// stream 1회용. stream에 대해 최종연산을 수행하면 stream이 닫힌다.
		intStream2 = list.stream(); // list로부터 stream을 생성
		intStream2.forEach(System.out::print);

		String[] strArr = { "a", "b", "c", "d" };
//		Stream<String> strStream = Stream.of(new String[] {"a","b","c","d"});
		Stream<String> strStream = Arrays.stream(strArr);
		strStream.forEach(System.out::println);

		int[] intArr = { 1, 2, 3, 4, 5 };
		Arrays.stream(intArr);
		IntStream intStream = Arrays.stream(intArr);
//		intStream.forEach(System.out::println);
//		System.out.println("count="+intStream.count());
//		System.out.println("sum="+intStream.sum());
		System.out.println("average="+intStream.average());

		Integer[] intArr2 = { 1, 2, 3, 4, 5 };
		Arrays.stream(intArr2);
		Stream<Integer> intStream3 = Arrays.stream(intArr2);
		intStream3.forEach(System.out::println);

기본형 IntStream 같은 경우는 count, sum, average를 제공을 해준다.

count 개수 sum 합계 average 평균

근데 객체 Stream은 숫자인지 문자인지 모르기 때문에 sum, average 같은 숫자형 메서드는 없다.

average 사용시 OptionalDouble[3.0] 출력


스트림 만들기 - 임의의 수

- 난수를 요소로 갖는 스트림 생성하기

IntStreamintStream = new Random().ints(); // 무한 스트림
intStream.limit(5).forEach(System.out::println); // 5개의 요소만 출력한다.

IntStream intStream = new Random().ints(5); // 크기가 5인 난수 스트림을 반환

스트림에는 유한 스트림, 무한 스트림이 존재한다.

무한 스트림을 생성해서 5개를 잘라준다.

 

밑에 코드는 크기가 5인 난수 스트림을 반환하는 것.

	//IntStream intStream = new Random().ints();
	IntStream intStream = new Random().ints(1,45);
	intStream.limit(6).forEach(System.out::println);

. limit(5) 없으면 계속 나옴 무한 스트림이라..

. ints(5,10) 범위를 지정해 줄 수도 있음.. 그럼 5~10의 값이 나오고 1,45를 하면 로또 번호 1~45를 만들 수 있음

distinct() 메서드를 추가시키면 중복도 없애겠지?

 

- 특정 범위의 정수를 요소로 갖는 스트림 생성하기

IntStream intStream = IntStream.range(1, 5);		// 1,2,3,4
IntStream intStream = IntStream.rangeClosed(1, 5);  // 1,2,3,4,5

closed 포함 없는 건 그냥 ~


스트림 만들기 - 람다식 iterate(), generate()

- 람다식을 소스로 하는 스트림 생성

iterate는 종속적 , generate는 독립적

- iterate()는 이전 요소를 seed로 해서 다음 요소를 계산함.

Stream <Integer> evenStream = Stream.iterate(0, n->n+2); // 0, 2, 4, 6 ,...

n -> n+2면

0 -> 0+2

2 -> 2+2

4 -> 4+2

- generate()는 seed를 사용하지 않는다.

Stream <Double> randomStream = Stream.generate(Math::random);

Stream <Integer> oneStream = Stream.generate(()->1);

Stream<Integer> intStream = Stream.iterate(0, n -> n+2);
intStream.limit(10).forEach(System.out::println);

결과

Stream<Integer> oneStream = Stream.generate(()->1);
oneStream.limit(10).forEach(System.out::println);

결과

리미트가 없으면 계속 찍힘.

generate(Supplier s) : 입력은 없고 출력만 있음. 주기만 한다.


스트림 만들기 - 파일과 빈 스트림

- 파일을 소스로 하는 스트림 생성하기

Stream <Path> Files.list(Path dir) // Path는 파일 또는 디렉터리

 

Stream <String> Files.lines(Path path)

Stream<String> Files.lines(Path path, Charset cs)

Stream<String> lines() // BufferdReader클래스의 메서드

 

파일의 내용을 라인 단위로 읽어서 스트림에 넣어줄 수 있다.

 

- 비어있는 스트림 생성

Stream emptyStream = Stream.empty(); // empty()는 빈 스트림을 생성해서 반환한다.

long count = emptyStream.count(); // count의 값은 0

 

reachable : 도달 가능한 , 닿을 수 있는 

unreachable : 도달 할 수 없는

이 뜻을 먼저 알고 가면 좋을 것 같다.. ㅎㅎ


GC란 무엇인가?

JVM의 Heap 영역에서 사용하지 않는 객체를 삭제하는 프로세스를 말한다.

저번에 말했듯이 heap 영역에는 String,List 등 객체가 들어간다.

 

근데 어떻게 GC는 삭제할 객체와 삭제하지 않을 객체를 구분할까?

 

바로 GC Roots 에서 부터 하나씩 참조하고 있는 객체를 찾아나간다.

 

참조되고있는 객체들을 Reachable, 그렇지 않다면 Unreachable하다고 생각하면 된다.

 

그렇다면 GC Roots 가 될 수 있는 데이터는?

 

stack 영역의 데이터들

method 영역의 static 데이터들

JNI에 의해 생성된 객체들

 

어느 곳에서도 Unreachable한 객체는 GC의 수거 대상이 된다.


GC의 동작순서

기본적으로 Mark and Sweep으로 동작한다.

 

Mark

GC는 GC Root로부터 모든 변수를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.

식별하는 과정이라고 생각하면 됨.

Sweep

Unreachable 객체들을 Heap에서 제거하는 과정

Compact

알고리즘에 따라서 compact과정이 추가되기도 함.

Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 나눠준다.

 

이렇게 한 싸이클이 돈다고 생각하면 된다.


GC는 언제 일어날까?

Yong Generation : 새로운 객체들이 할당되는 영역

Old Generation : Young Generation에서 오랫동안 살아남은 객체들이 존재하는 영역

 

Young Generation은 Eden Survivor 0 Survivor 1 이렇게 세개로 나뉘어진다.

 

새로운 객체들이 Eden영역에 할당이 됩니다.

 

더 이상 할당이 되지 못하면 Minor GC 발생합니다! 그러면 Mark and Sweep 과정이 발생함.

 

리처블 언리처블 공간을 마킹하고 GC에서 살아남은 객체들은 Survivor 영역으로 이동한다.

 

※ Survivor 0에 객체가 들어있으면 1은 비워둬야 되고 1에 들어있으면 0은 비워져 있어야 한다.

 

Sweep의 과정을 통해서 Eden에 있는 언리처블한 객체들은 지워지고 살아남은 객체들은 Survivor 0으로 들어갔다고 가정하면 age 가 0살에서 1살로 올라간다.

 

그리고 다시 eden 영역에 꽉 차면 , 또 마이너 GC로 발생 이번에 살아남은 객체들은 Survivor 1 객체로 이동한다.

 

Minor GC 는 발생할때마다 Survivor 0,1 영역을 왔다 갔다함. 그렇게 해서 객체들은 2살 1살 1살 이렇게 나이를 먹는다.

 

계속 반복된다.

 

그러가 객체의 나이가 8살?(특정 age값,임계점 에 도달하면) Old Generation으로 이동하게 된다.

 

높은 객체들이(나이가 많은 객체들) Old Generation에 계속 들어가면 Major GC가 발생하게 된다.

 

Why? GC를 나눌까?
GC설계자들은

1. 대부분의 객체는 금방 접근 불가능한 상태(unreachable)가 된다.

2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.

 

마이너 GC는 메이저 GC 보다 많이 일어나고 그 만큼 메모리 낭비를 막을 수 있고

그렇기 때문에 영역을 eden sur0,1을 나눠놨기 떄문.


GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것

: GC를 실행하는 쓰레드 외의 모든 쓰레드가 작업을 중단한다.

GC의 종류

Serial GC

가장 단순하고 기본적인 GC.

- GC를 처리하는 쓰레드가 1개(싱글 쓰레드)

- 다른 GC에 비해 stop-the-worl 시간이 길다.

- Mark-Compact(Sweep 포함) 알고리즘 사용

 

Parallel GC

 

- Java 8의 default GC

- Young 영역의 GC를 멀티 쓰레드로 수행

- Serial GC에 비해 stop-the-world 시간 감소

 

Parallel Old GC

아까 Parallel GC와 똑같은데

- Parallel GC를 개선

- OLd 영역에서도 멀티 쓰레드 방식의 GC 수행

- Mark-Summary-Compact 알고리즘 사용

 sweep: 단일 쓰레드가 old 영역 전체를 훑는다.

 summary : 멀티 쓰레드가 old 영역을 분리해서 훑는다.

 

 

JAVA의 메모리 구조

 

JAVA는 JVM을 통해 실행되므로 운영체제 독립적 , 자동으로 메모리 관리 , 안정적이다.

 

이클립스를 켜고 코드를 작성한다. 

 

이 .java 코드는 javac(자바 컴파일러)가 바이트 코드로 바꿔준다

 

.java -> .class

 

이렇게 바이트 코드로 변경된 .class 파일이 Class Loader로 들어가게 된다.

 

Class Loder는 Runtime Data Area로 .class 파일을 로딩시켜준다.

 

Runtime Data Area는 5가지 영역으로 나눠진다.


Runtime Data Area

Method Area , Heap은 모든 스레드가 공유

Stack , PC Register , Native Method Stack은 스레드마다 하나씩 생성되는 공간

Method 영역은 JVM이 시작될때 생성되는 공간으로 바이트코드가 이 영역에 저장됨.

모든 클래스 정보, 변수 정보 들이 여기있고 모든 스레드(일꾼)들이 공유하는 영역

 

Heap 영역은 동적으로 생성된 객체가 저장되는 영역으로 GC(가비지 컬렉터)의 대상이 되는 공간

new 키워드(연산)을 통해서 생성된 객체가 저장됨.

 

Heap 영역은 효율적인 GC를 위해서 5가지 영역으로 나뉘었음.

힙의 5가지 영역

Stack 영역은 지역변수나 메서드의 매개변수, 임시적으로 사용되는 변수, 메서드의 정보가 저장되는 영역입니다.

 

PC Register은 우리가 일반적으로 알고 있는 컴퓨터를 말하는 것이 아니다.

스레드가 시작될때 생성되며, 현재 수행중인 JVM의 명령어 주소를 저장하는 공간.

스레드가 어떤 부분을 어떤 명령어로 수행할지를 저장하는 공간. 

 

Native Method Stack 영역은 JAVA가 아닌 다른 언어로 작성된 코드를 위한 공간.

바이트 코드가 아닌 실제 실행시키는 기계어로 작성된 파일을 실행시키는 영역


Execution Engine으로 Runtime Data Area를 해석할 차례

로드된 바이트코드의 .class파일을 실행시켜주는 엔진임(해석)

해석하려면 컴퓨터가 이해할 수 있는 기계어로 바꾸는게 필요한데

Interpreter는 명령어를 한줄한줄 해석하면서 실행, JIT Compiler는 인터프리터의 단점을 해결하기 위한 방법

런타임시간에 변경하여 실행합니다.

 

이제 이렇게 기계어로 해석된 것들이 스레드 동기화나 가비지 컬렉션을 실행하게 됨.

 

마지막으로 Native Method Interface(JNI)

JVM에 의해 실행되는 코드 중 네이티브로 실행하는 것이 있다면

해당 네이티브 코드를 호출하거나 호출 될 수 있도록 만든 일종의 프레임 워크

 

Native Method Libraries

네이트 메소드 실행에 필요한 라이브러리

 

- 자주 사용되는 다양한 함수형 인터페이스를 제공

 

우리가 사용하는 함수형 인터페이스는 사실상 몇 개 없다.

함수형 인터페이스 메서드 설명
java.lang.Runnable void run() 매개변수도 없고, 반환값도 없음.
Supplier<T> T get() 매개변수는 없고, 반환값만 있음.
Consumer<T> void accept(T t) Supplier와 반대로 매개변수만 있고,
반환값이 없음
Function<T,R> R apply(T t) 일반적인 함수, 하나의 매개변수를 받아서 결과를 반환
Predicate<T> boolean test(T t) 조건식을 표현하는데 사용됨.
매개변수는 하나, 반환 타입은 boolean

Predicate<T> 사용 방법

문자열의 길이를 받아서 참 길이가 0이면 참 , 아니면 거짓

Predicate<String> isEmptyStr = s -> s.length()==0;
String s = "";
if(isEmptyStr.test(s)) // if(s.length()==0)
	System.out.println("This is an empty String.");

함수형 인터페이스를 알맞게 넣어주세요.

[1] f = () -> (int)(Math.random()*100)+1;
[2] f = i -> System.out.print(i+", ");
[3] f = i -> i%2 == 0;
[4] f = i -> i/10*10;

 

 

 

내가 적은 정답

[1] = Supplier

[2] = Runnable ( 임포트하고 )

[3] = Predicate

[4] = ?

 

정답

[1] = Supplier<Integer> // 제네릭 타입 맞아야 하므로 Integer 적어줘야 함.

[2] = Consumer // 값을 받기만 하므로 consumer임 매개변수 i 있는데 Runnable은 매개변수도 없고, 반환 값도 없음.

[3] = Predicate<Integer> // 역시 제네릭 포함으로 적어줘야 함. 결과를 true 아니면 false로 주기 때문.

[4] = Function<Integer,Intger> // 입력 값도 있고, 출력 값도 있음 25를 넣으면 20을 반환해 줌.

 


매개변수가 2개인 함수형 인터페이스

함수형 인터페이스 메서드 설명
BiConsumer<T,U> void accept(T t, U u) 두개의 매개변수만 있고,
반환값이 없음
BiPredicate<T,U> boolean test(T t, U u) 조건식을 표현하는데 사용됨,
매개변수는 둘, 반환값은 boolean
BiFunction<T,U,R> R apply(T t, U u) 두 개의 매개변수를 받아서 하나의
결과를 반환

매개변수는 2개까지 받을 수 있고 3개를 받으려고 하면 직접 구현

@FunctionalInterface
interface TriFunction<T,U,V,R> {
	R apply(T t, U u, V V);
}

매개변수의 타입과 반환 타입이 일치하는 함수형 인터페이스

함수형 인터페이스 메서드 설명
UnaryOperator<T> T apply(T t) Function의 자손, Function과 달리
매개변수와 결과의 타입이 같다.
BinaryOperator<T> T apply(T t, T t) BiFunction의 자손, BiFunction과
달리 매개변수와 결과의 타입이 같다.

예 제

package lamdaPt;

import java.util.*;
import java.util.function.*;

public class lamda3 {

	public static void main(String[] args) {
		Supplier<Integer> s = ()-> (int)(Math.random()*100)+1; // 1~100 난수
		Consumer<Integer> c = i -> System.out.print(i+", ");
		Predicate<Integer> p = i -> i%2==0;
		Function<Integer, Integer> f = i -> i/10*10; // i의 일의 자리를 없앤다.
		
		List<Integer> list = new ArrayList<>();
		makeRandomList(s, list);
		System.out.println(list);
		
		// 짝수를 출력
		printEvenNum(p, c, list);
		List<Integer> newList = doSomething(f, list);
		System.out.println(newList);
	}

	static <T> void makeRandomList(Supplier<T> s, List<T> list) {
		for(int i=0; i<10; i++) {
			list.add(s.get());
		}
	}

	//짝수인지 검사 Predicate, 어떻게 출력 Consumer, list에 담아 줌.
	static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
		System.out.print("[");
		for(T i : list) {
			if(p.test(i)) // 짝수인지 검사
				c.accept(i); // 짝수면 Consummer 출력
		}
		System.out.println("]");
	}
	
	// 1의 자리 없애는 람다식 Function 그리고 list를 받아왔음
	static <T> List<T> doSomething(Function<T, T> f, List<T> list){
		List<T> newList = new ArrayList<T>(list.size());
		
		for(T i : list) {
			newList.add(f.apply(i));
		}
		
		return newList;
	}
}

 

함수형 인터페이스

- 함수형 인터페이스 : 단 하나의 추상 메서드만 선언된 인터페이스

- 함수형 인터페이스 타입의 참조 변수로 람다식을 참조할 수 있음.

@FunctionalInterface // 함수형 인터페이스는 단 하나의 추상 메서드만 가져야 함.
interface MyFunction {
	public abstract int max (int a , int b);
}

@FunctionalInterface 사용하면 메서드 2개 사용 못함, 함수형 인터페이스는 단 하나의 추상 메서드만 가져야 하기 때문.

 

package lamdaPt;

public class lamda1 {

	public static void main(String[] args) {
//		Object obj = (a, b) -> a > b ? a : b; // 람다식. 익명 객체
		MyFunction f = new MyFunction() {
			public int max(int a, int b) {
				return a > b ? a : b;
			}
		};
		
		int value = f.max(3,5); // 함수형 인터페이스
		System.out.println(value);
	}
		
}

@FunctionalInterface // 함수형 인터페이스는 단 하나의 추상 메서드만 가져야 함.
interface MyFunction {
	public abstract int max (int a , int b);
}

함수형 인터페이스 MyFunction을 이용해서 익명 객체를 만들어 오버라이딩 해서 사용 가능

 

value = 5가 찍힌 것을 볼 수 있음.

 

이것을 짧게 만들기 위해서 람다식을 사용해서 바꿔본다 !

 

package lamdaPt;

public class lamda1 {

	public static void main(String[] args) {
		// 람다식(익명 객체)을 다루기 위한 참조변수의 타입은 함수형 인터페이스로 한다.
		MyFunction f = (a, b) -> a > b ? a : b;
		int value = f.max(3,5); // 함수형 인터페이스
		System.out.println(value);
	}
		
}

@FunctionalInterface // 함수형 인터페이스는 단 하나의 추상 메서드만 가져야 함.
interface MyFunction {
	public abstract int max (int a , int b);
}

람다식 메서드 이름을 지워놨는데, 실제로 호출하려면 이름이 있어야 함.

 

함수형 인터페이스에 가보면 int max 이름이 있으므로 호출이 가능함.


함수형 인터페이스 - example

- 익명 객체를 람다식으로 대체

List<String> list = Arrays.asList("abc", "aaa", "bbb", "ddd", "aaa");

Collections.sort(list,new Comparator<String>(){
	public int compare(String s1, String s2){
        return s2.compareTo(s1);	
    }
});

이것도 너무 길어서 Comparator에 가보면 

@FunctionalInterface // 이게 붙어있다. 그래서 람다식 사용 가능 !
interface Comparator<T> {
	int compare(T o1, T o2);
}
List<String> list = Arrays.asList("abc", "aaa", "bbb", "ddd", "aaa");

Collections.sort(list,(s1,s2)-> s2.compareTo(s1));

Comparetor가 함수형 인터페이스 이므로 이렇게 바꿀 수 있다.


마지막 정리

package lamdaPt;

@FunctionalInterface
interface MyFunction2{
	void run(); // 인터페이스 추상 메서드이므로 public static 생략됨!
}

public class lamda2 {
	static void execute(MyFunction2 f) {
		f.run();
	}
	
	static MyFunction2 getMyFunction() {
		return () -> System.out.println("f3.run()");
	}
	
	public static void main(String[] args) {
		// 람다식으로 MyFunction의 run()을 구현
		MyFunction2 f1 = ()-> System.out.println("f1.run()");
		
		MyFunction2 f2 = new MyFunction2() { // 익명클래스로 run()을 구현
			public void run() { // public 안붙이면 default가 기본값인데 조상님이 public이므로 붙여줘야 에러가 나지 않습니다 ★
				System.out.println("f2.run()");
			}
		};
		
		MyFunction2 f3 = () -> getMyFunction();
		
		f1.run();
		f2.run();
		f3.run();
		
		execute(f1);
		execute(()-> System.out.println("run()"));
	}

}

첫 번째 f1은 람다식으로 MyFunction2 함수형 인터페이스의 run()을 구현한 것.

 

두 번째 f2는 익명 클래스로 run()을 구현한 것, 오버 라이딩 필수! 접근 제어자 확인!

 

세 번째 f3는 getMyFunction 메서드를 가지고 오는데 함수형 인터페이스에 접근해서 run을 구현해주는 것

 

마지막 execute는 람다식을 매개변수로 받아서 , 람다식과 주고받는 방법을 보여주는 것

+ Recent posts