반응형

ASCII code를 활용하여 정수형 데이터를 문자열로 변경 할 수 있다.

 

 

정수형 데이터를 char형으로 변환해보기

다음 예제를 보자.

 

int의 자료형을 바로 chat에 넣으면?

public class intTochar {

	public static void main(String[] args) {
		int n = 65;
		char test = (char) n;
		System.out.println(test);
	}
}

결과값은 A가 찍힌다.

65의 정수값은 A가 나온다.

타입캐스팅을 처리하여 65의 정수 데이터를 처리하니 A가 나왔다.

65, A 아스키코드표의 데이터이다.

 

구글링에서 아스키코드표를 검색해보면 각  진수별 아스키코드 데이터를 확인 할 수 있다.

10진법 65의 아스키코드가 A이기 때문에 test변수의 값은 A가 나오는 것을 볼 수 있다.

 

여기서 우리가 원하는 숫자 데이터를 변환하려면 어떻게 하면 될까?

아스키코드값의 '0'에 해당하는 값만큼 더해주고 시작하면 된다.

 

아스키코드값 더하기

public class intTochar {

	public static void main(String[] args) {
		int n = 1;
		char test = (char) (n + '0');
		System.out.println(test);
	}
}

원하는 1의 값이 나왔다.

아까와의 차이가 있다면 '0'을 더해주는 작업이 추가되었다.

아스키코드의 '0'의 값은 10진수 값 48이다.

 

실제로 48을 더해주면 어떻게 될까?

public class intTochar {

	public static void main(String[] args) {
		int n = 1;
		char test = (char) (n + 48);
		System.out.println(test);
	}
}

동일한 1을 얻는다.

예상한대로 동일한 값을 얻을 수 있다.

char test = (char) (n + '0'); 이 부분에서 +'0'은 실제로 48을 더해준 값이기 때문이다.

 

 

Character.forDigit()

타입캐스팅방식이 아닌 메소드를 통해 형변환도 가능하다.

public class intTochar {

	public static void main(String[] args) {
		int REDIX = 10; // redix 10은 십진수이고 hexa는 redix 16입니다.  
		int a = 5 ;    
		char c = Character.forDigit (a, REDIX);   
		System.out.println(c);
	}
}

메소드를 활용

 

10이 넘어가면?

10이 넘어가면 어떻게 될까?

public class intTochar {

	public static void main(String[] args) {
		int n = 10;
		char test = (char) (n + '0');
		System.out.println(test);
	}
}

: 이라니!

아스키코드표를 계속 언급하였으니, 눈치채신분들은 이미 알고 있었겠지만 당연히 10부터는 나오지 않는다.

'0'은 48이라고 하였다. 10 + 48의 아스키코드표의 값은 ':'이므로 당연히 저런 결과가 나온다.

 

애초에 10의 값을 문자열 형태로 표현하고 싶어서 char를 접근하였다면... 잘 못된 접근이므로 정수형을 문자열로 변경하는방법을 찾는것이 더 빠르고 효율적이였을겁니다.

 

반응형
반응형

자바는 자주 쓰이는 형식의 함수형 인터페이스를 미리 정의해놓았다. 가능하면 매번 새롭게 정의하지 말고 해당 패키지의 인터페이스를 활용하는 것이 좋다고 하여 정리해본다.(메서드 이름의 통일화, 재사용, 유지보수 이점 등)

함수형 인터페이스

메소드

설명

java.land.Runnable

void run()

매개변수, 반환 모두 없음

Supplier<T>

T get()

매개변수는 없고 반환만 있음

Consumer<T>

void accept(T t)

Supplier와 반대

매개변수만 있고 반환은 없음

Function<T, R>

R apply(T t)

일반적인 함수. 하나의 매개변수를 받아서 결과를 반환

Predicate<T>

boolean test(T t)

조건식을 표현하는데 사용.

매개변수 1, 반환 타입은 boolean

 

매개변수가 2개인 경우 접두사 Bi를 붙인다.

함수형 인터페이스

메소드

설명

BiConsumer<T, U>

void accept(T t, U u)

두 개의 매개변수만 있고, 반환 값이 없음

BiFunction<T, U, R>

R apply(T t, U u)

일반적인 함수. 두개의 매개변수를 받아서 결과를 반환

BiPredicate<T>

boolean test(T t, U u)

조건식을 표현하는데 사용.

매개변수 2, 반환 타입은 boolean

 

* 매개변수가 3개이상인 경우엔 직접 만들어야 한다.

 

반응형
반응형

MQTT 프로토콜은 IOT, M2M을 위한 프로토콜로 최근 IOT에서 많이 사용되는 프로토콜이라고 하여 간단하게 다뤄봤습니다.

 

Publisher와 Brocker, Subscriber 구조로 나뉘어있고 Publisher는 Topic을 발행하고 Subscriber는 특정 Topic을 감시 구독합니다. Brocker는 이를 중계하는 역할을 하며 한개의 Topic에 여러 Subscriber가 존재할 수 있는 구조를 가집니다.

 

여기서 Brocker로 ActiveMQ를 사용해보았습니다.

 

ActiveMQ는 위에서도 말했듯이 메시지 브로커로 오픈소스입니다.

 

dependency 추가를하여 사용하기 위해 Maven프로젝트로 구성하여 작성하였습니다.

 

mqttv3 - dependency추가

<dependency>
  <groupId>org.eclipse.paho</groupId>
  <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
  <version>1.2.0</version>
</dependency>

 

 

MqttService.java

package com.psw.mqtts.service;

public interface MqttService{
	public String connect(String connect_url, String clientId, String username, String password, String[] subscribe);
	public String sendMsg(String topic, String msg);
	public void disConnect();
}

interface로 연결 메시지발송 종료를 담당하는 메소드를 작성하였습니다.

MQTT.java에서 상속받아 사용할 것입니다.

 

 

MQTT.java

package com.psw.mqtts.utils;

import java.util.function.Consumer;
import java.util.function.Function;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import com.psw.mqtts.service.MqttService;

public class MQTT implements MqttCallback, MqttService{
	private String receiveMsg;
	private String inputMsg;
	private MqttClient mqttClient;
	
	private Consumer<MQTT> receiver;
	private Consumer<String> delivery;
	private Consumer<String> lost;
	
	final static Function<Object, Boolean> STRING_NVL = str -> str != null && !((String) str).trim().equals("") ? false : true;
	
	final static Function<Object, Boolean> STRING_ARR_NVL = str -> str != null && ((String[]) str).length > 0 ? true : false;
	
	public MQTT() {
		this.receiveMsg = null;
		this.inputMsg = null;
		this.mqttClient = null;
		this.receiver = null;
		this.delivery = null;
		this.lost = null;
	}

	public void setReceiver(Consumer<MQTT> receiver) {
		this.receiver = receiver;
	}

	
	public void setDelivery(Consumer<String> delivery) {
		this.delivery = delivery;
	}
	
	
	public void setLost(Consumer<String> lost) {
		this.lost = lost;
	}


	public String getInputMsg() {
		return inputMsg;
	}

	
	public void setInputMsg(String inputMsg) {
		this.inputMsg = inputMsg;
	}
	
	
	public String getReceiveMsg() {
		return receiveMsg;
	}
	
	
	public void setReceiveMsg(String receiveMsg) {
		this.receiveMsg = receiveMsg;
	}
	
	
	public MqttClient getMqttClient() {
		return mqttClient;
	}
	
	
	public void setMqttClient(MqttClient mqttClient) {
		this.mqttClient = mqttClient;
	}
	
	
	/**
	 * mqtt 연결처리 함수
	 * @param connect_url 연결정보
	 * @param clientId 연결 클라이언트 ID
	 * @param username 유저정보
	 * @param password 비밀번호
	 * @param subscribe 구독할 토픽 배열 //데이터가 없으면 구독을 하지 않음
	 * @return Map
	 */
	@Override
	public String connect(String connect_url, String clientId, String username, String password, String[] subscribe) {
		String res = "";
		if(STRING_NVL.apply(connect_url)) { return "연결정보를 입력해주세요.";}
		if(STRING_NVL.apply(clientId)) { return "클라이언트 ID를 입력해주세요.";}
		if(STRING_NVL.apply(username)) { return "유저정보를 입력해주세요.";}
		if(STRING_NVL.apply(password)) { return "비밀번호를 입력해주세요.";}
		
		if(mqttClient == null) {
			try {
				mqttClient = new MqttClient(connect_url, clientId);
				MqttConnectOptions connOpts = new MqttConnectOptions();
				connOpts.setUserName(username);
				connOpts.setPassword(password.toCharArray());
				
				mqttClient.connect(connOpts);
    	        mqttClient.setCallback(this);
    	        if(STRING_ARR_NVL.apply(subscribe)) {
    	        	mqttClient.subscribe(subscribe);
    	        }
    	        setMqttClient(mqttClient);
    	        res = "정상정으로 연결 되었습니다.";
			} catch (MqttException e) {
				e.printStackTrace();
				return e.getMessage();
			}
		}else {
			res = "기존에 연결되어 있는 정보가 존재합니다.";
		}
		return res;
	}
	
	
	/**
	 * 메시지 전송 함수
	 * @param topic 바라볼 토픽
	 * @param msg 전송할 메시지
	 * @return String
	 */
	@Override
	public String sendMsg(String topic, String msg) {
		String res = "";
		if(STRING_NVL.apply(topic)) { return "전송하고자 하는 Topic을 입력하세요."; }
		if(STRING_NVL.apply(msg)) { return "전송하고자 하는 메시지를 입력하세요."; }
		if(STRING_NVL.apply(topic)) { return "연결하고자 하는 토픽 "; }
		
		if(mqttClient != null && !STRING_NVL.apply(msg)) {
			if(msg.equals("exit")) {
				disConnect();
			}else {
				try {
	        		MqttMessage message = new MqttMessage(msg.getBytes());
	        		mqttClient.publish(topic, message);
	        		res = "1";
				} catch (MqttException e) {
					e.printStackTrace();
					res = e.getMessage();
				}
			}
    	}else {
    		res = "activeMq 연결을 먼저 해주세요.";
    	}
		return res;
	}
	
	@Override
	public void disConnect() {
		if(mqttClient != null) {
			try {
				mqttClient.disconnect();
				mqttClient.close();
			} catch (MqttException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void connectionLost(Throwable cause) {
		if(lost != null) {
			lost.accept("연결이 종료되었습니다.");
		}
	}

	@Override
	public void messageArrived(String topic, MqttMessage message) throws Exception {
		String res = "topic : " + topic + "  ||  message : " + message;
		setReceiveMsg(res);
		if(receiver != null) {
			receiver.accept(this);
		}
	}

	@Override
	public void deliveryComplete(IMqttDeliveryToken token) {
		if(delivery != null) {
			delivery.accept("메시지가 정상적으로 전달되었습니다.");
		}
	}
}

의존성 추가로 생성된 MqttCallback과 MqttService를 상속받아 작성하였습니다.

receiver, delivery, lost Consumer들은 콜백함수로 구현하여 각각 사용부분에서 메소드가 동작하면 데이터를 받아와서 동작시키도록 처리하였습니다.(connectionLost, messageArrived, deliveryComplete)

 

connect메소드에서는 subscribe배열이 존재하는지 체크하고 존재한다면 구독을 할 수 있도록 설정하였습니다.

 

sendMsg메소드에서는 입력으로 들어오는 문자열값이 exit가 들어오면 종료메소드를 실행하도록하여 연결을 종료하고 닫도록 처리하였고, 그 외에는 입력 topic으로 메시지를 전달합니다.

 

 

App.java

package com.psw.mqtts.main;

import java.util.Scanner;
import java.util.function.Consumer;

import org.eclipse.paho.client.mqttv3.MqttClient;

import com.psw.mqtts.utils.MQTT;

public class App{
	
	private static String input = "";
	
    public static void main( String[] args ){
    	String res = "";
    	MQTT mqtt = new MQTT();
    	
    	Consumer<MQTT> recv = (cons)->{
    		System.out.println("Received message // " +  cons.getReceiveMsg());
    	};
    	
    	Consumer<String> deli = (cons)->{
    		System.out.println(cons);
    	};
    	
    	Consumer<String> lost = (cons)->{
    		System.out.println(cons);
    	};
    	
    	mqtt.setReceiver(recv); //메시지가 수신되면 동작
    	mqtt.setDelivery(deli); //메시지가 정상적으로 전달되면 동작
    	mqtt.setLost(lost); //연결이 끊기면 동작
    	
    	
    	// 연결정보
    	String connect_url = "tcp://localhost:61616"; // "tcp://localhost:61616"
    	String clientId = "psw_test"; 
    	String username = "admin"; 
    	String password = "admin";
    	String[] subscribe = {"psw", "kjg"};
    	
    	//activemq 연결
    	res = mqtt.connect(connect_url, clientId, username, password, subscribe);
    	System.out.println(res); //연결 메시지 출력
    	
    	//연결 후 클라이언트 정보를 가져옴
    	MqttClient mqttClient = mqtt.getMqttClient();
    	if(mqttClient != null) { //정상적으로 접근되어 클라이언트가 비어있지 않다면 메시지 발송 진행
    		Scanner sc = new Scanner(System.in); //입력을 위해 Scanner 객체 생성
        	while(input != null && !input.equals("exit")) { //exit 문자열 입력이 들어올 때까지 동작
        		System.out.println("발송하실 메시지를 입력하세요. 연결을 종료하시려면 \"exit\"를 입력하세요");
        		input = sc.nextLine(); //입력 받기
        		mqtt.setInputMsg(input); //입력 데이터 저장
        		mqtt.sendMsg("psw", mqtt.getInputMsg()); //메시지 전송하고자 하는 topic으로 발송
        	}
        	
        	//종료가 되면 Scanner 닫기
        	try {
        		sc.close();
        	}catch(Exception e) {
        		e.printStackTrace();
        	}
    	}else {
    		System.out.println("연결 할 수 없습니다.");
    	}
    	
    	//연결 종료
    	mqtt.connectionLost(new Throwable());
    	
    	//시스템 종료
    	System.exit(0);
    }
}

사용예제입니다.

 

연결 후 메시지를 출력하고 Scanner를 통해 입력받은 문자열을 특정 topic으로 전달합니다.

subscribe로 psw, kjg를 처리하여 해당 topic에 메시지가 들어오면 messageArrived메소드가 실행되고 콜백함수로 인해 receiver가 동작하면서 구독한 메시지를 출력합니다.

반응형
반응형

자바 interface로 구현된 라이브러리를 상속받아 사용하던 중 특정 메소드가 수신받으면 동작하는 메소드가 존재하였습니다.

 

메인문에서 해당 메소드가 동작하는 것을 감지하고 싶었고, 찾아본 내용은 콜백함수였습니다.

그 동안 사용한 interface의 특징을 제대로 파악하지 못하고 기능을 제대로 사용하지 못하고 있다는걸 느끼게 되었습니다.

 

 

콜백을 공부하면서 CALLER(호출자), CALLEE(피호출자)로 불리는 단어가 존재하였는데, 해당 단어를 가지고 정리를 하면 다음과 같습니다.

일반적인 호출 CALLER가 CALLEE에게 요청하여 수행

콜백 CALLEE가 CALLER에게 요청으로 이해하시면 될 것 같습니다.

 

 


관련 예제를 확인해보겠습니다. Scanner를 통해 메시지를 입력받고 콜백함수를 호출해보겠습니다.

 

CallBack

Interface를 활용하여 콜백 구현하기

Callee.java

package com.psw.callback;

import java.util.Scanner;

public class Callee {
	private String msg;
	private CallBack callback;
	
	@FunctionalInterface
	public interface CallBack{
		public void onGetMessage(Callee callee);
	}
	
	public Callee() {
		this.msg = "";
		this.callback = null;
	}

	public String getMsg() {
		return msg;
	}

	public void setCallback(CallBack callback) {
		this.callback = callback;
	}
	
	public void onInputMessage() {
		Scanner scanner = new Scanner(System.in);
		this.msg = ""; //초기화
		System.out.print("메시지를 입력하세요 : ");
		this.msg = scanner.nextLine();
		
		if(this.callback != null) { //callback처리
			this.callback.onGetMessage(Callee.this);
		}
	}
}

 

CallBack.java

package com.psw.callback;

public class CallBack {

	public static void main(String[] args) {
		Callee callee = new Callee();
		callee.setCallback(new Callee.CallBack() {
			
			@Override
			public void onGetMessage(Callee callee) {
				//callback
				System.out.println("입력받은 메시지 >" + callee.getMsg());
			}
		});
		
		for(int i=0; i<5; i++){ //메시지 발송을 5번까지 보낸다
			callee.onInputMessage();
		}
	}

}

 

callback이 null이 아니면 onGetMessage를 수행하고 전달합니다.

메시지를 입력해보겠습니다.

잘받아지는 모습!

메시지를 입력하는 함수만 동작시키고 있지만 override 처리한 onGetMessage에서 콜백 동작을 하면서 정상적으로 메시지를 받는 모습을 볼 수 있습니다.

 

 


Functional Interface 방식으로 구현하기

동일한 동작이지만 java8에서 추가된 함수형 인터페이스를 사용한 표현방법을 알아보겠습니다.

 

Callee.java

package com.psw.callback;

import java.util.Scanner;
import java.util.function.Consumer;


public class Callee {
	private String msg;
	private Consumer<Callee> callback;
	
	public Callee() {
		this.msg = "";
		this.callback = null;
	}

	public String getMsg() {
		return msg;
	}

	public void setCallback(Consumer<Callee> callback) {
		this.callback = callback;
	}

	public void onInputMessage() {
		Scanner scanner = new Scanner(System.in);
		this.msg = ""; //초기화
		System.out.print("메시지를 입력하세요 : ");
		this.msg = scanner.nextLine();
		
		if(this.callback != null) { //callback처리
			this.callback.accept(Callee.this);
		}
	}
}

 

CallBack.java

package com.psw.callback;

public class CallBack {

	public static void main(String[] args) {
		Callee callee = new Callee();
		callee.setCallback((arg)->{
			System.out.println(arg.getMsg());
		});
		
		for(int i=0; i<5; i++){ //메시지 발송을 5번까지 보낸다
			callee.onInputMessage();
		}
	}
}

 

동일한 동작이지만 정의부분이나 사용부분이 많이 간결해진 것을 볼 수 있습니다.

리턴값이 없는 메소드였기때문에 Consumer를 사용하여 정의하였고, 람다식을 통해 인자값만 넘겨주어 바로 표현식으로 sysout처리를 하였습니다.

 

동일하게 잘 작동!

 

반응형
반응형

납품한 스프링 프로젝트 중 오류가 발생하는 이슈가 있었습니다.

 

저장을 할때 마다 오류가 발생한다는 메시지가 출력되었고, 소스를 분석하는데, 꽤 오랜시간을 소요되었습니다. XSS악성 스크립트를 막고자 인터셉터 설정을 하였고 모든 파라미터를 확인해서 특정 키워드가 존재하면 동작을 멈추게 하도록 설정해뒀는데, 해당 키워드에서 계속 걸려서 진행이 불가한 상황이였습니다.

 

해당 스크립트에 걸려도 괜찮고 저장할 수 있는 로직을 구성하기 위해 자바스크립트단에서 문자열데이터를 utf8 바이트 배열로 변환하고 컨트롤러단에서는 바이트 배열을 바이트로 형변환 후 new String메소드를 통해 문자열로 변환시키는 방식으로 해결하였습니다.

 

문자열데이터를 바이트배열로 변경하고 자바에서 바이트배열을 문자열로 변경하기

 

javascript단

function stringToUtf8Bytes(text){
    var result = [] ;
    if(text != null){
    	for (i = 0; i<text.length; i++) {
            var c = text.charCodeAt(i);
            if (c <= 0x7f) {
                result.push(c);
            } else if (c <= 0x07ff) {
                result.push(((c >> 6) & 0x1F) | 0xC0);
                result.push((c & 0x3F) | 0x80);
            } else {
                result.push(((c >> 12) & 0x0F) | 0xE0);
                result.push(((c >> 6) & 0x3F) | 0x80);
                result.push((c & 0x3F) | 0x80);
            }
        }
    }
    return result;
}

위의 함수 파라미터에 형변환을 할 문자열을 넣으면 utf8 바이트 배열로 변환됩니다.

변환 된 데이터를 컨트롤러에 넘기시면 됩니다. (ajax또는 form태그 데이터)

 

java단

@ResponseBody
@RequestMapping(value="addMember", method = RequestMethod.POST)
public String addMember(HttpServletRequest request, HttpServletResponse response) {
    String email = request.getParameter("email");
    //변환 시작
    String[] tmpEmailArr = email != null ? email.split(",") : null;
    String convEMail = "";
    if(tmpEmailArr != null) {
    	byte[] bta = new byte[tmpEmailArr.length];
    	for(int i=0;i<tmpEmailArr.length;i++) {
    		bta[i] = Byte.parseByte(tmpEmailArr[i]);
    	}
    	convEMail = new String(bta, StandardCharsets.UTF_8);
    }
    email = convEMail;
    //변환 완료
    
    ...
    //이후 동작 처리

파라미터로 받은 데이터는 문자열 덩어리로 들어와 있을텐데 (ex: 100, 105, 205, 88, 77 ...)

,을 통해 split처리 후 각각 데이터를 byte배열에 담아준 후 new String메소드를 통해 문자열로 파싱해줍니다.

 

 

바이트배열을 문자열로 변경

test@naver.com이라는 문자열을 자바스크립트단에서 변환하여

116, 101, 115, 116, 64, 110, 97, 118, 101, 114, 46, 99, 111, 109로 변환했고

 

자바에서 해당 데이터를 받아서 파싱하는것을 볼 수 있습니다.

반응형
반응형

JAVA8을 공부하면서 참 편하면서 활용도가 높다고 생각된 것 중 가장 크게 와닿은 함수적 인터페이스에 대해 알아보겠습니다.

 

public interface Function<T, R> { … }

Function 인터페이스는 파라미터 타입이 2개하나는 인자로, 하나는 리턴값으로 사용합니다. 또한, apply() 추상 메소드를 상속 받아 사용하고 기본 메소드로 andThen(), compose() 메소드를 사용합니다.

 

처음으로 사용 예시를 봤던 방식이 널 체크하는 예제였습니다.

보면서 가독성 면이나 사용법을 보면서 "와 편하다..."라고 생각이 되었습니다.

매번 com.~.~.utils에 있는 수많은 문자열 처리 함수들이 한줄 한줄로 처리 할 수 있고, 구분도 명확하게 느껴졌습니다.

 

바로 소스를 보겠습니다.

 

 

 


NVL 널체크

import java.util.function.Function;

public class FunctionC {
	
	final static Function<Object, String> NVL = str -> str != null && !((String) str).trim().equals("") ? str.toString() : "";
	
	public static void main(String[] args) {
		System.out.println("일반 함수 >" + NVL(null)); //일반 문자열 함수
		System.out.println("함수형 인터페이스" + NVL.apply(null)); //함수형 인터페이스
		
		System.out.println("일반 함수 >" + NVL("TEST"));
		System.out.println("함수형 인터페이스" + NVL.apply("TEST"));
		
		System.out.println("일반 함수 >" + NVL(""));
		System.out.println("함수형 인터페이스" + NVL.apply(""));
	}
	
	public static String NVL(String str) {
		String res = "";
		if(str != null && !str.trim().equals("")) {
			res = str;
		}
		return res;
	}
}

일반 함수와 함수형 인터페이스의 사용 비교

일반 함수 NVL과 함수형 인터페이스 NVL을 통해 비교를 해봤습니다.

그렇게 어렵지 않은 문자열이 null인지 공백값인지 확인 후 return값을 출력하는 예제입니다.

 

둘다 동일하게 동작하는 것을 볼 수 있습니다.

 

사용법에 차이가 있다면 함수형 인터페이스 Functionapply() 추상메소드를 상속받습니다.

차이점으로 코딩 줄 수의 차이는 조금 애매한 것 같습니다. 똑같이 선언하고 return에서 삼항연산자 처리를 하면 동일하기 때문입니다.

 

다른 예제를 보겠습니다.

 

 

 


문자열 숫자를 정수형으로 반환

import java.util.function.Function;

public class FunctionC {
	
	final static Function<String, Integer> toInt = str -> str.length() == 0 ? 0 : Integer.parseInt(str);
	
	public static void main(String[] args) {
		System.out.println(toInt.apply("555"));
	}
}

변환

 

문자열 숫자 데이터를 정수형으로 반환하는 예제입니다.

 

문제없이 동작하는것을 볼 수 있습니다. 하지만 문자열에는 null데이터가 존재합니다.

그럼 함수형 인터페이스에 null체크 로직을 삼항연산자 앞부분에 추가할까요? 그렇게 해도 상관은 없지만 함수형 인터페이스는 연결해서 사용이 가능합니다.

 

바로 아래처럼 말이죠.

 

 


Function.andThen()

import java.util.function.Function;

public class FunctionC {
	
	final static Function<Object, String> NVL = str -> str != null && !((String) str).trim().equals("") ? str.toString() : "";
	final static Function<String, Integer> toInt = str -> str.length() == 0 ? 0 : Integer.parseInt(str);
	
	public static void main(String[] args) {
		System.out.println(toInt.apply("555"));
		System.out.println(NVL.andThen(toInt).apply(null));
	}
}

null 데이터는 0이 나옴

null을 넣었지만 널포인트 에러가 아닌 0이 나온 것을 볼 수 있습니다.

바로 전 예제에서 만든 NVL 함수형 인터페이스를 통해 먼저 널체크를 하고 andThen() 메소드를 통해 문자열을 숫자로 변환하는 함수형 인터페이스를 연결해줍니다.

 

하지만 사용하면서 주의할 점이 있습니다.

당연한거지만 연결하는 순서도 차례대로 진행해야 합니다. 널체크를 먼저 하고 사용하고자 하는 함수형 인터페이스를 사용해야 합니다.

 

 

 

 

andThen()메소드의 잘 못된 연결방식? compose() !

import java.util.function.Function;

public class FunctionC {
	
	final static Function<Object, String> NVL = str -> str != null && !((String) str).trim().equals("") ? str.toString() : "";
	
	final static Function<Object, String> UPPERCASE = str -> ((String) str).toUpperCase();
	
	public static void main(String[] args) {
		System.out.println("andThen >" + NVL.andThen(UPPERCASE).apply("hello world!!!")); //잘 못된 문법 NULL을 먼저 체크해야 합니다.
		
		System.out.println();
		System.out.println();
		System.out.println();
		
		System.out.println("잘 못 된 문법 >" + UPPERCASE.andThen(NVL).apply(null)); //잘 못된 문법 NULL을 먼저 체크해야 합니다.
	}
}

연결 순서를 잘 못 연결하면 원하는 결과를 얻지 못한다.

 

또 다른 예제입니다. 이번엔 널체크와 문자열을 대문자로 변경하는 toUpperCase() 메소드를 연결하였습니다.

널체크를 먼저한 경우엔 문제가 없지만 아래처럼 강제로 null데이터를 꺼꾸로 입력한 경우엔 null데이터를 가지고 대문자로 변환을 하다보니 null 예외처리가 발생하는 것을 볼 수 있습니다.

 

하지만, 동작 순서를 반대로 하고 싶은 경우도 있을 것 같습니다.

처음에 시작할때 기본 메소드로 andThen()compose()가 존재한다고 했는데 여기서 compose() 메소드를 사용하면 정상적으로 동작이 됩니다.

 

 

 

 

compose()

System.out.println("잘 못 된 문법 >" + UPPERCASE.compose(NVL).apply("hello world~~~")); //잘 못된 문법 NULL을 먼저 체크해야 합니다.

compose()는 동작한다.

 

기본 null에러 발생하던부분의 andThen()compose()로만 변경해주었습니다.

andThen()의 방식이 1 -> 2 라면 compose()는 2 -> 1로 동작합니다.

즉, 내부에 있는 NVL 인터페이스가 먼저 동작하고 UPPERCASE 인터페이스가 동작해서 정상적으로 hello world~~~이 대문자로 변경되었습니다.

 

반응형
반응형

https://myhappyman.tistory.com/77

 

JAVA 8 - Stream 사용하기 - 1(filter, 중복제거)

자바 8에서는 Stream이 추가되었는데, 이 기술은 컬렉션이나 배열등을 람다식으로 표현하여 간결하고 가독성 좋은 코드를 만들어주고 복잡하고 많은 양의 코드를 원하는 인스턴스끼리 조합하여 필터링을 해주는등..

myhappyman.tistory.com

https://myhappyman.tistory.com/78

 

JAVA 8 - Stream 사용하기 - 2(sorted 데이터 정렬)

Stream 사용하기 1에 이어서 이번엔 Stream의 sorted를 사용해보겠습니다. 배열, 컬렉션에 담긴 데이터를 정렬하는 예제를 보겠습니다. sorted String배열 정렬하기. String[] animals = {"rabbit", "fox", "cat",..

myhappyman.tistory.com

 


 

1, 2에서 Stream을 통해 필터링, 중복제거, 정렬등을 봤는데, 사실 사용하면서 최종연산까지는 하지 않고 Stream객체에 담아두고 해당 데이터를 확인할때 최종연산인 forEcah를 사용해서 데이터를 확인했다.

 

Stream에는 최종연산이 존재하는데 이를 통해 Stream을 완전히 닫아버리고 종료하게 된다.

즉, Stream은 재사용이 불가하다.

 

Stream의 최종연산자들의 사용법에 대해 알아보겠습니다.

 


count

public static void main(String[] args) {
	//count
	List<String> words = Arrays.asList("book", "desk", "keyboard", "mouse", "cup");
	int count = (int) words.stream().filter(w->w.contains("o")).count();
	System.out.println("count >" + count);
}

"o"가 포함된 Stream의 개수를 찾습니다.

 

min

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//min
	int min = cal.stream().min(Comparator.comparing(x-> x)).get();
	System.out.println("min >" + min);
}

Integer 컬렉션의 최소값을 가져옵니다.

 

max

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//max
	int max = cal.stream().max(Comparator.comparing(x-> x)).get();
	System.out.println("max >" + max);
}

Integer 컬렉션의 최대값을 가져옵니다.

 

reduce

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//reduce
	Integer reduce = cal.stream().reduce((x, y) -> x+y).get();
	System.out.println("reduce >" + reduce);
}

누적된 값을 가져옵니다.

 

collect

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//collect
	List<Integer> newList = cal.stream().filter(x-> x > 80).collect(Collectors.toList());
	newList.forEach(System.out::println);
}

스트림 객체를 원하는 컬렉션형태로 파싱해줍니다.

 

forEach

public static void main(String[] args) {
	List<Integer> cal = Arrays.asList(49, 123, 22, 55, 21);
	//forEach
	cal.stream().forEach(x-> System.out.print(x + "  "));
}

반복문입니다. 컬렉션의 데이터를 각각 호출해줍니다.

반응형
반응형

Stream 사용하기 1에 이어서 이번엔 Stream의 sorted를 사용해보겠습니다.

 

배열, 컬렉션에 담긴 데이터를 정렬하는 예제를 보겠습니다.

 

sorted

String배열 정렬하기.

String[] animals = {"rabbit", "fox", "cat", "tiger", "dog"};
Stream.of(animals).sorted().forEach(System.out::println);

정렬된 문자열 스트림

배열데이터에는 stream()을 바로 사용할 수 없으니 Stream.of(T[]) 를통해 스트림을 생성하고 그것을 sorted()를 연결하여 정렬한 데이터를 보여줍니다.

 

Stream.of() 가 싫다면 아래처럼 Arrays.asList()를 통해 배열을 LIST화 시키고 해당 데이터를 정렬합니다.

String[] animals = {"rabbit", "fox", "cat", "tiger", "dog"};
Arrays.asList(animals).stream().sorted().forEach(System.out::println);

동일한 결과를 얻을 수 있습니다.

 

원본 데이터는 영향을 받지 않는다.

여기서 주의할 것은 "원본데이터인 animals라는 배열이 정렬되는게 아니다." 라는 점입니다.

배열데이터가 정렬된게 아닌 새로운 스트림데이터에 정렬된 데이터가 들어있습니다.

String[] animals = {"rabbit", "fox", "cat", "tiger", "dog"};
Arrays.asList(animals).stream().sorted().forEach(System.out::println);

System.out.println();System.out.println();
System.out.println("당연히 원본 배열의 정렬은 그대로이다.");
System.out.println();System.out.println();

Arrays.asList(animals).forEach(System.out::println);

animals 배열은 영향받지 않는다.

 

 

VO 객체 배열을 정렬하기

vo형태로 들어가있는 배열 데이터의 특정값을 기준으로 정렬을 해보겠습니다.

Student Class를 정의합니다.

Student.java

public class Student {
	private String name;
	private int jum;
	
	public Student(String name, int jum) {
		this.name = name;
		this.jum = jum;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getJum() {
		return jum;
	}
	public void setJum(int jum) {
		this.jum = jum;
	}
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", jum=" + jum + "]";
	}
}

 

VO Array Sorted

Student[] sArr = {
		new Student("철수", 79)
		, new Student("영희", 95)
		, new Student("영수", 80)
		, new Student("민희", 85)
		, new Student("유리", 40)
		, new Student("민수", 75)};

Stream<Student> filter_Arr = Stream.of(sArr).sorted(Comparator.comparing((Student s) -> s.getName()));
filter_Arr.forEach(System.out::println);

name을 기준으로 정렬

sArr 배열에 new 생성자를 통해서 학생 6명을 넣고 정렬한 예제입니다.

Comparator 인터페이스의 comparing 메소드를 활용하여 VO객체의 특정 값을 정렬하였습니다.

특정 값은 생성자로 사용한 첫번째 파라미터 name값이 되었습니다.

 

 

여기서 궁금증이 생길수 있습니다. 반대로 정렬은 어떻게 할까요?

reversed() 메소드를 활용하면 됩니다.

Stream<Student> filter_Arr = Stream.of(sArr).sorted(Comparator.comparing((Student s) -> s.getName()).reversed());
filter_Arr.forEach(System.out::println);

반대로 정렬 reversed()

 

LIST<VO> 데이터 정렬하기

List<Student> list = new ArrayList<Student>();
list.add(new Student("철수", 79));
list.add(new Student("영희", 95));
list.add(new Student("영수", 80));
list.add(new Student("민희", 85));
list.add(new Student("유리", 40));
list.add(new Student("민수", 75));

list.stream()
	.sorted(Comparator.comparing((Student s) -> s.getJum()).reversed())
	.forEach(System.out::println);

List<Student> 정렬

네.. 배열안에 있는 VO 데이터나 List안에 있으나 큰 차이는 없습니다.

이번엔 점수가 높은 데이터부터 정렬이 되도록 출력해봤습니다.

 

마지막으로 List<HashMap<Obejct, Object> 형태 정렬하는 방법을 알아보겠습니다.

List<Map>정렬하기

먼저 값이 정수형인 데이터부터 정렬을 해보겠습니다.

List<HashMap<Object, Object>> list = new ArrayList<HashMap<Object, Object>>();
list.add(new HashMap<Object, Object>(){{put("name", "영희");put("jum", 95);}});
list.add(new HashMap<Object, Object>(){{put("name", "영수");put("jum", 80);}});
list.add(new HashMap<Object, Object>(){{put("name", "민수");put("jum", 75);}});
list.add(new HashMap<Object, Object>(){{put("name", "철수");put("jum", 79);}});
list.add(new HashMap<Object, Object>(){{put("name", "민희");put("jum", 85);}});
list.add(new HashMap<Object, Object>(){{put("name", "유리");put("jum", 40);}});
list.add(new HashMap<Object, Object>(){{put("name", "유리");put("jum", 55);}});
list.stream()
	.sorted((s1, s2) -> Integer.compare((int)s1.get("jum"), (int)s2.get("jum")))
	.forEach(System.out::println);

작은 값부터 정렬

Wrapper.class.compare를 통해 정렬을 하였습니다.

첫번째 파라미터가 앞에 있으면 작은값부터 정렬되며, 두번째 파라미터가 앞에 있으면 큰값부터 정렬이 됩니다.

 

파라미터 위치에 따른 정렬 변경

list.stream()
	.sorted((s1, s2) -> Integer.compare((int)s2.get("jum"), (int)s1.get("jum")) )
	.forEach(System.out::println);

큰 값부터 정렬

 

 

다음으로 문자열데이터가 들어간 key값이 "name"인 데이터를 정렬해보겠습니다.

compareTo()

Stream<HashMap<Object, Object>> string_list = list.stream()
			.sorted((s1, s2) -> 
				s1.get("name").toString().compareTo( s2.get("name").toString() )
			);
string_list.forEach(System.out::println);

문자열 정렬

 

반대로 정렬할때는 역시 순서를 바꿔주면 됩니다. 람다식 표현의 s1, s2의 위치를 바꿔주면 됩니다.

Stream<HashMap<Object, Object>> string_list = list.stream()
			.sorted((s1, s2) -> 
				s2.get("name").toString().compareTo( s1.get("name").toString())
			);
string_list.forEach(System.out::println);

문자열 정렬 역순

 

반응형