|
Back-End/Java 2019. 4. 25. 01:50
- 서버소켓 채널과 소켓 채널의 용도 -
이 두 채널은 IO의 ServerSocket과 Socket에 대응되는 클래스로, IO가 버퍼를 사용하지 않고
블로킹 입출력 방식만 지원한다면 ServerSocketChannel, SocketChannel은 버퍼를 이용하고 블로킹과 넌블로킹 방식을 모두 지원한다.
사용방법은 ServerSocketChannel은 클라이언트 SocketChannel의 연결 요청을 수락하고 통신용 SocketChannel을 생성한다.
서버를 개발하려면 ServerSocketChannel 객체를 얻어야 한다.
ServerSocketChannel은 정적 메소드인 open( )으로 생성하고, 블로킹 방식으로 동작시키기 위해 configureBlocking(true)
메소드를 호출한다.
기본적으로 블로킹 방식으로 동작한다.
포트에 바인딩하기 위해서는 bind( ) 메소드가 호출되어야 한다.
포트 정보를 가진 InetSocketAddress 객체를 매개값으로 주면 된다.
포트 바인딩까지 끝났다면 ServerSocketChannel은 클라이언트 연결 수락을 위해 accept( ) 메소드를 실행해야 한다.
accept( ) 메소드는 클라이언트가 연결 요청을 하기 전까지 블로킹되기 때문에 UI 및 이벤트를 처리하는 스레드에서 accept( ) 메소드를
호출하지 않도록 한다.
클라이언트가 연결 요청을 하면 accept( )는 클라이언트와 통신할 SocketChannel을 만들고 리턴한다.
블로킹이란?
스레드가 대기 상태가 된다는 뜻이다.
바인딩이란?
컴퓨터 프로그래밍에서 각종 값들이 확정되어 더 이상 변경할 수 없는 구속(bind) 상태가 되는 것
포트란?
모뎀과 컴퓨터 사이에 데이터를 주고받을 수 있는 통로
|
SocketChannel socketChannel = serverSocketChannel.accept( );
|
연결된 클라이언트의 IP와 포트 정보를 알고 싶다면 SocketChannel의 getRemoteAddress( ) 메소드를 호출해서
SocketAddress를 얻으면 된다. 실제 리턴되는 것은 InetSocketAddress 인스턴스이므로 타입 변환할 수 있다.
InetSocketAddress socketAddress = (InetSocketAddress) socketChannel.getRemoteAddress( );
|
InetSocketAddress에는 다음과 같이 IP와 포트 정보를 리턴하는 메소드들이 있다.
리턴 타입 |
메소드명(매개 변수) |
설명 |
String |
getHostName( ) |
클라이언트 IP 리턴 |
int |
getPort( ) |
클라이언트 포트 번호 리턴 |
String |
toString( ) |
"IP : 포트번호" 형태의 문자열 리턴 |
더 이상 클라이언트를 위해 연결 수락이 필요 없다면 ServerSocketChannel의 close( ) 메소드를 호출해서 포트를 언바인딩 시켜야 한다.
그래야 다른 프로그램에서 해당 포트를 재사용할 수 있다.
serverSocketChannel.close( );
|
- 예제 및 출력 결과 -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 |
package com.hs.chap19;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class ServerExample {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
try {
serverSocketChannel = ServerSocketChannel.open(); // open() 메소드를 사용해 서버 소켓 채널 얻기
serverSocketChannel.configureBlocking(true); // 블로킹 방식으로 동작시키기 위해 호출한다. 이걸 안써도 기본적으로 블로킹 방식으로 동작하지만 //명시적으로
// 설정하는 이유는 넌블로킹과 구분하기 위해서이다.
serverSocketChannel.bind(new InetSocketAddress(5001)); // bind()메소드의 매개값으로 5001번 포트를 줘서 바인딩 시킨다.
while (true) {
System.out.println("[연결 기다림]");
SocketChannel socketChannel = serverSocketChannel.accept(); // 연결 수락을 위해 accept() 메소드를 호출
InetSocketAddress isa = (InetSocketAddress) socketChannel.getRemoteAddress(); // getRemoteAddress() 메소드를
// 호출해 IP와 포트정보를 얻고,
// 타입변환을한다.
System.out.println("[연결 수락함]" + isa.getHostName());
}
} catch (Exception e) {
}
if (serverSocketChannel.isOpen()) { // ServerSocketChannel이 열려있을 경우
try {
serverSocketChannel.close(); // ServerSocketChannel 닫기
} catch (IOException e1) {
}
}
}
}
|
cs |

- 소켓 채널 생성과 연결 요청 -
SocketChannel은 정적 메소드인 open( ) 으로 생성하고, 블로킹 방식으로 동작시키기 위해 configureBlocking(true) 메소드를 호출한다.
기본적으로 블로킹 방식으로 동작하지만, 명시적으로 설정하는 이유는 넌블로킹과 구분하기 위해서이다.
서버 연결 요청은 connect( ) 메소드를 호출하면 되는데, 서버 IP와 포트 정보를 가진 InetSocketAddress 객체를 매개값으로 주면 된다.
connect( ) 메소드는 연결이 완료될 때까지 블로킹되고, 연결이 완료되면 리턴된다.
다음은 로컬 PC의 5001 포트에 바인딩된 서버에 연결을 요청하는 코드이다.
SocketChannel socketChannel = SocketChannel.open( );
socketChannel.configureBlocking(true);
socketChannel.connect(new InetSocketAddress("localhost", 5001));
|
- 예제 및 출력 결과 -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 |
package com.hs.chap19;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class ServerExample {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null;
try {
serverSocketChannel = ServerSocketChannel.open(); // open() 메소드를 사용해 서버 소켓 채널 얻기
serverSocketChannel.configureBlocking(true); // 블로킹 방식으로 동작시키기 위해 호출한다. 이걸 안써도 기본적으로 블로킹 방식으로 동작하지만 //명시적으로
// 설정하는 이유는 넌블로킹과 구분하기 위해서이다.
serverSocketChannel.bind(new InetSocketAddress(5001)); // bind()메소드의 매개값으로 5001번 포트를 줘서 바인딩 시킨다.
while (true) {
System.out.println("[연결 기다림]");
SocketChannel socketChannel = serverSocketChannel.accept(); // 연결 수락을 위해 accept() 메소드를 호출
InetSocketAddress isa = (InetSocketAddress) socketChannel.getRemoteAddress(); // getRemoteAddress() 메소드를
// 호출해 IP와 포트정보를 얻고,
// 타입변환을한다.
System.out.println("[연결 수락함]" + isa.getHostName());
}
} catch (Exception e) {
}
if (serverSocketChannel.isOpen()) { // ServerSocketChannel이 열려있을 경우
try {
serverSocketChannel.close(); // ServerSocketChannel 닫기
} catch (IOException e1) {
}
}
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 |
package com.hs.chap19;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
public class ClientExample {
public static void main(String[] args) {
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open(); // SocketChannel을 open()메소드를 사용해 생성
socketChannel.configureBlocking(true); // 블로킹 방식으로 동작시키기 위해 configureBlocking 메소드를 호출
System.out.println("[연결 요청]");
socketChannel.connect(new InetSocketAddress("localhost", 5001)); // localhost 5001포트로 연결요청을 한다.
System.out.println("연결 성공");
} catch (Exception e) {
}
if (socketChannel.isOpen()) {
try {
socketChannel.close();
} catch (IOException e1) {
}
}
}
}
|
cs |
- 소켓 채널 데이터 통신 -
클라이언트가 연결 요청 (connect( )) 하고 서버가 연결 수락 (accept()) 했다면, 양쪽 SocketChannel 객체의 read( ), write( ) 메소드를 호출해서 데이터통신
을 할 수 있다. 이 메소드들은 모두 버퍼를 이용하기 때문에 버퍼로 읽고, 쓰는 작업을 해야한다.
다음은 SocketChannel의 write( ) 메소드를 이용해서 문자열을 보내는 코드이다.
Charset charset = Charset.forName("UTF-8");
ByteBuffer byteBuffer = charset.encode("Hello Server");
socketChannel.write(byteBuffer);
|
다음은 SocketChannel의 read( ) 메소드를 이용해서 문자열을 받는 코드이다.
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
int byteCount = socketChannel.read(byteBuffer);
byteBuffer.flip( );
Charset charset = Charset.forName("UTF-8");
String message = charset.decode(byteBuffer).toString( );
|
- 예제 및 출력 결과 -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 |
package com.hs.chap19;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
public class ServerExample {
public static void main(String[] args) {
ServerSocketChannel serverSocketChannel = null; // 소켓채널에 null값을 준다. 값을 담아야하기 때문
try {
serverSocketChannel = ServerSocketChannel.open(); // open() 메소드를 사용해 SocketChannel객체를 얻는다.
serverSocketChannel.configureBlocking(true); // 블록킹방식으로 동작하기 위해 configureBlocking(true)호출
serverSocketChannel.bind(new InetSocketAddress(5001)); // 새로운 5001번 로컬호스트를 연결 시킨다.
while (true) {
System.out.println("[연결 기다림]");
SocketChannel socketChannel = serverSocketChannel.accept(); // 클라이언트 연결 수락을 위해 accept() 메소드 실행
InetSocketAddress isa = (InetSocketAddress) socketChannel.getRemoteAddress(); // socketChannel의 IP주소와 포트
// 정보를 isa에 저장한다.
System.out.println("[연결 수락함]" + isa.getHostName());
ByteBuffer byteBuffer = null; // 바이트버퍼에 null값을 준다. 값을 담아야하기때문
Charset charset = Charset.forName("UTF-8"); // 매개값으로 주어진 문자셋을 담아놓는다. (문자열을 전송하기위해)
byteBuffer = ByteBuffer.allocate(100); // 바이트버퍼에 100개의 공간을 새로 만들어 저장.
int byteCount = socketChannel.read(byteBuffer); // 바이트버퍼의 수만큼 읽어서 카운트 변수에 저장
byteBuffer.flip(); // position의 위치를 0번인덱스로 설정, (0이어야 ByteBuffer의 첫 바이트 부터 저장되기 때문)
String message = charset.decode(byteBuffer).toString(); // 바이트버퍼를 디코딩해서 string타입으로 변환한다
System.out.println("[데이터 받기 성공]: " + message);
byteBuffer = charset.encode("Hello Client");
socketChannel.write(byteBuffer);
System.out.println("[데이터 보내기 성공]");
}
} catch (Exception e) {
}
if (serverSocketChannel.isOpen()) {
try {
serverSocketChannel.close();
} catch (IOException e1) {
}
}
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 |
package com.hs.chap19;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
public class ClientExample {
public static void main(String[] args) {
SocketChannel socketChannel = null; // 소켓채널에 null값을 준다. 값을 담아야하기 때문
try {
socketChannel = SocketChannel.open(); // open() 메소드를 사용해 SocketChannel객체를 얻는다.
socketChannel.configureBlocking(true); // 블록킹방식으로 동작하기 위해 configureBlocking(true)호출
System.out.println("[연결 요청]");
socketChannel.connect(new InetSocketAddress("localhost", 5001)); // 새로운 5001번 로컬호스트를 연결 시킨다.
System.out.println("[연결 성공]");
ByteBuffer byteBuffer = null; // 바이트버퍼에 null값을 준다. 값을 담아야하기때문
Charset charset = Charset.forName("UTF-8"); // 매개값으로 주어진 문자셋을 담아놓는다. (문자열을 전송하기위해)
byteBuffer = charset.encode("Hello Server"); // 위에서 담은 문자셋으로 Hello Server을 변환한다.
socketChannel.write(byteBuffer); // 바이트버퍼에 담은 문자열을 보낸다.
System.out.println("[데이터 보내기 성공]");
byteBuffer = ByteBuffer.allocate(100); // 바이트버퍼에 100개의 공간을 새로 만들어 저장.
int byteCount = socketChannel.read(byteBuffer); // 바이트버퍼의 수만큼 읽어서 카운트 변수에 저장
byteBuffer.flip(); // position의 위치를 0번인덱스로 설정, (0이어야 ByteBuffer의 첫 바이트 부터 저장되기 때문)
String message = charset.decode(byteBuffer).toString(); // 바이트버퍼를 디코딩해서 string타입으로 변환한다
System.out.println("[데이터 받기 성공]: " + message);
} catch (Exception e) {
}
if (socketChannel.isOpen()) {
try {
socketChannel.close();
} catch (IOException e1) {
}
}
}
}
|
cs |

read( ) 메소드가 블로킹 해제되고 리턴되는 경우
블로킹이 해제되는 경우 |
리턴값 |
상대방이 데이터를 보냄 |
읽은 바이트 수 |
상대방이 정상적으로 SocketChannel의 close( )를 호출 |
-1 |
상대방이 비정상적으로 종료 |
IOException 발생 |
- 스레드 병렬 처리 -
TCP 블로킹 방식은 데이터 입출력이 완료되기 전까지 read( )와 write( ) 메소드가 블로킹 된다.
만약 애플리케이션을 실행시키는 main 스레드가 직접 입출력 작업을 담당하게 되면 입출력이 완료될 때까지 다른 작업을 할 수 없는 상태가 된다.
그렇기 때문에 클라이언트 연결(채널) 하나에 작업 스레드 하나를 할당해서 병렬 처리해야 한다.
- 스레드풀 -
클라이언트 폭증으로 인해 서버의 과도한 스레드 생성을 방지하려면 스레드풀을 사용하는 것이 바람직하다.
스레드풀은 스레드 수를 제한해서 사용하기 때문에 갑작스런 클라이언트의 폭증은 작업 큐의 작업량만 증가시킬 뿐
스레드 수에는 변함이 없기 때문에 서버 성능은 완만히 저하된다.

Back-End/Java 2019. 4. 24. 01:05
- 파일 채널 -
파일 채널을 이용하면 파일 읽기와 쓰기를 할 수 있다.
FileChannel은 동기화 처리가 되어 있기 때문에 멀티 스레드 환경에서 사용해도 안전하다.
FileChannel은 정적 메소드인 open()을 호출해서 얻을수도 있지만, IO의 FileInputStream,
FileOutputStream의 getChannel() 메소드를 호출해서 얻을 수도 있다.
다음은 open() 메소드로 FileChannel을 얻는 방법을 보여준다.
FileChannel fileChannel = FileChannel.open(Path path, OpenOption...options);
|
첫 번째 path 매개값은 열거나, 생성하고자 하는 파일의 경로를 Path 객체로 생성해서 지정하고,
두 번째 options 매개값은 열기 옵션 값인데 StandardOpenOption의 열거 상수를 나열해주면 된다.
열거 상수 |
설명 |
READ |
읽기용으로 파일을 연다. |
WRITE |
쓰기용으로 파일을 연다. |
CREATE |
파일이 없다면 새 파일을 생성한다. |
CREATE_NEW |
새 파일을 만든다. 이미 파일이 있으면 예외와 함께 실패한다. |
APPEND |
파일 끝에 데이터를 추가한다(WRITE 나 CREATE와 함께 사용됨) |
DELETE_ON_CLOSE |
채널을 닫을 때 파일을 삭제한다(임시 파일을 삭제할 때 사용) |
TRUNCATE_EXISTING |
파일을 0바이트로 잘라낸다(WRITE 옵션과 함께 사용됨) |
FileChannel을 더이상 이용하지 않을 경우에는 close() 메소드를 호출해서 닫아주어야 한다.
- 파일 쓰기와 읽기 -
파일에 바이트를 쓰려면 FileChannel의 write() 메소드를 호출하면 된다.
매개값으로 ByteBuffer 객체를 주면 되는데, 파일에 쓰여지는 바이트는 ByteBuffer의 position부터 limit까지이다.
position이 0이고 limit가 capacity와 동일하다면 ByteBuffer의 모든 바이트가 파일에 쓰여진다.
write() 메소드의 리턴값은 ByteBuffer에서 파일로 쓰여진 바이트 수이다.
-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 |
package com.hs.chap19;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileChannelWriteExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("C:/Temp/file.txt"); // C:/Temp/file.txt경로를 객체로 얻는다.
Files.createDirectories(path.getParent()); // path 경로에 있는 부모 디렉터리를 생성 한다.
FileChannel fileChannel = FileChannel.open( // open()메소드를 사용해 파일의 경로를 지정하고, 파일이 없다면 새 파일을 생성하고 쓰기용으로 파일을 연다.
path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
String data = "안녕하세요";
Charset charset = Charset.defaultCharset();
ByteBuffer byteBuffer = charset.encode(data); // encode()메소드를 사용해 data를 byteBuffer로 변환한다.
int byteCount = fileChannel.write(byteBuffer); // write() 메소드로 byteBuffer를 쓰고, 그 바이트의 수를 count에 저장
System.out.println("file.txt : " + byteCount + "bytes written");
fileChannel.close();
}
}
|
cs |

- 예제 및 출력 결과 -
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 |
package com.hs.chap19;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileChannelReadExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("C:/Temp/file.txt"); // get()메소드로 파일이 저장될 경로를 얻는다.
FileChannel fileChannel = FileChannel.open( // open()메소드를 사용해 path경로를 열고, 읽기용으로 파일을 연다.
path, StandardOpenOption.READ);
ByteBuffer byteBuffer = ByteBuffer.allocate(100); // allocate() 메소드를 이용, 100개의 바이트를 저장할수 있는 넌다이렉트 버퍼를 생성한다.
Charset charset = Charset.defaultCharset();
String data = "";
int byteCount;
while (true) {
byteCount = fileChannel.read(byteBuffer); // read()메소드를 사용해 byteBuffer 파일로부터 데이터를 읽음
if (byteCount == -1) // 더이상 읽을 바이트가 없으면.
break;
byteBuffer.flip(); // position의 위치를 0번인덱스로 설정, (0이어야 ByteBuffer의 첫 바이트 부터 저장되기 대문)
data += charset.decode(byteBuffer).toString(); // byteBuffer을 String로 변환해 하나씩 data에 담는다.
byteBuffer.clear(); // 모든 위치 속성을 초기화
}
fileChannel.close();
System.out.println("file.txt : " + data);
}
}
|
cs |

- 콜백 메소드 -
콜백이란 애플리케이션이 스레드에게 작업 처리를 요청한 후, 스레드가 작업을 완료하면 특정 메소드를 자동 실행하는 기법을 말한다. 이때 자동 실행되는 메소드를 콜백 메소드라고 한다.
블로킹 방식은 작업 처리를 요청한 후 작업이 완료될 때까지 블로킹되지만, 콜백 방식은 작업 처리를 요청한 후 결과를 기다릴 필요없이 다른 기능을 수행할 수 있다. 그 이유는 작업 처리가 완료되면 자동적으로 콜백 메소드가 실행되어 결과를 알 수 있기 때문이다.
콜백 메소드를 만들때는 직접 정의하거나 java.nio.channels.CompletionHandler를 이용해도 좋다. CompletionHandler를 사용하면 콜백 객체를 만들수 있다. 다음은 CompletionHandler 객체를 생성하는 코드이다.
CompletionHandler<V,A> callback = new CompletionHander<V,A>( ) { //CompletionHander의 V 타입 파라미터는 결과값의 타입이고 @Override // A는 첨부값의 타입이다. public void completed(V result, A attachment){ // 첨부값은 콜백 메소드에 결과값 이외에 추가적으로 전달하는 객체이다. } // 첨부값이 필요 없다면 A는 Void로 지정해주면 된다. @Override public void failed(Throwable exc, A attachment){ } };
|
- 콜백 메소드 종류 -
completed( ) : 작업을 정상 처리 완료했을 때 호출되는 콜백 메소드
failed( ) : 작업 처리 도중 예외가 발생했을 때 호출되는 콜백 메소드
- 파일 비동기 채널 -
FileChannel의 read( )와 write( ) 메소드는 파일 입출력 작업 동안 블로킹 된다. 만약 UI 및 이벤트를 처리하는 스레드에서 이 메소드들을 호출하면 블로킹되는 동안에 UI 갱신이나 이벤트 처리를 할 수 없다. 따라서 별도의 작업 스레드를 생성해서 이 메소드들을 호출해야 하는데, 파일의 수가 많으면 스레드의 수도 많아지기 때문에 문제가 될 수 있다. 그렇기 때문에 불특정 다수의 파일 및 대용량 파일의 입출력 작업을 위해 비동기 파일 채널을 제공하고 있다.
- 파일 비동기 채널 파일 입출력 순서 -
1. 파일의 데이터 입출력을 위해 read( )와 write( ) 메소드를 호출
2. 스레드풀에게 작업 처리를 요청하고 이 메소드들을 즉시 리턴시킨다.
3. 비동기채널에서 스레드풀로 작업처리 요청을 한다.
4. 스레드풀의 작업큐에서 비동기채널로 부터 받은 작업을 실행대기 시킨다.
5. 각 스레드는 스레드풀의 큐에서 작업을 가져와 입출력을 시킨다.
6. 작업 스레드가 파일 입출력을 완료하게 되면 콜백 메소드가 자동 호출된다.
|

|
- AsynchronousFileChannel 생성과 닫기 -
AsynchronousFileChannel는 두가지 정적 메소드인 open( )을 호출해서 얻을 수 있다. 첫번째 open( ) 메소드는 파일의 Path 객체와 열기 옵션 값을 매개값으로 받는다.
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open ( Path file, OpenOption...options )';
|
생성된 AsynchronousFileChannel는 내부적으로 생성되는 기본 스레드풀을 이용해서 스레드를 관리한다. 기본 스레드풀의 최대 스레드 개수는 개발자가 지정할 수 없기 때문에 두번째 open( ) 메소드로 AsynchronousFileChannel 를 만들수도 있다.
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open( Path file, Set<? extends OpenOption> options, ExecutorService executor, FileAttribute<?>...attrs );
|
- 파일 읽기와 쓰기 -
AsynchronousFileChannel이 생성되었다면 read( ), write( ) 메소드를 이용해서 입출력할 수 있다.
read(ByteBuffer dst, long position, A attachment, CompletionHandler<Integer, A> handler);
write(ByteBuffer src, long position, A attachment, CompletionHandler<Integer, A> handler);
|
이 메소드들을 호출하면 즉시 리턴되고, 스레드풀의 스레드가 입출력 작업을 진행한다.
dst와 src 매개값 : 읽거나 쓰기 위한 ByteBuffer position 매개값 : 파일에서 읽을 위치이거나 쓸 위치이다. 파일의 첫 번째 바이트부터 읽거나 첫 번째 위치에 바이트를 쓰고 싶다면 position을 0으로 주면 된다. attachment 매개값 : 콜백 메소드로 전달할 첨부 객체. 첨부 객체 : 콜백 메소드에서 결과값 이외에 추가적인 정보를 얻고자 할 때 사용하는 객체이고 필요없다면 null을 대입해도 된다. handler 매개값 : CompletionHandler<Integer, A> 구현 객체를 지정한다. Integer : 입출력 작업의 결과 타입으로, read( ) 와 write( ) 가 읽거나 쓴 바이트 수이다.
A는 첨부 객체 타입으로 개발자가 CompletionHandler 구현 객체를 작성할 때 임의로 지정이 가능하다. 만약 첨부 객체가 필요 없다면 A는 Void가 된다.
CompletionHandler<Integer, A> 구현 객체는 비동기 작업이 정상적으로 완료된 경우와 예외 발생으로 실패된 경우에 자동 콜백되는 다음 두 가지 메소드를 가지고 있어야 한다. 리턴타입 |
메소드명 (매개 변수) |
설명 |
void |
completed(Integer result, A attachment) |
작업이 정상적으로 완료된 경우 콜백 | void |
failed(Throwable exc, A attachment) |
예외 때문에 작업이 실패된 경우 콜백 |
- 예제 및 출력 결과 (파일 생성 및 쓰기) -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | package com.hs.chap19; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.CompletionHandler; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.EnumSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class AsynchronousFileChannelWriteExample { public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool( // cpu코어의 수 만큼 스레드풀을 생성한다. Runtime.getRuntime().availableProcessors() // cpu코어의 수를 리턴 ); for (int i = 0; i < 10; i++) { Path path = Paths.get("C:/Temp/file" + i + ".txt"); Files.createDirectories(path.getParent()); // 해당 디렉터리의 경로에 디렉터리(폴더)를 생성한다. // 비동기 파일 채널 생성 AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open( // 정적 메소드인 open()을 비동기채널을 만든다. path, // 작업해야될 파일의 경로 EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE), // 열기옵션(생성하고 쓸수있게)들을 담아서 리턴한다. executorService // 위에서 만든 스레드풀 ); Charset charset = Charset.defaultCharset(); ByteBuffer byteBuffer = charset.encode("안녕하세요."); // encode()메소드를 사용해 문자열을 인코딩해 Buffer에 넣는다. // 첨부 객체 생성 (콜백 메소드에서 결과값 이외에 추가적으로 보여주고싶은 값) class Attachment { Path path; // 파일의 경로를 저장 AsynchronousFileChannel fileChannel; // 비동기 파일 채널을 저장 } Attachment attachment = new Attachment(); attachment.path = path; attachment.fileChannel = fileChannel; CompletionHandler<Integer, Attachment> completionHander = new CompletionHandler<Integer, Attachment>() { // 콜백 // 메소드를 @Override public void completed(Integer result, Attachment attachment) { // 작업을 정상 처리 완료했을때 호출되는 콜백 메소드. System.out.println(attachment.path.getFileName() + ":" + result + "bytes written :" + Thread.currentThread().getName()); // 첨부객체의 파일의 이름을 출력하고, 결과값, 현재 실행중인 스레드의 이름을 출력한다. try { attachment.fileChannel.close(); } catch (IOException e) { } } @Override public void failed(Throwable exc, Attachment attachment) { exc.printStackTrace(); try { attachment.fileChannel.close(); } catch (IOException e) { } } }; // 파일 읽기 fileChannel.write(byteBuffer, 0, attachment, completionHander); // (읽은데이터를 저장할 버퍼 객체, 버퍼인덱스, 첨부 객체, 콜백 메소드를 가진 객체) // 스레드풀 종료 Thread.sleep(1000); executorService.isShutdown(); } } } | cs |

- 예제 및 출력 결과 (파일 읽기) -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | ppackage com.hs.chap19; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.CompletionHandler; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.EnumSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class AsynchronousFileChannelWriteExample { public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool( // cpu코어의 수 만큼 스레드풀을 생성한다. Runtime.getRuntime().availableProcessors() // cpu코어의 수를 리턴 ); for (int i = 0; i < 10; i++) { Path path = Paths.get("C:/Temp/file" + i + ".txt"); // 해당 경로에있는 파일을 읽는다. // 비동기 파일 채널 생성 AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open( // 정적 메소드인 open()을 비동기채널을 만든다. path, // 작업해야될 파일의 경로 EnumSet.of(StandardOpenOption.READ), // 열기옵션(파일을 읽을수 있게)을 담아서 리턴한다. executorService // 위에서 만든 스레드풀 ); //파일의 크기와 동일한 capacity를 갖는 버퍼 생성 ByteBuffer byteBuffer = ByteBuffer.allocate((int)fileChannel.size()); // 첨부 객체 생성 (콜백 메소드에서 결과값 이외에 추가적으로 보여주고싶은 값) class Attachment { Path path; // 파일의 경로를 저장 AsynchronousFileChannel fileChannel; // 비동기 파일 채널을 저장 ByteBuffer byteBuffer; } Attachment attachment = new Attachment(); attachment.path = path; attachment.fileChannel = fileChannel; attachment.byteBuffer = byteBuffer; //CompletionHandler 객체 생성 CompletionHandler<Integer, Attachment> completionHanderlernew = new CompletionHandler<Integer, Attachment>() { // 콜백 // 메소드를 @Override public void completed(Integer result, Attachment attachment) { attachment.byteBuffer.flip(); // 작업을 정상 처리 완료했을때 호출되는 콜백 메소드. Charset charset = Charset.defaultCharset(); String data = charset.decode(attachment.byteBuffer).toString(); System.out.println(attachment.path.getFileName() + ":" + data + ":" + Thread.currentThread().getName()); // 첨부객체의 파일의 이름을 출력하고, 결과값, 현재 실행중인 스레드의 이름을 출력한다. try { fileChannel.close(); } catch (IOException e) { } } @Override public void failed(Throwable exc, Attachment attachment) { exc.printStackTrace(); try { fileChannel.close(); } catch (IOException e) { } } }; // 파일 읽기 fileChannel.read(byteBuffer, 0, attachment, completionHanderlernew); // (읽은데이터를 저장할 버퍼 객체, 버퍼인덱스, 첨부 객체, 콜백 메소드를 가진 객체) // 스레드풀 종료 executorService.isShutdown(); } } } | cs |

Back-End/Java 2019. 4. 23. 15:33
- NIO(New Input/Output) -
새로운 입출력 방식이라는 뜻에서 java.nio 패키지가 포함되었는데, 자바 7로 버전업하면서 자바 IO와 NIO 사이의 일관성 없는 클래스 설계를 바로잡고,
비 동기 채널 등의 네트워크 지원을 대폭 강화한 NIO.2 API가 추가되었다.
- IO와 NIO의 차이점 -
I0와 NIO는 데이터를 입출력한다는 목적은 동일하지만, 방식에 있어서 크게 차이가 난다. 아래 표는 IO와 NIO의 차이점을 정리한 것이다.
구분 |
IO |
NIO |
입출력 방식 |
스트림 방식 (입력과 출력이 구분되어 효율 떨어짐) |
채널방식 (양방향으로 입력과 출력이 가능) |
버퍼 방식 |
넌버퍼(non-buffer) (속도 느림) |
버퍼(buffer) (속도 빠름) |
비동기 방식 |
지원 안 함 |
지원 |
블로킹 / 넌블로킹 방식 |
블로킹 방식만 지원 |
블로킹 / 넌블로킹 방식 모두 지원 |
- IO와 NIO의 선택 -
- NIO -
불특정 다수의 클라이언트 연결 또는 멀티 파일들을 넌블로킹이나 비동기로 처리할 수 있기 때문에 과도한 스레드 생성 방지 및 효과적 재사용 가능
또한 운영체제의 버퍼를 이용한 입출력이 가능하기 때문에 입출력 성능이 향상
NIO는 좀 더 다양한 파일의 속성 정보를 제공해주는 클래스와 인터페이스를 java.nio.file, java.nio.file.attribute 패키지에서 제공하고 있다.
- IO -
대용량 데이터를 처리할 경우에는 IO가 더 유리.
NIO는 모든 작업에 버퍼를 무조건 사용하기 때문에 복잡하므로 받은즉시 처리하는 IO를 사용.
연결 클라이언트 수가 적고, 전송되는 데이터가 대용량이면서 순차적으로 처리될 필요성이 있을 경우에는 IO로 서버를 구현하는 것이 좋다.
파일 속성 정보를 읽기 위해 File 클래스만 제공
-경로 정의(Path)-
NIO의 API에서 파일의 경로를 지정하기 위해 Path를 사용한다.
Path 구현 객체를 얻기 위해서는 java.nio.file.Paths 클래스의 정적 메소드인 get() 메소드를 호출하면 된다.
Path path = Paths.get (String first, String...more)
Path path = Paths.get(URI uri);
|
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 |
package com.hs.chap19;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
public class PathExample {
public static void main(String[] args) throws Exception {
Path path = Paths.get("src/sec02/exam01_path/PathExample.java"); // get() 메소드를 사용해 파일을 가져온다.
System.out.println("[파일명]" + path.getFileName()); // getFileName() 메소드를 사용해 파일의 이름을 가져와 출력한다.
System.out.println("[부모 디렉터리명]: " + path.getParent().getFileName()); // getParent().getFileName()메소드를 사용해 부모
// 디렉터리이름을 출력 한다.
System.out.println("중첩 경로수 : " + path.getNameCount()); // 파일의 이름의 개수(파일의 이름의 개수는 경로의 수)를 출력한다.
System.out.println();
for (int i = 0; i < path.getNameCount(); i++) {
System.out.println(path.getName(i)); // 파일의 경로를 하나씩 출력한다.
}
System.out.println();
Iterator<Path> iterator = path.iterator(); // 반복자 메소드를 사용
while (iterator.hasNext()) { // hasNext()메소드를 사용해 다음 디렉터리가 없을때 까지 출력
Path temp = iterator.next();
System.out.println(temp.getFileName()); //getFileName() 메소드를 사용해 얻은 디렉터리의 이름을 출력한다.
}
}
}
|
cs |

- 파일 시스템 정보 (FileSystem) -
운영체제의 파일 시스템은 FileSystem 인터페이스를 통해서 접근할 수 있다.
FileSystem 구현 객체는 FileSystem의 정적 메소드인 getDefault( )로 얻을 수 있다.
FileSystem fileSystem = FileSystems.getDefault( );
|
리턴 타입 |
매소드(매개 변수) |
설명 |
Iterable<FileStore> |
getFileStores( ) |
드라이버 정보를 가진 FileStore 객체들을 리턴 |
Iterable<Path> |
getRootDirectories( ) |
루트 디렉토리 정보를 가진 Path 객체들을 리턴 |
String |
getSeparator( ) |
디렉토리 구분자 리턴 |
-FileStore-
드라이버(컴퓨터 드라이버, ex- 그래픽드라이버 같은것) 를 표현한 객체.
리턴 타입 |
메소드 (매개 변수) |
설명 |
long |
getTotalSpace( ) |
드라이버 전체 공간 크기 (바이트 단위) 리런 |
long |
getUnallocatedSpace( ) |
할당되지 않은 공간 크기 (바이트 단위) 리턴 |
long |
getUsableSpace( ) |
사용 가능한 공간 크기,getUnallocatedSpace( ) 동일 |
boolean |
isReadOnly( ) |
읽기 전용 여부 |
String |
name( ) |
드라이버명 리턴 |
String |
type( ) |
파일 시스템 종류 |
-속성 이란?-
파일이나 디렉토리가 숨김인지, 디렉토리인지, 크기가 어떻게 되는지, 소유자가 누구인지에 대한 정보를 말한다.
다음은 java.nio.file.Files 클래스가 제공하는 정적 메소드들이다. (너무 많아서 전부다는 안쓰고 헷갈리는것들만 적음)
리턴 타입 |
메소드(매개 변수) |
설명 |
Path |
createDirectories( ) |
모든 부모 디렉토리 생성 |
Path |
createDirectory( ) |
경로의 마지막 디렉토리만 생성 |
FileStore |
getFileStore( ) |
파일이 위치한 FileStore(드라이브) 리턴 |
BufferedReader |
newBufferedReader( ) |
텍스트 파일을 읽는 BufferedReader 리턴 |
BufferedWriter |
newBufferedWriter( ) |
텍스트 파일에 쓰는 BufferedWriter 리턴 |
boolean |
notExists( ) |
존재하지 않는지 여부 |
String |
probeContentType( ) |
파일의 MIME 타입을 리턴 |
byte[ ] |
readAllBytes( ) |
파일의 모든 바이트를 읽고 배열로 리턴 |
List<String> |
readAllLines( ) |
텍스트 파일의 모든 라인을 읽고 리턴 |
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 |
package com.hs.chap19;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class DirectoryExample {
public static void main(String[] args) throws Exception {
Path path1 = Paths.get("C:/Temp/dir/subdir"); // 디렉토리 주소를 얻는다.
Path path2 = Paths.get("C:/Temp/file.txt"); // 파일 주소를 얻는다.
if (Files.notExists(path1)) // notExists 메소드를 사용해 path1 경로에 디렉토리가 존재하지 않는지 확인
{
Files.createDirectories(path1); // 디렉토리의 경로가 존재하지 않으면 createDirectories() 메소드를 사용해 path1 경로에 디렉토리를 생성한다.
}
if (Files.notExists(path2)) // notExists 메소드를 사용해 path2 경로에 파일이 존재하지 않는지 확인
{
Files.createFile(path2); // 디렉토리의 경로가 존재하지 않으면 createDirectories() 메소드를 사용해 path2 경로에 파일을 생성한다.
}
Path path3 = Paths.get("C:/Temp"); // 폴더의 주소를 얻는다.
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path3); // newDirectoryStream() 메소들 사용해서 Temp
// 디렉토리 내용을 스트림으로 얻는다.
for (Path path : directoryStream) // 얻는 스트림을 path에 하나씩 집어넣는다.
{
if (Files.isDirectory(path)) {
System.out.println("[디렉토리]" + path.getFileName()); // 디렉토리의 이름을 순차적으로 출력한다.
} else {
System.out.println("[파일]" + path.getFileName() + "(크기: " + Files.size(path) + ")"); // 디렉토리의 이름을 출력한 후에
// 파일의 크기를 출력한다.
}
}
}
}
|
cs |

- 와치 서비스(WatchService) -
자바7에서 처음 소개된 것으로 디렉토리 내부에서 파일 생성, 삭제, 수정 등의 내용 변화를 감시하는데 사용된다.
와치 서비스의 적용 예는 에디터에서 파일을 편집하고 있을 때, 에디터 바깥에서 파일 내용을 수정하게 되면 파일 내용이 변경됐으니
다시 불러올 것인지 묻는 대화상자를 띄우는 것이다.
WatchService를 생성하려면 FileSystem의 newWatchService( ) 메소드를 호출하면 된다.
WatchService watchService = FileSystems.getDefault( ).newWatchService( );
|
* 와치 서비스 사용법 *
1. FileSystem의 newWatchService( ) 메소드를 호출해서 WatchService 생성.
2. 감시가 필요한 디렉토리의 path객체에서 register( ) 메소드로 WatchService 를 등록.
3. 어떤 변화(생성, 삭제, 수정)를 감시할 것인지를 Standard WatchService 상수로 저장.
4. 디렉토리에 WatchService 를 등록한 순간부터 WatchEvent가 발생하고, WatchService는 해당 이벤트 정보를 가진 와치키(WatchKey)를
생성하여 큐에 저장
5. 프로그램이 무한 루프를 돌면서 WatchService의 take() 메소드를 호출하여 WatchKey가 큐에 들어올 때까지 대기하고 있다가 WatchKey가
큐에 들어오면 WatchKey를 얻는다.
6. WatchKey를 얻고나서 pollEvents( ) 메소드를 호출해서 WatchEvent 리스트를 얻어낸다.
7. 프로그램은 WatchEvent 리스트에서 WatchEvent를 하나씩 꺼내어 이벤트의 종류와 Path 객체를 얻어낸 다음 적절히 처리하면 된다.
|
- JNI(Java Native Interface) -
자바 코드에서 C함수를 호출할 수 있도록 해주는 API이다.
- 버퍼 -
Buffer 종류
Buffer은 저장되는 데이터 타입에 따라 분류될 수 있고, 어떤 메모리를 사용하느냐에 따라 다이렉트(Direct)와 넌다이렉트(NonDirect)로 분류할 수도 있다.
Buffer 클래스 allocate( ), wrap( ) 메소드 = 넌다이렉트 버퍼 생성
ByteBuffer의 allocateDirect( ) 메소드 = 다이렉트 버퍼 생성
구분 |
넌다이렉트 버퍼 |
다이렉트 버퍼 |
사용하는 메모리 공간 |
JVM의 힙 메모리 |
운영체제의 메모리 |
버퍼 생성 시간 |
버퍼의 생성이 빠르다. |
버퍼 생성이 느리다. |
버퍼의 크기 |
작다. |
크다.(큰 데이터를 처리할 때 유리) |
입출력 성능 |
낮다. |
높다. (입출력이 빈번할 때 유리) |
- allocate( ) 메소드 -
JVM 힙 메모리에 넌다이렉트 버퍼를 생성하는 메소드.
다음은 최대 100개의 바이트를 저장하는 ByteBuffer를 생성하고, 최대 100개의 문자를 저장하는 CharBuffer를 생성하는 코드이다.
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
CharBuffer charBuffer = CharBuffer.allocate(100); |
- wrap( ) 메소드 -
이미 생성되어 있는 자바 배열을 래핑해서 Buffer 객체를 생성한다.
자바 배열은 JVM 힙 메모리에 생성되므로 wrap( )은 넌다이렉트 버퍼를 생성한다.
다음은 길이가 100인 byte[]를 이용해서 ByteBuffer를 생성하고, 길이가 100인 char[]를 이용해서 CharBuffer를 생성한다.
byte[] byteArray = new byte[100];
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
char[] charArray = new char[100];
CharBuffer charBuffer = CharBuffer.wrap(charArray);
|
- allocateDirect( ) 메소드 -
JVM 힙 메모리 바깥쪽, 즉 운영체제가 관리하는 메모리에 다이렉트 버퍼를 생성한다.
Buffer 클래스에는 없고, ByteBuffer에서만 제공된다.
다음은 100개의 바이트(byte) 를 저장하는 다이렉트 ByteBuffer과 50개의 문자(char)를 저장하는 다이렉트 CharBuffer, 25개의 정수(int)를 저장하는
다이렉트 IntBuffer를 생성하는 코드이다. char은 2바이트 크기를 가지고, int는 4바이트 크기를 가지기 때문에 초기 다이렉트 ByteBuffer 생성 크기에
따라 저장 용량이 결정된다.
ByteBuffer byteBuffer = ByteBuffer.allocateDriect(100); //100개의 byte값 저장 (char은 2바이트 크기, int는 4바이트 크기를 가짐)
CharBuffer charBuffer = ByteBuffer.allocateDirect(100).asCharBuffer( ); //50개의 char값 저장 (100개의 char값을 변환한 값)
IntBuffer intBuffer = ByteBuffer.allocateDirect(100).asIntBuffer( ); //25개의 int값 저장 (50개의 char값을 변환한 값)
|
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
package com.hs.chap19;
import java.nio.ByteBuffer;
public class BufferSizeExample {
public static void main(String[] args) {
ByteBuffer directBuffer = ByteBuffer.allocateDirect(200 * 1024 * 1024); // allocateDirect(버퍼크기 설정)버퍼 크기 설정해서
// 다이렉트 버퍼 생성
System.out.println(directBuffer);
System.out.println("다이렉트 버퍼가 생성되었습니다.");
ByteBuffer nonDirectBuffer = ByteBuffer.allocate(200 * 1024 * 1024); // allocate(버퍼크기 설정)버퍼 크기 설정해서 넌다이렉트 버퍼 생성
System.out.println(nonDirectBuffer);
System.out.println("넌다이렉트 버퍼가 생성되었습니다.");
}
}
|
cs |
- byte 해석 순서(ByteOrder) -
데이터를 처리할 때 바이트 처리 순서는 운영체제마다 차이가 있다. 이러한 차이는 데이터를 다른 운영체제로 보내거나 받을 때
영향을 미치기 때문에 데이터를 다루는 버퍼도 이를 고려해야 한다.
앞쪽 바이트부터 먼저 처리하는 것을 Big endian 이라고 하고, 뒤쪽 바이트부터 먼저 처리하는 것을 Little endian이라고 한다.
- Buffer의 위치 속성 -
-속성 크기 관계-
0 <= mark <= position <= limit <= capacity |
속성 |
설명 |
position |
현재 읽거나 쓰는 위치값이다. 인덱스 값이기 때문에 0부터 시작하며, limit 보다 큰 값을 가질 수 없다.
만약 position과 limit의 값이 같아진다면 더 이상 데이터를 쓰거나 읽을 수 없다는 뜻이 된다. |
limit |
버퍼에서 읽거나 쓸 수 있는 위치의 한계를 나타낸다. 이 값은 capacity보다 작거나 같은 값을 가진다.
최초에 버퍼를 만들었을 때는 capacity와 같은 값을 가진다, |
capacity |
버퍼의 최대 데이터 개수(메모리 크기)를 나타낸다. 인덱스 값이 아니라 수량임을 주의하자. |
mark |
reset( ) 메소드를 실행했을 때에 돌아오는 위치를 지정하는 인덱스로서 mark( ) 메소드로 지정할 수 있다.
주의할 점은 반드시 position 이하의 값으로 지정해주어야 한다.
position이나 limit의 값이 mark 값보다 작은 경우, mark는 자동 제거된다.
mark가 없는 상태에서 reset( ) 메소드를 호출하면 InvalidMarkException이 발생한다. |
flip( ) 메소드 : 현재 position의 위치를 limit로 설정하고, position을 0번 인덱스로 설정한다.
mark( ) 메소드 : 현재 position의 위치를 기억시켜 놓는다.
reset( ) 메소드 : position을 mark가 있는 인덱스로 이동시킨다. (mark가 없는 상태에서 호출하면 예외발생-기억한 위치가 없기 때문)
rewind( ) 메소드 : limit는 변하지 않지만 position은 0번 인덱스로 다시 설정된다.
clear( ) 메소드 : Buffer의 세가지 속성이 초기화 된다. (limit는 capacity (버퍼의 최대 메모리값)으로, position은 0으로 설정되고, mark는 없어짐)
하지만 데이터는 삭제되지 않는다.
compact( ) 메소드 : 현재 position에서 limit 사이의 데이터가 0번 인덱스로 복사 되고 현재 position은 복사된 데이터 다음 위치로 이동한다.
(예를들어 flip() 메소드 호출 후 세 개의 데이터를 읽고 다음과 같이 position이 3번 인덱스 위치에 있을 때 compact( )가
호출되면 3번 인덱스와 4번 인덱스의 데이터는 0번 인덱스와 1번 인덱스로 복사되고 position 2번 인덱스로 이동한다.)
주의할 점은 0번과 1번 인덱스를 제외한 나머지 인덱스의 데이터는 삭제되지 않고 남아있다.
compact( ) 메소드를 호출하는 이유는 읽지 않은 데이터 뒤에 새로운 데이터를 저장하기 위해서이다.
|
- 데이터를 읽고 저장하는 메소드 -
put( ) 메소드 : 버퍼에 데이터를 저장하는 메소드
get( ) 메소드 : 버퍼에 있는 데이터를 읽는 메소드
|
이 두가지 메소드는 상대적과 절대적으로 구분된다.
버퍼 내의 현재 위치 속성인 position에서 데이터를 읽고, 저장할 경우는 상대적이고, position과 상관없이 주어진 인덱스에서
데이터를 읽고, 저장할 경우는 절대적이다.
상대적 get( )과 put( ) 메소드를 호출하면 position의 값은 증가하지만, 절대적 get( )와 put( ) 메소드를 호출하면
position의 값은 증가되지 않는다.
만약 position 값이 limit 값까지 증가했는데도 상대적 get( )을 사용하면 BufferUnderflowException 예외가 발생하고,
put( ) 메소드를 사용하면 BufferOverflowException 예외가 발생한다.
상대적 메소드와 절대적 메소드를 쉽게 구분하는 방법은 index 매개 변수가 없으면 상대적이고, index 매개 변수가 있으면 절대적이다.
- 버퍼 예외의 종류 -
주로 버퍼가 다 찼을 때 데이터를 저장하려는 경우와 버퍼에서 더 이상 읽어올 데이터가 없을 때 데이터를 읽으려는 경우에 예외가 발생
예외 |
설명 |
BufferOverflowException |
position이 limit에 도달했을 때 put( )을 호출하면 발생 |
BufferUnderflowException |
position이 limit에 도달했을 때 get( )을 호출하면 발생 |
InvalidMarkException |
mark가 없는 상태에서 reset( ) 메소드를 호출하면 발생 |
ReadOnlyBufferException |
읽기 전용 버퍼에서 put( ) 또는 compact( ) 메소드를 호출하면 발생 |
-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 |
package com.hs.chap19;
import java.nio.ByteBuffer;
public class BufferExample {
public static void main(String[] args) {
System.out.println("[7바이트 크기로 버퍼 생성]");
ByteBuffer buffer = ByteBuffer.allocateDirect(7); // allocateDirect()메소드로 7바이트 크기로 다이렉트 버퍼 생성
printState(buffer); // 버퍼에 인덱스를 매개값으로 받는 메소드 선언
buffer.put((byte) 10); // put()메소드를 사용해 버퍼에 10바이트를 저장, 0번 인덱스에 값 저장
buffer.put((byte) 11); // put()메소드를 사용해 버퍼에 11바이트를 저장, 1번 인덱스에 값 저장
System.out.println("[2바이트 저장후]");
printState(buffer); // 인덱스 매개변수가 있으므로 상대적 저장임
buffer.put((byte) 12); // put() 메소드를 사용해 버퍼에 12바이트를 저장, 2번 인덱스에 값 저장
buffer.put((byte) 13); // put() 메소드를 사용해 버퍼에 13바이트를 저장, 3번 인덱스에 값 저장
buffer.put((byte) 14); // put() 메소드를 사용해 버퍼에 14바이트를 저장, 4번 인덱스에 값 저장
System.out.println("[3바이트 저장후]");
printState(buffer); // 인덱스 매개변수가 있으므로 상대적 저장
buffer.flip(); // flip()메소드를 사용해 현재 position의 위치를 limit로 설정하고, position을 0번 인덱스로 설정
System.out.println("[flip() 실행후]");
printState(buffer); // 출력값 position : 0, limit: 5 으로 예상됨
buffer.get(new byte[3]); // get() 메소드를 사용해 3바이트를 읽음
System.out.println("[3바이트를 읽은 후]");
printState(buffer); // 상대적 읽기
buffer.mark(); // 현재 position위치를 저장해놓음 (현재 3번 인덱스)
System.out.println("--------[현재 위치를 마크 해놓음]---------");
buffer.get(new byte[2]); // get()메소드를 사용해 2바이트를 읽음 (현재위치 5번 인덱스)
System.out.println("[2바이트 읽은후]");
printState(buffer);
buffer.reset(); // reset() 메소드를 사용해 mark위치로 position을 옮김 (현재 3번 인덱스)
System.out.println("-----[position을 마크 위치로 옮김]");
printState(buffer);
buffer.rewind(); // rewind()메소드를 사용하여 position을 0번으로 이동 (현재 0번 인덱스)
System.out.println("[rewind() 실행후]");
printState(buffer);
buffer.clear(); // clear() 메소드를 사용해 위치 값을 초기화 (position,limit,capacity 즉 처음 버퍼 만들었을 때로 돌아감)
System.out.println("[clear() 실행후]");
printState(buffer);
}
private static void printState(ByteBuffer buffer) { // 버퍼의 인덱스를 매개값으로 받아 position, limit, capacity를 출력하는 메소드
System.out.println("position: " + buffer.position() + ", ");
System.out.println("limit: " + buffer.limit() + ", ");
System.out.println("capacity: " + buffer.capacity() + ", ");
}
}
|
cs |

- Buffer 변환 -
채널이 데이터를 읽고 쓰는 버퍼는 모두 ByteBuffer이다.
그렇기 때문에 채널을 통해 읽은 데이터를 복원하려면 ByteBuffer를 문자열 또는 다른 타입 버퍼( CharBuffer, IntBuffer, LongBuffer 등 )
반대로 문자열 또는 다른 타입의 버퍼의 내용을 채널을 통해 쓰고 싶다면 ByteBuffer로 변환해야 한다.
ByteBuffer <-- String 변환
채널을 통해 문자열을 파일이나 네트워크로 전송하려면 특정문자셋(UTF-8, EUC-KR)으로 인코딩해서 ByteBuffer로 변환해야 한다.
문자셋을 표현하는 java.nio.charset.Charset 객체는 두 가지 방법으로 얻을 수 있다.
Charset charset = Charset.forName("UTF-8"); //매개값으로 주어진 문자셋
Charset charset = Charset.defaultCharset( ); //운영체제가 사용하는 디폴트 문자셋
Charset를 이용해서 문자열을 ByteBuffer로 변환하려면 다음과 같이 encode( ) 메소드를 호출 하면 된다.
String data = ...;
ByteBuffer byteBuffer = charset.encode(data);
|
ByteBuffer --> String 변환
반대로 파일이나 네트워크로부터 읽은 ByteBuffer이 특정 문자셋으로 인코딩되어 있을 경우, 해당 문자셋으로 디코딩해야만
문자열로 복원할 수 있다.
Charset는 ByteBuffer를 디코딩해서 CharBuffer로 변환시키는 decode( ) 메소드를 제공하고 있기 때문에 쉽게 문자열로 복원할 수 있다.
ByteBuffer byteBuffer = ...;
String data = charset.decode(byteBuffer).toString( );
|
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 |
package com.hs.chap19;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
public class ByteBufferToStringExample {
public static void main(String[] args) {
Charset charset = Charset.forName("UTF-8"); // forName() 메소드의 매개값인 문자셋으로 Charset 객체를 얻는다.
// 문자열 -> 인코딩 -> ByteBuffer
String data = "안녕하세요";
ByteBuffer byteBuffer = charset.encode(data); // charset의 encode메소드로 data에 있는 문자열을 변환한다.
// ByteBuffer -> 디코딩 -> CharBuffer -> 문자열
data = charset.decode(byteBuffer).toString();// decode 메소드 사용 byteBuffer을 String문자열로 변환한다.
System.out.println("문자열 복원: " + data);
}
}
|
cs |

ByteBuffer <-- IntBuffer 변환
int[ ] 배열을 생성하고 이것을 파일이나 네트워크로 출력하기 위해서는 int[ ] 배열 또는 IntBuffer로 부터
ByteBuffer을 생성해야 한다.
int 타입은 4byte 크기를 가지므로 int[] 배열 크기 또는 IntBuffer의 capacity보다 4배큰 capacity를 가진 ByteBuffer을 생성하고,
ByteBuffer의 putInt() 메소드로 정수값을 하나씩 저장하면 된다.
다음은 int[] 배열을 IntBuffer로 래핑하고, 4배 큰 ByteBuffer를 생성한 후 정수값을 저장한다.
주의할 점은 putInt() 메소드는 position을 이동시키기 때문에 모두 저장한 후에는 position을 0으로 되돌려 놓는 flip() 메소드를 호출해야 한다.
int[ ] data = new int[ ] {10,20};
IntBuffer intBuffer = IntBuffer.wrap(data);
ByteBuffer byteBuffer = ByteBuffer.allocate(intBuffer.capacity()*4);
for(int i = 0; i < intBuffer.capacity( ); i++) {
byteBuffer.putInt(intBuffer.get(i);
}
byteBuffer.flip( );
|
ByteBuffer --> IntBuffer 변환
반대로 파일이나 네트워크로부터 입력된 ByteBuffer에 4바이트씩 연속된 int 데이터가 저장되어 있을 경우,
int[ ] 배열로 복원이 가능하다.
ByteBuffer의 asIntBuffer( ) 메소드로 IntBuffer를 얻고, IntBuffer의 capacity와 동일한 크기의 int[ ] 배열을 생성한다.
그리고 IntBuffer의 get( ) 메소드로 int값들을 배열에 저장하면 된다.
ByteBuffer byteBuffer = ...;
IntBuffer intBuffer = byteBuffer.asIntBuffer( );
int[ ] data = new int [intBuffer.capacity( )];
intBuffer.get(data);
|
Back-End/Java 2019. 4. 22. 18:00
-보조 스트림-
다른 스트림과 연결되어 여러 가지 편리한 기능을 제공해주는 스트림을 말한다. 보조 스트림을 필터(filter) 스트림이라고도 하는데, 이는 보조 스트림의 일부가 FilterInputStream, FilterOutputStream의 하위 클래스이기 때문이다.
보조 스트림은 문자 변환, 입출력 성능 향상, 기본 데이터 타입 입출력, 객체 입출력 등의 기능을 제공한다.
보조 스트림을 생성할 때에는 자신이 연결될 스트림을 다음과 같이 생성자의 매개값으로 받는다.
보조스트림 변수 = new 보조스트림(연결스트림)
-문자 변환 보조 스트림-
소스 스트림이 바이트 기반 스트림(InputStream, OutputStream 등) 이면서 입출력 데이터가 문자라면
Reader과 Writer로 변환해서 사용하는 것을 고려해야한다.
-InputStreamReader-
바이트 입력 스트림에 연결되어 문자 스트림인 Reader로 변환시키는 보조 스트림이다.
Reader reader = new InputStreamReader(바이트입력스트림);
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 |
package com.hs.chap18;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
public class InputStreamReadeExample {
public static void main(String[] args) throws Exception {
InputStream is = System.in; // 시스템으로 부터 입력을 받음
Reader reader = new InputStreamReader(is); // 입력받은 값을 InputStreamReader메소드를 사용해 문자입력 스트림인 Reader로 변환
int readCharNo;
char[] cbuf = new char[100]; // cbuf에 100개의 공간을 만듦
while ((readCharNo = reader.read(cbuf)) != -1) // 아까 선언한 readCharNo의 값이 -1과 같지 않으면 (출력할 값이 남아있으면) 문자를 계속 출력
{
String data = new String(cbuf, 0, readCharNo);
System.out.println(data);
}
reader.close();
}
}
|
cs |
-OutputStreamWriter-
바이트 출력 스트림에 연결되어 문자 출력 스트림인 Writer로 변환시키는 보조 스트림이다.
Writer writer = new OutStreamWriter(바이트출력스트림);
-성능 향상 보조 스트림-
프로그램의 실행 입출력이 가장 늦은 장치를 따라간다. CPU와 메모리가 아무리 뛰어나도 하드디스크의 입출력이 늦어지면 프로그램의 실행 성능은 하드디스크의 처리 속도에 맞춰진다. 프로그램이 입출력 소스와 직접 작업하지 않고 중간에 메모리 버퍼(buffer)와 작업함으로써 실행 성능을 향상시킬 수 있다.
-BufferdInputStream과 BufferedReader-
BufferedInputStream는 바이트 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이고,
BufferedReader는 문자 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다.
BufferedInputStream bis = new BufferedInputStrem(바이트입력스트림);
BufferedReader br = new BufferedReader(문자입력스트림);
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 |
package com.hs.chap18;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class BufferdInputStreamExample {
public static void main(String[] args) throws Exception {
long start = 0;
long end = 0;
FileInputStream fis1 = new FileInputStream("C:/forest.jpg");
start = System.currentTimeMillis(); // fis1을 실행하는데 걸리는 시간 측정 (시간참조 메소드를 사용)
while (fis1.read() != -1) {
} // 더이상 읽을값이 없을때까지 출력 (-1이 될때까지)
end = System.currentTimeMillis(); // 끝나는 시간 측정
System.out.println("사용하지 않았을 때: " + (end - start) + "ms"); // 끝난시간에서 시작시간을 빼서 총 걸린 시간 측정.
fis1.close(); // 파일출력이 끝났으므로 종료한다.
FileInputStream fis2 = new FileInputStream("C:/forest.jpg");
BufferedInputStream bis = new BufferedInputStream(fis2);
start = System.currentTimeMillis();
while (bis.read() != -1) {
}
end = System.currentTimeMillis();
System.out.println("사용했을 때: " + (end - start) + "ms");
bis.close();
fis2.close();
}
}
|
cs |

-BufferedOutputStream과 BufferedWriter-
BufferedOutputStream은 바이트 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이고,
BufferedWriter은 문자 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다.
BufferedOutputStream bos = new BufferedOutputStream(바이트출력스트림);
BufferedWriter bw = new Buffered(문자출력스트림);
-기본 타입 입출력 보조 스트림-
바이트 스트림은 바이트 단위로 입출력하기 때문에 자바의 기본 데이터 타입인 boolean, char, short, int, long, float, double 단위로 입출력할 수 없다. 이 메소스들로 입출력할 때는 데이터 타입의 크기가 모두 다르므로 DataOutputStream으로 출력한 데이터를 다시 DataInputStream으로 읽어올 때는 출력한 순서와 동일한 순서로 읽어야 한다. 예를 들어 출력할 때의 순서와 int -> boolean -> double 이라면 읽을 때의 순서도 int -> boolean -> double 이어야 한다.
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 |
package com.hs.chap18;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class DataInputOutputStreamExample {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:/forest.txt"); // fos에 C:/forest.txt을 출력하는 객체를 만든다.
DataOutputStream dos = new DataOutputStream(fos); // dos에 fos의 데이터를 출력하는 객체를 만든다.
dos.writeUTF("홍길동"); // write메소드를 사용해 txt에 값을 입력
dos.writeDouble(5); // write메소드를 사용해 txt에 값을 입력
dos.writeInt(1); // write메소드를 사용해 txt에 값을 입력
dos.writeUTF("감자바"); // write메소드를 사용해 txt에 값을 입력
dos.writeDouble(90.3); // write메소드를 사용해 txt에 값을 입력
dos.writeInt(2); // write메소드를 사용해 txt에 값을 입력
dos.flush();
dos.close(); // flush()메소드를 사용해 문자가 다 출력되면 잔여값이 남기지 않도록 한다.
// close()모든 출력이 종료되면 종료시킨다.
FileInputStream fis = new FileInputStream("C://forest.txt");
DataInputStream dis = new DataInputStream(fis);
for (int i = 0; i < 2; i++) {
String name = dis.readUTF();
double score = dis.readDouble();
int order = dis.readInt();
System.out.println(name + " : " + score + " : " + order);
}
dis.close();
fis.close();
}
}
|
cs |

-프린트 보조 스트림-
PrintStrem과 PrintWriter은 프린터와 유사하게 출력하는 print(), println()메소드를 가지고 있는 보조 스트림이다. 콘솔 출력 스트림인 System.out이 바로 PrintStream 타입이기 때문에 print(), println() 메소드를 사용할 수 있었다. 다른 보조 스트림과 마찬가지로 연결할 출력 스트림을 생성자의 매개값으로 받는다.
PrintStream ps = new PrintStream(바이트출력스트림);
PrintWriter pw = new PrintWriter(문자출력스트림);
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 |
package com.hs.chap18;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class PrintStreamExample {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:/forest.txt");
PrintStream ps = new PrintStream(fos); // 출력 스트림의 변수를 매개값으로 받아옴
ps.println("[프린터 보조 스트림]");
ps.print("마치");
ps.println("프린트가 출력하는 것처럼");
ps.println("데이터를 출력합니다.");
System.out.println();
ps.flush();
ps.close();
fos.close();
}
}
|
cs |
-printf 메소드-
첫 번째 매개값으로 형식화된 문자열을 지정하고, 두 번째 매개값부터 형식화된 문자열에 들어갈 값을 나열해주면 된다.
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 |
package com.hs.chap18;
import java.util.Date;
public class PrintfExample {
public static void main(String[] args) {
System.out.printf("상품의 가격 %d원/n", 123);
System.out.printf("상품의 가격 %6d원/n", 123);
System.out.printf("상품의 가격 %-6d원/n", 123);
System.out.printf("상품의 가격 %06d원/n", 123);
System.out.printf("반지름이 %d인 원의 넓이:%10.2f/n", 10, Math.PI * 10 * 10);
System.out.printf("%6d | %-10s | %10s/n", 1, "홍길동", "도적");
Date now = new Date();
System.out.printf("오늘은 %tY년 %tm월 %td일 입니다/n", now, now, now);
}
}
|
|

-객체 입출력 보조 스트림-
객체는 문자가 아니기 때문에 바이트 기반 스트림으로 출력해야 한다. 객체를 출력하기 위해서는 객체의 데이터(필드값)를 일렬로 늘어선 연속적인 바이트로 변경해야 하는데, 이것을 객체 직렬화(serialization)라고 한다. 입력 스트림으로부터 읽어들인 연속적인 바이트를 객체로 복원하는 것을 역직렬화라고 한다.
-ObjectInputStream, ObjectOutputStream-
ObjectInputStream는 바이트 출력 스트림과 연결되어 객체를 직렬화 하는 역할을 하고, ObjectOutputStream는 바이트 입력 스트림과 연결되어 객체로 역직렬화 하는 역할을 한다.
두가지 모두다 다른 보조 스트림과 마찬가지로 연결할 바이트 입출력 스트림을 생성자의 매개값으로 받는다.
ObjectInputStream ois = new ObjectInputStream(바이트입력스트림);
ObjectInputStream oos = new ObjectOutputStream(바이트출력스트림);
ObjectOutputStream으로 객체를 직렬화하기 위해서는 writeObject()메소드를 사용한다.
oos.writeObject(객체);
반대로 ObjectInputStream의 readObject() 메소드는 입력 스트림에서 읽은 바이트를 역직렬화 해서 객체로 생성한다. readObject() 메소드의 리턴 타입은 Object 타입이기 때문에 객체 원래의 타입으로 변환해야 한다.
객체타입 변수 = (객체타입) ois.readObject();
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 |
package com.hs.chap18;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectInputOutputStreamExample {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:/forest.txt"); // 파일출력스트림 txt를 fos에 저장
ObjectOutputStream oos = new ObjectOutputStream(fos); // fos의 객체를 받아 객체출력스트림을 oos에 저장
oos.writeObject(new Integer(10)); // 새로운 객체를 만들어서 oos에 저장
oos.writeObject(new Double(3.14));
oos.writeObject(new int[] { 1, 2, 3 });
oos.writeObject(new String("홍길동"));
oos.flush(); // 남은(필요없는부분)이 없도록 출력
oos.close();
fos.close();
FileInputStream fis = new FileInputStream("C:/forest.txt"); // 파일입력스트림 txt를 fis에 저장
ObjectInputStream ois = new ObjectInputStream(fis);
Integer obj1 = (Integer) ois.readObject(); // ois의 객체를 읽어서 Integer타입으로 타입변환해 obj1에 저장
Double obj2 = (Double) ois.readObject(); // ois의 객체를 읽어서 Double타입으로 타입변환해 obj2에 저장
int[] obj3 = (int[]) ois.readObject(); // ois의 객체를 읽어서 Double타입으로 타입변환해 obj3에 저장
String obj4 = (String) ois.readObject(); // ois의 객체를 읽어서 Double타입으로 타입변환해 obj4에 저장
ois.close();
fis.close();
System.out.println(obj1);
System.out.println(obj2);
System.out.println(obj3[0] + "," + obj3[1] + "," + obj3[2]);
System.out.println(obj4);
}
}
|
cs |

-직렬화가 가능한 클래스(Serializable)-
자바가 Seralizable 인터페이스를 구현한 클래스만 직렬화할 수 있도록 제한하고 있다. Serializable 인터페이스는 필드나 메소드가 없는 빈 인터페이스이지만, 객체를 직렬화할 때 private 필드를 포함한 모든 필드를 바이트로 변환해도 좋다는 표시 역할을 한다.
public class XXX implements Serializable { }
또한 직렬화는 필드만 해당하고, 메소드나 생성자는 해당되지 않는다. 하지만 필드선언에 static 또는 transient가 붙어 있을 경우에는 직렬화가 되지 않는다.
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12 |
package com.hs.chap18;
import java.io.Serializable;
public class ClassA implements Serializable { // 필드를 직렬화 하기 위해서 Serializable 인터페이스 구현
int field1;
ClassB field2 = new ClassB();
int field3; // static와 transient는 직렬화에서 제외됨
int field4;
}
|
cs |
|
package com.hs.chap18;
import java.io.Serializable;
public class ClassB implements Serializable { // 필드를 직렬화 하기 위해서 Serializable 인터페이스 구현
int field1;
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 |
package com.hs.chap18;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class SerializableReader {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("C:/forest.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
ClassA v = (ClassA) ois.readObject(); // ois로 읽은 객체를 classA타입으로 타입변환해 v에 저장
System.out.println("field1: " + v.field1);
System.out.println("field2.field1: " + v.field2.field1);
System.out.println("field3: " + v.field3);
System.out.println("field4: " + v.field4);
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 |
package com.hs.chap18;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializableWriter {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:/forest.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
ClassA classA = new ClassA(); // classA의 객체 생성
classA.field1 = 1;
classA.field2.field1 = 2;
classA.field3 = 3;
classA.field4 = 4;
oos.writeObject(classA); // 직렬화해서 파일에 저장
oos.flush();
oos.close();
fos.close();
}
}
|
cs |
-serialVersion UID 필드-
직렬화된 객체를 역직렬화할 때는 직렬화했을 때와 같은 클래스를 사용해야한다. 예외의 내용은 직렬화할 때와 역직렬화할 때 사용된 클래스의 serialVersionUID가 다르다는 것이다. 클래스의 serialVersionUID가 명시적으로 선언되어 있으면 컴파일 시에 serialVersionUID 필드가 추가되지 않기 때문에 동일한 serialVersionUID 값을 유지할 수 있다.
-writeObject()와 readObject() 메소드-
두 클래스가 상속 관계에 있을때 부모 클래스가 Serializable 인터페이스를 구현하고 있으면 자식 클래스는 Serializabled 인터페이스를 구현하지 않아도 자식 객체를 직렬화 하면 부모 필드 및 자식 필드가 모두 직렬화 된다. 하지만 부모 클래스는 그렇지 않다. 부모 클래스의 필드를 직렬화 하고 싶다면 2가지 방법이 있다. 주의할 점은 접근 제한자가 private가 아니면 자동 호출되지 않기 때문에 반드시 private를 붙여주어야 한다.
1. 부모 클래스가 Serializable 인터페이스를 구현하도록 한다.
2. 자식 클래스에서 writeObject()와 readObject() 메소드를 선언해서 부모 객체의 필드를 직접 출력시킨다.
private void writeObject(ObjectOutputStream out) throws IOException { out.writeXXX(부모필드); -> 부모 객체의 필드값을 출력함 . . . . out.defaultWriteObject(); ->자식 객체의 필드값을 직렬화
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 부모필드 = in.readXXX(); -> 부모 객체의 필드값을 읽어옴 . . . . in.defaultReadObject(); ->자식 객체의 필드값을 역직렬화
}
-예제 및 출력 결과-
|
package com.hs.chap18;
public class Parent {
public String field1;
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 |
package com.hs.chap18;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Child extends Parent implements Serializable { // 부모 클래스를 상속받고 Serializable 인터페이스를 구현한다.
public String field2;
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeUTF(field1); // 부모 객체의 필드 값을 출력한다.
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
field1 = in.readUTF(); // 부모 객체의 필드값을 읽어옴
in.defaultReadObject();
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 |
package com.hs.chap18;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class NonSerializableParentExample {
public static void main(String[] args) throws Exception{
FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Child child = new Child();
child.field1 = "홍길동";
child.field2 = "홍삼원";
oos.writeObject(child);
oos.flush();
oos.close();
fos.close();
FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
Child v = (Child) ois.readObject();
System.out.println("field1: "+v.field1);
System.out.println("field2: "+v.field2);
ois.close();
fis.close();
}
}
|
cs |
-네트워크 기초-
네트워크는 여러 대의 컴퓨터를 통신 회선으로 연결한 것을 말한다. 인터넷은 지역 네트워크를 통신 회선으로 연결한 것을 말한다.
-서버와 클라이언트-
서비스를 제공하는 프로그램을 일반적으로 서버(server)이라고 부르고, 서비스를 받는 프로그램을 클라이언트 (client)라고 부른다. 클라이언트는 서비스를 받기 위해 연결을 요청하고, 서버는 연결을 수락하여 서비스를 제공한다.
-InetAddress로 IP주소 얻기-
InetAddress.getLocalHost() 메소드 사용.
InetAddress ia = InetAddress.getLocalHost();
외부 컴퓨터의 도메인 이름을 알고 있다면 다음 두 개의 메소드를 사용하여 InetAddress 객체를 얻으면 된다.
InetAddress ia = InetAddress.getByName(String host); InetAddress[ ] iaArr = InetAddress.getAllByName(String host);
-TCP 네트워킹-
TCP는 연결 지향적 프로토콜이다. 연결 지향 프로토콜이란, 클라이언트와 서버가 연결된 상태에서 데이터를 주고받는 프로토콜을 말한다. TCP의 단점은 데이터를 보내기 전에 반드시 연결이 형성되어야 하고, 고정된 통신 선로가 최단선이 아닐 경우 상대적으로 UDP보다 데이터 전송 속도가 느릴 수 있다.
-TCP 서버의 역할-
1. 클라이언트가 연결 요청을 해오면 연결을 수락하는 것. (java.net.ServerSocket 클래스) 2. 연결된 클라이언트와 통신하는 것. (java.net.Socket 클래스)
-ServerSocket와 Socket의 용도-
서버를 개발하려면 우선ServerSocket 객체를 얻어야 한다. 가장 간단한 방법은 생성자에 바인딩 포트를 대입하고 객체를 생성하는 것이다. 다음은 5001번 포트에 바인딩하는 ServerSocket를 생성한다.
ServerSocket serverSocket = new ServerSocket(5001);
다른 방법은 디폴트 생성자로 객체를 생성하고 포트 바인딩을 위해 bind() 메소드를 호출하는것이다. bind() 메소드의 매개값은 포트 정보를 가진 InetSocketAddress 이다.
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(5001));
포트 바인딩이 끝났다면 ServerSocket는 클라이언트 연결 수락을 위해 accept() 메소드를 실행해야 한다. accept() 메소드는 클라이언트가 연결 요청하기 전까지 블로킹 되는데, 블로킹이란 스레드가 대기 상태가 된다는 뜻이다.
연결된 클라이언트의 IP와 포트 정보를 알고 싶다면 Socket의 getRemoteSocketAddress() 메소드를 호출해서 SocketAddress를 얻으면 된다.
실제 리턴되는 것은 InetSockAddress 객체 이므로 타입 변환할 수 있다.
InetSocketAddress socketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 |
package com.hs.chap18;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerExample {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(); // 서버소켓 객체를 생성
serverSocket.bind(new InetSocketAddress("localhost", 5001)); // bind 메소드 호출해서 로컬호스트 5001번포트에 바인딩하는 서버소켓을
// 생성한다.
while (true) {
System.out.println("[연결 기다림]");
Socket socket = serverSocket.accept(); // 서버소켓의 연결 수락을 위해 accept()메소드 실행
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();// 소켓의
// getRemoteSocketAddress()메소드를
// 호출해서 소켓주소를 얻은후
System.out.println("[연결 수락함]" + isa.getHostName()); // InetSocketAddress 타입으로 타입변환을 한후 isa에 저장한다.
}
} catch (Exception e) {
}
if (!serverSocket.isClosed()) {
try {
serverSocket.close();
} catch (IOException e1) {
}
}
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 |
package com.hs.chap18;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
public class ClientExample {
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket(); // 소켓을 생성
System.out.println("[연결 요청]");
socket.connect(new InetSocketAddress("localhost", 5001)); //커넥트 메소드로 5001포트로 연결
System.out.println("[연결 성공]");
} catch (Exception e) {
}
if (!socket.isClosed()) { //소켓이 닫히지 않으면 예외처리 (연결이 되어있을 경우)
try {
socket.close();
} catch (IOException e1) {
}
}
}
}
|
cs |

-Socket 데이터 통신-
클라이언트가 연결 요청(connect()) 하고 서버가 연결 수락(accept()) 했다면, 양쪽의 Socket 객체로부터 각각 입력 스트림(InputStream)과 출력 스트림(OutputStream)을 얻을 수 있다.
다음은 Socket으로 부터 InputStream과 OutputStream을 얻는 코드이다.
//입력 스트림 얻기
InputStream is = socket.getInputStream();
//출력 스트림 얻기
OutputStream os = socket.getOutputStream();
-상대방에게 데이터 보내기-
String data = "보낼 데이터"; byte[ ] byteArr = data.getBytes("UTF-8"); OutputStream outputStream = socket.getOutputStream(); outputStream.write(byteArr); outputStream.flush();
-상대방에게 데이터 받기-
byte[ ] byteArr = new byte[100]; InputStream inputStream = socket.getInputStream(); int readByteCount = inputStream.read(byteArr); String data = new String(byteArr,0,readByteCount,"UTF-8");
-스레드 병렬 처리-
연결 수락을 위해 ServerSocket의 accept()를 실행하거나, 서버 연결 요청을 위해 Socket 생성자 또는 connect()를 실행할 경우에는 해당 작업이 완료되기 전까지 블로킹 된다. 결론적으로 ServerSocket과 Socket는 동기방식으로 구동된다. 만약 서버를 실행시키는 main 스레드가 직접 입출력 작업을 담당하게 되면 입출력이 완료될 때까지 다른 작업을 할 수 없는 상태가 된다. 그렇기 때문에 accept(), connect(), read(), write()는 별도의 작업 스레드를 생성해서 병렬적으로 처리하는 것이 좋다. 그렇기 때문에 과도한 스레드 생성을 방지하려면 스레드풀을 사용하는 것이 바람직하다.
-서버 클래스 구조-
1.startServer() 메소드
start 버튼을 클릭하면 실행하는 메소드. ExecutorService 생성, ServerSocket 생성 및 포트 바인딩, 연결 수락 코드가 있다.
2.stopServer() 메소드
stop 버튼을 클릭하면 stopServer() 메소드가 실행되는데, stopServer() 메소드에는 연결된 모든 Socket 닫기, ServerSocket 닫기, ExecutorService 종료 코드가 있다.
-클라이언트 클래스 구조-
1.startCilent() 메소드
start 버튼을 클릭하면 실행되는 메소드. startClient() 메소드에는 Socket 생성 및 연결 요청 코드가 있다.
2.stopClient() 메소드
stop 버튼을 클릭하거나 서버 통신이 안 될 경우 실행되는 메소드. stopClient() 메소드에는 Socket를 닫는 close() 메소드 호출 코드가 있다.
3.receive() 메소드
receive() 메소드는 서버에서 보낸 데이터를 받는 역할을 한다. 이 메소드는 startClient()에서 생성한 작업 스레드상에서 호출된다.
4.send(String data) 메소드
send(String data) 메소드는 사용자가 메시지를 입력하고 send 버튼을 클릭하면 메시지를 매개값으로 호출한다. send() 메소드는 서버로 메시지를 보내는 역할을 한다.
-UDP 네트워킹-
UDP는 비연결 지향적 프로토콜이다. 비연결 지향적이란? 데이터를 주고받을 때 연결 절차를 거치지 않고, 발신자가 일방적으로 데이터를 발신하는 방식이다. 연결 과정이 없기 때문에 TCP 보다는 빠른 전송을 할 수 있지만 데이터 전달의 신뢰성은 떨어진다.
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 |
package com.hs.chap18;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class UdpSendExample {
public static void main(String[] args) throws Exception {
DatagramSocket datagramSocket = new DatagramSocket(); // DatagramSocket객체를 생성
System.out.println("[발신 시작]");
for (int i = 1; i < 3; i++) {
String data = "메시지" + i; // 2번 보냄
byte[] byteArr = data.getBytes("UTF-8"); // 문자열을 UTF-8로 인코딩하고, 배열을 얻는다.
DatagramPacket packet = new DatagramPacket(byteArr, byteArr.length, new InetSocketAddress("localhost", 5001)
// Datagram Packet 생성
);
datagramSocket.send(packet); // 패킷을 전송한다.
System.out.println("[보낸 바이트 수]: " + byteArr.length + "bytes");
}
System.out.println("[발신 종료]");
datagramSocket.close();
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 |
package com.hs.chap18;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpReceiveExample extends Thread {
public static void main(String[] args) throws Exception {
DatagramSocket datagramSocket = new DatagramSocket(5001); //5001번 포트에서 수신하는 DatagramSocket 객체 생성
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("[수신 시작]");
try {
while (true) {
DatagramPacket packet = new DatagramPacket(new byte[100], 100); //Datagram Packet를 담을 배열을 만든다.
datagramSocket.receive(packet);
String data = new String(packet.getData(), 0, packet.getLength(), "UTF-8");
System.out.println("[받은 내용: " + packet.getSocketAddress() + "]" + data);
}
} catch (Exception e) {
System.out.println("[수신 종료]");
}
}
};
thread.start();
Thread.sleep(10000);
datagramSocket.close();
}
}
|
cs |

Back-End/Java 2019. 4. 21. 18:31
-입력 스트림과 출력 스트림-
프로그램이 데이터를 입력받을 때에는 입력스트림(InputStream)이라고 부르고, 프로그램이 데이터를 보낼 때에는 출력스트림(OutputStream)이라고 부른다.
-콘솔 입출력-
콘솔은 시스템을 사용하기 위해 키보드로 입력을 받고 화면으로 출력하는 소프트웨어를 말한다. 자바는 콘솔로부터 데이터를 입력받을 때 System.in을 사용하고, 콘솔에 데이터를 출력할 때 System.out를 사용한다. 그리고 에러를 출력할 때에는 System.err을 사용한다.
-System.in 필드-
콘솔로부터 데이터를 입력받을 수 있도록 System클래스의 in정적 필드를 제공하고 있다. System.in은 InputStream 타입의 필드이므로 InputStream 변수로 참조가 가능하다.
InputStream is System.in;
read()메소드로 입력된 바이트를 읽으면 아스키 코드가 들어있다. 키보드에서 입력한 문자를 직접 얻고 싶다면 read() 메소드로 읽은 아스키 코드를 char로 타입 변환하면 된다.
char inputChar = (char) is.read();
-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 |
package com.hs.chap18;
import java.io.IOException;
import java.io.InputStream;
public class SystemInExample1 {
public static void main(String[] args) throws Exception {
System.out.println("== 메뉴 ==");
System.out.println("1. 예금 조회");
System.out.println("2. 예금 출금");
System.out.println("3. 예금 입금");
System.out.println("4. 종료 하기");
System.out.println("메뉴를 선택하세요: ");
InputStream is = System.in; //키보드 입력 스트림 얻기
char inputChar = (char) is.read(); //얻은 아스키 코드를 문자로 리턴
switch (inputChar) {
case '1':
System.out.println("예금 조회를 선택하셨습니다.");
break;
case '2':
System.out.println("예금 출금을 선택하셨습니다.");
break;
case '3':
System.out.println("예금 입금을 선택하셨습니다.");
break;
case '4':
System.out.println("종료하기를 선택하셨습니다.");
break;
}
}
}
|
cs |

-System.out 필드-
콘솔로 데이터를 출력하기 위해서 사용하는 필드. out는 PrintStream 타입의 필드이다.
OutputStream os = System.out;
-Console 클래스-
콘솔에서 입력받은 문자열을 쉽게 읽을수 있도록 하는 클래스. 이클립스에서 실행하면 System.console() 메소드는 null을 리턴하기 때문에 반드시 명령 프롬프트에서 실행해야 한다.
-Scanner 클래스-
Console 클래스는 콘솔로부터 문자열은 읽을 수 있지만 기본타입(정수,실수) 값을 바로 읽을 수는 없다. Scanner 클래스를 이용하면 콘솔로부터 기본 타입의 값을 바로 읽을 수 있다. Scanner 객체를 생성하려면 생성자에 System.in 매개값을 주면 된다.
Scanner scanner = new Scanner(System.in);
-FileInputStream-
파일로부터 바이트 단위로 읽어드릴때 사용하는 바이트 기반 입력 스트림이다. 바이트 단위로 읽기 때문에 그림, 오디오, 비디오, 텍스트 파일 등 모든 종류의 파일을 읽을 수 있다.
//첫번째 형식
FileInputStream fis = new FileInputStream("C:/Temp/image.gif"); //문자열로된 파일의 경로를 가지고 생성
//두번째 형식
File file = new File("C:/Temp/image.gif"); FileInputStream fis = new FileInputStream(file);
-FileWriter-
텍스트 데이터를 파일에 저장할 때 사용하는 문자 기반 스트림.
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 |
package com.hs.chap18;
import java.io.File;
import java.io.FileWriter;
public class FileWriterExample {
public static void main(String[] args) throws Exception {
File file = new File("C:/Temp/file.txt"); // C:/Temp/file.txt경로에 txt파일을 만든다.
FileWriter fw = new FileWriter(file, true);
fw.write("FileWriter는 한글로된" + "/r/n");
fw.write("문자열을 바로 출력할 수 있다." + "/r/n");
fw.flush(); // 출력 버퍼에 있는 데이터를 파일로 완전히 출력
fw.close(); // 파일을 닫는다.
System.out.println("파일에 저장되었습니다.");
}
}
|
cs |

Back-End/Java 2019. 4. 20. 21:13
-스트림(Stream)-
컬렉션 (배열포함)의 저장 요소를 하나씩 참조해서 람다식(함수적-스타일)로 처리할 수 있도록 해주는 반복자이다. Stream은 Iterator과 비슷한 역할을 하는 반복자이지만, 람다식으로 요소 처리 코드를 제공하는 점과 내부 반복자를 사용하므로 병렬 처리가 쉽다는점, 그리고 중간 처리와 최종 처리 작업을 수행하는 점에서 차이를 가지고 있다.
-외부 반복자-
개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드. index를 이용하는 for문 그리고 Iterator를 이용하는 while문 등이 있다.
-내부 반복자-
컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야 할 코드만 제공하는 코드 패턴을 말한다. 내부 반복자를 사용해서 얻는 장점은 컬렉션 내부에서 어떻게 요소를 반복시킬 것인가는 컬렉션에게 맞겨두고, 개발자는 요소 처리 코드에만 집중할 수 있다는 것이다.

-병렬 처리-
한 가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것을 말한다.
-스트림 파이트라인-
스트림은 데이터의 필터링, 매핑, 정렬, 그룹핑 등의 중간 처리와 합계, 평균, 카운팅, 최댓값. 최소값 등의 최종 처리를 파이프라인으로 해결한다. 파이프라인은 여러개의 스트림이 연결되어 있는 구조를 말한다. 파이프라인에서 최종 처리를 제외하고는 모두 중간 처리 스트림이다. 중간 스트림이 생성될 때 요소들이 중간 처리(필터링, 정렬 등) 되는 것이 아니라 최종 처리가 시작되기 전까지 중간 처리는 지연된다. 최종 처리가 시작되면 비로소 컬렉션의 요소가 하나씩 중간 스트림에서 처리되고 최종 처리까지 오게 된다.

-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 |
package com.hs.chap16;
public class Member {
public static int MALE = 0;
public static int FEMALE = 1;
private String name;
private int sex;
private int age;
public Member(String name, int sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public int getSex() {
return sex;
}
public int getAge() {
return age;
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 |
package com.hs.chap16;
import java.util.Arrays;
import java.util.List;
public class StreamPipelinesExample {
public static void main(String[] args) {// Arrays.asList 메소드를 사용해서 새로운 객체를 생성해 list에 넣어준다.
List<Member> list = Arrays.asList(new Member("홍길동", Member.MALE, 30), new Member("김나리", Member.FEMALE, 20),
new Member("신용권", Member.MALE, 45), new Member("박수미", Member.FEMALE, 27)
);
double ageAvg = list.parallelStream().filter(m -> m.getSex() == Member.MALE).mapToInt(Member::getAge).average()
.getAsDouble(); // 매개변수 m의 getsex()메소드를 호출해 그 값이 MALE와 같은것만 필터링하고, Member 객체를 age 값으로 매핑해 age를
// 요소로 갖는 스트림을 생성하고 그 평균을 계산하고, 그 값을 읽기위해 getAsDouble()메서드를 호출해서 그 값을 읽고,
// ageAvg에 저장한다.
System.out.println("남자 평균 나이: " + ageAvg);
}
}
|
cs |

-필터링-
필터링은 중간 처리 기능으로 요소를 걸러내는 역할을 한다. 필터링 메소드인 distinct()와 filter() 메소드는 모든 스트림이 가지고 있는 공통 메소드이다.
distinct() 메소드는 중복을 제거하는데 equals를 사용해 true면 동일한 객체로 판단하고 중복을 제거한다.
filter() 메소드는 매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링한다.
-매핑-
매핑(mapping)는 중간 처리 기능으로 스트림의 요소를 다른 요소로 대체하는 작업을 말한다.
flatMapXXX() 메소드는 요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴한다.
A -> A1,A2
B -> B1,B2
mapXXX() 메소드는 요소를 대체하는 요소로 구성된 새로운 스트림을 리턴한다.
A -> C B -> D
-정렬-
스트림은 요소가 최종 처리되기 전에 중간 단계에서 요소를 정렬해서 최종 처리 순서를 변경할 수 있다.
-루핑-
루핑은 요소 전체를 반복하는 것을 말한다. 루핑하는 메소드에는 peek(), forEach()가 있다.
peek()는 중간 처리 단계에서 전체 요소를 루핑하면서 추가적인 작업을 하기 위해 사용한다. 또한 최종 처리 메소드가 호출되어야 동작한다.
intStream .filter( a -> a%2 == 0) .peek( a -> System.out.println(a) ) .sum()
하지만 forEach()는 최종 처리 메소드이기 때문에 파이프라인 마지막에 루핑하면서 요소를 하나씩 처리한다.
-집계-
최종 처리 기능으로 요소들을 처리해서 카운팅, 합계, 평균값, 최대값, 최소값 등과 같이 하나의 값으로 산출하는 것
-수집-
스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공하고 있다.
이 메소드를 이용하면 필요한 요소만 컬렉션으로 담을 수 있고, 요소들을 그룹핑한 후 집계 할 수 있다.
List<Student> maleList = totalList.stream( )
.filter(s -> s.getSex() == Student.Sex.MALE)
.collect(Collectors.toList( ) );
-예제 및 출력결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 |
package com.hs.chap16;
public class Student {
public enum Sex {
MALE, FEMALE
}
public enum City {
Seoul, Pusan
}
private String name;
private int score;
private Sex sex;
private City city;
public Student(String name, int score, Sex sex) {
this.name = name;
this.score = score;
this.sex = sex;
}
public Student(String name, int score, Sex sex, City city) {
this.name = name;
this.score = score;
this.sex = sex;
this.city = city;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public Sex getSex() {
return sex;
}
public City getCity() {
return city;
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 |
package com.hs.chap16;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class ToListExample {
public static void main(String[] args) {
List<Student> totalList = Arrays.asList(
new Student("홍길동",10,Student.Sex.MALE),
new Student("김수애",6,Student.Sex.FEMALE),
new Student("신용권",10,Student.Sex.MALE),
new Student("박수미",6,Student.Sex.FEMALE)
);
//남학생들만 묶어 List 작성
List<Student> maleList = totalList.stream()
.filter(s->s.getSex() == Student.Sex.MALE)
.collect(Collectors.toList());
maleList.parallelStream()
.forEach(s->System.out.println(s.getName()));
System.out.println();
//여학생들만 묶어 HashSet 생성
Set<Student> femaleSet = totalList.stream()
.filter(s->s.getSex() == Student.Sex.FEMALE)
.collect(Collectors.toCollection(HashSet :: new));
femaleSet.stream()
.forEach(s-> System.out.println(s.getName()));
}
}
|
cs |

-동시성-
멀티 작업을 위해 멀티 스레드가 번갈아가며 실행하는 성질
-병렬성-
멀티 작업을 위해 멀티 코어를 이용해서 동시에 실행하는 성질
-데이터 병렬성-
데이터 병렬성은 전체 데이터를 쪼개어 서브 데이터들로 만들고 이 서브 데이터들을 병렬 처리해서 작업을 빨리 끝내는 것을 말한다. 예를 들어 쿼드 코어 CPU일 경우 4개의 서브 요소들로 나누고, 4개의 스레드가 각각의 서브 요소들을 병렬 처리한다.
-작업 병렬성-
작업 병렬성은 서로 다른 작업을 병렬 처리하는 것을 말한다.
작업 병렬성의 대표적인 예는 웹 서버이다.
웹 서버는 각각의 브라우저에서 요청한 내용을 개별 스레드에서 병렬로 처리한다.
-병렬 스트림-
parallelStream( ) 메소드는 컬렉션으로부터 병렬 스트림을 바로 리턴한다.
parallel( ) 메소드는 순차 처리 스트림을 병렬 처리 스트림으로 변환해서 리턴한다.
Back-End/Java 2019. 4. 19. 16:47
-컬렉션 프레임 워크-
객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 java.util 패키지에 컬렉션과 관련된 인터페이스와 클래스들을 포함시켜 놓은것을 컬렉션 프레임 워크라고 한다. 컬렉션 프레임 워크의 주요 인터페이스로는 List,Set,Map가 있다.
(배열을 사용하면 다수의 객체를 저장하고 삭제했을 때 해당 인덱스가 비게되고, 새로운 객체를 저장하려면 어디가 비어 있는지 확인하는 코드도 필요하기 때문이다)
-List 컬렉션-
객체를 인덱스로 관리하기 때문에 객체를 저장하면 자동 인덱스가 부여되고 인덱스로 객체를 검색, 삭제할 수 있는 기능을 제공한다. List 컬렉션은 객체 자체를 저장하는 것이 아니라 객체의 번지를 참조 한다.
-ArrayList-
일반 배열과 ArrayList는 인덱스로 객체를 관리한다는 점에서는 유사하지만, 배열은 생성할 때 크기가 고정되고 사용 중에 크기를 변경할 수 없지만, ArrayList는 저장용량(capacity)을 초과한 객체들이 들어오면 자동적으로 저장용량이 늘어난다. 빈번한 객체 삽입과 삭제가 일어나는 곳에서는 ArrayList를 사용하는 것이 바람직하지 않다. (ArrayList는 중간에 객체가 삭제되면 뒤에 있는 객체들이 전부다 한칸씩 앞으로 옮겨야 하는 등, 효율적이지 않기 때문이다.)
또한 size()는 컬렉션 타입의 길이를 확인할때 사용하고, length는 배열의 길이를 알려할때 사용한다. 즉 ArrayList에서는 length를 사용할 수 없다.
list<String> list = new ArrayList<String>(); //컬렉션 생성 list.add("홍길동"); //컬렉션에 객체를 추가 String name = list.get(0); //컬렉션에서 객체 검색, 홍길동을 바로 얻음
고정된 객체들로 구성된 List를 생성할 때는 다음과 같이 사용한다.
List<T> list = Arrays.asList(T...a); //제네릭 사용
-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 |
package com.hs.chap15;
import java.util.*; // *은 util디렉토리 안에 있는 모든 클래스를 사용하겠다는 말입니다.
public class ArrayListExample {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("java"); // add는 객체를 저장한다.
list.add("이한섭");
list.add("5");
int size = list.size(); // 저장된 총 객체수를 얻는다.
System.out.println("배열에 저장된 총 객체수 : " + list.size()); // add로 넣은 list에 총 3개의 문자열이 있기 때문에 3이 출력된다.
list.add(3, "영창"); // 인덱스 번호 3번에 영창이라는 문자열을 추가한다.
for (int i = 0; i < list.size(); i++) {
System.out.println("배열에 저장되어 있는 값 : " + list.get(i));
}
System.out.println("2번째 인덱스에 저장되어 있는 값 : " + list.get(2));
list.remove(1); // 1번 인덱스를 삭제
list.remove(2); // 2번 인덱스를 삭제
for (int i = 0; i < list.size(); i++) {
System.out.println("1번과 2번 인덱스 삭제한 배열에 저장되어 있는 값 : " + list.get(i));
}
}
}
|
cs |

-제네릭 타입 <E>-
타입파라미터라고 부른고,컴파일 할때 해당 타입이 일치하는지 확인할 수 있어, 좀 더 오류를 발생시키지 않고 안전하게 사용할 수 있게 하기 위해 사용한다.
-Vector-
ArrayList와 동일한 내부 구조를 가지고 있고, Vector를 생성하기 위해서는 저장할 객체 타입을 타입 파라미터로 표기하고 기본 생성자를 호출하면 된다.
ArrayList와 다른점은 동기화된(synchronized) 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 이 메소드들을 실행할수 없고,
하나의 스레드가 실행을 완료해야만 다른 스레드를 실행할 수 있다.
그렇기 때문에 멀티 스레드 환경에서 안전하게 객체를 추가, 삭제할 수 있다.
List<E> list = new Vector<E>();
-LinkedList-
LinkedList는 List 구현 클래스이므로 ArrayList와 사용방법은 같지만 내부구조는 다르다. ArrayList는 내부 배열에 객체를 저장해서 인덱스로 관리하지만, LinkedList는 인접 참조를 링크해서 체인처럼 관리한다.
LinkedList를 생성하기 위해서는 저장할 객체 타입을 타입 파라미터(E)에 표기하고 기본 생성자를 호출하면 된다. 또한 처음 생성될때는 어떠한 링크도 만들어지지 않기 때문에 내부는 비어 있다고 보면 된다.
List<E> list = new LinkedList<E>();
-Set 컬렉션-
List 컬렉션은 저장 순서를 유지하지만, Set 컬렉션은 저장 순서가 유지되지 않는다. 또한 객체를 중복해서 저장할 수 없고, 하나의 null만 저장할 수 있다. Set 컬렉션은 인덱스로 객체를 검색해서 가져오는 메소드(get())가 없다.
Iterator() 메소드 = 전체 객체를 대상으로 한번씩 반복해서 가져온다.
next() 메소드 = 하나의 객체를 가져올 때 사용.
hasNext() 메소드 = 가져올 객체가 있으면 true를 리턴하고, 더 이상 가져올 객체가 없으면 false를 리턴한다.
-HashSet-
HashSet는 객체를 저장하기 전에 먼저 hashCode()메소드를 호출해서 해시코드를 얻어낸다. 그리고 이미 저장되어 있는 객체들의 해시코드와 비교한다. 만약 동일한 해시코드가 있다면 다시 equals() 메소드로 두 객체를 비교한다. true가 나오면 동일한 객체로 판단하고 중복저장을 하지 않는다.
-Map 컬렉션-
Map 컬렉션은 키(key)와 값(value)으로 구성된 Entry 객체를 저장하는 구조를 가지고 있다. 여기서 키와 값은 모두 객체이다. 키는 중복 저장될 수 없지만 값은 중복 저장될 수 있다. 만약 기존에 저장된 키와 동일한 키로 값을 저장하면 기존의 값은 없어지고 새로운 값으로 대치된다.
ex) HashMap<String, Integer> a = new HashMap<String, Integer>(); //키와 값을 받아야 하기 때문에 제네릭 타입을 <String, Integer> 두개를 설정
a.put("one",1); //키 값을 one, value 값을 1로 설정 System.out.println(a.get("one")); //키(key)인 one를 호출하면 그 키의 값(value)인 1이 출력이 된다.
-예시-
Map 컬렉션은사우나의 물품보관함과 비슷한 개념을 가진다. - 물품보관함의 번호는 순서대로 있지만 열쇠(키)가 없으면 물품을 보관을 할 수가 없다. - 중복된 열쇠는 허용하지 않으며 물품은 중복이 가능해도 된다. •위 처럼 열쇠(키)와 물품(값)으로 구성된 자료구조를 Map계열 컬렉션이라고 한다. •Map계열에서 키는 유니크해야한다.
HashMap<Integer, String> hashMap = new HashMap<Integer, String>(); - HashMap컬렉션은 생성형태는 intger를 키로사용하고 String을 값으로 사용했다.
•hashMap.put(0, "str0"); - List계열과는 달리 put메소드를 이용해 데이터를 추가한다.
•hashMap.remove(2); - 여기서 "2"는 키를 의미한다. - 2번 키를 삭제한다.
•hashMap.clear(); - 컬렉션에 담은 키와 값을 전체 삭제한다. - 아직은 메모리에 연결된 상태로 추가를 할 수 있다.
•Iterator<Integer> iterator = hashMap.keySet().iterator(); - Iterator 인터페이스는 반복적인 데이터를 검색하는데 유용하다. - hashMap.keySet() 메소드를 이용해 반복적인 작업을 수행한다.
-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 |
package com.hs.chap15;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class HashMapExample {
public static void main(String[] args) {
// Map 컬렉션 생성
Map<String, Integer> map = new HashMap<String, Integer>(); // 맵 컬렉션으로 키와 값을 받기 위해 키는 String, 값은 Integer 타입을
// 선언한다.
// 객체 저장
map.put("신용권", 85); // 키 와 값을 저장
map.put("홍길동", 90);
map.put("동장군", 80);
map.put("홍길동", 95); // "홍길동" 키가 같기 때문에 2번째 저장한 값은 마지막에 저장한 값으로 대치된다.
System.out.println("총 Entry 수: " + map.size());
// 객체 찾기
System.out.println("/t홍길동 : " + map.get("홍길동")); // 홍길동 이라는 키로 값을 검색한다.
System.out.println();
// 객체를 하나씩 처리
Set<String> keySet = map.keySet(); // 키를 얻는 keyset 메소드로 키를 얻어서 keyset에 저장한다.
Iterator<String> keyIterator = keySet.iterator(); // 모든 키값을 불러와서 keyIterator에 저장
while (keyIterator.hasNext()) // 키를 한개씩 불러온다.
{
String key = keyIterator.next();
Integer value = map.get(key);
System.out.println("/t" + key + ":" + value); // 키 4개중에 홍길동은 위에서 중복되었기 때문에 키가 3개만 출력이 된다.
}
System.out.println();
// 객체 삭제
map.remove("홍길동"); // 키 3개중에서 1개가 삭제되었기 때문에 2개가 남는다.
System.out.println("총 Entry 수 : " + map.size()); // 남은 키는 2개이기 때문에 size()의 값은 2개가 된다.
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> entryIterator = entrySet.iterator();
while (entryIterator.hasNext()) // 위에서 처럼 키를 얻어 저장된 값을 출력한다.
{
Map.Entry<String, Integer> entry = entryIterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("/t" + key + " : " + value); //남은 키는 2개이기 때문에 해당 키에 해당되는 값 2개가 출력이 된다.
}
System.out.println();
// 객체 전체 삭제
map.clear(); //남은 2개의 키가 삭제 되었기 때문에 남은 Entry의 수는 0개가 된다.
System.out.println("총 Entry 수: " + map.size());
}
}
|
cs |

-Hashtable-
Hashtable는 HashMap와 동일한 내부 구조를 가지고 있다.
Hashtable도 키로 사용할 객체는 hashCode()와 equals() 메소드를 재정의해서 동등 객체가 될 조건을 정해야 한다.
HashMap과의 차이점은 Hashtable은 동기화된(synchronized) 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 이 메소드들을 실행할 수는 없고, 하나의 스레드가 실행을 완료해야만 다른 스레드를 실행할 수 있다.
형식 : Map<K, V> map = new Hashtable<K, V>( );
-Properties-
Properties는 Hashtable의 하위 클래스이기 때문에 Hashtable의 모든 특징을 그대로 가지고 있다.
차이점으로는 Hashtable는 키와 값을 다양한 타입으로 지정이 가능한데 비해 Properties는 키와 값을 String 타입으로
제한한 컬렉션이다.
주로 애플리케이션의 옵션 정보, 데이터베이스 연결 정보, 그리고 프로퍼티 파일을 읽을 때 주로 사용한다.
-이진 트리 구조-
여러개의 노드(node)가 트리 형태로 연결된 구조. 루트 노드라고 불리는 하나의 노드에서 시작해 각 노드에 최대 2개의 노드를 연결할 수 있는 구조를 가지고 있다. 위아래로 연결된 두 노드를 부모-자식관계에 있다고 하여 위의 노드를 부모 노드, 아래의 노드를 자식 노드라고 한다. 하나의 부모 노드는 최대 두 개의 자식 노드와 연결 될 수 있다. 또한 작은 값은 왼쪽에, 큰 값은 오른쪽에 저장한다. 숫자가 아닌 문자를 저장할 경우에는 문자의 유니코드 값으로 비교한다.
왼쪽 마지막 노드에서부터 오른쪽 마지막 노드까지[왼쪽 노드 -> 부모 노드 -> 오른쪽 노드] 순으로 읽으면 오름차순으로 정렬된 값을 얻을수 있고,
오른쪽 마지막 노드에서부터 왼쪽 마지막 노드까지[오른쪽 노드 -> 부모 노드 -> 왼쪽 노드] 순으로 읽으면 내림차순으로 정렬된 값을 얻을수 있다.

-TreeSet-
TreeSet는 이진트리를 기반으로한 Set 컬렉션이다. 하나의 노드는 노드값인 value와 왼쪽과 오른쪽 자식 노드를 참조하기 위한 두개의 변수로 구성된다.
부모값과 비교해서 낮은 것은 왼쪽 자식 노드에, 높은 것은 오른쪽 자식 노드에 저장한다.
TreeSet를 생성하기 위해서는 저장할 객체 타입을 파라미터로 표기하고 기본 생성자를 호출하면 된다.
TreeSet<E> treeSet = new TreeSet<E>( );
String 객체를 저장하는 TreeSet은 다음과 같이 생성할 수 있다.
TreeSet<String> treeSet = new TreeSet<String>( );
-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 |
package com.hs.chap15;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<Integer> scores = new TreeSet<Integer>();
scores.add(new Integer(87));
scores.add(new Integer(98));
scores.add(new Integer(75));
scores.add(new Integer(95));
scores.add(new Integer(80));
Integer score = null;
score = scores.first(); // TreeSet의 first()메소드는 가장 낮은 값을 리턴한다.
System.out.println("가장 낮은 점수: " + score);
score = scores.last(); // TreeSet의 first()메소드는 가장 낮은 값을 리턴한다.
System.out.println("가장 큰 점수: " + score);
score = scores.lower(new Integer(95)); // TreeSet의 lower()메소드는 매개값보다 낮은 객체를 리턴한다.
System.out.println("95점 아래 점수: " + score);
score = scores.higher(new Integer(95)); // TreeSet의 Integer()메소드는 매개값보다 큰 객체를 리턴한다.
System.out.println("95점 위의 점수: " + score);
score = scores.floor(new Integer(95)); // TreeSet의 floor()메소드는 매개값보다 작은 바로 아래 객체를 리턴한다.
System.out.println("95점 이거나 바로 아래 점수: " + score);
score = scores.ceiling(new Integer(85)); // TreeSet의 ceiling()메소드는 매개값보다 작은 바로 아래 객체를 리턴한다.
System.out.println("85점 이거나 바로 위의 점수: " + score);
while (!scores.isEmpty()) {
score = scores.pollFirst();
System.out.println(score + "(남은 객체 수: " + scores.size() + ")");
}
}
}
|
cs |

-TreeMap-
TreeMap는 이진 트리를 기반으로 한 Map 컬렉션이다. TreeSet와의 차이점은 키와 값이 저장된 Map.Entry를 저장한다는 점이다.
기본적으로 부모 키 값과 비교해서 키 값이 낮은 것은 왼쪽 자식 노드에, 키 값이 높은 것은 오른쪽 자식 노드에 Map.Entry 객체를 저장한다.
TreeMap<K, V> treeMap = new TreeMap<K, V>( ); // K는 키 타입, V는 값 타입
-LIFO 와 FIFO-
후입선출(LIFO)은 나중에 넣은 객체가 먼저 빠져나가는 자료구조를 말한다.
선입선출(FIFO)은 먼저 넣은 객체가 먼저 빠져나가는 구조.
-Stack-
Stack 클래스는 LIFO 자료구조를 구현한 클래스이다.
Stack<E> stack = new Stack<E>( );
-Queus-
Queue 인터페이스는 FIFO 자료구조에서 사용되는 메소드를 정의하고 있다.
다음은 Queue 인터페이스에 정의되어 있는 메소드를 보여준다.
Quesu 인터페이스를 구현한 대표적인 클래스는 LinkedList이다.
LinkedList는 List 인터페이스를 구현했기 때문에 List 컬렉션이기도 하다.
Queue<E> queue = new LinkedList<E>( );
Back-End/Java 2019. 4. 18. 18:08
-람다식-
람다식을 사용하는 이유는 자바 코드가 매우 간결해지고, 컬렉션의 요소를 필터링 하거나 매핑해서 원하는 결과를 쉽게 집계할 수 있기 때문이다.
Runnable 인터페이스의 익명 구현 객체를 생성하는 코드
Runnable runnable = new Runnable(){ public void run(){........} //익명 구현 객체 }
익명 구현 객체를 람다식으로 표현한 코드 Runnable runnable = (타입 매개변수) -> {실행문.......}; <- 람다식
-> 기호는 타입 매개 변수를 이용해서 중괄호{실행문}블록을 실행한다는 뜻으로 이해하면 된다. 만약 실행문에 return문만 있으면 중괄호는 생략할 수 있다. ex) (x,y) -> x + y
-익명 구현 객체-
-말 그대로 이름이 없는 객체. -단독 생성이 불가능 해서 클래스를 상속하거나 인터페이스를 구현해야만 생성이 가능하다. -익명 구현 객체를 사용하면 인터페이스를 구현한 클래스를 따로 만들 필요가 없게 되어 더 간결하게 인터페이스를 구현할 수 있다.
ㄴ형식 : new 클래스명() {내용} 이렇게 사용한다.
-타겟 타입-
인터페이스는 직접 객체화할 수 없기 때문에 구현 클래스가 필요한데, 람다식은 구현 클래스를 생성하고 객체화 한다. 람다식은 대입될 인터페이스의 종류에 따라 작성 방법이 달라지기 때문에 람다식이 대입될 인터페이스를 람다식의 타겟 타입이라고 한다.
-함수적 인터페이스-
람다식은 하나의 메소드만 정의하기때문에 두개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 사용해 구현 객체를 생성할 수 없다. 하나의 추상 메소드가 선언된 인터페이스만이 람다식의 타겟 타입이 될 수 있는데, 이러한 인터페이스를 함수적 인터페이스라고 한다. 함수적 인터페이스를 작성할때 컴파일러가 두 개 이상의 추상 메소드가 선언되지 않도록 체킹해주는 기능이 있는데 인터페이스 선언시 @Functionallnterface 어노테이션을 붙이면 된다. 어노테이션은 두 개 이상의 추상 메소드가 선언되면 컴파일 오류를 발생시킨다.
-예제 및 출력 결과-
|
package com.hs.chap14;
public interface MyFunctionalInterface {
public void method();
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 |
package com.hs.chap14;
public class MyFunctionalInterfaceExample {
public static void main(String[] args) {
MyFunctionalInterface fi;
fi = () -> {
String str = "method call1";
System.out.println(str);
//람다식으로 fi필드에 저장될 method메소드의 실행문을 적어준다.
};
fi.method(); //fi의 메소드를 호출하면 위에서 람다식으로 작성한 실행문이 출력된다.
fi = () -> {
System.out.println("method call2");
};
fi.method();
fi = () -> System.out.println("method call3");
fi.method();
}
}
|
cs |
-예제 및 출력결과-
(매개변수가 있는 추상메소드 정의)
|
package com.hs.chap14;
public interface MyFunctionalInterface {
//매개 변수가 있는 추상 메소드 정의
public void method(int a);
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 |
package com.hs.chap14;
public class MyFunctionalInterfaceExample {
// 추상메소드 매개변수에 들어갈 값을 설정.
private static int a = 5;
public static void main(String[] args) {
MyFunctionalInterface fi;
// 람다식으로 fi필드에 저장될 method메소드의 실행문과 매개변수를 적어준다.
fi = (a) -> {
String str = "매개변수가 들어간 람다식 실행";
System.out.println(str);
System.out.println("나의 이름은" + a + "글자 입니다.");
};
fi.method(a); // fi의 메소드를 호출하면 위에서 람다식으로 작성한 실행문이 출력된다.
}
}
|
cs |
-예제 및 출력결과-
(매개변수와 리턴값이 있는 추상메소드 정의 및 호출)
|
package com.hs.chap14;
public interface MyFunctionalInterface {
//두개의 매개 변수가 있는 추상 메소드 정의, 리턴값 없음
public int method(int a, int b);
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 |
package com.hs.chap14;
public class MyFunctionalInterfaceExample {
public static void main(String[] args) {
MyFunctionalInterface fi;
// 람다식으로 fi필드에 저장될 method메소드의 매개변수로 a,b를 선언하고, a+b의 값을 리턴해서 fi필드에 저장한다.
fi = (a, b) -> {
int result = a + b;
return result;
};
System.out.println(fi.method(5, 10)); // fi의 method에 5,10을 주고 출력하면 method가 호출되서 두수를 더한값이 출력된다.
}
}
|
cs |

-추상 메소드-
매개변수와 리턴값이 없는 메소드
-클래스 멤버와 로컬변수를 람다식으로 사용-
일반적으로 익명 객체 내부에서 this는 익명 객체의 참조이지만, 람다식에서 this는 내부적으로 생성되는 익명 객체의 참조가 아니라 람다식을 실행한 객체의 참조이다.
-디폴트 메소드-
인터페이스에 신규 메소드를 모든 구현 클래스 수정 없이 추가하고자 할때 사용한다. 인터페이스에 디폴트 메소드를 추가하고 Override하고 싶은 클래스만 구현하여 실행할 수 있다. 디폴트 메소드를 가진 인터페이스를 상속받을 때 추상 메소드로 재선언이 가능하다.
-로컬 변수-
메소드 안에 선언한 변수이고, 매소드의 매개 변수 또는 로컬 변수를 사용하면 이 변수는 final 특성을 가져야 한다. 그렇기 때문에 람다식 내부 또는 외부에서 로컬변수를 변경할 수 없다.
하지만 메소드 블록 내부에서 사용하는 것은 가능하다.
(이유 : 변수의 값이 변하는 것을 원치 않아서 메소드 내부에 변수 선언을 하게 되는데 시스템에서 이러한 특성때문에 메소드 내부에 선언된 변수는 fianl 타입으로 자동적으로 인식되서 메소드 밖에서 변환 할수가 없게끔 되는것이다.)
-Consumer 함수적 인터페이스-
리턴값이 없는 accept() 메소드를 가지고 있다. accept() 메소드는 단지 매개값을 소비하는 역할만 한다. 여기서 소비한다는 말은 사용만 할 뿐 리턴값이 없다는 뜻이다.
-Supplier 함수적 인터페이스-
매개 변수가 없고 리턴값이 있는 getXXX() 메소드를 가지고 있다. 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 한다.
-Predicate 함수적 인터페이스-
매개 변수와 boolean 리턴값이 있는 testXXXX() 메소드를 가지고 있다. 이 메소드들은 매개값을 조사해서 true 또는 false를 리턴하는 역할을 한다.
-andThen()와 compose()디폴트 메소드-
andThen()와 compose()디폴트 메소드 두개의 함수적 인터페이스를 순차적으로 연결하고, 첫 번째 처리 결과를 두 번째 매개값으로 제공해서 최종 결과값을 얻을때 사용한다.
인터페이스AB = 인터페이스A.andThen(인터페이스B); 최종결과 = 인터페이스AB.method();
andThen() 메소드는 인터페이스 AB의 method()를 호출하면 우선 인터페이스A 부터 처리하고 결과를 인터페이스B의 매개값으로 제공한다. 인터페이스B는 제공받은 매개값을 가지고 처리한 후 최종 결과를 리턴
compose() 메소드는 인터페이스AB의 method()를 호출하면 인터페이스 B부터 처리하고 결과를 인터페이스A의 매개값으로 제공한다. 인터페이스A는 제공받은 매개값을 가지고 처리한 후 최종 결과를 리턴한다.
-Predicate 종류 함수적 인터페이스-
and() 메소드 : 두 Predicate가 모두 true를 리턴하면 최종적으로 true를 리턴하는 Predicate를 생성한다.
or() 메소드 : 두 Predicate 중 하나만 true를 리턴하더라도 최종적으로 true를 리턴하는 Predicate를 생성한다.
negate() 메소드 : 원래 Predicate의 결과가 true이면 false로, false면 true를 리턴하는 새로운 Predicate를 생성한다.
isEqual() 메소드 : test() 매개값인 sourceObject와 isEqual()의 매개값인 targetObject를 Objects 클래스의 equals()의 매개값으로 제공하고, equals(sourceObject,targetObject)의 리턴값을 얻어 새로운 Predicate<T>를 생성한다.
-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 |
package com.hs.chap14;
import java.util.function.Predicate;
public class PredicateIsEqualExample {
public static void main(String[] args) {
Predicate<String> predicate;
predicate = Predicate.isEqual(null);
System.out.println("null과 null은"+predicate.test(null));
predicate = Predicate.isEqual("Java");
System.out.println("Java와 null은"+predicate.test(null));
}
}
|
cs |

-정적 메소드와 인스턴스 메소드 참조-
정적 메소드를 참조할 경우에는 클래스 이름 뒤에 : : 기호를 붙이고 정적 메소드 이름을 기입한다.
클래스 : : 메소드
인스턴스 메소드일 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 : : 기호를 붙이고 인스턴스 메소드 이름을 기술하면 된다.
참조변수 : : 메소드
-예제 및 출력 결과-
1
2
3
4
5
6
7
8
9
10
11
12 |
package com.hs.chap14;
public class Calculator {
public static int staticMethod(int x, int y) {
return x + y;
}
public int instanceMethod(int x, int y) {
return x + y;
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 |
package com.hs.chap14;
import java.util.function.IntBinaryOperator;
public class MethodReferencesExample {
public static void main(String[] args) {
IntBinaryOperator operator;
// 정적 메소드 참조
operator = (x, y) -> Calculator.staticMethod(x, y); // 람다식 사용
System.out.println("결과1: " + operator.applyAsInt(1, 2)); // x,y값에 각각 1과 2를 주어서 더한값을 출력함.
operator = Calculator::staticMethod; // staticMethod(정적메소드)를 참조
System.out.println("결과2: " + operator.applyAsInt(3, 4)); // x,y값에 각각 3과 4를 주어서 더한값을 출력함.
// 인스턴트 메소드 참조
Calculator obj = new Calculator();
operator = (x, y) -> obj.instanceMethod(x, y); // 람다식 사용
System.out.println("결과3:" + operator.applyAsInt(5, 6)); // x,y값에 5와 6을 넣어서 더한값 출력
operator = obj::instanceMethod; // instanceMethod(인스턴스 메소드)를 참조
System.out.println("결과4:" + operator.applyAsInt(7, 8)); // x,y값에 7과 8을 넣어서 더한값 출력
operator = (x, y) -> obj.instanceMethod(x, y); // 람다식 사용
System.out.println("결과3:" + operator.applyAsInt(5, 6)); // x,y값에 5와 6을 넣어서 더한값 출력
}
}
|
cs |

-중요 예제-
http://www.java2s.com/Tutorials/Java/java.util.function/Consumer/index.htm
|