Generics puzzle with implements Comparable

Discussion in 'Java' started by Roedy Green, May 21, 2008.

  1. Roedy Green

    Roedy Green Guest

    Let's say I have a class

    class SalesTaxItem implements Comparable<SalesTaxItem>

    And I want to add a field to the this class, and make it Comparable
    too.

    if I try "class DatedSalesTaxItem extends SalesTaxItem implements
    Comparable<DatedSalesTaxItem>"

    Then I get this error message:

    Comparable cannot be inherited with different arguments.

    Is there a way to get both classes comparable?


    --

    Roedy Green Canadian Mind Products
    The Java Glossary
    http://mindprod.com
     
    Roedy Green, May 21, 2008
    #1
    1. Advertising

  2. On May 20, 10:17 pm, Roedy Green <>
    wrote:
    > Let's say I have a class
    >
    >  class SalesTaxItem implements Comparable<SalesTaxItem>
    >
    > And I want to add a field to the this class, and make it Comparable
    > too.
    >
    > if I try "class DatedSalesTaxItem extends SalesTaxItem implements
    > Comparable<DatedSalesTaxItem>"
    >
    > Then I get this error message:
    >
    > Comparable cannot be inherited with different arguments.
    >
    > Is there a way to get both classes comparable?


    If class Foo is comparable to other instances of class Foo, then
    subclass Bar of Foo must also be comparable to other instances of
    class Foo. Thus, the subclass must also implement Comparable<Foo> --
    which it does anyways, via inheritance. You also can't implement the
    same interface twice, and two generics specializations of an interface
    are actually the same interface under the hood.

    You'll have to either do double-dispatch tricks or check which
    subclass is involved on the right in the derived class' compareTo
    method.

    This isn't entirely a generics-related problem. The following might
    demonstrate the issue:

    class Foo {
    public void frobWith(Foo otherFoo) {...}
    }

    class Bar extends Foo {
    public void frobWith(Bar otherBar) {...}
    }

    The derived class' frobWith method doesn't actually override the
    parent class's and won't be called via dynamic dispatch.
     
    Owen Jacobson, May 21, 2008
    #2
    1. Advertising

  3. Roedy Green

    Tom Anderson Guest

    On Wed, 21 May 2008, Roedy Green wrote:

    > Let's say I have a class
    >
    > class SalesTaxItem implements Comparable<SalesTaxItem>
    >
    > And I want to add a field to the this class, and make it Comparable
    > too.
    >
    > if I try "class DatedSalesTaxItem extends SalesTaxItem implements
    > Comparable<DatedSalesTaxItem>"
    >
    > Then I get this error message:
    >
    > Comparable cannot be inherited with different arguments.


    This is a problem with type erasure, and one of the reasons this is a bad
    way of doing generics.

    > Is there a way to get both classes comparable?


    Could you clarify what you want to do? Comparing two SalesTaxItems, fine.
    Comparing two DatedSalesTaxItems, fine, i guess. What should happen when
    you compare one of one with one of the other? Do you want a SalesTaxItem
    to be comparable with a DatedSalesTaxItem? Do you want that to behave the
    same as doing the same comparison the other way around?

    I was wondering if you could use wildcards for this, but i don't think you
    can. You're going to have to do isinstance/cast in the DatedSalesTaxItem
    method.

    tom

    --
    I need a proper outlet for my tendency towards analytical thought. --
    Geneva Melzack
     
    Tom Anderson, May 21, 2008
    #3
  4. Roedy Green

    Roedy Green Guest

    On Wed, 21 May 2008 12:26:23 +0100, Tom Anderson
    <> wrote, quoted or indirectly quoted someone who
    said :

    >Could you clarify what you want to do? Comparing two SalesTaxItems, fine.
    >Comparing two DatedSalesTaxItems, fine, i guess. What should happen when
    >you compare one of one with one of the other? Do you want a SalesTaxItem
    >to be comparable with a DatedSalesTaxItem? Do you want that to behave the
    >same as doing the same comparison the other way around?


    My Comparator Comparables are for sorting collections of either pure
    SalesTaxItem or pure DatedSalesTaxItem

    I handled it by cloning the code. It is mostly the same except for
    adding one field and changing the Comparable to pay attention to the
    extra field.


    Here is the code cloned.

    Ideally it should be rewritten somehow with DatedSalesTaxItem
    extending SalesTaxItem.

    ----------------------------------------------------------------------------------------

    package com.mindprod.americantax;

    import java.util.Comparator;

    /**
    * triple country, percent, city
    */
    class SalesTaxItem implements Comparable<SalesTaxItem>
    {
    protected String city;

    protected final String county;

    protected final double percent;

    // -------------------------- PUBLIC INSTANCE METHODS
    --------------------------
    /**
    * constructor
    *
    * @param county county name
    * @param percent tax rate percent including state tax
    * @param city city, region of county
    */
    public SalesTaxItem( String county, double percent, String city )
    {
    this.county = county.trim();
    this.city = city.trim();
    this.percent = percent;
    }

    public void clearCity()
    {
    this.city = "";
    }

    /**
    * Compare two Pairs. Callback for natural order sort for
    deduping. effectively returns a-b;
    *
    * @return +1, a>b, 0 a=b, -1 a<b, case insensitive
    */
    public final int compareTo( SalesTaxItem o )
    {
    int diff = this.county.compareTo( o.county );
    if ( diff != 0 )
    {
    return diff;
    }
    if ( this.percent > o.percent )
    {
    return 1;
    }
    if ( this.percent < o.percent )
    {
    return -1;
    }
    return this.city.compareTo( o.city );
    }// end compareTo

    public String getCity()
    {
    return city;
    }

    public String getCounty()
    {
    return county;
    }

    public String getCountyCity()
    {
    if ( city == null || city.length() == 0 )
    {
    return county;
    }
    else
    {
    return county + ", " + city;
    }
    }

    public double getPercent()
    {
    return percent;
    }

    // -------------------------- INNER CLASSES
    --------------------------

    /**
    * sort triples alphabetically
    */
    static final class Alphabetically implements
    Comparator<SalesTaxItem>
    {
    /**
    * Compare two triples. sort alphabetically without tax rate
    considered. effectively returns b-a;
    *
    * @return +1, b>a, 0 a=b, -1 b<a, case insensitive
    */
    public final int compare( SalesTaxItem o1, SalesTaxItem o2 )
    {
    int diff = o1.county.compareTo( o2.county );
    if ( diff != 0 )
    {
    return diff;
    }

    return o1.city.compareTo( o2.city );
    }// end compare
    }
    }

    --------------------------------------------------------------------------------------

    package com.mindprod.americantax;

    import java.util.Comparator;


    /**
    * country, percent, city, date. Can't extend SalesTaxItem because of
    Comparable compatibility.
    */
    class DatedSalesTaxItem implements Comparable<DatedSalesTaxItem>
    {
    private String city;

    private final String county;
    private final String yyyymmdd;// in form yyyymmdd

    private final double percent;

    // -------------------------- PUBLIC INSTANCE METHODS
    --------------------------
    /**
    * constructor
    *
    * @param county county name
    * @param percent tax rate percent including state tax
    * @param city city, region of county
    * @param yyyymmdd date tax came into effect
    */
    public DatedSalesTaxItem( String county, double percent, String
    city, String yyyymmdd )
    {
    this.county = county.trim();
    this.city = city.trim();
    this.percent = percent;
    this.yyyymmdd = yyyymmdd;
    }

    public void clearCity()
    {
    this.city = "";
    }

    /**
    * Compare two Pairs. Callback for natural order sort for
    deduping. effectively returns a-b;
    *
    * @return +1, a>b, 0 a=b, -1 a<b, case insensitive
    */
    public final int compareTo( DatedSalesTaxItem o )
    {
    int diff = this.county.compareTo( o.county );
    if ( diff != 0 )
    {
    return diff;
    }
    diff = this.city.compareTo( o.city );
    if ( diff != 0 )
    {
    return diff;
    }
    diff = this.yyyymmdd.compareTo( o.yyyymmdd );
    if ( diff != 0 )
    {
    return -diff;
    }
    if ( this.percent > o.percent )
    {
    return 1;
    }
    if ( this.percent < o.percent )
    {
    return -1;
    }
    return 0;
    }// end compareTo

    public String getCity()
    {
    return city;
    }

    public String getYYYYMMDD()
    {
    return yyyymmdd;
    }

    public String getCounty()
    {
    return county;
    }

    public String getCountyCity()
    {
    if ( city == null || city.length() == 0 )
    {
    return county;
    }
    else
    {
    return county + ", " + city;
    }
    }

    public double getPercent()
    {
    return percent;
    }

    // -------------------------- INNER CLASSES
    --------------------------

    /**
    * sort triples alphabetically
    */
    static final class Alphabetically implements
    Comparator<DatedSalesTaxItem>
    {
    /**
    * Compare two triples. sort alphabetically without tax rate
    considered. effectively returns b-a;
    *
    * @return +1, b>a, 0 a=b, -1 b<a, case insensitive
    */
    public final int compare( DatedSalesTaxItem o1,
    DatedSalesTaxItem o2 )
    {
    int diff = o1.county.compareTo( o2.county );
    if ( diff != 0 )
    {
    return diff;
    }

    return o1.city.compareTo( o2.city );
    }// end compare
    }
    }


    --

    Roedy Green Canadian Mind Products
    The Java Glossary
    http://mindprod.com
     
    Roedy Green, May 21, 2008
    #4
  5. Roedy Green

    Roedy Green Guest

    On Wed, 21 May 2008 12:26:23 +0100, Tom Anderson
    <> wrote, quoted or indirectly quoted someone who
    said :

    >I was wondering if you could use wildcards for this, but i don't think you
    >can. You're going to have to do isinstance/cast in the DatedSalesTaxItem
    >method.


    Perhaps the best you can do is create a common core without
    Comparable, then create two classes that implement Comparable that
    extend the core.

    --

    Roedy Green Canadian Mind Products
    The Java Glossary
    http://mindprod.com
     
    Roedy Green, May 21, 2008
    #5
  6. Roedy Green

    Tom Anderson Guest

    On Wed, 21 May 2008, Roedy Green wrote:

    > On Wed, 21 May 2008 12:26:23 +0100, Tom Anderson
    > <> wrote, quoted or indirectly quoted someone who
    > said :
    >
    >> Could you clarify what you want to do? Comparing two SalesTaxItems, fine.
    >> Comparing two DatedSalesTaxItems, fine, i guess. What should happen when
    >> you compare one of one with one of the other? Do you want a SalesTaxItem
    >> to be comparable with a DatedSalesTaxItem? Do you want that to behave the
    >> same as doing the same comparison the other way around?

    >
    > My Comparator Comparables are for sorting collections of either pure
    > SalesTaxItem or pure DatedSalesTaxItem
    >
    > I handled it by cloning the code. It is mostly the same except for
    > adding one field and changing the Comparable to pay attention to the
    > extra field.
    >
    > Here is the code cloned.
    >
    > Ideally it should be rewritten somehow with DatedSalesTaxItem extending
    > SalesTaxItem.


    That's the thing. It can't be. A SalesTaxItem is Comparable<SalesTaxItem>.
    If a DatedSalesTaxItem is a SalesTaxItem, then it is also
    Comparable<SalesTaxItem>. Them's the rules.

    My suggestion would be to forget about Comparable, and write two separate
    classes, a Comparator<SalesTaxItem>, and a Comparator<DatedSalesTaxItem>,
    to do the job. You have to use them explicitly, but it's typesafe, with no
    ickiness.

    A type-safe but icky solution would be to equip SalesTaxItem with a
    private method String getDate(), hardcoded to return a string indicating
    the beginning of time ("19700101"), then to write the comparison method
    using the date, as in DatedSalesTaxItem. In DatedSalesTaxItem, you just
    have to override the getDate method to return the actual date, and make it
    public. You can then declare SalesTaxItem Comparable<SalesTaxItem>, and
    all will be well, including in comparisons of mixed kinds of item.

    tom

    > ----------------------------------------------------------------------------------------
    >
    > package com.mindprod.americantax;
    >
    > import java.util.Comparator;
    >
    > /**
    > * triple country, percent, city
    > */
    > class SalesTaxItem implements Comparable<SalesTaxItem>
    > {
    > protected String city;
    >
    > protected final String county;
    >
    > protected final double percent;
    >
    > // -------------------------- PUBLIC INSTANCE METHODS
    > --------------------------
    > /**
    > * constructor
    > *
    > * @param county county name
    > * @param percent tax rate percent including state tax
    > * @param city city, region of county
    > */
    > public SalesTaxItem( String county, double percent, String city )
    > {
    > this.county = county.trim();
    > this.city = city.trim();
    > this.percent = percent;
    > }
    >
    > public void clearCity()
    > {
    > this.city = "";
    > }
    >
    > /**
    > * Compare two Pairs. Callback for natural order sort for
    > deduping. effectively returns a-b;
    > *
    > * @return +1, a>b, 0 a=b, -1 a<b, case insensitive
    > */
    > public final int compareTo( SalesTaxItem o )
    > {
    > int diff = this.county.compareTo( o.county );
    > if ( diff != 0 )
    > {
    > return diff;
    > }
    > if ( this.percent > o.percent )
    > {
    > return 1;
    > }
    > if ( this.percent < o.percent )
    > {
    > return -1;
    > }
    > return this.city.compareTo( o.city );
    > }// end compareTo
    >
    > public String getCity()
    > {
    > return city;
    > }
    >
    > public String getCounty()
    > {
    > return county;
    > }
    >
    > public String getCountyCity()
    > {
    > if ( city == null || city.length() == 0 )
    > {
    > return county;
    > }
    > else
    > {
    > return county + ", " + city;
    > }
    > }
    >
    > public double getPercent()
    > {
    > return percent;
    > }
    >
    > // -------------------------- INNER CLASSES
    > --------------------------
    >
    > /**
    > * sort triples alphabetically
    > */
    > static final class Alphabetically implements
    > Comparator<SalesTaxItem>
    > {
    > /**
    > * Compare two triples. sort alphabetically without tax rate
    > considered. effectively returns b-a;
    > *
    > * @return +1, b>a, 0 a=b, -1 b<a, case insensitive
    > */
    > public final int compare( SalesTaxItem o1, SalesTaxItem o2 )
    > {
    > int diff = o1.county.compareTo( o2.county );
    > if ( diff != 0 )
    > {
    > return diff;
    > }
    >
    > return o1.city.compareTo( o2.city );
    > }// end compare
    > }
    > }
    >
    > --------------------------------------------------------------------------------------
    >
    > package com.mindprod.americantax;
    >
    > import java.util.Comparator;
    >
    >
    > /**
    > * country, percent, city, date. Can't extend SalesTaxItem because of
    > Comparable compatibility.
    > */
    > class DatedSalesTaxItem implements Comparable<DatedSalesTaxItem>
    > {
    > private String city;
    >
    > private final String county;
    > private final String yyyymmdd;// in form yyyymmdd
    >
    > private final double percent;
    >
    > // -------------------------- PUBLIC INSTANCE METHODS
    > --------------------------
    > /**
    > * constructor
    > *
    > * @param county county name
    > * @param percent tax rate percent including state tax
    > * @param city city, region of county
    > * @param yyyymmdd date tax came into effect
    > */
    > public DatedSalesTaxItem( String county, double percent, String
    > city, String yyyymmdd )
    > {
    > this.county = county.trim();
    > this.city = city.trim();
    > this.percent = percent;
    > this.yyyymmdd = yyyymmdd;
    > }
    >
    > public void clearCity()
    > {
    > this.city = "";
    > }
    >
    > /**
    > * Compare two Pairs. Callback for natural order sort for
    > deduping. effectively returns a-b;
    > *
    > * @return +1, a>b, 0 a=b, -1 a<b, case insensitive
    > */
    > public final int compareTo( DatedSalesTaxItem o )
    > {
    > int diff = this.county.compareTo( o.county );
    > if ( diff != 0 )
    > {
    > return diff;
    > }
    > diff = this.city.compareTo( o.city );
    > if ( diff != 0 )
    > {
    > return diff;
    > }
    > diff = this.yyyymmdd.compareTo( o.yyyymmdd );
    > if ( diff != 0 )
    > {
    > return -diff;
    > }
    > if ( this.percent > o.percent )
    > {
    > return 1;
    > }
    > if ( this.percent < o.percent )
    > {
    > return -1;
    > }
    > return 0;
    > }// end compareTo
    >
    > public String getCity()
    > {
    > return city;
    > }
    >
    > public String getYYYYMMDD()
    > {
    > return yyyymmdd;
    > }
    >
    > public String getCounty()
    > {
    > return county;
    > }
    >
    > public String getCountyCity()
    > {
    > if ( city == null || city.length() == 0 )
    > {
    > return county;
    > }
    > else
    > {
    > return county + ", " + city;
    > }
    > }
    >
    > public double getPercent()
    > {
    > return percent;
    > }
    >
    > // -------------------------- INNER CLASSES
    > --------------------------
    >
    > /**
    > * sort triples alphabetically
    > */
    > static final class Alphabetically implements
    > Comparator<DatedSalesTaxItem>
    > {
    > /**
    > * Compare two triples. sort alphabetically without tax rate
    > considered. effectively returns b-a;
    > *
    > * @return +1, b>a, 0 a=b, -1 b<a, case insensitive
    > */
    > public final int compare( DatedSalesTaxItem o1,
    > DatedSalesTaxItem o2 )
    > {
    > int diff = o1.county.compareTo( o2.county );
    > if ( diff != 0 )
    > {
    > return diff;
    > }
    >
    > return o1.city.compareTo( o2.city );
    > }// end compare
    > }
    > }
    >
    >
    >


    --
    They didn't have any answers - they just wanted weed and entitlement.
     
    Tom Anderson, May 22, 2008
    #6
  7. Roedy Green

    Roedy Green Guest

    On Thu, 22 May 2008 19:15:00 +0100, Tom Anderson
    <> wrote, quoted or indirectly quoted someone who
    said :

    >My suggestion would be to forget about Comparable, and write two separate
    >classes, a Comparator<SalesTaxItem>, and a Comparator<DatedSalesTaxItem>,


    That is what I have written up as the way around this. Thanks for your
    solution.

    See http://mindprod.com/jgloss/comparable.html
    --

    Roedy Green Canadian Mind Products
    The Java Glossary
    http://mindprod.com
     
    Roedy Green, May 22, 2008
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Bergholt

    Generics and Comparable

    Bergholt, Nov 27, 2004, in forum: Java
    Replies:
    3
    Views:
    6,119
    Bergholt
    Nov 30, 2004
  2. Roedy Green

    EnumSet Generics puzzle

    Roedy Green, Aug 18, 2005, in forum: Java
    Replies:
    11
    Views:
    1,598
    Thomas Hawtin
    Aug 22, 2005
  3. Ron Albright

    Generics & Comparable

    Ron Albright, Oct 5, 2006, in forum: Java
    Replies:
    3
    Views:
    447
    Hendrik Maryns
    Oct 6, 2006
  4. Replies:
    2
    Views:
    418
  5. Bowbaq
    Replies:
    1
    Views:
    1,030
    Bowbaq
    Mar 25, 2010
Loading...

Share This Page