目的是使用流来迭代数组,根据需要过滤/扩展值,并在新的流中收集结果。
尝试使用 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
变量重命名为其他名称:)