V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
Newyorkcity
V2EX  ›  问与答

Java 中,一个 final 的 List 变量,被往里加了元素,算不算违反了 final 这个关键词在这里暗示的约定?

  •  
  •   Newyorkcity · 147 天前 · 3491 次点击
    这是一个创建于 147 天前的主题,其中的信息可能已经有所发展或是发生改变。
    final 关键词在 java 中,只能保证变量的值不变,但如果变量的值是一个本身提供了修改自身的方法的对象,那调用这一方法对值(内部)做修改,final 是约束不到的。

    然而这样的修改,是否是违背了 final 这个关键词放在这里时暗示的约定——该变量是不可变的?或者说,如果让一个全知全能的家伙来编写 Java ,它是否会为 final 所修饰的变量保证无论如何都无法被修改而不仅仅只是引用无法被修改的限制?(也就是说,java 之所以不能实现到这一步,是否可能是因为在编译器层面强制实现这一程度所需要的工程量太大划不来)

    谢谢
    第 1 条附言  ·  147 天前

    大家都认可,对于 final int a = 0,a 变为 1 是不可接受的。

    那为啥 final AtomicInteger a= new AtomicInteger(0) 的 a,由 0 变 1 是没有违背 final 所暗示的语义呢?

    Java编译器没有对此报错,是因为(A)在设计时就不觉得要在这里报错,还是(B)要实现这一程度的检查的工作量太大,这里留下final关键词做语义上的暗示算了呢?

    第 2 条附言  ·  147 天前
    感谢 V 友提供的官方文档

    https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.4
    If a final variable holds a reference to an object, then the state of the object may be changed by operations on the object, but the variable will always refer to the same object.

    就是我也学过一点 C++,所以知道指针,引用这些东西,但我印象里 Java 是不谈指针和引用的。因而我怀疑 java 的 final 是定在值上的,值不变的意思更应该是值的内容不变。

    那官方的文档就很好地否定了我的想法。
    55 条回复    2022-01-01 14:10:23 +08:00
    mineralsalt
        1
    mineralsalt  
       147 天前
    final 是限制变量不能被再次赋值, 而修改变量内部数据不属于 final 的管辖范围, 这根本就是两码事, 你要限制数组不能改动 , 可以用别的方法
    aptupdate
        2
    aptupdate  
       147 天前 via iPhone
    我也想过这个问题,但是没想通。
    dqzcwxb
        3
    dqzcwxb  
       147 天前
    不违反,你改的是字段不是当前这个对象你把这俩分开想就行
    banmuyutian
        4
    banmuyutian  
       147 天前
    final 只是限制不能再次赋值,但是对象内的行为是管不到的,如果你不想 List 被修改可以用 ImmutableList
    wolfie
        5
    wolfie  
       147 天前
    final 项目经理 = 张三。
    张三将任务分配给 李四。
    张三将任务重新分配给 王五。
    Newyorkcity
        6
    Newyorkcity  
    OP
       147 天前
    @wolfie 你这个比喻张三做了事但没有影响到他本身。。但如果
    张三.去洗脚店()
    张三.被逮捕( 10 年)
    你看,这个张三是不是就没法继续当项目经理了?
    Cabana
        7
    Cabana  
       147 天前
    你需要的是 ImmutableList
    misdake
        8
    misdake  
       147 天前
    想要内部不可变,java 的话就改用不可变对象,或者对外只暴露不可变接口。反正是可以做到,但可能费一些功夫去写接口。我在一定程度上赞同写一套只读接口,再 extend 出一个读写接口,有点啰嗦,但比较规整。
    otakustay
        9
    otakustay  
       147 天前   ❤️ 3
    final 和 immutable 是不一样的概念
    seanzxx
        10
    seanzxx  
       147 天前   ❤️ 9
    final 是保证变量的值不变,但什么是变量的值你没清楚

    final int a = 3; // a 的值是 3 ,你不能再修改 a 的值
    a = 4; // 编译错误: cannot assign a value to final variable a

    final List b = new ArrayList(); // b 的值是 List 对象的地址(引用),你不能改变 b 的值
    b= new LinkedList(); // 编译报错:cannot assign a value to final variable b
    Newyorkcity
        11
    Newyorkcity  
    OP
       147 天前
    @seanzxx 一个数组,在 A 时间点,里面啥元素点也没有,在 B 时间点,有了几个元素在里面。这个数组是否发生过变化?不纠结于数组,一个人,A 时间点身高一米七,B 时间点身高一米八,这个人算变化了吧?
    agagega
        12
    agagega  
       147 天前 via iPhone
    因为你这里的 List 不是值语义
    Vegetable
        13
    Vegetable  
       147 天前   ❤️ 3
    final 和 readonly/mutable 其实是不同层面的概念。在扩大解释之前应该慎重,不要想当然。
    aragakiyuii
        14
    aragakiyuii  
       147 天前
    @Newyorkcity #11 身份证号没变
    yangzzzzzz
        15
    yangzzzzzz  
       147 天前
    张三吃胖了还是张三
    Kaciras
        16
    Kaciras  
       147 天前
    final 修饰的是指针本身,不是所指的对象。
    kop1989
        18
    kop1989  
       147 天前
    @Newyorkcity #11 但 final ,其实指的是户口本上的这个“人”与生物学上的“人”的绑定关系。虽然你的体态、甚至性别都变了,你的户口本依然是你的户口本。
    mineralsalt
        19
    mineralsalt  
       147 天前
    那为啥 final AtomicInteger a= new AtomicInteger(0) 的 a ,由 0 变 1 是没有违背 final 所暗示的语义呢?

    你这种描述根本就不对, a 既不是 0 也不是 1, 它是一个对象, 只要这个对象的指针不改变, 那就没有违反 final
    wolfie
        20
    wolfie  
       147 天前
    @Newyorkcity #6
    final 就是不存在张三被换掉的可能。

    程序角度 final 限制栈修改,实际堆中的对象里面的成员是否被替换 管不着。


    class 项目经理 {

    实际干活的 = 李四、王五; // 这个指派张三为项目经理的人管不着。加不加 final 由张三决定。

    }
    seanzxx
        21
    seanzxx  
       147 天前   ❤️ 2
    @Newyorkcity final 和 immutable 是不一样的
    final 是指这个变量不能再赋值,也就保证了这个变量的值不会改变
    引用变量,他的值是地址,你改变的是地址指向的内存中内容

    举个例子,公司的员工管理系统保存了你的家庭地址,但你做的是改变你家的家具。
    如果你的家庭地址是 final ,你就不能再搬家了,但你怎么改变你家的布置是没有限制的
    geekfxxk
        22
    geekfxxk  
       147 天前   ❤️ 1
    值类型,引用类型没理解好
    yolee599
        23
    yolee599  
       147 天前
    假设有一个“人”,用来找到这个“人”的关键字叫“身份证号”,现在我们定义“身份证号” 是 final 的,一经赋值就不会变了,无论你身高怎么变,体重怎么变,年龄怎么变,性别怎么变。“身份证号”还是那个“身份证号”。

    现在把上面例子的“身份证号”替换为“对象”,是不是就好理解多了。
    Asan
        24
    Asan  
       147 天前
    final 不能修改改的是引用,但是引用指向的内存是可以涂涂改改的
    AoEiuV020CN
        25
    AoEiuV020CN  
       147 天前
    这种问题学 c 语言的时候讲的比较清楚,因为不搞清楚真的容易炸,
    指针不变和内容不变,两码事,基本类型不是指针,不存在指针不变的用法,引用类型指针不变情况内容变没问题,
    qwe520liao
        26
    qwe520liao  
       147 天前   ❤️ 1
    一个变量到底存储的是什么?变量类型指明了存储的数据如何使用,对于一个 int 类型的变量来说,存储的就是实际的数字。对于对象来说,存储的就是一个内存地址,它被解释为具体类型在内存中的起始地址加上成员变量类型偏移量。以上这些只是一些表面的解释,编译器或者解释器 /虚拟机会根据这些信息来优化,并最终操作计算机。

    所以 final 修饰的是变量值不可改变,而不是这个值再被用来解释翻译,并操作其他内存的数据不可改变。
    dcsuibian
        27
    dcsuibian  
       147 天前
    “final AtomicInteger a= new AtomicInteger(0) 的 a ,由 0 变 1”
    a 既不是 0 也不是 1 ,而是一个 AtomicInteger 实例,一直都是这个实例,没有变过
    如果我写了一个 final CustomClass c=new CustomClass(arg1,arg2,arg3)
    那么我的 c 就一直是这个 CustomClass 啊,你把 AtomicInteger 和数字直接连起来了,但其实它们是分开的
    Jooooooooo
        28
    Jooooooooo  
       147 天前
    final 设计就是如此.
    Shawlaw
        29
    Shawlaw  
       147 天前 via iPhone
    对象时,final 的是“指针”,定义如此。
    xiao109
        30
    xiao109  
       147 天前
    说明你还是没弄清 java 里基础类型和引用类型的区别
    xiao109
        31
    xiao109  
       147 天前
    简单一点理解就是 final 修饰的变量不能再用=操作符去操作了。
    adeng
        32
    adeng  
       147 天前 via Android
    @Newyorkcity 你有问题,你在按照自己的想法去套,seanzxx 说的很清楚了。你说的“一个数组”,这个数组要有名字,定为 arr ,arr 指向 内存地址 addr1 ,你所谓的“A 时间点”,addr1 啥元素也没有;“B 时间点”有几个元素,addr1 没变成别的啊,addr1 内存还是那块内存,没变成 addr2 内存。arr = addr1 ,始终没有变。
    kera0a
        33
    kera0a  
       147 天前
    楼主肯定明白值类型和引用类型,楼主应该说的是 final 这个概念,应该是只针对指针值不变,还是把指针指向的对象也保护不变。java 中对此的定义是只是保证值不变。

    但有些语言例如 swift 的 Array , 使用 let 修饰就会保护 Array 对象不变
    kratzer
        34
    kratzer  
       147 天前
    final 证件号=你
    kratzer
        35
    kratzer  
       147 天前
    证件号就不能再指定给她 /他了
    你可以变胖,减肥,变形
    Leviathann
        36
    Leviathann  
       147 天前
    swift 是这样的
    yaphets666
        37
    yaphets666  
       147 天前
    list 有个地址对吧,有一个变量 a 存储着这个 list 的地址, 你 final 的是变量 a ,不是 list 。
    cndotaer
        38
    cndotaer  
       147 天前   ❤️ 1
    https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.4
    If a final variable holds a reference to an object, then the state of the object may be changed by operations on the object, but the variable will always refer to the same object.
    dinghmcn
        39
    dinghmcn  
       147 天前
    cpp 双 const 能保证值和引用都不能改变,java 其实模糊了值和引用才导致这种理解的偏差
    nonoyang
        40
    nonoyang  
       147 天前
    java 归根到底还是值传递,从这个角度看这个 final 没啥问题,说不定未来 jdk20 就支持了呢。
    meeop
        41
    meeop  
       147 天前
    你说的对,final 和浅拷贝一样,都只管变量引用本身,不管更深层次的变量持有数据,纯粹从语义上看是效果是不对的

    不过,毕竟编程语言是需要和性能妥协的,深层次不可变约束会引入更多性能成本,也许未来的编程语言能支持上
    otakustay
        42
    otakustay  
       147 天前
    我还以为楼主在纠结 final 的自然语义,结果发现他被程序语义说服了……
    leaves615
        43
    leaves615  
       147 天前
    final int a = 0; //值赋值
    final AtomInteger a = new AtomInteger(0); //引用对象(也就是指针)赋值
    final 修饰符是用来标识变量赋值后不能进行重复赋值
    final int a 只能赋值一次。
    而 final AtomInteger a 也是赋值一次(指针赋值)
    sdushn
        44
    sdushn  
       147 天前
    final 是对 对象地址的限定。
    final int a = 0 ,a 变为 1 是不可接受的。并非是值的变化不可接受,而是 a 对象地址不能变,如果 值 0 所在的地址可以变更为 值 1 ,那么 a 的值也可以从 0 变 1 。
    kamal
        45
    kamal  
       147 天前
    看了大家的解释,感觉跟 ES 语法的 const 一个意思。
    Greatshu
        46
    Greatshu  
       147 天前
    折腾一下 C 指针就明白了
    GrayXu
        47
    GrayXu  
       147 天前
    @Newyorkcity 不是,到底是谁和你说 java 没有引用的。。。
    moonmagian
        48
    moonmagian  
       147 天前 via Android
    cpp 为了区分这种区别有 logical 和 physical 的 const ,physical const 是指值本身的不变性,而 logical const 指从引用 /指针的视角看指向的对象时的不变性(尽管这个对象可能并不是 physical const )。
    java 对引用类型变量的 final 声明更类似 cpp 中的 physical const (指针本身的不变性,T* const ),而不是 logical const (指针指向对象从指针视角看的不变性,const T*),只是 final 对值类型的变量有特殊的逻辑( const T )
    SingeeKing
        49
    SingeeKing  
       147 天前 via iPhone
    其实就是设计缺陷吧,例如 Rust 就增加了 mut 关键字来专门标识是否允许修改
    wiix
        50
    wiix  
       147 天前
    Collections.unmodifiableList(list)
    List.of(list.toArray(new String[]{}))
    Dragonphy
        51
    Dragonphy  
       147 天前
    这个我记得属于 JVM 的范畴,可以看看相关书籍技能
    bsg1992
        52
    bsg1992  
       147 天前
    完全不一样的 final 修饰的是 内存引用不能改变。 你 add 没有改变内存指向。
    season8
        53
    season8  
       147 天前
    List 集合 -->火车见过吧,就比作火车好了
    final List --> D3062 次列车就是这个编号 xxx 的车,不能用别的车
    每天坐的人不一样能说 火车变了吗,火车还是那个火车。
    其实就是一个概念的问题,你认为 一个 List 就应该是 List + 它的元素 ,但很明显 Java 设计的就是 List 只是容器,你说的这层 “final” 只能你自己去实现。

    了解概念区别就行了,再探究下去感觉没啥意义。
    Cbdy
        54
    Cbdy  
       147 天前 via Android
    totoro52
        55
    totoro52  
       146 天前 via iPhone
    一个杯子 装了水,你倒了,换成了可乐,你能说这个杯子的物理性质发生了变化吗
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2138 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 01:12 · PVG 09:12 · LAX 18:12 · JFK 21:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.