TIL - day40

정상화·2023년 4월 18일
0

TIL

목록 보기
32/46
post-thumbnail

알고리즘

일급컬렉션


컬렉션을 클래스로 한번 더 감싼 것

장점
1. 비즈니스 로직이 관련 도메인에 캡슐화된다.
2. 불변성이 유지된다.
3. 코드 중복이 최소화된다.
4. 테스트하기에 용이해진다.

import java.util.Iterator;
import java.util.ArrayList;

public class CustomCollection<T> implements Iterable<T> {
    private ArrayList<T> items = new ArrayList<>();

    public void add(T item) {
        items.add(item);
    }

    @Override
    public Iterator<T> iterator() {
        return items.iterator();
    }
}

// 사용 예시
public class Main {
    public static void main(String[] args) {
        CustomCollection<String> customCollection = new CustomCollection<>();
        customCollection.add("item1");
        customCollection.add("item2");
        customCollection.add("item3");

        // foreach 사용
        for (String item : customCollection) {
            System.out.println(item);
        }
    }
}

리플렉션


클래스에 접근 제어자를 붙이면 테스트에서 각 메소드들을 테스트하기가 쉽지 않다.
이럴땐 리플렉션으로 메소드에 접근할 수 있다.

public class TestUtil {
    private static final Map<Class<?>, Class<?>> wrapperToPrimitiveMap = new HashMap<>();
    private static final Map<Class<?>, Class<?>> primitiveToWrapperMap = new HashMap<>();

    static {
        wrapperToPrimitiveMap.put(Integer.class, int.class);
        wrapperToPrimitiveMap.put(Long.class, long.class);
        wrapperToPrimitiveMap.put(Double.class, double.class);
        wrapperToPrimitiveMap.put(Float.class, float.class);
        wrapperToPrimitiveMap.put(Boolean.class, boolean.class);
        wrapperToPrimitiveMap.put(Character.class, char.class);
        wrapperToPrimitiveMap.put(Byte.class, byte.class);
        wrapperToPrimitiveMap.put(Short.class, short.class);

        primitiveToWrapperMap.put(int.class, Integer.class);
        primitiveToWrapperMap.put(long.class, Long.class);
        primitiveToWrapperMap.put(double.class, Double.class);
        primitiveToWrapperMap.put(float.class, Float.class);
        primitiveToWrapperMap.put(boolean.class, Boolean.class);
        primitiveToWrapperMap.put(char.class, Character.class);
        primitiveToWrapperMap.put(byte.class, Byte.class);
        primitiveToWrapperMap.put(short.class, Short.class);
    }

    public <T> T run(Object target, String methodName, Object... params) throws Exception {
        Class<?>[] paramTypes = Arrays.stream(params)
                .map(Object::getClass)
                .toArray(Class[]::new);

        List<Class<?>[]> possibleParamTypes = generatePossibleParamTypes(paramTypes);

        for (Class<?>[] possibleParamType : possibleParamTypes) {
            Optional<Method> methodOptional = findMethod(target.getClass(), methodName, possibleParamType);
            if (methodOptional.isPresent()) {
                Method method = methodOptional.get();
                method.setAccessible(true);
                return (T) method.invoke(target, params);
            }
        }

        throw new NoSuchMethodException(
                "No such method found: " + methodName + " with parameter types " + Arrays.toString(paramTypes));
    }

    private Optional<Method> findMethod(Class<?> clazz, String methodName, Class<?>[] paramTypes) {
        try {
            return Optional.of(clazz.getDeclaredMethod(methodName, paramTypes));
        } catch (NoSuchMethodException e) {
            return Optional.empty();
        }
    }

    private List<Class<?>[]> generatePossibleParamTypes(Class<?>[] paramTypes) {
        List<Class<?>[]> possibleParamTypesList = new ArrayList<>();
        generatePossibleParamTypes(paramTypes, 0, possibleParamTypesList);
        return possibleParamTypesList;
    }

    private void generatePossibleParamTypes(Class<?>[] paramTypes, int index, List<Class<?>[]> possibleParamTypesList) {
        if (index == paramTypes.length) {
            possibleParamTypesList.add(paramTypes.clone());
            return;
        }
        Class<?> originalType = paramTypes[index];
        Class<?> primitiveType = wrapperToPrimitiveMap.get(originalType);
        Class<?> wrapperType = primitiveToWrapperMap.get(originalType);

        // 일반 오브젝트임
        if (primitiveType == null && wrapperType == null) {
            generatePossibleParamTypes(paramTypes, index + 1, possibleParamTypesList);
        }
        // 래퍼 혹은 기본 클래스임
        else{
            if (primitiveType != null) { // 래퍼클래스임
                paramTypes[index] = primitiveType;
            } else  { // 기본클래스임
                paramTypes[index] = wrapperType;
            }
            generatePossibleParamTypes(paramTypes, index + 1, possibleParamTypesList);
            paramTypes[index] = originalType;
            generatePossibleParamTypes(paramTypes, index + 1, possibleParamTypesList);
        }
    }
}

리턴타입 제네릭을 이용하면 호출 후에 형변환을 할 필요가 없어진다.

기본자료형까지 모두 지원하려면 조금 까다로워진다.
getDeclaredMethod 메소드는 이름과 파라미터 타입들이 매칭하는 메소드를 찾는다.
근데 얘가 유도리가 없어서 래퍼클래스와 기본자료형을 철저하게 구분한다.

private int method(Integer num1, int num2);
위와 같은 메소드가 있을 때 getDeclaredMethod가 찾으려는 파라미터 타입이
{int,int}이면 NoSuchMethodException이 발생한다.

결국 완전 탐색으로 래퍼클래스와 기본자료형인 경우를 모두 찾기로 했다.
어차피 함수 인자로 4개 이상이 들어가는 경우는 흔하지도 않아서 큰 부담은 없다.

profile
백엔드 희망

0개의 댓글