开发者

Which Java generic should be used in ambiguous cases?

开发者 https://www.devze.com 2022-12-13 02:03 出处:网络
I\'m having some problems with a Wicket 1.3开发者_开发问答 -> Wicket 1.4 migration, but this question could be applied to Java generics overall, too.The migration has caused hundreds of warnings to sp

I'm having some problems with a Wicket 1.3开发者_开发问答 -> Wicket 1.4 migration, but this question could be applied to Java generics overall, too. The migration has caused hundreds of warnings to spring up out of nowhere -- for those unfamiliar with Wicket, many Wicket classes are derived from a common ancestor, which became generified in v1.4 -- and I'm not sure what parameters to apply in some cases, mostly assorted forms and tables. I'm thinking they could do with <?>, <Object> or <Void>, but I'm not sure which.

<?> seems most appropriate to me, but there are many places where I can't use a wildcard. <Object> works in all cases, but it makes me uneasy because it's basically writing a wildcard without using the wildcard, which just feels inherently wrong to part of my brain. And using <Void> was suggested in the Wicket migration guide.

So what is the proper thing to do in this case?


EDIT 2: I think my first edit (now at the bottom of the question) confused people by making it seem like I was just asking about collections of strings. Here are other examples and their warnings:

public class DocumentProcessor extends Form implements DocumentManagement { ...

Form is a raw type. References to generic type Form should be parameterized

AjaxFallbackDefaultDataTable theTable = new AjaxFallbackDefaultDataTable("theTable", cols, dataProvider, recPerPg);

Multiple markers at this line

- Type safety: The constructor AjaxFallbackDefaultDataTable(String, List, ISortableDataProvider, int) belongs to the raw type AjaxFallbackDefaultDataTable. References to generic type AjaxFallbackDefaultDataTable should be parameterized

- AjaxFallbackDefaultDataTable is a raw type. References to generic type AjaxFallbackDefaultDataTable should be parameterized

- AjaxFallbackDefaultDataTable is a raw type. References to generic type AjaxFallbackDefaultDataTable should be parameterized


EDIT: I was hoping to make the question so broad it didn't need sample code, but here is some.

List<IColumn> columns = new ArrayList<IColumn>();
columns.add(new PropertyColumn(new Model<String>("Number"), "revisionID"));

These warnings are generated:

Multiple markers at [the first] line

- IColumn is a raw type. References to generic type IColumn should be parameterized

- IColumn is a raw type. References to generic type IColumn should be parameterized

Multiple markers at [the second] line

- Type safety: The constructor PropertyColumn(IModel, String) belongs to the raw type PropertyColumn. References to generic type PropertyColumn should be parameterized

- PropertyColumn is a raw type. References to generic type PropertyColumn should be parameterized

There are no errors.


Use Void if you're not going to make use of the component's underlying model object.

Semantically, it is more sound and conveys better the idea that it's not that the model object can be anything, but that it is semantically nothing and will never be used. The Void keyword is mostly used as the conventional solution in this kind of situation.

If you're going to use the model object and don't care, which I don't think is what you meant, **use wildcards where you can** and, where you can't (constructor arguments, etc), either Void, Object, or possibly some other "encompassing" class, making a decision based on your component's specific semantics and desired generic-typing behavior (as an example, in the case of a constructor for a component variable, you'd think about what your constructor will do with a Void or an Object typing).

Of course, that's in terms of "good programming practices" theory, in practice you don't have to care much, though this kind of thinking can help your teammates maintain your code, and can help you better understand it, maybe even predict bugs.

Using wildcards everywhere is fairly common among wicket users, perhaps even more common than deciding as I suggested, but that's not because wildcards are the convention, but is most likely simply due to most code examples that pop up in search engines preferring wildcards. Nonetheless, as the migration guide's suggestion of Void shows, not only are wildcards less semantically coherent, but also they don't seem to be an absolute convention, even challenged, it would seem, by wicket's developers, who, we'd better assume, know enough about their types' inner workings for their recommendations to be taken seriously.


Alternatives available would be:

  1. To just use the raw type, as you have in the sample code, simply ignore the warnings
  2. To use the wildcard/Object generic
  3. To use an extends generic

I am assuming from your question that #1 is not a viable option for you.

Example for #2 (wildcard/Object)

List<IColumn<?>> columns = new ArrayList<IColumn<?>>();

OR

List<IColumn<Object>> columns = new ArrayList<IColumn<Object>>();

IMO I don't think it really matters whether you choose ? or Object, and neither one is more correct than the other, at least functionally.
If you don't care what the generic is, and you never access it, then it is of nigh consequence; although think ahead carefully, if indeed it is possible you would use generics here in the future. This will likely be the case only where in your pre-migration code, you found yourself not having to typecast anything from within the IColumn objects.

Example for #3 (extends generic)

Create a supertype or common interface to all the possible generics of the IColumn type. Where
T extends MyType:

List<IColumn<T>> columns = new ArrayList<IColumn<T>>();

I would base the decision in choosing, between the 2nd and 3rd method, on what the possible generic attributes for IColumn actually are.

  • If they are your own classes AND you actualy want to access objects of the generic type, I would go for the 3rd method,
  • otherwise, for example with String or boxed primitives such as Integer, or if you don't acees the objects of the generic type, I would go for method 2.

HTH


Semantically, using <?> means "I don't know the type, and I actually don't care at all. Using anything else sets expectations on the form of the expected content. Practically, <Object> does the same, but states you'll use the properties of your generic that use the parameter type.

So the rule of thumb should be:

  • if you only work on the genetic object but not with it's parametrized content, use <?> so you know at first sight the parameter doesn't matter to the behavior.
  • in any other case, use the most specific parameter that encompasses all types your method is designed to work with. Extreme case is <Object>, other include <? extends SomeTopLevelType>


I haven't used wicket and Vodafone is blocking me from seeing API docs. However, it appears that you are missing many generic arguments and want something like:

List<IColumn<String>> columns = new ArrayList<IColumn<String>>();
columns.add(new PropertyColumn<String>(new Model<String>("Number"), "revisionID"));

If you want to add other IColumns with unrelated generic argument, you will need something like;

List<IColumn<?>> columns = new ArrayList<IColumn<?>>();
columns.add(new PropertyColumn<String>(new Model<String>("Number"), "revisionID"));

Or if you need to get at the column's properties, perhaps something like:

List<IColumn<String>> strColumns = new ArrayList<IColumn<String>>();
List<IColumn<?>> columns = new ArrayList<IColumn<?>>();
PropertyColumn<String> column =
    new PropertyColumn<String>(new Model<String>("Number"), "revisionID");
strColumns.add(column);
columns.add(column);


You want to use the type of the model associated with your component. That is, use the type returned by a call to getModelObject(). So, to use an example from the migration guide:

ListView<Person> peopleListView = new ListView<Person>("people", people) {
    protected void populateItem(ListItem<Person> item) {
        item.add(new Link<Person>("editPerson", item.getModel()){
            public void onClick() {
                Person p = getModelObject();
                setResponsePage(new EditPersonPage(p));
            }
        });
    }
};

With generics it is easy to tell that that is a list of Persons, with a link to an edit page that uses a person as it's model. Unfortunately, very often in wicket your components won't have a model associated with them. In that case getModel() will return null so the proper type to use is <Void>, which is essentially a place holder for null.

DocumentProcessor

public class DocumentProcessor extends Form implements DocumentManagement { ...

if you aren't setting the model for DocumentProcessor it would look like so:

public class DocumentProcessor extends Form<Void> implements DocumentManagement {
    public DocumentProcessor(String id) {
        super(id);
        ....

but with a model DocumentProcessor looks something like this:

public class DocumentProcessor extends Form<Document> implements DocumentManagement {
    public DocumentProcessor(String id, Document doc) {
        super(id, doc);

AjaxFallbackDefaultDataTable

Judging from it's constructors AjaxFallbackDefaultDataTable will likely store either an IColumn[] or List in it's model, but for your implementation you don't know or care so <?> is appropriate, the difference between this and DocumentProcessor is that you're extending Form and therefore do know and do care how it is using it's model.

IColumn

For the IColumn/PropertyColumn example, I'm going to assume that the revisionID field is a Long, then I would write it like so:

List<PropertyColumn> columns = new ArrayList<PropertyColumn>();
columns.add(new PropertyColumn<Long>(new Model<String>("Number"), "revisionID"));

You might look at

More 1.4 Migration info

Void type parameter


The warnings say that IColumn interface and PropertyColumn class are parameterized types, so you just need to define type parameters for them.

Consider the following example:

List<Set> list = new ArrayList<Set>();

List and ArrayList are parameterized types and their type parameters are defined. However, Set is parameterized type as well but it's raw version is used as a type parameter, hence, compiler generates a warning at that case.

We can fix our example by explicitly specifying all type arguments, e.g.

List<Set<Integer>> list1 = new ArrayList<Set<Integer>>();
List<Set<String>> list1 = new ArrayList<Set<String>>();

You need to do the same thing for your generic classes and interfaces.

0

精彩评论

暂无评论...
验证码 换一张
取 消