라면 순서대로 끓이기

위 그림처럼 라면 끓이는 Step들이 정의 되어 있다.

어떻게하면 순서대로 출력 할까?

위상 정렬을 사용하면 된다.

 

스텝 정의

public final class Step {
    private final String name;
    private final ArrayList<Step> nextSteps = new ArrayList<>();

    public Step(final String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public ArrayList<Step> getNextSteps() {
        return nextSteps;
    }

    public void addNext(final Step step) {
        this.nextSteps.add(step);
    }
}

스텝 클래스는 자신이 수행하는 이름과 다음 스텝들을 정의한다.

 

 

그래프 생성

    private static ArrayList<Step> createStepGraph() {
        final Step start = new Step("start: 시작");
        final Step water = new Step("water: 물");
        final Step boil = new Step("boil: 물끓이기");

        final Step noodle = new Step("noodle: 면");
        final Step powder = new Step("powder: 스프");
        final Step powderEtc = new Step("powderEtc: 건더기");

        final Step boilFinish = new Step("boilFinish: 물끓이기 완료");
        final Step eat = new Step("eat: 라면 잡수기");

        final Step chopsticks = new Step("chopsticks: 젓가락 세팅");
        final Step kimchi = new Step("kimchi: 김치 세팅");

        start.addNext(water);

        water.addNext(boil);

        boil.addNext(noodle);
        boil.addNext(powder);
        boil.addNext(powderEtc);

        noodle.addNext(boilFinish);
        powder.addNext(boilFinish);
        powderEtc.addNext(boilFinish);

        boilFinish.addNext(eat);
        chopsticks.addNext(eat);
        kimchi.addNext(eat);

        ArrayList<Step> steps = new ArrayList<>();

        steps.add(start);
        steps.add(water);
        steps.add(boil);
        steps.add(noodle);
        steps.add(powder);
        steps.add(powderEtc);
        steps.add(boilFinish);
        steps.add(eat);
        steps.add(chopsticks);
        steps.add(kimchi);

        return steps;
    }

그래프 생성 : 각각의 스텝들을 만들고 그 다음 스텝은 무엇인지 정의한다

 

 

위성 정렬 실행

    public static void main(String[] args) {
        ArrayList<Step> steps = createStepGraph();
        LinkedList<Step> sortedSteps = sortTopologically(steps);

        for (Step step : sortedSteps) {
            System.out.println(step.getName());
        }

    }

    private static LinkedList<Step> sortTopologically(ArrayList<Step> steps) {
        LinkedList<Step> linkedList = new LinkedList<Step>();
        HashSet<Step> discovered = new HashSet<Step>();
        for (Step step : steps) {
            topologicalSortRecursive(step, discovered, linkedList);
        }

        return linkedList;
    }

    private static void topologicalSortRecursive(Step step, HashSet<Step> discovered, LinkedList<Step> linkedList) {
        if (discovered.contains(step)) {
            return;
        }

        discovered.add(step);

        for (Step nextStep : step.getNextSteps()) {
            if (discovered.contains(nextStep)) {
                continue;
            }

            topologicalSortRecursive(nextStep, discovered, linkedList);
        }

        linkedList.addFirst(step);
    }

위 코드에서 핵심은 각각의 모든 Step들이 위상정렬을 재귀적으로 실행 한다는 것이다.

 ※ 이미 실행한 Step이 있으면 반환(discovered)

그리고 DFS 처럼 자기 자신을 add 하기 전에 다음 스텝을 먼저 add한다

 

'JAVA' 카테고리의 다른 글

java annotation  (0) 2021.03.12
JAVA final 키워드  (0) 2020.06.26

1. 시나리오

여러개의 클래스중 아래의 Person 클래스만 그리고 Person 중에서
'@시리얼할래'가 적용된 필드에 한해서 serialize 하고싶다.

@Person클래스만
public class Person {
    @시리얼할래
    private String firstName;
    @시리얼할래
    private String lastName;
    private String age;
    private String address;
}

2. custom annotation 작성

JsonSerializable(Person클래스만) annotation 만든다
아래의 옵션을 간략히 설명하자면 언제? 런타임시, 타겟은? 클래스나 인터페이스

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(TYPE)
public @interface JsonSerializable {

}

 

@JsonElement(시리얼할래) annotation 만든다
아래의 옵션을 간략히 설명하자면 언제? 런타임시, 타겟은? 필드에 적용

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target({ FIELD })
public @interface JsonElement {
    public String key() default "";
}

 

Person 클래스에 적용

@JsonSerializable
public class Person {
    @JsonElement
    private String firstName;
    @JsonElement
    private String lastName;
    @JsonElement
    private String age;

    private String address;

3. object to json 시리얼 클래스 작성

checkIfSerializable 메소드를 보면 object.getClass()를 이용해 해당 오브젝트를 가져오고
clazz.isAnnotationPresent(JsonSerializable.class)을 통해 @Person클래스만 annotation이 있는지 확인한다.
말그대로 Person 클래스인지 확인 후 아니면 런타임 Exception을 던진다.

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class ObjectToJsonConverter {
    private void checkIfSerializable(Object object) {
        if (Objects.isNull(object)) {
            throw new RuntimeException("Can't serialize a null object");
        }

        Class<?> clazz = object.getClass();
        if (!clazz.isAnnotationPresent(JsonSerializable.class)) {
            throw new RuntimeException("The class " + clazz.getSimpleName() + " is not annotated with JsonSerializable");
        }
    }
}

 

getJsonString 메소드는 object의 모든 필드를 찾은 뒤 @시리얼할래 annotation 있는지 확인하는 코드다.

(있으면 시리얼 실시)

private String getJsonString(Object object) throws IllegalArgumentException, IllegalAccessException {
    Class<?> clazz = object.getClass();
    Map<String, String> jsonElementsMap = new HashMap<>();
    for (Field field : clazz.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.isAnnotationPresent(JsonElement.class)) {
            jsonElementsMap.put(field.getName(), (String) field.get(object));
        }
    }

    String jsonString = jsonElementsMap.entrySet()
        .stream()
        .map(entry -> "\"" + entry.getKey() + "\":\"" + entry.getValue() + "\"")
        .collect(Collectors.joining(","));
    return "{" + jsonString + "}";
}

5. 테스트 코드 작성

첫번째는 일반 object가 시리얼이 되는지 체크하는 테스트다.(Person만 통과)
두번째는 Person object가 정상적으로 시리얼 됐는지 확인하는 테스트.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

public class JsonSerializerUnitTest {

    @Test
    public void givenObjectNotSerializedThenExceptionThrown() throws JsonSerializationException {
        Object object = new Object();
        ObjectToJsonConverter serializer = new ObjectToJsonConverter();
        assertThrows(RuntimeException.class, () -> {
            serializer.convertToJson(object);
        });
    }

    @Test
    public void givenObjectSerializedThenTrueReturned() throws JsonSerializationException {
        Person person = new Person("soufiane", "cheouati", "34");
        ObjectToJsonConverter serializer = new ObjectToJsonConverter();
        String jsonString = serializer.convertToJson(person);
        assertEquals("{\"firstName\":\"soufiane\",\"lastName\":\"cheouati\",\"age\":\"34\"}", jsonString);
    }
}

결론

여러 클래스가 있을때 시리얼 하고 싶은 클래스 필드에 "@시리얼할래" annotation만 박으면 알아서 시리얼을 해주는 공통된 약속(annotation)을 정해 놓고 코딩을 하면 프로젝트 관리할 때 편하다.

 

 

※ 출처 : www.geeksforgeeks.org/annotations-in-java/

'JAVA' 카테고리의 다른 글

그래프 DFS(위상정렬)  (0) 2021.07.20
JAVA final 키워드  (0) 2020.06.26

포큐아카데미 OOP

 

변수 앞에 final은 C나 javascript에서 사용하는 const와 같다. 변경못함! immutable!

 

 

포큐아카데미 OOP

메서드 앞에 붙는 final은 부모클래스의 메서드를 자식이 오버라이딩해서 사용 못한다는 뜻!

Athlete의 getHeight를 자식 클래스인 BasketballPlayer가 오버라이딩 하려고 하면 컴파일 오류가난다.

참고로 JAVA에서 final을 붙이면 정적바인딩으로 변한다.(기본 동작은 동적바인딩)

 

 

클래스 앞에 final 붙이면 상속을 못한다는 뜻이다.

 

final은 const와 같이 가능하면 모두 붙이는게 좋다

'JAVA' 카테고리의 다른 글

그래프 DFS(위상정렬)  (0) 2021.07.20
java annotation  (0) 2021.03.12

+ Recent posts