JAXB能否生成ArrayList而不是List?

4
<complexType name="BookShelf">
   <sequence>
      <choice minOccurs="0" maxOccurs="unbounded">
         <element name="newBook" type="string"/>
         <element name="oldBook" type="string"/>
      </choice>
   </sequence>
</complexType>

JAXB会将属性生成为List<JAXBElement<String>>,有没有办法将其生成为ArrayList?


2
为什么?JAXB从模式中生成了一种API。 API不应包含具体类。 ArrayList仅比List多一个公共方法:trimToSize(),这对您来说是否必要?顺便说一下:生成的代码在惰性getter中创建ArrrayList实例。 - Arne Burmeister
3个回答

14

为什么,那样做对你有什么好处?

  1. ArrayList<E> 没有公共方法不在List<E> 接口中,因此你不能使用 ArrayList<E> 做其他 List<E> 做不了的操作(当然实际上是有一个方法:ArrayList.trimToSize(),感谢 @Joachim Sauer 提供的信息,但很少用到)。
  2. 接受或返回实现类型而不是基础接口类型是可怕的做法。我建议你遵循 Sun Java 教程的 集合 Trail 和 / 或阅读 Joshua Bloch 的 Effective Java(你可以从这个短预览中获得他所讲述的想法,这是下面引用的来源)来了解更多关于集合框架和接口使用的知识。
  3. 谁说基础的 List 实现不是 ArrayListArrayList 是最常用的 List 实现,因此 JAXB 实际上可能会返回一个 ArrayList,只是不会告诉你(因为你不需要知道)。

第52条:通过接口引用对象(节选)

第40条包含建议,即应该使用接口而不是类作为参数类型。总的来说,应该优先使用接口而不是类来引用对象。如果适当的接口类型存在,则参数、返回值、变量和字段都应该使用接口类型声明。当你使用构造函数创建对象时,唯一需要引用对象的类的时候。以 Vector 为例,它是 List 接口的实现之一。养成这个习惯:

// Good - uses interface as type
List<Subscriber> subscribers = new Vector<Subscriber>();

而不是这样:

// Bad - uses class as type!
Vector<Subscriber> subscribers = new Vector<Subscriber>();

[ ... ]

来源:《Effective Java》,SafariBooksOnline预览


4
第一项并不完全正确(参见Arne提到的trimToSize())。 - Joachim Sauer
1
@Joachim 糟糕,是的,我错过了那个 :-) - Sean Patrick Floyd
3
用另一个问题回答问题是不礼貌的。 - hkropp
@jonbros 或许是这样,但如果直接回答问题而不让提问者意识到问题的缺陷,那就是不可原谅的。 - Sean Patrick Floyd
1
@jonbros 不,我没有忘记回答这个问题,我选择不按照字面意思回答。其他人确实按照字面意思回答了,但我选择挑战这个问题。显然,我的观点并不孤立。去回答一些问题,获得一些声望,并且可以将我的答案投下负票,但这仍然是回答这个问题的唯一合适方式。 - Sean Patrick Floyd

5
默认情况下,属性将是一个List,底层实现将是一个ArrayList。当然,您可以使用JAXB自定义来更改底层实现,或者使用自己的类并具有类型为ArrayList的属性(尽管出于其他答案中提到的原因,这很少是好主意)。
默认的JAXB生成:
给定您的XML模式:
<schema xmlns="http://www.w3.org/2001/XMLSchema">
   <complexType name="BookShelf">
      <sequence>
         <choice minOccurs="0" maxOccurs="unbounded">
            <element name="newBook" type="string"/>
            <element name="oldBook" type="string"/>
         </choice>
      </sequence>
   </complexType>
</schema>

使用以下命令行:

xjc -d out your-schema.xsd

JAXB 将生成以下类:
package generated;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementRefs;
import javax.xml.bind.annotation.XmlType;


@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BookShelf", propOrder = {
    "newBookOrOldBook"
})
public class BookShelf {

    @XmlElementRefs({
        @XmlElementRef(name = "newBook", type = JAXBElement.class),
        @XmlElementRef(name = "oldBook", type = JAXBElement.class)
    })
    protected List<JAXBElement<String>> newBookOrOldBook;

    public List<JAXBElement<String>> getNewBookOrOldBook() {
        if (newBookOrOldBook == null) {
            newBookOrOldBook = new ArrayList<JAXBElement<String>>();
        }
        return this.newBookOrOldBook;
    }

}

自定义生成

JAXB默认情况下,属性类型为List,底层实现为ArrayList。如果您希望控制底层实现,可以使用外部绑定文件,例如:

<jxb:bindings 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    version="2.1">

    <jxb:bindings schemaLocation="f3.xsd">
            <jxb:bindings node="//xs:complexType[@name='BookShelf']/xs:sequence/xs:choice">
                <jxb:property collectionType="java.util.LinkedList"/>
            </jxb:bindings>
    </jxb:bindings>

</jxb:bindings>

以下是 XJC 的调用:

xjc -d out -b binding.xml your-schema.xsd

为了获得以下类:
package generated;

import java.util.LinkedList;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementRefs;
import javax.xml.bind.annotation.XmlType;


@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BookShelf", propOrder = {
    "newBookOrOldBook"
})
public class BookShelf {

    @XmlElementRefs({
        @XmlElementRef(name = "oldBook", type = JAXBElement.class),
        @XmlElementRef(name = "newBook", type = JAXBElement.class)
    })
    protected List<JAXBElement<String>> newBookOrOldBook = new LinkedList<JAXBElement<String>>();

    public List<JAXBElement<String>> getNewBookOrOldBook() {
        if (newBookOrOldBook == null) {
            newBookOrOldBook = new LinkedList<JAXBElement<String>>();
        }
        return this.newBookOrOldBook;
    }

}

使用自己的类:

您也可以使用自己的类,并拥有一个类型为ArrayList的属性(尽管出于其他答案中提到的原因,这很少是一个好主意)。

package com.example;

import java.util.ArrayList;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementRefs;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BookShelf", propOrder = {
    "newBookOrOldBook"
})
public class BookShelf {

    @XmlElementRefs({
        @XmlElementRef(name = "oldBook", type = JAXBElement.class),
        @XmlElementRef(name = "newBook", type = JAXBElement.class)
    })
    protected ArrayList<JAXBElement<String>> newBookOrOldBook ;

    public ArrayList<JAXBElement<String>> getNewBookOrOldBook() {
        if (newBookOrOldBook == null) {
            newBookOrOldBook = new ArrayList<JAXBElement<String>>();
        }
        return this.newBookOrOldBook;
    }

}

更多信息:


喜欢你的帖子。谢谢。但是如果你想使用集合而不是列表或其他任何集合类型怎么办? - hkropp
@jonbros - 如果你从Java类开始,你可以使用一个集合类型的属性。如果你从XML模式开始,我不认为你能让它生成一个集合。虽然你可能可以通过XJC插件实现这一点:http://weblogs.java.net/blog/kohsuke/archive/2005/06/writing_a_plugi.html - bdoughan
是的,从XML开始。我会看一下插件。我在这里阅读了有关用户定义数据类型的信息:http://download.oracle.com/docs/cd/E12840_01/wls/docs103/webserv/data_types.html#wp221622它说对于java.utils.Set,“等效的XML模式数据类型”是Literal Array。但我不知道那意味着什么。谢谢! - hkropp

1

你无法改变API生成List的事实。

然而,假设底层实现实际上生成了一个ArrayList,你总是可以将其强制转换为ArrayList:

ArrayList<JAXBElement<String>> arrayList = 
        (ArrayList<JAXBElement<String>>) list;

或者如果它不是一个ArrayList(即,尝试上述操作会导致异常...),您可以生成一个包含列表相同元素的新ArrayList。

ArrayList<JAXBElement<String>> arrayList = 
        new ArrayList<JAXBElement<String>>(list);

通常情况下,您不需要执行任何此类操作:只要可能,最好针对接口抽象编写代码,而不是针对底层具体类编写代码。

1
这是一个经典的+1 / -1帖子。从技术角度来看,分享这个是很好的,但你应该a)更加强调这是一种糟糕的做法(也许用“不应该”代替“不需要”),b)几乎不应该在没有instanceof检查的情况下进行转换。 - Sean Patrick Floyd
1
@Sean Patrick Floyd - 对我来说,这是一个明确的(+1),因为(1)它提供了解决问题的方案,(2)我不知道实际要求是什么。也许有一个很好的理由。也许没有,但那么他应该研究你的答案 ;) - Andreas Dolk
嗨,肖恩:在许多情况下,没有instanceof检查的IMO转换是完全良好的实践。这意味着您知道或愿意对基础具体类做出假设。如果该假设被证明是错误的,则会出现运行时异常,这正是正确的行为... 这类似于您不需要在可能引起异常的每个函数调用周围放置try / catch的方式。 - mikera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接