ui:repeat中的UISelectMany导致java.lang.ClassCastException:[Ljava.lang.Object;无法转换为java.util.List [英] UISelectMany in ui:repeat causes java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.util.List

查看:544
本文介绍了ui:repeat中的UISelectMany导致java.lang.ClassCastException:[Ljava.lang.Object;无法转换为java.util.List的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经使用HashMap方法成功地将复选框列表绑定到Map<String, Boolean>.很好,因为它允许您动态地显示复选框.

I have used the HashMap method for binding a list of checkboxes to a Map<String, Boolean> with success. This is nice since it allows you to have a dynamic number of checkboxes.

我正在尝试将其扩展为selectManyMenu的可变长度列表.由于它们是selectMany,我希望能够绑定到Map<String, List<MyObject>>.我有一个单独的示例,可以将单个selectManyMenu绑定到List<MyObject>,并且一切正常,但是乳清我在ui:repeat内放入了动态数量的selectManyMenus并尝试绑定到地图,最终结果很奇怪.值已正确地存储在映射中(由调试器验证)并调用toString(),但是运行时认为映射的值是Object而不是List<MyObject>类型,当我尝试访问映射的键时抛出ClassCastExceptions

I'm trying to extend that to a variable length list of selectManyMenu. Being that they are selectMany, I'd like to be able to bind to a Map<String, List<MyObject>>. I have a single example working where I can bind a single selectManyMenu to a List<MyObject> and everything works fine, but whey I put a dynamic number of selectManyMenus inside a ui:repeat and attempt to bind to the map, I end up with weird results. The values are stored correctly in the map, as verified by the debugger, and calling toString(), but the runtime thinks the map's values are of type Object and not List<MyObject> and throws ClassCastExceptions when I try to access the map's keys.

我猜测这与JSF如何确定绑定目标的运行时类型有关,并且由于我绑定到Map中的值,因此不知道从中获取类型地图的值类型参数.除了修补Mojarra之外,是否有其他解决方法?

I'm guessing it has something to do with how JSF determines the runtime type of the target of your binding, and since I am binding to a value in a Map, it doesn't know to get the type from the value type parameter of the map. Is there any workaround to this, other than probably patching Mojarra?

通常,我如何拥有一个动态数量为selectManyMenus的页面?没有,当然要使用Primefaces的<p:solveThisProblemForMe>组件. (实际上,由于我无法控制的因素,Primefaces在这里不是一个选择.)

In general, how can I have a page with a dynamic number of selectManyMenus? Without, of course using Primefaces' <p:solveThisProblemForMe> component. (In all seriousness, Primefaces is not an option here, due to factors outside of my control.)

问题 UISelectMany on List <&导致java.lang.ClassCastException:无法将java.lang.String强制转换为T ,其中有些我不知道的好信息,但是此SSCE仍然存在问题:

The question UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T had some good information that I wasn't aware of, but I'm still having issues with this SSCE:

JSF:

JSF:

  <ui:define name="content">
    <h:form>
      <ui:repeat value="#{testBean.itemCategories}" var="category">
        <h:selectManyMenu value="#{testBean.selectedItemMap[category]}">
          <f:selectItems value="#{testBean.availableItems}" var="item" itemValue="#{item}" itemLabel="#{item.name}"></f:selectItems>
          <f:converter binding="#{itemConverter}"></f:converter>
          <f:validator validatorId="test.itemValidator"></f:validator>
        </h:selectManyMenu>
      </ui:repeat>
      <h:commandButton value="Submit">
        <f:ajax listener="#{testBean.submitSelections}" execute="@form"></f:ajax>
      </h:commandButton>
    </h:form>
  </ui:define>

转换器:

@Named
public class ItemConverter implements Converter {

  @Inject
  ItemStore itemStore;

  @Override
  public Object getAsObject(FacesContext context, UIComponent component, String value) {
    return itemStore.getById(value);
  }

  @Override
  public String getAsString(FacesContext context, UIComponent component, Object value) {
    return Optional.of(value)
                   .filter(v -> Item.class.isInstance(v))
                   .map(v -> ((Item) v).getId())
                   .orElse(null);
  }
}

Backing Bean:

Backing Bean:

@Data
@Slf4j
@Named
@ViewScoped
public class TestBean implements Serializable {

  private static final long serialVersionUID = 1L;

  @Inject
  ItemStore itemStore;

  List<Item> availableItems;

  List<String> itemCategories;

  Map<String, List<Item>> selectedItemMap = new HashMap<>();

  public void initialize() {
    log.debug("Initialized TestBean");

    availableItems = itemStore.getAllItems();

    itemCategories = new ArrayList<>();
    itemCategories.add("First Category");
    itemCategories.add("Second Category");
    itemCategories.add("Third Category");
  }

  public void submitSelections(AjaxBehaviorEvent event) {
    log.debug("Submitted Selections");

    selectedItemMap.entrySet().forEach(entry -> {
      String key = entry.getKey();
      List<Item> items = entry.getValue();

      log.debug("Key: {}", key);

      items.forEach(item -> {
        log.debug("   Value: {}", item);
      });

    });

  }

}

ItemStore仅包含HashMap和委托方法,以按其ID字段访问Items.

ItemStore just contains a HashMap and delegate methods to access Items by their ID field.

项目:

@Data
@Builder
public class Item {
  private String id;
  private String name;
  private String value;
}

ItemListValidator:

ItemListValidator:

@FacesValidator("test.itemValidator")
public class ItemListValidator implements Validator {

  @Override
  public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
    if (List.class.isInstance(value)) {

      if (((List) value).size() < 1) {
        throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_FATAL, "You must select at least 1 Admin Area", "You must select at least 1 Admin Area"));
      }
    }
  }

}

错误:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.util.List

Stacktrace被截断,但发生在此行:

Stacktrace snipped but occurs on this line:

List<Item> items = entry.getValue();

我在这里想念什么?

推荐答案

如相关问题所示

As hinted in the related question UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T, generic type arguments are unavailable during runtime. In other words, EL doesn't know you have a Map<String, List<Item>>. All EL knows is that you have a Map, so unless you explicitly specify a converter for the selected values, and a collection type for the collection, JSF will default to String for selected values and an object array Object[] for the collection. Do note that the [ in [Ljava.lang.Object indicates an array.

鉴于您希望集合类型是java.util.List的实例,您需要使用所需具体实现的FQN指定collectionType属性.

Given that you want the collection type to be an instance of java.util.List, you need to specify the collectionType attribute with the FQN of the desired concrete implementation.

<h:selectManyMenu ... collectionType="java.util.ArrayList">

然后,JSF将确保实例化正确的集合类型,以填充所选项目并放入模型中.这是一个相关的问题,在其中使用了这样的解决方案,但随后又出于不同的原因:

JSF will then make sure that the right collection type is being instantiated in order to fill the selected items and put in the model. Here's a related question where such a solution is being used but then for a different reason: org.hibernate.LazyInitializationException at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel.

更新:我应该已经验证了上述理论.当collectionType后面的集合又包装在另一个通用集合/地图中时,这在Mojarra中不起作用. Mojarra仅在UISelectMany值本身已经表示java.util.Collection的实例时检查collectionType.但是,由于将其包装在Map中,其(原始)类型变为java.lang.Object,因此Mojarra将跳过对任何collectionType的检查.

Update: I should have tested the above theory. This doesn't work in Mojarra when the collection behind collectionType is in turn wrapped in another generic collection/map. Mojarra only checks the collectionType if the UISelectMany value itself already represents an instance of java.util.Collection. However, due to it being wrapped in a Map, its (raw) type becomes java.lang.Object and then Mojarra will skip the check for any collectionType.

MyFaces在其UISelectMany渲染器中做得更好,可以在那工作.

MyFaces did a better job in this in its UISelectMany renderer, it works over there.

据我检查Mojarra的源代码,除了用List<Category>代替Map<String, List<Long>>之外,没有其他方法可以解决,其中Category是具有String nameList<MyObject> selectedItems属性的自定义对象.没错,这确实消除了Map在EL中具有动态键的优势,但这就是它的本质.

As far as I inspected Mojarra's source code, there's no way to work around this other way than replacing Map<String, List<Long>> by a List<Category> where Category is a custom object having String name and List<MyObject> selectedItems properties. True, this really kills the advantage of Map of having dynamic keys in EL, but it is what it is.

这是一个 MCVE ,其中使用Long作为项目类型(只需将其替换为您的MyObject):

Here's a MCVE using Long as item type (just substitute it with your MyObject):

private List<Category> categories;
private List<Long> availableItems;

@PostConstruct
public void init() {
    categories = Arrays.asList(new Category("one"), new Category("two"), new Category("three"));
    availableItems = Arrays.asList(1L, 2L, 3L, 4L, 5L);
}

public void submit() {
    categories.forEach(c -> {
        System.out.println("Name: " + c.getName());

        for (Long selectedItem : c.getSelectedItems()) {
            System.out.println("Selected item: " + selectedItem);
        }
    });

    // ...
}

public class Category {

    private String name;
    private List<Long> selectedItems;

    public Category(String name) {
        this.name = name;
    }

    // ...
}

<h:form>
    <ui:repeat value="#{bean.categories}" var="category">
        <h:selectManyMenu value="#{category.selectedItems}" converter="javax.faces.Long">
            <f:selectItems value="#{bean.availableItems}" />
        </h:selectManyMenu>
    </ui:repeat>
    <h:commandButton value="submit" action="#{bean.submit}">
        <f:ajax execute="@form" />
    </h:commandButton>
</h:form>

请注意,此处不需要collectionType.仍然只需要converter.

Do note that collectionType is unnecessary here. Only the converter is still necessary.

无关与具体问题无关,我想指出的是,selectedItemMap.entrySet().forEach(entry -> { String key ...; List<Item> items ...;})可以简化为selectedItemMap.forEach((key, items) -> {}),而ItemListValidator则是不必要的,如果您仅在计算机上使用required="true"输入组件.

Unrelated to the concrete problem, I'd like to point out that selectedItemMap.entrySet().forEach(entry -> { String key ...; List<Item> items ...;}) can be simplified to selectedItemMap.forEach((key, items) -> {}) and that ItemListValidator is unnecessary if you just use required="true" on the input component.

这篇关于ui:repeat中的UISelectMany导致java.lang.ClassCastException:[Ljava.lang.Object;无法转换为java.util.List的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆