Summary
A number of publicly exported classes in java.base are not subclassable but are not marked as such by lacking the final modifier.
Problem
When a class is designed to be non-subclassable, then it should document this intent using the final modifier. See Effective Java, Third Edition, Joshua Bloch (for example, Item 19 or Item 22).
A number of publicly exported classes in java.base are "effectively non-subclassable", by having only private or package-private constructors with no actual subclasses, yet they are not marked with the final modifier.
One of these classes deserve attention beyond just lacking the final modifier:
- The class description for
java.lang.constant.DynamicCallSiteDesc
includes a note referencing subtypes, yet the class itself is has a private constructor and is non-subclassable. This class was subclassable in an early development version, but was changed to be non-subclassable before the initial released version.
Solution
- Add the
final
modifier to the following classes:- java.lang.invoke.MethodHandleProxies
- java.lang.invoke.MethodHandles
- java.lang.Runtime
- java.nio.charset.CodingErrorAction
- java.nio.charset.CoderResult
- java.lang.constant.DynamicCallSiteDesc
- java.lang.reflect.Modifier
- java.lang.runtime.ObjectMethods
- java.lang.runtime.SwitchBootstraps
- java.net.URLDecoder
- java.net.URLEncoder
- java.security.DrbgParameters
- java.util.Base64
- java.util.Collections
- java.util.FormattableFlags
- java.util.concurrent.Executors
- java.util.concurrent.locks.LockSupport
- java.net.InterfaceAddress
- java.lang.module.ModuleDescriptor
- java.lang.Package
- java.io.OptionalDataException
- Update the class description for
java.lang.constant.DynamicCallSiteDesc
to not reference subclasses in the note about immutability and object identity.
Specification
The specification diff below may easier to review in this draft PR: https://github.com/openjdk/jdk/pull/22389/files
diff --git a/src/java.base/share/classes/java/io/OptionalDataException.java b/src/java.base/share/classes/java/io/OptionalDataException.java
index f98bf079e8e..37736090092 100644
--- a/src/java.base/share/classes/java/io/OptionalDataException.java
+++ b/src/java.base/share/classes/java/io/OptionalDataException.java
@@ -44,7 +44,7 @@
*
* @since 1.1
*/
-public class OptionalDataException extends ObjectStreamException {
+public final class OptionalDataException extends ObjectStreamException {
@java.io.Serial
private static final long serialVersionUID = -8011121865681257820L;
diff --git a/src/java.base/share/classes/java/lang/Package.java b/src/java.base/share/classes/java/lang/Package.java
index 424c390c8ef..e3121cfa9a7 100644
--- a/src/java.base/share/classes/java/lang/Package.java
+++ b/src/java.base/share/classes/java/lang/Package.java
@@ -113,7 +113,7 @@
*
* @since 1.2
*/
-public class Package extends NamedPackage implements java.lang.reflect.AnnotatedElement {
+public final class Package extends NamedPackage implements java.lang.reflect.AnnotatedElement {
/**
* Return the name of this package.
*
diff --git a/src/java.base/share/classes/java/lang/Runtime.java b/src/java.base/share/classes/java/lang/Runtime.java
index 2f8f003af7c..d5cbbbc2423 100644
--- a/src/java.base/share/classes/java/lang/Runtime.java
+++ b/src/java.base/share/classes/java/lang/Runtime.java
@@ -120,7 +120,7 @@
* @since 1.0
*/
-public class Runtime {
+public final class Runtime {
private static final Runtime currentRuntime = new Runtime();
private static Version version;
diff --git a/src/java.base/share/classes/java/lang/constant/DynamicCallSiteDesc.java b/src/java.base/share/classes/java/lang/constant/DynamicCallSiteDesc.java
index 0cfbcd4abf5..76cef1985b3 100644
--- a/src/java.base/share/classes/java/lang/constant/DynamicCallSiteDesc.java
+++ b/src/java.base/share/classes/java/lang/constant/DynamicCallSiteDesc.java
@@ -41,12 +41,12 @@
* A <a href="package-summary.html#nominal">nominal descriptor</a> for an
* {@code invokedynamic} call site.
*
- * <p>Concrete subtypes of {@linkplain DynamicCallSiteDesc} should be immutable
- * and their behavior should not rely on object identity.
+ * <p>A {@code DynamicCallSiteDesc} is immutable and its behavior does not
+ * rely on object identity.
*
* @since 12
*/
-public class DynamicCallSiteDesc {
+public final class DynamicCallSiteDesc {
private final DirectMethodHandleDesc bootstrapMethod;
private final ConstantDesc[] bootstrapArgs;
diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java
index 0ab2dbfdae9..9d93d8e549c 100644
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java
@@ -69,7 +69,7 @@
*
* @since 1.7
*/
-public class MethodHandleProxies {
+public final class MethodHandleProxies {
private MethodHandleProxies() { } // do not instantiate
diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java
index 7542b0e513a..db64c5e81a2 100644
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java
@@ -84,7 +84,7 @@
* @author John Rose, JSR 292 EG
* @since 1.7
*/
-public class MethodHandles {
+public final class MethodHandles {
private MethodHandles() { } // do not instantiate
diff --git a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
index b38d1b0cc87..4b063b3d622 100644
--- a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
+++ b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java
@@ -91,7 +91,7 @@
* @since 9
*/
-public class ModuleDescriptor
+public final class ModuleDescriptor
implements Comparable<ModuleDescriptor>
{
diff --git a/src/java.base/share/classes/java/lang/reflect/Modifier.java b/src/java.base/share/classes/java/lang/reflect/Modifier.java
index 4b13d19f85e..d46aa6a4b11 100644
--- a/src/java.base/share/classes/java/lang/reflect/Modifier.java
+++ b/src/java.base/share/classes/java/lang/reflect/Modifier.java
@@ -52,7 +52,7 @@
* @author Kenneth Russell
* @since 1.1
*/
-public class Modifier {
+public final class Modifier {
/**
* Do not call.
*/
diff --git a/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java
index 51ee21e46b3..277f36dc486 100644
--- a/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java
+++ b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java
@@ -48,7 +48,7 @@
*
* @since 16
*/
-public class ObjectMethods {
+public final class ObjectMethods {
private ObjectMethods() { }
diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
index 53f1572fa74..f826f1e2e72 100644
--- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
+++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
@@ -74,7 +74,7 @@
*
* @since 21
*/
-public class SwitchBootstraps {
+public final class SwitchBootstraps {
private SwitchBootstraps() {}
diff --git a/src/java.base/share/classes/java/net/InterfaceAddress.java b/src/java.base/share/classes/java/net/InterfaceAddress.java
index f5b76ec9f90..9062347d074 100644
--- a/src/java.base/share/classes/java/net/InterfaceAddress.java
+++ b/src/java.base/share/classes/java/net/InterfaceAddress.java
@@ -36,7 +36,7 @@
* @see java.net.NetworkInterface
* @since 1.6
*/
-public class InterfaceAddress {
+public final class InterfaceAddress {
private InetAddress address = null;
private Inet4Address broadcast = null;
private short maskLength = 0;
diff --git a/src/java.base/share/classes/java/net/URLDecoder.java b/src/java.base/share/classes/java/net/URLDecoder.java
index e2070458e2b..aa22e740d67 100644
--- a/src/java.base/share/classes/java/net/URLDecoder.java
+++ b/src/java.base/share/classes/java/net/URLDecoder.java
@@ -81,7 +81,7 @@
* @since 1.2
*/
-public class URLDecoder {
+public final class URLDecoder {
/**
* Do not call.
diff --git a/src/java.base/share/classes/java/net/URLEncoder.java b/src/java.base/share/classes/java/net/URLEncoder.java
index 3c02dd50e6e..88be16862c9 100644
--- a/src/java.base/share/classes/java/net/URLEncoder.java
+++ b/src/java.base/share/classes/java/net/URLEncoder.java
@@ -84,7 +84,7 @@
* @author Herb Jellinek
* @since 1.0
*/
-public class URLEncoder {
+public final class URLEncoder {
private static final IntPredicate DONT_NEED_ENCODING;
static {
diff --git a/src/java.base/share/classes/java/nio/charset/CoderResult.java b/src/java.base/share/classes/java/nio/charset/CoderResult.java
index fcce8774171..d5dd397d465 100644
--- a/src/java.base/share/classes/java/nio/charset/CoderResult.java
+++ b/src/java.base/share/classes/java/nio/charset/CoderResult.java
@@ -81,7 +81,7 @@
* @since 1.4
*/
-public class CoderResult {
+public final class CoderResult {
private static final int CR_UNDERFLOW = 0;
private static final int CR_OVERFLOW = 1;
diff --git a/src/java.base/share/classes/java/nio/charset/CodingErrorAction.java b/src/java.base/share/classes/java/nio/charset/CodingErrorAction.java
index c708e634de1..617f30305dc 100644
--- a/src/java.base/share/classes/java/nio/charset/CodingErrorAction.java
+++ b/src/java.base/share/classes/java/nio/charset/CodingErrorAction.java
@@ -40,7 +40,7 @@
* @since 1.4
*/
-public class CodingErrorAction {
+public final class CodingErrorAction {
private String name;
diff --git a/src/java.base/share/classes/java/security/DrbgParameters.java b/src/java.base/share/classes/java/security/DrbgParameters.java
index d979aba9531..e53413e9d09 100644
--- a/src/java.base/share/classes/java/security/DrbgParameters.java
+++ b/src/java.base/share/classes/java/security/DrbgParameters.java
@@ -232,7 +232,7 @@
*
* @since 9
*/
-public class DrbgParameters {
+public final class DrbgParameters {
private DrbgParameters() {
// This class should not be instantiated
diff --git a/src/java.base/share/classes/java/util/Base64.java b/src/java.base/share/classes/java/util/Base64.java
index 60b6c18c078..f053a94e74c 100644
--- a/src/java.base/share/classes/java/util/Base64.java
+++ b/src/java.base/share/classes/java/util/Base64.java
@@ -81,7 +81,7 @@
* @since 1.8
*/
-public class Base64 {
+public final class Base64 {
private Base64() {}
diff --git a/src/java.base/share/classes/java/util/Collections.java b/src/java.base/share/classes/java/util/Collections.java
index 4ec5756f712..c6503ab2287 100644
--- a/src/java.base/share/classes/java/util/Collections.java
+++ b/src/java.base/share/classes/java/util/Collections.java
@@ -82,7 +82,7 @@
* @since 1.2
*/
-public class Collections {
+public final class Collections {
// Suppresses default constructor, ensuring non-instantiability.
private Collections() {
}
diff --git a/src/java.base/share/classes/java/util/FormattableFlags.java b/src/java.base/share/classes/java/util/FormattableFlags.java
index 92c020f9991..8939a52b397 100644
--- a/src/java.base/share/classes/java/util/FormattableFlags.java
+++ b/src/java.base/share/classes/java/util/FormattableFlags.java
@@ -33,7 +33,7 @@
*
* @since 1.5
*/
-public class FormattableFlags {
+public final class FormattableFlags {
// Explicit instantiation of this class is prohibited.
private FormattableFlags() {}
diff --git a/src/java.base/share/classes/java/util/concurrent/Executors.java b/src/java.base/share/classes/java/util/concurrent/Executors.java
index f804e225790..49cf497c20b 100644
--- a/src/java.base/share/classes/java/util/concurrent/Executors.java
+++ b/src/java.base/share/classes/java/util/concurrent/Executors.java
@@ -68,7 +68,7 @@
* @since 1.5
* @author Doug Lea
*/
-public class Executors {
+public final class Executors {
/**
* Creates a thread pool that reuses a fixed number of threads
diff --git a/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java b/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java
index d6c45c7b04f..917678b5f1e 100644
--- a/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java
+++ b/src/java.base/share/classes/java/util/concurrent/locks/LockSupport.java
@@ -139,7 +139,7 @@
*
* @since 1.5
*/
-public class LockSupport {
+public final class LockSupport {
private LockSupport() {} // Cannot be instantiated.
private static void setBlocker(Thread t, Object arg) {
- csr of
-
JDK-8344943 Mark not subclassable classes final in java.base exported classes
-
- Resolved
-