Why are shell classes in Java final?

Just how it sounds. I need to expand the functionality, and I can’t. Why are they identified as final?

Example:

class MyInteger extends Integer { } 
+5
source share
3 answers

There are good reasons to make primitive wrappers final.

First, note that these classes receive special treatment in the language itself - unlike any normal classes, they are recognized by the compiler to implement automatic (un) boxing. Just allowing subclasses would already create pitfalls (expanding a subclass, doing arithmetic, and wrapping would change the type back).

In addition, wrapper types can share instances (for example, see Integer.valueOf (int)), requiring that the wrapper instance be strictly unchanged. Subclass resolution will open a can of worms where immutability can no longer be guaranteed, forcing reliable code to be written as if the wrappers were mutable, leading to protective copies, wasting memory and processor time, and also creating problems with their usefulness in multi-user mode, multi-threaded scripts .

Essence: wrapper types must be unchanged to ensure uniform capabilities in all cases; immunity is an important part of their known properties. And to guarantee immutability, types must be final.

If you need additional functionality, implement it as a utility class, as for primitives (see, for example, java.lang.Math).


Edit: In order to address a case made for “classes are optional”, a final one is needed to ensure immutability. Strictly speaking, it is true that wrappers can be designed as non-final classes, only by making all methods final. This will alleviate most errors, but it will lead to another problem: compatibility with newer versions of Java. Imagine that you decide to create your own subtype MyInteger, which will use the new method, for example. "Integer decrement ()". Everything works fine until ... in Java 20, language developers will not want to add the decment () method to the API (which will be final, as discussed above). Bam, your class is no longer loading, as it is trying to overwrite the final method.

+14
source

This is a security feature that allows libraries to use strings and wrappers for primitives in collections or otherwise without creating “protective copies”.

If users were allowed to get their own values, say, from Integer , they could make a volatile version. They can then pass instances of this Integer to a library that expects an Integer s collection, for example:

 class ApiClass { private final List<Integer> intList; ApiClass (List<Integer> ints) { // Make a defensive copy intList = new ArrayList<Integer>(ints); // Go through the list, and check the values ... } } 

Currently, the constructor must create a protective copy of the collection, but a small enough copy, because the Integer inside cannot change. If Java had not insisted on Integer final , however, such a guarantee would not exist, so the code would have to make a deep copy. Otherwise, the caller could build ApiClass by passing a valid Integer in the list, and then going through this list of MyInteger s and changing their values ​​to what the constructor considers invalid. This violates the assumptions about other methods inside ApiClass , which can lead to crashes or disclosure that the “plain” Integer will not leak.

+4
source

They must be unchanged. And the key feature of correctly implemented immutability is that it is impossible to extend. If you can expand, you can do this:

 class MyInteger extends Integer { private int myOverrideValue; @Override public int intValue() { return myOverrideValue; } public void setIntValue(int value) { myOverrideValue = value; } } 

Now I can pass objects of the MyInteger mutable type to classes and code that expect intValue not change.

+1
source

Source: https://habr.com/ru/post/1212274/


All Articles