|
Comments
|
Submitted On 24-JAN-2002
davidtribble
The problem with using an 'Immutable' marker interface
is that it really doesn't solve the problem that a true
'const' keyword would.
For example, we want to provide an Employee class that
contains a Date object encoding the date of hire, among
other attributes:
public class Employee
{
Date m_hire; // (A)
...
}
We would also like to have getter method to establish
the hire date for a given employee:
// First attempt
public void setHireDate(Date d)
{
m_hire = d; // (B)
}
The problem with the code above is that the 'm_hire'
member refers to a mutable Date object, which could be
modified by other methods outside the class. It would
be desirable to recode it like this:
// Second and better attempt
public void setHireDate(const Date d)
{
m_hire = new Date(d); // (C)
}
The new code makes a copy of the Date object, so that
no outside influence can modify it.
We would also like to provide a getter method to
retrieve the hire date:
// First attempt
public Date getHireDate()
{
return (m_hire); // (D)
}
The problem with the code above is that the 'm_hire'
member is returned to the caller, which can now quite
easily modify its contents. This makes useless all of
our previous efforts to keep the Date object private
and unaffected by outside influences. One way to
prevent our private Date object from being modified is
to return a copy, which the caller can then modify to
his heart's desire:
// Second attempt
public Date getHireDate()
{
return (new Date(m_hire)); // (E)
}
While this protects our private 'm_hire' member from
undesired modifications, it does so at the cost of
creating an entire new Date object. If we use this
technique for very large or complex members, this could
become quite expensive in terms of memory and time. A
better approach is to rewrite the getter method so it
returns a Date object that can't be modified at all:
// Third and better attempt
public const Date getHireDate()
{
return (m_hire); // (F)
}
Now we can simply return a reference to our private
Date object, while insuring that it won't (can't) be
modified by any methods outside our class.
The complete class code now looks like this:
public class Employee
{
Date m_hire; // (A)
...
public void setHireDate(const Date d)
{
m_hire = new Date(d); // (C)
}
public const Date getHireDate()
{
return (m_hire); // (F)
}
...
}
Continued...
Submitted On 24-JAN-2002
davidtribble
Continued...
In contrast, we can't do such a thing near as easily
using an 'Immutable' marker interface class. One
reason is that an Immutable class provides immutability
(or constness) at the class level instead of at the
object level. This means that our 'm_hire' member
can't simply have type 'Date', but must be declared as
having a type which we must create that both extends
'Date' and implements 'Immutable':
private class OurDate // (1)
extends Date
implements Immutable
{
OurDate(Date d)
{
super(d);
}
}
public class Employee2
{
OurDate m_hire; // (2)
...
}
Our setter method is fairly simple:
public void setHireDate(Date d)
{
m_hire = new OurDate(d); // (3)
}
But our getter method suffers from the same problem as
we had at (C), that we must somehow make a publicly
accessible Date object from a privately inaccessible
ConstDate object:
public Date getHireDate()
{
return (new Date(m_hire)); // (4)
}
Getting this to work might require adding a conversion
method to the OurDate class.
The alternative is not any prettier, since it must
expose our immutable ConstDate class to the outside
world:
// Second attempt
public OurDate getHireDate()
{
return (m_hire); // (5)
}
For this to work properly, we will have to override the
methods of Date in our ConstDate class which allow its
contents to be modified. Of course, if we want to
modify the contents within our package, we must also
provide package accessible setter methods as well:
public class OurDate // (6)
extends Date
implements Immutable
{
OurDate(Date d)
{
super(d);
}
public void setTime(long t) // (7)
{
// Overrides super.setTime()
throw (new Error("Not supported"));
}
void modifyTime(long t) // (8)
{
super.setTime(t);
}
...
}
What a mess. It's so much simpler when we have 'const'.
See the 'const' proposal, RFE 4211070, at
<http://developer.java.sun.com/developer/bugParade/bugs/4211070.html>
Submitted On 24-JAN-2002
hlovatt
davidtribble, thanks for your reply. One point concerning my
proposal that I need to clarify; you can't arbitrarily add
"implements Immutable" to make a class immutable. For a
class to be immutable it can only extend from class
hierarchies that don't define any non-static fields and the
class itself can only have Immutable or primitive fields.
These are necessary conditions so that the compiler can
enforce immutability (constness) without adding runtime
checks or conservatively copying all returns. Therefore
there would be no way to use Date, for example, in an
immutable type.
The compiler can't enforce immutability with your const
proposal unlike my immutable proposal, the problem with your
const proposal is generally referred to as const aliasing in
the C++ world. You give the example of returning a const
Date; but the compiler (without runtime checks) can't
enforce that the Date isn't changed! Consider:
public class Employee {
Date hire;
void set(Date hire) { this.hire = new Date(hire); }
const Date supposedlyConstGet() { return hire; }
public static void main(String[] args) {
Employee e = new Employee();
e.set( new Date(1) );
const Date notConst = e.supposedlyConstGet();
e.set( new Date(2) ); // Oops notConst changed!
}
There is no way round this problem without extra runtime
checks or copying, hence it is necessary to ban mutable
types completely from immutable classes if you want them
fast. I gave another example of this const aliasing written
in C++ in the 'add const RFE' (4211070).
Submitted On 03-FEB-2002
hlovatt
Hi, I made a number of mistakes in the code I posted
above - late at night! The correct code is below, I have
commented out const so that it can be compiled and run.
import java.util.Date;
public class ConstAliasing {
Date date = new Date();
void set(Date date) {
this.date.setTime( date.getTime() );
}
/*const*/ Date supposedlyConstGet() { return date; }
public static void main(String[] args) {
ConstAliasing ca = new ConstAliasing();
ca.set( new Date(1000) );
/*const*/ Date notConst = ca.supposedlyConstGet();
System.out.println(notConst);
ca.set( new Date(2000) ); // Oops notConst changed!
System.out.println(notConst);
}
}
Submitted On 09-FEB-2002
abies
cowwoc:
They have to be final to allow expanding on stack and inside
other objects. For example if jvm want to expand
ImmutableData inside some other class, it has to allocate
certain amount of place. It could be an unpleasant suprise
if later some subclass of ImmutableData would be assigned
there, with many more fields.
Converting from/to immutability would allocate new objects.
Problem with C++ is that you get non-const view of the
_same_ object. Here you will get different object, not
aliased with previous one, just with same data copied on
very start. I still hardly see a use for it, but I think
this was a reason behind this part.
Submitted On 09-FEB-2002
hlovatt
Hi,
Abies is correct in all he/she said re. Cowwoc's comments.
I would also add that you can't write an equals method for
derived types (see 'Effective Java' Item 7). I will also
reiterate Abies's point that the call toMutable() allocates
a new object and therefore the const alias problem seen in
C++ doesn't occur.
The reason for providing this mutable/immutable facility is
for large objects that take some time to allocate, I was
particularly thinking of matrices, you can get a
performance improvement by overwriting the old object
instead of allocating a new one. This is much like String
(immutable) and StringBuffer (mutable) do today. If you
write:
s + s1 + s2
Where s, s1, and s2 are java.lang.String the compiler
actually generates:
new StringBuffer(s).append(s1).append(s2).toString()
Using this new proposal the equivalent, i.e. if s, s1, and
s2 are java.lang.immutable.String (the proposed new String
class), would be:
s.toMutable().append(s1).append(s2).toImmutable()
Submitted On 09-FEB-2002
cowwoc
Hi,
Please clarify the following two issues:
1) I don't see why immutable classes need be final. What's
wrong with allowing them to be extended?
2) I don't like the idea of converting from/to immutability.
Personally I believe one of the biggest problem with the
'const' identifier in C++ is that it isn't _really_
enforced. Users can easily use the "const_cast" to modify
"constant" variables. This shouldn't be allowed. If
something is constant, I don't forsee any situation why you
were ever _have_ to convert it to non-constant in its
lifetime. Am I missing some basic design issue here? Please
clarify..
Submitted On 11-FEB-2002
Ixchel
I've said before that I support the concept of adding
immutable types to Java. However, I do NOT think that this
is in any sense a replacement for the 'const' keyword.
Quite simply, immutability (a const object) and constness
(a const reference to a possibly non-const object) are two
different things, with completely different intents and
uses. Comparing the two as if one could replace the other
is just not reasonable. They are completely orthogonal
concepts. I do not think in any sense that having immutable
objects would remove the need for a way to document and
preserve constness of references, however.
With that said, I do support some kind of immutability in
Java. However, I am uncomfortable with the idea of using an
interface to indicate it instead of some kind of special
keyword (e.g. "immutable" and "isimmutable"). Unlike
Serializable, the semantics for the Immutable interface as
described would impact how (and if) legal Java source code
could be compiled. This would create a version skew of Java
compilers just as large as the one that would be created by
adding new keywords.
Also, I don't think immutable classes should be forced to
be 'final'. Extending an immutable class is a perfectly
reasonable thing to do. Yes, certain compiler optimizations
(pass-by-value) may not be able to be used for non-final
immutable objects, but this may be perfectly fine in many
cases. There is no need to REQUIRE immutable objects to be
final.
Also, it is quite common to have both an immutable object
and a non-immutable object share the same interface
(containing only accessor methods), so that a reference to
that interface can be passed around without knowing which
it refers to. This does not seem possible in the current
proposal. In fact, it does not seem possible to declare an
interface 'immutable' at all under the current proposal.
This is a problem.
Submitted On 11-FEB-2002
andythomascramer
While immutable types would be appreciated, they are not a
replacement for const-restricted references. The two are
orthogonal concepts.
Hlovatt's defense against davidtribble's illustration is
flawed by a lack of understanding of both existing Java and
the proposal for const-restricted references. Consider:
> /*const*/ Date notConst = ca.supposedlyConstGet();
> ca.set( new Date(2000) ); // Oops notConst changed!
Hlovatt is incorrect. The second line does not change
notConst. The variable notConst is a pointer, whose value
is not changed by the second statement. Java does not offer
object variables.
Hlovatt's criticism of const-restricted references depend
on a presumption that they're intended to make an object
immutable. They're not. They're only intended to restrict
certain references to const operations.
My dessert topping doesn't offer immutable behavior either.
Perhaps this RFE should be an alternative to it as well.
Submitted On 11-FEB-2002
dkf
Problems as I see them:
1) The thing which it would be very nice to have would be
immutable/const arrays (i.e. where you can't assign to any
of the members after creation.) The proposal does not give
that.
2) When people say an immutable/const object, do they
really mean that? Or do they actually just refer to the
view of the object? Immutability is a property of the
object itself, and constness is a property of the view of
the object (which is effectively the reference to the object
in Java.) It is conceivable that access through a const
reference might cause update to the internal state of the
object (e.g. uses an internal pool of objects, or a count of
the number of times a method has been called.)
3) Immutability has *nothing at all* to do with object
identity. Do not confuse them! Equality of objects is
defined by equals() (and, by implication, hashCode()) and
identity of objects is defined by == (and, by implication,
System.defaultHashCode()). Immutability of objects is
defined by whether they change between one look at them and
another, and in general might only be true over a restricted
timespan or through some particular set of views.
4) I might expect a compiler to perform subexpression
elimination on methods called from a const reference.
If I thought really hard, I might even be able to bring
these points together in a coherent whole; but I've never
done my best thinking on a Monday, so this is what you're
getting instead... :^)
Submitted On 13-FEB-2002
hlovatt
Thanks for all your postings, various topics have been
covered. Forgive me if I don't do all the issues raised
justice. I am conscious of the length of this post, this
post is virtually a FAQ for the proposal already!
Typical usage
=============
I imagine that if the proposal was accepted then people
would start of writting immutable classes and using them in
a style similar to that used in functional programming.
Where you create objects and throw them away with great
regularity. Despite the heroic efforts of the people who
write JVMs this can still cause a problem because it can
lead to lots of short lifetime objects.
Taking the example of an Integer class, which I am am
actually proposing to be part of a new package of classes
called java.lang.immutable, it is intended that these new
classes replace the current classes like java.lang.Integer
and java.lang.String.
But for the moment lets suppose that we are writting our
own Integer, we might first write:
public class Integer impliment Immutable {
int value;
public Integer(final int value) { this.value = value; }
public Integer add(final Integer value) {
return new Integer( value + value.value );
}
}
After some profiling we might find that too many
temporaries are created because we are using add a lot.
This code would be re-worked to give a companion mutable
class to prevent the creation of temporaries.
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();
}
}
Then instead of writting:
Integer i = new Integer(1);
Integer j = i.add(1).add(2).add(3).add(4);
you can write
Integer j = i.toMutable().setAdd(1).setAdd(2)
.setAdd(3).setAdd(4).toImmutable();
which saves on the creation of temporary objects. This is
very similar to how strings are currently concatonated in
Java using a StringBuffer.
Submitted On 13-FEB-2002
hwc
I like this proposal. I’d like to be able to create an
array of immutable objects, for example, without the
overhead of allocating each object on the stack and copying
them each time, but…
1) I’m not sure how your == deals with null. Supposing,
for example, that I wanted to initialise a static variable
the first time I used it, I might say:
static SomeType constantData;
if( contantData == null )
constantData = readFromDatabase();
Is this a compile-time or run-time error? Either way it
breaks a useful idiom. Primitive types always store a
value; object references don’t. This makes optimisation
more difficult, too.
2) I’d rather the compiler complained if I hadn’t followed
your rules - making everything final and so on, instead of
doing it for me. The meaning of a piece of code should be
evident to the reader, not dependent on its context. I
suspect this would give greater backwards compatibility,
too.
3) A similar effect could be achieved by marking a class
const but const has a finer level of granularity –
individual methods and object references could also be
marked const.
What seems to be at issue is a question of idiom: should
mutable and immutable objects be different, even if they
appear to share a common interface (e.g. String and
StringBuffer), or should you be able to restrict the
interface to existing objects, making them appear immutable
to the user.
Submitted On 13-FEB-2002
hlovatt
Const view isn't the same as immutable
======================================
A const view of an object isn't the same as an immutable
object, but they are used in many overlapping
circumstances. For example in the const view RFE people are
continually saying that what they want is a const array
(also see array section below), they mean an array of int,
say, where you can't change the values in the array. This
usage is covered by both a const view and immutable. I
would argue that people nearly always want immutable, not a
const view, even if they say const (see below). For example
in davidtribble's post he contrasts, as many people do,
const with conservatively copying returned values. He
clearly can't mean a const view, because if you change the
original the returned copy isn't changed. He is using const
to give some form of seperation (elimination of side
effects) between the returned object and the original. I
think this is one of the most common usages for const in
C++ and is better handelled with an immutable object.
Much of this confusion about what people mean when they say
const is caused because const is a poor term for const
view, readonly would be much better - as people have said
in the const veiw RFE. I used Immutable to avoid the word
const or constant because of this confusion.
However you can argue that being able to change an object
and have a const view of the object is a useful feature. It
is not incompatible with my immutable proposal, the
proposed package, java.lang.immutable, could be extended to
include const view versions of each set of classes, e.g.
using the Integer example given above the original
MutableInteger could be broken into two classes,
ConstInteger and a new MutableInteger, as below:
public class ConstInteger extends AbstractInteger {
protected int value;
public ConstInteger(final int value) {
this.value = value;
}
public AbstractInteger create(final int value) {
return new ConstInteger(value);
}
public int getValue() { return value; }
}
public class MutableInteger extends ConstInteger
implements ToImmutable {
public MutableInteger(final int value) { super(value); }
public AbstractInteger create(final int value) {
return new MutableInteger(value);
}
public Integer toImmutable() {
return new Integer(value);
}
public void setAdd(final AbstractInteger value) {
this.value += value.getValue();
}
}
Therefore we have an immutable 'Integer', const
view 'ConstInteger', and mutable 'MutableInteger'. They all
interoperate via the common base class 'AbstractInteger'.
I think that the fine distinction between Integer and
ConstInteger isn't necessary, people will use Integer
because they want immutability and all the benefits that go
along with it like: its value can't be changed, elimination
of pointers, speed, compiler written cloning, etc. Hence I
didn't suggest ConstInteger. If you really need
ConstInteger you can easily write your own.
Another reason for not providing ConstInteger is that its
usage overlaps with the toMutable/toImmutable idiom
explained above.
Submitted On 13-FEB-2002
hlovatt
Non-final immutable values
==========================
My argument for not providing these is much the same as for
not providing ConstInteger, you loose many of the benefits
like its value is no longer constant (see below) pointer
elimination, and speed. Also you can easily write your own
if you need it:
public class ExtendableInteger extends AbstractInteger {
private final int value;
public ExtendableInteger(final int value) {
this.value = value;
}
public AbstractInteger create(final int value) {
return new ExtendableInteger(value);
}
public int getValue() { return value; }
}
If you then extend ExtendableInteger with a class that has
a field that can change, then this new class clearly isn't
immutable. But the new mutable class can be used in place
of ExtendableInteger, which is Immutable. I don't like this
ability to be able to substitute something that is not
immutable for a type that I intend to be immutable, hence I
always make my immutable types 'final' when I write them
currently in Java. Mutable derived types wasn't the primary
reason I made the proposed immutable type final, it was for
ensuring that people didn't forget the final keyword and by
forgetting it forgo the performance advantages.
Hence I thought that the default for Immutable should be
final, but it is possible to write a clever compiler that
behaves differently if the Immutable class is final or not.
Also see section Aside below.
Support for immutable arrays
============================
I have added support for immutable arrays in the proposal
in two forms.
1. You can write 'SomeImmutableType[] x', e.g. 'Integer[]
x' and the JVM can eliminate all the pointers etc. so that
the example is an immutable form of 'int[] x'.
2. Also I have added a convenience class Array that is
used 'Array<Integer> x' (I assumed Generic types are in).
Array is immutable itself and therefore forms the basis of
immutable multi-dimensional arrays,
e.g. 'Array<Array<Integer>> x'. I would hope that in time
the syntax Array<Integer> would replace Integer[].
Keyword
=======
It is possible to infer immutability using a keyword
instead of a marker interface, James Gosling, no less, has
suggested this. I preferred the marker interface because:
1. It doesn't need a keyword and hence won't beak old code
that used the new keyword as a variable name.
2. You can write 'x instanceof Immutable'.
3. RTTI doesn't require changing.
4. It fits in logically, you can say ''a variable of type
Immutable'' and people know you mean some class that
impliments Immutable.
Submitted On 13-FEB-2002
hlovatt
Immutable interfaces
====================
The proposal allows an interface to extend Immutable and
therefore mark another interface as immutable. For example
part of the proposal is the interface ToMutable that
extends Immutable.
Coding for either Immutable or Mutable forms
============================================
The proposal supports programming to an immutable
interface, see above, and also to class hierarchies that
don't define non-static fields. For example you can declare
something to be AbstractInteger and use either Integer or
MutableInteger.
Identity
========
In general in Java you want == and equals to distinguish
between objects, but for an immutable object you only want
equals. For an immutable object the JVM is quite entitled
to eliminate the pointer completely. Therefore == has no
meaning, in its conventional Java sense, hence it is mapped
to equals.
Notnull
=======
Because the JVM can eliminate the pointer to an immutable
object a variable of type Immutable cannot be assigned the
value null. This is a useful feature that can be used to
save all that 'if (x == null) throw ...'.
Optimization
============
Any optimizations the compiler or the JVM can do for a
const view of an object it can do for an immutable object.
Vice versa isn't true, there is much more that can be done
with an immutable object.
Aside
=====
I didn't put this in my proposal because I thought people
would complain too much ;). I would prefer an immutable
declaration to infer finality. As the proposal stands you
can write:
Integer i = new Integer(1);
i = new Integer(2);
I think this is confusing, when you say something is
immutable you generally really mean it. You don't mean
under some circumstances it is immutable. Therefore you
will nearly always mean:
final Integer i = new Integer(1);
even if you write:
Integer i = new Integer(1);
Therefore I would also like to have added into the proposal
that immutable type declarations were by default final,
however I thought people would think I was mutating [sic]
the language.
Submitted On 13-FEB-2002
finking
Regarding immutable arrays, suggestion 1 by hvolatt is not
sufficient.
Integer[] intArray = { new Integer(0), new Integer(1) };
is not an immutable array. It prevents intArray from being
reassigned like this:
intArray = blah; // error
and it prevents any of it's members being changed like this:
intArray[0].modifier(); // error
but it does not prevent any of its elements being
reassigned:
intArray[0] = new Integer(9); // OK
The second solution works (as far as I can see), but has
totally different syntax to normal arrays, which is not
what you want. Finally, even if solution 1 did work note
that it still forces you to jump through hoops if you want
an immutable array of primitive types; you have to put them
inside objects first. Why all the complication? What's
wrong with using the const keyword? It is already a reserved
word in Java after all.
Regarding this RFE in general, others have already captured
my thoughts, here are some excerpts that sumarise them:
>...I don't think immutable classes should be forced to
>be 'final'. Extending an immutable class is a perfectly
>reasonable thing to do. Yes, certain compiler
>optimizations
>(pass-by-value) may not be able to be used for non-final
>immutable objects, but this may be perfectly fine in many
>cases. There is no need to REQUIRE immutable objects to be
>final...
Moreover there is no reason why the compiler can't
establish whether a particular class is final and optimise
it (or not) accordingly. The inlining optimisation that the
hotspot compiler does already does similar checking.
Finally, the crux of the matter:
>...I do support some kind of immutability in
>Java. However, I am uncomfortable with the idea of using
>an interface to indicate it...
Regards
Submitted On 14-FEB-2002
hlovatt
3. Should immutability be per class or per object?
==================================================
This is probably the question that is most contriversial
and seems to be at the route of assertions that this
proposal, immutability, doesn't have anything in common
with the C++ style const proposal. Whereas I see a good
deal of overlap between the two. I thought about per object
declarations when I made the proposal and I will briefly
outline my thoughts below.
But first I will digress onto a philisophical point. My
main feeling on this is that you always have to design your
class to use immutability or similar. EG in C++ if you
don't use const in any method argument declaration and
don't use const as a modifier to any method signature then
althogh someone can declare an object as const, they can't
do anything with it! Therefore I actually see very little
difference between the two approaches, you always have to
design your class to use immutability even if the syntax
implies it is on a per object basis.
I think the question of immutability on a per object basis
has three parts: A. is it practical, B. what syntax is
suitable, and C. is it desirable?
A. Is it practical?
-------------------
Yes it is practical (I think!). The compiler could
automatically split a class into Abstract$Integer,
$Integer, and Mutable$Integer say (I have put dollars in
because they are compiler generated and used the
terminology I chose for this proosal). Then at the object
level the user could choose which version they required.
This is really just asking the compiler to automatically
follow the idiom outlined in this proposal or similar (see
below under syntax). This is similar to what the compiler
does for inner classes currently.
B. What syntax?
---------------
The syntax is a tricky question, certainly the C++ syntax
isn't very good. It is difficult to think up something OK,
see all the discussions in the C++ style const proposal. I
went off the idea of a per object basis, partially because
of the syntax question. Below I have outlined my version of
the syntax, but because I never thought through per object
immutability fully there could be errors in this syntax.
The syntax needs keywords or symbols (I can't think of a
way to use a marker interface), I will use keywords below.
The declaration of an object (field, local, or argument)
could become.
Integer eitherImmutableOrMutable;
immutable Integer immutableOnly = new immutable Integer(1);
mutable Integer mutableOnly;
and for arrays
Array<Integer> eitherImmutableOrMutable;
immutable Array<immutable Integer> immutableOnly = ...;
mutable Array<mutable Integer> mutableOnly;
immutable Array<mutable Integer> error; // Error
i.e. Integer maps to Abstract$Integer, immutable Integer
to $Integer, and mutable Integer to Mutable$Integer.
Submitted On 14-FEB-2002
hlovatt
The declaration of methods is trickier, my thoughts were
that if no fields or methods are declared immutable or
mutable then the compiler behaves as it currently does
(except that returns, see below, could optionally be used).
But if any fields or methods are immutable or mutable then
new declaration syntax for methods is used. IE:
[modifiers] <name>(...) returns ... throws ... {...}
in particular, the return type is moved to after the method
name and is introduced by the new keyword returns. Moving
the return type nicely distinguishes an immutable method
from one that returns an immutable type and keeps a Java
like modifier list at the front of the declaration. The
modifiers list is extended to include immutable and mutable.
Methods declared immutable only have access to fields
declared immutable, like static. Methods declared mutable
have access to all fields, again like static. Methods not
declared either immutable or mutable behave like mutable
methods.
For example the Integer class is given below. Note the
compiler supplies toMutable() and toImmutable() as well as
cloning, final fields, etc. for the immutable form $Integer.
public class Integer {
private immutable int value;
public Integer(final int value) { this.value = value; }
public immutable add(final int value) returns Integer {
return new Integer(this.value + value);
}
public mutable setAdd(final int value) returns void {
this.value += value;
}
}
After compilation the resulting classes Abstract$Integer,
$Integer, and Mutable$Integer are as described in this
proposal (but they don't use $ in their names in this
proposal).
C. Is it desirable?
-------------------
Many people would say yes. I came down on the side of it
was too big a change and the simplier alternative as
proposed does everything the more complicated option of a
per object basis does and therefore go with the simpler
alternative. I guess that like 2, above, it is best to go
with the majority.
Submitted On 14-FEB-2002
hlovatt
Reply to Hwc - thanks for saying you like the proposal :)
1. Lazy initialization idiom
============================
Changing your example to use the Integer class spelt out
above, because the code is spelt out (your data base
example is more realistic):
class X {
static Integer constantData;
public final static Integer get() {
if ( contantData == null )
constantData = new Integer(1);
return constantData;
}
}
Would be two compile time errors: 1 because constantData
wasn't initialized and 2 because constantData is compared
to null.
The way to program this is:
static AbstractInteger constantData;
and
if ( contantData == null )
constantData = new Integer(1);
An AbstractInteger can be null, so this works. But the
compiler/JVM can do less optimization, it can no longer
eliminate the pointer, but your data is still immutable. An
alternative that retains the optimizations is the
initialize on demand, holder, class idiom:
Class X {
private static class Holder {
static final Integer i = new Integer(1);
}
public final static Integer get() { return Holder.i; }
}
See ''Effective Java'', Item 48 (this also works in
multithreaded programs, unlike lazy initialization).
2. Compiler shouldn't insert final etc.
=======================================
Difficult to know what it is best to do, I err on the side
of getting the compiler to do more because people tend not
to put things in if they are optional (and in this case
they would loose optimization) and get annoyed if they have
to put something in always (why doesn't the compiler do
this - it knows it is needed). I look on this a bit like
not needing to use public in interface definitions.
Ultimately it isn't that important and we should go with
the majority.
Submitted On 14-FEB-2002
hlovatt
Reply to finking - thanks for your support :)
1. Integer[] x isn't constant
=============================
Good point - I missed this!
2. Array<Integer> syntax
========================
It is different, but I like the syntax. Remember that we
will soon, post generics, be writting:
List<Integer> x;
Map<Integer> x;
etc.
I imagine Array to inherite from Collection or List and then
for (final Iterator<Integer> i = x.iterator(); i.hasNext
(); ) {
Integer integer = i.next();
...
}
will work for Lists, Maps, SortedSets, and Arrays.
3. Automatically final, marker interface
========================================
Not important - go with the majority.
Submitted On 23-FEB-2002
hlovatt
In a previous post I said that a const view of an object
could be added to the object hierarchy. I missed that the
abstract class in the proposed hierarchy, AbstractInteger
for example, already provides this functionality. So const
views are part of the proposal!
The next two points are in reply to suggestions by finking.
Non-final immutable classes
===========================
The abstract classes also provide the basis for
inheritance, since you can't inherit from the immutable
classes. I.E. if you want to extend the immutable integer
class, Integer, you can't; but you can extend
AbstractInteger which gives the same functionality, well
almost. As proposed AbstractInteger doesn't implement
ToMutual, so I propose that it does and to enable this
ToMutual no longer extends Immutable.
Annoying to have to wrap primitives
===================================
As the proposal stands you have to wrap a primitive in an
immutable before an immutable array can be used (note there
is no speed or memory penalty as there currently is with
the Integer etc. wrappers). In most circumstances this will
be painless, e.g.:
Array<Integer> ai = new Array<Integer>(new int[] {1, 2});
In other circumstances, particularly when using
MutableArray, you will manually have to do this, e.g.:
MutableArray<Integer> mai = new MutableArray<Integer>(2);
int x = calculateX();
int y = calculateY();
mai.set( 0, new Integer(x) );
mai.set( 1, new Integer(y) );
Array<Integer> ai = mai.toImmutable();
However it is proposed that the new Integer type etc.,
unlike the existing Integer type etc., have a full set of
operators and since they are immutable they will be of
similar speed to primitive types and take up no more
memory, therefore instead of calculateX using and returning
a primitive it could be written using Integer. Therefore
the example above becomes:
MutableArray<Integer> mai = new MutableArray<Integer>(2);
mai.set( 0, calculateX() );
mai.set( 1, calculateY() );
Array<Integer> ai = mai.toImmutable();
Submitted On 19-MAR-2002
hlovatt
Below is a summary of changes to the original suggestion in
light of comments made in this forum and my further
thoughts. In the description below I have used the terms
XXX, AbstractXXX, and MutableXXX where XXX is the name of
the immutable type, e.g. Integer.
1. The interface ToMutable does not impliment Immutable
2. The AbstractXXX form of a class impliments ToMutable
The above changes make the AbstractXXX and XXX types have
identical interfaces, therefore AbstractXXX can be used as
a base class to extend from (you can't extend XXX because
it is final). The AbstractXXX form also provides a read-
only (const) view to a MutableXXX object.
3. Arrays of types that impliment Immutable are themselves
Immutable
Thus 'integerArray[0] = new Integer(1);' is an error.
4. A declaration of an immutable object is automatically
final
Thus 'integerObject = new Integer(1);' is an error.
5. The proposed new package java.lang.immutable should
contain:
Interfaces: Immutable, ToMutable, and ToImmutable
Byte classes: AbstractByte and Byte
Short classes: AbstractShort and Short
Integer classes: AbstractInteger and Integer
Long classes: AbstractLong and Long
BigInteger classes: AbstractBigInteger, BigInteger, and
MutableBigInterger
Float classes: AbstractFloat and Float
Double classes: AbstractDouble and Double
BigDecimal classes: AbstractBigDecimal, BigDecimal, and
MutableBigDecimal
Character classes: AbstractCharacter and Character
String classes: AbstractString, String, and
MutableString
Array classes: AbstractArray, Array, and
MutableArray
The original proposal had MutableXXX for the equivalent of
the primitive types, this isn't necessary as the only point
in the MutableXXX forms is as a speed optimization when the
creation of a new object is slow. It is unlikely that the
primative equivalent types will be slow to create,
therefore don't bother with MutableXXX forms of these small
classes.
The original proposal didn't include the BigInteger and
BigDecimal classes from java.math.
Submitted On 21-MAR-2002
hwc
Is it possible to simplify the suggested heirarchy
further? Taking this proposal together with the work being
done on XML-RPC, there is a danger that we’ll end up with
far too many classes for most people to understand the
differences.
Taking integers for example, we seem to need four classes:
an abstract base class and three descendants, one immutable
and not nullable, one mutable and nullable, one immutable
and nullable (i.e. java.lang.Integer).
This leads me to ask two questions:
1) Why not graft the new classes into java.lang? This
would allow the standard Integer to extend AbstractInteger
which could itself extend Number.
2) How important is the re-definition of ==? This
seems to be the essential difference between
java.immutable.Integer and java.lang.Integer. I can see
why its useful to hide the identity of immutable objects
but, in practice, might it be better not to rather than
create extra classes? What if == always returned false for
immutable objects?
Submitted On 01-APR-2002
hlovatt
Sorry for not replying sooner, this forum seems to have had
a problem where you could not make a post.
Reply to hwc post point 1
=========================
You are suggesting reducing the number of classes in the
proposed API, I think this is a good thing. You suggest
eliminating java.lang.Integer, I think this is a good idea.
If we had java.lang.immutable.Integer you wouldn't need
java.lang.Integer (particularly if its interface was
compatible in the manner you suggested). One option is to
simple depreciate it, this is what I had in mind.
Your suggestion is that we put the immutable classes into
java.lang, presumably the new immutable Integer is then
called something like ImmutableInteger to distinguish it
from the existing Integer. I would like to use the name
Integer because it is shorter and putting everything into
java.lang still leaves just as many classes, although a
package isn't eliminated. So to me this idea isn't ideal
and I will make a different suggestion.
Another idea is that rather than introduce a new package we
could set a bit in the class file to indicate that the new
java.lang package should be used rather than the old
package. The new java.lang package contains the new
Immutable implementing Integer and the old Integer is no
longer present. A compiler savvy to immutable could have a
compile time switch to allow it to work with the old non-
immutable java.lang package. What do you think, a good idea?
That leaves AbstractInteger and Integer (I have already
suggested eliminating MutableInteger). AbstractInteger is
there to allow extensions because you can't extend from an
immutable type (an immutable type must take up a fixed size
in memory). Is there another way of allowing extensions
without having AbstractInteger a public class or Interface?
Reply to hwc post point 2
=========================
> How important is the re-definition of ==?
Not very in the scheme of things, this could easily be
dropped. Also it enters dangerous territory, namely
operator overloading. You suggest == returns false, how
about == is an error? If == is an error, code using
immutable types won't be broken by operator overloading.
Submitted On 05-APR-2002
hwc
Thanks Hlovatt; you’ve got me thinking.
Rather than introduce too many new types, why not make
greater use of automatic boxing?
Suppose that most of the functionality of the current
java.lang.Integer was implemented in AbstractInteger.
Integer would then be more like your MutableInteger (final
but nullable, even if it didn’t have mutator functions).
int would remain as it was. The compiler would convert it
to an AbstractInteger object when necessary, or casts used
explicitly.
Immutable types could behave in the same way. They might
not appear to be descended from Object: == and synchronized
methods would be illegal, but would always provide a
toMutable() or clone() method to create an Object with the
same value.
StringLiteral would be an Immutable type, converted to
AbstractString when necessary. String and StringBuffer
would extend AbstractString. It might be nice to deprecate
or re-define String but I’d imagine there’s already too
much code out there using ==.
Underlying this suggestion is the idea that Java could
appear to have a unified type system, as proposed for Kava:
int, float, Object and Immutable types would all extend the
same notional super-class. Given such a system, toImmutable
() could return any sub-type. Boxing – toMutable() may be
implicit; un-boxing – toImmutable() probably wouldn’t be.
Without this, I’m not sure how immutable arrays of
immutable types could be implemented efficiently.
A “constructor” like Array< int > a = { 1,2 3 }; might be
OK but Array< Point > b = { new Point( 1, 2 ), new Point(
3, 4 ) }; might create an array of references. Assignment
to a standard array would require a call to toMutable(),
clone(), or even a cast, but it’s too expensive to do
automatically.
Submitted On 07-APR-2002
hlovatt
Hwc, I agree with all your points except possibly:
{ new Point(1, 2), new Point(3, 4) }
not going into an immutable array. I was hoping that it
would be possible to make the compiler smart enough to do
this, since an array of immutable is itself immutable.
Submitted On 07-APR-2002
hlovatt
Lazy Initialization
===================
The language Haskell (http://www.haskell.org/) makes
extensive use of immutable types, most things in Haskell
are immutable. The Haskell runtime automatically does lazy
initialization for immutable types. IE
Integer integer = <some expression>;
is not evaluated until integer is actually used (assuming
Integer is an immutable type). Once the variable is used
the first time its value is remembered and subsequently it
is not re-evaluated. Hopefully the same will be true in
Java if we get immutable types.
Submitted On 07-SEP-2002
YATArchivist
My thoughts...
I agree with those who think that immutable classes don't
need to be final. The JVM can still allocate space on the
stack for final immutables if it wants to.
Obviously, if immutables are not automatically final your point
3, that all fields should be private, could also be dropped;
likewise point 9, that immutables should inherit from classes
with no instance fields.
Similarly, I don't see the need for point 5 (== to .equals).
Points 7 and 8 (compiler generating clone() and toString())
should IMO be rethought and reworded. WRT clone, perhaps
the compiler should automatically make an immutable type T
implement Cloneable<T> (assuming generics, as you also do).
Automatic generation of toString I disagree with - where is
the benefit? I'll almost certainly want to write my own, so I
can order the fields as I wish and treat Collection fields etc as
I wish.
I disagree with 11 and 12.
13 is handled automatically if you use interfaces.
What does the readResolve() of 14 do?
Submitted On 16-OCT-2002
hlovatt
cont...
11 and 12 (immutables must be initialized and can't be null)
are necessary to allow stack allocation. Not that bad in
Java you can say:
final Integer i;
if ( someCondition() ) {
i = new Integer(1);
}
else {
i = new Integer(0);
}
This is different than C++ when you can have problems with
reference types needing to be initialized.
13 (Mark Immutable for JVM). Yes it is already covered, I
was thinking of a bit in the class file so that it was
quick. But you are correct, this assumption that the JVM
needs this optimization may be incorrect.
14 readResolve. This isn't really necessary and should be
dropped since I now think it is impractical.
As an aside the idea was that if the compiler can optionally
stack allocate. If it chooses not to stack allocate then I
had in mind that if the object was deserialized it would use
readResolve to return a reference to an already created
object of identical value. But this is overkill!
Submitted On 16-OCT-2002
hlovatt
Reply to YATArchivist,
Sorry for not posting sooner, I had some how missed your post.
I seem to be in the minority saying that immutables should
be final (point 1), therefore this should be dropped from
the proposal.
Similarly with regard to private fields I am again in the
minority, therefore drop point 3.
As you say, if 1 and 3 are dropped then 9 (restrictions on
inheritance) can also be dropped.
Point 5 (== same as equals); most people think this is a bad
idea, so drop this.
Points 7 & 8 (clone and toString).
First clone, I didn't want the compiler to make immutables
cloneable since there is no point in cloneing an immutable.
Just copy a reference to it (or its values if the JVM has
stack allocated), e.g. "Integer i2 = i;". The point of
covering clone was to deal with the case of someone
implementing Cloneable, another option would be to make this
an error.
Second toString. I think it is necessary for a default
toString method for if the JVM stack allocates. There is no
address and therefore toString from Object will fail.
Submitted On 19-DEC-2002
M.R.Atkinson
hlovatt,
Although the following are not necessary for Immutatble
types I agree with you that they are desirable:
Array<Integer> (and Array<Array<Integer>> x for square arrays).
Arrays of Immutable types are themselves Immutable.
I am not shure that there is any advantage of using
AbstractXXX classes rather than iterfaces, as the abstract
classes cannot have state. On general principles I would
rather use interfaces Effective Java, Item 16.
You may be in a minority as regards to Immutable requiring
final, but it is a minority that includes Joshua Bloch (not
counting Allen Holub and myself), stick with your original
proposal.
I think that == mapping on to equals() is essential, as
another reason think about
Integer i = 3;
if (i == 3) { .. }
If automatic boxing of the 3 in the if statement occurs this
will evaulate false, if unboxing of the i in the if
statement occurs it will evaulate true. Now you could make a
rule, but it would be completely arbitary and difficult to
remember.
Integer x = new Integer(5); // x is implicitly final
finally, it should be made clear that Immutable has benefits
for:
The writer of a Immutable class, it allows him to make his
intension clear and for the compiler to check it.
The user of a Immutable class, it allows her to be shure
that the class is truely immutable (see the problems with
BigInteger, in Effective Java Item 13). It allows her to
effectively reason about her code, because interactions are
less.
The client code can perform added checks, have arrays of
Immutables also Immutable and insert implit final for
variables declared to be of Immutable types.
The JVM can use pass by value for small Immutable objects if
it desires, or place Immutable objects directly into arrays
rather than having to use references.
Continued ...
Submitted On 19-DEC-2002
M.R.Atkinson
Restating my list of requirements (with better formatting
and a few bug fixes):
Immutable classes have the following characteristics:
1. They are "final" or contain only private constructors.
2. All instance variables are private final.
3. All instance variables are primitive types, or
immutable or contain only final instance variables
themselves (recursively). There is one exception to
this, a volatile instance variable may be lazily
constructed.
4. They are not allowed to start Threads in any of their
constructors.
5. They are not allowed to write to any static variable
in any of their constructors.
6. Immutable objects must be fully initialized in their
constructors (except for any volatile fields).
7. They cannot implement Cloneable.
8. If they implement Serializable they must have a
readObject() or readResolve() implementations that
obeys the same rules as for constructors.
9. They cannot contain be a copy constructor.
10. An immutable class should not extend another class
that contains non-static fields.
11. No non-private methods are provided that modify
any of the instance variables.
Immutable classes may also be treated as lightweight
classes, as long as:
12. toString() should be implemented, the default
toString() in Object that gives an objects address
is not meaningful in lightweight classes.
13. Like primitive instance variables they are cannot be
synchonized on.
14. a==b maps onto a.equals(b).
15. The equals() and hashCode() methods are
implemented.
16. Casting from or to an immutable class does boxing or
unboxing.
17. They cannot be compared to or assigned null.
The above does not specify how Immutable will be implemented
I have a slight preferrence for adding a keyword, but your
points about using a marker interface are well made.
Mike Atkinson
Submitted On 19-DEC-2002
M.R.Atkinson
No non-private methods should be able to modify the visible
state of the Immutable. Private methods may modify the state
but are only callable by constructors (or readObject()).
The extra conditions are required to allow immutable objects
to be treated as lightweight objects being able to be passed
by value (if they are small) at the descretion of the JVM.
== should map to a.equals(b), as they both must have the
same samantics for an immutable class. Remember Immutable
objects may not be cloned or copy constructed and
implementation details like hash-consing (and pass by
reference or pass by value) should be invisible to the
programmer.
Casting to or from an Immutable will have to perform boxing
or unboxing. This allows:
Integer x = 1;
AbstractInteger y = new MutableInteger(2);
Integer z = y;
As a value type has no reference it cannot be null.
continued ...
Submitted On 19-DEC-2002
M.R.Atkinson
I have created by own list of rules for Immutable classes
Immutable classes have the following characteristics:
1. They are "final" or contain only private constructors.
2. All instance variables are private final
3. All instance variables are primitive types, or
immutable or contain only final
instance variables themselves (recursively). There is
one exception to this,
a volatile instance variable may be lazily constructed.
4. Like primitive instance variables they are cannot be
synchonized on.
5. They are not allowed to start Threads in any of their
constructors.
6. They are not allowed to write to any static variable
in any of their
constructors
7. Immutable objects must be initialized.
8. They cannot implement Cloneable.
9. If they implement Serializable they must have a
readObject() implementation
that obeys the same rules as for constructors.
10. They cannot contain be a copy constructor.
11. An immutable class should not extend another class
(except Object) that contains
non-static fields.
12. No non-private methods are provided that modify any
of the instance variables.
Immutable classes may also be treated as lightweight
classes, as long as:
12. a==b maps onto a.equals(b)
13. The equals() and hashCode() methods are implemented.
14. Casting from or to an immutable class does boxing or
unboxing.
15. They cannot be compared to or assigned null.
Immutable classes have to be final (or have all their
methods final a less satisfactory solution) otherwise they
could have a method overridden to return a mutable value.
Private constructors with static factories are in some ways
better than using constructors to create objects (Effective
Java Item 1), it impossible to extend a class with only
private constructors so it is effectively final
Instance variables have to be final, and should be private,
using accessors allows the implementation to be changed.
Lazy construction is sometimes necessary for performance
reasons, its probably best to make the field volatile to
indicate that it is not part of the externally visible
state. (see Effective Java, Item 13).
Continued ...
Submitted On 19-DEC-2002
M.R.Atkinson
I suspect that if synchonization is allowed on Immutable
objects it would force them to be reference types. Opps,
this should have been included in the list for additional
requirements for lightweight objects.
Threads started in the constructors allow Imutable objects
to become mutable and unpredictable, see "If I Were King: A
proposal for fixing the Java programming language's
threading problems", Allen Holub.
http://www-106.ibm.com/developerworks/java/library/j-king.html
public class Broken {
private long x;
Broken() {
new Thread() {
public void run() {
x = -1;
}
}.start();
x = 0;
}
}
The thread that sets x to -1 can run in parallel to the
thread that is running the constructor which sets x to 0.
Consequently, the value of x is unpredictable.
Similarly writing to static variables allow half constructed
objects to be observered:
public class Test {
public static Test test;
private int x;
private int y;
public Test(int x, int y) {
this.x = x;
test = this;
this.y = y;
}
}
Implementing Cloneable does not make much sense, as any
cloned Immutable will be indistinguishable from the original
and will neither will ever change. clone() also acts
another constructor and can be tricky to get right.
For the reasons outlined in Effective Java, Serializable
Immutable objects should explicitly implement the
readObject() or readResolve() as the default readObject()
would allow an attacker to create a mutable version of your
Immutable class, see Items 13 & 56.
Copy constructors are useless, for the same reason that
clone() is useless, having one in an Immutable class shows a
design error or missunderstanding, the compiler should issue
an error.
Immutable classes should not extend classes that contain
instance variables, I think not even final instance
variables, as this would allow an immutable class to be
modified.
Continued ...
Submitted On 25-JAN-2003
hlovatt
cont.
4. They are not allowed to start Threads in any of their
constructors.
I don't think this is necessary since the fields are
final, e.g.:
final long x;
ThreadStartedInConstructor() {
new Thread() {
public void run() {
x = -1;
}
}.start();
x = 0;
}
Is an error at compile time.
Submitted On 25-JAN-2003
hlovatt
Reply to Mike Atkinson: Sorry for not replying sooner, I use
bug watch to notify me of changes, unfortunately it didn't
work for some reason and I have only just realized that you
posted comments. Taking each of your points in turn:
1. They are "final" or contain only private constructors.
Go with the majority it isn't a make or break issue. You
can have non-final immutables and they do have some
uses,
e.g. adding new methods. Since the extended type would
also be immutable the need for final as suggested by JB
in "Effective Java" isn't necessary, he needs final
because the compiler doesn't enforce the extended type
to be immutable when you 'hand craft' an immutable.
The JVM already knows if a class has an extension loaded,
it is called "effectively final", therefore it can make
the object lightweight if there is no extension loaded
at the time the object was made provided it can back out
of this decision if an extension class is later loaded.
The JVMs do this for effectively final methods already,
which they inline until an extension class is loaded.
2. All instance variables are private final.
Private is good practice, but again go with the majority,
it isn't a make or break issue. The definition of
immutable used allows for public fields since the fields
are final and either primitive or immutable, therefore
they can be public, etc.
3. All instance variables are primitive types, or
immutable or contain only final instance variables
themselves (recursively). There is one exception to
this, a volatile instance variable may be lazily
constructed.
I don't get what you mean by instance variables, do you
mean instance fields or local variables. Sorry we
obviously use different terminology.
I am not good enough at JVMs to know whether this
volatile idea is possible. On the general subject of
lazy initialization I was hoping that the compiler/JVM
could do this automatically, like the language Haskell
(http://www.haskell.org/). You don't manually code lazy
initialization in Haskell, the compiler just does it.
Submitted On 25-JAN-2003
hlovatt
cont.
10. An immutable class should not extend another class
that contains non-static fields.
Except that it can extend other immutable classes, see 1.
11. No non-private methods are provided that modify
any of the instance variables.
Since the fields are final and either primitive or
immutable then no method, private or otherwise, can
modify the fields.
12. toString() should be implemented, the default
toString() in Object that gives an objects address
is not meaningful in lightweight classes.
I favour the compiler supply a toString if the
programmer doesn't.
13. Like primitive instance variables they are cannot be
synchonized on.
Yes.
14. a==b maps onto a.equals(b).
Again not a 'show stopper', go with the majority.
Another alternative is that == is an error when applied
to an immutable.
15. The equals() and hashCode() methods are
implemented.
I favour the compiler supplying these methods if the
programmer doesn't.
16. Casting from or to an immutable class does boxing or
unboxing.
Yes.
17. They cannot be compared to or assigned null.
Yes, applies all the time since the compiler can't
check this (it could happen in a class compiled
seperately).
18. The above does not specify how Immutable will be
implemented I have a slight preferrence for adding
a keyword, but your points about using a marker
interface are well made.
Not a show stopper, go with the majority.
Submitted On 25-JAN-2003
hlovatt
cont.
6. Immutable objects must be fully initialized in their
constructors (except for any volatile fields).
This is ensured by final fields (see 3 above re.
volatile).
7. They cannot implement Cloneable.
See 8 below.
8. If they implement Serializable they must have a
readObject() or readResolve() implementations that
obeys the same rules as for constructors.
My original idea was as you stated above, but I have
backed away from this because of using immutable types
more in my own code. To be able to ensure that a copy of
an immutable isn't made you need to keep a list of
immutables and compare the new immutable to the list and
return the existing one from the list instead of making
a new one (this is called "const hashing" sometimes).
Const hashing can be very slow and use a lot of memory
for large objects, therefore just let repeats be made
and rely on equals returning true when they are compared.
These comments also apply to Cloneable, boxing and
unboxing, and to making to identical immutables
manually, e.g.:
ImmuableInteger i1 = new ImmuableInteger( 1 );
ImmuableInteger i2 = new ImmuableInteger( 1 );
They are the same! Just like copies made by
serialization.
9. They cannot contain be a copy constructor.
See 8. Just let people do this if they want; there may
be a legitamate reason and it is hard to stop, e.g:
ImmutableType( ImmutableType objectToBeCopied,
int extraUnsusedArgumentToFoolCompiler ) { ... }
Submitted On 25-JAN-2003
hlovatt
cont.
5. They are not allowed to write to any static variable
in any of their constructors.
You raise an interesting point here. I will widen the
discussion to general access to statics either in their
own class or in another class. It is in one way odd that
an immutable can have a changing value via a static, but
all the immutables of that type will always have the
same value. Therefore it is not like two immutable that
were the same sudenly become different, they both change
together. Since Java has statics I chose to leave this
as is and I think this is still the right decision. On
a practical note I can't think how you would stop this,
e.g.:
public class HidingStaticsInsideInstances
implements Immutable {
void accessHiddenStatic() {
final DifferentClass dc =
new DifferentClass();
final int x = dc.accessStaticViaInstance();
System.out.println( x );
}
public static void main(
final String[] notUsed ) {
new HidingStaticsInsideInstances().
accessHiddenStatic();
}
}
class DifferentClass {
static int x = 1;
int accessStaticViaInstance() {
return x;
}
}
Submitted On 11-FEB-2003
M.R.Atkinson
Opps,
2. All instance variables are private final.
should have said
2. All instance variables are private or final.
otherwise my point 11. does not make sense.
Submitted On 11-FEB-2003
M.R.Atkinson
Further to rule 1.
while a agree that a sufficiently cleaver JVM could unlearn
all of its optimisations when an extension class for a
(non-final) immutable was loaded, this would cause lots of
unnecessary complications. There are I believe relatively
few cases where immutable classes can be extended without
significantly changing the semantices of most instance methods.
Further to point 3.
I mean instance fields. The lazy intialisation of volatiles
is a performance improvement (which I think works).
Further to rule 4.
final long x;
ThreadStartedInConstructor() {
new Thread() {
public void run() {
System.out.println(x); // what gets
written out
}
}.start();
x = 1;
}
}
The rule about threads is necessary to avoid half
constructed immutables being visible outside the constructor.
Further to rule 5.
Perhaps a different version of this rule and rule 4. could
be that all fields have to be initialised before methods or
fields of other classes are accessed. I think something like
rules 4&5 are necessary as there seems little point in
adding immutables to the language and them allowing them to
be mutable!
Further to rule 7.
Cloneable makes no sense to implement as the clone will be
indistinguishable from the original. Clones also do not make
sense for a lightwieght class (whose objects may be copied
rahter than passed by reference). Any immutable class that
implemented Cloneable would indicate a conceptual error on
the part of the programmer, as it is easy for a compiler to
pick up then this should be flaged as an error.
Futher to rule 8.
Thinking about it I think rule 8 should be:
8. If they implement Serializable then their
readObject() or readResolve() implementations (if
present) must obey the same rules as for
constructors.
This is because readObject() and readResolve() are in effect
constructors, and could allow partially constructed
immutables to escape, leading to them being visibly mutable
to other classes.
Futher to rule 9.
Copy constructors indicate a conceptual error on the part of
the programmer and should not be allowed. I agree that a
programmer could fool the compiler if he tried hard enough
but that is no reason for the compiler not to check for the
common mistake.
Submitted On 02-MAR-2003
hlovatt
Reply to Mark Atkinson
I think your rule 2 should say:
All instance fields are final.
Your rule 4 example is subtly different than the previous
example you gave of a thread starting in the constructor.
The previous example changed x, which wasn't possible
because x would be final. The new example simply has this
exposed by using an inner class. Another example of
exposing this is:
package junk;
final class CopyInConstructor {
final long x;
final Copy copy;
CopyInConstructor() {
copy = new Copy( this );
x = 1;
}
void print() {
System.out.println( copy.x );
}
public static void main( final String[] notUsed ) {
new CopyInConstructor().print();
}
class Copy {
final long x;
Copy( final CopyInConstructor original ) {
x = original.x;
}
}
}
The exposure of this in a constructor is a general problem
in Java and isn't easy to stop. I suggest we let
immutables behave like the rest of Java and live with
the problem.
Submitted On 14-DEC-2003
Mike_Atkinson
It looks almost certain now that immutable will not be in
JDK 1.5 as a keyword, and probably not as a marker
interface. It may not yet be too late to include it as an
attribute.
Further to the above discussions, immutables may also be
used in place of "struct" as long as only primitive types
are used and they are packed
Submitted On 08-JAN-2004
hlovatt
Reply to Mike Attkinson - Yes to both your points
Submitted On 19-FEB-2004
hwc
This sounds like a job for annotations! Instances of an
@Immutable class would behave rather like primitives:
they could not change their value after they had been
constructed and they would have no identity. Each field
would be static or final instances of a primitive or
@Immutable type. @Immutable classes should not
be Runnable and should not include synchronized
methods.
Variables holding a [reference to] an @Immutable
need not be final, although they often would be, but
they should always be initialised. This presents an
interesting problem: existing classes that followed the
rules given above might be annotated @Immutable
and re-compiled without breaking existing code but it
might not be possible to re-compile that code if, for
example, lazy initialisation is used.
I suspect @Immutable classes should be final,
otherwise they should be considered abstract. If they
extend anther class, that class should obviously obey
all the rules given above.
An array of @Immutable values would not itself be
immutable. Its elements could never be null, though,
which suggests the need for a default constructor,
perhaps machine generated. Immutable arrays would
also be useful, either using the Array< Type > syntax or,
perhaps, @Immutable Type array[]. Elements of an
immutable array should themselves be primitive or
@Immutable.
It should be possible to cast any @Immutable to an
Object. In many cases this would be a no-op but if the
JVM stored small @Immutables on the stack, they
would need to be copied. Casting an Object back to
an @Immutable would almost always involve a
defensive copy.
The compiler should be able to generate equals()
[instances of the same type and all member variables
equal] and hashCode(). The user would normally
implement toString(). I imagine clone() and a copy
constructor may be useful in some circumstances for
auto-boxing and casting from Object repectively.
There should also be an @Immuatable Complex type,
an ImmutableDate, auto-boxed to java.lang.Date, and
a StringLiteral, auto-boxed to String, StringBuffer or
whatever. Ideally, string constants would be
StringLiterals. A Pair or Tuple class would be nice,
too. I think it’s too late for Integer and Double – making
them @Immutable would break too much existing
code and new classes would offer little benefit over the
primitive types unless the rules for auto-boxing
generics changed (C# seems to have the advantage
here).
Comparison using == would give undefined results.
Ideally the compiler should flag up an error, e.g.
String input = keyboard.readLine();
if( input == “yes” ) // a common mistake, might be
spotted by the compiler
However, auto-boxing might kick in at this point. The
rules would have to be defined very carefully so that the
program behaved consistently, whether or not the JVM
optimised the storage of small objects. I’m not sure I
like the idea of == becoming equals() behind the
scenes but at least that would be predictable.
Like many of the ideas introduced in Java 1.5, these
changes in themselves should make life a little easier
for the programmer, reducing common causes of
error: objects cannot change behind your back, local
method calls become more like RMI etc. However, as
others have pointed out, they also facilitate better code
optimisation and that must be a good thing.
Submitted On 20-FEB-2004
Nicholas.Goodwin
Although not strictly necessary, I would like immutable
classes to act as values in JAX-RPC. They would have
a public default constructor and not implement
java.rmi.Remote - OK so far, but they might have final
public fields and they would not have setter methods
for their private fields. Is there any way of reconciling
these differing requirements?
The problem would Its fields must be JAX-RPC
supported types. Also, a public field cannot be final or
transient, and a non-public field must have the
corresponding getter and setter methods.
Submitted On 08-MAR-2004
hlovatt
Reply to hwc
Sorry fpor not replying sooner - bug watch failed yet again!
I think on balance that sticking with an interface is
better for immutables because expressions like:
x instanceof Immutable
class ImmutableCollection< E extends Immutable > ...
Are natural for an interface but not for an attribute.
I agree with all your comments re. boxing, ==, etc.
Submitted On 08-MAR-2004
hlovatt
I have written a compiler available for download that
includes immutables in addition to other features and uses
the concept of a marker interface to identify an immutable.
It is a free download from pec.dev.java.net under the Lesser
GNU Public Licence. Let me know what you think.
Submitted On 08-MAR-2004
hlovatt
Reply to Nicholas.Goodwin
Unfortunately I can't see how you can introduce an immutable
that is compatible with many existing classes, you sight
JAX_RPC and hwc has mentioned Date for example.
I think it is necessary to have a two stage process, first
to introduce the new feature and then to change existing
classes/APIs. This for example is the process for generics,
when introduced in 1.5 we will only have the collections API
that uses them even though many other API could benefit.
Submitted On 11-MAR-2004
hlovatt
Reply to Mike_Atkinson
Thanks for your posting. I agree that exposing the this
pointer during construction is a bad idea but I don't know
the best way to stop it, there are so many different ways of
exposing it! I have given this subject some thought and the
best I have come up with is that the programmer isn't
allowed to write constructors at all, the compiler supplies
a protected one that simply initializes the final fields
from arguments. Then the programmer writes static factories.
As you point out in your post it was a while ago since we
discussed this and I re-read your original post. Whilst I
think we have broad agreement in many areas some of the
details that you wish are not quite the same as myself. I
have written a compiler that enforces one form of immutable.
You can doenload it under LGPL from pec.dev.java.net. An
interesting feature of this compiler is that you can write
your own immutable, the compiler is extendable. IE you can
take my Immutable and modify it to be Mike's immutable. You
might like to give it a try.
Submitted On 11-MAR-2004
Mike_Atkinson
hlovatt, its now a year since our long discussion on
immutables. The new Memory Model spec (JSR 133) says (secion 9):
"Objects that have only final fields and are not made
visible to other threads during
construction should be perceived as immutable even if
references to those objects are
passed between threads via data races.
– Storing a reference to an object X into the heap during
construction of X does
not necessarily violate this requirement. For example,
synchronization could ensure
that no other thread could load the reference to X during
construction.
Alternatively, during construction of X a reference to X
could be stored into another
object Y ; if no references to Y are made visible to other
threads until after
construction of X is complete, then final field guarantees
still hold."
For immutable object (that may be passed by value, but
needn't, the decission should be up to the JVM)
synchronization cannot be used. So to make objects immutable
really does require that partially constructed instances are
not visible outside the thread that is creating them.
Submitted On 24-AUG-2004
PtrBckr
I recently found an experimental programming language which does a distinction between "state objects" and "value objects", which is very much like the distinctions made here and definitely something I'd like to see. There is an overview of the two types here: http://lavape.sourceforge.net/doc/html/TwoObjectCategories.htm
I find this distinction quite useful and I agree that something along these lines would be far better than having "const" and/or "struct".
I'd even scrap equals() and hashCode() for it, if I could :-) At the moment it is just a potential cause of pain when equals() is overridden on a mutable object. Value identity and mutability don't agree.
Submitted On 29-AUG-2004
hlovatt
PtrBckr ,
Thanks for the URL. The Lava proposal is definately along the same lines:
1. Their State objects are normal Java objects
2. Their Value objects are similar to the immutable objects proposed in this RFE
They don't have the concept of a companion mutable class to the immutables and they don't have conversions between mutable and immutable forms.
You can download, under the GNU LGPL, a compiler I have written that conforms closely to this proposal:
http://pec.dev.java.net/
In the compiler whose URL is given above the mutable types are called Value types; the name change was to be more consistent with standard terminology, in particular the term Value semantics appears in Computer Science literature.
Submitted On 09-NOV-2004
lucretiussecundus
hlovatt, great that you've done an implementation of your ideas. But I find your choice of name 'Value' for mutable types very confusing. Java's primitive types are normally spoken of as 'values', but they are immutable.
Maybe you should steer clear of the word 'value' altogether since it's ambiguous?
Submitted On 14-NOV-2004
hlovatt
lucretiussecundus,
Any suggestions for an alternative name? I used Value
because it is often used in terms like Value Semantics,
Lightweight Value Semantic Objects, etc.
Just a point about primitives, you can
Submitted On 14-NOV-2004
hlovatt
Cont. - the posting got cutt off!!!!
Just a note about primitives, you can't tell if a primitive is a value type of an immutable in Java because you can't take the address and System.identityHashCode doesn't work for them.
If you use my compiler let me know how you get on.
Cheers.
Submitted On 12-DEC-2004
lucretiussecundus
hlovatt - maybe simply use the term
Submitted On 27-JAN-2005
gbishop
Why should == be an error or always return false? What if I am comparing a bunch of things, some immutable, some not?
I don't like all these unnecessary restrictions. This proposal now that ti's been batted back and forth seams to me to be overly complicated.
I don't like the conversions classes. I don't like restrictions on subclassing either. This looks more and more like a glorified version of interface where all the members are public static final values.
Submitted On 27-JAN-2005
hlovatt
The reason for making == an error is so that this request for an enhancement doesn't get bogged in the operator overloading debate. You could map == to equals for immutables but the concensus in this forum was that it would be better to simply ban == on immutables. Note the default comparison of == based on memory address is almost certainly not what you want, because it will be inconsistent with equals for an immutable, so it is better to ban == than allow the default ==.
The reason for the design with an immutable and a mutable (value) version is for efficiency. If you don't need the mutable version then don't use it. Just use the immutable version. Or don't make the mutable version public.
The reason for converting between the immutable and mutable versions by copying is to avoid the problems associated with const aliasing in C++. The immutables here really are immutable, they do not change and nothing can change them.
Submitted On 20-OCT-2006
pron
This is one of the best RFE proposals I have seen. It is an excellent compromise between enhancement and compatibiliy, and it essentially provides lightweight objects without being a heavyweight change to the language.
If accepted, this RFE may open a whole new horizon to high-performance numeric computation in Java. Finally, we could have decent arrays of complex numbers.
+1
Submitted On 25-OCT-2006
hlovatt
Thanks for you supportive comments. Incidentally complex numbers was one of my motivations for posting this RFE.
Submitted On 26-JAN-2007
By the Liskov Substitution Principle, a List<Integer> is not a subtype of List<Number>. But what about: ImmutableList<Integer> and ImmutableList<Number>? It seems reasonable to me that a singly-linked list could sensibly be implemented as immutable, and it would be nice if such an immutable list would allow more flexible typing.
Submitted On 01-FEB-2007
hlovatt
I agree with the above post, it would be nice if the typing understood immutable and could reason that this was safe since the list itself is immutable.
As an aside I have mixed feelings about this behaviour of generics, in particular I can't recal this ever been a problem for me with arrays and the restriction has been a pain in some code.
Submitted On 11-JUN-2007
Support.
In writing a Java based database, we have had significant pains in the ToMutable use case, which happens when we modify an object as part of *some* write transactions.
Language support would definitely ease our pain.
Submitted On 29-JUN-2007
hlovatt
You can download, under the GNU LGPL, a compiler I have written that conforms closely to this proposal:
http://pec.dev.java.net/
In the compiler whose URL is given above the mutable types are called Value types; the name change was to be more consistent with standard terminology, in particular the term Value semantics appears in Computer Science literature.
Submitted On 12-AUG-2007
I frequently want to create class hierarchies of immutable objects. I don't want such a feature to make all immutable classes final. I do think that any subclasses of an immutable object also need to be immutable, though.
PLEASE NOTE: JDK6 is formerly known as Project Mustang
|