Generics puzzle with implements Comparable


R

Roedy Green

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?
 
Ad

Advertisements

O

Owen Jacobson

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.
 
T

Tom Anderson

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
 
R

Roedy Green

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
}
}
 
R

Roedy Green

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.
 
T

Tom Anderson

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
 
Ad

Advertisements


Top