Solution for Programming Exercise 10.5
This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.
Exercise 10.5:
This is a short exercise in using the stream API. Suppose that the class Score is defined as
/** * Data for one student about a score on a test. */ private record ScoreInfo( String lastName, String firstName, int score ) { };
defined here as a record class for convenience (see Section 7.4). And suppose that scoreData is an array of ScoreInfos containing information about the scores of students on a test. Use the stream API to do each of the following tasks:
- print the number of students (without using scoreData.length)
- print the average score for all of the students
- print the number of students who got an A (score greater than or equal to 90)
- use the collect() stream operation to create a List<String> that contains the names of students whose score was less than 70; the names should be in the form first name followed by last name
- print the names from the List that was generated in the previous task
- print out the students' names and scores, ordered by last name
- print out the students' names and scores, ordered by score
You can put all of the code in main() routine and include ScoreInfo as a nested class. Do not use any for loops or other control structures. Do everything using the stream API. For testing your code, you can use this array:
private static ScoreInfo[] scoreData = new ScoreInfo[] { new ScoreInfo("Smith","John",70), new ScoreInfo("Doe","Mary",85), new ScoreInfo("Page","Alice",82), new ScoreInfo("Cooper","Jill",97), new ScoreInfo("Flintstone","Fred",66), new ScoreInfo("Rubble","Barney",80), new ScoreInfo("Smith","Judy",48), new ScoreInfo("Dean","James",90), new ScoreInfo("Russ","Joe",55), new ScoreInfo("Wolfe","Bill",73), new ScoreInfo("Dart","Mary",54), new ScoreInfo("Rogers","Chris",78), new ScoreInfo("Toole","Pat",51), new ScoreInfo("Khan","Omar",93), new ScoreInfo("Smith","Ann",95) };
In each case, to process the values from the array using the stream API, we need to make a stream containing those values. That's done by calling Arrays.stream(scoreData), except in the case of printing out the names in a List, where list.stream() can be used. From there, we just need to apply the appropriate stream operators to get the desired results. I have added some comments on each of the assigned tasks to the program.
import java.util.List; import java.util.Arrays; import java.util.stream.Collectors; /** * This program tests the stream API by doing various computations. */ public class StreamTest { /** * Data for one student about a score on a test. */ private record ScoreInfo( String lastName, String firstName, int score ) { }; /** * A test array of StudentInfo that will be used to create streams. */ private static ScoreInfo[] scoreData = new ScoreInfo[] { new ScoreInfo("Smith","John",70), new ScoreInfo("Doe","Mary",85), new ScoreInfo("Page","Alice",82), new ScoreInfo("Cooper","Jill",97), new ScoreInfo("Flintstone","Fred",66), new ScoreInfo("Rubble","Barney",80), new ScoreInfo("Smith","Judy",48), new ScoreInfo("Dean","James",90), new ScoreInfo("Russ","Joe",55), new ScoreInfo("Wolfe","Bill",73), new ScoreInfo("Dart","Mary",54), new ScoreInfo("Rogers","Chris",78), new ScoreInfo("Toole","Pat",51), new ScoreInfo("Khan","Omar",93), new ScoreInfo("Smith","Ann",95) }; public static void main(String[] args) { /* The number of students can be computed simply by applying * the .count() operation to a stream created from the array. * This is more than a little silly, since the answer will * be the same as scoreData.length, but it sets up the * general format for the stream operations in the rest of * the program. */ long count = Arrays.stream(scoreData).parallel().count(); System.out.println("Number of students: " + count); /* To get the average, map the stream of ScoreData objects * to an IntStream containing just the scores, and then use * the .average() operation from the IntStream class. Since * .average() returns an Optional<Double>, we need to * use getAsDouble() to retrieve the final answer. */ double avg = Arrays.stream(scoreData).parallel() .mapToInt( s -> s.score ) .average() .getAsDouble(); System.out.printf("Average grade: %1.2f%n", avg ); /* To get the number of students who got an A, first filter the * stream letting through only students who satisfy the * predicate s -> s.score >= 90. This predicate returns true * only for students whose score is greater than or equal to 90. * The number is then computed by applying .count() to the * resulting stream. */ long countA = Arrays.stream(scoreData).parallel() .filter( s -> s.score >= 90 ) .count(); System.out.println("Number who got an A: " + countA); System.out.println(); /* To get a list of names of students who failed, first filter * the stream to let through only ScoreInfo objects with score * less than 70. Since we want the names of students, we need * to convert the stream of ScoreInfo objects into a stream * of strings by applying a mapping operation. Then applying * .collect(Collectors.toList()) produces the List that we need. */ List<String> failing = Arrays.stream(scoreData) .filter( s -> (s.score < 70) ) .map( s -> (s.firstName + " " + s.lastName) ) .collect( Collectors.toList() ); /* Now we need to print the list, which can easily be done with * a for-each loop. However, it can also be done using .forEach() * operation on a stream. In this case, the String Consumer in the * .forEach() operation is given as the method reference * System.out::println. That is, it is the println() method in the * object System.out. This method reference is the same as writing * the lambda expression s -> System.out.println(s). */ System.out.println("Failing students: "); failing.stream().forEach( System.out::println ); System.out.println(); /* To print the data ordered by last name, apply the .sorted() operation * to sort the values in the stream by last name, then use .forEach() * to output the information from each ScoreInfo object in the stream. * The .sorted() operation takes a parameter that is a Comparator for * comparing two values from the stream. In this case, we just * compare the lastNames from two ScoreInfo objects, using the standard * compareTo() method for Strings. */ System.out.println("Data sorted by last name:"); Arrays.stream(scoreData) .sorted( (s1,s2) -> s1.lastName.compareTo(s2.lastName) ) .forEach( s -> System.out.printf( " %s, %s: %d%n", s.lastName, s.firstName, s.score) ); System.out.println(); /* The data can be output ordered by score in the same way, using a * different comparator in the .sorted() operation. */ System.out.println("Data sorted by score:"); Arrays.stream(scoreData) .sorted( (s1,s2) -> s1.score - s2.score ) .forEach( s -> System.out.printf( " %s, %s: %d%n", s.lastName, s.firstName, s.score) ); } } // end StreamTest