|
| 1 | + |
| 2 | +## 一 Java中的值传递和引用传递(非常重要) |
| 3 | + |
| 4 | +**首先要明确的是:“对象传递(数组、类、接口)是引用传递,原始类型数据(整型、浮点型、字符型、布尔型)传递是值传递。”** |
| 5 | + |
| 6 | +### 那么什么是值传递和应用传递呢? |
| 7 | + |
| 8 | +**值传递**是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。) |
| 9 | + |
| 10 | +**引用传递**是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。) |
| 11 | + |
| 12 | +有时候面试官不是单纯问你“Java中是值传递还是引用传递”是什么啊,骚年?而是给出一个例子,然后让你写出答案,这种也常见在笔试题目中!所以,非常重要了,请看下面的例子: |
| 13 | + |
| 14 | +### 值传递和应用传递实例 |
| 15 | + |
| 16 | +#### 1. 值传递 |
| 17 | + |
| 18 | +```java |
| 19 | +public static void main(String[] args) { |
| 20 | + int num1 = 10; |
| 21 | + int num2 = 20; |
| 22 | + |
| 23 | + swap(num1, num2); |
| 24 | + |
| 25 | + System.out.println("num1 = " + num1); |
| 26 | + System.out.println("num2 = " + num2); |
| 27 | +} |
| 28 | + |
| 29 | +public static void swap(int a, int b) { |
| 30 | + int temp = a; |
| 31 | + a = b; |
| 32 | + b = temp; |
| 33 | + |
| 34 | + System.out.println("a = " + a); |
| 35 | + System.out.println("b = " + b); |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +**结果:** |
| 40 | + |
| 41 | +``` |
| 42 | +a = 20 |
| 43 | +b = 10 |
| 44 | +num1 = 10 |
| 45 | +num2 = 20 |
| 46 | +``` |
| 47 | + |
| 48 | +**解析:** |
| 49 | + |
| 50 | +在swap方法中,a、b的值进行交换,并不会影响到num1、num2。因为,a、b中的值,只是从num1、num2的复制过来的。 |
| 51 | +也就是说,a、b相当于num1、num2的副本,副本的内容无论怎么修改,都不会影响到原件本身。 |
| 52 | + |
| 53 | +#### 2. 引用传递 |
| 54 | + |
| 55 | +```java |
| 56 | +public static void main(String[] args) { |
| 57 | + int[] arr = {1,2,3,4,5}; |
| 58 | + |
| 59 | + change(arr); |
| 60 | + |
| 61 | + System.out.println(arr[0]); |
| 62 | +} |
| 63 | + |
| 64 | +public static void change(int[] array) { |
| 65 | +//将数组的第一个元素变为0 |
| 66 | + array[0] = 0; |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +**结果:** |
| 71 | + |
| 72 | +``` |
| 73 | +1 |
| 74 | +0 |
| 75 | +``` |
| 76 | + |
| 77 | +**解析:** |
| 78 | + |
| 79 | +无论是主函数,还是change方法,操作的都是同一个地址值对应的数组。 。因此,外部对引用对象的改变会反映到所有的对象上。 |
| 80 | + |
| 81 | +### 一些特殊的例子 |
| 82 | + |
| 83 | +#### 1. StringBuffer类型传递 |
| 84 | + |
| 85 | +```java |
| 86 | + // 测试引用传递:StringBuffer |
| 87 | + @org.junit.Test |
| 88 | + public void method1() { |
| 89 | + StringBuffer str = new StringBuffer("公众号:Java面试通关手册"); |
| 90 | + System.out.println(str); |
| 91 | + change1(str); |
| 92 | + System.out.println(str); |
| 93 | + } |
| 94 | + |
| 95 | + public static void change1(StringBuffer str) { |
| 96 | + str = new StringBuffer("abc");//输出:“公众号:Java面试通关手册” |
| 97 | + //str.append("欢迎大家关注");//输出:公众号:Java面试通关手册欢迎大家关注 |
| 98 | + //str.insert(3, "(编程)");//输出:公众号(编程):Java面试通关手册 |
| 99 | + |
| 100 | + } |
| 101 | +``` |
| 102 | + |
| 103 | +**结果:** |
| 104 | + |
| 105 | +``` |
| 106 | +公众号:Java面试通关手册 |
| 107 | +公众号:Java面试通关手册 |
| 108 | +``` |
| 109 | + |
| 110 | +**解析:** |
| 111 | + |
| 112 | + |
| 113 | +很多要这个时候要问了:StringBuffer创建的明明也是对象,那为什么输出结果依然是原来的值呢? |
| 114 | + |
| 115 | +因为在`change1`方法内部我们是新建了一个StringBuffer对象,所以`str`指向了另外一个地址,相应的操作也同样是指向另外的地址的。 |
| 116 | + |
| 117 | +那么,如果将`change1`方法改成如下图所示,想必大家应该知道输出什么了,如果你还不知道,那可能就是我讲的有问题了,我反思(开个玩笑,上面程序中已经给出答案): |
| 118 | + |
| 119 | +``` |
| 120 | + public static void change1(StringBuffer str) { |
| 121 | +
|
| 122 | + str.append("欢迎大家关注"); |
| 123 | + str.insert(3, "(编程)"); |
| 124 | + |
| 125 | + } |
| 126 | +``` |
| 127 | + |
| 128 | + |
| 129 | +#### 2. String类型传递 |
| 130 | + |
| 131 | +```java |
| 132 | + // 测试引用传递:Sring |
| 133 | + @org.junit.Test |
| 134 | + public void method2() { |
| 135 | + String str = new String("公众号:Java面试通关手册"); |
| 136 | + System.out.println(str); |
| 137 | + change2(str); |
| 138 | + System.out.println(str); |
| 139 | + } |
| 140 | + |
| 141 | + public static void change2(String str) { |
| 142 | + // str="abc"; //输出:公众号:Java面试通关手册 |
| 143 | + str = new String("abc"); //输出:公众号:Java面试通关手册 |
| 144 | + } |
| 145 | + |
| 146 | +``` |
| 147 | + |
| 148 | +**结果:** |
| 149 | + |
| 150 | +``` |
| 151 | +公众号:Java面试通关手册 |
| 152 | +公众号:Java面试通关手册 |
| 153 | +``` |
| 154 | + |
| 155 | +可以看到不论是执行`str="abc;"`还是`str = new String("abc");`str的输出的值都不变。 |
| 156 | +按照我们上面讲“StringBuffer类型传递”的时候说的,`str="abc;"`应该会让str的输出的值都不变。为什么呢?因为String在创建之后是不可变的。 |
| 157 | + |
| 158 | +#### 3. 一道类似的题目 |
| 159 | + |
| 160 | +下面的程序输出是什么? |
| 161 | +```java |
| 162 | +public class Demo { |
| 163 | + public static void main(String[] args) { |
| 164 | + Person p = new Person("张三"); |
| 165 | + |
| 166 | + change(p); |
| 167 | + |
| 168 | + System.out.println(p.name); |
| 169 | + } |
| 170 | + |
| 171 | + public static void change(Person p) { |
| 172 | + Person person = new Person("李四"); |
| 173 | + p = person; |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +class Person { |
| 178 | + String name; |
| 179 | + |
| 180 | + public Person(String name) { |
| 181 | + this.name = name; |
| 182 | + } |
| 183 | +} |
| 184 | +``` |
| 185 | +很明显仍然会输出`张三`。因为`change`方法中重新创建了一个`Person`对象。 |
| 186 | + |
| 187 | +那么,如果把` change`方法改为下图所示,输出结果又是什么呢? |
| 188 | + |
| 189 | +```java |
| 190 | + public static void change(Person p) { |
| 191 | + p.name="李四"; |
| 192 | + } |
| 193 | +``` |
| 194 | +答案我就不说了,我觉得大家如果认真看完上面的内容之后应该很很清楚了。 |
| 195 | + |
| 196 | +## 二 ==与equals(重要) |
| 197 | + |
| 198 | +**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。 |
| 199 | + |
| 200 | +**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况(前面第1部分已详细介绍过): |
| 201 | +- 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。 |
| 202 | +- 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。 |
| 203 | + |
| 204 | + |
| 205 | +## 三 hashCode与equals(重要) |
| 206 | + |
| 207 | +面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” |
| 208 | + |
| 209 | +### hashCode()介绍 |
| 210 | +hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。 |
| 211 | + |
| 212 | +散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) |
| 213 | + |
| 214 | +### 为什么要有hashCode |
| 215 | + |
| 216 | + |
| 217 | +**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:** |
| 218 | + |
| 219 | +当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。 |
| 220 | + |
| 221 | + |
| 222 | + |
| 223 | +### hashCode()与equals()的相关规定 |
| 224 | + |
| 225 | +1. 如果两个对象相等,则hashcode一定也是相同的 |
| 226 | +2. 两个对象相等,对两个对象分别调用equals方法都返回true |
| 227 | +3. 两个对象有相同的hashcode值,它们也不一定是相等的 |
| 228 | +4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖** |
| 229 | +5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) |
| 230 | + |
| 231 | + |
| 232 | + |
| 233 | + |
| 234 | + |
| 235 | + |
| 236 | + |
0 commit comments