Question

Say that you have some different classes that does not really have any functionality in common, but somewhere in your program, you need one of them (which one is not necessarily important) because they have a functionality in that context. What is the best way to do that?

I know that you can take an Object and use instanceof to check whether it is one of those types, but I am looking for a way to let the type system ensure that it will just be one of those types. Or is that impractical/impossible?

What I thought about doing was to create an empty interface and implement it in all the classes that needed it. but I have seen that you are supposed to use annotations instead of a marker interface, but I am not sure if that applies in this situation because I do not really care about run-time information. Another thing I am worried about is that implementing that interface in those classes will cause spaghetti code. Those classes should not really have to know anything about how they are used elsewhere.

Was it helpful?

Solution

Here's a barebones generic implementation for two alternatives:

public abstract class Either<A, B> {
    private Either() { /* prevent outside subclassing */ }

    public static Either<A, B> ofA(A a) {
        return new OptionA<A, B>(a);
    }

    public static Either<A, B> ofB(B b) {
        return new OptionB<A, B>(b);
    }

    public abstract <R> R match(Function<? super A, R> ifA, Function<? super B, R> ifB);

    private static final OptionA<A, B> extends Either<A, B> {
        private final A value;

        private OptionA(A value) { this.value = value; }

        @Override
        public <R> R match(
                Function<? super A, R> ifA,
                Function<? super B, R ifB) {
            return ifA.apply(value);
        }
    }

    private static final OptionB<A, B> extends Either<A, B> {
        private final B value;

        private OptionB(B value) { this.value = value; }

        @Override
        public <R> R match(
                Function<? super A, R> ifA,
                Function<? super B, R> ifB) {
            return ifB.apply(value);
        }
    }
}

You box up objects of one of two types into an Either and then use match to call a function based on which type the boxed object has. The combination of a private constructor and final inner subclasses ensures OptionA and OptionB are the only possible subclasses of Either, so unless you start messing around with casting, if you get your hands on an Either<Integer, String> you know it can only contain an Integer or a String.

It should be fairly straightforward to:

  • Extend this to 3+ alternatives.
  • Add convenience methods like boolean hasA() or A getA() throws NoSuchElementException() for times when match would be too verbose.
  • Use a visitor interface with two methods instead of two individual Functions (but the Functions are more concise if you use lambdas)
  • Choose other names for the alternatives (e.g. Left/Right or First/Second instead of A/B).
  • Add equals, hashCode, and all that stuff.
  • Add null-checking.
  • Add caching in the factory methods.

See also How do you encode Algebraic Data Types in a C#- or Java-like language?.

OTHER TIPS

You cannot meld three classes so that an instance of this chimeric class can be used as an instance of either of the three types (as in LSP).

You can make an umbrella class that, unlike an empty interface, gives you real type-checking.

class AppleBookCar() {
   final Apple apple;
   final Book book;
   final Car car;
   private AppleBookCar(Apple a, Book b, Car c) {
     apple = a; book = b; car = c;
   }
   public static AppleBookCar fromApple(Apple apple) { 
     return AppleBookCar(a, null, null);
   }
   public static AppleBookCar fromBook(Book book) { 
     return AppleBookCar(null, book, null);
   }
   public static AppleBookCar fromCar(Car car) { 
     return AppleBookCar(null, null, car);
   }
   public Apple asApple() { return this.apple; }
   public Book asBook() { return this.book; }
   public Car asCar() { return this.car; }
}

This boilerplate is, of course, boring; better languages would write it for you.

Well, all objects extend from the Object class, so maybe you could try to create a generic class and pass in as parameters each of the types. But I am not sure how or why they would all be related because as you said, they have nothing to do with each other. Interesting.

Licensed under: CC-BY-SA with attribution
scroll top