Why would an add() in a TreeSet subclass fail?

R

Rhino

I have a weird problem with regards to an add() method in the constructor of
a TreeSet subclass: the add() fails but I can't figure out why.

I have a class called StringRange that subclasses TreeSet as follows:

public class StringRange extends TreeSet {

public StringRange(String firstString, String secondString) {

super();

boolean result = add(firstString);
if (result != true) {
throw new IllegalArgumentException("The first String was not
added to the Range.");
}
result = add(secondString);
if (result != true) {
throw new IllegalArgumentException("The second String was not
added to the Range.");
}
}

}

I instantiate StringRange like this:

StringRange myRange = new StringRange("cat", "dog");

and the size of the StringRange is correctly reported as two elements.


Now, I'm trying to create a generalized Range class, like this:

public class Range extends TreeSet {

public Range(Object firstObject, Object secondObject) {

super();

boolean result = add(firstObject);
if (result != true) {
throw new IllegalArgumentException("The first Object was not
added to the Range.");
}
result = add(secondObject);
if (result != true) {
throw new IllegalArgumentException("The second Object was not
added to the Range.");
}
}

}

However, when I execute this code:

Range myRange = new Range("cat", "dog");

the reported size of the Range is 0, not 2. I traced through the code with
my debugger and found that the add() method in the Range() constructor
returns false; neither value is added to the Range.

I'm baffled: *WHY* does add() fail? There is no feedback from Java beyond
the 'false' returned by the add() method; the only difference I can see
between StringRange and Range is that Range expects two Objects while
StringRange expects two Strings in its constructor yet add() fails in the
Range class while it works perfectly in StringRange.

What's going on here?

--
Rhino
---
rhino1 AT sympatico DOT ca
"There are two ways of constructing a software design. One way is to make it
so simple that there are obviously no deficiencies. And the other way is to
make it so complicated that there are no obvious deficiencies." - C.A.R.
Hoare
 
E

Eric Sosman

Rhino said:
I have a weird problem with regards to an add() method in the constructor of
a TreeSet subclass: the add() fails but I can't figure out why.
[...]

Neither can I: The sample code you provided works just
fine for me, once I add the missing pieces. Perhaps the
pieces I added aren't the same as those you're using? Try
posting a minimal *complete* program that demonstrates the
problem, and I'll take another whack at it.
 
S

Steve Bosman

Rhino said:
I have a weird problem with regards to an add() method in the constructor of
a TreeSet subclass: the add() fails but I can't figure out why.

I have a class called StringRange that subclasses TreeSet as follows:
[Snip StringRange class]

I instantiate StringRange like this:

StringRange myRange = new StringRange("cat", "dog");

and the size of the StringRange is correctly reported as two elements.


Now, I'm trying to create a generalized Range class, like this:
[Snip Range class]
However, when I execute this code:

Range myRange = new Range("cat", "dog");

the reported size of the Range is 0, not 2. I traced through the code with
my debugger and found that the add() method in the Range() constructor
returns false; neither value is added to the Range.

I'm baffled: *WHY* does add() fail? There is no feedback from Java beyond
the 'false' returned by the add() method; the only difference I can see
between StringRange and Range is that Range expects two Objects while
StringRange expects two Strings in its constructor yet add() fails in the
Range class while it works perfectly in StringRange.

What's going on here?
As far as I can tell nothing, I pasted the two classes into classes in
my IDE, created a main method and both test lines report a size of 2.
Are you positive this is the same code that was failing?
 
J

John C. Bollinger

Rhino,

Do please consider the responses to your other thread about ranges.
Chris Uppal in particular provided a nice, concise explanation for why
it isn't a good idea to base your generic Range class on TreeSet, and he
also raised some relevant issues concerning endpoints that you should
consider. Other responses discuss a simple solution not based on
TreeSet; although it uses generics ala Java 1.5, it can easily be
modified to be compatible with earlier versions of Java.
 
R

Rhino

Eric Sosman said:
I have a weird problem with regards to an add() method in the constructor of
a TreeSet subclass: the add() fails but I can't figure out why.
[...]

Neither can I: The sample code you provided works just
fine for me, once I add the missing pieces. Perhaps the
pieces I added aren't the same as those you're using? Try
posting a minimal *complete* program that demonstrates the
problem, and I'll take another whack at it.
As requested, here's a complete example. It compiles fine in my IDE but the
add() fails in both constructors, throwing the IllegalArgumentException; if
I omit the try blocks, the adds invariably return false and proceed
silently.


import java.util.Collection;
import java.util.TreeSet;

/**
* Class Range describes the behaviour of all ranges, regardless of the
class of
* data stored in it.
*
* <p>
* A Range is a special TreeSet containing exactly two elements, the first
of
* which represents the low end of the range and the second of which
represents
* the high end of the range.
* </p>
*
*/
public class Range extends TreeSet {

public static void main(String[] args) {

Range myRange = new Range("cat", "dog");
System.out.println("Size: " + myRange.size());
}

/**
* Constructor Range(Object, Object) creates an instance of the class.
*
* <p>
* Additional comments about constructor Range.
* </p>
*
* @param Object
* firstObject one of the two Object values that will
encompass
* the range
* @param Object
* secondObject one of the two Object values that will
encompass
* the range
*/
public Range(Object firstObject, Object secondObject) {

super();

System.out.println("firstObject: " + firstObject);
System.out.println("secondObject: " + secondObject);

/* Verify that both objects are the same class. */
String firstObjectClass = firstObject.getClass().getName();
String secondObjectClass = secondObject.getClass().getName();
if (!firstObjectClass.equals(secondObjectClass)) {
throw new IllegalArgumentException("The two Objects passed to
this constructor must be the same class.");
}

/* Add the two objects to the Range. Make sure both adds succeed. */
boolean result = add((String) firstObject);
if (result != true) {
throw new IllegalArgumentException("The first Object was not
added to the Range.");
}

result = add((String) secondObject);
if (result != true) {
throw new IllegalArgumentException("The second Object was not
added to the Range.");
}

System.out.println("Range(Object, Object) size: " + size());
}

/**
* Constructor Range(Object[]) creates an instance of the class.
*
* @param Object[]
* range an array of objects
*/
public Range(Object[] range) {

super();

System.out.println("range.length: " + range.length);
System.out.println("range[0]: " + range[0]);
System.out.println("range[1]: " + range[1]);

if (range.length != 2) {
throw new IllegalArgumentException(
"The range array must contain exactly two elements.");
}

/* Verify that both objects are the same class. */
String firstObjectClass = range[0].getClass().getName();
String secondObjectClass = range[1].getClass().getName();
if (!firstObjectClass.equals(secondObjectClass)) {
throw new IllegalArgumentException("The two Objects passed to
this constructor must be the same class.");
}

/* Add the two objects to the Range. Make sure both adds succeed. */
boolean result = add(range[0]);
if (result != true) {
throw new IllegalArgumentException("The first Object was not
added to the Range.");
}

result = add(range[1]);
if (result != true) {
throw new IllegalArgumentException("The second Object was not
added to the Range.");
}

System.out.println("Range(Object[]) size: " + size());
}

/**
* This overridden version of the add() method ensures that no elements
are ever added to
* the range after it has been instantiated.
*
* @return false
*/
public boolean add(Object object) {

return false;
}

/**
* This overridden version of the addAll() method ensures that no
elements are ever added to
* the range after it has been instantiated.
*
* @return false
*/
public boolean addAll(Collection collection) {

return false;
}

/**
* This overridden version of the remove() method ensures that no
elements are ever
* removed from the range after it has been instantiated.
*
* @return false
*/
public boolean remove(Object object) {

return false;
}

/**
* This method gets the values that comprise the range. There will only
ever be two values.
*
* @return Object array containing the low and high values in the range,
respectively
*/
public Object[] getValues() {

Object[] values = new Object[size()];

values[0] = first();
values[1] = last();

return values;
}

}
 
R

Rhino

John C. Bollinger said:
Rhino,

Do please consider the responses to your other thread about ranges.
Chris Uppal in particular provided a nice, concise explanation for why
it isn't a good idea to base your generic Range class on TreeSet, and he
also raised some relevant issues concerning endpoints that you should
consider. Other responses discuss a simple solution not based on
TreeSet; although it uses generics ala Java 1.5, it can easily be
modified to be compatible with earlier versions of Java.
Thanks, I've just seen his note - which wasn't there yet when I started this
thread - and am strongly considering his remarks. I will almost certainly
redesign my Range class NOT to be a subclass of TreeSet.

However, I'm still stumped by why my constructors aren't adding the values
passed to them and I'd like to figure that out even if I don't subclass
TreeSet in this particular case. Any ideas?

I've just posted a complete version of the class which demonstrates the
problem in reply to Eric Sosman's response to my question. The code is so
SIMPLE that I can't see why it doesn't work; it's either a really basic
coding mistake or something that is going to teach me something important
about Java. In the latter case, I expect to learn something useful about
Java ;-)

Rhino
 
E

Eric Sosman

Rhino said:
I have a weird problem with regards to an add() method in the
constructor of
a TreeSet subclass: the add() fails but I can't figure out why.
[...]

As requested, here's a complete example. It compiles fine in my IDE but the
add() fails in both constructors, [...]

Could it be because of the following? (Just a hunch ;-)
 
E

el_bandido

As requested, here's a complete example. It compiles fine in my IDE but the
add() fails in both constructors, throwing the IllegalArgumentException; if
I omit the try blocks, the adds invariably return false and proceed
silently.

The code does exactly what it should. You return false in your method.
So, congratulation, you haven't gone mad yet.

[...]
public class Range extends TreeSet {

public static void main(String[] args) {

Range myRange = new Range("cat", "dog");
[...]

boolean result = add((String) firstObject);
if (result != true) {
throw new IllegalArgumentException("The first Object was not
added to the Range.");
}
[...]

public boolean add(Object object) {

return false;

Do you want to do this?

Maybe some sleep helps ;). Best regards,
Roberto Nibali, ratz
 
R

Rhino

The code does exactly what it should. You return false in your method.
So, congratulation, you haven't gone mad yet.

Are you saying that the add() SHOULD fail or are you just congratulating me
on detecting the failure cleanly and reporting it to the user?


Do you have any idea why the add() fails or what I need to do to make it
work?

[...]
public class Range extends TreeSet {

public static void main(String[] args) {

Range myRange = new Range("cat", "dog");
[...]

boolean result = add((String) firstObject);
if (result != true) {
throw new IllegalArgumentException("The first Object was not
added to the Range.");
}
[...]

public boolean add(Object object) {

return false;

Do you want to do this?
That's another issue that I'd like to leave aside for now; I'll get back to
that later.
 
E

el_bandido

The code does exactly what it should. You return false in your method.
Are you saying that the add() SHOULD fail

Yes, since that's what you wrote in your code.
or are you just congratulating me
on detecting the failure cleanly and reporting it to the user?

Neither ;).
Do you have any idea why the add() fails or what I need to do to make it
work?

I tend to say yes ... but you're actually starting to confusing me.
Let's see you call path:

Is it ok if I add following line to your code?

public boolean add(Object object) {
----> System.out.println("I will return false here");
return false;
}

Let's run your code with my line in it:

ratz@webphish:~> javac Range.java
ratz@webphish:~> java Range
firstObject: cat
secondObject: dog
I will return false here
Exception in thread "main" java.lang.IllegalArgumentException: The first
Object was not added to the Range.
at Range.<init>(Range.java:20)
at Range.main(Range.java:6)
ratz@webphish:~>

I have taken out all comments and unneccessary empty lines of your code:

import java.util.Collection;
import java.util.TreeSet;

public class Range extends TreeSet {
public static void main(String[] args) {
Range myRange = new Range("cat", "dog");

# here you try to get a new instance of your class Range, let's
# skip to your constructor ...

System.out.println("Size: " + myRange.size());
}
public Range(Object firstObject, Object secondObject) {

# so we're here now during the instantiation of the class ...

super();
System.out.println("firstObject: " + firstObject);
System.out.println("secondObject: " + secondObject);

# this actually also gets printed as you can see in my test run

String firstObjectClass = firstObject.getClass().getName();
String secondObjectClass = secondObject.getClass().getName();
if (!firstObjectClass.equals(secondObjectClass)) {
throw new IllegalArgumentException("The two Objects passed
to this c
onstructor must be the same class.");
}

# nothing to see here, so let's proceed quickly

boolean result = add((String) firstObject);

# so we call our add() method, let's jump to the code location ...

if (result != true) {

# since the result variable will be false this statement is true and
# an Exception is thrown. End of program and of story.

throw new IllegalArgumentException("The first Object was
not added t
o the Range.");
}
result = add((String) secondObject);
if (result != true) {
throw new IllegalArgumentException("The second Object was
not added
to the Range.");
}
System.out.println("Range(Object, Object) size: " + size());
}
public Range(Object[] range) {
super();
System.out.println("range.length: " + range.length);
System.out.println("range[0]: " + range[0]);
System.out.println("range[1]: " + range[1]);
if (range.length != 2) {
throw new IllegalArgumentException( "The range array must
contain ex
actly two elements.");
}
String firstObjectClass = range[0].getClass().getName();
String secondObjectClass = range[1].getClass().getName();
if (!firstObjectClass.equals(secondObjectClass)) {
throw new IllegalArgumentException("The two Objects passed
to this c
onstructor must be the same class.");
}
boolean result = add(range[0]);
if (result != true) {
throw new IllegalArgumentException("The second Object was
not added
to the Range.");
}
System.out.println("Range(Object[]) size: " + size());
}
public boolean add(Object object) {

# this is the infamous add method

System.out.println("I will return false here");

# this will be printed as shown in my test run

return false;

# and the only statement that you have here is to return false!!!
# let's jump back to the caller code fragment ...

}
public boolean addAll(Collection collection) {
return false;
}
public boolean remove(Object object) {
return false;
}
public Object[] getValues() {
Object[] values = new Object[size()];
values[0] = first();
values[1] = last();
return values;
}
}

HTH and best regards,
Roberto Nibali, ratz
 
E

Eric Sosman

Rhino said:
Are you saying that the add() SHOULD fail or are you just congratulating me
on detecting the failure cleanly and reporting it to the user?

Do you have any idea why the add() fails or what I need to do to make it
work?

What you seem to be missing is that it's *your* add()
method that fails. You overrode TreeSet's add() with your
own, whose entire body is `return false;' -- so it returns
`false', just as you told it to. Your code never calls
TreeSet's add() method at all.
 
R

Rhino

Eric Sosman said:
What you seem to be missing is that it's *your* add()
method that fails. You overrode TreeSet's add() with your
own, whose entire body is `return false;' -- so it returns
`false', just as you told it to. Your code never calls
TreeSet's add() method at all.
As Homer Simpson would say if he were a Java coder, D'OH! Wow, I can't
believe I missed that! Talk about shooting yourself in the foot ;-)

Okay, the failure of the add() now makes perfect sense.

It raises another question though. What is the best way to ensure that my
Range class never gets any new members? I want to give it two values in the
constructor - either two discrete values or an array containing two discrete
values - and then I never want anyone to add new values or remove existing
values.

That's why I overrode the add() method to always return false(); I didn't
want to let it add additional values beyond the two supplied via the
constructor.

Rhino
 
E

Eric Sosman

Rhino said:
[...]
It raises another question though. What is the best way to ensure that my
Range class never gets any new members? I want to give it two values in the
constructor - either two discrete values or an array containing two discrete
values - and then I never want anyone to add new values or remove existing
values.

That's why I overrode the add() method to always return false(); I didn't
want to let it add additional values beyond the two supplied via the
constructor.

The bad way would be to override add() and addAll() as
you do now, but to invoke super.add() instead of plain add()
in the constructor.

A better way would be to use composition instead of
inheritance. Don't have Range extend TreeSet; have it
contain a TreeSet instance as a private member variable:

public class Range {
private TreeSet set;

public Range(Object u, Object v) {
set = new TreeSet();
set.add(u);
set.add(v);
}

/* ... other fields and methods ... */
}

Points to ponder:

- If the "other fields and methods" don't absolutely
require a TreeSet, `private Set set;' would be better.

- You could make Range implement Set by adding methods
that forwarded to the contained TreeSet.

- You might learn a good deal from reading "Effective
Java" by Joshua Bloch. In particular, Bloch explains
why the first approach suggested above is "bad" and
why the second is "better."

- Why in the ever-lovin' blue-eyed mornin' do you want
to drag out the entire machinery of a TreeSet just to
keep track of two count them two objects? Canaricide
by cannon, that's what it is.
 
R

Rhino

Eric Sosman said:
[...]
It raises another question though. What is the best way to ensure that my
Range class never gets any new members? I want to give it two values in the
constructor - either two discrete values or an array containing two discrete
values - and then I never want anyone to add new values or remove existing
values.

That's why I overrode the add() method to always return false(); I didn't
want to let it add additional values beyond the two supplied via the
constructor.

The bad way would be to override add() and addAll() as
you do now, but to invoke super.add() instead of plain add()
in the constructor.
This is the current version of my Range.add() method; it works fine, even if
the design is bad:

public boolean add(Object object) {

if (size() >= 2) {
return false;
} else {
return super.add(object);
}
}

I'm not quite clear why this is a bad design. It certainly feels kludgey to
have code like 'if (size() >= 2)' but what are the risks and dangers of
leaving this as it is?
A better way would be to use composition instead of
inheritance. Don't have Range extend TreeSet; have it
contain a TreeSet instance as a private member variable:

public class Range {
private TreeSet set;

public Range(Object u, Object v) {
set = new TreeSet();
set.add(u);
set.add(v);
}

/* ... other fields and methods ... */
}

Points to ponder:

- If the "other fields and methods" don't absolutely
require a TreeSet, `private Set set;' would be better.

- You could make Range implement Set by adding methods
that forwarded to the contained TreeSet.
I'm a little confused: if it is a bad idea to subclass TreeSet for the
limited functionality that I need for reasons of overkill, doesn't the use
of a TreeSet as a private variable raise the same concerns? Either way, I'm
still implementing a TreeSet with all of its overhead when a simpler class
would probably do the job....
- You might learn a good deal from reading "Effective
Java" by Joshua Bloch. In particular, Bloch explains
why the first approach suggested above is "bad" and
why the second is "better."
I'm certainly not opposed to learning how to design better in Java. As I
said at the start of the other thread, my OO design background isn't
particularly strong. First, I haven't had any formal training in OO (for a
variety of reasons). Second, I haven't found a really good explanation of OO
design yet. The attempts I've seen all seem to talk only in Big Concepts
without giving concrete examples that help clarify *exactly* what is meant
by the terms they are using. Without a clear understanding of those first
pages, it feels pointless to go deeper into these explanations so I tend to
move on and do something else.

I'll put Bloch's book on my list of books to investigate and buy a copy if
it communicates better than the other books I've seen on OO design.

If you are saying my current design is bad - and I am perfectly willing to
agree that it probably is! - and Bloch's design is better, does that mean
there is a 'best' out there, i.e. a solution that is even better than
Bloch's? If so, how would I find out what that solution is and HOW IT WORKS?
(I won't learn much if I see a solution that is supposed to be better unless
it comes with an explanation of how it works and why it's better.)
- Why in the ever-lovin' blue-eyed mornin' do you want
to drag out the entire machinery of a TreeSet just to
keep track of two count them two objects? Canaricide
by cannon, that's what it is.
You raise a perfectly valid point and one that I have been mulling over
since someone else mentioned it on the other thread I launched to discuss
the design of my Range class.

I'm not sure this is a _GOOD_ answer but the truth is that I was trying to
imitate the SQL operators IN and BETWEEN. I don't know if you are familiar
with SQL so forgive me if I elaborate on that just a tiny bit.

In SQL, the IN operator gets used like this:

select * from employee
where empno in (1, 2, 5, 6, 8)

The BETWEEN operator gets used like this:

select * from employee
where hiredate between '1990-01-01' and '1999-12-31'

A key feature of both operators is that they are overloaded: IN and BETWEEN
both let you compare a value to either a set or a range of any of the SQL
datatypes as long as the search value and the values in the set or range are
comparable. Therefore, you can do an IN or BETWEEN on Strings, ints, dates,
or whatever.

Since the IN operator can have a (virtually) unlimited number of values in
its argument list, I decided that the Java equivalent of that argument list
would be a Collection, specifically a TreeSet since the algorithm to
determine if a given value was in the Collection seemed likely to be more
efficient if the Collection values were in order. I developed a class
containing static methods that did a variety of INs.

Having decided that the set required by an IN was suited to a TreeSet, it
seemed a logical extension to store the range implied by the BETWEEN
operator in a specialized Collection, specifically a TreeSet containing two
values (where the low value was always first). So, I started developing
specific Range classes, like StringRange and IntRange to handle String and
int ranges respectively. Then I thought I should generalize the solution so
I came up with the concept of the Range class. When I got into trouble
making Range work, I started this thread.

Now that I've fixed the problem with the add() method, this approach seems
to be working and gives me the behaviour I want - at least as far as I've
tested.

On the other hand, this solution is probably massive overkill compared to
what I actually need to properly store ranges so that I can do BETWEENs.

Someone on the other thread suggested doing something a lot simpler so I've
been thinking along the lines of something more like the Point class and
have started work on this simpler Range2 class (I'm calling it Range2 to
distinguish it from Range; if I finally decide to delete Range entirely,
I'll rename Range2 to Range.)

Rhino
 
C

Chris Uppal

Rhino said:
I'm certainly not opposed to learning how to design better in Java. As I
said at the start of the other thread, my OO design background isn't
particularly strong. First, I haven't had any formal training in OO (for a
variety of reasons). Second, I haven't found a really good explanation of
OO design yet. The attempts I've seen all seem to talk only in Big
Concepts without giving concrete examples that help clarify *exactly*
what is meant by the terms they are using.

There are /masses/ of bad books that claim to teach OO. They fall into at
least two camps: one camp confuses "OO" with "the syntax/semantics of
Java/C++/<whatever>" (I assume their authors don't actually understand OO at
all [*]); the other camp goes soaring off into the stratosphere and apparently
forget that OO is just a way to make programming abstractions more amenable to
the intuitions about people (and things, but mostly people) that have been
hard-wired into our brains by millenia of evolution -- i.e. its the very
/opposite/ of abstract!

([*] though they may be good books on the syntax/semantics of
Java/C++/<whatever> in their own right, that doesn't in itself make them good
books to learn OO from.)

FWIW, the best book I know of on this subject is:

Object Design
Roles, Responsibilities, and Collaborations
Rebacca Wirfs-Brock and Alan McKean

That's not a primer -- it assumes that you are interested enough in the subject
to be willing to read a /whole book/ about it (and most programmers would not
be). It's also language neutral (so there's no code in it). OTOH it is
entirely free from UML (I tend to see UML in books as a symptom of falling into
one or other of the camps I mentioned earlier -- though a little bit of it is
OK). It's pretty concrete in that it uses lots of examples and avoids the
theoretical verbiage. But it still is /fairly/ abstract, though, so don't
spend money on it without a chance to browse through it first.

-- chris
 
R

Rhino

Chris Uppal said:
Rhino said:
I'm certainly not opposed to learning how to design better in Java. As I
said at the start of the other thread, my OO design background isn't
particularly strong. First, I haven't had any formal training in OO (for a
variety of reasons). Second, I haven't found a really good explanation of
OO design yet. The attempts I've seen all seem to talk only in Big
Concepts without giving concrete examples that help clarify *exactly*
what is meant by the terms they are using.

There are /masses/ of bad books that claim to teach OO. They fall into at
least two camps: one camp confuses "OO" with "the syntax/semantics of
Java/C++/<whatever>" (I assume their authors don't actually understand OO at
all [*]); the other camp goes soaring off into the stratosphere and apparently
forget that OO is just a way to make programming abstractions more amenable to
the intuitions about people (and things, but mostly people) that have been
hard-wired into our brains by millenia of evolution -- i.e. its the very
/opposite/ of abstract!

([*] though they may be good books on the syntax/semantics of
Java/C++/<whatever> in their own right, that doesn't in itself make them good
books to learn OO from.)
I'm glad to hear it isn't only me that has had these experiences; I was
beginning to think I just wasn't very bright....
FWIW, the best book I know of on this subject is:

Object Design
Roles, Responsibilities, and Collaborations
Rebacca Wirfs-Brock and Alan McKean

That's not a primer -- it assumes that you are interested enough in the subject
to be willing to read a /whole book/ about it (and most programmers would not
be). It's also language neutral (so there's no code in it). OTOH it is
entirely free from UML (I tend to see UML in books as a symptom of falling into
one or other of the camps I mentioned earlier -- though a little bit of it is
OK). It's pretty concrete in that it uses lots of examples and avoids the
theoretical verbiage. But it still is /fairly/ abstract, though, so don't
spend money on it without a chance to browse through it first.
Thanks for the suggestion; I'll have a look for that book and see if it
speaks to me as clearly as it does to you ;-)

Rhino
 
E

Eric Sosman

Rhino said:
[trying to extend TreeSet but ensure that two and only two
objects are ever inserted into it]

This is the current version of my Range.add() method; it works fine, even if
the design is bad:

public boolean add(Object object) {

if (size() >= 2) {
return false;
} else {
return super.add(object);
}
}

I'm not quite clear why this is a bad design. It certainly feels kludgey to
have code like 'if (size() >= 2)' but what are the risks and dangers of
leaving this as it is?

.... and you've overridden addAll() as well, to make sure nobody
can insert an unwanted object into your TreeSet that way, either.
Fine, you've locked all the doors.

.... except that TreeSet offers clear() and remove() methods
that a client could use to delete one or both of the "two and
only two" objects after your Range is constructed. Do you
want to prevent that? Then get busy and write a few more
overrides to lock those two doors. TreeSet also has iterator(),
and a client could use the returned Iterator object to muck
with your Range. Do you want to prevent that? More busy work.
And what about the subSet(), tailSet(), and headSet() methods?
Haul out your editor and write still more overrides ...

Eventually, after many overrides, you'll have locked all
the doors and made your Range unmodifiable. But wait! Acceding
to enormous community pressure, Java 1.7 comes along and defines
a new addFiltered(Collection, Filter) method to all the modifiable
collections. A new door has suddenly opened in your fortress
wall, and the Orcs are charging through it ... Your nightmare
never ends.
I'm a little confused: if it is a bad idea to subclass TreeSet for the
limited functionality that I need for reasons of overkill, doesn't the use
of a TreeSet as a private variable raise the same concerns? Either way, I'm
still implementing a TreeSet with all of its overhead when a simpler class
would probably do the job....

If Range extends TreeSet, it automatically exposes all of
TreeSet's behaviors and characteristics that you don't explicitly
override. If Range instead *contains* a TreeSet instance, you
can choose to expose only those characteristics you want. In
particular, if the TreeSet instance is private within Range and
if Range offers no method that lets a client get hold of the
TreeSet reference, there is no way the client can call any methods
on the TreeSet; only your Range class itself has the means to
modify the TreeSet, and there's no way to do an end-run around
your protective blockade.
- Why in the ever-lovin' blue-eyed mornin' do you want
to drag out the entire machinery of a TreeSet just to
keep track of two count them two objects? Canaricide
by cannon, that's what it is.
[trying to do something analogous to SQL's IN and BETWEEN]
Since the IN operator can have a (virtually) unlimited number of values in
its argument list, I decided that the Java equivalent of that argument list
would be a Collection, specifically a TreeSet since the algorithm to
determine if a given value was in the Collection seemed likely to be more
efficient if the Collection values were in order. I developed a class
containing static methods that did a variety of INs.

Having decided that the set required by an IN was suited to a TreeSet, it
seemed a logical extension to store the range implied by the BETWEEN

Here, I think, is where you go wrong. What's sauce for the
goose is sauce for the gander, but tastes perfectly awful atop
calamari. It is true you could model a range as the set of all
elements within the range (back off, mathematicians: there are
no non-denumerable sets on a finite computer), but that's not a
great model. All you really care about are the endpoints of the
range, and you'll use comparisons to decide whether a test item
does or doesn't lie between them. Note, too, that a range requires
an ordering notion while a set does not: you could speak of a set
of points on the plane, but you cannot speak of a range of points
on the plane (mathematicians: anybody who mentions space-filling
curves is out of, er, line).

Since ranges and sets are different in so many ways, there
seems little reason to base them on the same data structure.
Start by deciding on the behaviors you want; then and only
then start thinking about the data structures to support them.
 
R

Rhino

Eric Sosman said:
[trying to extend TreeSet but ensure that two and only two
objects are ever inserted into it]

This is the current version of my Range.add() method; it works fine, even if
the design is bad:

public boolean add(Object object) {

if (size() >= 2) {
return false;
} else {
return super.add(object);
}
}

I'm not quite clear why this is a bad design. It certainly feels kludgey to
have code like 'if (size() >= 2)' but what are the risks and dangers of
leaving this as it is?

... and you've overridden addAll() as well, to make sure nobody
can insert an unwanted object into your TreeSet that way, either.
Fine, you've locked all the doors.

... except that TreeSet offers clear() and remove() methods
that a client could use to delete one or both of the "two and
only two" objects after your Range is constructed. Do you
want to prevent that? Then get busy and write a few more
overrides to lock those two doors. TreeSet also has iterator(),
and a client could use the returned Iterator object to muck
with your Range. Do you want to prevent that? More busy work.
And what about the subSet(), tailSet(), and headSet() methods?
Haul out your editor and write still more overrides ...

Eventually, after many overrides, you'll have locked all
the doors and made your Range unmodifiable. But wait! Acceding
to enormous community pressure, Java 1.7 comes along and defines
a new addFiltered(Collection, Filter) method to all the modifiable
collections. A new door has suddenly opened in your fortress
wall, and the Orcs are charging through it ... Your nightmare
never ends.
Ok, you've made your point :) I see the dangers now.
If Range extends TreeSet, it automatically exposes all of
TreeSet's behaviors and characteristics that you don't explicitly
override. If Range instead *contains* a TreeSet instance, you
can choose to expose only those characteristics you want. In
particular, if the TreeSet instance is private within Range and
if Range offers no method that lets a client get hold of the
TreeSet reference, there is no way the client can call any methods
on the TreeSet; only your Range class itself has the means to
modify the TreeSet, and there's no way to do an end-run around
your protective blockade.
Excellent! That answers my question very clearly. That makes a great deal of
sense.

For some reason, I got it into my head that using a TreeSet as my foundation
was bad at least partially because it had so much overhead so that using
composition reduced that overhead somehow; I was trying to figure out how
that would work. Now I see that the real benefit is that you get far better
security/stability since the TreeSet is totally internal to the Range.
- Why in the ever-lovin' blue-eyed mornin' do you want
to drag out the entire machinery of a TreeSet just to
keep track of two count them two objects? Canaricide
by cannon, that's what it is.
[trying to do something analogous to SQL's IN and BETWEEN]
Since the IN operator can have a (virtually) unlimited number of values in
its argument list, I decided that the Java equivalent of that argument list
would be a Collection, specifically a TreeSet since the algorithm to
determine if a given value was in the Collection seemed likely to be more
efficient if the Collection values were in order. I developed a class
containing static methods that did a variety of INs.

Having decided that the set required by an IN was suited to a TreeSet, it
seemed a logical extension to store the range implied by the BETWEEN

Here, I think, is where you go wrong. What's sauce for the
goose is sauce for the gander, but tastes perfectly awful atop
calamari. It is true you could model a range as the set of all
elements within the range (back off, mathematicians: there are
no non-denumerable sets on a finite computer), but that's not a
great model. All you really care about are the endpoints of the
range, and you'll use comparisons to decide whether a test item
does or doesn't lie between them. Note, too, that a range requires
an ordering notion while a set does not: you could speak of a set
of points on the plane, but you cannot speak of a range of points
on the plane (mathematicians: anybody who mentions space-filling
curves is out of, er, line).

Since ranges and sets are different in so many ways, there
seems little reason to base them on the same data structure.
Start by deciding on the behaviors you want; then and only
then start thinking about the data structures to support them.
Fair enough; as I said, I didn't really think it through very thoroughly
before going with a TreeSet: I just got the notion that a Range was a very
specialized set and therefore merited being a TreeSet just like the IN sets.
I didn't realize all the consequences that you have pointed out so well.

For what it's worth, I now have a SimpleRange class, which subclasses
Object, that does all of what I need it to do to meet my needs without
carrying around all the baggage of the TreeSet. Just for the heck of it,
here it is. If you can see any problems or omissions in it, I'd be delighted
to hear about it. I'm not sure I've covered everything we've talked about
but I tried. I've also used JUnit to test everything; I probably haven't
thrown as many exotic cases at it as I should but every test I have given it
passed. By the way, I initially assumed that my Range or SimpleRange class
would have to be further subclassed to handle specific datatypes but I'm
starting to think that won't be necessary; thanks to polymorphism, this baby
might just do the whole job ;-)


/**
* Class SimpleRange describes the behaviour of all ranges, regardless of
the class of
* data stored in it.
*
* <p>A SimpleRange is a class containing exactly two elements of the same
type. These
* elements represent the end-points of a range of values, e.g. 18 to 21 or
A to E.</p>
*
* @author Rhino
*
*/
public class SimpleRange {

public static int NUMBER_OF_ELEMENTS = 2;

Comparable lowValue = null;
Comparable highValue = null;

/**
* Constructor SimpleRange(Comparable, Comparable) creates an instance
of the class.
*
* @param Comparable
* firstObject one of the two Object values that will
encompass
* the range
* @param Comparable
* secondObject the other of the two Object values that will
encompass
* the range
*/
public SimpleRange(Comparable firstObject, Comparable secondObject) {

/* Verify that neither object is null. */
if (firstObject == null) {
throw new IllegalArgumentException("The first Object cannot be
null.");
}

if (secondObject == null) {
throw new IllegalArgumentException("The second Object cannot be
null.");
}

/*
* Since only Comparable objects are permitted by this constructor,
compare them
* and store the lower one in the lowValue class variable. Store the
other one in the
* highValue class variable.
*/
try {
if (firstObject.compareTo(secondObject) > 0) {
highValue = firstObject;
lowValue = secondObject;
} else {
lowValue = firstObject;
highValue = secondObject;
}
} catch (ClassCastException cc_excp) {
throw new IllegalArgumentException("The first Object, " +
firstObject.toString() + ", belongs to class " +
firstObject.getClass().getName() + ". The second Object, " +
secondObject.toString() + ", belongs to class " +
secondObject.getClass().getName() + ". Both Objects implement the Comparable
interface but they cannot be compared with each other.");
}

}

/**
* Constructor SimpleRange(Comparable[]) creates an instance of the
class.
*
* @param Object[]
* range an array of Comparables
*/
public SimpleRange(Comparable[] range) {

/* Verify that the array is not null. */
if (range == null) {
throw new IllegalArgumentException("The range array cannot be
null.");
}

/* Verify that the array contains exactly two elements. */
if (range.length != 2) {
throw new IllegalArgumentException(
"The range array must contain exactly two elements.");
}

/* Verify that the array elements aren't null. */
if (range[0] == null) {
throw new IllegalArgumentException("The first array element
cannot be null.");
}

if (range[1] == null) {
throw new IllegalArgumentException("The second array element
cannot be null.");
}

/*
* Since only Comparable objects are permitted by this constructor,
compare them
* and store the lower one in the lowValue class variable. Store the
other one in the
* highValue class variable.
*/
try {
if (range[0].compareTo(range[1]) > 0) {
lowValue = range[1];
highValue = range[0];
} else {
lowValue = range[0];
highValue = range[1];
}
} catch (ClassCastException cc_excp) {
throw new IllegalArgumentException("The first Object, " +
range[0].toString() + ", belongs to class " + range[0].getClass().getName()
+ ". The second Object, " + range[1].toString() + ", belongs to class " +
range[1].getClass().getName() + ". Both Objects implement the Comparable
interface but they cannot be compared with each other.");
}

}

/**
* Method size() returns the size of this SimpleRange; the value will
always be 2.
*
* @return the size of this SimpleRange (always 2)
*/
public int size() {

return NUMBER_OF_ELEMENTS;
}

/**
* Method getValues() returns all of the values in this SimpleRange.
*
* @return all of the values in this SimpleRange
*/
public Comparable[] getValues() {

Comparable[] values = new Comparable[size()];

values[0] = lowValue;
values[1] = highValue;

return values;
}

/**
* Method getLow() returns the lower of the two values in the
SimpleRange.
*
* @return Comparable the lower of the two values in the SimpleRange
*/
public Comparable getLow() {

return lowValue;
}

/**
* Method getHigh() returns the higher of the two values in the
SimpleRange.
*
* @return Comparable the higher of the two values in the SimpleRange
*/
public Comparable getHigh() {

return highValue;
}

/**
* This version of between() is a convenience method that determines if
a given int
* value is within a range of int values.
*
* <p>This version of between() will return true if the input value
matches
* either end-point of the range or lies anywhere between them.</p>
*
* @param Comparable input the value which is being compared to the
range
* @return boolean true if the input value is equal to either endpoint
or lies between them, otherwise false
*/
public boolean between(Comparable input) {

return between(true, input, true);
}


/**
* This version of between() determines if a given value is within a
range of values.
*
* <p>Since different users have different expectations about the
behaviour of a
* between method, this method provides two booleans that control which
behaviour
* will be experienced. For example, if the user desires between to be
true if the input
* value is strictly between the two endpoints but not equal to any of
them, both booleans
* should be set to false.</p>
*
* @param includesLow if true the low end of the range should include
the low value, otherwise the low end of the range starts just above the low
value
* @param input the value that is being compared to the range
* @param includesHigh if true the high end of the range should include
the high value, otherwise the high end of the range ends just below the high
value
* @return true if the input value is in the range, otherwise false
*/
public boolean between(boolean includesLow, Comparable input, boolean
includesHigh) {

/*
* If 'includesLow' and 'includesHigh' are both true, return true if
'input'
* satisfies this condition: lowValue <= input <= highValue.
*/
if (includesLow && includesHigh) {
try {
if (input.compareTo(lowValue) >= 0 &&
input.compareTo(highValue) <= 0) {
return true;
} else {
return false;
}
} catch (ClassCastException cc_excp) {
throw new IllegalArgumentException("The input value, " +
input.toString() + ", belongs to class " + input.getClass().getName() + ".
Although the end-points of the range can be compared with each other, they
cannot be compared with the input value.");
}
}
else
/*
* If 'includesLow' and 'includesHigh' are both false, return true
if 'input'
* satisfies this condition: lowValue < input < highValue.
*/
if (!includesLow && !includesHigh) {
try {
if (input.compareTo(lowValue) > 0 &&
input.compareTo(highValue) < 0) {
return true;
} else {
return false;
}
} catch (ClassCastException cc_excp) {
throw new IllegalArgumentException("The input value, " +
input.toString() + ", belongs to class " + input.getClass().getName() + ".
Although the end-points of the range can be compared with each other, they
cannot be compared with the input value.");
}
}
else
/*
* If 'includesLow' is true and 'includesHigh' is false, return true
if 'input'
* satisfies this condition: lowValue <= input < highValue.
*/
if (includesLow && !includesHigh) {
try {
if (input.compareTo(lowValue) >= 0 &&
input.compareTo(highValue) < 0) {
return true;
} else {
return false;
}
} catch (ClassCastException cc_excp) {
throw new IllegalArgumentException("The input value, " +
input.toString() + ", belongs to class " + input.getClass().getName() + ".
Although the end-points of the range can be compared with each other, they
cannot be compared with the input value.");
}
}
else
/*
* If 'includesLow' is false and 'includesHigh' is true, return true
if 'input'
* satisfies this condition: lowValue < input <= highValue.
*/
if (!includesLow && includesHigh) {
try {
if (input.compareTo(lowValue) > 0 &&
input.compareTo(highValue) <= 0) {
return true;
} else {
return false;
}
} catch (RuntimeException excp) {
throw new IllegalArgumentException("The input value, " +
input.toString() + ", belongs to class " + input.getClass().getName() + ".
Although the end-points of the range can be compared with each other, they
cannot be compared with the input value.");
}
}
else {
return false;
}
}
}
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top