Frage an overloading, polymorphism, generics, java – Methode, die zwei verschiedene Typen als Parameter akzeptiert

25

Ich schreibe eine Methode, die als Parameter ein Objekt eines von zwei Typen annehmen soll, die keinen anderen übergeordneten Typ als Object gemeinsam haben. Die Typen sind beispielsweise Träume und Knoblauch. Sie können beides tundreams.crush() undgarlic.crush(). Ich möchte eine Methode habenutterlyDestroy(parameter), das würde sowohl Träume als auch Knoblauch als Parameter akzeptieren.

utterlyDestroy(parameter) {
    parameter.crush()
}

Sowohl Knoblauch als auch Träume sind Teil einer Bibliothek, so dass sie eine Schnittstelle ICrushable implementieren (damit ich schreiben kann)utterlyDestroy(ICrushable parameter) ) ist keine Option.

Mein Methodenkörper ist ziemlich lang, so dass eine Überladung das Duplizieren von Code bedeuten würde. Hässlich. Ich bin mir sicher, ich könnte Reflexion gebrauchen und ein bisschen Hacking machen. Hässlich.

Ich habe versucht, Generika zu verwenden, aber anscheinend kann ich so etwas nicht schreiben

utterlyDestroy(<T instanceof Dreams || T instanceof Garlic> parameter)

Ist es möglich, Knoblauch in Träume zu tippen?

utterlyDestroy(Object parameter) {
    ((Dreams)parameter).crush()
}

Das wäre aber immer noch hässlich. Was sind meine anderen Möglichkeiten und wie gehe ich bevorzugt mit der Situation um?

Deine Antwort

8   die antwort
1

sein. Ist die Untertypisierung von Knoblauch oder Träumen eine Option und das Hinzufügen Ihres Interfaces zum Untertyp?

Ansonsten können Sie allgemeinen Code in eine private Methode einfügen und die beiden Versionen von utterlyDestroy die erforderlichen Aktionen für die einzelnen Objekte ausführen lassen, bevor Sie den allgemeinen Code aufrufen. Wenn Ihr Methodenkörper lang ist, müssen Sie ihn wahrscheinlich ohnehin in private Methoden aufteilen. Ich nehme an, Sie haben bereits darüber nachgedacht, da es noch offensichtlicher ist, eine Lösung zu finden, als ein Interface hinzuzufügen.

Sie können den Parameter als Objekt einfügen und dann umwandeln. Ist es das, was du mit Reflektion meinst? d.h.

public void utterlyCrush(Object crushable) {
    if (crushable instanceOf Dream) {
         ...
    }
    if (curshable instanceOf Garlic) {
         ...
    }

Das Casting von Knoblauch zu Traum ist jedoch keine Option, da das eine kein Untertyp des anderen ist.

Nein, nachdenklich wollte ich einige der raffinierten Features verwenden, um eine Methode namens crush () zu finden und auszuführen oder so ähnlich. Ich habe das nur geschrieben, um "die Reflexionstrolle" von der Beantwortung abzuhalten. JohnEye
2

Sie könnten eine Schnittstelle und verwendenanpassen Ihre Typen dazu.

Schnittstelle:

public interface Crushable {
  public void crush();
}

Beispielaufruf:

public class Crusher {
  public static void crush(Crushable crushable) {
    crushable.crush();
  }
}

Beispiel für eine Adapter-Factory-Methode:

public final class Dreams {
  public static Crushable asCrushable(final Dream dream) {
    class DreamCrusher implements Crushable {
      @Override
      public void crush() {
        dream.crush();
      }
    }
    return new DreamCrusher();
  }

  private Dreams() {}
}

Der Verbrauchercode sieht folgendermaßen aus:

  Dream dream = new Dream();
  Crushable crushable = Dreams.asCrushable(dream);
  Crusher.crush(crushable);

Wenn Sie viele Typen anpassen müssen, können Sie überlegen, ob Sie nachdenken möchten. Hier ist eine (nicht optimierte) Adapterfabrik, die das verwendetProxy Art:

public final class Crushables {
  private static final Class<?>[] INTERFACES = { Crushable.class };

  public static Crushable adapt(final Object crushable) {
    class Handler implements InvocationHandler {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
        return crushable.getClass()
            .getMethod(method.getName(), method.getParameterTypes())
            .invoke(crushable, args);
      }
    }

    ClassLoader loader = Thread.currentThread()
        .getContextClassLoader();
    return (Crushable) Proxy.newProxyInstance(loader, INTERFACES, new Handler());
  }

  private Crushables() {}
}

Für den API-Konsumenten ist das nicht so hässlich:

  Dream dream = new Dream();
  Crushable crushable = Crushables.adapt(dream);
  Crusher.crush(crushable);

Wie bei der Reflektion üblich, wird jedoch die Typprüfung während der Kompilierung geopfert.

4

was wie das:

class Either<L,R>
{
    private Object value;

    public static enum Side {LEFT, RIGHT}

    public Either(L left)  {value = left;}
    public Either(R right) {value = right;}

    public Side getSide() {return value instanceof L ? Side.LEFT : Side.RIGHT;}

    // Both return null if the correct side isn't contained.
    public L getLeft() {return value instanceof L ? (L) value : null;}
    public R getRight() {return value instanceof R ? (R) value : null;}
}

Dann lassen Sie diese Methode etwas von Typ annehmenEither<Dreams, Garlic>.

1

Wenn Sie sie an vielen Stellen Ihres Projekts auf die gleiche Weise behandeln, empfehle ich, sie in eine Klasse zu packen, so etwas wie einen Adapter.

Nein, es ist nur an einem Ort. Vielen Dank für die Antwort. JohnEye
2

Verwenden Sie einfach Methodenüberladung.

public void utterlyDestroy(Dreams parameter) {
    parameter.crush();
}

public void utterlyDestroy(Garlic parameter) {
    parameter.crush();
}

Wenn Sie mehr als diese beiden Typen auf dieselbe Weise unterstützen möchten, können Sie eine gemeinsame Schnittstelle für alle definieren und Generika verwenden.

@JohnEye - Rufe dann schon bei nur zwei Klassen ein Interface auf. Siehe auch die Antwort von AadvarkSoup für den Fall, dass Sie Ihre Klassenhierarchie nicht richtig strukturieren können. Jirka Hanika
Wie gesagt, meine Methode ist ziemlich lang und dies würde das Duplizieren von Code bedeuten. JohnEye
6

Wie wäre es mit so etwas Einfachem?

utterlyDestroy(Object parameter) {    
    if(parameter instanceOf Dreams){  
        Dream dream = (Dreams)parameter;  
        dream.crush();
        //Here you can use a Dream 
    }  
    else if(parameter instanceOf Garlic){  
       Garlic garlic = (Garlic)parameter;   
        //Here you can use a Garlic  
       garlic.crush();  
   }
} 

Wenn dasutterlyDestroy ist zu komplex und groß und du willst nur das nennencrush dann macht das was du willst

Das ist mir natürlich aufgefallen, aber das Verwenden von Object als Parameter scheint falsch zu sein. JohnEye
Wiederholen Sie hier nicht denselben Code? Ergibt für mich keinen Sinn. Akshay
0

Wie ich benutze:

void fooFunction(Object o){
Type1 foo=null;
if(o instanceof Type1) foo=(Type1)o;
if(o instanceof Type2) foo=((Type2)o).toType1();
// code
}

Dies funktioniert jedoch nur, wenn Type2 in Type1 konvertiert werden kann

29

Wie wäre es damit:

interface ICrushable {
    void crush();
}

utterlyDestroy(ICrushable parameter) {
    // Very long crushing process goes here
    parameter.crush()
}

utterlyDestroy(Dreams parameter) {
    utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}

utterlyDestroy(Garlic parameter) {
    utterlyDestroy(new ICrushable() { crush() {parameter.crush();});
}

Neuentwicklungen sollten die ICrushable-Schnittstelle implementieren, aber für die vorhandenen Klassen wird der Parameter in ICrushable eingeschlossen und an utterlyDestroy (ICrushable) übergeben, das die gesamte Arbeit erledigt.

Verwandte Fragen