3

I have a class that extends another one. It should have an enum that should have certain values for the extending class, so I have the following:

public class Shepherd extends Dog {

    public enum Color {
        BLACK,
        GREEN,
        INVISIBLE
    }

    // ...

}

This enum should be a part of the extending class, as another class that extends Dog should have its own enum with its own values, that's why I can't make the Color enum a part of the Dog class. For example:

public class Corgi extends Dog {

    public enum Color {
        RED,
        BLACK,
        SABLE
    }

    // ...

}

Also, I have a constructor:

public class Shepherd extends Dog {

    public enum Color {
        // ...
    }

    public Shepherd(Color color) {
        super(color);
    }

    // ...

}

I need the base class (Dog) to have the color field that would be accessible from other methods of the same base class (Dog).

public class Dog {

    public enum Color { } // not sure in that :-(

    private Color color;

    public Dog(Color color) {
        this.color = color;
    }

    public Color getColor() {
        return this.color;
    }

    // ...

}

Obviously, it won't work, as Dog.Color is not the same type as Shepherd.Color. OK, let's do it another way:

public class Shepherd extends Dog<Shepherd> {

    // ...

    public Shepherd(Color color) {
        super(color);
    }

    // ...

}
public class Dog<T extends Dog> {

    public enum Color { } // ???

    private T.Color color;

    public Dog(T.Color color) {
        this.color = color;
    }

    public T.Color getColor() {
        return this.color;
    }

    // ...

}

And I'm still getting incompatible types: Shepherd.Color cannot be converted to Dog.Color. :-( Why doesn't Java accept it? The parameter has type T.Color, which should mean Shepherd.Color or Corgi.Color dependent on the class we use, isn't it?

Would anyone be so kind as to show me the correct way with an example? Thanks in advance!

Volodymyr Melnyk
  • 383
  • 3
  • 13
  • 3
    I fear that what you want is not possible. There is no way to enforce that each subtype of `Dog` has a `Color`-enum defined. – Turing85 Jan 03 '20 at 14:44

3 Answers3

4

Enums in Java cannot be extended with new elements. If you want two distinct classes to use two very similar Enums, the right approach is to make them both use the same single Enum class (Color).

Your best option for restricting the subset of Colors allowed for each type of Dog is runtime validation using an inherited superclass method - named, say, getValidColors(); and marking color as final so the validation cannot be (easily) bypassed.

public enum Color {
    BLACK, GREEN, INVISIBLE, RED, SABLE
}

public abstract class Dog {
    protected final Color color;
    public Dog(final Color color) {
        if (!this.getValidColors().contains(color)) {
            throw new IllegalArgumentException(color);
        }
        this.color = color;
    }
    public abstract List<Color> getValidColors();
}

public class Shepherd extends Dog {
    public Shepherd(final Color color) {
        super(color);
    }
    @Override
    public List<Color> getValidColors() {
        return Arrays.asList(Color.BLACK, Color.GREEN, Color.INVISIBLE);
    }
}

public class Corgi extends Dog {
    public Corgi(final Color color) {
        super(color);
    }
    @Override
    public List<Color> getValidColors() {
        return Arrays.asList(Color.RED, Color.BLACK, Color.SABLE);
    }
}
Alex Walker
  • 2,337
  • 1
  • 17
  • 32
1

It might not be exacly what you want, but this is a way to solve it, but again it might not be what you want. Because I am using the Color as the generic field instead of the Dog Child itself.

Color interface

public interface IColor<C> {
    C getColor();
}

Dog.class

public class Dog<C extends IColor<? extends Enum<?>>> {

  private C color;

  public Dog(C color) {
      this.color = color;
  }

  public C getColor() {
      return color;
  }
}

Dog child color:

public enum ShepherdColor implements IColor<ShepherdColor> {
  BLACK,
  GREEN,
  INVISIBLE;

  @Override
  public ShepherdColor getColor() {
      return this;
  }
}

Dog Child class

public class Shepherd extends Dog<ShepherdColor> {
  public Shepherd(ShepherdColor color) {
      super(color);
  }
}

Main

public static void main(String[] args) {
  Shepherd s = new Shepherd(ShepherdColor.GREEN);
  Dog<?> d = s;
  System.out.println(s.getColor());
  System.out.println(d.getColor());
}
Maxdola
  • 1,562
  • 2
  • 10
  • 29
1

Basing contracts on static members (let alone that those members are nested types) won't work.

What you need is a type for the color instance field in Dog. It's a type, that's it; and after that, it needs to be abstract enough to be meaningfully referenced across your type hierarchy.

Now, talking about your code, I can think of two ways in which you can write a Color type that is used by your classes.

  1. Use an interface as the base Color type. This interface can be implemented by your enums. I don't see how Dog uses color, so I'm assuming it's just a type.

    interface Color {}
    
    public class Dog {
        //...
        private Color color;
        //...
    }
    
    class Corgi extends Dog {
        public enum CorgiColor implements Color {
            RED, BLACK, SABLE
        }
        // ...
    }
    
  2. Make your types generic:

    class Dog<E extends Enum<E>, T extends Dog<E, T>> {
        //...
        private E color;
        //...
    }
    
    class Corgi extends Dog<CorgiColor, Corgi> {
        public enum CorgiColor {
            RED, BLACK, SABLE
        }
        // ...
    }
    

There may be other ways, but each will have its own pros and cons, depending on how you use the color value.

ernest_k
  • 44,416
  • 5
  • 53
  • 99