EL acessa um valor de mapa por chave inteira

85

Eu tenho um mapa codificado por inteiro. Usando EL, como posso acessar um valor por sua chave?

Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "One");
map.put(2, "Two");
map.put(3, "Three");

Achei que isso funcionaria, mas não (onde map já está nos atributos da solicitação):

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

Acompanhamento: localizei o problema. Aparentemente, ${name[1]}faz uma pesquisa no mapa com o número como um Long. Eu descobri isso quando mudei HashMappara TreeMape recebi o erro:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long

Se eu mudar meu mapa para:

Map<Long, String> map = new HashMap<Long, String>();
map.put(1L, "One");

em seguida, ${name[1]}retorna "Um". O que há com isso? Por que <c:out>trata um número como um longo. Parece contra-intuitivo para mim (já que int é mais comumente usado do que long).

Portanto, minha nova pergunta é: existe uma notação EL para acessar um mapa por um Integervalor?

Steve Kuo
fonte

Respostas:

117

Resposta inicial (EL 2.1, maio de 2009)

Conforme mencionado neste tópico do fórum java :

Basicamente, o autoboxing coloca um objeto Integer no Mapa. ie:

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

EL (Expressions Languages) avalia 0 como um Long e, portanto, vai à procura de um Long como a chave no mapa. ou seja, avalia:

map.get(new Long(0))

Como a Longnunca é igual a um Integerobjeto, ele não encontra a entrada no mapa.
É isso em poucas palavras.


Atualização desde maio de 2009 (EL 2.2)

Dezembro de 2009 viu a introdução de EL 2.2 com JSP 2.2 / Java EE 6 , com algumas diferenças em comparação com EL 2.1 .
Parece (" EL Expression parsing integer as long ") que:

você pode chamar o método intValueno próprio Longobjeto dentro de EL 2.2 :

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

Isso poderia ser uma boa solução aqui (também mencionado no Tobias Liefke 's resposta )


Resposta original:

EL usa os seguintes invólucros:

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

Página JSP demonstrando isso:

 <%@ 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>
VonC
fonte
Portanto, não há como fazer com que EL expanda um número como um inteiro?
Steve Kuo
1
@Steve: na verdade, EL não parece apoiar isso.
VonC
Eu encontrei esta pergunta e resposta em uma pesquisa no Google. Com certeza, assim que mudei de Map <Integer, String> para Map <Long, String>, fui capaz de extrair dele usando o EL que tinha em minha página JSP. Obrigado!!
John Munsch
@Steve: É possível - veja minha resposta
Tobias Liefke
@SteveKuo Deve ser mesmo possível. Agora percebo que esta resposta de 6 anos foi escrita quando o EL 2.2 ainda não era lançado. Eu editei e atualizei essa resposta.
VonC
11

Outra dica útil, além do comentário acima, seria quando você tem um valor de string contido em alguma variável, como um parâmetro de solicitação. Nesse caso, passar isso também resultará em JSTL digitando o valor de digamos "1" como um sting e, como tal, nenhuma correspondência sendo encontrada em um mapa de hash do mapa.

Uma maneira de contornar isso é fazer algo assim.

<c:set var="longKey" value="${param.selectedIndex + 0}"/>

Isso agora será tratado como um objeto Longo e então terá a chance de corresponder a um objeto quando estiver contido no mapa Mapa ou qualquer outra coisa.

Em seguida, continue como de costume com algo como

${map[longKey]}
Dave
fonte
10

Você pode usar todas as funções de Long, se você colocar o número em "(" ")". Dessa forma, você pode lançar o longo para um int:

<c:out value="${map[(1).intValue()]}"/>
Tobias Liefke
fonte
Não vi sua resposta imediatamente. +1. Eu incluí e documentei essa possibilidade em minha resposta para obter mais visibilidade.
VonC,
3

Com base na postagem acima, tentei fazer isso e funcionou bem. Eu queria usar o valor do Mapa B como chaves para o Mapa A:

<c:if test="${not empty activityCodeMap and not empty activityDescMap}">
<c:forEach var="valueMap" items="${auditMap}">
<tr>
<td class="activity_white"><c:out value="${activityCodeMap[valueMap.value.activityCode]}"/></td>
<td class="activity_white"><c:out value="${activityDescMap[valueMap.value.activityDescCode]}"/></td>
<td class="activity_white">${valueMap.value.dateTime}</td>
</tr>
</c:forEach>
</c:if>
Dhanashri
fonte
3

Se acontecer de você ter um Mapcom Integerteclas que não podem ser alteradas, você pode escrever uma função EL personalizada para converter um Longpara Integer. Isso permitiria que você fizesse algo como:

<c:out value="${map[myLib:longToInteger(1)]}"/>
Jasper de Vries
fonte