ZOFTINO.COM android and web dev tutorials

Java Stream Examples

Java stream package contains classes which support functional style operations on stream of elements, for example it allows you to define functions and apply them sequentially or parallel on each element in the stream. Stream doesn’t store any elements, it provides operations which are applied on the elements of a source such as collections, arrays, IO or function which returns elements.

Table of Contents

Computations using Stream

The flow of using streams consists of source which contains elements, stream object, intermediate stream operations which apply functionality on elements of the source and return streams, and terminal operation which consumes the elements, returns results and ends the flow.

Following example first gets stream object from List which is a source of the stream, then applies filter, an intermediate stream operation which returns stream. The filter executes the given predicate on each element and returns true or false to indicate whether to filter the element or not. Then it applies forEach operation, which is a terminal operations, to print the remaining elements.

		List<String> brands = new ArrayList<String>();		
		brands.add("samsung");
		brands.add("apple");
		brands.add("boss");
		brands.add("nautica");
		
		List<String> electronicsBrands = new ArrayList<String>();		
		electronicsBrands.add("samsung");
		electronicsBrands.add("apple");
		electronicsBrands.add("lenovo");
		
		System.out.println("electronics brands from the given list are: ");
		brands.stream().filter( s -> {
			return electronicsBrands.contains(s) ? true	: false;
		}).forEach(s -> {
			System.out.println(s);
		});

Output

electronics brands from the given list are: 
samsung
apple

Stream from Collection

All collections data structures such as list, set and queue can be used as source of streams. Following examples applies stream operations on elements of a set.

	Set<String> payType = new HashSet<String>();
	payType.add("credit");
	payType.add("debit");
	payType.add("paypal");
	
	payType.stream().map(s -> {
		if(s.equals("credit")) {
			return "credit - charge 3%";
		}
		return s+" - charge 1%";
	}).forEach(sc -> {
		System.out.println(sc);
	});

Output

credit - charge 3%
debit - charge 1%
paypal - charge 1%

Stream from File

Element source for stream object can be an input stream. Stream object from an input stream can be obtained using new input out API Files class and calling lines methods on it as shown. You can perform all the stream operations.

	Path path =  Paths.get(file);		
	try{
		Stream<String> stream = Files.lines(path) ;
		
		stream.forEach(s -> {
			System.out.println("element from file: "+s);
		});
		stream.close();
	} catch (Exception e) {
		e.printStackTrace();
	}

Stream from Array

Stream operations can be applied on elements of an array. To obtain the stream object with array as source, you need to call stream method on Arrays class passing array as input to it.

	String[] stores = {"amazon", "ebay", "sears", 
			"jcpenny", "ebay"};
	
	Stream<String>  storesStream = Arrays.stream(stores);
	storesStream.distinct().forEach(s -> {
		System.out.println(s);
	});

Output

amazon
ebay
sears
jcpenny

Stream from Generator Function

Source of elements for a stream can be a generator function. For example range method of IntStream returns stream which emits elements in the given range.

	IntStream.range(10, 13).forEach(i -> {
		System.out.println(i);
	});

Output

10
11
12

Primitive Type Streams

Streams which can emit values of primitive data type are IntStream, LongStream, and DoubleStream. Primitive stream objects can be created using Arrays or range operations. Following is the IntStream and average operation example.

	int[] marks = {22, 35, 45, 28, 98};
	IntStream intStream = Arrays.stream(marks);
	
	System.out.println("average: "+intStream.average().getAsDouble());

Output

average: 45.6

DoubleStream and max operation example.

	DoubleStream doubleStream = DoubleStream.of(22.33, 33.45, 26.39, 76.28);
	System.out.println("max: "+doubleStream.max().getAsDouble());

Output

max: 76.28

Using Streams without Terminal Operations

If you try to use stream with just intermediate operations, which transform one stream into another stream, without using terminal operations which return non-stream objects, nothing will happen as stream are lazy meaning operations are applied on source elements only when terminal operation is called on the transformed stream.

If you run following example, nothing will be printed on console because terminal operation is not applied.

		Set<String> offers = new HashSet<String>();
		offers.add("coupons");
		offers.add("cashback");
		offers.add("deals");
		
		offers.stream().map(s -> {
			System.out.println("getting "+s);
			return "here are "+s;
		});

After you apply any terminal operation such as forEach, count, etc, all operations get executed.

	Object[] cpns = offers.stream().map(s -> {
		System.out.println("getting "+s);
		return "here are "+s;
	}).toArray();

Output

getting coupons
getting deals
getting cashback

Reusability of Stream Object

Stream object can be used only once. If you try to operate stream two times it will throw IllegalStateException exception. Following example throws the exception because priceStream is tried to be used two times.

	List<Double> price = new ArrayList<Double>();
	price.add(45.89);
	price.add(34.22);
	price.add(78.19);
	
	Stream<Double> priceStream  = price.stream();
	
	System.out.println("number of prices: "
			+priceStream.count());
	System.out.println("number of unique prices:"
			+priceStream.distinct().count());

Output

number of prices: 3
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

Parallel Stream

In the stream examples we have seen in previous sections, stream operations are sequentially applied on each element of the source. To apply operations parallel, you need to obtain parallel stream object, for example by calling parallelStream() method on collection source.

Following example shows forEach being applied sequentially and parallel. Parallel streams use multiple threads and apply operations, output is not in the order in which elements exist in the source.

	List<Double> price = new ArrayList<Double>();
	price.add(45.89);
	price.add(34.22);
	price.add(65.89);
	price.add(74.12);
	price.add(73.19);
	price.add(78.44);
	
	System.out.println("sequential stream output: ");
	price.stream().forEach( s -> {
		System.out.println(s);
	});
	
	System.out.println("parallel stream output: ");
	price.parallelStream().forEach( s -> {
		System.out.println(s);
	});

Output

sequential stream output: 
45.89
34.22
65.89
74.12
73.19
78.44
parallel stream output: 
74.12
78.44
73.19
34.22
45.89
65.89

Infinite Stream

Streams can be infinite meaning stream operations can be applied on elements of a source which produces unlimited elements. For example, Random object’s ints() methods returns unlimited stream of int values. You can limit the number of elements using limit() operation to create a stream which contains the limited number of integers or you can apply findFirst() operation to get first element.

	Random random = new Random();		
	System.out.println("first random number out of four: "
			+random.ints().limit(4).findFirst().getAsInt());

allMatch Operation

Stream operation allMatch takes predicate as input which will be executed against each element of the source. If the predicate returns true for all the elements, allMatch operation returns true.

	List<Double> price = new ArrayList<Double>();
	price.add(45.89);
	price.add(34.22);
	price.add(65.89);
	price.add(73.19);
	price.add(78.44);
	
	boolean isMatching = price.stream().allMatch(p -> {
		if(p > 50) {
			return true;
		}
		return false;
	});

Output:

all elements in the list are greater than 50: false

anyMatch Operation

Stream operation anyMatch is similar to allMatch, it takes predicate as input which will be executed against each element of the source. If the predicate returns true for any one of the elements, anyMatch operation will return true.

	List<Integer> marks = new ArrayList<Integer>();
	marks.add(67);
	marks.add(38);
	marks.add(49);
	
	boolean anyMatch = marks.stream().anyMatch(p -> {
		if(p > 50) {
			return true;
		}
		return false;
	});
	System.out.println("any element in the list is greater than 50: "+anyMatch);

Output

any element in the list is greater than 50: true

filter Operation

Using filter operation, you can create a new stream which contains elements from the source for which the given predicate returns true.

	List<String> coupons = new ArrayList<String>();		
	coupons.add("upto 20% off on fashion");
	coupons.add("flat 40% off on electronics");
	coupons.add("min 50% off on luggage");
	coupons.add("min 60% off on shoes");
	
	System.out.println("min offers: ");
	coupons.stream().filter(c -> {
		if(c.contains("min")) {
			return true;
		}
		return false;
	}).forEach(c -> {
		System.out.println(c);
	});

Output

min offers: 
min 50% off on luggage
min 60% off on shoes

flatMap Operation

Operation flatMap applies on each element in the source the given function which returns stream, then it combines the stream objects of all the elements and returns new stream object. The new stream will emit all the element of streams which are returned by the function.

	List<String> categories = new ArrayList<String>();		
	categories.add("fashion");
	categories.add("electronics");
	categories.add("appliance");
	
	categories.stream().flatMap(cat -> {
		List<String> subCategories = new ArrayList<String>();	
		if(cat.equals("fashion")) {
			subCategories.add("mens fashion");
			subCategories.add("women fashion");
			subCategories.add("kids fashion");
		}else if(cat.equals("electronics")) {
			subCategories.add("mobile");
			subCategories.add("tablets");
			subCategories.add("laptops");
		}else if(cat.equals("appliance")) {
			subCategories.add("home appliance");
			subCategories.add("kitchen appliance");
		}
		return subCategories.stream();
	}).forEach(sc -> {
		System.out.println(sc);
	});

Output

mens fashion
women fashion
kids fashion
mobile
tablets
laptops
home appliance
kitchen appliance

flatMapToInt Operation

Operation flatMapToInt is similar to flatMap except that the function passed to flatMapToInt operation returns IntStream. Similar to flatMapToInt , there are flatMapToDouble and flatMapToLong operations.

	List<String> students = new ArrayList<String>();		
	students.add("student one");
	students.add("student two");
	students.add("student three");
	
	int totalAmt = students.stream().flatMapToInt(st -> {
		int[] amount = {};
		if(st.equals("student one")) {
			amount = new int[] {23232, 45677};
		}else if(st.equals("student two")) {
			amount = new int[] {45454, 67883};
		}else if(st.equals("student three")) {
			amount = new int[] {2678, 21456};
		}
		return IntStream.of(amount);
	}).sum();
	
	System.out.println("total amount "+totalAmt);

Output

total amount 206380

map Operation

Operation map applies the given function on each element of the source, transforms each element and creates new streams containing the transformed elements.

	List<Double> price = new ArrayList<Double>();
	price.add(234.50);
	price.add(443.99);
	
	price.stream().map(p -> {
		System.out.println("calculating tax for price: "+p);
		return (30*p/100)-5;
	}).forEach(t -> {
		System.out.println("tax :"+t);
	});

Output

calculating tax for price: 234.5
tax :65.35
calculating tax for price: 443.99
tax :128.197

peek Operation

Peek operation executes the given consumer for each element of the source and returns the stream which contains elements of the first stream.

	List<Double> price = new ArrayList<Double>();
	price.add(234.50);
	price.add(565.99);
	price.add(873.49);
	price.add(23.29);
	
	double totalPrice = price.stream().mapToDouble(p -> {
		return p;	
	}).peek(p -> {
		System.out.println("adding price "+p);
	}).sum();
	
	System.out.println("total price "+totalPrice);

Output

adding price 234.5
adding price 565.99
adding price 873.49
adding price 23.29
total price 1697.27

reduce Operation

Operation reduce applies bi function on each element. The following example calculates the total, first argument of the bi function is element and second argument is the last value returned by the function.

	List<Double> price = new ArrayList<Double>();
	price.add(234.50);
	price.add(565.99);
	price.add(873.49);
	price.add(23.29);
	
	double total = price.stream().reduce((p, t) -> {
		return t+p;
	}).get();
	System.out.println("total  "+total);

Output

total  1697.27

sorted Operation

To create a stream with elements from source stream sorted in natural order or based on the given comparator, you can use sorted operation on the source stream.

	List<Double> price = new ArrayList<Double>();
	price.add(234.50);
	price.add(565.99);
	price.add(873.49);
	price.add(23.29);
	
	System.out.println("natural order");
	price.stream().sorted().forEach(p -> {
		System.out.println(p);
	});
	
	System.out.println("descending order");
	
	price.stream().sorted((p, np) -> {
		if(p > np) {
			return -1;
		}else {
			return 1;
		}
	}).forEach(p -> {
		System.out.println(p);
	});

Output

natural order
23.29
234.5
565.99
873.49
descending order
873.49
565.99
234.5
23.29