본문 바로가기
국비지원/JAVA

[JAVA] 23. Nested Class / Anonymous / Lambda / Stream API / Thread

by cosmog 2022. 10. 28.
반응형
Nested Class (내포 클래스)
  • 클래스 안에 존재하는 클래스
  • 자바는 클래스 안에 클래스를 생성하는 문법을 지원한다.

1. Inner Class

  • 클래스 안에 존재하는 클래스
  • 클래스 내부에서만 사용할 목적으로 생성
  • 일반 클래스는 접근 지정자가 package(접근 지정자가 없는 경우)와 public만 가능하지만 inner class에서는 private과 protected도 가능하다.
  • 클래스가 컴파일 되었을 때는 외부클래스이름$내부클래스이름.class로 만들어진다.


2. Static inner Class

  • class 앞에 static을 붙이는 클래스
  • 내포 클래스에 static 멤버가 있으면 일반 inner class는 에러가 발생한다.
  • 내포 클래스에 static 멤버가 있는 경우는 인스턴스 생성없이 사용할 수 있도록 static을 붙여주어야 한다.
  • static이 붙은 클래스는 인스턴스 생성 없이 사용가능하다.

 

3. Local inner Class

  • method안에 만들어지는 내포 클래스
  • method안에서만 사용이 가능하다.

Inner / StaticInner / LocalInner의 사용

package com.eb1028.nestedclass;

public class Outer {
	//내포 클래스 - 다른 클래스 안에 만들어진 클래스
	class Inner{
		public int num;
	}
	
	//내포 클래스 안에 static 멤버가 있으면 인스턴스 생성없이 사용할 수 있도록 static을 추가
	static class StaticInner{
		public int num;
		public static int share;
	}
	
	public void method() {
		//method안에 만들어진 class = Local Inner
		//method안에서만 사용이 가능한 클래스
		class LocalInner{
			public int num;
		}
	}
	
}


4. Annonymous Class

  • 이름 없는 클래스 또는 객체
  • 인터페이스를 구현하거나 클래스를 상속 받아야 할 때 별도의 클래스를 만들지 않고 필요한 메서드만 재정의해서 사용하는 문법.
  • 이벤트 처리 할 때 많이 사용한다.
  • 이 경우에 인터페이스 안에 메서드가 한개인 경우는 람다 표현식으로 작성하는 것이 가능하다.
package com.eb1028.nestedclass;

import java.util.Arrays;
import java.util.Comparator;

//method가 1개인 인터페이스
interface Sample{
	//추상 method 선언
	public void display();
}

//인터페이스를 구현한 클래스
class SampleImpl implements Sample{
	@Override
	public void display() {
		System.out.println("클래스를 만들어서 사용");
	}
}

public class AnonymousMain {
	
	public static void main(String[] args) {
		//인터페이스를 구현한 클래스의 인스턴스를 생성해서 매서드 호출
		//인스턴스를 여러개 만들어야 한다면 클래스를 만드는 것이 효율적
		Sample sample = new SampleImpl();
		sample.display();

		//Sample 인터페이스를 anonymous로 사용
		//인스턴스가 1개만 필요하다면 클래스를 만들지 않는 것이 효율적
		new Sample() {
			@Override
			public void display() {
				System.out.println("anonymous");
			}
		}.display();
		
		//배열의 정렬
		String[] ar = {"펄", "노트북", "고양이보은", "인사이드아웃", "미드소마"};
		
		//배열의 내림차순 정렬
		//Arrays.sort(배열, 비교를 위한 Comparator<T> 인터페이스를 구현한 클래스의 객체를 호출해야한다.
		//comparator는 method가 1개만 존재한다.(lambda가능)
		Arrays.sort(ar, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				//String이라 연산을 할수 없다. compareTo 사용.
				return o1.compareTo(o2);
			}
		});
		
		//배열의 요소를 빠르게 확인.
		System.out.println(Arrays.toString(ar));
		
	}
}

 

Anonymous
  • 작성하는 방법을 알아두어야 한다.
  • 인터페이스를 구현하거나 클래스를 상속받아서 사용을 하고자 하는 경우에 별도의 클래스를 생성하지 않고 사용하는 방법
  • 상속이나 구현을 하는 클래스를 만들지 않고 활용
new 인터페이스 이름이나 상위클래스이름( 매개변수 나열 ){
	필요한 매서드 정의
};

이런식으로 사용한다.

Anonymous 사용

		new Sample() {
			@Override
			public void display() {
				System.out.println("anonymous");
			}
		}.display();
		Arrays.sort(ar, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				//String이라 연산을 할수 없다. compareTo 사용.
				return o1.compareTo(o2);
			}
		});

 

Lambda(람다)

1. 개요

  • jdk 1.7부터 지원하는 함수형 프로그래밍을 위해서 추가
  • java는 함수형 프로그래밍을 지원하지 않는데 함수형 프로그래밍을 한것처럼 보이도록 하게 해주는 기능을 위한 것.
  • 이름 없는 함수를 만들기 위해서 사용한다.
  • 익명 객체를 만들어서 사용하던 것을 조금 더 간결하게 함수를 대입하는 형태처럼 보이도록 하기 위해서 사용한다.
  • java에서의 한계 : method가 1개만 존재하는 인터페이스에서만 사용이 가능하다.


2. 작성방법

(매개변수 나열) -> {내용을 작성}
  • 매개변수를 작성할 때는 자료형과 매개변수 이름을 작성하는데 매개변수 자료형은 생략이 가능하다.
  • 자료형을 생략하면 호출할 떄 자료형을 결정한다.
  • 매개변수가 1개인 경우는 ()를 생략가능하다. 매개변수가 없거나 2개 이상인 경우 생략 불가능하다.
  • 매서드에 return type이 있다면 return 만들면 된다.
  • 중괄호 안에 return 하는 문장만 있다면 {}와 return을 생략하는 것이 가능하다.
package com.eb1028.lambda;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;

//method가 1개인 인터페이스
interface Sample{
	//추상 method 선언
	public void display();
}

//인터페이스를 구현한 클래스
class SampleImpl implements Sample{
	@Override
	public void display() {
		System.out.println("클래스를 만들어서 사용");
	}
}

public class LambdaMain {
	
	public static void main(String[] args) {
		//인터페이스를 구현한 클래스의 인스턴스를 생성해서 매서드 호출
		//인스턴스를 여러개 만들어야 한다면 클래스를 만드는 것이 효율적
		Sample sample = new SampleImpl();
		sample.display();

		//Sample 인터페이스를 anonymous로 사용
		//인스턴스가 1개만 필요하다면 클래스를 만들지 않는 것이 효율적
		new Sample() {
			@Override
			public void display() {
				System.out.println("anonymous");
			}
		}.display();;
		
		//배열의 정렬
		String[] ar = {"펄", "노트북", "고양이보은", "인사이드아웃", "미드소마"};
		
		//배열의 내림차순 정렬
		//Arrays.sort(배열, 비교를 위한 Comparator<T> 인터페이스를 구현한 클래스의 객체를 호출해야한다.
		//comparator는 method가 1개만 존재한다.(lambda가능)
		Arrays.sort(ar, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				//String이라 연산을 할수 없다. compareTo 사용.
				return o1.compareTo(o2);
			}
		});
		
		/* 자료형 생략가능
		Arrays.sort(ar, (String o1,String o2) ->{
			o2.comparTo(o1);
		});
		*/
		
		/* {} 와 return 생략가능 */
		Arrays.sort(ar, (o1,o2) -> o2.compareTo(o1));
		/* {}와 return 둘다 생략 안함 (두개는 세트임)*/
		Arrays.sort(ar, (o1,o2) -> {
			return o2.compareTo(o1);
		});
		
		//Comparator 인터페이스는 메서드가 1개 밖에 없으므로 람다로 표현하는 것이 가능
		//람다를 만들 때는 인터페이스 이름과 메서드 이름은 중요하지 않음
		//매개 변수의 개수와 리턴 타입만 확인하면 됩니다.
		//매개변수는 2개이고 리턴 타입은 정수이다.
		//return 하는 문장만 존재한다면 {}와 return 을 생략하는 것이 가능하다.
		//메서드의 매개변수로 코드(함수)를 대입한 것 처럼 보이도록 함.
		//메서드의 매개변수로 코드(함수)를 대입할 수 있는 방식을 함수형 프로그래밍이라고 함.

		//배열의 요소를 빠르게 확인.
		System.out.println(Arrays.toString(ar));
		
	}
}

 

JAVA 가 제공하는 Lambda 인터페이스
  1. Consumer : 매개변수 O , return X method를 소유한 인터페이스
  2. Supplier : 매개변수 X , return O method를 소유한 인터페이스
  3. Funtiona : 매개변수 O , return O method를 소유한 인터페이스
  4. Operator : 매개변수 O , return X method를 소유한 인터페이스인데 이 인터페이스는 일반적으로 메서드 내부에서 연산 작업 수행
  5. Perdicate : 매개변수 O , return boolean인 method를 소유한 인터페이스
package com.eb1028.stream;

import java.util.ArrayList;

public class LoopingMain {
	
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<>();
		list.add("프로그래밍 언어");
		list.add("데이터베이스");
		list.add("프레임워크");
		list.add("소프트웨어 공학");
		list.add("Toy Project");
		
		//전체 데이터 출력 - 실행 속도는 가장 빠르지만 list의 데이터 개수가 변경되면 수정 필요.
		/* 방법1 */
		System.out.println(list.get(0));
		System.out.println(list.get(1));
		System.out.println(list.get(2));
		System.out.println(list.get(3));
		System.out.println(list.get(4));
		
		
		//반복문 이용 - list의 데이터 개수를 이용해서 순회를 하면 list의 데이터 개수가 변경되어도 수정 필요 없음.
		/* 방법2 - list.size()를 6번 호출하게 됨.(stack을 6번 만들게 됨) */
		for(int i = 0; i < list.size(); i++) {
			System.out.println(list.get(i));
		}
		
		//변하지 않는 메서드의 호출 결과는 반복문에서 여러번 호출하는 것은 자원의 낭비
		/* 방법2 upgrade */
		int len = list.size(); //stack사용이 적음, 변수를 불러오는것이 훨씬 빠름
		for(int i = 0; i < len; i++) {
			System.out.println(list.get(i));
		}
		
		//모든 데이터를 순회하는 경우라면 빠른 열거를 이용하는 것이 효율적
		/* 방법3 */
		for(String job : list) {
			System.out.println(job);
		}
		
		//빠른 열거는 반복자를 외부에 만들어서 사용하는데 Stream API는 내부 반복자를 사용
		//데이터가 많을 때 효율적이다.
		/* 방법4 - Stream API 사용 (안에는 람다를 사용) */
		list.stream().forEach(subject -> {
			System.out.println(subject);
		});
	}

}

 

Stream API

1. 개요

  • Collection 데이터를 다룰 때 내부 반복자를 만들어서 접근하기 위한 API
  • 내부 반복자를 이용해서 접근하면 반복문이나 빠른 열거를 사용하는 것보다 빠르게 작업을 수행할 수 있다.
  • How보다 What
    • 작업의 과정 : 생성 > 중간작업 > 최종작업
    • 중간작업은 여러개를 묶어도 된다.
    •  
    • 생성은 배열이나 List를 가지고 수행하고,
    • 중간작업은 스트림의 데이터를 순회하면서 작업을 수행해서 다시 스트림을 리턴하기 떄문에 다른 중간 작업을 연속해서 배치하는 것이 가능하다.
    • 최종작업은 1번만 수행 가능하다. 집계를 수행하는 함수(count, average, sum등..)나 forEach처럼 하나씩 순회하면서 작업을 수행하기 위한 함수 또는 collect 처럼 배열이나 List를 생성해주는 함수를 사용한다.


2. 스트림 생성

  1. Collection 인터페이스로부터 상속받은 객체는 stream()이나 parallelStream()을 호출, parallelStream()을 호출하면 병렬 처리가 가능한 스트림
  2. 배열의 경우는 Arrays.stream(배열)을 이용해서 생성
  3. 일련변호 형태의 정수 스트림은 intStream.range(start, end)나 rangeClosed(start, end)를 이용해서 생성하는 것이 가능하다.

 

3. 중간작업 - 작업을 수행한 후 Stream을 return

  1. distinct() :중복을 제거
  2. filter(매개변수를 1개 받아서 Boolean을 리턴하는 람다) : 람다가 true를 리턴하는 데이터만 모아서 스트림을 생성
  3. map(매개변수 1개 받아서 리턴하는 람다) : 리턴하는 데이터를 모아서 새로운 스트림을 리턴
  4. sorted(비교처리를 수행해주는 람다) : 람다를 기준으로 정렬
  5. skip(long n) : n만큼 건너뜀
  6. limit(long n) : n개 만큼 추출


4. 최종연산

count() : 데이터 개수 리턴 
max() : 최대값 리턴 
min() : 최솟값 리턴 
average() : 평균 리턴 
forEach() : 데이터를 순회하면서 작업 
collect() : 데이터를 모아서 다른 자료형으로 변경

 

  • 계산에 의해서 나오는 max, min, average 는 리턴 타입이 Optional입니다.
  • Optional은 isPresent()로 데이터의 존재 여부를 확인할 수 있고 get()으로 실제 데이터를 가져올 수 있습니다.
package com.eb1028.ArrayList;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.OptionalDouble;

public class StreamMain {
	
	public static void main(String[] args) {
		//숫자 형태의 문자열 리스트
		ArrayList<String> list = new ArrayList<>();
		list.add("28");
		list.add("2");
		list.add("3");
		list.add("6");
		list.add("5");
		list.add("9");
		
		//최종 연산을 이용해서 출력
		//forEach는 매개변수 1개를 받고 리턴이 없는 매서드를 매개변수로 받음
		//Collection의 모든 데이터를 매개변수에 대입해서 내용을 수행
		
		//list안의 데이터를 순차적으로 e에 대입해서 {}안의 내용을 수행
		//최종 작업만 수행해서 데이터 출력
		list.stream().forEach(e -> {System.out.println(e);});
		
		//데이터 3개만 출력
		list.stream().limit(3).forEach(e -> {System.out.println(e);});
		
		//데이터 정렬 후 출력
		list.stream().sorted().forEach(e -> {System.out.println(e);}); //오름차순
		
		// 내림차순 정렬
		//sorted 메서드에 내림차순 정렬을 위한 Comparator 인터페이스를 구현한 클래스의 객체를 설정하면 된다.
		//Comparator 인터페이스는 매개 변수가 2개이고 정수를 리턴하는 메서드 1개만 존재한다.
		
		/* 짧게 쓰는 방법 - method가 하나인경우 가능 */
		list.stream().sorted((o1,o2) -> o2.compareTo(o1)).forEach(e -> {System.out.println(e);}); 
		
		/* 정석의 방법 */
		list.stream().sorted(new Comparator<String>() {
			public int compare(String o1, String o2) {
				return o2.compareTo(o1);
			}
		}).forEach(e->{System.out.println(e);});
		
		
		//데이터를 정수로 변환해서 정렬
		//중간처리 메서드 중에는 Int로 리턴해주는 mapToInt라는 메서드가 존재하고
		//이 메서드를 사용할 때는 변환에 사용하는 메서드를 설정만 해주면 된다.
		//클래스이름::메서드 이름
		//문자열을 정수로 변환해서 합계 구하기
		
		
		int result = list.stream().mapToInt(Integer::parseInt).sum();
		System.out.println(result);
		
		
		//홀수의 합
		//filter : 조건에 맞는 데이터만 추출
		//조건에 맞는 추출하고자 할 때는 하나의 매개변수를 받아서 boolean을 리턴하는 람다를 만들어서 대입해주면 된다.
		int result1 = list.stream().mapToInt(Integer::parseInt).filter(a -> a % 2 == 1 ).sum(); // 홀수의 합 추출
		System.out.println(result1);
		
		//홀수의 평균
		//일반적으로 생각하게이는 평균의 결과가 정수나 실수가 나와야 하는데 자바에서는 OptionalDouble이 된다. ** 
		//Optional이 붙으면 null을 저장할 수 있는 자료형이 된다. 
		//isPresent라는 메서드를 이용해 null여부를 판단하고 get이라는 메서드로 데이터를 가져온다.
		OptionalDouble result2 = list.stream().mapToInt(Integer::parseInt).filter(a -> a % 2 ==1).average();
		
		// 연산을 정상적으로 수행한 경우
		if(result2.isPresent()) {
			System.out.println(result2.getAsDouble());
		}else {
			System.out.println("평균 계산 실패 - 아마도 데이터가 없는것 같음");
		}
		System.out.println(result);
		
		//숫자의 경우는 크기 비교가 가능해서 별도의 인스턴스를 대입하지 않아도 정렬이 되고
		//매림차순을 하고자 하는 경우 reverse 옵션 설정해주면 된다.
		list.stream().map(Integer::parseInt).sorted(Comparator.reverseOrder()).forEach(e -> System.out.println(e));
	}

}

 

Thread

프로그래밍에서 가장 중요한 개념 중의 하나인데, 실제 생성해서 사용하는 경우는 안드로이드나 자바 애플리케이션을 만들 떄 이고 Web Programming에서는 직접 생성해서 사용하는 경우가 드물다.

1. 작업단위

1) Process : 실행 중인 프로그램
프로세서를 할당받아서 실행되는 것
한 번 실행되면 자신의 작업이 종료될 때까지 제어권을 다른 프로세스에게 넘기지 않고 계속 수행된다.

2) Thread : Process를 작게 나누어서 작업을 수행하는 단위
Thread는 단독으로 실행될 수 없고 Process안에서 실행되어야 한다.
자신의 작업 도중 쉬는 시간이 생기거나 일정한 시간이 지나면 다른 스레드에게 제어권을 양도할 수 있습니다.

2. Thread Programming을 할 때 반드시 알아야 될 사항
1) 하나의 스레드가 사용 중인 자원은 다른 스레드가 수정하면 안된다. 
Mutual Exclusion (상호배제), Synchronus(동기화)

2) 생상자와 소비자 문제
소비자는 생산자가 물건을 생성해 주어야만 작업을 수행한다.

3) Dead Lock
결코 발생할 수 없는 사건을 무한정 기다리는 것.

3. Java에서 Thread를 생성하는 기본적인 방법 
1) Thread 클래스로 부터 상속받는 클래스를 만들고 Public void run이라는 method안에 스레드로 수행할 내용을 작성한 후 인스턴스를 만들고 start method로 호출해주면된다.

2) Runnable인터페이스로 부터 상속받는 클래스를 만들고 public void run이라는 메서드에 스레드로 수행할 내용을 작성한 후 Thread 클래스의 신스턴스를 만들 때 생성자에 생성한 클래스의 인스턴스를 대입하고 Thread 클래스의 인스턴스가 start method를 호출하면된다.

3) Callable 인터페이스를 구현한 클래스를 이용해서도 생성 가능합니다

4. Thread.sleep (long msec[,long nano])

  • []는 있을 수도 있고 없을 수도 있다.
  • msec 밀리초 동안 현재 스레드를 중지.
  • nano를 입력하면 msec 밀리초 + nano 나노초 만큼 대기.
  • 이 메서드를 사용할 때는 InterrupedException을 처리해주어야 합니다.
  • 실습을 할 떄 이 메서드를 사용하는 이유는 일정 시간 이상 대기를 해야만 다른 스레드에게 제어권이 이동되기 때문입니다. 실제 시간의 의미를 거의 없습니다.
package com.eb1028.thread;

//Thread 클래스로 부터 상속받는 클래스를 생성 (class 상속받기 extends)
class ThreadEx extends Thread{
	@Override //요거는 어노테이션이라고 부른다 : 상위 클래스나 인터페이스에서 제공하는 메서드가 아닌 경우 에러를 발생시켜 주는 어노테이션이다.
	public void run() {
		//Thread로 수행할 내용
		//1초마다 Thread 클래스라는 문장을 10번 출력
		for(int i = 0; i < 10; i++) {
			try {
				Thread.sleep(1000);
				System.out.println("Thread Class");
			} catch (Exception e) {
				System.out.println(e.getLocalizedMessage());
			}
		}

	}
}

//Runnable 인터페이스를 구현한 클래스를 생성 (인터페이스 상속받기 implements)
class RunnableImpl implements Runnable{ 
	@Override
	public void run() {
		for(int i = 0; i < 10; i++) {
			try {
				Thread.sleep(1000);
				System.out.println("Runnable Interface");
			} catch (Exception e) {
				System.out.println(e.getLocalizedMessage());
			}
		}
	}	
}


public class ThreadCreate{

	public static void main(String[] args) {
		//Thread 클래스로부터 상속받은 클래스를 이용해서 스레드를 생성하고 실행
		Thread th1 = new ThreadEx();
		//start를 호출하면 run 메서드의 내용을 수행
		th1.start();

		//Runnable 인터페이스를 implements 클래스를 이용해서 스레드를 생성하고 실행
		Thread th2 = new Thread(new RunnableImpl());
		th2.start();

		//Runnable 인터페이스를 Anonymous Class를 이용해서 사용
		Thread th3 = new Thread(new RunnableImpl() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					try {
						Thread.sleep(1000);
						System.out.println("Anonymous 활용");
					} catch (Exception e) {
						System.out.println(e.getLocalizedMessage());
					}
				}
			}
		});
		th3.start();

		//Runnable 인터페이스를 Public void run 메서드 1개만 소유 즉, 람다로 가능
		//람다를 이용해서 작성하는 것도 가능
		Thread th4 = new Thread(() -> {
			for(int i = 0; i < 10; i++) {
				try {
					Thread.sleep(1000);
					System.out.println("Lambda 활용");
				} catch (Exception e) {
					System.out.println(e.getLocalizedMessage());
				}
			}
		});
		th4.start();
		
	}
}

 

 Daemon Thread 
  • 다른 Thread가 수행중이 아니면 자동으로 종료되는 스레드
  • 스레드의 역할을 도와주는 보조적인 목적으로 사용
  • start 하기 전에 setDaemon 메서드를 호출하면 

package com.eb1028.thread;

public class DaemonThread {
	
	public static void main(String[] args) {
		
		//1부터 10까지를 1초씩 딜레이하면서 출력해주는 스레드.
		Thread th = new Thread(() -> {
			for(int i = 0; i < 10; i++) {
				try {
					Thread.sleep(1000);
					System.out.println(i);
				} catch (Exception e) {
					System.out.println(e.getLocalizedMessage());
				}
			}
		});
		
		//데몬 스레드로 설정 - 다른 작업이 없으면 자동을 종료
		th.setDaemon(true); //내거를 하다가 다른 애들이 다 끝나면 알아서 죽어버림.
		th.start();
		
		try {
			Thread.sleep(3000);
			System.out.println("메인 종료");
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
	}

}

 

Thread의 Priority(우선순위)
  • 여러개의 스레드가 수행될 떄 어떤 스레드가 먼저되고 어떤 스레드가 나중에 수행될지는 알수 없다.
  • 우선 순위를 설정하면 확률적으로 먼저 또는 자주 실행되도록 할 수있다. (반드시는 아니다!!)
  • setPriority method를 이용해서 설정한다.
setPriority(int priority)


매개변수가 정수이지만 아무 정수나 사용하는 것은 안되고, 제약이 있다.
일반 정수를 사용해도 에러는 발생하지 않지만 되도록이면 Thread.MAX_PRIORITY, NOMAL, MIN의 상수를 사용하는 것을 권장한다.(높음,보통,낮음 순)

<정수를 사용하지 않고 상수를 사용해야 하는 이유>
💡 WORA (한번만 작성하면 모든 플랫폼에서 동작가능 , 자바의 장점)
=> 정수를 사용하게 되면 다른 OS에 가면 오동작할 확률이 있다. 상수를 활용하면 OS가 바뀌어도 알아서 바뀌어져서 들어가게된다.

반응형