JDK-8061411 : Add Immutable types to Java
  • Type: Enhancement
  • Component: specification
  • Sub-Component: language
  • Priority: P5
  • Status: Closed
  • Resolution: Other
  • Submitted: 2001-12-21
  • Updated: 2014-10-17
  • Resolved: 2014-10-17
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
Name: ddT132432			Date: 12/21/2001


FULL PRODUCT VERSION :
All, e.g.

java version "1.3.1"
Java(TM) 2 Runtime Environment, Standard Edition (build
1.3.1-b24)
Java HotSpot(TM) Client VM (build 1.3.1-b24, mixed mode)

FULL OPERATING SYSTEM VERSION : all


ADDITIONAL OPERATING SYSTEMS : all



EXTRA RELEVANT SYSTEM CONFIGURATION :
This is an RFE for the langauage itself, therefore it
applies to all versions.

A DESCRIPTION OF THE PROBLEM :
Currently Immutable types can be hand coded in Java and are
very useful, e.g. String, Integer, also see "Effective
Java" Item 13: Favour Immutability. However they are: not
compiler enforced, you can't test for immutability, their
are no companion mutable classes derived from a common base
class, and the JVM doesn't know they are immutable and
therefore can't fully optimise them (e.g. eliminate
pointers for small objects, aggregate into arrays, assume
object not changed by another thread, assume object same
between JVMs and JVM invocations). Hand coded immutability
can be tricky; see Workaround section below, in particular
points J and K.

This proposal is to add an interface to mark a class as
immutable (note immutability on a per class basis not a per
object basis) and to add immutable and companion mutable
classes plus two helper interfaces for conversion to/from
immutable and mutable. In particular make a new package
java.lang.immutable containing:

    public interface Immutable { /* Empty */ }
    public interface ToMutable extends Immutable {
        Object toMutable();
    }
    public interface ToImmutable {
        Immutable toImmutable();
    }

    public abstract class AbstractArray { ... }
    public class Array<ToMutable T> extends AbstractArray
                                implements ToMutable { ... }
    public class MutableArray<ToImmutable T>
        extends AbstractArray implements ToImmutable { ... }

    public abstract class AbstractString { ... }
    public class String extends AbstractString
                                implements ToMutable { ... }
    public class MutableString extends AbstractString
                              implements ToImmutable { ... }

    public abstract class AbstractInteger { ... }
    public class Integer extends AbstractInteger
                                implements ToMutable { ... }
    public class MutableInteger extends AbstractInteger
                              implements ToImmutable { ... }

    ...

Compiler extensions for classes that implement Immutable
--------------------------------------------------------
1. Make class final, if not already
2. Make all fields final, if not already
3. Make all fields private, if not already (not strictly
   necessary)
4. Only allow fields to be an Immutable inherited type or
   primitive
5. a==b maps to a.equals(b)
6. The compiler generates equals() and hashCode(), if not
   provided
7. The compiler generates clone() and toString(), if not
   provided
8. The type returned by clone() is the type of the class,
   not Object
9. Only inherit from classes without non-static fields or
   from interfaces
10. Automatic boxing/unboxing when casting to/from super
    types
11. Immutable objects must be initialised
12. Immutable objects cannot be compared to or assigned null
13. Mark class as immutable for JVM
14. Compiler provides a readResolve() method if necessary

Compiler and source code compatibility issues
---------------------------------------------
If code written for a compiler that understands Immutable
is accidentally given to a pre-Immutable compiler it might
compile producing spurious code. Probably not a major
problem in practice, similar problems in the past have
occurred (e.g. when Serializable was added) and these have
not proved to be serious.

JVM issues
----------
The proposal would be compatible with existing JVMs. An
unused modifier bit in the class description in the class
file would be set by the compiler to tell the JVM that the
class is immutable. Future JVMs could do more optimisation
knowing that the Object is immutable. For example: small
immutable objects could be stack allocated and copied in
and out of methods and immutable objects could also be
aggregated into arrays, thus eliminating arrays of
pointers. Immutable Objects would enable the JVM to pass
them to threads without having to worry about memory
synchronization issues. JVM would know an immutable object
is the same between JVM invocations and between JVMs.

Related proposals
-----------------
1. This proposal builds on: immutable keyword proposal from
   James Gosling http://java.sun.com/people/jag/FP.html (it
   is with some trepidation that I suggest that a proposal
   from James Gosling can be improved upon!). This proposal
   doesn't require a new keyword, it allows testing for
   immutability (x instanceof Immutable), takes into
   account liasing and security problems due to
   serialization, implements a truly immutable type (fields
   are Immutable or primitive in this proposal), and the
   James Gosling proposal allowed inheritance from objects
   that define fields and therefore you can't write an
   equals method and in turn == wouldn't work
   (see "Effective Java" Item 7).

2. This proposal is suggested as a better alternative than
   adding C/C++ style const keyword (RFE: 4211070). C/C++
   const has liasing problems and the syntax is poor, this
   proposal rectifies these deficiencies.

3. The proposal also overlaps with User-Defined Lightweight
   Value-Semantics Objects (RFE: 4213096) which are in C#
   as struct objects. The disadvantage of C# style struct
   objects are that they can lead to excessive copying, a
   problem seen in C++ also. Immutable objects never HAVE
   to be copied, it is up to the JVM, and therefore this
   problem is eliminated. C# struct objects do not help
   with memory synchronisation issues in multi-threaded
   applications, unlike immutable objects. You can't rely
   on C# struct objects between JVMs or between JVM
   invocations.

4. Other immutable proposals: RFEs 4037498 and 4395140 asks
   for immutable on a per object not per class basis and
   therefore need a runtime check, this proposal doesn't.
   RFEs 4069541 and 4213096 are similar but don't address
   all the issues this proposal addresses.

5. Design be contract (DBC) is often used to give
   immutability in languages that support DBC, but I would
   argue that immutable types are more useful than DBC,
   simpler to implement, and don't incur runtime penalties
   (in fact quite the opposite). For immutable types you
   just check the arguments to the  constructor, therefore
   there is no need for DBC with immutable types. Note
   inheritance of checks automatically happens because
   derived types call super. DBC is a requested feature for
   Java, see RFE 4449383.

Design decisions explained
--------------------------
The proposal makes fields private, this isn't necessary for
an immutable type but I think it is good practice and hence
its inclusion.

You can't write equals() if you allow inheritance of non-
static fields, see "Effective Java" Item 7, therefore
inheritance of non-static fields is not allowed.

Also it doesn't make sense to inherit from an object that
has state since the state of the super object is part of
the derived objects state and therefore the super class
needs to be Immutable and therefore you can't inherit from
it (Immutable classes need to be final). Instead use
composition, see "Effective Java" Item 7.

The compiler does automatic boxing and unboxing when
casting to and from a super type, this automatic boxing is
not in a hash-consing manner. In particular: ((Object)
immutable)==((Object)immutable) is false. Although it would
be nice to be hash-consing when boxing, the overhead is too
great.

The purpose of the ToImmutable and ToMutable interfaces and
allowing inheritance from objects without non-static fields
is to encourage the following immutable/mutable companion
class idiom (the purpose is to ensure Integer and
MutableInteger both have the same add() method).

public abstract class AbstractInteger {
    public abstract AbstractInteger create(final int value);
    public abstract int getValue();
    public AbstractInteger
                         add(final AbstractInteger value) {
        return create( getValue() + value.getValue() );
    }
}

public class Integer extends AbstractInteger implements
  ToMutable {
    int value;
    public Integer(final int value) { this.value = value; }
    public AbstractInteger create(final int value) {
        return new Integer(value);
    }
    public int getValue() { return value; }
    public MutableInteger toMutable() {
        return new MutableInteger(value);
    }
}

public class MutableInteger extends AbstractInteger
implements ToImmutable {
    int value;
    public MutableInteger(final int value) {
        this.value = value;
    }
    public AbstractInteger create(final int value) {
        return new MutableInteger(value);
    }
    public int getValue() { return value; }
    public Integer toImmutable() {
        return new Integer(value);
    }
    public void setAdd(final AbstractInteger value) {
        this.value += value.getValue();
    }
}

Integer's add() method arguments are two AbstractIntegers
and it returns an AbstractInteger, therefore this is three
boxing operations on the call immutable.add(immutable) if
immutable is a java.lang.immutable.Integer. All this boxing
could make add() slow and therefore for performance reasons
some operators may be coded directly in Integer as well as
in AbstractInteger.

The extra linguistic feature of a marker interface,
Immutable, gives backward compatibility (no new keyword)
and allows:

if (immutable instanceof Immutable) ...


This bug can be reproduced always.

CUSTOMER WORKAROUND :
Hand code an Immutable object:
1. Make class final
2. Make all fields final
3. Make all mutable fields private
4. Write hashCode() and equals() or control creation (see K
   below)
5. Don't inherit from an object that has non-static fields
6. Defensively copy mutable constructor arguments
7. Defensively copy mutable fields returned from methods

Disadvantages:
A. Not compiler enforced
B. Can't test for immutability
C. JVM can't eliminate pointers for small objects
D. JVM can't aggregate into arrays
E. JVM can't stack allocate
F. JVM can't assume same object between JVMs (distributed
   app.)
G. JVM can't assume same object between invocations
H. Compiler can't enforce initialisation (instance could be
   null)
I. == isn't necessarily the same as equals() and isn't
   mapped to equals()
J. Defensive copying is tricky, see "Effective Java" Item 24
K. Instead of writing equals() and hashCode() and to ensure
   that ==, hashCode(), and equals() mean the same (see I
   above), object creation could be controlled so that
   identical objects are not multiply created (hash-
   consing). Unfortunately this can be a performance
   problem and is tricky to do in a manner that still
   allows garbage collection ("Effective Java", Item 4,
   page 16, and Item 5).
L. Easy to accidentally circumvent with serialization
M. Little support in current libraries
(Review ID: 137637) 
======================================================================

Comments
The 'specification' component of the Java Bug System is for reporting technical errors and ambiguities in the text of The Java Language Specification and The JVM Specification. It is not the venue to propose new features in the Java language or JVM. Ongoing feature development is carried out in OpenJDK (http://openjdk.java.net/jeps/); corresponding enhancements to The Java Language Specification and The JVM Specification are managed through the Java Community Process (http://jcp.org/).
17-10-2014

EVALUATION We are seriously considering this issue, and have similar (though not identical) proposals under review. I can't say whether this or anything like it will get into the language. ###@###.### 2001-12-21
21-12-2001