为啥在java中不允许分配给'this'?

Posted

技术标签:

【中文标题】为啥在java中不允许分配给\'this\'?【英文标题】:Why is assignment to 'this' not allowed in java?为什么在java中不允许分配给'this'? 【发布时间】:2011-12-20 05:54:45 【问题描述】:

我从编译器得到的错误是“赋值的左边必须是一个变量”。我的用例是深度复制,但并不真正相关。

在 C++ 中,可以分配给*this

问题不在于如何规避对this 的分配。这很简单,但决定不将this 设为变量的原因是什么。

原因是技术性的还是概念性的?

到目前为止我的猜测 - 以随机方法重建对象的可能性很容易出错(概念上),但在技术上是可能的。

编辑请克制“因为 java 规范这么说”的变体。我想知道决定的原因

【问题讨论】:

仅仅因为在 C++ 中可以更改 this,并不意味着您应该这样做。创建一个新变量也会做同样的事情,不会造成任何混乱。 为什么投反对票?有什么不具建设性的吗? 我想知道 this = xy; 的语义应该是什么。你认为它应该怎么做? @Hanno 提供的 xy 类型正确,this 的引用将设置为 xy,使“原始”对象符合 gc 条件 同时在整个应用程序中更改所有对该对象的其他引用? - 请注意,如果 this 实际上 变量,它将是每个对象的 private 并且为其分配值对环境不可见. 【参考方案1】:

因为thisfinal

this 是关键字,而不是变量。并且您不能为关键字分配某些内容。现在考虑一下如果它是设计规范中的参考变量..并参见下面的示例

它包含对对象调用方法的隐式引用。它仅用于参考目的,现在考虑您为this 分配了一些东西,所以它不会破坏一切吗?

示例

考虑String类中的以下代码(注意:以下代码包含编译错误只是为了演示OP情况)

   public CharSequence subSequence(int beginIndex, int endIndex) 
      //if you assign something here
       this = "XYZ"  ;
       // you can imagine the zoombie situation here
      return this.substring(beginIndex, endIndex);
   

【讨论】:

就像在 C++ 中一样,也许吧?我不知道它是如何完成的,但这显然是可能的。我可以很容易地想象它不会破坏任何东西的情况。也许您可以详细说明技术方面? @JigarJoshi,你从哪里得知thisfinal?我以前从未听说过。 this 甚至不是一个变量。这是一个关键字。 @Jigar IMO 该示例不成立-在其 getter 中重新分配变量并获得意外结果是完全合法的(即使错误)。我不赞成重新分配this,只是好奇。 如果this 确实被设计为一个变量,那么您的 sn-p 将进行类型检查,并按“预期”工作。这里的问题是为什么它没有被设计成一个变量。您的示例(以及整个答案)未能完全解决这个问题。【参考方案2】:

Java 中的this 是语言的一部分,是关键字,而不是简单的变量。它是为从其方法之一访问对象而不是另一个对象而设计的。将另一个对象分配给它会导致混乱。如果你想在你的对象中保存另一个对象引用,只需创建一个新变量。

原因只是概念上的。 this 用于访问对象本身,例如在方法中返回它。就像我说的,如果你给它分配另一个引用,它会造成混乱。告诉我为什么更改 this 有意义。

【讨论】:

问题在于基本原理,而不是规格 我只能提供一个用例,而不是一个原因——使用返回副本的实用程序在复制构造函数中深度复制对象。当然你可以在 ctor 之外克隆,所以没有正当理由。我只是好奇【参考方案3】:

this 甚至不是一个变量。它是一个关键字,在Java Language Specification 中定义:

当用作主要表达式时,关键字 this 表示一个值,该值是对调用实例方法的对象(第 15.12 节)或正在构造的对象的引用

因此,这是不可能的,因为无法为 while 赋值。

【讨论】:

【参考方案4】:

在 C++ 中,可以分配给*this

是的,但您不能在 C++ 中执行 this = something,我实际上认为这与您在 Java 方面所询问的内容更接近。

[...] 决定不将 this 设为变量的原因是什么。

我会说清晰/可读性。

this 被选为保留字,可能是因为它没有作为显式参数传递给方法。将其用作普通参数并能够为其重新分配新值会严重破坏可读性。

事实上,出于这个原因,许多人认为根本不应该更改参数变量。

原因是技术性的还是概念性的?

我认为主要是概念性的。但是会出现一些技术怪癖。如果您可以将值重新分配给this,例如,您可以将实例变量完全隐藏在局部变量后面。

到目前为止我的猜测 - 以随机方法重建对象的可能性很容易出错(概念上),但在技术上是可行的。

我不确定我是否完全理解这句话,但是是的,容易出错可能是决定将其设为关键字而不是变量的主要原因。

【讨论】:

迄今为止最明确的答案。谢谢【参考方案5】:

原因是技术性的还是概念性的?

IMO,概念性的。

this 关键字是“对当前正在执行其方法的对象的引用”的简写。你不能改变那个对象是什么。它在 Java 执行模型中根本没有意义。

由于this 的更改没有意义,因此将其设为变量也没有任何意义。

(请注意,在 C++ 中,您分配给 *this,而不是 this。在 Java 中,没有 * 运算符,也没有真正的等价物。)


如果您认为您可以在中途更改方法的目标对象,那么这里有一些反问题。

这样做有什么用?这个(假设的)语言功能可以帮助您解决哪些问题......无法以更易于理解的方式解决?

您将如何处理互斥锁?例如,如果您在同步方法的中间分配给this 会发生什么......并且建议的语义是否有意义? (问题是你要么最终在一个没有锁定的对象上以同步方法执行......要么你必须解锁旧的this并锁定新的this需要。此外,就互斥锁的设计目标而言,这有什么意义?)

你如何理解这样的事情:

class Animal 
    foo(Animal other) 
       this = other;
       // At this point we could be executing the overridden
       // Animal version of the foo method ... on a Llama.  
    


class Llama 
    foo(Animal other) 
    

当然,您可以为此赋予语义,但是:

您以难以理解的方式破坏了子类的封装,并且 您实际上并没有取得任何特别有用的成果。

如果您认真地尝试回答这些问题,我希望您会得出这样的结论:实施此方法是一个坏主意。 (但如果您确实有满意的答案,我鼓励您将它们写下来并发布为您自己的问题答案!)

但实际上,我怀疑 Java 设计者是否考虑过这个想法。 (没错,IMO)


C++ 的*this = ... 形式实际上只是对当前对象属性的一系列赋值的简写。我们已经可以在 Java 中做到这一点......通过一系列正常的分配。当然不需要新的语法来支持这一点。 (一个类多久从另一个类的状态重新初始化一次?)

我注意到你是这样评论的:

我想知道this = xy; 的语义应该是什么。你认为它应该做什么? – JimmyB 2011 年 11 月 2 日 12:18

如果 xy 是正确的类型,则其引用将设置为 xy使“原始”对象符合 gc-eligible - kostja 2011 年 11 月 2 日 12 点: 24

那行不通。

    this 的值在调用方法时(有效地)按值传递给方法。被调用者不知道 this 引用来自哪里。

    即使是这样,那也只是保存引用的一个地方。除非在所有地方都分配了null,否则该对象不能进行垃圾回收。

忽略这在技术上是不可能的事实,我认为您的想法不会有用或有助于编写可读/可维护的代码。考虑一下:

public class MyClass 
    public void kill(MyClass other) 
        this = other;
    


MyClass mine = new MyClass();

....

mine.kill(new MyClass());

// 'mine' is now null!

您为什么要这样做?假设方法名称是无害的而不是 kill,您是否期望该方法能够删除 mine 的值?

我没有。事实上,我认为这是一个错误的特征:无用且危险。

即使没有这些讨厌的“使其无法访问”语义,我实际上也没有看到任何修改 this 的好用例。

【讨论】:

谢谢,斯蒂芬。编辑后,您的回答对我来说很有意义。在 C++ 中对 *this 的赋值在语义上是不同的,但我什至无法找到关于 C++ 变体的第二个和第三个问题的答案。对于第一个 - “在复制构造函数中深度复制对象,使用返回副本的实用程序”,但当然还有许多其他方法可以实现。 第二个问题;提议的语义将(我假设)是this 具有局部变量的行为,并且该语言已经允许您在非最终变量上进行同步,所以我看不出有什么意外。对于第三个问题:再一次,假设您采用该确切代码,将 this 替换为 this2,然后在顶部执行 Animal this2 = this;,然后您就有了建议的语义。 (是的,它很难阅读。)【参考方案6】:

在 C++ 中分配给 *this 并不等同于在 Java 中分配 this。分配this 是,在任何一种语言中都是不合法的。

【讨论】:

【参考方案7】:

在 C++ 中分配给 (*this) 执行复制操作 -- 将对象视为 值类型

Java 没有为类使用值类型的概念。对象分配总是通过引用

将对象复制为值类型:How do I copy an object in Java?


尽管用于 Java 的术语令人困惑:Is Java “pass-by-reference” or “pass-by-value”

答案:Java 按值传递引用。(来自here)

换句话说,因为 Java 从不将非原始类型视为值类型,所以每个类类型变量都是一个引用(实际上是一个指针)。

因此,当我说“对象分配始终是通过引用”时,将其表述为“对象分配始终由引用的值”在技术上可能更准确。 p>

Java 总是是按值传递的区别的实际含义体现在问题“How do I make my swap function in java?”中,其答案是:You can't。诸如 C 和 C++ 之类的语言能够提供交换函数,因为它们与 Java 不同,允许您通过使用对该变量的引用来从 任何变量 进行赋值,从而允许您更改其值(如果non-const) 而不更改它之前引用的对象的内容。

想一想这件事可能会让你头晕目眩,但这里什么都没有……

Java 类类型变量始终是“引用”,实际上是指针。 Java 指针是原始类型。 Java 赋值始终由底层原语的值(在本例中为指针)进行。 Java 根本没有与 C/C++ 传递引用等效的机制,它允许您间接修改独立的原始类型,它可能是“指针”,例如 this

此外,有趣的是,C++ 实际上有两种不同的语法用于传递引用。一种是基于显式指针,并继承自 C 语言。另一个基于 C++ reference-type operator &。 [还有 C++ 智能指针 形式的引用管理,但这更类似于类似 Java 的语义——引用本身是按值传递的。]

注意:在上述讨论中,assign-bypass-by 通常是可互换的术语。在任何赋值的基础上,是一个概念性的操作符函数,它根据被传入的右侧对象执行赋值


所以回到原来的问题:如果您可以在 Java 中分配给 this,这将意味着更改 this 持有的引用的值。这实际上相当于在 C++ 中直接分配给 this,这在该语言中也是不合法的。

在 Java 和 C++ 中,this 实际上是一个无法修改的指针。 Java 似乎有所不同,因为它使用. 运算符来取消引用指针——如果您习惯于 C++ 语法,那么会给您的印象是它不是一个。

当然,您可以用 Java 编写类似于 C++ copy constructor 的东西,但与 C++ 不同的是,没有办法绕过实现需要以显式成员初始化的形式提供。 [在 C++ 中,您最终可以避免这种情况,只是因为编译器会为您提供 赋值运算符的成员实现。]

您不能将 Java 限制整体复制到 this 是有点人为的。您可以通过逐个成员编写它来获得完全相同的结果,但是该语言没有一种自然的方式来指定要在 this 上执行的此类操作 - C++语法,(*this) 在 Java 中没有类似物。 事实上,Java 中没有内置操作可以重新分配任何现有对象的内容——即使它不被称为this [这样的操作可能更重要对于基于堆栈的对象,例如在 C++ 中很常见。]


关于执行深拷贝的用例:It's complicated in Java.

对于 C++,一种面向值类型的语言。赋值的语义意图通常是显而易见的。如果我说a=b,我通常希望a 独立于clone 和b,包含相等的C++ 会自动执行此操作以进行分配,并且有 plans 可以自动执行此过程,也可以进行比较。

对于 Java 和其他面向引用的语言,复制一个对象,在一般意义上,具有模棱两可的含义。除了原语,Java 不区分值类型和引用类型,因此复制对象必须考虑每个嵌套的类类型成员(包括父类的成员)并根据情况决定- 逐案,如果该成员对象应该被复制或只是引用。 如果留给默认实现,那么结果很可能不是您想要的。 比较 Java 中的对象是否相等受到same ambiguities 的影响。

基于所有这些,基本问题的答案为什么我不能通过this上的一些简单、自动生成的操作来复制对象 ,是从根本上说,Java 对复制对象的含义没有一个清晰的概念


最后一点,回答字面问题:

决定不将this 设为变量的原因是什么?

这样做毫无意义。 this 的值只是一个传递给函数的指针,如果您能够更改 this 的值,它不会直接影响用于调用该方法的任何对象或引用。毕竟,Java 是按值传递的。

【讨论】:

C++ 也通过值传递this ***上的语言比较:value type and reference type 由于上面讨论的模棱两可,比较集合may do the wrong thing,如果你还没有为包含的类实现hashCode()equals()。这个小问题激发了我对 Java 的赋值和比较操作深深的兴趣。 How do you make a deep copy of an object 在 Java 中,即使 'String' 也不是值类型。因为the obvious method of simple string comparison turns out to be incorrect,引入了多少bug?我敢肯定,这些年来我已经多次介绍过这个错误。 if ( a == "hello" ) 永远不会做任何有用的事情——为什么这甚至可以编译?我想知道考虑到 Java 的引用值传递语义,这种语言选择是否不可避免。

以上是关于为啥在java中不允许分配给'this'?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Typescript 允许将“任何”对象类型分配给类对象?

Java中数据存储分配

为啥允许将字符串文字分配给 C++ 中 char * 类型的指针 [重复]

为啥条件运算符不能正确地允许使用“null”来分配给可为空的类型? [复制]

为啥 Typescript 允许将 `a: 1, b: 2` 分配给类型 `a: any | b:任何`? [复制]

为啥 SQL Server 不允许我启用混合身份验证模式,以便我可以使用 sa 用户将角色分配给其他用户?