Enums cannot be extended. What does that mean? If we have an enum, and it had a method, then we cannot create another enum that implements the same method signature while being under the same type. There's a workaround for that.
You can make enum implement an interface. This interface becomes the super type that can be extended by other enums.
GradeCalculable Interface
YearLevel Enum
Advantage 1: flexible implementation for each enum instance
Pay attention to how GradeCalculable is implemented. The abstract method, calculateGrade, could have been simply overriden at the enum class level. However, in this case, we choose to have different implementations for each enum. Remember, each enum is effectively an instance of the type as the enum. The enum instance, NINE, is an object (of type YearLevel) that you can invoke instance method on. If we implemented calculateGrade() method at the enum class level (that is, after enum declaration), then we will only have one override. However, if we choose to override this method at enum instance level, each enum instance gets different implementations. This flexibility is useful for the purpose of this example, because each enum (representing differnet year level) will calculate grade differently, with different grade boundaries.
Advantage 2: emulate extensible enum
Now, imagine you wanted to extend the enum set. You can't really do ExtendedYearLevel extends YearLevel, because enum is by design not extendible. This means you can't create a separate enum set that can extend the original enum set (different client logic need to be implemented to handle different enum types. The solution is to create an enum that implements the same interface. How you can do that is shown above.
Usage / client code
This code will calculate, for each enum of YearLevel, the grade (credit, participation, distinction, high distinction) given for 70%. We can see that each YearLevel enum can now act as an enum (can invoke .name()), and also carry out implementation of GradeCalculable. Here's the output for the above client code:
Of course, there's a more complex usecase, and it's this:
However, if you were to interoperate with both enum sets (YearLevel enum and ExtendedYearLevel enum), then you need to do something more. We need to be able to group two different enum sets under GradeCalculable interface, but how do we do that? As shown above, we simply concatenate them under list of generic `? extends Student.GradeCalculable`. Now (caution), this means each enum loses its property as an enum. You can't invoke .name(). You can't even cast it back to enum. Funnily though, if you do .getClass, it still recognises itself as the enum instance that it was originally. So .toString() returns the original enum implementation.
The output for above code is:
So unless you need the above usecase of combining two enum sets, it's probably more preferrable to just cast the enum to interface type only when we need calculateGrade(). That way, you don't completely lose track of the enum property.
'Book > Effective Java' 카테고리의 다른 글
[Effective Java] Item 45: Use streams judiciously (0) | 2021.11.13 |
---|---|
[Effective Java] Item 43: Prefer method reference to lambda (0) | 2021.11.08 |
[Effective Java] Item 42: Prefer lambda to anonymous classes (0) | 2021.11.07 |
[Effective Java] Item 37: Using EnumMap as a way to group items by enum (0) | 2021.11.05 |