Solved

Java Generics - No way around all this duplicate code?

Posted on 2014-09-08
22
337 Views
Last Modified: 2014-09-14
I have a program that deals with measurements. The measurement might be Temperature, Speed, Weight, Pressure, etc.

So I have a class which represents a Temperature measurement, another class which represents a Speed measurement, another class for Weight, and another class for Pressure, etc.

Then I have all this nearly identical code, except one version deals with Temperature readings, another version deals with Speed readings, another version deals with Weight readings, another version deals with Pressure readings, etc.

So I think maybe I can eliminate a lot of this duplicate code by using Generics.

But I eventually come across a problem: somewhere along the line I'll need to make an instance of a measurement class reading, an instance of type T:
T newReading = new T(value);

Open in new window

and that can't be done with Generics.

(Actually it can be done if I didn't need to pass in a parameter, such as the value of the reading, but creating an immutable measurement reading only works if I can set the value in the constructor (as far as I know).

So I'm wondering if there's any way around this problem. (Turn the problem upside down on it's head? View it from a different angle?) Or, can it be proved there's no workaround?

Currently I went the route of a code generator. I create a single version of all the code using the word "measurement", then the code generator replaces all instances of "measurement" with "temperature", and repeats replacing "measurement" with "speed", "weight", "pressure", etc. until I have all the needed nearly identical code versions. With more effort I might be able to expand this to be a Domain Specific Language (DSL), or perhaps it already qualifies as one.

I still wonder if there's an easier way.
0
Comment
Question by:deleyd
  • 7
  • 6
  • 4
  • +4
22 Comments
 
LVL 32

Expert Comment

by:Stefan Hoffmann
Comment Utility
Use normal OOP, thus inheritance. Create an abstract Measurement class with all its default methods and derive Temperature, Speed and Weight from it.
0
 
LVL 86

Expert Comment

by:CEHJ
Comment Utility
Sounds like you might be using the wrong design pattern. Perhaps what you currently have as classes should be attributes. Difficult to say without knowing more.
0
 
LVL 14

Expert Comment

by:CPColin
Comment Utility
Hi deleyd,

Please attach one or two of your classes. We'll be able to see what's going on much more easily.
0
 
LVL 26

Assisted Solution

by:dpearson
dpearson earned 100 total points
Comment Utility
Also if you wish to keep your existing code structure, you can produce this behavior:

T newReading = new T(value);

By modifying the generic classes/methods to take 2 parameters - one the type T and a second parameter which is the Class of T.

So if today you have:

public class MyStuff<T>
{
    public void makeAT() {
        // This won't work
       T newReading = new T(value);
    }
}

it becomes:

public class MyStuff<T, C extends Class>
{
    public void makeAT() {
       // Pseudo code  - create a new instance using reflection
       // I may be a bit off with the precise generic types to use (it's always rather picky) but you hopefully get the idea
       T newReading = (T)C.newInstance() ;
    }
}

You'd call this with
MyStuff<Temperature, Temperature.class>
instead of
MyStuff<Temperature>

You can read more about the specifics of creating an instance through reflection here:
http://docs.oracle.com/javase/tutorial/reflect/member/ctorInstance.html

Hope that helps,

Doug
0
 

Author Comment

by:deleyd
Comment Utility
Here's a sample of my code and one of the binds I get in when I try to convert to Generics.

Here's the current code:
public interface IUnits {}
public interface ITemperatureUnits extends IUnits {}
public interface IFlowUnits extends IUnits {}


public enum FlowUnits implements IFlowUnits {
  GALLONS_PER_MINUTE,
  LITERS_PER_MINUTE,
}


public enum TemperatureUnits implements ITemperatureUnits {
    FAHRENHEIT,
    CELSIUS,
    KELVIN,
}


public interface ITemperatureSws extends ISws {

  public ITemperatureUnits getSelectedTemperatureUnits();
  public void setUnits(ITemperatureUnits temperatureUnits);
  public void setNextUnits(ITemperatureUnits temperatureUnits, TemperatureUnits.values());
}


private void doStuff() {
    ...
    setNextUnits(TemperatureUnits.FAHRENHEIT, TemperatureUnits.values());
    ...
}

private void setNextUnits(ITemperatureUnits currentTemperatureUnits, TemperatureUnits[] units ) {

  for(int i=0; i < units.length; ++i) {
    if (units[i] == currentTemperatureUnits) {
      ++i;
      if (i < units.length) { this.sws.setUnits(units[i]); }
      else { this.sws.setUnits(units[0]); }
    }
  }
}

Open in new window

And here's my attempt at converting to Generics:
public interface IUnits<T> {}
public interface IFlowUnits extends IUnits<FlowUnits> {}
public interface ITemperatureUnits extends IUnits<TemperatureUnits> {}


public enum FlowUnits implements IUnits<FLowUnits> {
  GALLONS_PER_MINUTE,
  LITERS_PER_MINUTE,
}

public enum TemperatureUnits implements IUnits<TemperatureUnits> {
    FAHRENHEIT,
    CELSIUS,
    KELVIN,
}

public interface IMeasurementSws<T> extends ISws {

  public IUnits<T> getSelectedUnits();
  public void setUnits(IUnits<T> measurementUnits);
  public void setNextUnits(IMeasurementUnits measurementUnits, MeasurementUnits<T>.values());
}


private void doStuff() {
    ...
    setNextUnits(TemperatureUnits.FAHRENHEIT, TemperatureUnits.values());
    ...
}

private void setNextUnits(IMeasurementUnits currentMeasurementUnits, MeasurementUnits<T>[] units ) {

  for(int i=0; i < units.length; ++i) {
    if (units[i] == currentMeasurementUnits) {
      ++i;
      if (i < units.length) { this.sws.setUnits(units[i]); }
      else { this.sws.setUnits(units[0]); }
    }
  }
}

Open in new window

Specifically:
MeasurementUnits<T>.values()
and
MeasurementUnits<T>[] units
I'm having a hard time getting that off the ground.

I've looked at a package called JScience, but haven't found documentation for it. The scant sample code I've found for it doesn't compile, so I suspect it's changed a bit since those samples were written. Looks promising though, if I can figure out how to get it to work.
0
 
LVL 86

Expert Comment

by:CEHJ
Comment Utility
You're overthinking things. I don't see a need for an interface in conjunction with the enums (as in the enums implementing an interface) especially since enums don't have behaviours as such, other than the vestigial 'behaviours' they can have to make them a little richer. The principle feature of enums in Java is to enforce type-safety.

I've looked at a package called JScience, but haven't found documentation for it.
http://jscience.org/api/index.html

The source code builds fine for me
0
 
LVL 37

Expert Comment

by:zzynx
Comment Utility
Rethink ste5an's comment.

Can't you bundle all your units together?

public enum Unit {
  // Flow units:
  GALLONS_PER_MINUTE,
  LITERS_PER_MINUTE,
  // Temperature units:
  FAHRENHEIT,
  CELSIUS,
  KELVIN
  // Other units you might have:
  ...
}

Then have:

public void setUnit(Unit unit);
public void setNextUnit(Unit currentUnit, List<Unit> applicableUnits);
0
 
LVL 14

Accepted Solution

by:
CPColin earned 200 total points
Comment Utility
This compiles for me and might get you closer to what you're trying to do:

public class UnitEnumTest
{
   public interface Unit
   {
   }
   
   public enum TemperatureUnit implements Unit
   {
      FAHRENHEIT,
      CELSIUS,
      KELVIN;
   }
   
   private Unit unit;
   
   public <T extends Enum<T> & Unit> void setNextUnits(Enum<T> currentUnit)
   {
      T[] units = currentUnit.getDeclaringClass().getEnumConstants();
      int newOrdinal = currentUnit.ordinal() + 1;
      
      if (newOrdinal == units.length)
      {
         newOrdinal = 0;
      }
      
      unit = units[newOrdinal];
   }
   
   public static void main(String[] args)
   {
      UnitEnumTest enumTest = new UnitEnumTest();
      
      enumTest.setNextUnits(TemperatureUnit.CELSIUS);
   }
}

Open in new window


But yeah, if an existing library serves your needs, it's usually best to go with that.
0
 
LVL 86

Assisted Solution

by:CEHJ
CEHJ earned 200 total points
Comment Utility
Yes, i can see that making an enum implement an interface is a tempting 'solution' of a sort, but i don't like it. Some might say that one moral of this story is that enum has not been well carried out
0
 
LVL 14

Assisted Solution

by:CPColin
CPColin earned 200 total points
Comment Utility
Some might. I wouldn't.
0
 
LVL 86

Assisted Solution

by:CEHJ
CEHJ earned 200 total points
Comment Utility
Well they certainly won't do what's required of them here, which essentially is to nest. Also they were meant to be a solution to the kludgy business of using static classes and interfaces to enforce type safety but the latter are being used here to do just that (for grouping purposes), even though enum is present at the same time.
0
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 
LVL 14

Expert Comment

by:CPColin
Comment Utility
Debating the merits of enums and Java's implementation of them is not going to help the asker get a solution. If you want to discuss this topic more, please send me a private message.
0
 
LVL 16

Expert Comment

by:krakatoa
Comment Utility
Interesting question. You illicit "standing on head . ." suggestions, so -:

There are any number of things that you can do with the API, that don't necessarily fit right together, but still work. Seems this could be such a case at first reading - overcomplicated concepts trying to handle what are simply sets of numbers. Reflection is slow, and enums are designed to hold discrete references, intransitively, not masses of data points. Shouldn't you be focussing on determining the route to most quickly creating, accessing, and evaluating your readings? Regular class inheritance models, (as has already been mentioned), would need to be productively surpassed - by miles - before other suggestions can be taken seriously.
0
 

Author Comment

by:deleyd
Comment Utility
Thank you everyone for the excellent help!

I've been studying the JScience code for the past few days and I've learned a lot.

First thing is as usual if the code were a tree all the leaves are well documented but there's scarce documentation on the tree itself.
Second the code is very complicated, difficult to reverse engineer (necessitated by the First problem).
But it did help me understand my own errors of design, which I document here partly for my own benefit.

(I didn't publish all of my source code here so these problems weren't detectable to those kind people who helped me here.)

My First Mistake:
I have:
public enum TemperatureUnit implements Unit
   {
      FAHRENHEIT,
      CELSIUS,
      KELVIN;
   }

Open in new window

and I also have:
public class Fahrenheit extends Abstract Temperature...
public class Celsius extends Abstract Temperature...
public class Kelvin extends Abstract Temperature...

Open in new window

Thus I have two representations for the same thing.

This mistake lead to the necessity of code that tied the two together:
if (units == TemperatureUnits.FAHRENHEIT) {
    return asFahrenheit().doubleValue();
}
else if (units == TemperatureUnits.CELSIUS) {
    return asCelsius().doubleValue();
}
else if (units == TemperatureUnits.KELVIN) {
    return asKelvin().doubleValue();
}

Open in new window

(At first I thought this looked like loose coupling, a good thing: "I've got a thing over here, and a thing over there, and I have code that connects the two together." However, there's only one proper way to connect these two sides together! FAHRENHEIT goes with Fahrenheit, CELSIUS goes with Celsius, KELVIN goes with Kelvin. Any other mapping is chaos! Hence it's duplicative.)

My Second Mistake:
I added a value property to each temperature unit class so:
an instance of the Fahrenheit class would represent a measurement in Fahrenheit
an instance of the Celsius class would represent a measurement in Celsius
an instance of the Kelvin class would represent a measurement in Kelvin

A Fahrenheit is not a measurement. A Fahrenheit is a Temperature Scale. Every instance of Fahrenheit should represent the Fahrenheit temperature scale, and nothing more.

Third:
I actually got this one right: The code that converts from one Unit to another belongs in the class for each Unit. (e.g. Fahrenheit knows how to convert to Celsius. Thus Fahrenheit knows a little something about Celsius.)

Fourth:
I originally introduced enums for Units because I didn't want to create a new instance of Fahrenheit every time I just wanted to specify the Fahrenheit scale:
if (units == new Fahrenheit()) {
    return asFahrenheit().doubleValue();
}
else if (units == new Celsius()) {
    return asCelsius().doubleValue();
}
else if (units == new Kelvin()) {
    return asKelvin().doubleValue();
}

Open in new window

I then encountered this code in JScience which initially had me going in circles:
public final class Dimension {

    public static final Dimension LENGTH = new Dimension('L');
    public static final Dimension MASS = new Dimension('M');
    public static final Dimension TIME = new Dimension('T');
    public static final Dimension ELECTRIC_CURRENT = new Dimension('I');
    public static final Dimension TEMPERATURE = new Dimension('θ');
    public static final Dimension AMOUNT_OF_SUBSTANCE = new Dimension('N');
...

Open in new window

Looks like a class containing instances of itself, leading to an infinite regression. Took me awhile to understand how this was legitimate.  It's a class defining multiple different versions of itself.
0
 

Author Closing Comment

by:deleyd
Comment Utility
Thank you everyone for commenting. Little bits and pieces of ideas from everyone helped guide me along towards new knowledge.
0
 
LVL 86

Expert Comment

by:CEHJ
Comment Utility
Took me awhile to understand how this was legitimate.  It's a class defining multiple different versions of itself.
It's actually a typical example of what i was referring to above: a pre-enum kludge to produce type-safe (as opposed to the sidestepped String) constants
0
 

Author Comment

by:deleyd
Comment Utility
Is there a better way?
0
 
LVL 86

Expert Comment

by:CEHJ
Comment Utility
Well you'd normally use an enum instead nowadays
0
 

Author Comment

by:deleyd
Comment Utility
I thought you were against using enums?
0
 
LVL 32

Expert Comment

by:Stefan Hoffmann
Comment Utility
It depends on the kind of enum usage. When I consider the entire range of possibilities you have in Java, then imho they are often misused as a kind of light-weight classes.

Just my 2¢.
0
 
LVL 86

Expert Comment

by:CEHJ
Comment Utility
I thought you were against using enums?
No, not at all. I'm just saying if they're needed and can't really do the job then that's a pity. In the relatively simple example you just gave, an enum could be used, but not if it doesn't fit with the rest of the design
0
 

Author Comment

by:deleyd
Comment Utility
I just learned I can add code to an enum class.

I'm also trying to wrap my head around class emun<E extends enum<E>>

I thought I had this units thing all figured out, but I still keep running into snags when I try to code it. I'll probably post another question when I figure out what the underlying cause of my snag is.
0

Featured Post

Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

Join & Write a Comment

Entering a date in Microsoft Access can be tricky. A typo can cause month and day to be shuffled, entering the day only causes an error, as does entering, say, day 31 in June. This article shows how an inputmask supported by code can help the user a…
In this post we will learn how to connect and configure Android Device (Smartphone etc.) with Android Studio. After that we will run a simple Hello World Program.
Viewers learn about the “for” loop and how it works in Java. By comparing it to the while loop learned before, viewers can make the transition easily. You will learn about the formatting of the for loop as we write a program that prints even numbers…
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now