【问题标题】:Is Java List of multiple types possible多种类型的Java列表是否可能
【发布时间】:2019-03-16 06:11:28
【问题描述】:

昨天我偶然发现了一些我无法解释的奇怪的 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');

这是我的实体类:

@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

【问题讨论】:

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


    【解决方案1】:

    考虑以下示例:

    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。

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

    【讨论】:

      【解决方案2】:

      这是可能的,因为 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&lt;String&gt;,但在运行时,Java 只知道List

      【讨论】:

      • 谢谢!不知何故,我忘记了类型擦除。希望我终于设法打破 Java :D
      • @PatrykKrawczyk 没问题!总是乐于助人! :)
      【解决方案3】:

      可以,这是一个小例子

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多