Java列表可能是多种类型吗

时间:2018-10-11 08:16:32

标签: java spring spring-boot jpa java-8

昨天我偶然发现了一些我无法解释的奇怪的Java / Spring / IntelliJ行为。

这是使用jdk1.8.0_152制作的Spring Boot应用程序。

我运行了这个简单的SQL来填充我的数据库:

CREATE TABLE TEST_ORGANIZATION
(
    ID NUMBER(19) NOT NULL,
    NAME VARCHAR(255) NOT NULL
);

INSERT INTO TEST_ORGANIZATION (ID, NAME) VALUES ('1', 'test1');
INSERT INTO TEST_ORGANIZATION (ID, NAME) VALUES ('2', 'test2');

这是我的Entity类:

@Data
@NoArgsConstructor
@Entity
public class TestOrganization {

    @Id
    private Long id;

    @NotNull
    private String name;
}

还有我的JPA存储库:

public interface TestOrganizationRepository extends JpaRepository<TestOrganization, Long> {

    @Query("SELECT new map(id as key, name as value) FROM TestOrganization")
    List<Map<String, String>> findAllAndMapById();
}

这就是令人困惑的地方。 我编写了一个简单的单元测试来检查这些值,但事实证明它在第二个断言时失败:

@Test
public void shouldGetDocumentByName() {
    List<String> values = testOrganizationRepository.findAllAndMapById()
            .stream()
            .flatMap( m -> m.values().stream() )
            .collect( Collectors.toList() );

    assertThat( values ).isNotEmpty();
    assertThat( values ).allMatch( n -> n instanceof String );
}

使用IntelliJ进行调试时,它显示如下:

enter image description here

这怎么可能?为什么我可以在字符串列表中使用Long值?

也:

 values.add(1L) // fails to compile
 values.get(0).getClass().name // returns java.lang.String
 values.get(1).getClass().name // returns java.lang.Long

3 个答案:

答案 0 :(得分:4)

这可能是因为Java中的类型擦除。简而言之,这意味着在运行时Java不了解您的泛型类型,因此任何List对象都可以使用Object类型进行操作。使泛型具有编译类型的安全性。 但是您可以阅读有关类型擦除的更多详细信息,例如here

您可以自己模仿的相同行为:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    addToList(list);
    System.out.println(list);
}

private static void addToList(List list) {
    list.add(1L);
    list.add(42);
    list.add("String");
    list.add(new Runnable() {
        @Override
        public void run() {}
    });
}

只要您不尝试将列表项作为字符串使用,此代码就可以正常工作。但是,当您添加类似内容时:

for (String s : list) {
    System.out.println(s);
}

您将获得java.lang.ClassCastException

因此,在编译时,您可以使用List<String>,但是在运行时,Java只知道List

答案 1 :(得分:1)

是的,这是一个小例子

public static void main(String args) {
        List objects = new ArrayList();
        objects.add("string");
        objects.add(1L);

        List<String> onlyStrings = objects;

    }

如果愿意,可以将非通用列表转换为List(会收到一堆编译器警告)。

答案 2 :(得分:0)

考虑以下示例:

public static void main(String[] args) {
    List a = new ArrayList();
    a.add("a");
    a.add(1L);

    List<String> b = new ArrayList(a);

    System.out.println(b);
}

这将输出

[a, 1]

以上是合法的,因为java执行类型擦除,因此在运行时丢弃元素类型信息。这意味着每个List本质上都是一个List,并且可以包含多个类实例。

public interface List<E> extends Collection<E>

转换为

public interface List extends Collection

在编译时。

如果绑定了E,即E扩展了MyClass,则它将转换为MyClass。

在您的情况下,您要为每一行实例化一个映射,没有类型限制,您要对其进行映射,然后将其收集到一个List中,这实际上与我的示例相同,因此是合法的。