Java基础知识疑难点.md 15.5 KB
Newer Older
S
SnailClimb 已提交
1 2
<!-- TOC -->

L
LIU 已提交
3 4 5 6 7 8 9 10 11 12
- [1. 基础](#1-基础)
    - [1.1. 正确使用 equals 方法](#11-正确使用-equals-方法)
    - [1.2. 整型包装类值的比较](#12-整型包装类值的比较)
    - [1.3. BigDecimal](#13-bigdecimal)
        - [1.3.1. BigDecimal 的用处](#131-bigdecimal-的用处)
        - [1.3.2. BigDecimal 的大小比较](#132-bigdecimal-的大小比较)
        - [1.3.3. BigDecimal 保留几位小数](#133-bigdecimal-保留几位小数)
        - [1.3.4. BigDecimal 的使用注意事项](#134-bigdecimal-的使用注意事项)
        - [1.3.5. 总结](#135-总结)
    - [1.4. 基本数据类型与包装数据类型的使用标准](#14-基本数据类型与包装数据类型的使用标准)
李鹏 已提交
13
- [2. 集合](#_2-集合)
L
LIU 已提交
14 15 16 17 18 19 20
    - [2.1. Arrays.asList()使用指南](#21-arraysaslist使用指南)
        - [2.1.1. 简介](#211-简介)
        - [2.1.2. 《阿里巴巴Java 开发手册》对其的描述](#212-阿里巴巴java-开发手册对其的描述)
        - [2.1.3. 使用时的注意事项总结](#213-使用时的注意事项总结)
        - [2.1.4. 如何正确的将数组转换为ArrayList?](#214-如何正确的将数组转换为arraylist)
    - [2.2. Collection.toArray()方法使用的坑&如何反转数组](#22-collectiontoarray方法使用的坑如何反转数组)
    - [2.3. 不要在 foreach 循环里进行元素的 remove/add 操作](#23-不要在-foreach-循环里进行元素的-removeadd-操作)
S
SnailClimb 已提交
21 22 23

<!-- /TOC -->

24
# 1. 基础
S
SnailClimb 已提交
25

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
## 1.1. 正确使用 equals 方法

Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。 

举个例子:

```java
// 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
String str = null;
if (str.equals("SnailClimb")) {
  ...
} else {
  ..
}
```

运行上面的程序会抛出空指针异常,但是我们把第二行的条件判断语句改为下面这样的话,就不会抛出空指针异常,else 语句块得到执行。:

```java
"SnailClimb".equals(str);// false 
```
不过更推荐使用 `java.util.Objects#equals`(JDK7 引入的工具类)。

```java
Objects.equals(null,"SnailClimb");// false
```
我们看一下`java.util.Objects#equals`的源码就知道原因了。
```java
public static boolean equals(Object a, Object b) {
ishellhub's avatar
ishellhub 已提交
55 56 57
    // 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
    return (a == b) || (a != null && a.equals(b));
}
58 59 60 61 62 63
```

**注意:**

Reference:[Java中equals方法造成空指针异常的原因及解决方案](https://blog.csdn.net/tick_tock97/article/details/72824894)

S
SnailClimb 已提交
64
- 每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。
65
- 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中`null == null`将返回true。
66 67
- 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常

H
hughyu 已提交
68
## 1.2. 整型包装类值的比较
S
SnailClimb 已提交
69

H
hughyu 已提交
70
所有整型包装类对象值的比较必须使用equals方法。
S
SnailClimb 已提交
71 72 73 74 75 76 77 78 79 80

先看下面这个例子:

```java
Integer x = 3;
Integer y = 3;
System.out.println(x == y);// true
Integer a = new Integer(3);
Integer b = new Integer(3);
System.out.println(a == b);//false
S
SnailClimb 已提交
81
System.out.println(a.equals(b));//true
S
SnailClimb 已提交
82 83
```

S
SnailClimb 已提交
84
当使用自动装箱方式创建一个Integer对象时,当数值在-128 ~127时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。所以上述代码中,x和y引用的是相同的Integer对象。	
S
SnailClimb 已提交
85

L
LIU 已提交
86
**注意:** 如果你的IDE(IDEA/Eclipse)上安装了阿里巴巴的p3c插件,这个插件如果检测到你用 ==的话会报错提示,推荐安装一个这个插件,很不错。
S
SnailClimb 已提交
87

88
## 1.3. BigDecimal
S
SnailClimb 已提交
89

90
### 1.3.1. BigDecimal 的用处
S
SnailClimb 已提交
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106

《阿里巴巴Java开发手册》中提到:**浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。** 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:

```java
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999964
System.out.println(a == b);// false
```
具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(**精度丢失**),我们如何解决这个问题呢?一种很常用的方法是:**使用使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。**

```java
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
ishellhub's avatar
ishellhub 已提交
107 108 109 110 111 112 113

BigDecimal x = a.subtract(b); 
BigDecimal y = b.subtract(c); 

System.out.println(x); /* 0.1 */
System.out.println(y); /* 0.1 */
System.out.println(Objects.equals(x, y)); /* true */
S
SnailClimb 已提交
114 115
```

116
### 1.3.2. BigDecimal 的大小比较
S
SnailClimb 已提交
117

ishellhub's avatar
ishellhub 已提交
118
`a.compareTo(b)` : 返回 -1 表示 `a` 小于 `b`,0 表示 `a` 等于 `b` , 1表示 `a` 大于 `b`
S
SnailClimb 已提交
119 120 121 122 123 124

```java
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1
```
125
### 1.3.3. BigDecimal 保留几位小数
S
SnailClimb 已提交
126 127 128 129 130 131 132 133 134

通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。

```java
BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
System.out.println(n);// 1.255
```

135
### 1.3.4. BigDecimal 的使用注意事项
S
SnailClimb 已提交
136

137
注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 **BigDecimal(String)** 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。
S
SnailClimb 已提交
138

S
SnailClimb 已提交
139
![《阿里巴巴Java开发手册》对这部分BigDecimal的描述](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/BigDecimal.png)
S
SnailClimb 已提交
140

141
### 1.3.5. 总结
S
SnailClimb 已提交
142 143 144 145 146

BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。

BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念

147 148 149 150 151 152 153 154 155 156
## 1.4. 基本数据类型与包装数据类型的使用标准

Reference:《阿里巴巴Java开发手册》

- 【强制】所有的 POJO 类属性必须使用包装数据类型。
- 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
- 【推荐】所有的局部变量使用基本数据类型。

比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样.

H
hughyu 已提交
157
**说明** :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。
158 159 160

**正例** : 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。

S
SnailClimb 已提交
161
**反例** : 比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
162 163

# 2. 集合
S
SnailClimb 已提交
164

165
## 2.1. Arrays.asList()使用指南
S
SnailClimb 已提交
166

L
Lingxu Meng 已提交
167
最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。
S
SnailClimb 已提交
168

169
### 2.1.1. 简介
S
SnailClimb 已提交
170 171 172 173

`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个List集合。

```java
ishellhub's avatar
ishellhub 已提交
174
String[] myArray = {"Apple", "Banana", "Orange"};
S
SnailClimb 已提交
175 176 177 178 179 180 181 182 183
List<String> myList = Arrays.asList(myArray);
//上面两个语句等价于下面一条语句
List<String> myList = Arrays.asList("Apple","Banana", "Orange");
```

JDK 源码对于这个方法的说明:

```java
/**
ishellhub's avatar
ishellhub 已提交
184 185 186
  *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,
  * 与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。
  */
S
SnailClimb 已提交
187 188 189 190 191
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}
```

192
### 2.1.2. 《阿里巴巴Java 开发手册》对其的描述
S
SnailClimb 已提交
193 194 195 196 197

`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴Java 开发手册》对于这个方法有如下描述:

![阿里巴巴Java开发手-Arrays.asList()方法](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/阿里巴巴Java开发手-Arrays.asList()方法.png)

198
### 2.1.3. 使用时的注意事项总结
S
SnailClimb 已提交
199 200 201 202 203 204

**传递的数组必须是对象数组,而不是基本类型。** 

`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。

```java
ishellhub's avatar
ishellhub 已提交
205
int[] myArray = {1, 2, 3};
S
SnailClimb 已提交
206 207 208 209
List myList = Arrays.asList(myArray);
System.out.println(myList.size());//1
System.out.println(myList.get(0));//数组地址值
System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
ishellhub's avatar
ishellhub 已提交
210
int[] array = (int[]) myList.get(0);
S
SnailClimb 已提交
211 212 213 214 215 216 217
System.out.println(array[0]);//1
```
当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组,这也就解释了上面的代码。

我们使用包装类型数组就可以解决这个问题。

```java
ishellhub's avatar
ishellhub 已提交
218
Integer[] myArray = {1, 2, 3};
S
SnailClimb 已提交
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
```

**使用集合的修改方法:`add()`、`remove()`、`clear()`会抛出异常。**

```java
List myList = Arrays.asList(1, 2, 3);
myList.add(4);//运行时报错:UnsupportedOperationException
myList.remove(1);//运行时报错:UnsupportedOperationException
myList.clear();//运行时报错:UnsupportedOperationException
```

`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。

```java
List myList = Arrays.asList(1, 2, 3);
System.out.println(myList.getClass());//class java.util.Arrays$ArrayList
```

下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。

```java
  private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        ...

        @Override
        public E get(int index) {
          ...
        }

        @Override
        public E set(int index, E element) {
          ...
        }

        @Override
        public int indexOf(Object o) {
          ...
        }

        @Override
        public boolean contains(Object o) {
           ...
        }

        @Override
        public void forEach(Consumer<? super E> action) {
          ...
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
          ...
        }

        @Override
        public void sort(Comparator<? super E> c) {
          ...
        }
    }
```

我们再看一下`java.util.AbstractList``remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`

```java
public E remove(int index) {
    throw new UnsupportedOperationException();
}
```

290
### 2.1.4. 如何正确的将数组转换为ArrayList?
S
SnailClimb 已提交
291 292 293 294 295 296 297 298 299 300 301 302 303

stackoverflow:https://dwz.cn/vcBkTiTW

**1. 自己动手实现(教育目的)**

```java
//JDK1.5+
static <T> List<T> arrayToList(final T[] array) {
  final List<T> l = new ArrayList<T>(array.length);

  for (final T s : array) {
    l.add(s);
  }
ishellhub's avatar
ishellhub 已提交
304
  return l;
S
SnailClimb 已提交
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
}
```

```java
Integer [] myArray = { 1, 2, 3 };
System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList
```

**2. 最简便的方法(推荐)**

```java
List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
```

**3. 使用 Java8 的Stream(推荐)**

```java
Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());
//基本类型也可以实现转换(依赖boxed的装箱操作)
int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
```

**4. 使用 Guava(推荐)**

对于不可变集合,你可以使用[`ImmutableList`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java)类及其[`of()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101)[`copyOf()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225)工厂方法:(参数不能为空)

```java
List<String> il = ImmutableList.of("string", "elements");  // from varargs
List<String> il = ImmutableList.copyOf(aStringArray);      // from array
```
对于可变集合,你可以使用[`Lists`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java)类及其[`newArrayList()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87)工厂方法:

```java
List<String> l1 = Lists.newArrayList(anotherListOrCollection);    // from collection
List<String> l2 = Lists.newArrayList(aStringArray);               // from array
List<String> l3 = Lists.newArrayList("or", "string", "elements"); // from varargs
```

**5. 使用 Apache Commons Collections**

```java
List<String> list = new ArrayList<String>();
CollectionUtils.addAll(list, str);
S
SnailClimb 已提交
350 351
```

ishellhub's avatar
ishellhub 已提交
352 353 354 355 356 357 358 359
**6. 使用 Java9 的 `List.of()`方法**
``` java
Integer[] array = {1, 2, 3};
List<Integer> list = List.of(array);
System.out.println(list); /* [1, 2, 3] */
/* 不支持基本数据类型 */
```

360
## 2.2. Collection.toArray()方法使用的坑&如何反转数组
S
SnailClimb 已提交
361 362 363 364 365 366 367 368 369 370 371 372

该方法是一个泛型方法:`<T> T[] toArray(T[] a);` 如果`toArray`方法中没有传递任何参数的话返回的是`Object`类型数组。

```java
String [] s= new String[]{
    "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
};
List<String> list = Arrays.asList(s);
Collections.reverse(list);
s=list.toArray(new String[0]);//没有指定类型的话会报错
```

S
SnailClimb 已提交
373 374
由于JVM优化,`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。详见:<https://shipilev.net/blog/2016/arrays-wisdom-ancients/>

375
## 2.3. 不要在 foreach 循环里进行元素的 remove/add 操作
S
SnailClimb 已提交
376

S
SnailClimb 已提交
377 378 379 380
如果要进行`remove`操作,可以调用迭代器的 `remove `方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身`remove/add`方法,迭代器都将抛出一个`ConcurrentModificationException`,这就是单线程状态下产生的 **fail-fast 机制**

> **fail-fast 机制** :多个线程对 fail-fast 集合进行修改的时,可能会抛出ConcurrentModificationException,单线程下也会出现这种情况,上面已经提到过。

ishellhub's avatar
ishellhub 已提交
381 382 383 384 385 386 387 388 389 390
Java8开始,可以使用`Collection#removeIf()`方法删除满足特定条件的元素,如
``` java
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; ++i) {
    list.add(i);
}
list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */
System.out.println(list); /* [1, 3, 5, 7, 9] */
```

S
SnailClimb 已提交
391 392
`java.util`包下面的所有的集合类都是fail-fast的,而`java.util.concurrent`包下面的所有的类都是fail-safe的。

S
SnailClimb 已提交
393
![不要在 foreach 循环里进行元素的 remove/add 操作](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/foreach-remove:add.png)
S
SnailClimb 已提交
394 395 396