Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8061411

Add Immutable types to Java

XMLWordPrintable

    • Icon: Enhancement Enhancement
    • Resolution: Other
    • Icon: P5 P5
    • None
    • None
    • specification


      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)
      ======================================================================

            abuckley Alex Buckley
            abuckley Alex Buckley
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: