经常会有这样一道面试题,有两个整形变量分别是a = 1 ,b = 2。编写一个方法swap互换他们的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class
Main
{
public
static
void
main
(
String
[]
args
)
{
Integer
b
=
2
;
System
.
out
.
println
(
"a="
+
a
+
",b="
+
b
);
swap
(
a
,
b
);
System
.
out
.
println
(
"a="
+
a
+
",b="
+
b
);
}
private
static
void
swap
(
Integer
numa
,
Integer
numb
)
{
//请实现
}
}
|
1、首先大家看到这到题目后,仔细看后,在main方法中变量a和b的类型是Integer,而不是Int类型,因为这里涉及到了java的基本类型,Int是属于Java的基本类型,基本类型在调用swap的方法时,是修改不了变量a和b的值,说到这里涉及到另一个知识点了,那就是形参和实参的区别,值传递和引用传递的区别,下面慢慢说啊。
2、形参和实参的区别
实参顾名思义:就是实际参数,用于调用时传递给方法的参数。实参在传递给别的方法之前是要被预先赋值的。
形参顾名思义:就是形式参数,用于定义方法的时候使用的参数,是用来接收调用者传递的参数的,形参只有在方法被调用的时候,虚拟机才会分配内存单元,在方法调用结束之后便会释放所分配的内存单元。因此,形参只在方法内部有效,所以针对引用对象的改动也无法影响到方法外。
以这到题目为例,方法swap ( Integer numa , Integer numb )中的numa和numb是形参, 而在main方法中 传递给swap ( a , b )中的a和b是实参。
3、值传递和引用传递的区别
值传递调用过程只能把实参传递给形参,而不能把形参的值反向作用到实参上。在函数调用过程中,形参的值发生改变,而实参的值不会发生改变,函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法中的操作都是对形参这个值的修改,不影响实际参数的值。说到这里就明白了题目中声明a和b的数据类型不为Int的原因了吧。
引用传递也称为 地址传递, 址传递,引用传递调用的机制中,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象。说到这里就明白了题目中声明a和b的数据类型为Integer的原因了吧。
4、完成swap 方法实现
1
2
3
4
5
6
7
8
9
10
11
|
static
void
swap
(
Integer
numa
,
Integer
numb
)
{
Integer
tmp
=
numa
;
numa
=
numb
;
numb
=
tmp
;
System
.
out
.
println
(
"numa="
+
numa
+
",numb="
+
numb
);
}
|
是不是感觉挺简单,但是运行后main方法中的a和b的值没有互换,分别还是a = 1 ,b = 2。那这是为什么呢?因为Interger虽然是引用类型,但是Integer在方法中没有提供value的get和set方法,也是对对象的安全保护,也就是传递过程中在Integer里面copy了一个副本指向值,而不是引用地址,是不是没有办法了,这就涉及到反射的用法,我们用反射改变Integer内部的value属性值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static
void
swap
(
Integer
numa
,
Integer
numb
)
{
Integer
tmp
=
numa
;
try
{
Field
field
=
Integer
.
class
.
getDeclaredField
(
"value"
);
field
.
setAccessible
(
true
);
field
.
set
(
numa
,
numb
);
//成功的将numa 引用的 1的对象 值改为 2
field
.
set
(
numb
,
tmp
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
|
是不是感觉大功告成了,但是运行以后,a 和b的值都变为2。难道 Integer tmp = numa ; 是这句话的问题吗,因为numa对象的值已经是2了,那这样的话tmp对象也是2,所以a 和b的值都变为2,那咱们把这句话改一下试试对不对。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static
void
swap
(
Integer
numa
,
Integer
numb
)
{
int tmp = numa.intValue();
try
{
Field
field
=
Integer
.
class
.
getDeclaredField
(
"value"
);
field
.
setAccessible
(
true
);
field
.
set
(
numa
,
numb
);
//成功的将numa 引用的 1的对象 值改为 2
field
.
set
(
numb
,
tmp
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
|
这是应该没有问题,但是运行后,a 和b的值还都是2。我真想说真二,这是为什么呢?这样试一下,咱们把a和b的初始改为a = 199,b = 299,再试一下。经过运行后发现a 和b的值成功互换。这是为什么呢?难道和数值的大小有关系吗?我们再变一种写法试试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
static
void
main
(
String
[]
args
)
{
Integer
a
=
new
Integer
(
1
);
Integer
b
=
new
Integer
(
2
);
System
.
out
.
println
(
"a="
+
a
+
",b="
+
b
);
swap
(
a
,
b
);
System
.
out
.
println
(
"a="
+
a
+
",b="
+
b
);
}
private
static
void
swap
(
Integer
numa
,
Integer
numb
)
{
int
tmp
=
numa
.
intValue
();
try
{
Field
field
=
Integer
.
class
.
getDeclaredField
(
"value"
);
field
.
setAccessible
(
true
);
field
.
set
(
numa
,
numb
);
field
.
set
(
numb
,
tmp
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
|
运行以后,a 和b的值成功互换,a = 2, b = 1。那这又是为什么呢?难道和装箱和拆箱有关系吗,为什么 Integer a = 1 和 Integer a = new Integer(1) 效果不一样了,当Integer a = 1;时,编译器会将其转化为Integer a = Integer.valueOf(1); 但是数值分别是199和299 怎么又正常了呢,通过看源码Integer.valueOf 的方法
下面大家可以验证一下,理解默认的Integer缓存int常量值的范围
1
2
3
4
5
6
7
|
System.out.println( 127 == 127 ); //true , int type compare System.out.println( 128 == 128 ); //true , int type compare System.out.println( new Integer( 127 ) == new Integer( 127 )); //false, object compare System.out.println(Integer.parseInt( "128" )==Integer.parseInt( "128" )); //true, int type compare System.out.println(Integer.valueOf( "127" )==Integer.valueOf( "127" )); //true ,object compare, because IntegerCache return a same object System.out.println(Integer.valueOf( "128" )==Integer.valueOf( "128" )); //false ,object compare, because number beyond the IntegerCache System.out.println(Integer.parseInt( "128" )==Integer.valueOf( "128" )); //true , int type compare |
通过阅读源码发现,Integer.valueOf 方式初始化一个 Interger因为有 缓存了 -128-127的数字,再看 field.set(numb,tmp); 我们打断点,发现通过反射设置 value时 竟然走了 Integer.valueOf 方法。
大家可以在代码中验证一下,在 field . set ( numa , numb ); 后增加 System.out.println("tmp3="+new Integer(tmp));
System.out.println("tmp4="+Integer.valueOf(tmp)); 运行后,发现打印的tmp3 = 1 ,tmp4 = 2 , 说到这里大家明白其中的原因了吧。
最后正确的swap方法是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static
void
swap
(
Integer
numa
,
Integer
numb
)
{
int
tmp
=
numa
.
intValue
();
try
{
Field
field
=
Integer
.
class
.
getDeclaredField
(
"value"
);
field
.
setAccessible
(
true
);
field
.
set
(
numa
,
numb
);
field
.
set
(
numb
,
new
Integer
(
tmp
));
//避免从缓冲取值
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
|
补充知识:java 传递参数的两种方式讲解
Java中没有指针,所以也没有引用传递了,仅仅有值传递不过可以通过对象的方式来实现引用传递 类似java没有多继承 但可以用多次implements 接口实现多继承的功能
值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参 数的值。
引用传递:也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。
a.传递值的数据类型:八种基本数据类型和String(这样理解可以,但是事实上String也是传递的地址,只是string对象和其他对象是不同的,string对象是不能被改变的,内容改变就会产生新对象。那么StringBuffer就可以了,但只是改变其内容。不能改变外部变量所指向的内存地址)。
b.传递地址值的数据类型:除String以外的所有复合数据类型,包括数组、类和接口
值传递的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package com.other.test; public class Test { public static void change( int i, int j) { int temp = i; i = j; j = temp; } public static void main(String[] args) { int a = 3 ; int b = 4 ; change(a, b); System.out.println( "a=" + a); System.out.println( "b=" + b); } } |
输出的结果是 a=3 b=4,传递的值并不会改变原值
引用传递的例子:(数组)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package com.other.test; public class Test { public static void change( int [] counts) { counts[ 0 ] = 6 ; System.out.println(counts[ 0 ]); } public static void main(String[] args) { int [] count = { 1 , 2 , 3 , 4 , 5 }; change(count); System.out.println(count[ 0 ]); } } |
输出结果是6 6 也就是引用的值改变了原来的值
引用传递的例子:(对象)
定义一个A对象:
1
2
3
4
5
|
package com.other.test; public class A { int i = 0 ; } |
对上边对象操作的类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package com.other.test; public class Test { public static void add(A a) { //a = new A(); ① a.i++; } public static void main(String args[]) { A a = new A(); add(a); System.out.println(a.i ); } } |
当把①注解掉时,输出的结果是1,当①没有注解是是0,原因是 a =new A();构造了新的A对象就不是传递的那个对象了。
看看String的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package com.other.test; public class Test { String str = new String( "old" ); char [] ch = { 'a' , 'b' , 'c' }; public static void main(String args[]) { Test ex = new Test(); ex.change(ex.str, ex.ch); System.out.print(ex.str + " and " ); System.out.println(ex.ch); } public void change(String str, char ch[]) { str = "new" ; ch[ 0 ] = 'd' ; } } |
输出的结果的是 old and dbc也就是传递String并不会改变原值,而是创建了一个新值。 ch[]就是一个简单的数组的传递。
( 对象包括对象引用即地址和对象的内容)
String 比较特别,看过String 代码的都知道, String 是 final的。所以值是不变的。 函数中String对象引用的副本指向了另外一个新String对象,而数组对象引用的副本没有改变,而是改变对象中数据的内容.
对于对象类型,也就是Object的子类,如果你在方法中修改了它的成员的值,那个修改是生效的,方法调用结束后,它的成员是新的值,但是如果你把它指向一个其它的对象,方法调用结束后,原来对它的引用并没用指向新的对象。
Java参数,不管是原始类型还是引用类型,传递的都是副本(有另外一种说法是传值,但是说传副本更好理解吧,传值通常是相对传址而言)。
如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,这个跟之前所谈的传值是一样的。如果在函数中改变了副本的值不会改变原始的值.
如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地址。如果在函数中没有改变这个副本的地址,而是改变了地址中的值,那么在函数内的改变会影响到传入的参数。如果在函数中改变了副本的地址,如new一个,那么副本就指向了一个新的地址,此时传入的参数还是指向原来的地址,所以不会改变参数的值。
以上这篇Java形参和实参的实例之Integer类型与Int类型用法说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/tomy123456123456/article/details/86228612