This is what strongly-typed functional programming languages have been teaching and preaching and practising for years.
https://en.wikipedia.org/wiki/Composite_pattern
https://en.wikipedia.org/wiki/Algebraic_data_type
Good type systems are a trade-off between complexity and expressiveness, but it's something that is hard to get right. Obviously Java's type system is simpler thus in theory easier to learn. But in practice F#'s is easier to use if understood and might lead to smaller thus more maintainable code bases.