第一周(2018-8-7).md 10.8 KB
Newer Older
S
Snailclimb 已提交
1

S
Snailclimb 已提交
2

S
Snailclimb 已提交
3
## 一 Java中的值传递和引用传递(非常重要)
S
Snailclimb 已提交
4

S
Snailclimb 已提交
5
**首先要明确的是:“对象传递(数组、类、接口)是引用传递,原始类型数据(整型、浮点型、字符型、布尔型)传递是值传递。”**
S
Snailclimb 已提交
6

S
Snailclimb 已提交
7
### 那么什么是值传递和应用传递呢?
8

S
Snailclimb 已提交
9
**值传递**是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。)
10

S
Snailclimb 已提交
11
**引用传递**是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。)
12

S
Snailclimb 已提交
13
有时候面试官不是单纯问你“Java中是值传递还是引用传递”是什么啊,骚年?而是给出一个例子,然后让你写出答案,这种也常见在笔试题目中!所以,非常重要了,请看下面的例子:
14

S
Snailclimb 已提交
15
### 值传递和应用传递实例
16

S
Snailclimb 已提交
17
#### 1. 值传递
18

S
Snailclimb 已提交
19 20 21 22
```java
public static void main(String[] args) {
    int num1 = 10;
    int num2 = 20;
23

S
Snailclimb 已提交
24
    swap(num1, num2);
25

S
Snailclimb 已提交
26 27 28
    System.out.println("num1 = " + num1);
    System.out.println("num2 = " + num2);
}
29

S
Snailclimb 已提交
30 31 32 33
public static void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
34

S
Snailclimb 已提交
35 36 37 38
    System.out.println("a = " + a);
    System.out.println("b = " + b);
}
```
39

S
Snailclimb 已提交
40
**结果:**
41

S
Snailclimb 已提交
42 43 44 45 46 47
```
a = 20
b = 10
num1 = 10
num2 = 20
```
48

S
Snailclimb 已提交
49
**解析:**
50

S
Snailclimb 已提交
51 52
在swap方法中,a、b的值进行交换,并不会影响到num1、num2。因为,a、b中的值,只是从num1、num2的复制过来的。
也就是说,a、b相当于num1、num2的副本,副本的内容无论怎么修改,都不会影响到原件本身。
53

S
Snailclimb 已提交
54
#### 2. 引用传递
55

S
Snailclimb 已提交
56 57 58
```java
public static void main(String[] args) {
    int[] arr = {1,2,3,4,5};
59

S
Snailclimb 已提交
60
    change(arr);
61

S
Snailclimb 已提交
62 63
    System.out.println(arr[0]);
}
64

S
Snailclimb 已提交
65 66 67
public static void change(int[] array) {
//将数组的第一个元素变为0
    array[0] = 0;
68 69 70
}
```

S
Snailclimb 已提交
71 72 73 74 75 76
**结果:**

```
1
0
```
77

S
Snailclimb 已提交
78
**解析:**
79

S
Snailclimb 已提交
80
无论是主函数,还是change方法,操作的都是同一个地址值对应的数组。 。因此,外部对引用对象的改变会反映到所有的对象上。
81

S
Snailclimb 已提交
82
### 一些特殊的例子
83

S
Snailclimb 已提交
84
#### 1. StringBuffer类型传递
85 86

```java
S
Snailclimb 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
	// 测试引用传递:StringBuffer
	@org.junit.Test
	public void method1() {
		StringBuffer str = new StringBuffer("公众号:Java面试通关手册");
		System.out.println(str);
		change1(str);
		System.out.println(str);
	}

	public static void change1(StringBuffer str) {
		str = new StringBuffer("abc");//输出:“公众号:Java面试通关手册”
		//str.append("欢迎大家关注");//输出:公众号:Java面试通关手册欢迎大家关注
		//str.insert(3, "(编程)");//输出:公众号(编程):Java面试通关手册
		
	}
```
103

S
Snailclimb 已提交
104
**结果:**
木木匠 已提交
105

S
Snailclimb 已提交
106 107 108 109
```
公众号:Java面试通关手册
公众号:Java面试通关手册
```
110

S
Snailclimb 已提交
111 112 113 114 115 116 117 118
**解析:**


很多要这个时候要问了:StringBuffer创建的明明也是对象,那为什么输出结果依然是原来的值呢?

因为在`change1`方法内部我们是新建了一个StringBuffer对象,所以`str`指向了另外一个地址,相应的操作也同样是指向另外的地址的。

那么,如果将`change1`方法改成如下图所示,想必大家应该知道输出什么了,如果你还不知道,那可能就是我讲的有问题了,我反思(开个玩笑,上面程序中已经给出答案):
119 120

```
S
Snailclimb 已提交
121
	public static void change1(StringBuffer str) {
122

S
Snailclimb 已提交
123 124 125 126 127
		str.append("欢迎大家关注");
		str.insert(3, "(编程)");
		
	}
```
128 129


S
Snailclimb 已提交
130
#### 2. String类型传递
131

S
Snailclimb 已提交
132 133 134 135 136 137 138 139 140 141 142 143 144 145
```java
	// 测试引用传递:Sring
	@org.junit.Test
	public void method2() {
		String str = new String("公众号:Java面试通关手册");
		System.out.println(str);
		change2(str);
		System.out.println(str);
	}

	public static void change2(String str) {
		// str="abc"; //输出:公众号:Java面试通关手册
		str = new String("abc"); //输出:公众号:Java面试通关手册
	}
146

S
Snailclimb 已提交
147
```
148

S
Snailclimb 已提交
149
**结果:**
150

S
Snailclimb 已提交
151 152 153 154
```
公众号:Java面试通关手册
公众号:Java面试通关手册
```
155

S
Snailclimb 已提交
156 157
可以看到不论是执行`str="abc;"`还是`str = new String("abc");`str的输出的值都不变。
按照我们上面讲“StringBuffer类型传递”的时候说的,`str="abc;"`应该会让str的输出的值都不变。为什么呢?因为String在创建之后是不可变的。
158

S
Snailclimb 已提交
159
#### 3. 一道类似的题目
160

S
Snailclimb 已提交
161 162 163 164 165
下面的程序输出是什么?
```java
public class Demo {
	public static void main(String[] args) {
		Person p = new Person("张三");
S
Snailclimb 已提交
166

S
Snailclimb 已提交
167
		change(p);
168

S
Snailclimb 已提交
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
		System.out.println(p.name);
	}

	public static void change(Person p) {
		Person person = new Person("李四");
		p = person;
	}
}

class Person {
	String name;

	public Person(String name) {
		this.name = name;
	}
184 185
}
```
S
Snailclimb 已提交
186 187 188
很明显仍然会输出`张三`。因为`change`方法中重新创建了一个`Person`对象。

那么,如果把` change`方法改为下图所示,输出结果又是什么呢?
189 190

```java
S
Snailclimb 已提交
191 192 193
	public static void change(Person p) {
		p.name="李四";
	}
194
```
S
Snailclimb 已提交
195
答案我就不说了,我觉得大家如果认真看完上面的内容之后应该很很清楚了。
196

S
Snailclimb 已提交
197
## 二  ==与equals(重要)
198

S
Snailclimb 已提交
199 200 201 202 203
**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)

**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-  情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
- 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。
204

S
Snailclimb 已提交
205

S
Snailclimb 已提交
206 207 208 209
**举个例子:**

```java
public class test1 {
S
Snailclimb 已提交
210
    public static void main(String[] args) {
S
Snailclimb 已提交
211 212 213 214 215 216 217 218 219 220 221 222
        String a = new String("ab"); // a 为一个引用
        String b = new String("ab"); // b为另一个引用,对象的内容一样
        String aa = "ab"; // 放在常量池中
        String bb = "ab"; // 从常量池中查找
        if (aa == bb) // true
            System.out.println("aa==bb");
        if (a == b) // false,非同一对象
            System.out.println("a==b");
        if (a.equals(b)) // true
            System.out.println("aEQb");
        if (42 == 42.0) { // true
            System.out.println("true");
S
Snailclimb 已提交
223 224
        }
    }
S
Snailclimb 已提交
225 226
}
```
227

S
Snailclimb 已提交
228 229 230
**说明:**
- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
231 232 233



S
Snailclimb 已提交
234 235 236 237 238
## 三  hashCode与equals(重要)

面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”

### hashCode()介绍
S
Snailclimb 已提交
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。

```java
    /**
     * Returns a hash code value for the object. This method is
     * supported for the benefit of hash tables such as those provided by
     * {@link java.util.HashMap}.
     * <p>
     * As much as is reasonably practical, the hashCode method defined by
     * class {@code Object} does return distinct integers for distinct
     * objects. (This is typically implemented by converting the internal
     * address of the object into an integer, but this implementation
     * technique is not required by the
     * Java&trade; programming language.)
     *
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.lang.System#identityHashCode
     */
    public native int hashCode();
```
S
Snailclimb 已提交
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

### 为什么要有hashCode


**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:**

当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。



### hashCode()与equals()的相关规定

1. 如果两个对象相等,则hashcode一定也是相同的
2. 两个对象相等,对两个对象分别调用equals方法都返回true
3. 两个对象有相同的hashcode值,它们也不一定是相等的
4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖**
5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

280
### 为什么两个对象有相同的hashcode值,它们也不一定是相等的?
281

282
在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
283

284
因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。 
S
Snailclimb 已提交
285

286
我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
S
Snailclimb 已提交
287

S
Snailclimb 已提交
288
参考:
S
Snailclimb 已提交
289

S
Snailclimb 已提交
290
[https://blog.csdn.net/zhzhao999/article/details/53449504](https://blog.csdn.net/zhzhao999/article/details/53449504)
S
Snailclimb 已提交
291

S
Snailclimb 已提交
292
[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html)
293

S
Snailclimb 已提交
294
[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html)
295

S
Snailclimb 已提交
296
[https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html)
297