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 인터페이스
- Consumer : 매개변수 O , return X method를 소유한 인터페이스
- Supplier : 매개변수 X , return O method를 소유한 인터페이스
- Funtiona : 매개변수 O , return O method를 소유한 인터페이스
- Operator : 매개변수 O , return X method를 소유한 인터페이스인데 이 인터페이스는 일반적으로 메서드 내부에서 연산 작업 수행
- 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. 스트림 생성
- Collection 인터페이스로부터 상속받은 객체는 stream()이나 parallelStream()을 호출, parallelStream()을 호출하면 병렬 처리가 가능한 스트림
- 배열의 경우는 Arrays.stream(배열)을 이용해서 생성
- 일련변호 형태의 정수 스트림은 intStream.range(start, end)나 rangeClosed(start, end)를 이용해서 생성하는 것이 가능하다.
3. 중간작업 - 작업을 수행한 후 Stream을 return
- distinct() :중복을 제거
- filter(매개변수를 1개 받아서 Boolean을 리턴하는 람다) : 람다가 true를 리턴하는 데이터만 모아서 스트림을 생성
- map(매개변수 1개 받아서 리턴하는 람다) : 리턴하는 데이터를 모아서 새로운 스트림을 리턴
- sorted(비교처리를 수행해주는 람다) : 람다를 기준으로 정렬
- skip(long n) : n만큼 건너뜀
- 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가 바뀌어도 알아서 바뀌어져서 들어가게된다.
'국비지원 > JAVA' 카테고리의 다른 글
| [JAVA] minimini project (pc방 키오스크 프로그램 만들기 - 미완) (0) | 2022.11.07 |
|---|---|
| [JAVA] 24. Multi-Thread(multi-thread 사용시 고려할 점)와 정규표현식 (0) | 2022.10.31 |
| [JAVA] 22. API - I/O (InputStream/OutputStream) (0) | 2022.10.27 |
| [JAVA] 21. List(stack, queue, deque) / Set / Map (0) | 2022.10.26 |
| [JAVA] 21-2. API - java.util pkg (컬렉션 프레임워크) (0) | 2022.10.25 |