Why final keyword is necessary for immutable class

2019-01-16 15:08发布

Could you please clarify that why final keyword is required before class when we are making it an immutable one. I mean, if we declare all of it's attributes as private and final, then also it is an immutable class, isn't it?

Sorry if the question seems easy, but i am truly confused about it. Help me out.

Editted: I know that a class declared final can't be subclassed.. But if each attribute is private and final then what difference does that make?

7条回答
Explosion°爆炸
2楼-- · 2019-01-16 15:22

If an immutable class Foo is sealed ("final"), then anyone who receives a reference to a Foo may be assured that if Foo was implemented correctly, the referenced instance will in fact be immutable. If an immutable class is not sealed, then someone who receives a reference to a Foo may be assured that if the actual class of of the referenced object (which may be Foo or some derivative type implemented by some arbitrary unknown person) was implemented correctly, the instance will be immutable. Leaving Foo unsealed means that anyone who relies upon Foo to be immutable will have to trust that everyone who writes a class that derives from Foo will implement it correctly. If one wants to be certain that every reference to a Foo will in fact target an immutable instance without having to rely upon the authors of derivative classes to abide by the contract, making Foo final can aid in such assurance.

On the other hand, the possibility that a class might derive from Foo but violate its immutability isn't terribly different from the possibility that a class which derives from any other class might violate the contracts of its parent class. Any code which accepts a reference of any type which can be subclasssed by outside code might be given an instance of a subclass which violates its parent's contract.

The fundamental question when deciding whether an immutable class should be sealed is the same as for any other class: whether the benefits of leaving the type unsealed outweigh any dangers that would be posed by doing so. In some cases, it may make sense to have an extensible immutable class, or even an abstract class or interface whose concrete implementations are all contractually obligated to be immutable; for example, a drawing package might have an ImmutableShape class with some concrete fields, properties, and methods to define 2D transformations, but an abstract Draw method, allowing for the definition of derivative types ImmutablePolygon, ImmutableTextObject, ImmutableBezierCurve, etc. If someone implements an ImmutableGradientFilledEllipse class but fails to have that type make its own copy of a mutable GradientColorSelector, the colors of gradient-filled polygons might change unexpectedly, but that would be a fault of the ImmutableGradientFilledEllipse class, and not the consuming code. Despite the possibility of a broken implementation failing to uphold the "immutability" contract, an extensible ImmutableShape class would be much more versatile than a sealed one.

查看更多
何必那么认真
3楼-- · 2019-01-16 15:23

An immutable object is an object which state is guaranteed to stay identical over its entire lifetime. While it is perfectly possible to implement immutability without final, its use makes that purpose explicit, to the human (the software developer) and the machine (the compiler).

Immutable objects carry some very desirable characteristics:

they are simple to understand and easy to use
they are inherently thread-safe: they require no synchronization
they make great building blocks for other objects 

Clearly final is going to help us define immutable objects. First in labelling our object as immutable, which makes it simple to use and understand by other programmers. Second in guaranteeing that the object's state never changes, which enable the thread-safe property: thread concurrency issues are relevant when one thread can change data while another thread is reading the same data. Because an immutable object never changes its data, synchronizing access to it is not needed.

Create an immutable class by meeting all of the following conditions:

Declare all fields private final.
Set all fields in the constructor.
Don't provide any methods that modify the state of the object; provide only getter methods (no setters).
Declare the class final, so that no methods may be overridden.
Ensure exclusive access to any mutable components, e.g. by returning copies.

A class declared final cannot be sub classed. Other classes cannot extend final class. It provides some benefit to security and thread safety.

查看更多
唯我独甜
4楼-- · 2019-01-16 15:25

A class that is declared final cannot be subclassed. See also http://docs.oracle.com/javase/tutorial/java/IandI/final.html

The different semantics of all uses of the final keyword are described in the The Java Language Specification

  • 4.12.4 final Variables Page 80
  • 8.1.1.2 final Classes Page 184
  • 8.3.1.2 final Fields Page 209
  • 8.4.3.3 final Methods Page 223
查看更多
一夜七次
5楼-- · 2019-01-16 15:29

As stacker says, final makes sure the class isn't subclassed. That's important so that any code which is relying on its immutability can do so safely.

For example, immutable types (where each field is also of an immutable type) can be freely used between threads without worrying about data races etc. Now consider:

public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

That looks like you can share Person instances freely across threads with no problem. But what about when the object you're sharing is actually a mutable subclass:

public class Employee extends Person {
    private String company;

    public Employee(String name, String company) {
        super(name);
        this.company = company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    public String getCompany() {
        return company; 
    }
}

Now instances of Employee aren't safe to share between threads, because they're not immutable. But the code doing the sharing may only know about them as instances of Person... leading them into a false sense of security.

The same goes for caching - it should be safe to cache and reuse immutable types, right? Well, it is safe to cache instances which are genuinely of an immutable type - but if you're dealing with a type which itself doesn't allow mutation, but does allow subclasses, it's suddenly not safe any more.

Think about java.lang.Object. It doesn't have any mutable fields, but it's clearly a bad idea to treat every Object reference as if it's a reference to an immutable type. Basically it depends on whether you think about immutability as a property of the type or of objects. A truly immutable type declares "any time you see a reference of this type, you can treat it as immutable" - whereas a type which allows arbitrary subclassing can't make that claim.

As an aside, there's a half-way house: if you can limit the subclassing to only "trusted" places, you can ensure that everything's immutable, but still allow that subclassing. The access in Java makes that tricky, but in C# for example you could have a public class which only allowed subclassing within the same assembly - giving a public API which is nice and strong in terms of immutability, while still allowing for the benefits of polymorphism.

查看更多
Animai°情兽
6楼-- · 2019-01-16 15:39

If all public and protected methods are final and none of them allows modifying private fields, and all public and protected fields are both final and immutable, then I guess it could be said class is semi-immutable, or sort of constant.

But things break down when you create a subclass and need to override equals and hashcode. And can not because you made them final... So the whole thing is broken, so just make the whole class final to prevent programmer from being a fool by accident.

As an alternative to doing this kind of bastardized version immutability, you have several options.

If you want to attach extra data to immutable instance, use Map. Like if you wanted to add age to name, you would not do class NameAge extends String... :-)

If you want to add methods, create a class of static utility functions. That is a bit klunky, but it is the current Java way, Apache commons for example is full of such classes.

If you want to add extra methods and data, create a wrapper class with delegate methods to methods of the immutable class. Anybody needing to use the extra methods needs to be aware of them anyway, and there is not much practical difference in casting to derived non-immutable class or doing something like new MyWrapper(myImmutableObj) for many use cases.

When you really have to have reference to original imutable object (like storing it in existing class you can not change), but need the extra data somewhere, you need to use the Map approach to keep the extra data around, or something like that.

查看更多
爱情/是我丢掉的垃圾
7楼-- · 2019-01-16 15:40

You don't strictly need final to make an immutable class. i.e. you can make an immutable class without it being final.

However, if you don't make it final, then it is possible for someone to extend a class and create a subclass that is mutable (either by adding new mutable fields, or overriding methods in a way that enables you to mutate protected fields of the original immutable class). This is a potential problem - it violates the Liskov Substitution Principle, in the sense that you would expect the property of immutablity to be preserved by all subtypes.

Hence, it is usually good practice to make immutable classes final to avoid this risk.

查看更多
登录 后发表回答