EdgeCase
EdgeCase

Reputation: 4827

Avoiding instanceof with Generics

I need to add to a List of child attributes (ProductOption and ProductAttribute) that are a property of a parent object called Product. All three classes extend an abstract class CMS.

I want to call the method "attachChildToParent" generically, but I am delaying the inevitable by deferring the instanceof and cast to the Product.

Is there a way I can write this generically so I can avoid the cast?

To Test:

package puzzler;

import java.util.ArrayList;
import java.util.List;

public class Tester {
    public static void main(String[] args) {
        Product p = new Product();
        ProductAttribute pa = new ProductAttribute();
        ProductOffering po = new ProductOffering();

        List<ProductAttribute> lpa = new ArrayList<ProductAttribute>();
        List<ProductOffering> lpo = new ArrayList<ProductOffering>();

        attachChildToParent(lpa, p);
    }

    static void attachChildToParent(List<? extends CMS> listChild, Product parent) {
        for (CMS cmsItem : listChild) {
            parent.attach(cmsItem);         
        }
    }
}

The Product class (parent)

package puzzler;

import java.util.List;

abstract class CMS {
    String node;
}
public class Product extends CMS {
    List<ProductAttribute> lpa;
    List<ProductOffering> lpo;

    public List<ProductAttribute> getLpa() {
        return lpa;
    }

    public void setLpa(List<ProductAttribute> lpa) {
        this.lpa = lpa;
    }

    public List<ProductOffering> getLpo() {
        return lpo;
    }

    public void setLpo(List<ProductOffering> lpo) {
        this.lpo = lpo;
    }

    public void attach(ProductAttribute childNode) {
        this.getLpa().add(childNode);
    }

    public void attach(ProductOffering childNode) {
        this.getLpo().add(childNode);
    }

    //  I want to avoid this.  Defeats the purpose of generics.
    public void attach(CMS cms) {
        if (cms instanceof ProductOffering) {
            this.getLpo().add((ProductOffering) cms);
        } else         {
            if (cms instanceof ProductAttribute) {
                this.getLpa().add((ProductAttribute) cms);
            }
        }
    }
}

Child class 1

package puzzler;

import puzzler.CMS;

public class ProductAttribute extends CMS {
    String node;

    public String getNode() {
        return node;
    }

    public void setNode(String node) {
        this.node = node;
    }
}

Child class 2

package puzzler;

import puzzler.CMS;

public class ProductOffering extends CMS {
    String node;

    public String getNode() {
        return node;
    }

    public void setNode(String node) {
        this.node = node;
    }
}

Upvotes: 1

Views: 958

Answers (5)

Amit Deshpande
Amit Deshpande

Reputation: 19185

Generics can not solve your problem. Since you loose instance specific information after <? extends CMS> that all elements in List are children of CMS only.

Why not add simple method which takes both list as parameters which fits in your design and solves your problem also it is efficient

public void attach(List<ProductAttribute> lpa, List<ProductOffering> lpo) {
    this.lpa.addAll(lpa);
    this.lpo.addAll(lpo);
}

Upvotes: 0

Dan
Dan

Reputation: 1018

Override?

public void attach(ProductOffering cms) {
   this.getLpo().add(cms);
}

public void attach(ProductAttribute cms) {
   this.getLpa().add(cms);
}

Upvotes: 0

Aaron Digulla
Aaron Digulla

Reputation: 328536

There are ways to avoid this but not with generics. Generics allow you to write code which allows to avoid casts but it doesn't help when you need to avoid instanceof. The main reason is that all variables with a generic type are treated as Object internally.

And this is why the compiler uses the generic attach(CMS) method when you call it from attachChildToParent(): The type of the children to attach will be CMS - there is no way for the compiler to preserve the type that you use when you call attachChildToParent().

The only solution is to write an attach method in the children:

public class ProductAttribute extends CMS {
    @Override
    public void attachTo( Product p ) {
        p.getLpo().add( this );
    }
}

Upvotes: 0

Mathias Schwarz
Mathias Schwarz

Reputation: 7197

Delegate to the sub classes:

public void attach(CMS cms) {
    cms.callAdd(this);

}

On CMS add:

public abstract void callAdd(Product product);

And on ProductOffering add:

public void callAdd(Product product) {
   getLpo().add(this)       
}

An similarly for ProductAttribute...

Upvotes: 2

Andrew Aylett
Andrew Aylett

Reputation: 40690

You can work around the casting by using the visitor pattern: in your CMS class, add a new (abstract) method attachTo(Product parent). In each of the sub-classes, you can implement this method to call attach on the parent, and the right function will be resolved.

Upvotes: 5

Related Questions