As of Java 17 (with --enable-preview) you can combine two features - pattern matching and sealed types - to represent and deal with cases where two things have different sets of data.
Say you want to represent three different kinds of items you can check out from a library.
A book, which has a title and author
Book(String title, String author) {} record
A CD, which has a runtime and genre
CD(double runtime, String genre) {} record
And a VHS tape, which contains precious memories
VHSTape(List<Memory> preciousMemories) {} record
What you can do to have a method which might return any one of these cases, or to have a list of any item in the library, is make a common interface that all three of them share
interface LibraryItem {}
Book(String title, String author) implements LibraryItem {}
record
CD(double runtime, String genre) implements LibraryItem {}
record
VHSTape(List<Memory> preciousMemories) implements LibraryItem {} record
The problem is that if you have a LibraryItem object, there isn't much you can actually do with it since the three cases of library items have different kinds of data and thus you cant access everything from the interface
= ...;
LibraryItem item .title(); // nope
item.runtime(); // also nope
item.preciousMemories(); // you'll never get them back item
To remedy this, you can make the interface sealed. This guarantees to the language that Book, CD, and VHSTape are the only classes which implement LibraryItem
interface LibraryItem permits Book, CD, VHSTape {} sealed
Now if you have an instance of library item, you can use it with a "pattern switch expression" to safely recover the actual type of the item
= ...;
LibraryItem item switch (item) {
case Book book ->
System.out.println(book.title());
case CD cd ->
System.out.println(cd.genre());
case VHSTape tape ->
System.out.println(tape.preciousMemories());
}