Method accepting two different types as parameter
Asked Answered
D

10

42

I am writing a method that should accept as its parameter an object of one of two types which do not share a parent type other than Object. For example, the types are Dreams and Garlic. You can do both dreams.crush() and garlic.crush(). I want to have a method utterlyDestroy(parameter), that would accept as its parameter both Dreams and Garlic.

utterlyDestroy(parameter) {
    parameter.crush()
}

Both Garlic and dreams are a part of some library, so having them implement an interface ICrushable (so that I could write utterlyDestroy(ICrushable parameter) ) is not an option.

My method body is quite long so overloading it would mean duplicating code. Ugly. I am sure I could use reflection and do some class hacking. Ugly.

I tried using generics but apparently I cannot write something like

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

Is it possible to typecast Garlic to Dreams?

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

This would still be ugly though. What are my other options and what is the preferred method of dealing with the situation?

Digital answered 27/5, 2012 at 21:22 Comment(0)
B
42

How about this:

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();});
}

New development should implement the ICrushable interface, but for the existing Classes, the parameter is wrapped in an ICrushable and passed to the utterlyDestroy(ICrushable) that does all the work.

Boyne answered 27/5, 2012 at 21:44 Comment(2)
Is there a way to make utterlyDestroy garlic or dreams listed as one function so you don't have to copy paste code?Dietz
@Dietz those implementations are expressly to handle legacy objects that do not implement ICrushable. Any classes that implement the interface will run through the first utterlyDestroy method and will not need a custom method.Boyne
S
13

How about something as simple as this?

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

...or if you are on Java 14+:

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

If the utterlyDestroy is too complex and big and you just want to call the crush then this does what you want.

Sunderance answered 27/5, 2012 at 21:48 Comment(2)
Naturally this occurred to me, but using Object as a parameter seems wrong.Digital
Aren't you repeating the same code here? Doesn't make sense to me.Seedman
K
7

You can implement a Haskell-esque Either-class in Java; something like this:

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

Then you let that method take something of type Either<Dreams, Garlic>.

Kammerer answered 27/5, 2012 at 21:39 Comment(1)
This comment seems closest to OPs intent. But assuming they'd [eventually] want more than just two type options, it sounds like they really want a new typeclass (data Target = Dreams | Garlic) so they could write a function (public void crush (Target t)). Unclear if this is possible in Java without carrying around a massive list of type specifications.Townley
G
3

You could use an interface and adapt your types to it.

Interface:

public interface Crushable {
  public void crush();
}

Example invocation:

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

Example adapter factory method:

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() {}
}

The consumer code looks like this:

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

If you have many types to adapt, you could consider reflection. Here is an (unoptimized) adapter factory that uses the Proxy type:

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() {}
}

To the API consumer, this isn't that ugly:

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

However, as is usual with reflection, you sacrifice compile-time type checking.

Guadalquivir answered 28/5, 2012 at 8:46 Comment(0)
Z
1

Creating an Interface Crushable seems like the cleanest way to go. Is subtyping Garlic or Dreams an option, and adding your Interface to the subtype?

Barring that, you can put common code in a private method, and have the two versions of utterlyDestroy do what they have to do to the individual objects before calling the common code. If you method body is long, probably need to break it up into private methods anyway. I'm guessing you already thought of this, though, as it is even more obvious a solution than adding an Interface.

You can bring the parameter in as an Object and then cast it. Is this what you mean by reflection? i.e.,

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

But casting from Garlic to Dream is not an option given that one is not a subtype of the other.

Zoonosis answered 27/5, 2012 at 21:33 Comment(1)
No, by reflection I meant using some of the nifty features to find a method called crush() and executing it or something like that. I only wrote that to discourage 'the reflection trolls' from answering.Digital
P
1

Simply use method overloading.

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

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

If you want to support more than these two types in the same way, you can define a common interface for them all and use generics.

Pillage answered 27/5, 2012 at 21:33 Comment(2)
As I said, my method is quite a long one and this would mean duplicating code.Digital
@Digital - Call for an interface already at only two classes, then. See also AadvarkSoup's answer in case that you cannot properly structure your class hierarchy.Pillage
D
1

If you are going to treat them the same way in many places of your project, I suggest to wrap them in a class, something like an Adapter.

Disembogue answered 27/5, 2012 at 21:40 Comment(1)
No, it is only in one place. Thanks for the answer though.Digital
M
1

As I'm using :

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

But that only works if Type2 can be converted to Type1

Michelemichelina answered 8/4, 2014 at 14:42 Comment(0)
R
0

Updating Devon_C_Miller's answer using lambda and method reference:

    interface ICrushable {
        void crush();
    }

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

    public void utterlyDestroy(Dreams dreams) {
        utterlyDestroy(dreams::crush);
    }

    public void utterlyDestroy(Garlic garlic) {
        utterlyDestroy(garlic::crush);
    }
Rossner answered 8/3, 2023 at 10:29 Comment(0)
E
0

Another method of doing this which avoids inheritance and using instanceOf would be to use generics combined with functional interfaces.

 <T> void utterlyDestroy(T parameter, Consumer<T> crush) {
    crush.accept(parameter);
}

// usage
var dreams = new Dreams();
var garlic = new Garlic();

utterlyDestroy(dreams, (Dreams it) -> it.crush());
utterlyDestroy(garlic, (Garlic it) -> it.crush());
Editor answered 11/9, 2023 at 8:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.