将复杂的while循环重构到Java 8流中

时间:2018-11-06 17:33:50

标签: java java-8 java-stream

我有一个复杂的实体结构。其中包含上一个项目的ID(“ previodElementId”)

interface IPreviousElementEntity<PK> {
    public void setId(PK id);
    public PK getId();
    public void setPreviousElementId(PK previousElementId);
    public PK getPreviousElementId();
}

从DB接收到所有实体后,我需要将结果列表转换为链接列表,并且该链接列表应按先前的ID进行组织。

我编写了以下代码进行转换:

static <T extends IPreviousElementEntity> LinkedList<T> getLinkedListByPreviousId(Collection<T> collection) {
    LinkedList<T> linkedList = new LinkedList<>();
    if (collection == null || collection.isEmpty())
        return linkedList;

    // first find root element
    collection.stream()
            .filter(element -> element.getPreviousElementId() == null)
            .forEach(linkedList::add);

    if (linkedList.isEmpty()) return linkedList;

    // TODO: convert to use stream. Please help!
    Boolean isRun = true;
    while (isRun) {
        for (T element : collection) {
            isRun = false;
            if (linkedList.getLast().getId().equals(element.getPreviousElementId())) {
                linkedList.add(element);
                isRun = true;
                break;
            }
        }
    }

    return linkedList;
}

但是这个代码太糟糕了!是否可以将所有这些转换写在流上?我特别希望摆脱while循环的轰动。

我的完整代码:

import java.util.*;

public class App {
    public static void main(String[] args) {
        Entity entity1 = new Entity(3L, 2L, "third");
        Entity entity2 = new Entity(2L, 1L, "second");
        Entity entity3 = new Entity(4L, 3L, "forth");
        Entity entity4 = new Entity(1L, null, "first");

        List<Entity> entities = new ArrayList<>();
        entities.add(entity1);
        entities.add(entity2);
        entities.add(entity3);
        entities.add(entity4);

        LinkedList<Entity> linkedListByPreviousId = getLinkedListByPreviousId(entities);
        System.out.println(linkedListByPreviousId);
    }

    private static <T extends IPreviousElementEntity> LinkedList<T> getLinkedListByPreviousId(Collection<T> collection) {
        LinkedList<T> linkedList = new LinkedList<>();
        if (collection == null || collection.isEmpty())
            return linkedList;

        // first find root element
        collection.stream()
                .filter(element -> element.getPreviousElementId() == null)
                .forEach(linkedList::add);

        if (linkedList.isEmpty()) return linkedList;

        //TODO: convert to use stream. Please help!
        Boolean isRun = true;
        while (isRun) {
            for (T element : collection) {
                isRun = false;
                if (linkedList.getLast().getId().equals(element.getPreviousElementId())) {
                    linkedList.add(element);
                    isRun = true;
                    break;
                }
            }
        }

        return linkedList;
    }
}

interface IPreviousElementEntity<PK> {
    public void setId(PK id);
    public PK getId();
    public void setPreviousElementId(PK previousElementId);
    public PK getPreviousElementId();
}

class Entity implements IPreviousElementEntity<Long> {
    private Long id;
    private Long previousElementId;
    private String name;

    public Entity(Long id, Long previousElementId, String name) {
        this.id = id;
        this.previousElementId = previousElementId;
        this.name = name;
    }

    @Override
    public Long getId() {
        return id;
    }

    @Override
    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public Long getPreviousElementId() {
        return previousElementId;
    }

    @Override
    public void setPreviousElementId(Long previousElementId) {
        this.previousElementId = previousElementId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Entity entity = (Entity) o;
        return Objects.equals(id, entity.id) &&
                Objects.equals(previousElementId, entity.previousElementId) &&
                Objects.equals(name, entity.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, previousElementId, name);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Entity{");
        sb.append("id=").append(id);
        sb.append(", previousElementId=").append(previousElementId);
        sb.append(", name='").append(name).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

2 个答案:

答案 0 :(得分:4)

while循环很麻烦,因为它尝试使用列表执行O(n ^ 2)操作,并不断重复直到没有更多选项为止。

通过使用以previousElementId为键的Map,O(n)操作更合适。

if (linkedList.isEmpty()) return linkedList;

//create a map with previousElementId as Key, T as Object
Map<Integer, T> map = collection.stream().collect(
            Collectors.toMap(T::getPreviousElementId, Function.identity()));

//we fetch nodes using the current ID as the key
T node = map.get(linkedList.getLast().getId());
while(node != null) {
    linkedList.add(node);
    node = map.get(node.getId());
}

答案 1 :(得分:1)

您有一个常见的用例,我认为您应该能够使用流提出更干净的解决方案。 这是仅适用于流的方法:

private static < T extends IPreviousElementEntity<?> > LinkedList<T> getLinkedListByPreviousId(
Collection<T> collection) {
    //first create map with previous id mapped to element, this assumes 
    //whatever id you use has proper implementation of equals and hashCode
    Map<?, T> map = collection.stream()
        .collect( 
            Collectors.toMap(
                IPreviousElementEntity::getPreviousElementId, Function.identity(), 
            (i1, i2) -> i1 ) ); 

    //then create infinite stream which starts with element that has null previous id
    //and moves on to the next element that points to it via previous id
    //since this is an infinite stream we need to limit it by the number of elements in the map
    return Stream
        .iterate( map.get(null), i -> map.get( i.getId() ) )
        .limit( map.size() )
        .collect( Collectors.toCollection(LinkedList::new) );
}