Коллекция Java, дженерики и абстрактный класс: потеря информации о конкретном классе - PullRequest
0 голосов
/ 11 октября 2018

У меня есть класс Bar (и, возможно, ряд других классов), который расширяет абстрактный класс AbstractFoo.При преобразовании экземпляра Bar в FooDTO обнаруживается конкретный класс.

Однако при преобразовании коллекции Bar экземпляров в список FooDTO информация о конкретном классе теряется.и преобразование выполняется на основе AbstractFoo.

Что здесь не так?

public class CollectionGenericsNGTest {


    public static abstract class AbstractFoo { }

    public static class Bar extends AbstractFoo { }

    public static class FooDTO {
        final boolean isBar;

        public FooDTO(AbstractFoo f) {
            this.isBar = false;
        }

        public FooDTO(Bar b) {
            this.isBar = true;
        }
    }

    public static class FooDTOList {
        List<FooDTO> list;

        public FooDTOList(Collection<? extends AbstractFoo> source) {
            list = source.stream()
                    .map(entry -> new FooDTO(entry))
                    .collect(Collectors.toList());
        }

        public List<FooDTO> getList() {
            return list;
        }
    }

    @Test
    public void testDTO() {
        Bar b = new Bar();
        FooDTO f = new FooDTO(b);

        assertTrue(f.isBar);
    }

    @Test
    public void testDTO_abstract() {
        AbstractFoo b = new Bar();
        FooDTO f = new FooDTO(b);

        assertTrue(f.isBar); // <-- fails, too
    }

    @Test
    public void testDTOList() {
        Bar b = new Bar();
        List<Bar> collection = Arrays.asList(b);
        FooDTOList list = new FooDTOList(collection);

        FooDTO f = list.getList().get(0);
        assertTrue(f.isBar); // <--- this fails!
    }

}

1 Ответ

0 голосов
/ 11 октября 2018

Здесь

.map(entry -> new FooDTO(entry))

вы всегда , вызывая

new FooDTO(AbstractFoo)

конструктор, который устанавливает this.isBar в ложь.

Даже еслиобъект, удерживаемый entry, имеет тип времени выполнения Bar, переменная entry имеет тип AbstractFoo, поскольку она является частью Collection<? extends AbstractFoo>, поэтому компилятор знает, что объект должен быть AbstractFoo,но не знает, что это Bar.Разрешение перегрузки работает с типом ссылки в типе компиляции, а не с типом объекта во время выполнения.

Если вы хотите проверить тип времени выполнения объекта, удерживаемого entry,вместо типа переменной вы можете использовать

this.isBar = (f instanceof Bar);

при назначении для своего поля.Это проверит тип времени выполнения реального объекта, на который ссылается f.


В вашем более простом случае

Bar b = new Bar();
FooDTO f = new FooDTO(b);

вызов конструктора преобразуется в new FooDTO(Bar), потому чтовы передаете ему ссылку типа Bar.

Если вместо этого у вас было:

AbstractFoo b = new Bar();
FooDTO f = new FooDTO(b);

, то вызов конструктора разрешится в new FooDTO(AbstractFoo), потому что вы передадите ссылкутипа AbtractFoo.

...