Home > Mobile >  How to make a method with parameters of several types in Java?
How to make a method with parameters of several types in Java?

Time:11-02

For example, there is a method with overloading:

public void setBorder(Color color) { ... }
public void setBorder(String colorName) { ... }
public void setBorder(Image image) { ... }
public void setBorder(File imageFile) { ... }

But if I want to overload a little different method, there would be too many similar methods:

public void setBorders(Color colorTop, Color colorRight, Color colorBottom, Color colorLeft) { ... }
public void setBorders(String colorTopName, Color colorRight, Color colorBottom, Color colorLeft) { ... }
public void setBorders(Image imageTop, Color colorRight, Color colorBottom, Color colorLeft) { ... }
/* ... */
public void setBorders(File imageTopFile, File imageRightFile, File imageBottomFile, Image imageLeft) { ... }
public void setBorders(File imageTopFile, File imageRightFile, File imageBottomFile, File imageLeftFile) { ... }

On the other hand, I don't want to use Object something with something instanceof here. So how to make for each parameter here several correct data types?

CodePudding user response:

The direct answer to your question is very very simple: Nope. No can do, impossible.

Java simply doesn't have what you want; there is no option to write intersection types and no option to let java provide for you a combinatory explosion of your various desired options here.

So we need to get creative; there are many many options that fulfill the general idea of what you want whilst not actually being what you specifically asked for.

Overloads

You have 4 different types you want to accept (File, Image, String, and Color), for each of 4 parameters. That's 4*4*4*4 = 256 methods required. You can write them all (oooof!), or you can use a code gen tool to generate them for you. Presumably you wouldn't want to regenerate it every time you change something, so code gen integrated into the build process: Annotation Processors.

It's... possible. But complicated, and I really don't think you'd want this. There's a limit to the # of methods in a single class file, and the java community as a whole just doesn't do it this way. As a consequence, the tools provided in the ecosystem don't expect it and therefore won't act 'nice' with this. Trivial case in point: If you really were to make 256 overloads here, any attempt to auto-complete on this one method is going to show all 256 methods in the dropdown and probably have a significant slowdown effect on your IDE, as the authors of the code completion code weren't thinking of this scenario.

Thus, I recommend strongly against this plan.

Sealed types and wrappers

Java16 brings sealed types directly, but even without java16 you can effectively have them by using access control of constructors.

Make a type called, I dunno, Border, perhaps. This type has no public constructor; it only has 4 static methods that create a Border:

public class Border {
  private Border(...) {}

  public static Border ofColor(Color color) { ... }
  public static Border ofImage(Color color) { ... }
  ...
}

You then write 1 setBorders method that takes 4 Border parameters, and that is all. One would call it as follows:

foo.setBorders(Border.ofColor(BLACK), Border.ofImage(leftImage), ...);

Given that there are only 4 ofX methods and those are the only way to create Border objects, this is the only way you can invoke this method.

There is a slight issue with wordiness; Border.ofColor(Color.BLACK) is a little superfluous and doesn't add much more visual info than the much shorter Color.BLACK. That is unfortunate. One could statically import the ofX methods, resulting in just ofColor(Color.BLACK) which is a bit better, perhaps.

Builders

Even with the 'fix' of using sealed types/wrappers, one needs to remember that the ordering of the borders. Was it like CSS and ordered top, right, bot, left? Or was it something else? Yes, IDEs will help, but still. A builder solves both problems. You still need 17 methods, but that's better than 256.

Example:

public SetBordersBuilder setBorders() {
  return new SetBordersBuilder();
}

public static class SetBordersBuilder {
  private SetBordersBuilder() {}

  public left(Image borderImage) { ... }
  public left(String borderColorName) { .... }
  // same for top, right, and bottom.

  public void go() {
    // actually sets the borders.
  }
}

To use this, one would go:

foo.setBorders()
  .left(Color.BLACK)
  .top(someImage)
  .. etcetera
  .go();

runtime check only

Write one method, give them 4 parameters of type Object, and go on an instanceof bonanza. This is cruddy API: It lacks discoverability. Perusing just the signatures (for example, by reading an auto-complete dialog) I have no idea. It's a bit of a mystifying method call: I pass in 4 borders, and a border can be... any object? Weird. It's hard to know without diving into the docs what kinds of objects I can pass and what that might mean (how would I know that passing a String works and means the string is interpreted as the name of a border color?), and if I misunderstand and pass a type that the setBorders method doesn't understand I won't know it until I run that code and I get the exception. Much nicer if I get a red wavy underline as a type, and guidance as I call this thing. Hence, not recommended.

My advice? The builder solution, hands down.

CodePudding user response:

You are talking about quantities that can be specified/implements via different means, so you could create such a class.

This provides:

  • a type/class/category for a too versatile notion
  • avoiding many and maybe incomplete overloads at several use points

So:

public void setBorder(Border border) { ... }
public void setBorders(Border... borders) { ... }

public class Border {
    public Border(Color color) { ... }
    public Border(String colorName) { ... }
    public Border(Image image) { ... }
    public Border(Path imageFile) { ... }
}

public class Border {
    ...
    public static Border fromColor(Color color) { ... }
    public static Border fromName(String colorName) { ... }
    public static Border fromImage(Image image) { ... }
    public static Border fromPath(Path imageFile) { ... }
}

The second version is an extra step with factory methods. This allows to not return a Border, but a specialized child of a Border.

And of course you could create your own implementation of a border:

class CssBorder extends Border { ... }

So you need not specify all variations at once, in one place.

CodePudding user response:

public class Main implements Context {

    // YOU NEED ONLY ONE METHOD (NO NEED TO OVERLOAD MULTIPLE ONES)
    public void setBorders(BorderBuilder borderBuilder) {
        borderBuilder.setBorder(this);
    }

}

interface BorderBuilder {
    void setBorder(Context context);
}

public class FourColorBorderBuilder implements BorderBuilder {
    
    private Color top;
    private Color right;
    private Color bottom;
    private Color left;
    
    @Override
    public void setBorder(Context context) {
        // impl
    }
    
    public FourColorBorderBuilder withTopColor(Color top) {
        this.top = top;
        return this;
    }
    
    public FourColorBorderBuilder withRightColor(Color right) {
        this.right = right;
        return this;
    }

    public FourColorBorderBuilder withBottomColor(Color bottom) {
        this.bottom = bottom;
        return this;
    }

    public FourColorBorderBuilder withLeftColor(Color left) {
        this.left = left;
        return this;
    }      
    
}

public class StringAndThreeColorBorderBuilder implements BorderBuilder {
    
    private String topName;
    private Color right;
    private Color bottom;
    private Color left;
    
    @Override
    public void setBorder(Context context) {
        // impl
    }
    
    public StringAndThreeColorBorderBuilder withTopNameColor(String topName) {
        this.topName = topName;
        return this;
    }
    
    public StringAndThreeColorBorderBuilder withRightColor(Color right) {
        this.right = right;
        return this;
    }

    public StringAndThreeColorBorderBuilder withBottomColor(Color bottom) {
        this.bottom = bottom;
        return this;
    }

    public StringAndThreeColorBorderBuilder withLeftColor(Color left) {
        this.left = left;
        return this;
    }      
    
}
  •  Tags:  
  • java
  • Related