73

通过使 ArrayList(或其他集合)最终化,我们可以获得哪些优点/缺点?我仍然可以向 ArrayList 添加新元素、删除元素并对其进行更新。但是最终的效果是什么?

4

13 回答 13

138

但是最终的效果是什么?

这意味着您不能重新绑定变量以指向不同的集合实例

final List<Integer> list = new ArrayList<Integer>();
list = new ArrayList<Integer>(); // Since `list' is final, this won't compile

作为风格问题,我将大多数我不打算更改的引用声明为final.

我仍然可以向 ArrayList 添加新元素、删除元素并对其进行更新。

如果您愿意,可以使用以下方法防止插入、移除等Collections.unmodifiableList()

final List<Integer> list = Collections.unmodifiableList(new ArrayList<Integer>(...));
于 2012-05-25T08:10:01.340 回答
16

这只是意味着您不能重新分配其参考。尝试执行以下操作将导致编译器错误。

final List<String> list = new ArrayList<String>();

list = new LinkedList<String>();
     ^
     Compiler error here

如果你真的想要一个不可变的列表,你应该使用该Collections.unmodifiableList()方法。

于 2012-05-25T08:10:42.417 回答
8

例如,您将无法修改其引用new ArrayList

于 2012-05-25T08:11:16.707 回答
5

制作变量final可确保您在分配后无法重新分配该对象引用。正如您提到的,您仍然可以使用该列表方法进行更改。

如果您将final关键字与Collections.unmodifiableList的使用结合起来,您将获得您可能试图实现的行为,例如:

final List fixedList = Collections.unmodifiableList(someList);

fixedList这导致无法更改指向的列表。但是请注意,它仍然可以通过someList引用进行更改(因此请确保在此分配之后它超出范围。)

于 2012-05-25T08:19:21.230 回答
5

final在多线程下有很多后果。

  1. JMM 明确定义了一个final字段的初始化完成是有保证的。

没有明确定义的是:

  1. 编译器可以自由地跨内存屏障重新排序它们。
  2. 编译器总是可以读取缓存的副本。
于 2016-08-27T23:01:21.023 回答
4

正如您正确观察到的那样,它不会影响您可以使用 ArrayList 做什么 - ArrayList 本身仍然是可变的。您刚刚使引用不可变。

但是制作变量 final 确实有其他好处:

  • 如果期望变量保持不变,它可以防止变量被更改。这可以帮助防止未来的错误。
  • 制作变量final可以帮助编译器进行某些性能优化。

一般来说,你使不可变的东西越多越好。因此,使引用成为最终引用(即使它们是对可变对象的引用)通常是一个好主意。

于 2012-05-25T08:15:09.173 回答
1

final是Java中的关键字或保留字,可以应用于Java中的成员变量、方法、类和局部变量。一旦你做了一个最终引用,你就不能改变那个引用,如果你尝试在 java 中重新初始化最终变量,编译器将验证这一点并引发编译错误。

于 2012-05-25T08:13:15.133 回答
1

正如 aix 所说,您不能将其重新绑定到不同的集合。

示例:结合不可变列表实现,您可以获得可以公开的安全成员。

示例:当您依赖该引用不会更改时,您需要 final。例如,在同步场景中就是如此。

可能还有更多的例子。如果您根本不打算更改引用,则将成员声明为 final 是一种很好的风格。

于 2012-05-25T08:17:56.610 回答
1

我想到了同样的问题,并编码和示例以从另一个角度添加到解释中。

最终的arrayList仍然可以修改,参考下面的示例并运行它自己看看。

这是具有不可变 List 声明的不可变类:

public final class ImmutableClassWithArrayList {
 final List<String> theFinalListVar = new ArrayList<String>();
}

这是驱动程序:

public class ImmutableClassWithArrayListTester {
public static void main(String[] args) {
    ImmutableClassWithArrayList immClass = new ImmutableClassWithArrayList();
    immClass.theFinalListVar.add("name");
    immClass.theFinalListVar.forEach(str -> System.out.println(str));
 }
}

如您所见,主要方法是添加(修改)列表。所以唯一要注意的是,对集合类型对象的“引用”不能重新分配给另一个这样的对象。正如上面 adarshr 的回答,你不能做 immClass.theFinalListVar = new ArrayList(); 在这里的主要方法中。

修改部分确实帮助我理解了这一点,并希望它以同样的方式有所帮助。

于 2018-04-18T03:06:10.370 回答
1

要获得一个真正不可变的列表,您必须对列表的内容进行深度复制。UnmodifiableList 只会使引用列表有些不可变。现在,随着大小的增加,制作 List 或数组的深层副本将很难占用内存。您可以利用序列化/反序列化并将数组/列表的深层副本存储到临时文件中。setter 将不可用,因为成员变量需要是不可变的。getter 会将成员变量序列化到文件中,然后对其进行反序列化以获取深层副本。序列化具有进入对象树深处的先天性质。不过,这将以一定的性能成本确保完全不变。

 package com.home.immutable.serial;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public final class ImmutableBySerial {

    private final int num;
    private final String str;
    private final ArrayList<TestObjSerial> immutableList;

    ImmutableBySerial(int num, String str, ArrayList<TestObjSerial> list){
        this.num = num;
        this.str = str;
        this.immutableList = getDeepCloned(list);
    }

    public int getNum(){
        return num;
    }

    public String getStr(){
        return str;
    }

    public ArrayList<TestObjSerial> getImmutableList(){
        return getDeepCloned(immutableList);
    }

    private ArrayList<TestObjSerial> getDeepCloned(ArrayList<TestObjSerial> list){
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        ArrayList<TestObjSerial> clonedObj = null;
        try {
             fos = new FileOutputStream(new File("temp"));
             oos = new ObjectOutputStream(fos);
             oos.writeObject(list);
             fis = new FileInputStream(new File("temp"));
             ois = new ObjectInputStream(fis);
             clonedObj = (ArrayList<TestObjSerial>)ois.readObject();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return clonedObj;
    }
}
于 2017-05-27T07:13:03.417 回答
1

我个人标记了我的类的集合字段,final以使我的类的用户免于检查它是否为空。这是有效的,因为一旦该值已经分配给最终变量,它就永远不能重新分配给另一个值,包括 null。

于 2016-06-15T02:22:34.360 回答
0

如果我们将 ArrayList 声明为 final,我们仍然可以向它添加元素,但我们不能重新分配它。

final ArrayList<String> list = new ArrayList<>();
        list.add("abc"); //allowed
        list.add("pqr"); //allowed

list = new ArrayList<>(); // Not allowed
于 2021-08-12T19:26:47.823 回答
0

基本上你在这里想要实现的是使列表不可变我猜。但是,当您将列表引用标记为 final 时,这意味着该引用不能指向除此之外的任何其他列表对象。

如果你想要一个最终的(不可变的)ArrayList,请使用 Collections 类的实用方法 Collections.unmodifaibleList(list)。

于 2017-08-06T12:51:06.443 回答