三、运算
参数传递
Java的参数是以==值传递==的形式传入方法中,而不是引用传递。
如何理解参数都是以值传递的形式,而不是引用传递?
在Java中,理解参数传递是以值传递的形式而不是引用传递,可以通过以下几点来解释:
- Java中一切皆对象:在Java中,几乎所有的数据类型都是对象,包括基本数据类型(如int、float等),它们都是以值的形式存在。但是,即使是对象,也只是在内存中有一个引用指向它们的实际值。
- 值传递:当你将一个参数传递给一个方法时,实际上传递的是该参数的值,而不是参数本身。
- 对于基本数据类型,这意味着方法中对参数的任何更改都不会影响到原始值,因为只是对原始值的拷贝进行操作。
- 对于对象引用,传递的是引用的拷贝,这意味着在方法内部对引用的操作会影响到原始引用指向的对象,但是对引用本身的修改(比如指向另一个对象)则不会影响到原始的引用。
- 引用传递的误解:一些人错误的认为Java是引用传递的,是因为当你将一个对象作为参数传递给方法时,你传递的是对象的引用,而不是对象本身。因此,在方法内部对对象属性的修改会影响到原始对象。这种情况下,容易将Java误解为引用传递。
总的来说:
- Java中的参数传递是以值传递的方式进行的。
- 但对于对象的引用,传递的是引用的拷贝,而不是引用本身。
以下代码中Dog dog的dog是一个指针:存储的是对象的地址。
在将一个参数传入方法时:本质上是将对象的地址以值的方式传递到形参中。
public class Dog {
String name;
Dog(String name) {
this.name = name;
}
String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
String getObjectAddress() {
return super.toString();
}
}
在方法中改变对象的字段值会改变原对象该字段值,因为引用的是同一个对象(同一个对象处理了两次(更改值 + 打印值))。
class PassByValueExample {
public static void main(String[] args) {
//指向A对象
Dog dog = new Dog("A");
func(dog);
System.out.println(dog.getName()); // B
}
private static void func(Dog dog) {
//修改值为B对象
dog.setName("B");
}
}
但是在方法中如果将指针引用了其它对象
那么此时方法里和方法外的两个指针指向了不同的对象
在一个指针改变其所指向对象的内容对另一个指针所指向的对象没有影响。
public class PassByValueExample {
public static void main(String[] args) {
//方法内执行对象A
Dog dog = new Dog("A");
System.out.println(dog.getObjectAddress()); // Dog@4554617c
func(dog);
//System.out.println(dog.getObjectAddress()); // Dog@4554617c
//System.out.println(dog.getName()); // A
}
private static void func(Dog dog) {
System.out.println(dog.getObjectAddress()); // Dog@4554617c
//方法外的指针指向另一个对象B
dog = new Dog("B");
System.out.println(dog.getObjectAddress()); // Dog@74a14482
System.out.println(dog.getName()); // B
}
}
输出:
初始化对象A,值也为A,此时引用地址为:vo.Dog@4f023edb
func方法执行前参数引用地址为:vo.Dog@4f023edb
func方法执行后参数引用地址为:vo.Dog@3a71f4dd
func方法执行后参数值为:B
总结
Java是按值传递的!
- 基本数据类型(如int、float、boolean等)是按值传递的。
- 在Java中,对象并不直接传递;相反,对象的引用被按值传递。
StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?
float 与 double
Java 不能隐式执行向下转型,因为这会使得精度降低。
1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。
//向下转型,会使得精度降低,所以Java不能隐式的向下转型。必须使用强制类型转换!
// float f = 1.1;
1.1f 字面量才是 float 类型。
float f = 1.1f;
隐式类型转换
因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型向下转型为 short 类型。
但是short类型却可以隐式向上转换为int
short s1 = 1;
// s1 = s1 + 1;
但是使用 += 或者 ++ 运算符会执行隐式类型转换。
s1 += 1;
s1++;
上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:
s1 = (short) (s1 + 1);
StackOverflow : Why don’t Java’s +=, -=, *=, /= compound assignment operators require casting?
switch
从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。
String s = "a";
switch (s) {
case "a":
System.out.println("aaa");
break;
case "b":
System.out.println("bbb");
break;
}
为什么从Java7才开始可以在switch语句中使用String对象呢?
在Java中引入了对字符串对象的switch语句支持主要是因为:
在之前的Java的早期版本中,switch语句的实现采用了基于整数的跳转表(jump table)
来进行快速的分支选择,而对于字符串对象来说,由于其可变性和比较复杂的相等性判断,直接使用跳转表来实现会非常困难、
Java7引入一项重要的改进,即对switch语句的实现进行了改进,使其可以支持字符串对象。
这项改进的核心在于使用了更加高效的哈希表(hash table)实现,以便快速查找匹配的字符串。这样一来,就可以在switch语句中直接使用字符串对象作为分支条件,而无需接其转换为其它类型。
总的来说,Java7中引入对字符串对象的switch语句支持,是通过改进switch语句的底层实现的,以适应字符串对象的特性和需求。
基于整数的跳转表(jump table)
基于整数的跳转表(jump table)是一种用于优化 switch 语句执行的技术。在编译 switch 语句时,编译器会生成一个跳转表,其中包含了各个 case 标签对应的代码块在内存中的地址(或偏移量)。当 switch 语句被执行时,程序会根据 switch 表达式的值在跳转表中查找对应的地址,然后直接跳转到相应的代码块处执行,从而避免了逐个比较每个 case 标签的时间消耗,大大提高了 switch 语句的执行效率。
基于整数的跳转表通常适用于 switch 语句的分支条件为整数或枚举类型的情况,因为在跳转表中的索引通常与 case 标签中的整数值或枚举常量值一一对应。
然而,对于字符串对象等不同类型的情况,直接采用基于整数的跳转表会比较困难,因为字符串的相等性比较复杂且比较耗时,不易直接映射到整数值上。因此,在早期版本的 Java 中,并没有对字符串对象的 switch 语句提供支持。直到 Java 7 引入了对字符串对象的 switch 语句支持,采用了更高效的哈希表实现来解决这个问题。
为什么基于整数的跳转表不支持String?
整数的跳转表适用于整数类型和枚举类型等有限的、可枚举的值域,因为它们的值编译时就已经确定,并且可以轻松映射到跳转表的索引位置。这种映射关系使得在跳转表中快速的查找对应的执行代码块,非常高效!
而字符串类型不同于整数/枚举类型,它是一个引用类型,其值的范围可以是无限的。
也就是说,引用类型的字符串在编译时并不能确定所有可能出现的字符串的值。因此,在Java7及以前,并没有直接支持引用类型的跳转表,因为构建一个能够完整覆盖所有可能字符串值的跳转表是不太可能的。
既然引用类型的字符串值在编译时是不确定的。那么hashTable怎么又可以实现?
采用基于哈希表的实现方式,在使用字符串作为switch语句的条件时,Java编译器会将其哈希化并构建一个Hash表,将字符串的哈希码映射到相应的执行代码块上,从而实现了对字符串对象的快速匹配和执行。这种实现方式使得switch语句中使用字符串对象成为可能,并且具有较高的效率。
为什么switch 不支持 long、float、double?
是因为:
switch 的设计初衷是对那些只有少数几个值的类型进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
// long x = 111;
// switch (x) { // Incompatible types. Found: 'long', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'
// case 111:
// System.out.println(111);
// break;
// case 222:
// System.out.println(222);
// break;
// }
Java 中的 switch 语句最初设计时主要是为了处理整数类型的分支条件,因此只支持 byte、short、char 和 int 类型的整数。对于 long、float 和 double 类型,由于它们的取值范围较大,比较操作相对复杂,并且不适合直接映射到跳转表或哈希表中进行快速匹配。
具体来说:
- long 类型的问题: long 类型占据 64 位,其取值范围较大,直接将其映射到跳转表或哈希表可能会带来较大的内存开销和效率问题。
- float 和 double 类型的问题: 浮点数类型(float 和 double)由于浮点数的精度问题,直接比较它们是否相等可能会出现问题。而且浮点数的表示是按照 IEEE 754 标准规定的,涉及到正负零、无穷大以及 NaN 等特殊值的处理,不适合简单的跳转表或哈希表实现。
虽然 switch 语句对 long、float 和 double 类型不提供原生支持,但你仍然可以使用 if-else 结构来处理这些类型的条件分支。使用 if-else 结构更灵活,可以通过条件表达式进行比较和判断,不受限于 switch 语句的限制。
StackOverflow : Why can’t your switch statement data type be long, Java?