昨天我偶然发现了一些我无法解释的奇怪的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进行调试时,它显示值如下:
这怎么可能?为什么我可以在字符串列表中使用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
答案 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中,这实际上与我的示例相同,因此是合法的。