Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Java8: Lambda Expression with Shape Class

5.00/5 (2 votes)
10 Oct 2015CPOL2 min read 15.9K   13  
Implement the Lambda expression to handle the class Shape in Java8: the power of declaration programming.

Introduction

This tip is an introduction of declarative programming style in Java 8.
We will view the STREAM in Java 8, introducing some concepts of declarative/functional style to process the collections in other sources.

An example at a glance: shapes.
Given a collection of shapes represented by the "Shape Class", we want to collect all those that have an area less than 400, sorted.

Pre-Stream Solution

Java
List < Shape > lowAreaShapes = new ArrayList < > ();
for (Shape s: shapeSet) {
	if (s.getArea() < 400) lowAreaShapes.add(d);
}

Collections.sort(lowAreaShapes, new Comparator < Shape > () {
	public int compare(Shape s1, Shape s2) {
		return Double.compare(s1.getArea(), s2.getArea());
	}
});

List < String > lowAreaShapesName = new ArrayList < > ();
for (Shape s: lowAreaShapes) {
	lowAreaShapesName.add(d.getName());
}

Stream-Based Solution

Java
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;

List < String > lowAreaShapesName= 
    menu.stream()
	    .filter(s -> s.getArea() < 400)
	    .sorted(comparing((Shape s) -> s.getArea())
	    .map((Shape s) -> s.getName())
	    .collect(toList());

Stream-Bases Solution

Points of Interest

What are the Benefits?

The evident impact is the elegance of our source code:

  • More declarative
    • We want to specify what we want, not how to get it
    • Complex computations can be obtained by combining several operations together in cascade in pipeline style
  • More composable
    • We have algebraic composition, no side effect (how we note from the image upon)
  • Parallelizable
    • We can use, in the code, ".parallelStream() ", instead ".stream()"

Example

For understanding the full power of streams, in general, we need a simple example.
We have the classes P2d and V2d which represent a point and a vector in a plane in the graphics viewport (that having extreme (0.0) as a corner the upper left and (w,h), w>0, h>0 as in the lower right corner), and the class BBox representing a rectangular bounding box. We want to define, in the same package, the class "Shape" that is characterized by the following methods:

Java
package code.project.streams;
/**
 * Interface that represent a shape in the graphic viewport (0,0)->(w,h)
 */
public interface Shape {
    void move(V2d dv) //move the shape of a certain vector, passed as parameter
    double getPerim() //compute the perimeter
    bool isInside(BBox bbox) //checking if the figure falls within the bounding box specified
    bool contains(P2d p0) //checks if the point belongs or not to the figure 
}

Afterwards, define the classes that implement the interface upon:

  • Line
  • Rect (that represent a Rectangle)
  • Circle

Class Line

A line must have two points: "a" (point extreme left) and "b" (point extreme right) - for example. Not only: all methods of interfaces "shape" must be implemented.

Java
package code.project.streams;

public class Line implements Shape {

	private P2d a, b;
    //The method design a simply line that it composed by 
    //two coordinated: (x0,y0) - extreme left - and (x1,y1) - extreme right
	public Line(int x0, int y0, int x1, int y1) {
		a = new P2d(x0, y0);
		b = new P2d(x1, y1);
	}

    //To move the figure simply use the vector passed as a parameter
	@Override
	public void move(V2d v) {
		a = a.sum(v);
		b = b.sum(v);
	}

    //To return the perimeter is sufficient to calculate 
    //the distance between the point "a" and "b"
	@Override
	public double getPerim() {
		return Math.abs(P2d.distance(a, b));
	}

	@Override
	public boolean isInside(P2d p1, P2d p2) {
		if ((Math.abs(a.getX()) <= Math.abs(p1.getX())) && // left extreme X
				(Math.abs(a.getY()) >= Math.abs(p1.getY())) && // left extreme Y
				(Math.abs(b.getX()) <= Math.abs(p2.getX())) && // right extreme X
				(Math.abs(b.getY()) <= Math.abs(p2.getY()))) // right extreme Y
			return true;
		else
			return false;
	}

	@Override
	public boolean contains(P2d p) {
		// if (distance(A, C) + distance(B, C) == distance(A, B))
		// return true; // C is on the line.
		// return false; // C is not on the line.

		if (P2d.distance(a, p) + P2d.distance(b, p) == P2d.distance(a, b))
			return true;
		else
			return false;
	}

	@Override
	public String toString() {
		return "Line - Point a(" + a.getX() + 
			"-" + a.getY() + ") Point b("
				+ b.getX() + "-" + b.getY() + ")";
	}
}

For the other class, you can download the complete source code.

Now, we want to define the "Utility" class with the following methods, using appropriate expressions and Lambda Stream in their implementation:

  • moveShapes
    Java
    //given a list of shape and a vector v, moves each shape
    public static void moveShapes(List<shape> listShape, V2d v) {
    	listShape.forEach(s -> s.move(v));
    }
  • inBBox
    Java
    //given a list of shapes and a bounding box, 
    //computes the list of shapes contained in the bounding box p0 p1
    public static List<shape> inBBox(List<shape> listShape, P2d p0, P2d p1) {
    	return listShape.stream()
    					.filter(s -> s.isInside(p0, p1))
    					.collect(toList());
    }
  • maxPerim
    Java
    //given a list of shapes, identifies the max perimeter
    public static OptionalDouble maxPerim(List<shape> listShape) {
    	return listShape.stream()
    			.mapToDouble(s -> s.getPerim())
    			.max();
    }
  • shapeWithMaxPerim
    Java
    //given a list of shapes, determines the shape with the larger perimeter
    public static Shape shapeWithMaxPerim(List<shape> listShape) {
    	return listShape.stream()
    			.max((p1, p2) -> Double.compare(p1.getPerim(), 
                                             p2.getPerim()))
    			.get();
    			}
  • contains
    Java
    //given a list of shapes and a point, verify if there is a shape that contains the point
    public static Boolean contains(List<shape> listShape, P2d p) {
    	return listShape.stream()
    	                .filter(s -> s.contains(p))
    	                .findFirst()
    	        	.isPresent();
    }
  • getContaining
    Java
    //given a list of shapes and a point p, computes the list of shapes that contain the point
    public static List<shape> getContaining(List<shape> listShape, P2d p) {
    	return listShape.stream()
    	                .filter(s -> s.contains(p))
    	                .collect(toList());
    }
  • logAll
    Java
    //given a list of shapes, print the log
    public static void logAll(List<shape> listShape) {
    	listShape.forEach(System.out::println);
    }		

There are many examples to understand the power that the lambda expression has in general: without the streams, we would have had to write many lines of code; in this manner we can focus on "what" and not on "who".
For example, the method inBBox works in this manner:

stream-view

Stages

  • Creation of stream from a data source
    • with the instruction listShape.streams()
  • Application of one or more intermediate data processing operations
    • there returns a stream, so as to create pipelines
    • examples: filter, map, limit, ...
  • Closing operation
    • used to collect/sink elements from the stream, like a reducing.
    • examples:
      • collect: Converts the stream in another form
      • foreach: Applies a lambda to every element of the stream???

Test the Streams

For testing the streams, we can call the static method in this manner:

Java
package code.project.streams;

import java.util.Arrays;
import java.util.List;

public class TestShapes {

	public static void main(String args[]) {

		final List< shape > listShape = Arrays.asList(new Line(0, 0, 0, 7),
							      new Line (0,0,0,8),	
							      new Rect(1, 1, 3, 5)
									);
		Utils.logAll(listShape);
		Utils.moveShapes(listShape, new V2d(1, 1));
		Utils.logAll(listShape);
	}
}

Conclusion

As we can see, we can manage all shapes with the streams/lambda expression.
A few lines of code are enough for managing the shapes and making the code better, through a declarative style and therefore more readable. For the complete code, you can download here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)