El Access a Map Value by Integer Key

EL access a map value by Integer key

Initial answer (EL 2.1, May 2009)

As mentioned in this java forum thread:

Basically autoboxing puts an Integer object into the Map.
ie:

map.put(new Integer(0), "myValue")

EL (Expressions Languages) evaluates 0 as a Long and thus goes looking for a Long as the key in the map.
ie it evaluates:

map.get(new Long(0))

As a Long is never equal to an Integer object, it does not find the entry in the map.

That's it in a nutshell.


Update since May 2009 (EL 2.2)

Dec 2009 saw the introduction of EL 2.2 with JSP 2.2 / Java EE 6, with a few differences compared to EL 2.1.

It seems ("EL Expression parsing integer as long") that:

you can call the method intValue on the Long object self inside EL 2.2:

<c:out value="${map[(1).intValue()]}"/>

That could be a good workaround here (also mentioned below in Tobias Liefke's answer)


Original answer:

EL uses the following wrappers:

Terms                  Description               Type
null null value. -
123 int value. java.lang.Long
123.00 real value. java.lang.Double
"string" ou 'string' string. java.lang.String
true or false boolean. java.lang.Boolean

JSP page demonstrating this:

 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<%@ page import="java.util.*" %>

<h2> Server Info</h2>
Server info = <%= application.getServerInfo() %> <br>
Servlet engine version = <%= application.getMajorVersion() %>.<%= application.getMinorVersion() %><br>
Java version = <%= System.getProperty("java.vm.version") %><br>
<%
Map map = new LinkedHashMap();
map.put("2", "String(2)");
map.put(new Integer(2), "Integer(2)");
map.put(new Long(2), "Long(2)");
map.put(42, "AutoBoxedNumber");

pageContext.setAttribute("myMap", map);
Integer lifeInteger = new Integer(42);
Long lifeLong = new Long(42);
%>
<h3>Looking up map in JSTL - integer vs long </h3>

This page demonstrates how JSTL maps interact with different types used for keys in a map.
Specifically the issue relates to autoboxing by java using map.put(1, "MyValue") and attempting to display it as ${myMap[1]}
The map "myMap" consists of four entries with different keys: A String, an Integer, a Long and an entry put there by AutoBoxing Java 5 feature.

<table border="1">
<tr><th>Key</th><th>value</th><th>Key Class</th></tr>
<c:forEach var="entry" items="${myMap}" varStatus="status">
<tr>
<td>${entry.key}</td>
<td>${entry.value}</td>
<td>${entry.key.class}</td>
</tr>
</c:forEach>
</table>

<h4> Accessing the map</h4>
Evaluating: ${"${myMap['2']}"} = <c:out value="${myMap['2']}"/><br>
Evaluating: ${"${myMap[2]}"} = <c:out value="${myMap[2]}"/><br>
Evaluating: ${"${myMap[42]}"} = <c:out value="${myMap[42]}"/><br>

<p>
As you can see, the EL Expression for the literal number retrieves the value against the java.lang.Long entry in the map.
Attempting to access the entry created by autoboxing fails because a Long is never equal to an Integer
<p>

lifeInteger = <%= lifeInteger %><br/>
lifeLong = <%= lifeLong %><br/>
lifeInteger.equals(lifeLong) : <%= lifeInteger.equals(lifeLong) %> <br>

JSTL Access Integer/Long key in Hash Map

What is currObj? Can you redefine its currVal member as a Long (or long)?


A numeric literal (matching the IntegerLiteral production in the EL syntax) will be represented as a Long. The expression currObj.currVal evaluates to an Integer. A Long never equals() an Integer, so one expression must result in a different type.

Essentially, what you need is an explicit type conversion. Nothing like this is built into EL, but you could create a custom EL function to do it for you. This is a static function that you implement in Java, then describe in a TLD. Another answer of mine gives an example of the packaging. Here's what the function and its usage could look like in your case.

package com.y.taglib.core;

public final class CoercionUtil {

public static Long toLong(Long n) {
return n;
}

}

The TLD would look like this:

<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0">
<tlib-version>1.0</tlib-version>
<short-name>x-c</short-name>
<uri>http://dev.y.com/taglib/core/1.0</uri>
<function>
<description>Coerce to a java.lang.Long.</description>
<display-name>long</display-name>
<name>long</name>
<function-class>com.y.taglib.core.CoercionUtil</function-class>
<function-signature>java.lang.Long toLong(java.lang.Long)</function-signature>
</function>
</taglib>

In your JSP:

<%@taglib uri="http://dev.y.com/taglib/core/1.0" prefix="my" %>
...
<c:if test='${ ! empty testMap[my:long(currObj.currVal)]}'>

The JSP engine takes care of the necessary type coercion (from the Integer result of currVal to the Long required by the toLong() method. Your method is there simply to indicate the required type; without it, the JSP engine sees the (erased) type of the argument of testMap.get(Object), and doesn't see the need to perform any coercion since Integer is-an Object.

Accessing HashMap in EL with string + int key

This code can accomplish desired behavior

<c:forEach begin="1" end="5" var="current">
<c:set var="key" value="elem-${current}" />
<c:out value="${myHashMap[key]}"/>
</c:forEach>

Accessing Map of Map by a valid key gives NPE

A couple of null checks are needed to prevent NPE, however in both cases this can be resolved with the help of Map::getOrDefault method.

Also, the type and assignment to year should be simplified. Integer.valueOf is needed only if balance.getYear() returns a string value, otherwise it would be ok to have it as Integer year = balance.getYear();

for (AnnualLeaveBalance balance: annualLeaveBalanceList) {
Map<Integer, Integer> yearToCountMap = employeeYearWiseLeaveAppCount.getOrDefault(
balance.getEmployeeId(), Collections.emptyMap()
); // empty map is returned if no yearToCount map is found for a user

Integer year = balance.getYear();
int takenLeave = yearToCountMap.getOrDefault(year, 0); // no leave taken
}

LinkedHashMap assign to new map with different key

Such operation is generally called "mapping" or "transforming" a collection. It applies not only to maps, but to lists and other collections as well.

As you only need to map keys and keep values intact, you can use mapKeys() function in Kotlin:

val input = mapOf(1 to "foo", 2 to "bar")
val result = input.mapKeys { it.key.toString() }

Replace toString() with whatever you use to convert int to a string.



Related Topics



Leave a reply



Submit