调用Stream的构造器方法(Stream.builder)为何无法推断泛型类型?

Java版本:jdk8以上

复现代码:

// 为什么我传了1,还是推断的Object类型?
Stream<Object> build = Stream.builder().add(1).build();

// 这行报错
Stream<Integer> build2 = Stream.builder().add(1).build();

// 这行可以,为什么要指定Integer泛型?
Stream<Integer> build3 = Stream.<Integer>builder().add(1).build();

看了网上的一些关于泛型构造器文章,还是不知道怎么理解这块
这个问题我纠结了挺久的>_<各位大侠有没有心得指点一下Orz

已解决

因为调用构造方法的时候没有传具体参数的原因导致编译器默认泛型为Object。
下面这个demo复现了相同的问题:

public class MySSTest<T> {

    T data;

    public MySSTest() {
    }

    public MySSTest(T data) {
        this.data = data;
    }

    MySSTest<T> method(T param) {
        data = param;
        return this;
    }

    static <T> MySSTest<T> getBuilder() {
        return new MySSTest<>();
    }

    static <T> MySSTest<T> getBuilder(T param) {
        return new MySSTest<>(param);
    }

    public static void main(String[] args) {
        MySSTest<Object> builder = MySSTest.getBuilder();
        MySSTest<Integer> builder1 = MySSTest.getBuilder(1);

        MySSTest<Object> method = MySSTest.getBuilder().method(1);
        MySSTest<Integer> method1 = MySSTest.getBuilder(1).method(2);
    }
}

延展

关于一个泛型类的泛型构造方法的讨论,此时有一个类如下:

public class MyTest<T, R> {

    T data;

    R member;

    public MyTest() {
    }

    public MyTest(T data) {
        this.data = data;
    }

    public <U> MyTest(T data, U param, R member) {
        this.data = data;
        this.member = member;
        System.out.println(data + ", " + param);
    }

    static <T, R> MyTest<T, R> getBuilder() {
        return new MyTest<>();
    }


    public static void main(String[] args) {

        // 从这一行可以看出,调用泛型构造器时,构造器方法左边的泛型参数对应的是入参泛型,方法右边则是类的泛型
        MyTest<Integer, String > integerMyTest = new <Boolean>MyTest<Integer, String>(233, true, "123");

        // 当入参泛型不指定时,类的泛型也可以省略但不能省略菱形括号
        MyTest<Integer, String > integerMyTest1 = new MyTest<>(233, true, "123");

        // 当入参泛型指定时,类的泛型不能省略。 但是反过来类的类型指定时,入参泛型是可以省略的
        MyTest<Integer, String> integerMyTest1 = new <Boolean>MyTest<>(233, true, "123");
    }
}

此时再回顾Stream.builder()方法:

public static<T> Builder<T> builder() {
    return new Streams.StreamBuilderImpl<>();
}

所以调用静态方法Stream.<Integer>builder()时,<Integer>是作为入参泛型类型传递,虽然此时没有入参,但是已经能指定该泛型方法的泛型类型了,又因为返回值是该对象泛型,所以后续操作的对象内容也都是Integer类型。

阅读 2.4k
2 个回答

如果你拆分开来,你会发现是能够推断的。

Stream.Builder<Integer> builder = Stream.builder();
Stream<Integer> add = builder.add(1).build();

看一下 builder 源码,结合上面的代码看,你就能知道推断泛型的逻辑是通过被赋值的泛型推断出来的

public static<T> Builder<T> builder() {
    return new Streams.StreamBuilderImpl<>();
}

那如果你连起来呢?因为这个过程是连续的,没有给 Stream.builder() 机会去推断类型,那就只能用 object 了,虽然你后面给了 add(1),但是上一步已经泛型被确定为 object 了,给 add(1) 不能改变什么,因为给 add(1) 是后面发生的,泛型类型是上一步确定的。

Stream.builder().add(1).build()

连起来的代码等价于:

Stream.Builder<Object> builder = Stream.builder();
builder = builder.add(1);
Stream<Object> stream = builder.build();

那么再看这一行你就能明白了,Stream.<Integer>builder() 这一步已经提前确定了泛型。

Stream<Integer> build3 = Stream.<Integer>builder().add(1).build();

最后给你一段我模仿的测试代码调试玩玩:

public class Test {

    static class Stream<T> {
        interface Builder<T> {
            Builder<T> add(T t);
        }

        public static <T> Builder<T> builder() {
            return new Streams.StreamBuilderImpl<>();
        }
    }

    static class Streams {
        static class StreamBuilderImpl<T> implements Stream.Builder<T> {
            StreamBuilderImpl() {
            }

            @Override
            public Stream.Builder<T> add(T t) {
                return this;
            }
        }

    }

    public static void main(String[] args) {
        Stream.Builder<Integer> builder1 = Stream.builder();
        Stream.Builder<Integer> add = builder1.add(2);
        Stream.Builder<Object> builder = Stream.builder().add(1);
    }
}

补充

不能推断泛型,不是 stream 原因,如果你换一种方式使用 stream,你会发现实际上是能够推断的

Stream<Integer> stream = Stream.of(1);

你这段代码不能推断的根本原因,只是实现调用方式的问题。

这个问题感觉和Stream本身关系不大,主要还是Java的泛型,其本质是一个参数,是在编译阶段,用来告诉代码,确认和约束具体的数据参数类型。
而Stream类是设计用来为一系列的对象元素(A sequence of elements)提供顺序或者聚合等操作的,这里可以类比ArrayList这些容器类,其本身可以适用于任意对象类型,他本身不会限制类型,也没有设计根据第一个元素的类型,来约束其他元素

List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i=0; i <arrayList.size(); i++){
      System.out.println( arrayList.get(i));
}

上边的代码在编译和运行时,都是不会报错的,因为对于arrayList来说,它只是个容器,放任何元素都可以,但是当使用的时候,调用者需要自己明确取出来元素的对象类型,进行类型转换,然后再调用方法,这实际在使用时成本是很高的,需要配合大量的 if 和 instance of,来保证不会runtime exception。
因此会通过泛型,泛型构造,泛型方法等一系列的设计,告诉编译器/调用者,数据的具体类型是什么,在编译阶段,约束代码使用正确的类型和对应方法。
回到Stream,其本身是对一组同类型元素,执行转化,聚合,计算等操作的,所以使用了泛型构造,来约束元素的类型,来保证后续的操作是可以被执行的。

总结一下:Stream要求其中的元素都是相同类型,但它本身不够智能,无法动态的根据放入的第一个元素,来判断和约束其他放入的元素,所以要求在声明/构造Stream的时候,要通过泛型来明确这个Stream中的元素类型,否则他会认为都是Object类型,也就造成你第二行,Object != Integer,两个类型不匹配。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏