如何将条件转换为Java 8流

时间:2018-09-04 16:18:25

标签: java java-8 java-stream

当前,我有这个方法,我想将其转换为Java 8流样式(我对此API btw几乎没有实践,这是此小练习的目的):

private static Map<Integer, List<String>> splitByWords(List<String> list) {
   for (int i = 0; i < list.size(); i++) { 
        if(list.get(i).length() > 30 && list.get(i).contains("-")) {
            mapOfElements.put(i, Arrays.stream(list.get(i).split("-")).collect(Collectors.toList()));
        } else if(list.get(i).length() > 30) {
            mapOfElements.put(i, Arrays.asList(new String[]{list.get(i)}));
        } else {
            mapOfElements.put(i, Arrays.asList(new String[]{list.get(i) + "|"}));
        }
   }

   return mapOfElements;
}

这是我到目前为止所得到的:

private static Map<Integer, List<String>> splitByWords(List<String> list) {
   Map<Integer, List<String>> mapOfElements = new HashMap<>();
   IntStream.range(0, list.size())
        .filter(i-> list.get(i).length() > 30 && list.get(i).contains("-"))
        .boxed()
        .map(i-> mapOfElements.put(i, Arrays.stream(list.get(i).split("-")).collect(Collectors.toList())));

//Copy/paste the above code twice, just changing the filter() and map() functions?

以“老式”方式,我只需要进行一次for迭代就可以完成有关条件的一切操作。有没有一种方法可以使用Stream API来实现,或者,如果我想坚持下去,就必须重复上述代码,只是更改filter()和map()条件,因此需要进行三个for迭代?

2 个答案:

答案 0 :(得分:4)

当前带有for循环的解决方案看起来不错。由于您仅需区分三种情况,因此无需对处理进行概括。

如果需要区分更多的情况,则可以重构代码。我的方法是显式定义不同的条件及其相应的字符串处理。让我用问题代码解释一下。

首先,我使用枚举定义不同的条件。

$(document).ready(function() {

$("#submit").click(function(e) {
    e.preventDefault();

    var to = $("#to").val();
    var subject = $("#subject").val();
    var message = $("#message").val();

    var data = {
        'to': to,
        'subject': subject,
        'message': message
    }

    $.ajax({
        type: "POST",
        url: 'https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/xxxxx',
        contentType: 'application/json',
        data: JSON.stringify({data}),
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        success: function(res){
            console.log('Email was sent.');
        },
        error: function(){
            console.log('Error.');
        }
    });

})

});

使用该枚举,我定义了相应的字符串处理器:

  public enum StringClassification {
    CONTAINS_HYPHEN, LENGTH_GT_30, DEFAULT;

    public static StringClassification classify(String s) {
      if (s.length() > 30 && s.contains("-")) {
        return StringClassification.CONTAINS_HYPHEN;
      } else if (s.length() > 30) {
        return StringClassification.LENGTH_GT_30;
      } else {
        return StringClassification.DEFAULT;
      }
    }
  }

基于此,我可以使用请求的 private static final Map<StringClassification, Function<String, List<String>>> PROCESSORS; static { PROCESSORS = new EnumMap<>(StringClassification.class); PROCESSORS.put(StringClassification.CONTAINS_HYPHEN, l -> Arrays.stream(l.split("-")).collect(Collectors.toList())); PROCESSORS.put(StringClassification.LENGTH_GT_30, l -> Arrays.asList(new String[] { l })); PROCESSORS.put(StringClassification.DEFAULT, l -> Arrays.asList(new String[] { l + "|" })); } 进行整个处理:

IntStream

方法是为字符串检索适当的 private static Map<Integer, List<String>> splitByWords(List<String> list) { return IntStream.range(0, list.size()).boxed() .collect(Collectors.toMap(Function.identity(), i -> PROCESSORS.get(StringClassification.classify(list.get(i))).apply(list.get(i)))); } ,然后依次检索相应的字符串处理器。字符串处理器通过提供StringClassification来实现strategy pattern,该Function<String, List<String>>根据StringList<String>映射到StringClassification

一个简单的例子:

  public static void main(String[] args) {
    List<String> list = Arrays.asList("123",
      "1-2",
      "0987654321098765432109876543211",
      "098765432109876543210987654321a-b-c");
    System.out.println(splitByWords(list));
  }

输出为:

{0=[123|], 1=[1-2|], 2=[0987654321098765432109876543211], 3=[098765432109876543210987654321a, b, c]}

这使得添加或删除条件和字符串处理器变得容易。

答案 1 :(得分:3)

首先,当键是索引时,我看不出使用Map<Integer, List<String>>类型的任何理由。为什么不使用List<List<String>>呢?如果您不使用过滤器,则元素应与输入位于相同的索引上。

更实用的方法的强大之处在于它使您所做的工作更具可读性。因为您想对多个大小的字符串执行多项操作,所以很难编写一个干净的解决方案。但是,您可以在一个循环中完成此操作:

private static List<List<String>> splitByWords(List<String> list)
{
    return list.stream()
        .map(
            string -> string.length() > 30
                ? Arrays.asList(string.split("-")) 
                : Arrays.asList(string + "|")
        )
        .collect(Collectors.toList());
}

您可以通过制作lambda多行来添加更复杂的逻辑(在这种情况下不需要)。例如。

.map(string -> {
    // your complex logic

    // don't forget, when using curly braces you'll
    // need to return explicitly
    return result;
})

更实用的方法是按大小对字符串进行分组,然后为不同的组应用特定的处理程序。很难保持索引不变,因此我将返回值更改为Map<String, List<String>>,以便可以通过提供原始字符串来获取结果:

private static Map<String, List<String>> splitByWords(List<String> list)
{
    Map<String, List<String>> result = new HashMap<>();
    Map<Boolean, List<String>> greaterThan30;

    // group elements
    greaterThan30 = list.stream().collect(Collectors.groupingBy(
        string -> string.length() > 30
    ));

    // handle strings longer than 30 chars
    result.putAll(
        greaterThan30.get(true).stream().collect(Collectors.toMap(
            Function.identity(), // the same as: string -> string
            string -> Arrays.asList(string.split("-"))
        ))
    );

    // handle strings not longer than 30 chars
    result.putAll(
        greaterThan30.get(false).stream().collect(Collectors.toMap(
            Function.identity(), // the same as: string -> string
            string -> Arrays.asList(string + "|")
        ))
    );

    return result;
}

以上内容看起来很麻烦,但我认为更好理解。知道提供的字符串始终符合条件,您还可以将处理大和小的字符串的逻辑分派给其他方法。

这比第一个解决方案要慢。对于大小为n的列表,它必须遍历n元素以按条件分组。然后,循环遍历符合条件的x0 <= x <= n)个元素,然后循环遍历不符合条件的n - x个元素。 (总共是整个列表的2倍。)

在这种情况下,可能不值得麻烦,因为标准和适用的逻辑都非常简单。