Generics Einheitenumrechnung

Hallo,

Bin mal wieder am rumtesten. Und zwar wollte ich zur Übung einige Einheiten (Länge,Zeit, Größe) erstellen und die passenden umrechner.

So sieht das zur Zeit aus.

Die Frage ist jetzt wie ich die convertUnit() Methode am besten definiere, sodass zwar Umrechnungen innerhalb LenghtUnits erlaubt sind, jedoch die Umrechnung von Lenght zu Binary nicht erlaubt ist?

public class CommonUnits {
    public interface Unit<T> {


        long toBase(T value);

        T fromBase(long base);

        public String getLabel();
    }

    public static class LengthUnits {
        public interface LengthUnit extends Unit<Double> {

        }

        public static enum Metric implements LengthUnit {
            NanoMeter(1, "nm"),
            MicroMeter(1000, "µm"),
            MilliMeter(1000000, "mm"),
            CentiMeter(10000000, "cm"),
            DeciMeter(100000000, "dm"),
            Meter(1000000000, "m");
            long factor;
            String label;

            Metric(long factor, String label) {
                this.factor = factor;
                this.label = label;
            }

            @Override
            public long toBase(Double value) {
                return (long) (value * factor);
            }

            @Override
            public Double fromBase(long base) {
                return Double.valueOf(base) / factor;
            }

            @Override
            public String getLabel() {
                return label;
            }
        }

        public static enum Imperial implements LengthUnit {
            Inch(25400000, "inch"),
            Feet(304800000, "feet"),
            Miles(1609344000000l, "miles");
            long factor;
            String label;

            Imperial(long factor, String label) {
                this.factor = factor;
                this.label = label;
            }

            @Override
            public long toBase(Double value) {
                return (long) (value * factor);
            }

            @Override
            public Double fromBase(long base) {
                return Double.valueOf(base) / factor;
            }

            @Override
            public String getLabel() {
                return label;
            }
        }
    }

    public static class BinaryUnits {
        public interface BinaryUnit extends Unit<Double> {

        }

        public static enum Binary implements BinaryUnit {
            Bit(1, "bit");
            long factor;
            String label;

            BinaryUnits(long factor, String label) {
                this.factor = factor;
                this.label = label;
            }

            @Override
            public long toBase(Double value) {
                return (long) (value * factor);
            }

            @Override
            public Double fromBase(long base) {
                return Double.valueOf(base) / factor;
            }

            @Override
            public String getLabel() {
                return label;
            }
        }
    }


    static {
        convertUnit(1.2, LengthUnits.Metric.Meter, LengthUnits.Imperial.Miles);  //OK
        convertUnit(1.2, LengthUnits.Metric.Meter, BinaryUnits.Binary.Bit);      //Should not be possible
    }


    public static <T> T convertUnit(T value, Unit<T> from, Unit<T> to) {
        return to.fromBase(from.toBase(value));
    }
}

(Zeitbedingt leider kein Vorschlag, nur ein kurzer Pointer of JScience, wo das mit http://jscience.org/api/javax/measure/unit/Unit.html schon ziemlich “sophisticated” umgesetzt ist)

        public interface Unit<T, V> { // V das bisherige T, V = Value, T = Type, ob man das direkt ausschreiben will ist Ansichtssache..
            long toBase(V value);
            V fromBase(long base);
            public String getLabel();
        }

            public interface LengthUnit
                extends Unit<LengthUnits, Double>    { }

            public interface BinaryUnit
                extends Unit<BinaryUnit, Double>   {   }

        public static <T, V, U extends Unit<T,V>>V convertUnit(V value, U from, U to)   {
            return to.fromBase(from.toBase(value));
        }

[QUOTE=SlaterB]```
public interface Unit<T, V> { // V das bisherige T, V = Value, T = Type, ob man das direkt ausschreiben will ist Ansichtssache…
long toBase(V value);
V fromBase(long base);
public String getLabel();
}

        public interface LengthUnit
            extends Unit<LengthUnits, Double>    { }

        public interface BinaryUnit
            extends Unit<BinaryUnit, Double>   {   }

    public static <T, V, U extends Unit<T,V>>V convertUnit(V value, U from, U to)   {
        return to.fromBase(from.toBase(value));
    }

Cool, danke. 
Interessant ist, dass IntelliJ den Code:
```convertUnit(1.2, LengthUnits.Metric.Meter, LengthUnits.Imperial.Miles);```

nicht mag, Compilieren und Ausführen lässt es sich aber wunderbar

*** Edit ***

public static <T, V> V convert(V value, Unit<T, V> from, Unit<T, V> to) {
    return to.fromBase(from.toBase(value));
}

Funktioniert auch, und die IDE mag das auch :)

Ich hätte da auch noch 'ne Idee…
Zunächst kann man bei Units eigentlich auf Enums verzichten und stattdessen das Interface als abstrakte Klasse implementieren. Viel wichtiger aber ist, dass ich zumindest auf die generische Festlegung des Datantypen (im Zweifelsfalle immer double) verzichten würde. Das Ganze sieht etwa so aus:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

public class Engineer {
	public static abstract class Unit<T extends Unit<T>> implements Comparable<T> {
		private static final Map<Class<? extends Unit<?>>, Set<Unit<? extends Unit<?>>>> UNITS
					= new IdentityHashMap<>();

		private final double exponent;
		private final int ordinal;
		private final String unit, toString, name;

		protected Unit(double exponent, String unit, String name) {
			synchronized (UNITS) {
				Class<Unit<T>> clazz = (Class<Engineer.Unit<T>>) getClass();
				Set<Unit<? extends Unit<?>>> set = UNITS.get(clazz);
				if(set == null) {
					set = new TreeSet<>();
					UNITS.put(clazz, set);
				}
				ordinal = set.size();
				set.add(this);
				this.exponent = exponent;
				this.unit = unit;
				this.name = name;
				toString = (ordinal + 1) + ". " + name;
			}
		}

		protected int ordinal() {
			return ordinal;
		}

		@Override
		public final int compareTo(T o) {
			int a = ordinal();
			int b = o.ordinal();
			return (a > b)? 1 : (a < b)? -1 : 0;
		}

		@Override
		public final boolean equals(Object obj) {
			return this == obj;
		}

		@Override
		public final int hashCode() {
			return System.identityHashCode(this);
		}

		@Override
		public String toString() {
			return toString;
		}

		public final double calc(double value, T target) {
			if(target == null) {
				return value;
			}
			value *= Math.pow(Math.PI, target.exponent() - exponent());
			return value;
		}

		public final double exponent() {
			return exponent;
		}

		public final String unit() {
			return unit;
		}

		public final String name() {
			return name;
		}

		public static <T extends Unit<?>> T[] valuesOfClass(Class<T> clazz) {
			synchronized (UNITS) {
				Set<Unit<? extends Unit<?>>> set = UNITS.get(clazz);
				if(set == null) {
					Field[] fields = clazz.getDeclaredFields();
					int mod;
					for(Field f : fields) {
						mod = f.getModifiers();
						if(f.getType() == clazz
							&& (mod & Modifier.STATIC) != 0
								&& (mod & Modifier.PUBLIC) != 0
									&& (mod & Modifier.FINAL) != 0) {
							try {
								f.get(null);
							} catch(Throwable e) {
								// what can i say? ignore...
							}
						}
					}
					set = UNITS.get(clazz);
				}
				T[] rc = (T[]) Array.newInstance(clazz, set.size());
				rc = set.toArray(rc);
				return rc;
			}
		}
	}

	public static class Angles extends Unit<Angles> {
		public static final Angles RAD;
		public static final Angles DEG;
		public static final Angles GRAD;

		static {
			RAD = new Angles(1.0, "", "Radiands");
			DEG = new Angles(Math.log(180) / Math.log(Math.PI), "°", "Degrees");
			GRAD = new Angles(Math.log(200.0) / Math.log(Math.PI), "ng", "Gradiands");
		}

		private Angles(double exponent, String unit, String name) {
			super(exponent, unit, name);
		}
	}

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		Angles[] values = Unit.valuesOfClass(Angles.class);
		for(Angles u : values) {
			System.out.println(u);
		}
		System.out.print("Ausgangseinheit: ");
		int index = -1;
		Angles ae = null;
		while(ae == null) {
			index = sc.nextInt();
			if(index >= 1 && index <= values.length) {
				ae = values[index - 1];
			}
		}
		System.out.print("Zieleinheit: ");
		Angles ze = null;
		while(ze == null) {
			index = sc.nextInt();
			if(index >= 1 && index <= values.length) {
				ze = values[index - 1];
			}
		}
		System.out.print("Wieviel: ");
		double value = sc.nextDouble();
		System.out.println(String.format("Ergebnis: %1.6f", ae.calc(value, ze)) + ze.unit());
		sc.close();
	}
}```
Das ist natürlich nur ein KSKB mit Winkeln als Beispiel. Die Klassen Unit und Angles sollten normalerweise auf jeden Fall als Eigene Klassen in eigenen Dateien definiert werden. Die Verwendung von Reflections wird erforderlich, wenn "getValuesOfClass()" vor der anderweitigen Verwendung einer konkreten Klasse verwendet wird.

[QUOTE=CCMike]Interessant ist, dass IntelliJ den Code:
convertUnit(1.2, LengthUnits.Metric.Meter, LengthUnits.Imperial.Miles);

nicht mag, Compilieren und Ausführen lässt es sich aber wunderbar[/QUOTE]

In letzter Zeit habe ich dieses Pheromon auch öfter, ich habe das Gefühl, dass die JetBrains-Jungs da was verfriemelt haben.