Explain Codes LogoExplain Codes Logo

What is the difference between ? and Object in Java generics?

java
generics
wildcards
type-safety
Nikita BarsukovbyNikita Barsukov·Mar 11, 2025
TLDR

? (wildcard) is used to represent an unknown type in generic code, providing maximum flexibility. It enables methods to operate on sets of varying types.

List<?> wildcardList; // The contents of this list are a mystery, could be anything!

Object, on the other hand, is a type parameter that indicates a list can contain objects of any class, but lacks the type safety and specificity afforded by generics.

List<Object> objectList; // Just a good old-fashioned list of objects.

Wildcards are generally preferred in generic methods and classes where type invariance is necessary, whereas Object though more lenient is less instructive about collection content or operation intention.

Delving into Flexibility and Type Safety

The Flexibility of Wildcards

Using ? in generics permits interaction with a collection without being concerned about the particular type it contains. Such as, a HashMap<String, ?> allows any value type, providing immense flexibility:

Map<String, ?> map = new HashMap<>(); map.put("key1", "value1"); // String here... map.put("key2", 10); // And surprise! Integer there!

This is mainly useful when you only care about keys or if value types are heterogeneous.

Bounded Wildcards for Type Safety

By deploying ? extends T or ? super T, generics enhance type safety through type constraints. For example, List<? extends InputStream> only takes in InputStreams or subtypes:

List<? extends InputStream> streamList = new ArrayList<FileInputStream>();

Here, the added advantage is that operations on the streamList can be conducted knowing everything within is an InputStream.

Adopting the Get and Put Principle

Follow the "Get and Put Principle", use ? extends T when you plan to extract values from a collection and ? super T for adding values to it. This approach maintains strong type safety ensuring your intentions are clear.

Parsing Array Covariance vs. Generic Invariance

Covariance in Arrays

Java arrays are covariant. This means that an array of a subtype like (String[]) can be assigned to an array of its supertype (Object[]), but this could lead to runtime exceptions.

String[] strings = new String[1]; Object[] objects = strings; // Compiles, but it's a trap!

Invariance in Generics

However, generics are invariant, meaning a List<String> is not considered a subtype of List<Object>, and trading them yields compile-time errors, thus providing type safety at compile-time.

When Wildcards Should Take a Vacation

Writing to a Collection

Avoid using wildcards in generics when planning to both read and write a collection, as it jeopardizes type safety. Instead, be explicit about the type:

List<Integer> list = new ArrayList<>(); list.add(10); // Clear, safe, and content with life.

Narrower is Better for Method Parameters

Narrow down your generic type when designing methods to ensure maximum type safety and specificity:

public <T> void processItems(List<T> items) { // Process items of a specific type T. No impostors allowed. }

Storage vs. Holding: Place for Everyone

Any Object's Storage

A Collection<Object> can store any type of object, but can't be assigned from a collection of a specified type like Collection<String>.

Collection<Object> objects; Collection<String> strings = new ArrayList<>(); // objects = strings; // Nope, not happening.

Holding Any Collection

Conversely, a Collection<?> can retain any type of collection irrespective of its type parameters— the very portrait of polymorphism.

Collection<?> holder; Collection<String> strings = new ArrayList<>(); holder = strings; // Comes with open arms for any collection type.

Enhancing Broader Compatibility with ?

A HashMap<?, ?> can host any type for key and values— a useful feature when you need a map but the types are unknown or varied.

Map<?, ?> myMap = ... ; // Can hold anything. A dream come true for hoarders!

Driving Code Design with ? and Object

For Collection APIs

Appreciating the nuances between ? and Object is central for architecting flexible APIs for collections. Sticking with ?, your collections can offer greater adaptability to differing types:

public void printCollection(Collection<?> c) { for (Object item : c) { System.out.println(item); // Just print it, don't question it. } }

In Stream Operations

Working with Java Streams, employing wildcards keeps operations like filtering and mapping general, promoting fluency:

Stream<?> mixedStream = Stream.of(1, "two", 3.0); mixedStream.map(Object::toString).forEach(System.out::println); // Every object has a story to tell.

References