EL 3.0 overview
EL 3.0 (JSR 341, part of Java EE 7) represents a major boost of EL 2.2. The main features of EL 3.0 are as follows:
- New operators
+
,=
, and;
- Lambda expressions
- Collection objects support
- An API for standalone environments
In the upcoming sections, you will see how to use EL 3.0 features in JSF pages.
Working with the assignment operator
In an expression of type, x = y
, the assignment operator (=
), assign the value of y
to x
. In order to avoid an error of the kind PropertyNotWritableException
, the x
value must be an lvalue. The following examples show you how to use this operator in two simple expressions:
#{x = 3}
evaluates to 3#{y = x + 5}
evaluates to 8
The assignment operator is right-associative (z = y = x
is equivalent with z = (y = x)
). For example, #{z = y = x + 4}
evaluates to 7.
Working with the string concatenation operator
In an expression of type, x += y
, the string concatenation operator (+=
) returns the concatenated string of x
and y
. For example:
#{x += y}
evaluates to 37#{0 += 0 +=0 += 1 += 1 += 0 += 0 += 0}
evaluates to 00011000
In EL 2.2, you can do this using the following code:
#{'0'.concat(0).concat(0).concat(1).concat(1).concat(0).concat(0).concat(0)}
Working with the semicolon operator
In an expression of type, x; y
, x
is first evaluated, and its value is discarded. Next, y
is evaluated and its value is returned. For example, #{x = 5; y = 3; z = x + y}
evaluates to 8.
Exploring lambda expressions
A lambda expression can be disassembled in three main parts: parameters, the lambda operator (->
), and the function body.
Basically, in Java language, a lambda expression represents a method in an anonymous implementation of a functional interface. In EL, a lambda expression is reduced to an anonymous function that can be passed as an argument to a method.
It is important to not confuse Java 8 lambda expressions with EL lambda expressions, but in order to understand the next examples, it is important to know the fundamentals of Java 8 lambda expressions (http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html). They don't have the same syntax, but they are similar enough to not cause notable discomfort when we need to switch between them.
An EL lambda expression is a parameterized ValueExpression
object. The body of an EL lambda expression is an EL expression. EL supports several kinds of lambda expressions. The simplest type of EL lambda expressions are immediately invoked, for example:
#{(x->x+1)(3)}
evaluates to 4#{((x,y,z)->x-y*z)(1,7,3)}
evaluates to -20
Further, we have assigned lambda expressions. These are invoked indirectly. For example, #{q = x->x+1; q(3)}
evaluates to 4.
Indirectly, invocation can be used to write functions. For example, we can write a function to calculate n mod m
(without using the %
operator). The following example is evaluated to 3:
#{modulus = (n,m) -> m eq 0 ? 0 : (n lt m ? n: (modulus(n-m, m))); modulus(13,5)}
We can call this function from other expressions. For example, if we want to calculate the greatest common divisor of two numbers, we can exploit the preceding function; the following example is evaluated to 5:
#{gcd = (n,m) -> modulus(n,m) == 0 ? m: (gcd(m, modulus(n,m))); gcd(10, 15)}
Lambda expressions can be passed as arguments to methods. For example, in the following example, you call a method named firstLambdaAction
—the lambda expression is invoked from this method:
#{lambdaBean.firstLambdaAction(modulus = (n,m) -> m eq 0 ? 0 : (n lt m ? n: (modulus(n-m, m))))}
Now, the firstLambdaAction
method is as follows:
public Object firstLambdaAction(LambdaExpression lambdaExpression) { //useful in case of a custom ELContext FacesContext facesContext = FacesContext.getCurrentInstance(); ELContext elContext = facesContext.getELContext(); return lambdaExpression.invoke(elContext, 8, 3); //or simply, default ELContext: //return lambdaExpression.invoke(8, 3); }
Another powerful feature of lambda expressions consists of nested lambda expressions. For example (first, is evaluated the inner expression to 7, afterwards the outer expression to as, 10 - 7): #{(x->x-((x,y)->(x+y))(4,3))(10)}
evaluates to 3.
Do you think EL lambda expressions rocks? Well, get ready for more. The real power is unleashed only when we bring collection objects into equations.
Working with collection objects
EL 3.0 provides powerful support to manipulate collection objects by applying operations in a pipeline. The methods supporting the collection operations are implemented as ELResolvers
, and lambda expressions are indicated as arguments for these methods.
The main idea behind manipulating collection objects is based on streams. More precisely, the specific operations are accomplished as method calls to the stream of elements obtained from the collection. Many operations return streams, which can be used in other operations that return streams, and so on. In such a case, we can say that we have a chain of streams or a pipeline. The entry in the pipeline is known as the source, and the exit from the pipeline is known as the terminal operation (this operation doesn't return a stream). Between the source and terminal operation, we may have zero or more intermediate operations (all of them return streams).
The pipeline execution begins when the terminal operation starts. Because intermediate operations are lazy evaluated, they don't preserve intermediate results of the operations (an exception is the sorted operation, which needs all the elements to sort tasks).
Now, let's see some examples. We begin by declaring a set, a list, and a map—EL contains syntaxes to construct sets, lists, and maps dynamically as follows:
#{nr_set = {1,2,3,4,5,6,7,8,9,10}} #{nr_list = [1,2,3,4,5,6,7,8,9,10]} #{nr_map = {"one":1,"two":2,"three":3,"four":4,"five":5,"six":6,"seven":7,"eight":8,"nine":9,"ten":10}}
Now, let's go a step further and sort the list in ascending/descending order. For this, we use the stream
, sorted
(this is like the ORDER BY
statement of SQL), and toList
methods (the latter returns a List
that contains the elements of the source stream), as shown in the following code:
#{nr_list.stream().sorted((i,j)->i-j).toList()} #{ nr_list.stream().sorted((i,j)->j-i).toList()}
Further, let's say that we have the following list in a managed bean named LambdaBean
:
List<Integer> costBeforeVAT = Arrays.asList(34, 2200, 1350, 430, 57, 10000, 23, 15222, 1);
Next, we can apply 24 percent of VAT and compute the total for costs higher than 1,000 using the filter
(this is like SQL's WHERE
and GROUP BY
statements), map
(this is like SQL's SELECT
statement), and reduce
(this is like the aggregate functions) methods. These methods are used as follows:
#{(lambdaBean.costBeforeVAT.stream().filter((cost)-> cost gt 1000).map((cost) -> cost + .24*cost)).reduce((sum, cost) -> sum + cost).get()}
These were just a few examples of using collection objects in EL 3.0. A complete application named ch1_4
is available for download in the code bundle of this chapter. Since, in this application you can see more than 70 examples, I recommend you to take a look at it. Moreover, a nice example can be found on Michael Müller's blog at http://blog.mueller-bruehl.de/web-development/using-lambda-expressions-with-jsf-2-2/.
But, what if we want to take advantage of lambda expressions, but we don't like to write such expressions? Well, a solution can be to write parameterized functions based on lambda expressions, and call them in the JSTL style. For example, the following function is capable of extracting a sublist of a List
:
#{get_sublist = (list, left, right)->list.stream().substream(left, right).toList()}
Now, we can call it as shown in the following code:
<ui:repeat value="#{get_sublist(myList, from, to)}" var="t"> #{t} </ui:repeat>
In the complete application, named ch1_5
, you can see a bunch of 21 parameterized functions that can be used with List
s.