Duplicate :
|
|
Relates :
|
|
Relates :
|
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) ======================================================================
|