我正在阅读这个问题,我得到了以下代码片段:
public void testFinally(){
System.out.println(setOne().toString());
}
protected StringBuilder setOne(){
StringBuilder builder=new StringBuilder();
try{
builder.append("Cool");
return builder.append("Return");
}finally{
builder.append("+1");
}
}
Run Code Online (Sandbox Code Playgroud)
答案是: CoolReturn + 1
好的,然后我尝试了同样的String和int以下是我的代码片段String:
public void testFinally(){
System.out.println(setOne().toString());
}
protected String setOne(){
String str = "fail";
try{
str = "success";
return str;
}finally{
str = str + "fail";
}
}
Run Code Online (Sandbox Code Playgroud)
为什么答案是: success.为什么不successfail,因为在第一种情况下最终附加值,在这里我正在进行连接?
我也试过原始类型 int
public void testFinally(){
System.out.println(setOne());
}
protected int setOne(){
int value = 10;
try{
value = 20;
return value ;
}finally{
value = value + 10;
}
}
Run Code Online (Sandbox Code Playgroud)
这也是为什么答案是: 20为什么不是30.
TL; DR:您已经告诉该方法要返回什么.该finally块发生后,你这样做.另外,引用变量只是指向对象的指针,虽然finally块不能更改引用本身,但它肯定可以更改引用指向的对象.
答案很长:
这里有两件事:
首先,您已经告诉该方法返回什么值.JLS从14.20.2开始相当简单地说明了(强调我的):
通过首先执行try块来执行具有finally块的try语句.
也就是说,try块在finally块运行之前完全执行.另外,从14.7开始:
...执行这样的return语句首先评估Expression.
这意味着该try块具有的任何和所有效果(包括指定该方法应该返回的值)都是完整的,并且要返回的值已经完全评估,现在可以说是"一成不变".
让我们首先集中精力.在这里,我们看一下原始示例的略微修改版本.您发布的原始示例不是一个很好的示例,因为10整数的初始值加上10finally块中的值也恰好等于20,这使实际发生的事情变得模糊.所以让我们考虑一下:
protected int setOne(){
int value = 5;
try{
value = 20;
return value;
}finally{
value = value + 10;
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法的回报是20.不是15,不是30,而是20.为什么?因为在try块中,你设置value = 20,然后你告诉方法返回20; value在return语句中进行求值,并且它在那时的值是20. finally块没有做什么可以改变你已经告诉方法返回20的事实.
好的,容易的.
现在,在您的其他示例中,正在发生的第二件事是引用变量指向对象.也就是说,它们本质上是保持对象内存地址的原始整数变量.它们遵循与上述原始类型相同的规则!在我们查看其余示例之前,请考虑以下事项:
int array[] = new int[] { 100, 200, 300 };
int example () {
int index = 1;
try {
return index;
} finally {
array[index] = 500;
index = 2;
}
}
Run Code Online (Sandbox Code Playgroud)
此方法返回1,而不是2(由于上述原因).finally块也会修改array[1].那么,value在以下内容之后包含的内容如下:
int index = example();
int value = array[index];
Run Code Online (Sandbox Code Playgroud)
当然是500.我们可以看到这一点而不需要太多解释.该example方法将索引返回到数组中.该finally块修改数组中的数据.当我们稍后查看该索引处的数据时,我们看到它包含500,因为finally块将其设置为500. 但是更改数组中的数据与返回的索引仍为1的事实无关.
这与返回引用完全相同.将引用变量视为原始整数,它本质上是大型内存(堆)的索引.修改引用指向的对象就像修改该数组中的数据一样.现在,其余的例子应该更有意义.
那么让我们看看你的第一个例子:
protected StringBuilder setOne(){
StringBuilder builder=new StringBuilder();
try{
builder.append("Cool"); // [1]
return builder.append("Return"); // [2]
}finally{
builder.append("+1"); //[3]
}
}
Run Code Online (Sandbox Code Playgroud)
在你的问题中,你已经说过你很困惑因为这个方法返回"CoolReturn + 1".但是,这种说法从根本上说没有多大意义!此方法未返回"CoolReturn + 1".此方法返回对StringBuilder恰好包含数据"CoolReturn + 1"的引用.
在此示例中,评估第一行[1].然后评估行[2]并.append("Return")执行.然后finally发生块,并评估行[3].然后,由于您已经告诉该方法返回StringBuilder对该引用的引用,因此返回该引用.StringBuilder返回引用所指向的内容已被修改finally,并且没关系.这不会影响方法返回的值,这只是对象的引用(即我之前描述的那个大内存数组中的"索引").
好的,让我们看看你的第二个例子:
protected String setOne(){
String str = "fail";
try{
str = "success";
return str;
}finally{
str = str + "fail";
}
}
Run Code Online (Sandbox Code Playgroud)
这将返回对String包含数据"成功" 的引用.为什么?由于上面已经描述的所有原因.这一行:
str = str + "fail";
Run Code Online (Sandbox Code Playgroud)
只需创建一个新String对象,它是两个字符串的串联,然后str为该新对象分配一个引用.但是,与原始int示例一样,我们已经告诉函数返回对"成功"的引用String,无论我们做什么,我们都无法改变它!
结论:
您可以提供无数个示例,但规则将始终相同:返回值在return语句中计算,并且以后不能更改该值.引用变量只是保存对象的内存地址的值,并且即使该地址处的对象当然可以被修改,也无法更改内存地址值finally.
另请注意,不变性与此处的一般概念无关.在这个String例子中,它有点像红鲱鱼.请记住,即使Strings 是可变的,我们永远不会期望二元+运算符修改其左操作数的字段(例如,即使字符串有一个append()方法,a = a + b也不会期望修改任何字段a,它将是期望返回一个新对象,然后存储对该对象的引用a,使原始对象保持不变.这里混淆的一个原因是Java允许+String作为方便; 没有其他对象直接支持这样的运算符(不计算原始包装器的自动拆箱).
我确实将此问题标记为重复为什么更改finally块中的返回变量不会更改返回值?.我相信,一旦你的头脑被这里的概念所包围,很明显,那里的问题和答案与问题和答案基本相同.
第一个和第二个方法返回对象的引用,finally块稍后执行,第一个例子中发生的是你仍然保持对对象(构建器)的引用,以便你可以修改它.
在第二个示例中,您有一个字符串,它也是不可变的,因此您无法修改它,只能为变量分配一个新对象.但是您返回的对象未被修改.因此,当您str = str + "fail";为变量分配str新对象时.
在第三个示例中,您有一个整数,它不是一个对象,它返回它的值,稍后在finally块中您将变量赋值给一个新的整数,但返回的一个不会被修改
详细说明:
想象一下第四种情况:
public static class Container{
public int value = 0;
}
protected static Container setOne(){
Container container = new Container();
try{
container.value = 20;
return container ;
}finally{
container.value = container.value + 10;
}
}
Run Code Online (Sandbox Code Playgroud)
此函数检索对名为container的变量的引用,并在返回后将容器的value字段递增到+10,因此当您退出该函数时,container.value将为30,就像在StringBuilder示例中一样.
让我们将此方法与第三个示例(int方法)进行比较:
如果你得到这两种方法的字节码,你会得到:
对于int的示例:
bipush 10
istore_0
bipush 20
istore_0
iload_0
istore_2
iinc 0 10
iload_2
ireturn <- return point
astore_1
iinc 0 10 <- retrieve the variable value and add 10 to it's value
aload_1 <- Store the value of the result of the sum.
athrow
Run Code Online (Sandbox Code Playgroud)
对于Container包装类的示例:
new Test4$Container
dup
invokespecial Test4$Container/<init>()V
astore_0
aload_0
bipush 20
putfield Test4$Container/value I
aload_0
astore_2
aload_0
aload_0
getfield Test4$Container/value I
bipush 10
iadd
putfield Test4$Container/value I
aload_2
areturn <-- Return point
astore_1 <-- Stores the reference
aload_0
aload_0
getfield Test4$Container/value I <-- gets the value field from the object reference
bipush 10
iadd <-- add the value to the container.value field
putfield Test4$Container/value I <-- Stores the new value (30) to the field of the object
aload_1
athrow
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,在第二种情况下,finally语句访问引用的变量,增加它的值.但在int示例中,它只将变量的值加10,为变量赋值.
我已经使用过这个例子,因为它的字节码比字符串缓冲区更容易被读取,但你可以用它做,你会有类似的结果.
| 归档时间: |
|
| 查看次数: |
1388 次 |
| 最近记录: |