JSON에 대한 Java 객체 직렬화, GSON

구글의 GSON은 JSON 데이터를 Java 객체로 생성해주는 라이브러리입니다.

GSON에 대한 jar 라이브러리를 참조(필자는 gson-2.3.1.jar를 사용)합니다. 먼저 첫번째 예제는 JSON 문자열 대한 객체 직렬화입니다.

String strJSON = "{'name': 'Dip2K', 'age': 44 }";

Gson gson = new GsonBuilder().create();
Person person = gson.fromJson(strJSON, Person.class);
System.out.println(person);

strJSON에 담긴 JSON 데이터를 Person이라는 객체로 생성하고 있습니다. Person은 새롭게 정의한 클래스로 다음과 같습니다.

package tstThread;

import java.util.ArrayList;
import java.util.HashMap;

public class Person {
    private String name;
    private int age;
	
    private Car car = new Car("TEST");
	
    public Person(String name, int age) {	
        this.name = name;
        this.age = age;
    }
	
    @Override
    public String toString() {
        return "[" + name + ", " + age + "]";
    }
}

실행 결과는 다음과 같습니다.

[Dip2K, 44]

다음은 자바 객체를 JSON 데이터로 역직렬화하는 코드예입니다.

Person person = new Person("Dip2K", 44);

Gson gson = new GsonBuilder().create();
String strJson = gson.toJson(person);

System.out.println(strJson);

위의 코드에서 사용된 Person 클래스는 정의는 다음과 같습니다.

package tstThread;

import java.util.ArrayList;
import java.util.HashMap;

public class Person {
    private String name;
    private int age;
	
    private Car car = new Car("TEST");
	
    private ArrayList<String> array = new ArrayList<String>();
    private HashMap<String, Integer> map = new HashMap<String, Integer>();
	
    public Person(String name, int age) {	
        this.name = name;
        this.age = age;
		
        array.add("Card1");
        array.add("Card2");
        array.add("Card3");
		
        map.put("KEY1", 100);
        map.put("KEY2", 200);
        map.put("KEY3", 300);
    }
	
    @Override
    public String toString() {
        return "[" + name + ", " + age + ", " + array.get(1) + "]";
    }
}

새롭게 정의된 Person를 보면 내부 필드 객체로 Car 클래스 타입의 객체를 하나 더 갖고 있는데, 이 Car 클래스는 다음과 같습니다.

package tstThread;

public class Car {
    private String name;
	
    public Car(String name) {
        this.name = name;
    }
}

이 예제는 단순한 클래스 객체 뿐만이 아나리 객체 안에 또 다른 객체가 담겨 있을때에 대한 GSON의 역직렬화가 가능하다는 것을 확인하기 위함입니다. 실행해 보면 다음과 같습니다.

{"name":"Dip2K","age":44,"car":{"name":"TEST"},"array":["Card1","Card2","Card3"],"map":{"KEY2":200,"KEY1":100,"KEY3":300}}

이제 위의 예제에서 얻는 JSON 문자열을 다시 Person 객체로 직열화하는 예제입니다.

String strJSON = "{'name':'Dip2K','age':44,'car':{'name':'TEST'},'array':['Card1','Card2','Card3'],'map':{'KEY2':200,'KEY1':100,'KEY3':300}}";

Gson gson = new GsonBuilder().create();
Person person = gson.fromJson(strJSON, Person.class);
System.out.println(person);

결과는 다음처럼 직열화가 잘된것을 확인할 수 있습니다.

[Dip2K, 44, Card2]

예제로 정리하는 코틀린의 코루틴(Kotlin Coroutine)

코루틴은 스레드와 기능적으로 같지만, 스레드에 비교하면 좀더 가볍고 유연하며 한단계 더 진화된 병렬 프로그래밍을 위한 기술입니다. 하나의 스레드 내에서 여러개의 코루틴이 실행되는 개념인데, 아래의 코드는 동일한 기능을 스레드와 코루틴으로 각각 구현한 코드의 예시입니다.

Thread(Runnable {
    for(i in 1..10) {
        Thread.sleep(1000L)
        print("I'm working in Thread.")
    }
}).start()

GlobalScope.launch() {
    repeat(10) {
        delay(1000L)
        print("I'm working in Coroutine.")
    }
}

아래는 코루틴에 대해서 초점을 맞춰서 가장 간단한 코루틴의 예제입니다.

print("Start Main Thread")

GlobalScope.launch {
    delay(3000)
    print("in Coroutine ...")
}

print("End Main Thread")

코루틴은 GlobalScope.launch로 정의되며 { .. } 으로 묶은 코드가 비동기적으로 실행됩니다. 실행 결과는 다음과 같습니다.

V: Start Main Thread
V: End Main Thread
V: in Coroutine ...

다음은 비동기적으로 실행된 코루틴이 완료되어 그 결과를 반환받는 예제입니다.

GlobalScope.launch {
    launch {
        print("At Here!")
    }

    val value: Int = async {
        var total = 0
        for (i in 1..10) total += i
        total
    }.await()

    print("$value")
}

결과는 다음과 같습니다.

V: At Here!
V: 55

다음 코드 역시 비동기적으로 실행된 코루틴의 완료를 기다리고 그 결과를 반환받아 출력하는 예제입니다.

GlobalScope.launch {
    val x = doSomething()
    print("done something, $x")
}

private suspend fun doSomething():Int {
    val value: Int = GlobalScope.async(Dispatchers.IO) {
        var total = 0
        for (i in 1..10) total += i
        print("do something in a suspend method: $total")
        total
    }.await()

    return value
}

비동기적으로 실행되는 코루틴을 별도의 함수로 분리했는데, 코루틴 내부에서 실행되는 함수는 suspend로 지정해야 합니다. 위의 코드의 결과는 다음과 같습니다.

V: do something in a suspend method: 55
V: done something, 55

이번에는 2개의 코루틴을 실행하고 이 2개의 결과를 받아 출력하는 예제입니다.

print("Start...")
GlobalScope.launch(Dispatchers.Main) {
    val job1 = async(Dispatchers.IO) {
        var total = 0
        for (i in 1..10) {
            total += i
            delay(100)
        }
        print("job1")
        total
    }

    val job2 = async(Dispatchers.Main) {
        var total = 0
        for (i in 1..10) {
            delay(100)
            total += i
        }
        print("job2")
        total
    }

    val result1 = job1.await()
    val result2 = job2.await()

    print("results are $result1 and $result2")
}
print("End.")

위의 코드에서 볼 수 있는 Dispatchers.Main, Dispatchers.IO는 각각 UI 변경 등을 처리하는 메인 스레드 그리고 입출력 연산을 처리하기에 적합한 IO 스레드를 의미하며, 코루틴들은 이처럼 지정된 스레드 내에서 실행됩니다. 위 코드의 결과는 다음과 같습니다.

V: Start...
V: End.
V: job1
V: job2
V: results are 55 and 55

다음은 코루틴이 완료를 기다리기 위한 await 호출을 사용하지 않는 또다른 방법입니다.

GlobalScope.launch(Dispatchers.IO) {
    val v = withContext(Dispatchers.Main) {
        var total = 0
        for (i in 1..10) {
            delay(100)
            total += i
        }

        total
    }

    print("result: $v")
    print("Do something in IO thread")
}

withContext를 써서 새로운 코루틴을 다른 스레드에서 동기적으로 실행하도록 하는 코드입니다. 결과는 다음과 같습니다.

V: result: 55
V: Do something in IO thread

launch는 Job 객체를 반환하는데, 이를 통해 다음 예제처럼 코루틴을 중간에 중단시킬 수 있습니다.

print("start..")

val job = GlobalScope.launch() {
    repeat(10) {
        delay(1000L)
        print("I'm working.")
    }
}

runBlocking {
    delay(3000L)
    job.cancel()
}

print("stop")

실행 결과는 다음과 같습니다.

V: start..
V: I'm working.
V: I'm working.
V: stop

이번에는 Job 객체를 통해 코루틴이 완전이 종료될때까지 기다리는 예제입니다.

print("start..")

val job = GlobalScope.launch() {
    repeat(10) {
        delay(1000L)
        print("I'm working.")
    }
}

runBlocking {
    job.join()
}

print("stop")

결과는 다음과 같습니다.

V: start..
V: I'm working.
V: I'm working.
V: I'm working.
V: I'm working.
V: I'm working.
V: I'm working.
V: I'm working.
V: I'm working.
V: I'm working.
V: I'm working.
V: stop

코루틴은 정해진 시간이 되면 코루틴의 완료되지 못할지라도 중지하게 할 수 있는데, 아래의 코드가 바로 그 예입니다.

print("start")

val job = GlobalScope.launch {
    withTimeout(4000L) {
        repeat(10) {
            delay(1000L)
            print("I'm working.")
        }
    }
}

print("end")

결과는 다음과 같습니다.

V: start
V: end
V: I'm working.
V: I'm working.
V: I'm working.

코루틴은 채널(Channel)이라는 개념을 통해 코루틴에서 생성한 데이터를 또 다른 코루틴에게 전달할 수 있습니다. 아래의 코드는 코루틴에서 1~5까지의 정수에 대한 제곱값을 생성하면 생성된 정수 4개를 또 다른 코루틴에서 받아 출력하는 예입니다.

runBlocking {
    print("start")

    val channel = Channel<Int>()

    launch {
        for (x in 1..5) {
            channel.send(x * x)
        }
    }

    repeat(5) {
        val v = channel.receive()
        print("$v")
    }

    print("end")
}

결과는 다음과 같습니다.

V: start
V: 1
V: 4
V: 9
V: 16
V: 25
V: end

데이터를 생성하는 쪽이나 받는 쪽에서는 얼마나 많은 데이터를 생성할지 또는 받을지를 예측할 수 없는 경우가 대부분입니다. 데이터를 생성하는 쪽에서 채널의 close 함수를 호출하면 받는쪽에서 더 이상 데이터가 없다는 것을 인지하게 되는데, 아래는 이에 대한 코드 예입니다.

runBlocking {
    print("start")

    val channel = Channel<Int>()

    launch {
        for(x in 1..5) channel.send(x*x)
        channel.close()
    }

    for(y in channel) print("$y")

    print("end")
}

결과는 다음과 같습니다.

V: start
V: 1
V: 4
V: 9
V: 16
V: 25
V: end

다음은 데이터를 생성하는 코루틴을 함수화하여 이 함수를 통해 생성된 데이터를 처리하는 예제입니다.

runBlocking {
    print("start")

    val squares = procedureSquares()
    squares.consumeEach {
        print("$it")
    }

    print("end")
}

private fun CoroutineScope.procedureSquares(): ReceiveChannel<Int> = produce {
    for(x in 1..5) send(x*x)
}

결과는 다음과 같습니다.

V: start
V: 1
V: 4
V: 9
V: 16
V: 25
V: end

다음은 데이터를 생성하는 코루틴을 파이프라인 형태로 묶어 처리하는 것으로, 첫번째 코루틴에서 생성한 값을 또 다른 코루틴에서 받아 처리하여 또 다른 코루틴으로 전달하는 예제입니다.

runBlocking {
    print("start")

    val numbers = productNumbers()
    val squares = squares(numbers)

    for(i in 1..5) print("${squares.receive()}")

    print("end")

    coroutineContext.cancelChildren()
}

private fun CoroutineScope.productNumbers() = produce<Int> {
    var x = 1
    while(true) {
        print("send ${x} on productNumbers")
        send(x++)
        delay(100)
    }
}

private fun CoroutineScope.squares(numbers:ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
    for(x in numbers) {
        print("send ${x} on squares")
        send(x*x)
    }
}

결과는 다음과 같습니다.

V: start
V: send 1 on productNumbers
V: send 1 on squares
V: 1
V: send 2 on productNumbers
V: send 2 on squares
V: 4
V: send 3 on productNumbers
V: send 3 on squares
V: 9
V: send 4 on productNumbers
V: send 4 on squares
V: 16
V: send 5 on productNumbers
V: send 5 on squares
V: 25
V: end

데이터를 생성하는 코루틴은 1개지만, 이를 원활하게 처리하기 위해 여러개의 코루틴으로 생성된 데이터를 처리할 수 있습니다. 아래는 데이터를 생성하는 코루틴 1개와 생성된 데이터를 처리하는 5개의 코루틴에 대한 예제입니다.

runBlocking {
    val producer = productNumbers()
    repeat(5) {
        launchProcessor(it, producer)

    }

    delay(1000L)
    producer.cancel()
}

fun CoroutineScope.launchProcessor(id:Int, channel: ReceiveChannel<Int>) {
    launch {
        for(msg in channel) {
            print("Processor #$id received $msg")
        }
    }
}

private fun CoroutineScope.productNumbers() = produce<Int> {
    var x = 1
    while(true) {
        print("send ${x} on productNumbers")
        send(x++)
        delay(100)
    }
}

결과는 다음과 같습니다.

V: send 1 on productNumbers
V: Processor #0 received 1
V: send 2 on productNumbers
V: Processor #0 received 2
V: send 3 on productNumbers
V: Processor #1 received 3
V: send 4 on productNumbers
V: Processor #2 received 4
V: send 5 on productNumbers
V: Processor #3 received 5
V: send 6 on productNumbers
V: Processor #4 received 6
V: send 7 on productNumbers
V: Processor #0 received 7
V: send 8 on productNumbers
V: Processor #1 received 8
V: send 9 on productNumbers
V: Processor #2 received 9
V: send 10 on productNumbers
V: Processor #3 received 10

반대로 데이터를 생성하는 코루틴은 여러개이고 처리하는 코루틴은 1개인 경우도 있습니다. 아래는 데이터를 생성하는 코루틴 2개와 생성된 데이터를 처리하는 코루틴 1개에 대한 예제입니다.

runBlocking {
    val channel = Channel<String>()
    launch {
        sendString(channel, "foo", 200L)
    }

    launch {
        sendString(channel, "BAR", 500L)
    }

    repeat(6) {
        print("${channel.receive()}")
    }

    coroutineContext.cancelChildren()
}

private suspend fun sendString(channel: SendChannel<String>, s:String, time:Long) {
    while(true) {
        delay(time)
        channel.send(s)
    }
}

결과는 다음과 같습니다.

V: foo
V: foo
V: BAR
V: foo
V: foo
V: BAR

아래의 예제는 2개의 코루틴에서 하나의 데이터에 대해 어떤 처리를 해서 주고 받는 기능에 대한 코드입니다.

print("start")
data class Ball(var hits:Int)

suspend fun player(name:String, table: Channel) {
    for(ball in table) {
        ball.hits++
        print("$name $ball")
        delay(300)
        table.send(ball)
    }
}

runBlocking {
    var table = Channel<Ball>()

    launch {
        player("ping", table)
    }

    launch {
        player("pong", table)
    }

    table.send(Ball(0))
    delay(1000)
    coroutineContext.cancelChildren()
}
print("end")

결과는 다음과 같습니다.

V: start
V: ping Ball(hits=1)
V: pong Ball(hits=2)
V: ping Ball(hits=3)
V: pong Ball(hits=4)
V: end

ViewPropertyAnimator를 이용한 개별 뷰(View) 단위 애니메이션

안드로이드에서 하나의 뷰에 대한 특정 속성값을 애니메이션화하기 위한 API인 ViewPropertyAnimator에 대한 내용입니다. 먼저 아래와 같은 레이아웃에 애니메이션을 위한 버튼 뷰를 하나 배치합니다.

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:scrollbars="none">

    <Button
        android:id="@+id/myView"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:text="Hello" />

</ScrollView>

이제 버튼을 터치할때 애니매이션이 시작될 수 있도록 다음 코드를 추가합니다.

myView.setOnClickListener {
    myView
        .animate()
        .scaleX(5.0f)
        .scaleY(5.0f)
        .alpha(0.0f)
        .translationX(400.0f)
        .translationY(400.0f)
        .rotation(360.0f)
        .setDuration(1000)
        .withStartAction {
            Toast.makeText(this, "애니메이션 시작", Toast.LENGTH_SHORT).show()
        }
        .withEndAction {
            myView.scaleX = 1.0f;
            myView.scaleY = 1.0f;
            myView.alpha = 1.0f;
            myView.x = 0.0f;
            myView.y = 0.0f;
            myView.rotation = 0.0f;
            Toast.makeText(this, "완료", Toast.LENGTH_SHORT).show()
        }.start()
}

참고로 언어는 코들린입니다. 1초간 뷰의 크기를 5배로, 점점 투명하게, X와 Y의 위치를 각각 400과 400으로, z축으로 360회전 하도록 하는애니메이션입니다. 그 결과는 아래와 같습니다.

안드로이드의 Shape 형태의 Drawable

직사각형(Rectangle) 형태의 Shape Drawable 정의는 다음과 같다.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">
    <stroke android:width="10dp"
            android:color="#ffff00" />
    <solid android:color="@android:color/transparent" />
    <corners android:radius="50dp" />
</shape>

위 형태에 대한 결과는 다음과 같다.

실제 뷰의 배경(Background)에 적용할 수 있는데, 그 예는 다음과 같다.

<LinearLayout android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:background="@drawable/rectangle_shape_drawable"
              android:orientation="vertical">

...

Javascript의 async, await 정리

이 글을 이해하기 위해서는 먼저 Promise API에 대해 알고 있어야 하며 아래의 글을 참고하시기 바랍니다.

Javascript의 Promise API 요약

async와 awit의 사용은 비동기 처리에 대한 혼란스러운 코드의 가독성을 향상 시켜줌으로써 코드의 유지보수 및 견고한 코드를 작성할 수 있습니다.

먼저 아래의 코드는 Promise API를 이용한 비동기처리입니다.

function getItem() {
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            let result = { name: 'Dip2K', age: 44 };
            resolve(result);
        }, 2000);
    });
}

function callback(result) {
    console.log(result);
}

console.log('1');
getItem().then(callback);
console.log('2');

위의 코드를 async와 await를 이용해 동일하게 작동하도록 코드를 작성하면 다음과 같습니다.

function getItem() {
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            let result = { name: 'Dip2K', age: 44 };
            resolve(result);
        }, 2000);
    });
}

async function get() {
    let result = await getItem();
    console.log(result);
}

console.log('1');
get();
console.log('2');

콜백함수 없이 비동기처리가 된 경우로, 순차적인 흐름의 코드로 작성되었습니다.

아래의 코드는 Promise의 예외의 처리를 async 및 await 구분에서 어떻게 처리 하는지를 보여주는 코드입니다.

function getItem() {
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            let bOK = false;

            if(bOK) {
                let result = { name: 'Dip2K', age: 44 };
                resolve(result);
            } else {
                reject(null);
            }
        }, 2000);
    });
}

async function get() {
    try {
        let result = await getItem();
        console.log(result);
    } catch(e) {
        console.log(e);
    }
}

console.log('1');
get();
console.log('2');

await와 fetch를 함께 사용하는 코드 예시를 마지막으로 언급하고 마무리 합니다.

async function fetchData() {
  const response = await fetch("http:/...")
  const data = await response.json()
  cosnole.log(data)
}

fetchData()