Я пытаюсь научить себя использовать лямбда-выражения в Java вместо обычных решений внешнего итерационного типа. Я сделал короткую программу, в которой я использую поток целых чисел и присваиваю им оценки. Вместо использования типичного оператора «switch» я использовал эти лямбда-выражения. Есть ли способ упростить это? Похоже, есть способ лучше. Работает, но выглядит не так аккуратно.
grades.stream()
.mapToInt(Integer::intValue)
.filter(x -> x >= 90)
.forEach(x -> System.out.println(x + " -> A"));
grades.stream()
.mapToInt(Integer::intValue)
.filter(x -> x >= 80 && x <= 89)
.forEach(x -> System.out.println(x + " -> B"));
grades.stream()
.mapToInt(Integer::intValue)
.filter(x -> x >= 70 && x <= 79)
.forEach(x -> System.out.println(x + " -> C"));
grades.stream()
.mapToInt(Integer::intValue)
.filter(x -> x >= 60 && x <= 69)
.forEach(x -> System.out.println(x + " -> D"));
grades.stream()
.mapToInt(Integer::intValue)
.filter(x -> x >= 50 && x <= 59)
.forEach(x -> System.out.println(x + " -> F"));
grades - это список массивов.
Есть ли способ объединить логику в одном операторе потока? Я пытался просто заставить их следовать друг за другом, но продолжал получать синтаксические ошибки. Что мне не хватает? Я попытался изучить документацию для IntStream и Stream и не смог найти то, что искал.
@Sheepy Спасибо за понимание! Я ценю его. Пойду проверю ссылку, которую ты дал. Радость!




Вы можете добавить эти условия в оператор forEach примерно так:
grades.stream()
.mapToInt(Integer::intValue)
.forEach(x -> {
if (x >= 90)
System.out.println(x + " -> A");
else if (x >= 80)
System.out.println(x + " -> B");
else if (x >= 70)
System.out.println(x + " -> C");
......
// other conditions
});
Нет необходимости иметь сравнения <= 89 и <= 79.
for (int x : grades)
{
if (x >= 90)
System.out.println(x + " -> A");
else if (x >= 80)
System.out.println(x + " -> B");
else if (x >= 70)
System.out.println(x + " -> C");
else if (x >= 60)
System.out.println(x + " -> D");
else if (x >= 50)
System.out.println(x + " -> F");
}
Легче читать и работает быстрее. пс. А как насчет оценки «Е»?
Проголосовали против, потому что OP уже знает об итерации и пытается изучить потоки на игрушечном примере, а не решить, как это лучше всего кодировать.
Просто создайте отдельную функцию для сопоставления букв оценок и вызовите ее в одном потоке:
static char gradeLetter(int grade) {
if (grade >= 90) return 'A';
else if (grade >= 80) return 'B';
else if (grade >= 70) return 'C';
else if (grade >= 60) return 'D';
else return 'F';
}
grades.stream()
.mapToInt(Integer::intValue)
.filter(x -> x >= 50)
.forEach(x -> System.out.println(x + " -> " + gradeLetter(x)));
Если вы хотите упорядочить результаты по оценкам, вы можете добавить сортировку в начало потока:
.sorted(Comparator.comparing(x -> gradeLetter(x)))
IMHO, это намного лучше, чем включение встроенной логики в поток. Stream наиболее удобочитаем, когда выполняется несколько последовательных простых шагов.
Если вы готовы использовать стороннюю библиотеку, это решение будет работать с Java Streams и Коллекции Eclipse.
List<Integer> grades = List.of(0, 40, 51, 61, 71, 81, 91, 100);
grades.stream()
.mapToInt(Integer::intValue)
.mapToObj(new IntCaseFunction<>(x -> x + " -> F")
.addCase(x -> x >= 90, x -> x + " -> A")
.addCase(x -> x >= 80 && x <= 89, x -> x + " -> B")
.addCase(x -> x >= 70 && x <= 79, x -> x + " -> C")
.addCase(x -> x >= 60 && x <= 69, x -> x + " -> D")
.addCase(x -> x >= 50 && x <= 59, x -> x + " -> F"))
.forEach(System.out::println);
Это выводит следующее:
0 -> F
40 -> F
51 -> F
61 -> D
71 -> C
81 -> B
91 -> A
100 -> A
Я использовал фабричный метод Java 9 для списка и IntCaseFunction из коллекций Eclipse. Лямбда, которую я передал конструктору, будет вариантом по умолчанию, если ни один из других случаев не совпадет.
Диапазоны номеров также могут быть представлены экземплярами IntInterval, которые можно протестировать, используя contains в качестве ссылки на метод. Следующее решение будет работать с использованием вывода типа локальной переменной в Java 10.
var mapGradeToLetter = new IntCaseFunction<>(x -> x + " -> F")
.addCase(x -> x >= 90, x -> x + " -> A")
.addCase(IntInterval.fromTo(80, 89)::contains, x -> x + " -> B")
.addCase(IntInterval.fromTo(70, 79)::contains, x -> x + " -> C")
.addCase(IntInterval.fromTo(60, 69)::contains, x -> x + " -> D")
.addCase(IntInterval.fromTo(50, 59)::contains, x -> x + " -> F");
var grades = List.of(0, 40, 51, 61, 71, 81, 91, 100);
grades.stream()
.mapToInt(Integer::intValue)
.mapToObj(mapGradeToLetter)
.forEach(System.out::println);
Если я отброшу все Predicates и Functions, удалю IntCaseFunction и заменю ссылочным вызовом метода экземпляра с использованием тернарного оператора, следующее будет работать.
@Test
public void grades()
{
var grades = List.of(0, 40, 51, 61, 71, 81, 91, 100);
grades.stream()
.mapToInt(Integer::intValue)
.mapToObj(this::gradeToLetter)
.forEach(System.out::println);
}
private IntObjectPair<String> gradeToLetter(int grade)
{
var letterGrade =
grade >= 90 ? "A" :
grade >= 80 ? "B" :
grade >= 70 ? "C" :
grade >= 60 ? "D" : "F";
return PrimitiveTuples.pair(grade, letterGrade);
}
Я использую здесь реализацию пары toString(), поэтому на выходе будет следующее.
0:F
40:F
51:F
61:D
71:C
81:B
91:A
100:A
Вывод можно настроить в forEach, используя числовую и буквенную оценки, содержащиеся в паре.
Примечание: Я участник коллекций Eclipse.
Вот решение без переключателя / if, else.
Он имеет сопоставления между различными условиями и действием (логикой печати).
Map<Predicate<Integer>, Consumer<Integer>> actions = new LinkedHashMap<>();
//Beware of nulls in your list - It can result in a NullPointerException when unboxing
actions.put(x -> x >= 90, x -> System.out.println(x + " -> A"));
actions.put(x -> x >= 80 && x <= 89, x -> System.out.println(x + " -> B"));
actions.put(x -> x >= 70 && x <= 79, x -> System.out.println(x + " -> C"));
actions.put(x -> x >= 60 && x <= 69, x -> System.out.println(x + " -> D"));
actions.put(x -> x >= 50 && x <= 59, x -> System.out.println(x + " -> F"));
grades.forEach(x -> actions.entrySet().stream()
.filter(entry -> entry.getKey().apply(x))
.findFirst()
.ifPresent(entry -> entry.getValue().accept(x)));
Примечание:
Резюме: Я бы предпочел придерживаться исходного кода с помощью switch / if, else.
Невозможно разделить поток на несколько потоков, если это то, что вам нужно. То, как вы это написали сейчас, - лучшее, что вы можете сделать, если хотите, чтобы вся логика была написана для обработки потока.
Я думаю, что проще всего было бы просто написать логику для обычного создания строки. Если вы хотите представить себе это, вы можете использовать mapToObj, например:
grades.stream()
.mapToInt(Integer::intValue)
.mapToObj(x -> {
if (x >= 90) {
return "A";
}
//etc
})
.forEach(System.out::println);
Вы бы хотели разделить логику перевода оценок в оценку (int -> String) и циклического прохождения вашего arrayylist.
Используйте потоки как механизм для циклического перебора ваших данных и метод логики перевода
Вот быстрый пример
private static void getGradeByMarks(Integer marks) {
if (marks > 100 || marks < 0) {
System.err.println("Invalid value " + marks);
return;
}
// whatever the logic for your mapping is
switch(marks/10) {
case 9:
System.out.println(marks + " --> " + "A");
break;
case 8:
System.out.println(marks + " --> " + "B");
break;
case 7:
System.out.println(marks + " --> " + "C");
break;
case 6:
System.out.println(marks + " --> " + "D");
break;
default:
System.out.println(marks + " --> " + "F");
break;
}
}
public static void main(String[] args) {
List<Integer> grades = Arrays.asList(90,45,56,12,54,88,-6);
grades.forEach(GradesStream::getGradeByMarks);
}
Следующий код дает точно такой же результат, что и исходный код, то есть в том же порядке.
grades.stream()
.filter(x -> x >= 50)
.collect(Collectors.groupingBy(x -> x<60? 'F': 'E'+5-Math.min(9, x/10)))
.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.flatMap(e -> e.getValue().stream().map(i -> String.format("%d -> %c", i, e.getKey())))
.forEach(System.out::println);
Если этот порядок вам не нужен, вы можете пропустить этап группировки и сортировки:
grades.stream()
.filter(x -> x >= 50)
.map(x -> String.format("%d -> %c", x, x<60? 'F': 'E'+5-Math.min(9, x/10)))
.forEach(System.out::println);
или же
grades.stream()
.filter(x -> x >= 50)
.forEach(x -> System.out.printf("%d -> %c%n", x, x<60? 'F': 'E'+5-Math.min(9, x/10)));
или же
grades.forEach(x -> {
if (x >= 50) System.out.printf("%d -> %c%n", x, x<60? 'F': 'E'+5-Math.min(9, x/10));
});
Лямбда или нет, старый добрый переключатель определенно лучший способ сделать это, как с точки зрения читаемости, так и с точки зрения реальной производительности. Так же, как
Integerне заменяетint,streamне (всегда) заменяетfor, аfilterне заменяетswitch. Рассмотрите codereview.stackexchange.com, если ваш код работает и не содержит ошибок.