Java Stream.builder() in Collector null problem



目的是使用流来迭代数组,根据需要过滤/扩展值,并在新的流中收集结果。

尝试使用 Stream.builder(),如以下三个示例所示,我总是会得到一个带有预期字符串但大量尾随空值的 Stream。此外,我无法以这种方式处理空元素。

我怀疑,Stream.builder()中的内部固定缓冲区是问题所在。 有没有办法防止使用这种方法"尾随"null,而不会失去将null值用作Stream元素的能力?

String[] whitespaces = new String[] { " ", "n", "r", "t" };
int len = whitespaces.length;
boolean addNulls = false;
int flexBoundary = addNulls ? len : len - 1;
Stream<String> whitespaceNullStringStream = IntStream.rangeClosed(0, flexBoundary)
.mapToObj(idx -> 
addNulls && idx == flexBoundary 
? null 
: whitespaces[idx])

// #1
.collect(Stream::<String>builder, Builder::add, (b1, b2) -> Stream.concat(b1.build(), b2.build())).build();
// #2
//    .collect(Stream::<String>builder, Builder::add, (b1, b2) -> Stream.builder().add(b1).add(b2)).build();
// #3
//    .collect(
//      Collector.of(
//        Stream::<String>builder, 
//        Builder::add, 
//        (b1, b2) -> b1.add(b2.build().reduce(String::concat).get()), 
//        Builder::build
//      )
//    );

如果我改用以下内容,它将按预期工作,当然,除了null值转换为字符串,这在这里是不可取的:

.collect(
Collector.of(
StringBuilder::new, 
StringBuilder::append, 
StringBuilder::append, 
(sb) -> Stream.of(sb.toString())
)
)

为了克服这个问题,我使用了以下方法:

Stream<String> stream = IntStream.rangeClosed(0, flexBoundary)
.mapToObj(idx -> addNulls && idx == flexBoundary ? null : whitespaces[idx])
.collect(Collector.of(
ArrayList<String>::new, 
List::add, 
(l1, l2) -> { l1.addAll(l2); return l1; }, 
(list) -> list.stream()
)
);

但是,如上所述,我想在收集器中使用 Stream.builder() 方法,它的工作原理相同。

当涉及null时,大多数流 API 都会快速崩溃。这些东西不是为它设计的。

关于null的实际含义有不同的想法。从简化上讲,Java 中的null意味着以下 3 件事之一:

  • 字段未初始化(private String hello;开头为null)
  • 阵列插槽从未写入(new String[10]以 10 个null值开头)
  • 有人明确使用了null,关键字。

但这并不太有用。让我们谈谈语义。当 API 返回某些内容的null时,或者当您在某些代码中使用null时,这意味着什么?

它也有不同的语义:

  • 这意味着:未初始化、不适用、意外、无结果。

在这种情况下,例外是好的,你可能想要的任何东西都是错误的。你不能问"将这个未知的东西连接到这个字符串"。正确的答案是不要默默地跳过它。正确的答案是崩溃:你不能连接一个未知数。这就是SQL非常一致地使用null的方式,也是我强烈推荐的Java中null的用法。它将 null 的缺点变成了好处:如果任何代码尝试与指针指向的事物进行交互,您希望发生该异常时,请使用null(因为这个想法是:没有值,因此代码流甚至不应该检查。如果是这样,则存在错误,我希望在编写错误时发生异常!

根据你的代码,如果这是你对null的解释,那么你的代码通过抛出异常来正确运行。

  • 这是其他东西的哨兵值

这也很常见:null正在返回,并且这具有明确的语义含义,希望在文档中描述。如果你曾经写过这个陈述:

if (x == null || x.isEmpty())

您极有可能正在使用null的这种语义含义。毕竟,该代码说:"至少就本if而言,空字符串和null指针之间根本没有区别。

我强烈建议你永远不要这样做。这不是必需的(只需返回一个空字符串!!),它会导致摩擦: 如果你在一个名为getTitle()Person类中有一个名为 的方法在没有标题时返回null,并且该项目还指出无标题的人应该表现得好像标题是空字符串一样(似乎是合乎逻辑的), 那么这是错误的。不要返回null.返回"".毕竟,如果我打电话给person.getTitle().length(),那么对于没有头衔的人提出的问题,有一个无可争议的正确答案,那就是0.

有时,某些系统定义了特定行为,这些行为强烈倾向于给定字段的"未定义/未知/未设置"行为。例如,假设规则是:如果此人的.getStudentId()呼叫返回一个空字符串,这仅表示他们还没有 ID。那么你也不应该使用null。如果一个值可以表示一个事物,那么它应该只以一种方式表示该事物。如果任何代码试图询问有关此值性质的任何信息,则需要异常,请使用null,如果存在一个执行您想要的所有操作的现有值,请使用现有值,并创建一个 sentinel 对象,该对象引发某些调用,但如果您需要真正精细的控制,则返回其他调用的默认值。

是的,如果你曾经写过if (x == null || x.isEmpty()),没错:这是一种代码气味。高度指示次优设计的代码。(例外:边界代码。如果您从不受您直接控制的系统或代码接收对象,那么您就会挥舞着拳头。但是,如果他们的API 不好,你应该编写一个中间隔离层,将他们设计糟糕的东西明确地转换为设计良好的东西。允许该转换层写入if (x == null || x.isEmpty()).

听起来这是你想要的null:听起来你希望将null附加到字符串生成器的行为意味着:"然后什么都不附加"。

因此,当您将其附加到字符串生成器时,您希望现在null执行以下操作: 对该字符串生成器根本不执行任何操作。

已经有一个对象可以做你想要的:它是"".

因此:

mapToObj(idx -> 
addNulls && idx == flexBoundary 
? "" 
: whitespaces[idx])

您可能希望将addNulls变量重命名为其他名称:)

相关内容

  • 没有找到相关文章

最新更新