28장 다른 서버로 데이터를 보내려면 어떻게 하면 되나요?
네트워크 프로그래밍
- networking: 다른 장비와 데이터를 주고 받는 작업
- OSI 7 Layer
- HTTP(Hypertext Transfer Protocol), FTP(File Transfer Protocol), Telnet은 모두 TCP 통신을 사용
애플리케이션 레이어 (HTTP, FTP, Telnet, …) |
트랜스포트 레이어 (TCP, UDP, …) |
네트워크 레이어 (IP, …) |
링크 레이어 (device driver, …) |
TCP와 UDP 차이
TCP | UDP | |
연결 | 연결형 | 비연결형 |
데이터 전송 | 보장 | 보장하지 않음 |
속도 | 느림 | 빠름 |
가격 | 비쌈 | 저렴 |
- TCP는 데이터가 전송된다는 보장을 받을 수 있지만 UDP보다 비싸고 느리며 무거움
- UDP는 데이터 전송이 보장되지는 않지만 빠르기 때문에 몇 개 정도는 유실되어도 큰 문제가 되지 않는 시스템에 사용
Port
- 웹 애플리케이션: 80
- SSL: 443
Socket 통신 (TCP)
- ServerSocket 클래스 : 서버에서 데이터를 받을 때 사용
- backlog = 큐의 개수 = 최대 대기 개수 (default = 50)
- InetAddress : 특정 주소에서만 접근이 가능하도록 지정
- ServerSocket() 생성자는 accept()라는 메소드로 연결 작업을 해야 대기 가능
[생성자]
생성자 | 설명 |
ServerSocket() | 서버 소켓 객체만 생성 |
ServerSocket(int port) | 지정된 포트를 사용하는 서버 소켓을 생성 |
ServerSocket(int port, int backlog) | 지정된 포트와 backlog 개수를 가지는 소켓을 생성 |
ServerSocket(int port, int backlog, InetAddress bindAddr) | 지정된 포트와 backlog 개수를 가지는 소켓을 생성하며, bindAddr에 있는 주소에서의 접근만을 허용 |
[메소드]
리턴타입 | 메소드 | 설명 |
Socket | accept() | 새로운 소켓 연결을 기다리고, 연결이 되면 Socket 객체를 리턴 |
void | close() | 소켓 연결을 종료 |
- Socket 클래스 : 클라이언트에서 데이터를 보낼 떄 사용 (java.net.Socket)
- 주로 Socket(String host int port) 사용
[생성자]
생성자 | 설명 |
Socket() | 소켓 객체만 생성 |
Socket(Proxy, proxy) | 프록시 관련 설정과 함께 소켓 객체만 생성 |
Socket(SocketImpl impl) | SocketImpl 객체를 사용하여 소켓 객체만 생성 |
Socket(InetAddress address, int port) | 소켓 객체 생성 후 address와 port를 사용하는 서버에 연결 |
Socket(InetAddress address, int port, InetAddress localAddr, int localPort) | 소켓 객체 생성 후 address와 port를 사용하는 서버에 연결하며, 지정한 localAddr와 localPort에 접속 |
Socket(String host, int port) | 소켓 객체 생성 후 host와 port를 사용하는 서버에 연결 |
Socket(String host, int port, InetAddress localAddr, int localPort) | 소켓 객체 생성 후 host와 port를 사용하는 서버에 연결하며, 지정된 localAddr와 localPort에 접속 |
[메소드]
리턴타입 | 메소드 | 설명 |
void | close() | 소켓 연결을 종료 |
int | getSoTimeout() | 타임아웃 시간 리턴 |
void | setSoTimeout(int timeout) | 타임아웃 설정 |
소켓 통신 예제
서버 : 소켓 대기
public class SocketServerSample{
public static void main(STring[] args){
SocketServerSample sample = new SocketServerSample();
sample.startServer();
}
public void startServer(){
ServerSocket server = null;
Socket client = null;
try{
server = new ServerSocket(9999); // 포트 9999를 사용하여 객체 생성
while(true){
System.out.println("Server: Waiting for request.");
client = server.accept(); // 다른 원격 호출을 대기, 연결이 완료되면 socker 객체를 client에 할당
System.out.println("Server: Accepted");
InputStream stream = client.getInputStream(); // 데이터를 받기 위해 InputStream 객체 받음
BufferedReader in = new BufferedReader(new InputStreamReader(stream));
String data = null;
StringBuilder receivedData = new StringBuilder();
while((data=in.readLine())!=null){
receivedData.append(data);
}
System.out.println("received data: " + receivedData);
in.close();
stream.close();
client.close(); // socket 연결 종료
if(receivedData!=null && "EXIT".equals(receivedData.toString())){
System.out.println("Stop SocketSErver");
break;
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(server!=null){
try{
server.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
}
- 데이터가 올 때마다 해당 데이터 내용을 출력하고, 계속 대기 상태로 유지
- 데이터가 EXIT라면 더 이상 대기하지 않고 프로그램 종료
클라이언트 : 데이터 전송
public class SocketClientSample{
public static void main(String[] args){
SocketClientSample sample = new SocketClientSample();
sample.sendSocketSample();
}
public void sendSocketSample(){
for(int i=0; i<3; i++){
sendSocketData("Socket Test");
}
sendSocketData("EXIT");
}
public void sendSocketData(String data){
Socket socket = null;
try{
System.out.println("Client: Connecting");
socket = new Socket("127.0.0.1", 9999); // 127.0.0.1은 같은 장비, 포트 번호는 서버와 동일
System.out.println("Client: Connect status = " + socket.isConnected());
Thread.sleep(1000);
OutputStream stream = socket.getOutputStream(); // 데이터를 전달하기 위해
BufferedOutputStream out = new BufferedOutputStream(stream);
byte[] bytes = data.getBytes();
out.write(bytes);
System.out.println("Client: Sent data");
out.close(); // 소켓 연결 종료
}catch(Exception e){
e.printStackTrace();
}finally{
if(socket!=null){
try{
socket.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
}
- sendSocketData() 메소드를 3번 호출하고 EXIT를 호출하여 프로그램 종료
예외
- java.net.BindException
: Address already in use 서버를 띄워 놓고 또 띄웠을 떄 발생, 이미 지정된 port 번호를 사용하고 있기 떄문에 동일한 port 번호를 사용 불가 - java.net.ConnectException
: Connection refused 서버를 띄워 놓지 않고 클라이언트 프로그램만 수행했을 때 발생, 받을 서버가 없으므로
UDP 통신
- UDP는 TCP와 달리 데이터의 유실이 있어도 문제 없을 때에만 사용
- DatagramSocket 클래스: 클래스 하나에서 보내는 역할과 받는 역할을 모두 수행
- DatagramPacket 클래스: 데이터를 주고받을 때 사용
DatagramSocket 클래스
[생성자]
생성자 | 설명 |
DatagramSocket() | 소켓 객체 생성 후 사용 가느한 포트로 대기 |
DatagramSocket(DatagramSocketImpl impl) | 사용자가 지정한 SocketImpl 객체를 사용하여 소켓 객체만 생성 |
DatagramSocket(int port) | 소켓 객체 생성 후 지정된 port로 대기 |
DatagramSocket(int port, InetAddress address) | 소켓 객체 생성 후 address와 port를 사용하는 서버에 연결 |
DatagramSocket(SocketAddress address) | 소켓 객체 생성 후 address에 지정된 서버로 연결 |
[메소드]
리턴타입 | 메소드 | 설명 |
void | receive(DatagramPacket packet) | 메소드 호출시 요청을 대기하고, 데이터를 받았을 땐 packet 객체에 데이터 저장 |
void | send(DatagramPacket packet) | packet 데이터 전송 |
DatagramPacket 클래스
[생성자]
생성자 | 설명 |
DatagramPacket(byte[] buf, int length) | length의 크기를 갖는 데이터를 받기 위한 객체 생성 |
DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 지정된 address와 port로 데이터를 전송하기 위한 객체 생성 |
… | … |
- DatagramPacket(byte[] buf, int legnth) 생성자만 데이터를 받기 위한 생성자 (나머지는 전송 생성자)
- buf: 전송되는 데이터
- legnth: 데이터 크기 (byte 배열의 크기보다 작으면 IllegalArgumentException 발생)
[메소드]
리턴타입 | 메소드 | 설명 |
byte[] | getData() | 전송받은 데이터를 리턴 |
int | getLength() | 전송받은 데이터의 길이를 리턴 |
UDP 통신 예제
서버 : 데이터 수신
public class DatagramServerSample{
public static void main(STring[] args){
DatagramServerSample sample = new DatagramServerSample();
sample.startServer();
}
public void startServer(){
DatagramSocket server = null;
try{
server = new DatagramSocket(9999); // 포트 9999를 사용하여 객체 생성
int bufferLength = 256;
byte[] buffer = new byte[bufferLength];
DatagramPacket packet = new DatagramPacket(buffer, bufferLength); // 데이터를 받기 위한 Packet 객체를 생성
while(true){
System.out.println("Server: Waiting for request");
server.receive(packet); // 데이터를 받기 위해 대기하다가 데이터가 넘어오면 packet에 데이터 저장
int dataLength = packet.getLength(); // 전송받은 데이터의 크기 확인
System.out.println("Server: received. length = " + dataLength);
String data = new String(packet.getData(), 0, dataLength); // String 생성자를 사용하여 byte[]를 String으로 변경
System.out.println("Received data: " + data);
if(data.eqauls("EXIT")){
System.out.println("Stop DatagramServer");
break;
}
}
}catch (Exception e){
e.printStackTrace();
}finally{
if(server!=null){
try{
server.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
}
클라이언트 : 데이터 전송
public class DatagramClientSample{
public static void main(String[] args){
DatagramClientSample sample = new DatagramClientSample();
sample.sendDatagramSample();
}
public void sendDatagramSample(){
for(int i=0; i<3; i++){
sendDatagramData("UDP Test");
}
sendDatagramData("EXIT");
}
public void sendDatagramData(String data){
try{
DatagramSocket client = new DatagramSocket();
InetAddress address = InetAddress.getByName("127.0.0.1"); // 데이터를 받을 서버의 IP 설정
byte[] buffer = data.getBytes();
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length, address, 9999); // 데이터를 전송하기 위한 객체 생성
client.send(packet); // 데이터 전송
System.out.println("Client: Sent Data");
client.close(); // 소켓 연결 종료
Thread.sleep(1000);
}catch(Exception e){
e.printStackTrace();
}
}
}
- 서버에서 데이터를 받을 준비가 되어 있지 않더라도 예외를 발생시키지 않음
웹 페이지 요청
💡 Apache의 Http Components
29장 이제 주요 API도 알아봤으니 정리해 봅시다
예약어
타입 | 설명 |
byte | 8 bit 정수형 |
short | 16 bit 정수형 |
int | 32 bit 정수형 |
long | 64 bit 정수형 |
float | 32 bit 소수형 |
double | 64 bit 소수형 |
char | 0~65,535 범위를 갖는 정수형 |
boolean | true, flase만 존재하는 타입 |
타입 | 설명 |
package | 클래스의 패키지 선언 |
import | 다른 패키지에 있는 클래스 및 그 안에 선언된 요소들 사용할 때 선언 |
interface | 인터페이스 선언 |
abstract | abstract 클래스 선언 |
class | 클래스 선언 |
enum | enum 선언 |
implements | interface 구현시 선언 |
extends | 클래스나 abstract 클래스를 확장할 때 선언 |
private | 다른 클래스에서의 접근이 불가 |
protected | 같은 패키지 내에 있거나 상속받은 경우에만 접근 가능 |
public | 누구나 접근 가능 |
final | 변수에 사용할 경우 값을 변경하지 못하도록 선언, 클래스에 사용할 경우 확장을 못하도록 선언 |
synchronized | 동시 접근 제어자 |
void | 메소드에서 반환하는 값이 없음을 선언 |
static | 하나의 인스턴스만 허용하는 제어자 |
return | 메소드를 종료하고 값을 반환할 때 사용 |
assert | 검증을 위한 로직 선언 |
native | 다른 언어로 구현된 것을 선언 |
new | 새로운 객체 생성을 선언 |
null | 참조되고 있는 객체가 없다는 것을 선언 |
strictfp | strict 소수 값 제어자 |
super | 상위 클래스 참조 |
this | 현재 객체에 대한 참조를 명시적으로 나타낼 때 사용 |
transient | Serializable할 때 저장되거나 전송되지 않는 객체 선언 |
volatile | 하나의 변수를 여러 쓰레드가 참조할 때 모두 동일한 값을 바라보도록할 때 사용 |
instanceof | 객체의 타입을 확인할 때 사용 |
타입 | 설명 |
if | 데이터의 조건을 확인할 때 사용 |
else | if 조건에 부합하지 않는 경우 선언 |
switch | 정수형 및 String 문자열의 값에 따른 분기를 할 때 사용 |
case | switch에서 서언한 변수의 값에 따른 작업을 선언 |
default | switch에서 case 조건에 부합되는 값이 없을 때 default 작업 |
for | 조건에 따른 반복을 변쉥 따라서 실행하고자 할 때 사용 |
do | 한번 수행 후 조건 반복 |
while | 조건에 따른 반복만 수행하고자 하 ㄹ떄 사용 |
continue | 반복 작업 시 조건 확인문으로 이동 |
break | 반복 작업을 종료 |
타입 | 설명 |
try | 예외가 발생 가능한 코드의 범위 선언 |
catch | try로 묶은 범위에서 예외가 발생할 때 처리 방법 선언 |
finally | try-catch 수행 후 반드시 실행해야 하는 작업 선언 |
throw | 예외를 발생시키거나 호출한 클래스로 넘길 때 사용 |
throws | 예외를 던질 수도 있다는 것을 선언 |
제네릭
형 변환시 발생할 수 있는 문제들을 사전에 없애기 위해 만든 것
wildcard <?>
public void wildcardMethod(WildcardGeneric<?> c)
public void boundedWildcardMethod(WildcardGeneric<? extends Car> c) // 타입에 제한을 둠
쓰레드 Thread
- java.lang.Runnable 인터페이스
- java.lang.Thread 클래스
synchronized
- 메소드 선언 시 사용하면, 하나의 쓰레드만 해당 메소드를 수행 가능
- synchronized block을 만들면, 동일한 객체를 공유하는 블록은 하나의 쓰레드에서만 수행 가능
자료구조와 Collection
- java.util.Collection
List
- 목록을 저장, 각 데이터에 대한 위치가 있고 순서대로 데이터를 저장
- 중복 허용
Set
- 목록을 저장, 데이터의 위치가 정해져 있지 않고 순서가 중요하지 않음
- 중복 불가
Queue
- FIFO 구조, 순차적으로 처리
- offer(), poll(), peek()
Map
- Collection 인터페이스를 확장하지 않음
- Key-Value 쌍으로 데이터를 관리
- key는 중복 불가, value는 중복 허용
I/O
stream
- InputStream : 데이터 read
- OutputStream : 데이터 write
- char 기반의 데이터는 Reader, Writer
serializable
- 객체를 다른 서버로 전송하거나 파일에 쓰고 읽기 위해서 사용하는 인터페이스
- static final long serialVersionUID : 객체에 대한 serial 버전 값을 지정
- transient: Serializable로 선언한 객체 내에 전송하거나 저장하지 않는 변수 선언
NIO
- 보다 빠른 IO
- Channel 기반으로 데이터와 연결하고 Buffer를 사용하여 데이터를 읽고 쓰기
네트워크
TCP 통신을 위한 Socket 클래스
- Socket은 데이터를 전달하고, 받기 위해 사용
- ServerSocket은 데이터를 수신
- TCP는 연결할 때부터 데이터를 받는 서버가 시작되어 있어야만 데이터를 전송 가능
UDP 통신을 위한 Datagram 클래스
- DatagramPacket 클래스는 데이터를 전달하고 받기 위해 사용
- DatagramSocket은 데이터를 수신
- UDP는 데이터를 ㅂ다는 서버가 시작 여부 및 데이터 송수신 가능 여부와 상관 없이 데이터를 전송 가능
30장 Java 7에서 달라진 것들에는?
숫자 표시 방법 보완
- Java 6까지는 8, 10, 16진수 표현 가능
- Java 7부터 2진수 표현 추가
int decVal = 1106; // 10진수
int octVal = 02122; // 8진수, 숫자 앞에 0
int hexVal = 0x452; // 16진수, 숫자 앞에 0x
int binaryVal = 0b10001010010; // 2진수, 숫자 앞에 0b
- 1000단위 표현 가능
int binaryVal = 0b0100_0101_0010;
int million = 1_000_000;
switch 문에서 String 사용
- Java 6까지는 swtich-case문에 정수형만 가능
Java 7부터 String 추가
public double salaryIncreaseAmount(String level){
switch(level){
case "CEO":
return 10.0;
case "Manager":
return 15.0;
case "Engineer":
case "Developer":
return 100.0;
}
return 0.0;
}
제네릭을 쉽게 사용할 수 있는 Diamond
- Java 6까지는 제네릭을 사용할 떄 생성자에도 해당 타입들을 명시
- Java 7부터는 명시할 필요 없음
# Java 6
HashMap<String, Integer> map = new HashMap<String, Integer>();
# Java 7
HashMap<String, Integer> map = new HashMap<>();
- 컴파일러에서 자동으로 해당 생성자의 타입을 지정
Non Reifiable varargs 타입
- 제네릭 사용 시 발생 문제
- reifiable은 실행시에도 타입 정보가 남아있는 타입
- non reifiable은 컴파일 시 타입 정보가 손실되는 타입을 의미
예외 처리시 다중 처리 가능
- Exception 조건 식에서 or (|) 사용 가능
try{
...
} catch(IllegalArgumentException | FileNotFoundException | NullPointerException exception){
exception.printStackTrace();
}
AutoClosable 인터페이스
- try-with-resource : finally에서 별도로 close()를 호출해 처리할 필요가 없음 → AutoClosable 구현
public interface Closeable extends AutoCloseable
- 이 인터페이스를 구현한 클래스들은 명시적으로 close() 메소드를 사용하여 닫을 필요가 없음
31장 Java 7에 추가된 것들에는?
Fork/Join
- Fork: 여러 개로 나누는 것
- Join: 나누어서 작업한 결과를 모으는 것
- Work Stealing: 쉬고 있는 Dequeue가 바쁜 Dequeue에 대기하고 있는 일을 가져다 해주는 작업
💡 Fork/Join 클래스를 사용하면 별도로 구현하지 않아도 해당 라이브러리에서 Work steal 작업을 알아서 수행
if(작업의 단위가 충분히 작을 경우){
해당 작업을 수행
}else{
작업을 반으로 쪼개어 두 개의 작업으로 나눔
두 작업을 동시에 실행시키고, 두 작업이 끝날 때까지 결과를 기다림
}
→ 회귀적(Recursive)으로 수행될 때 많이 사용
java.util.concurrent
RecursiveAction | public abstract class RecursiveAction extends ForkJoinTask<Void> |
RecursiveTaskt | public abstract class RecursiveTask<V> extends ForkJoinTask<V> |
구분 | RecursiveTask | RecursiveAction |
Generic | O | X |
결과 리턴 | O <V> | X |
- 두 클래스 모두 compute() 메소드 존재: 재귀 호출 → 연산 수행
Fork/Join 클라이언트 밖에서 호출 | Fork/Join 클라이언트 내에서 호출 | |
비동기적 호출 수행시 | execute(ForkJoinTask) | ForkJoinTask.fork() |
호출 후 결과 대기 | invoke(ForkJoinTask) | ForkJoinTask.invoke() |
호출 후 Future 객체 수신 | submit(ForkJoinTask) | ForkJoinTask.fork() |
NIO2
Java 6 - File 클래스
- 심볼릭 링크, 속성, 파일의 권한 등에 대한 기능이 없음
- 파일을 삭제하는 delete() 메소드는 실패시 예외 발생 X, boolean 타입의 결과만 제공
- 파일이 변경되었는지 확인은 lastModified()라는 메소드에서 제공해주는 long 타입의 결과로 이전 시간과 비교해 알 수 있었고 성능상 문제도 있음
Java 7 - File 클래스를 대체하는 클래스 (java.nio.file)
- Paths: static한 get() 메소드 제공 - Path라는 인터페이스의 객체 (파일과 경로에 대한 정보 포함)
- Files: 기존 File 클래스에서 제공되던 클래스의 단점을 보완한 클래스 → Path 객체를 사용하여 파일을 통제
- FileSystems: 현재 사용중인 파일 시스템에 대한 정보를 처리하는 데 필요한 메소드 제공 - static한 getDefault() 메소드는 현재 사용중인 기본 파일 시스템에 대한 정보를 갖고 있는 FileSystem이라는 인터페이스 객체를 얻을 수 있음
- FileStore: 파일을 저장하는 디바이스, 파티션, 볼륨 등에 대한 정보 제공
Paths 클래스
- 생성자 X
- 메소드
리턴 타입 | 메소드 | 설명 |
static Path | get(String first, String…more) | 디렉터리의 경로를 문자열로 넣어 Path 객체를 리턴 |
static Path | get(URI uri) | URI 정보를 가지고 Path 객체를 리턴 |
- Path
리턴 타입 | 메소드 | 설명 |
Path | relativize(Path other) | other과 현재 Path와의 상대 경로를 리턴 |
Path | toAbsolutePath() | 상대 경로를 절대 경로로 변경 |
Path | normalize() | 경로 상에 있는 “.”과 “..”을 지움 |
Path | resolve(String other) | 현재 Path의 가장 마지막 path로 추가 |
Files 클래스
기능 | 관련 메소드 |
복사 및 이동 | copy(), move() |
파일, 디렉터리 등 생성 | createDirectories(), createDirectory(), createFile(), createLink(), createSymbolicLink(), createTempDirectory(), createTempFile() |
삭제 | delete(), deleteIfExists() |
읽기와 쓰기 | readAllBytes(), readAllLines(), readAttributes(), readSymbolicLink(), write() |
Stream 및 객체 생성 | newBufferedReader(), newBufferedWriter(), newByteChannel(), newDirectoryStream(), newInputStream(), newOutputStream() |
각종 확인 | get으로 시작하는 메소드와 is로 시작하는 메소드(파일의 상태 확인) |
WatichService 인터페이스
- Java 6에서는 파일이 변경되었는지 확인하려면 lastModified()로 비교하는 방법밖에 없었음
- 해당 디렉터리에 파일을 생성하거나, 수정하거나 삭제하면 WatchService로 알 수 있음
SeekableByteChannel(random access)
- 바이트 기반의 채널을 처리
- 현재의 위치를 관리
- 해당 위치가 변경되는 것을 허용
→ 채널을 보다 유연하게 처리 가능
*NIO 채널: 디바이스, 파일, 네트워크 등과의 연결 상태를 나타내는 클래스
NetworkChannel 및 MulticastChannel
- NeworkChannel: 소켓을 처리하기 위한 채널
- 네트워크 연결에 대한 바인딩, 소켓 옵션을 세팅, 로컬 주소를 제공
- MulticastChannel: IP 멀티캐스트를 지원하는 네트워크 채널
- 멀티캐스트: 네트워크 주소인 IP를 그룹으로 묶고, 그 그룹에 데이터를 전송하는 방식
AsynchronousFileChannel
- 비동기 처리
- Future 객체 / CompletionHandler 인터페이스를 구현한 객체로 결과를 리턴
- CompletionHandler: completed(), failed()
- AsynchronousChannelGroup: 비동기적 처리를 하는 쓰레드 풀을 제공하여 안정적으로 처리 가능
JDBC4.1
- 버전 업그레이드
- RowSetFactory, RowSetProvider 클래스 추가
- Connection 및 Statement 객체 생성 필요 없이 SQL Query 수행
ThransferQueue 추가
- 어떤 메시지를 처리할 때 사용
- Producer/Consumer
Objects 클래스 추가
- static한 compare(), equals(), hash(), hashCode(), toString()
- 매개 변수로 넘어오는 객체가 null이어도 예외 X
32장 Java 8에 추가된 것들은?
Optional
- 객체를 편리하게 처리하기 위해 만든 클래스
- null 처리를 보다 간편하게 하기 위해 사용
public final class Optional<T> extends Object
*final 클래스는 상속이 불가능한 클래스
Optional 객체 생성하는 방법 - empty(), of(), ofNullable()
private void createOptionalObjects(){
Optional<String> emptyString = Optional.empty();
String common = null;
Optional<String> nullableString = Optional.ofNullable(common);
common = "common";
Optional<String> commonString = Optional.of(common);
}
- empty() : 데이터가 없는 Optional 객체 생성
- ofNullable() : null이 추가될 수 있는 상황
- of() : 반드시 데이터가 들어갈 수 있는 상황
Optional 클래스가 비어있는 지 확인하는 메소드 - isPresent()
private void checkOptionalData(){
System.out.println(Optional.of("present").isPresent());
System.out.println(Optional.ofNullable(null).isPresent());
}
// 결과
true
false
Optional 객체를 리턴 - get(), orElse(), orElseGet(), orElseThrow()
private void getOptionalData(Optional<String> data) throws Exception{
String defaultValue = "default";
String result1 = data.get();
String result2 = data.orElse(defaultValue);
Supplier<String> stringSupplier = new Supplier<String>(){
@Override
public String get(){
return "GodOfJava";
}
};
String result3 = data.orElseGet(stringSupplier);
Supplier<Exception> exceptionSupplier = new Supplier<Exception>(){
@Override
public Exception get(){
return new Exception();
}
};
String result4 = data.orElseThrow(exceptionSupplier);
}
- get() : Optional 데이터를 리턴, 만약 데이터가 없을 경우 null 리턴
- orElse() : 값이 없을 경우 기본 값 지정
- orElseGet() : Supplier 인터페이스 활용
- orElseThrow() : 데이터가 없을 경우 예외 발생
Default method
- 하위 호환성을 위해 나온 메소드
- 이미 존재하는 인터페이스에 새로운 메소드를 만들어야 할 때
public interface DefaultStaticInterface{
static final String name = "GodOfJavaBook";
static final int since = 2013;
String getName();
int getSince();
default String getEMail(){
return name + "@godofjava.com";
}
}
- 인터페이스는 구현된 메소드가 있으면 안되지만 default 메소드는 가능
- 구현할 때는 implements를 사용해 구현
날짜 관련 클래스들
- java.time 패키지 추가
내용 | 버전 | 패키지 | 설명 |
값 유지 | 이전 버전 | java.util.Date | Date는 날짜 계산 불가 |
java.util.Calendar | Calendar는 불변 객체가 아니므로 연산시 객체 자체가 변경됨 | ||
Java 8 | java.time.ZonedDateTime | 둘 다 불변 객체 | |
java.time.LocalDate | 모든 클래스가 연산용의 메소드를 갖고 있고 연산 시 새로운 불변 객체를 리턴, 쓰레드에 안전 | ||
변경 | 이전 버전 | java.text.SimpleDateFormat | 쓰레드에 안전하지 않고 느림 |
Java 8 | java.time.format.DateTimeFormatter | 쓰레드에 안전하고 빠름 | |
시간대 | 이전 버전 | java.util.TimeZone | “Asia/Seoul”이나 “+09:00” |
Java 8 | java.time.ZoneId | ZoneId는 “Asia/Seoul” | |
java.time.ZoneOffset | ZoneOffset은 “+09:00” | ||
속성 관련 | 이전 버전 | java.util.Calendar | Calendar.YEAR Calendar.MONTH Calendar.DATE 등 모두 int |
Java 8 | java.time.temporal.ChronoField (java.time.temproal.TemporalField) | ChronoField.YEAR ChronoField.MONTH_OF_YEAR ChronoField.DAY_OF_MONTH 등 enum 타입 | |
Java 8 | java.time.temporal.ChronoUnit (java.time.temporal.TemporalUnit) | ChronoUnit.YEARS ChronoUnit.MONTHS ChronoUnit.DAYS 등 enum 타입 |
시간을 나타내는 클래스
- LocalDate: 시간대가 없는 시간
- Offset: UTC(그리니치 시간대)와의 차이를 가지는 시간, 한국은 +09:00
- ZonedDateTime: 시간대를 갖는 시간. 한국은 “Asia/Seoul”
요일을 나타내는 클래스
- DayOfWeek → enum
DayOfWeek.MONDAY
DayOfWeek.TUESDAY
...
병렬 배열 정렬(Parallel Array Sorting)
Arrays 클래스
- static binarySearch() : 배열 내에서의 검색
- static copyOf() : 배열의 복제
- static equals() : 배열의 비교
- static fill() : 배열 채우기
- static hashCode() : 배열의 해시코드 제공
- static sort() : 정렬
- static toString() 배열 내용을 출력
- static parallelSort() : 배열 정렬
int[] values = new int[10];
Arrays.parallelSort(values);
- sort() : 단일 쓰레드 수행
- parallelSort() : 필요에 따라 여러 개의 쓰레드로 나뉘어 수행 → 처리속도가 더 빠름
개수가 많지 않은 배열에서는 parallelSort()를 사용할 필요 없음
StringJoiner
- 순차적으로 나열되는 문자열 처리
...
String[] stringArray = new String[] {"Study", "GodOfJava", "Book"};
joinStringOnlyComma(stringArray);
...
public void joinStringOnlyComma(String[] stringArray){
StringJoiner joiner = new StringJoiner(",");
for(String string : stringArray){
joiner.add(string);
}
System.out.println(joiner);
}
// 결과
Study, GodOfJava, Book
33장 Java 8에서 변경된 것들은?
Lambda 표현식
- 인터페이스에 메소드가 하나인 것들만 적용 가능(기능적 인터페이스, Functional)
- 익명 클래스와 전환이 가능
메소드가 하나인 인터페이스
- java.lang.Runnable
- java.util.Comparator
- java.io.FileFilter
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
람다 표현식
매개 변수 목록 | 화살표 토큰(Arrow Token) | 처리식 |
(int x, int y) | -> | x + y |
*x와 y 값을 받아 x+y를 리턴한다는 의미
@FunctionalInterface // 하나의 메소드만 선언 가능
interface Calculate{
int operation(int a, int b);
}
# 익명 클래스로 구현
private void calculateClassic(){
Calculate calculateAdd = new Calculate(){
@Override
public int operation(int a, int b){
return a+b;
}
};
System.out.println(calculateAdd.operation(1,2));
}
# 람다 표현식으로 구현
private void calculateLambda(){
Calculate calculateAdd = (a, b) -> a+b;
System.out.println(calculateAdd.operation(1,2));
}
java.util.function 패키지 - Functional Interface
Predicate 인터페이스
- test(): 두 개의 객체를 비교, boolean 리턴
- default method: and(), negate(), or()
- static method: isEqual()
// 데이터가 해당 조건에 맞는지 확인
private void predicateTest(Predicate<String> p, String data){
System.out.println(p.test(data));
}
// 데이터가 두 개의 조건에 모두 맞는지 확인
private void predicateAnd(Predicate<String> p1, Predicate<String p2, String data){
System.out.println(p1.and(p2).test(data));
}
// 데이터가 두 개의 조건 중 하나라도 맞는지 확인
private void predicateOr(Predicate<String> p1, Predicate<String p2, String data){
System.out.println(p1.or(p2).test(data));
}
// 데이터가 조건과 다른지 확인
private void predicateNegate(Predicate<String> p, String data){
System.out.println(p.negate().test(data));
}
Supplier 인터페이스
- get(): generic를 리턴
Consumer 인터페이스
- accept(): 리턴 값이 없어 결과를 받을 일이 없을 때 사용
- default method: andThen() → 순차적인 작업 시 사용
Function
- apply(): 리턴 값 존재
- Function<T,R> → Generic 타입 2개, T는 입력 타입 R은 리턴 타입
- 변환이 필요할 때 사용
UnaryOperator: A unary operator from T → T
- apply()
- 한 가지 타입에 대하여 결과도 같은 타입일 경우 사용
BinaryOperator: A binary operator from (T, T) → T
- apply()
- 두 가지 타입에 대하여 결과가 같은 타입일 경우 사용
stream
- 연속된 정보를 처리하는 데 사용
stream의 구조
list.stream().filter(x -> x>10).count()
- stream() 스트림 생성: 컬렉션의 목록을 스트림 객체로 변환
- filter() 중개 연산: 생성된 스트림 객체를 사용하여 중개 연산 부분에서 처리, 리턴 X
- count() 종단 연산: 중개 연산에서 작업된 내용을 바탕으로 결과를 리턴
- 중개 연산은 반드시 있어야 하는 것은 아니다
stream에서 제공하는 연산의 종류
연산자 | 설명 |
filter(pred) | 데이터를 조건으로 거를 때 |
map(mapper) | 데이터를 특정 데이터로 변환 |
forEach(block) | for 루프를 수행하는 것처럼 각각의 항목을 꺼냄 |
flatMap(Flat-mapper) | 스트림의 데이터를 잘게 쪼개서 새로운 스트림 제공 |
sorted(comparator) | 데이터 정렬 |
toArray(array-factory) | 배열로 변환 |
any / all / noneMatch(pred) | 일치하는 것을 찾음 |
findFirst / Any(pred) | 맨 처음이나 순서와 상관없는 것을 찾음 |
reduce(binop) / reduce(base, binop) | 결과를 취합 |
collect(collector) | 원하는 타입으로 데이터를 리턴 |
ex) forEach()
List<StudentDTO> students = new ArrayList<>();
...
# 향상된 for문
for(StudentDTO student : students){
System.out.println(student.getName());
}
# stream의 forEach
students.stream.forEach(student -> System.out.println(student.getName());
ex) map()
students.stream().map(student -> student.getName())
.forEach(name -> System.out.println(name));
- map(student → student.getName()) : stream에서는 student.getName()의 결과를 사용하겠다(String)
메소드 참조
더블 콜론( :: ) = Method Reference
종류 | 예 |
static 메소드 참조 | ContainingClass::StaticMethodName |
특정 객체의 인스턴스 메소드 참조 | containingObject::instanceMethodName |
특정 유형의 임의의 객체에 대한 인스턴스 메소드 참조 | ContainingType::methodName |
생성자 참조 | ClassName::new |
static 메소드 참조
public class MethodReferenceSample{
public static void main(String args[]){
MethodReferenceSample sample = new MethodReferenceSample();
String[] stringArray = {"가나", "다라", "마바"};
sample.staticFerence(stringArray);
}
private static void printResult(String value){
System.out.println(value);
}
private void staticRefernce(String[] stringArray){
Stream.of(stringArray).forEach(MethodReferenceSample::printResult);
// printResult() 메소드에 String 스트림
}
}
특정 객체의 인스턴스 메소드 참조
- 변수에 선언된 메소드 호출
System.out::println
// out 변수에 println() 메소드 호출
특정 유형의 임의의 객체에 대한 인스턴스 메소드 참조
private void objectReference(String[] stringArray){
Arrays.sort(stringArray, String::compareToIgnoreCase); // 임의 객체 참조
Arrays.asList(stringArray).stream().forEach(System.out::println); // 인스턴스 메소드 참조
}
생성자 참조
임의의 인터페이스를 통해 생성자도 만들 수 있다
interface MakeString{
String fromBytes(char[] chars);
}
private void createInstance(){
MakeString makeString = String::new;
// String의 생성자 중 char[]을 매개 변수로 받는 생성자가 있기 때문에 가능
...
}
Stream map()
List<Integer> intList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
intList.stream().map(x->x*3).forEach(System.out::println);
- intList.stream().map(x : {1,2,3,4,5,6,7,8,9,10}
- ->x*3).forEach(System.out::println) : {3,6,9,12,15,18,21,24,27,30}
- map()을 사용하면 스트림에서 처리하는 값들을 중간에 변경 가능 + 객체도 변경 가능
Stream filter()
- 필요 없는 데이터나 웹 요청들을 걸러낼 때 if문 처럼 사용
studentList.stream()
.filter(student -> student.getScoreMath() > 80)
.forEach(student -> System.out.println(student.getName()));
종단 연산(Terminal) vs 중간 연산(Non-Terminal)
Terminal - 모든 값들을 한 곳으로 모으는 종단 연산
- forEach() / forEachOrdered()
- toArray()
- reduce()
- collect()
- min() / max() / count() / sum() / average()
- anyMatch() / allMatch() / noneMatch()
- findFirst() / findAny()
Non-Terminal - 각각의 값들을 처리하는 연산
- filter()
- map() / mapToInt() / mapToLong() / mapToDouble()
- flatMap() / flatMapToInt() / flatMapToLong() / flatMapToDouble()
- distinct()
- sorted()
- peek()
- limit()
- skip()
'STUDY > Java' 카테고리의 다른 글
성적 관리 프로그램 예제2 - 2021.09.08 (0) | 2023.12.09 |
---|---|
성적 관리 프로그램 예제1 - 2021.09.03 (0) | 2023.12.09 |
<자바의 신> 25장 쓰레드는 개발자라면 알아두는 것이 좋아요 (0) | 2023.12.09 |
<자바의 신> 21장 실수를 방지하기 위한 제네릭이라는 것도 있어요 (0) | 2023.12.09 |
<자바의 신> 16장 클래스 안에 클래스가 들어갈 수도 있구나 (0) | 2023.12.09 |