본문 바로가기

STUDY/Java

<자바의 신> 28장 다른 서버로 데이터를 보내려면 어떻게 하면 되나요?

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()