首页 > 试题广场 >

指出下列程序运行的结果()

[单选题]

指出下列程序运行的结果()

public class Example{
    String str = new String("good");
    char[ ] ch = { 'a' , 'b' , 'c' };
    public static void main(String args[]){
        Example ex = new Example();
        ex.change(ex.str,ex.ch);
        System.out.print(ex.str + " and ");
        System.out.print(ex.ch);
    }
    public void change(String str,char ch[ ]){
        str = "test ok";
        ch[0] = 'g';
    }
}


  • good and  abc
  • good and gbc
  • test ok and abc
  • test ok and gbc


简单总结一下:直接赋值而不是使用new关键字给字符串初始化,在编译时就将String对象放进字符串常量池中;使用new关键字初始化字符串时,是在堆栈区存放变量名和内容;字符串的拼接操作在程序运行时,才在堆中创建对象。一般,可以认为使用"=="比较的是引用,equals比较的是内容对于上面的题,看完下面的几个例子,你就会有所感悟:String str = new String("good");是在编译时在堆栈中创建对象和分配内容,而在传参的时候,传递的是地址,把外面的str引用地址复制了一份给方法内的str而不是里面的内容。
看例子:;
例子A:
String str1 = "java";
String str2 = "java";
System.out.print(str1==str2);
大部分人也许认为会输出false,因为==比较的是引用,equals比较的是内容。可以在自己的机子上运行一 下,结果是true!原因很简单,String对象被放进常量池里了,再次出现“java”字符串的时候,JVM很兴奋地把str2的引用也指向了 “java”对象,它认为自己节省了内存开销。不难理解吧 呵呵
例子B:
String str1 = new String("java");
String str2 = new String("java");
System.out.print(str1==str2);
看过上例的都学聪明了,这次肯定会输出true!很不幸,JVM并没有这么做,结果是false。原因很简单,例子A中那种直接赋值(而没有通过new关键字实例化的字符串变量)声明的方式确实是在 String常量池创建“java”对象,但是一旦看到new关键字,JVM会在堆中为String分配空间。两者声明方式貌合神离,这也是我把“如何创 建字符串对象”放到后面来讲的原因。大家要沉住气,还有一个例子。
例子C:
String str1 = "java";            //直接赋值而不是使用new关键字给字符串初始化,在编译时就将String对象放进字符串常量池中
String str2 = "blog"; //直接赋值而不是使用new关键字给字符串初始化,在编译时就将String对象放进字符串常量池中
String s = str1+str2;            //字符串的拼接操作在程序运行时,才在堆中创建对象,
System.out.print(s=="javablog");
再看这个例子,很多同志不敢妄言是true还是false了吧。爱玩脑筋急转弯的人会说是false吧……恭喜你,你会抢答了!把那个“吧”字去掉你就完 全正确。原因很简单,JVM确实会对型如String str1 = "java"; 的String对象放在字符串常量池里,但是它是在编译时刻那么做的,而String s = str1+str2; 是在运行时刻才能知道(我们当然一眼就看穿了,可是Java必须在运行时才知道的,人脑和电脑的结构不同),也就是说str1+str2是在堆里创建的, s引用当然不可能指向字符串常量池里的对象。没崩溃的人继续看例子D。
例子D:
String s1 = "java";
String s2 = new String("java");
System.out.print(s1.intern()==s2.intern());
intern()是什么东东?反正结果是true。如果没用过这个方法,而且训练有素的程序员会去看JDK文档了。简单点说就是用intern()方法就可以用“==”比较字符串的内容了。在我看到intern()方法到底有什么用之前,我认为它太多余了。其实我写的这一条也很多余,intern()方法 还存在诸多的问题,如效率、实现上的不统一……
例子E:
String str1 = "java";
String str2 = new String("java");
System.out.print(str1.equals(str2));
无论在常量池还是堆中的对象,用equals()方法比较的就是内容,就这么简单!



以上内容引用自:http://hi.baidu.com/dairywg/blog/item/495f81b11885fa500823027f.html

编辑于 2018-04-02 18:12:41 回复(34)
首先说下String确实是个不可变对象,这个不可变是JDK特有的,写JAVA的人特意针对的
但是这与本题无关,题目中的形参str只是原引用ex.str的一个引用副本,传的是一个副本地址值,这个值与ex.str地址值是不一样的,但是它们同时指向了堆中的对象new String("good"),当你在函数中改变形参也就是地址的副本值也就是这句str="test ok"只是将副本地址指向常量"test ok",并没有改变原ex.str的指向方向,它还是指向对象new String("good")的
char数组与String一样传的也是地址的副本,但是关键是形参ch它没有新的指向 ch[0]只是ch在指向原对象时改变了对象的内部结构, 所以在ex.ch指向与它是同一个对象的情况下当然也会随之变化
编辑于 2021-09-16 11:06:44 回复(14)
发表于 2017-09-28 10:02:39 回复(8)
1.这是《java编程思想》283页,字符串这章第一个概念,“String对象是不可变的(看了源码,String实质是final修饰的char数组!),每个看起来会修改的方法,其实都是创建了一个全新的String”...... 2.要知道字符串常量的概念,那个“test ok”是个字符串常量!在编译期就定好了,赋值的时候相当于new了新的字符串给那个引用。传参的时候,把外面的str引用地址复制了一份给方法内的str。(通常,我们以前认为的是,方法里修改了引用对应的值,然后里里外外都对应这个,全改了!注意!这里不是修改值!是相当于直接new了个新的地址给方法内的str。而原来的没变!)
编辑于 2017-07-16 01:22:58 回复(2)
首先得明确java的值传递和引用传递的区别 (可以从这里去看一下http://blog.csdn.net/yqlakers/article/details/70144766)
先把代码贴一下,顺便检验一下结果。
public class Example{
	String str = new String("good");
	char[] chs = {'a','b','c'};
    public static void main(String[] args) //4
    {
        Example ex = new Example(); ex.change(ex.str, ex.chs);
        System.out.print(ex.str+" and ");
        System.out.print(ex.chs);
    }
    public  void change(String str, char[] chs){
    	str= "test ok";
    	chs[0] = 'g';
    }
}
result:good and gbc
首先明确 这里str和数组都是对象都是引用传递 ,首先得清楚类中的str和change方法中的str都是指向堆中的new String(" good" )对象,但是它们不是同一个东西,类似于指向同一个对象的不同的指针。首先str="test ok" 使得change()中的str指向"test ok"这个常量字符串(也可以说这个字符串对象),根本的原因就是 由于java字符串对象是不能修改的,无法像数组一样可以在原来的地址上对数组的值进行修改。 但是原来的类中的str指向的还是 new String(" good" )这个对象,chs[0] = 'g',把原来chs[0]中的'a'改为'g'。
所以就会出现上面的结果
个人的想法,如有问题望不吝指出
编辑于 2017-04-13 08:48:09 回复(3)
1、这里str,和ch数组都是引用传递。上面new出来的那个str地址传到方法中,即方法中的str也指向new String”good”)这个对象,但是String是不可变类,就是说=”test ok”是无法直接覆盖new String”good”)而完成赋值。此时两个str引用均指向new String”good”),而”test ok”没有引用指向它。
2、数组则不同,是可变类型。上面数组的地址传递给方法中的ch,即方法中的ch也指向上面的数组。是可以对原数组进行修改的,所以 =”g”赋值操作可以覆盖原来位置的”a”
注:不可变类:所有基本类型的包装类+String类型

发表于 2017-08-03 17:21:51 回复(2)
看了赞最多的回答,我想说,那是在扯淡吗?强行把引用对象解释成值对象了。还有那么多人点赞。虽然我也不知道为什么,但是请你不知道不要出来误人子弟好么?
发表于 2017-04-09 15:08:15 回复(2)

有同学可能会疑惑,参数传递看起来不像是传递副本,那么就看看下面的图吧!
这个图中显示了main中定义的ab值,和swap中传入的ab值。很明显可以看出是副本吧。即在此处,更新或者个换ab的值,不影响main中的ab值。

在下图中显示的参数传递的是一个对象。对象就像是指针。它在swap中定义了副本。不可否认吧。但是在堆中他们所指的对象相同,故此时交换ab的值,会引起main中的ds对象中的ab值变化。但其本质仍然是值传递。

具体例子看下面。
```
public class Str {
    
    public static void main(String[] args){
        //对象的引用传递,即传递的是内存地址,此时s和s1均指向一个内存地址
        StringBuffer s = new StringBuffer("good");
        StringBuffer s1 = new StringBuffer();
        s1 = s;
        s1.append(" Morning");
        System.out.println(s1);//good Morning
        System.out.println(s);//good Morning
        //基本数据类型的传递方式是值传递,即传递的是原始值的副本
        int i=5;
        int i2=i;
        i2=6;
        System.out.println(i);//5
        System.out.println(i2);//6
        //参数传递是按照值传递的方式进行
        Str str = new Str();
        StringBuffer s2 = new StringBuffer();
        s1.setLength(0);
        s1.append("A");
        s2.append("B");
        str.test(s1,s2);
        System.out.println(s1);//AAAhah
        System.out.println(s2);//B
    }
    public void test(StringBuffer s1,StringBuffer s2){
        //注意!!
        //此处相当于main中的s1和test中的s1指向同一个内存地址,s2同理
        System.out.println(s1);//A
        System.out.println(s2);//B
        //此处并没有出现赋值操作,故s1和s2指向的内存地址不同,一个对象的改变不会引起另一个对象的改变
        s1.append("AA");//main中的s1和test中的指向相同,即具有一致性
        System.out.println(s1);//AAA
        System.out.println(s2);//B
        //出现赋值操作,内存地址相同
        //注意!!
        //test中的s2指向发生变化,而main中的s2仍指向B。故其后s2的值再怎么改变,也不影响main中的s2了。
        s2 = s1;
        System.out.println(s1);//AAA
        System.out.println(s2);//AAA
        //s1指向new的内存地址,此时main和test中的s1指向已不同,但是main中的s1和test中的s2指向相同,具有一致性。
        s1=new StringBuffer("new");
        System.out.println(s1);//new
        System.out.println(s2);//AAA
        //此处的s1改变不再影响main中的s1,而s2的改变影响main中的s1
        s1.append("hah");
        s2.append("hah");
        System.out.println(s1);//newhah
        System.out.println(s2);//AAAhah,和main中的s1相同。
    }
    
 }

```


编辑于 2017-11-20 10:56:20 回复(6)
String修饰的对象为不可变对象,所以ex.str不会被改变。
ch[0]会被改变,改变的是对象的值。
发表于 2016-12-18 20:22:05 回复(1)
ex.change(ex.str,ex.ch); 
第一个参数是 String 类型的参数,作为方法的形参在实际传参时其实传递的是该字符串的一个副本;
第二个参数时 char 类型的数组,数组本身就是一个对象,对象作为参数传递时,在方法内部对对象的修改将影响到方法外。
发表于 2018-02-27 20:14:23 回复(0)
简单点说基本类型传值 其他类型传引用 特殊记一下String也是传值就ok了
发表于 2017-05-18 23:56:10 回复(0)
真服了这题,,,直接拍个照片上来,还拍的这么模糊😭😭
发表于 2017-02-25 22:04:16 回复(0)
发表于 2022-05-13 21:27:37 回复(3)
发表于 2017-04-20 21:37:26 回复(0)
这么理解就好了,String前默认有final修饰,不可以改变其值
发表于 2018-04-01 09:41:49 回复(0)
this.str的值传入change方法,局部变量str修改,不会影响this.str的值.
同名变量导致的容易混淆.
发表于 2017-12-20 11:25:59 回复(0)
    ex.str和方法签名的str是两个不同的引用变量,但都是指向同一个字符串对象,但是String类是不可变类,举个例子就是,String s1 = "first";,这时候内存堆中有一个“first”对象,当试着s1 = "second"时,前面的“first”对象不会被覆盖掉,而是新出现了一个字符串对象"second",并且引用变量s1指向了“second”对象,回到问题,就会发现,ex.str指向“good”,但是方法签名的str却是指向“test ok”,两个变量从一个都只想同一个“good”,最后变成分别指向两个字符串对象。
    接着是char[],它是个数组,是一个引用类型,ch和方法签名中的ch是两个不同的引用变量但却是指向同一个对象。但是方法体里面改变的是ch[]里面的一个基本类型char,没有改变整个ch引用的指向。所以它们两个引用只要其中一个改变其内部的基本类型数据都会相互影响。除非是
方法体内的ch = {'a', 'b', 'd'}这时它指向了新对象,然后ch[0] = 'g',这时候两个ch指向的对象已经不一样了,所以它们对内部基本类型数据的改变不会相互影响。个人不严谨愚见,纯属意会。
发表于 2017-09-16 17:32:32 回复(0)
按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本。因此,如果函数修改了该参数,仅仅是改变副本,原始值保持不变。按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本。因此,如果函数修改了该参数的值,调用代码的原始值也随之改变。如果函数修改了该参数的地址,调用代码的原始值不会改变。
发表于 2017-08-12 21:10:20 回复(3)
形参str只是栈上的一个引用,和ex.str是一个性质,但是是另个变量,str=“test ok”改变的是str的指向,和ex.str没有一毛钱关系
发表于 2017-07-04 22:14:14 回复(0)
考察范围:值传递
无论形参str传的是基本数据类型还是引用类型,都是原来的副本。基本数据类型副本值的改变不会影响原来的;本题中由于引用类型的副本str的地址发生了改变,指向了常量池中值地址。如果在不改变副本地址的情况下(由于副本和原来的都是指向同一个地址),值如果改变则,则就会改变。

编辑于 2021-08-25 22:36:59 回复(0)