---
url: /doc/Java/1uh1yhxh/index.md
---
## 12.1 包装类

> 包装类（Wrapper）：针对 八种基本数据类型 相应的 引用类型
>
> 有了类的特点，就可以调用类中的方法

| 基本数据类型 | 包装类    | 父类   |
| ------------ | --------- | ------ |
| boolean      | Boolean   | Object |
| char         | Character | Object |
| int          | Integer   | Number |
| float        | Float     | Number |
| double       | Double    | Number |
| long         | Long      | Number |
| short        | Short     | Number |
| byte         | Byte      | Number |
| void         | Void      | Object |

### 12.1.1 装箱和拆箱

* **手动装箱和拆箱（JDK 5 以前）**

  ```
  int n1 = 100;
  Integer integer = new Integer(n1);			// 手动装箱
  Integer integer2 = Integer.valueOf(n1);		// 手动装箱
  int i = integer.intValue();					// 手动拆箱JAVA
  ```

* **自动装箱和拆箱（JDK 5 以后）**

  ```
  n2 = 200;
  Integer integer3 = n2;						// 自动装箱
  int j = integer3;							// 自动拆箱JAVA
  ```

  虽然可以自动装箱、拆箱，但使用 == 直接比较两个包装类时，仍然是比较其地址。以下比较通常会失败：

  ```
  Integer ia = 1000;
  Integer ib = 1000;
  System.out.print(ia == ib);					// falseJAVA
  ```

  但，Java 实现仍有可能使其成立。Byte、Boolean 以及 Short、Integer 中 \[-128, 127] 间的值已被包装到固定的对象中。对他们的比较可以成功。

  ```
  Integer ia = 127;
  Integer ib = 127;
  System.out.print(ia == ib);					// trueJAVA
  ```

  由此可见，使用 == 直接比较两个包装类会带来不确定性。**尽量使用 equals 方法对包装类进行比较。**

装箱与拆箱是 **编译器** 的工作。在生成可执行的字节码文件时，编译器已经插入了必要的方法调用。

### 12.1.2 包装类和 `String` 的相互转换

* 包装类转 `String`：

  > ```
  > >Integer integer = 100;
  > >String str1 = integer + "";					//方法1（自动拆箱）
  > >String str2 = integer.toString();			//方法2（toString方法）
  > >String str3 = String.valueOf(integer);		//方法3（自动拆箱）JAVA
  > ```

* `String` 转包装类：

  > ```
  > String str4 = "100";
  > Integer integer2 = Integer.parseInt(str4);	//方法1（自动装箱）
  > Integer integer3 = new Integer(str4);		//方法2（构造器）JAVA
  > ```

### 12.1.3 包装类的常用方法

* `Integer.MIN_VALUE`：返回最大值

* `Double.MAX_VALUE`：返回最小值

* `byteValue()`、`doubleValue()`、`floatValue()`、`intValue()`、`longValue()`

  按各种基本数据类型返回该对象的值

* `Character.isDigit(int)`：判断是不是数字

  `Character.isLetter(int)`：判断是不是字母

  `Character.isUpperCase(int)`：判断是不是大写字母

  `Character.isLowerCase(int)`：判断是不是小写字母

  `Characher.isWhitespace(int)`：判断是不是空格

* `Character.toUpperCase(int)`：转成大写字母

  `Character.toLowerCase(int)`：转成小写字母

* `Integer.parseInt(string)`：将 String 内容转为 int

  `Double.parseDouble(string)`

* `Integer.toBinaryString(int)`：将数字转为 2 进制表示的字符串

  `Integer.toHexString(int)`：将数字转为 16 进制表示的字符串

  `Integer.toOctalString(int)`：将数字转为 8 进制表示的字符串

  特别地，浮点数类型的包装类只有转成 16 进制的方法。而 Short、Byte 及其他包装类无此方法

* `int Integer.bitCount(i int)`：统计指定数字的二进制格式中 1 的数量

### 12.1.4 strictfp 关键字

> 由于不同处理器对于浮点数寄存采取不同策略（有些处理器使用 64 位寄存 double，有些则是 80 位），对于浮点数的运算在不同平台上可能出现不同结果。

使用 strictfp 关键字标记的方法或类中，所有指令都会使用严格统一的浮点数运算。

比如，把 main 方法标记为 strictfp

```
public static strictfp void main(String[] args) {
    double ᓚᘏᗢ = 1 / 13.97;
    System.out.println(ᓚᘏᗢ);
}JAVA
```

## 12.2 `String` 类

1. `String` 对象用于保存字符串，也就是一组字符序列

2. 字符串常量对象是用双引号扩起的字符序列。例如 `"你好"`

3. 字符串的字符使用 Unicode 字符编码。一个字符（不论字母汉字）占 2 字节

4. 常用构造器：

   * `String str1 = new String();`

   * `String str2 = new String(String original);`

   * `String str3 = new String(char[] a);`

   * `String str4 = new String(char[] a, int startIndex, int count);`

     这句意思是：`char[]` 从 `startIndex` 起的 `count` 个字符

5. `String` 实现了接口 `Serializable` 和 `Comparable` ，可以 串行化和 比较大小

   ***串行化：即，可以被网络传输，也能保存到文件***

6. `String` 是 `final` 类，不能被继承

7. `String` 有属性 `private final char[] value;` 用于存放字符串内容。

   `value` 是 `final` 属性。其在栈中的地址不能修改，但堆中的内容可以修改。

### 12.2.1 `String` 构造方法

* 直接指定

  ```
  String str1 = "哈哈哈";
  JAVA
  ```

  该方法：先从常量池看是否有 `"哈哈哈"` 数据空间。有的场合，指向那个空间；否则重新创建然后指向。

  这个方法，`str1` 指向 常量池中的地址。

* 构造器

  ```
  String str2 = new String("嘿嘿嘿");
  JAVA
  ```

  该方法：先在堆中创建空间，里面维护一个 `value` 属性，指向 或 创建后指向 常量池的 `"嘿嘿嘿"` 空间。

  这个方法，`str2` 指向 堆中的地址

### 12.2.2 字符串的特性

* 常量相加，看的是池

  ```
  String str1 = "aa" + "bb";				//常量相加，看的是池
  JAVA
  ```

  > 上例由于构造器自身优化，相当于 `String str1 = "aabb";`

* 变量相加，是在堆中

  ```
  String a = "aa";
  String b = "bb";
  String str2 = a + b;					//变量相加，是在堆中JAVA
  ```

  > 上例的底层是如下代码
  >
  > ```
  > StringBuilder sb = new StringBuilder();
  > sb.append(a);
  > sb.append(b);
  > str2 = sb.toString();	//sb.toString()：return new String(value, 0, count);JAVA
  > ```

### 12.2.3 `String` 的常用方法

以下方法不需死记硬背，手熟自然牢记

* `boolean equals(String s)`：区分大小写，判断内容是否相等

  `boolean equalsIgnoreCase(String s)`：判断内容是否相等（忽略大小写）

* `boolean empty()`：返回是否为空

* `int charAt(int index)`：获取某索引处的字符（代码单元）。

  必须用 `char c = str.charAt(15);`，不能用 `char c = str[15];`

  `int codePointAt(int index)`

  `int length()`：获取字符（**代码单元**）的个数

  —— 代码单元，见 2.6.2.4 字符本质与编码表

  `IntStream codePoints()`：返回字符串中全部码点构成的流

  `long codePoints().count()`：返回真正长度（码点数量）

  —— 流，见 [27.4 Stream API)](https://i-melody.github.io/2022/03/09/Java/%E5%85%A5%E9%97%A8%E9%98%B6%E6%AE%B5/27%20Java%208%20%E6%96%B0%E7%89%B9%E6%80%A7/#27-4-Stream-API)

* `int indexOf(String str)`：获取字符（串）在字符串中第一次出现的索引。如果找不到，返回 -1

  `int indexOf(int char)` 参数也可以传入一个 int。由于自动类型转换的存在，也能填入 char

  `int indexOf(String str, int index)`：从 index 处（包含）开始查找指定字符（串）

  `int lastIndexOf(String str)`：获取字符在字符串中最后一次出现的索引。如果找不到，返回 -1

* `String substring(int start, int end)`：返回截取指定范围 `[start, end) `的 **新** 字符串

  `String substring(int index)`：截取 index（包含）之后的部分

* `String trim()`：返回去前后空格的新字符串

* `String toUperCase()`：返回字母全部转为大写的新字符串

  `String toLowerCase()`：返回字母全部转为小写的新字符串

* `String concat(String another)`：返回拼接字符串

* `String replace(char oldChar, char newChar)`：替换字符串中的元素

  ```
  String str1 = "Foolish cultists";
  String str2 = str1.replace("cultists", "believers");	//str1不变，str2为改变的值JAVA
  ```

* `String[] split(String regex)`：分割字符串。

  对于某些分割字符，我们需要转义

  ```
  String str1 = "aaa,bbb,ccc";
  String[] strs1 = str1.split(",");		//这个场合，strs = {"aaa", "bbb", "ccc"};4
  String str2 = "aaa\bbb\ccc";
  String[] strs2 = str2.split("\\");		//"\" 是特殊字符，需要转义为 "\\"JAVA
  ```

* `int compareTo(String another)`：按照字典顺序比较两个字符串（的大小）。

  返回出现第一处不同的字符的编号差。前面字符相同，长度不同的场合，返回那个长度差。

  ```
  String str1 = "ccc";
  String str2 = "ca";
  String str3 = "ccc111abc";
  int n1 = str1.compareTo(str2);			//此时 n1 = 'c' - 'a' = 2
  int n2 = str1.compareTo(str3);			//此时 n2 = str1,length - str3.length = -6
  int n3 = str1.compareTo(str1);			//此时 n3 = 0JAVA
  ```

* `char[] toCharArray()`：转换成字符数组

  `byte[] getBytes()`：字符串转为字节数组

* `String String.format(String format, Object... args)`：（静态方法）格式字符串

  ```
  String name = "Roin";
  String age = "1M";
  String state = "computer";
  String formatStr = "I am %s, I am %s old, I am a %s";
  String str = String.format(formatStr, name, age, state);
  //其中 %s 是占位符。此时，str = "I am Roin, I am 1M old, I am a computer";
  //%s 表示字符串替换；%d 表示整数替换；#.2f 表示小数（四舍五入保留2位）替换；%c 表示字符替换JAVA
  ```

* `String join(deli, ele...)`：拼接字符串（`ele...`），以 `deli` 间隔。

* `boolean startsWith(str)`：测试 str 是否为当前字符串的前缀

* `String repeat(int n)`：返回该字符串重复 n 次的结果

## 12.3 `StringBuffer` 类

> `java.lang.StringBuffer` 代表可变的字符序列。可以对字符串内容进行增删。
>
> 很多方法和 `String` 相同，但 `StringBuffer` 是可变长度。同时，`StringBuffer` 是一个容器

1. `StringBuffer` 的直接父类是 `AbstractStringBuffer`
2. `StringBuffer` 实现了 `Serialiazable`，可以串行化
3. 在父类中，`AbstractStringBuffer` 有属性 `char[] value` 不是 `final`
4. `StringBuffer` 是一个 `final` 类，不能被继承

**`String` 对比 `StringBuffer`**

* `String` 保存字符串常量，其中的值不能更改。每次更新实际上是更改地址，效率较低
* `StringBuffer` 保存字符串变量，里面的值可以更改。每次更新是更新内容，不用每次更新地址。

### 12.3.1 `StringBuffer` 构造方法

1. 无参构造

   ```
   StringBuffer strb1 = new StringBuffer();
   JAVA
   ```

   > 创造一个 16 位容量的空 `StringBuffer`

2. 传入字符串构造

   ```
   String str1 = "abcabc";
   StringBuffer strb2 = new StringBuffer(str1);JAVA
   ```

   > （上例）创造一个 str1.length + 16 容量的 `StringBuffer`

3. 指定容量构造

   ```
   StringBuffer strb3 = new StringBuffer(3);
   JAVA
   ```

   > （上例）创造一个 3 容量的空 `StringBuffer`

### 12.3.2 `String` 和 `StringBuffer`的转换

1. 转 `StringBuffer`

   ```
   String str1 = "abcabc";
   StringBuffer strb1 = new StringBuffer(str1);	//方法1（构造器）
   StringBuffer strb1 = new StringBuffer();
   strb1 = strb1.append(str1);						//方法2（先空再append）JAVA
   ```

2. 转 `String`

   ```
   String str2 = strb1.toString();					//方法1（toString）
   String str3 = new String(strb1);				//方法2（构造器）JAVA
   ```

### 12.3.3 `StringBuffer` 的常用方法

* `append(char c)`：增加

  `append(String s)` 参数也能是字符串

  特别的，`append(null);` 的场合，等同于 `append("null");`

* `delete(start, end)`：删减 \[start, end) 的内容

* `replace(start, end, string)`：将 start 与 end 间的内容替换为 string

* `indexOf`：查找指定字符串第一次出现时的索引。没找到的场合返回 -1

* `insert`：在指定索引位置之前插入指定字符串

* `length()`：返回字符长度

  `capacity()`：返回当前的容量

  String 类对象分配内存时，按照对象中所含字符个数等量分配。

  StringBuffer 类对象分配内存时，除去字符所占空间外，会另加 16 字符大小的缓冲区。

  对于 `length()` 方法，返回的是字符串长度。对于 `capacity()` 方法，返回的是 字符串 + 缓冲区 的大小。

## 12.4 `StringBuilder` 类

> 一个可变的字符序列。此类提供一个与 `StringBuffer` 兼容的 API，但不保证同步（有线程安全问题）。该类被设计成 `StringBuffer` 的一个简易替换，**用在字符串缓冲区被单个线程使用的时候**。如果可能，建议优先使用该类。因为在大多数实现中，它比起 `StringBuffer` 要快。
>
> 在 `StringBuilder` 是的主要操作是 `append` 和 `insert` 方法。可以重载这些方法，以接受任意类型的数据。

1. `StringBuilder` 也继承了 `AbstractStringBuffer`
2. `StringBuilder` 也实现了 `Serialiazable`，可以串行化
3. 仍然是在父类中有属性 `char[] value` ，而且不是 `final`
4. `StringBuilder` 也是一个 `final` 类，不能被继承
5. `StringBuilder` 的方法，没有做互斥的处理（没有 `synchronize`），故而存在线程安全问题

### 12.4.1 `String`、`StringBuffer`、`StringBuilder` 的对比

1. `StringBuilder` 和 `StringBuffer` 类似，均代表可变字符序列，而且方法也一样

2. `String`：不可变字符序列，效率低，但复用率高

3. `StringBuffer`：可变字符序列，效率较高，线程安全

4. `StringBuilder`：可变字符序列，效率最高，存在线程安全问题

5. `String` 为何效率低：

   > ```
   > String str1 = "aa";					//创建了一个字符串
   > for(int n = 0; n < 100; n++){
   > 	str1 += "bb";					//这里，原先的字符串被丢弃，创建新字符串
   > }									//多次执行后，大量副本字符串留在内存中
   > 									//导致效率降低，也会影响程序性能JAVA
   > ```
   >
   > 如上，对 `String` 大量修改的场合，不要使用 `String`

## 12.5 `Math` 类

* `Math.multiplyExact(int n1, int n2)`：进行乘法运算，返回运算结果

  通常的乘法 `n1 * n2` 在结果大于那个数据类型存储上限时，可能返回错误的值。

  使用此方法，结果大于那个数据类型存储上限时，会抛出异常

  `Math.addExact(int n1, int n2)`：加法

  `Math.subtractExact(int n1, int n2)`：减法

  `Math.incrementExact(int n1)`：自增

  `Math.decrementExact(int n1)`：自减

  `Math.negateExact(int n1, int n2)`：改变符号

* `Math.abs(n)`：求绝对值，返回 |n1|

* `Math.pow(n, i)`：求幂，返回 n3 ^ i

* `Math.ceil(n)`：向上取整，返回 >= n3 的最小整数（转成 double）

* `Math.floor(n)`：向下取整，返回 <=n4 的最小整数（转成 double）

* `Math.floorMod(int n1, int n2)`：返回 n1 除以 n2 的余数

  `n1 % n2` 的场合，返回的可能是负数，而不是数学意义上的余数

* `Math.round(n)`：四舍五入，相当于 `Math.floor(n5 + 0.5)`

* `Math.sqrt(n)`：求开方。负数的场合，返回 `NaN`

* `Math.random()`：返回一个 \[0, 1) 区间的随机小数

* `Math.sin(n)`：正弦函数

  `Math.cos(n)`：余弦函数

  `Math.tan(n)`、`Math.atan(n)`、`Math.atan2(n)`

  要注意，上述方法传入的参数是 **弧度值**。

  要得到一个角度的弧度值，应使用：`Math.toRadians(n)`

* `Math.exp(n)`：e 的 n 次幂

  `Math.log10(n)`：10 为底的对数

  `Math.log()`：自然对数

* `Math.PI`：圆周率的近似值

  `Math.E`：e 的近似值

## 12.6 `Arrays` 类

* `Arrays.toString()`：返回数组的字符串形式

  > ```
  > int[] nums = {0, 1, 33};
  > String str = Array.toString(nums);		//此时，str = "[0, 1, 33]"JAVA
  > ```
  >
  > 特别的，输入为 null 时返回 “null”

* `Arrays.sort(arr)`：排序

  因为数组是引用类型，使用 sort 排序后，会直接影响到实参。

  默认（自然排序）从小到大排序。

  `Arrays.sort(arr, Comparator c)`：按照传入的比较器决定排序方法

  ```
  Integer[] nums;
  ...
  Comparator<Integer, Integer> c = new Comparator<Integer, Integer>(){
      @Override
      public int compare(Integer o1, Integer o2){
          return n2 - n1;						// 这个场合，变成从大到小排序
      }
  }
  Arrays.sort(nums, c);JAVA
  ```

* `Arrays.binarySearch(array, num)`：通过二分搜索法查找。前提是必须先排序。

  找不到的场合，返回 - (low + 1)。即，其应该在的位置的负值

  ```
  Integer[] nums2 = {-10, -5, -2, 0, 4, 5, 9};
  int index = Arrays.binarySearch(nums2, 7);	// 此时 index = -7
  											// 如果 7 存在，应该在第 7 个位置JAVA
  ```

* `Arrays.copyOf(arr, n)`：从 `arr` 中，复制 n 个元素（成为新的数组）。

  n > arr.length 的场合，在多余的位置添加 `null`。n < 0 的场合，抛出异常。

  该方法的底层使用的是 `System.arraycopy`

* `Arrays.fill(arr, o)`：用 o 填充 `num` 的所有元素。

* `Arrays.equals(arr1, arr2)`：比较两个数组元素是否完全一致（`true`/`false`）

* `Arrays.asList(a, b, c, d)`：将输入数据转成一个 `List` 集合

## 12.7 `System` 类

* `System.exit(0)`：退出当前程序。0 表示一个状态，正常状态是 0

* `System.arraycopy(arr, 0, newArr, 0 ,3)`：复制数组元素。

  上例是：arr 自下标 0 起开始，向 newArr 自下标 0 开始，依次拷贝 3 个值

  这个方法比较适合底层调用。我们一般使用 `Arrays.copyOf` 来做

* `System.currentTimeMillis`：返回当前时间距离 1970 - 1 - 1 的毫秒数

* `System.gc`：运行垃圾回收机制

## 12.8 `BigInteger` 和 `BigDecimal` 类

> `BigInteger`：适合保存更大的整数
>
> `BigDecimal`：适合保存精度更大的浮点数

```
//用引号把大数变成字符串
BigInteger bigNum = new BigInteger("100000000000000000000000");JAVA
```

**构造方法：**

* `new BigInteger(String intStr)`：通过一个字符串构建大数
* `BigInteger BigInteger.valueOf(1)`：通过静态方法，让整数类型转成大数

另外，在对 `BigInteger` 和 `BigDecimal` 进行加减乘除的时候，需要使用对应方法

不能直接用 `+` `-` `*` `/`

**常用方法：**

* `BigInteger add(BigInteger)`：加法运算。返回新的大数

* `BigInteger subtract(BigInteger)`：减法

* `BigInteger multiply(BigInteger)`：乘法

* `BigInteger divide(BigInteger)`：除法运算

  该方法可能抛出异常。因为可能产生是无限长度小数。

  解决方法（保留分子精度）：`bigDecimal.divide(bD3, BigDecimal.ROUND_CELLING)`

* 一些常量：

  `BigInteger.ONE`、`BigInteger.ZERO`、`BigInteger.TEN` 分别是 1、0、10

  one 就是英文的 1，zero 就是英文的 0……这个大家都懂的吧？

## 12.9 日期类

### 12.9.1 第一代日期类

> Date：精确到毫秒，代表特定瞬间。这里的是 java.util.Date
>
> SimpleDateFormat：格式和解析日期的类

1. `Date d1 = new Date();`：调用默认无参构造器，获取当前系统时间。

   默认输出日期格式是国外的格式，因此通常需要进行格式转换

   ```
   SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH.mm.ss");
   String dateFormated = sdf.(d1);							//日期转成指定格式。JAVA
   ```

2. 通过指定毫秒数得到时间：

   ```
   Date d2 = new Date(10000000000);
   JAVA
   ```

3. 把一个格式化的字符串转成对应的 Date：

   ```
   SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日 HH点mm分 E");
   Date d3 = sdf2.parse("2021年12月22日 00点03分 星期三");JAVA
   ```

   这个场合，给定的字符串格式应和 `sdf2` 格式相同，否则会抛出异常

### 12.9.2 第二代日期类

> Calendar：构造器是私有的，要通过 getInstance 方法获取实例

1. Calendar 是一个抽象类，其构造器私有

   ```
   Calendar c1 = Calendar.genInstance();				//获取实例的方法
   JAVA
   ```

2. 提供大量方法和字段提供给程序员使用

   * `c1.get(Calendar.YEAR)`：获取年份数

   * `c1.get(Calendar.MONTH)`：获取月份数

     特别的，实际月份是 返回值 +1。因为 Calendar 的月份是从 0 开始编号的

   * `c1.get(Calendar.DAY_OF_MONTH)`：获取日数

   * `c1.get(Calendar.HOUR)`：获取小时数（12 小时制）

     `c1.get(Calendar.HOUR_OF_DATE)`：获取小时数（24 小时制）

   * `c1.get(Calendar.MINUTE)`：获取分钟数

   * `c1.get(Calendar.SECOND)`：获取秒数

   Calendar 没有专门的格式化方法，需要程序员自己组合来显示

### 12.9.3 第三代日期类

> JDK 1.0 加入的 Date 在 JDK 1.1 加入 Calendar 后已被弃用
>
> 然而，Calendar 也存在不足：
>
> 1. 可变性：像日期和实际这样的类应该是不可改变的
> 2. 偏移性：年份都是从 1900 年开始，月份都是从 0 开始
> 3. 格式化：只对 Date 有用，对 Calendar 没用
> 4. 其他问题：如不能保证线程安全，不能处理闰秒（每隔 2 天多 1 秒）等
>
> 于是，在 JDK 8 加入了以下新日期类：
>
> * LocalDate：只包含 日期（年月日），可以获取 日期字段
> * LocalTime：只包含 时间（时分秒），可以获取 时间字段
> * LocalDateTime：包含 日期 + 时间，可以获取 日期 + 时间字段
> * DateTimeFormatter：格式化日期
> * Instant：时间戳

1. 使用 `now()` 方法返回当前时间的对象

   ```
   LocalDateTime ldt = LocalDateTime.now();				//获取当前时间
   JAVA
   ```

2. 获取各字段方法：

   * `ldt.getYear();`：获取年份数

   * `ldt.getMonth();`：获取月份数（英文）

     `ldt.getMonthValue();`：获取月份数（数字）

   * `ldt.getDayOfMonth();`：获取日数

   * `LocalDateTime ldt2 = ldt.plusDays(100);`：获取 ldt 时间 100 天后的时间实例

   * `LocalDateTime ldt3 = ldt.minusHours(100);`：获取 ldt 时间 100 小时前的时间实例

   * …

3. 格式化日期：

   ```
   DateTimeFormatter dtf = new DateTimeFormatter("yyyy.MM.dd HH.mm.ss");
   String date = dtf.format(ldt);							//获取格式化字符串JAVA
   ```

4. `Instant` 和 `Date` 类似

   * 获取当前时间戳：`Instant instant = Instant.now();`
   * 转换为 `Date`：`Date date = Date.form(instant);`
   * 由 `Date` 转换：`Instant instant = date.toInstant;`

# 12 泛型

泛型（generic）：又称 参数化类型。是 JDK 5 出现的新特性。解决数据类型的安全性问题。

在类声明或实例化时只要制定好需要的具体类型即可。

举例：

```java
Properties<Person> prop = new Properties<Person>();
```

> \[!CAUTION]
> 上例表示存放到 `prop` 中的必须是 `Person` 类型。如果编译器发现添加类型不符合要求，即报错。
>
> 遍历时，直接取出 `Person` 而非 `Object`

1. 编译时，检查添加元素的类型。可以保证如果编译时没发出警告，运行就不会产生 ClassCastException 异常。提高了安全性，使代码更加简洁、健壮。

2. 也减少了向下转型的次数，提高了效率。

::: info 案例 1

需要在 for 增强循环中如果要对特定集合内的对象的属性进行遍历

```java
//没有引入泛型需要这样:
/*
  Set set = new HashSet();
  for(Objecto o: set){//只能定义为Object类型
     Stu stu = (Stu) o;//每次循环都进行一次类型转换,效率低.
     System.out.prinln(stu.getName);
  }
*/

//如果在创建集合时这样做(添加类型参数):
Set<Stu> set = new HashSet<Stu>();
 for (Stu stu :set) {//在这里原本只能放Object类型的stu,现在引入泛型后可以指定类型了,不用向下转型了.
    System.out.println(stu.getName());
}
```

:::

::: info 案例 2

```java
//案例2
//迭代器,在没有引出泛型的代码如下,在右边还是要强制转型.因为返回的是个Object类型的对象,泛型后则直接返回一个指定类型的对象,所以无需强转,直接接收.
while (iterator.hasNext()) {
        Map.Entry e =  (Map.Entry)iterator.next();//右边强转类型
        Stu stu =  (Stu) e.getValue();//右边强转类型
        System.out.println(stu.getName()+stu.getAge());
}
//引入后(添加<Map.Entry<k,v>>并指定类型),直接可以省去向下转型的操作

 Set<Map.Entry<String,Stu>> entrySet = hashMap.entrySet();
 Iterator<Map.Entry<String,Stu>> iterator = entrySet.iterator();

while (iterator.hasNext()) {
    	Map.Entry<String, Stu> next = iterator.next();//无需转类型
    	Stu stu = next.getValue();//无需转类型
        System.out.println(stu.getName()+stu.getAge());
}
```

:::

1. 泛型的作用是：==可以在类声明是通过一个标识表示类中某个属性的类型，或某个方法返回值的类型，或参数类型。==

```java
class P<E> {
	E e;				//E 表示 e 的数据类型，在定义 P类 时指定。在编译期间即确认类型
	public P(E e){		//可作为参数类型
		this.e = e;
	}
	public E doSth(){	//可作为返回类型
		return this.e;
	}
}
```

实例化时指定 E 的类型，编译时上例所有 E 会被编译器替换为那个指定类型

## 使用方法：

* **声明泛型：**

  ```java
  interface InterfaceName<T> {...}
  class ClassName<A, B, C, D> {...}
  ```

  上例 T、A、B、C、D 不是值，而是类型。可以用任意字母代替

* **实例化泛型：**

  ```java
  List<String> strList = new ArrayList<String>();
  Iterator<Integer> iterator = vector.interator<Integer>();
  ```

  类名后面指定类型参数的值

**注意细节：**

1. 泛型只能是引用类型

2. 指定泛型具体类型后，可以传入该类型或其子类类型

3. 在实际开发中往往简写泛型

```java
List<String> strList = new ArrayList<>();
```

编译器会进行类型推断，右边 `< >` 内容可以省略

4. 实例化不写泛型的场合，相当于默认泛型为 `Object`

   ```java
   ArrayList arrayList = new ArrayList();//等价于ArrayList<Object> arrayList = new ArrayList<>();可以传入任意类型
   ```

## 自定义泛型类 · 接口：

```java
class Name<A, B...> {...}				//泛型标识符 可有多个，一般是单个大写字母表示
```

1. 普通成员可以使用泛型（属性、方法）

2. 泛型类的类型，是在创建对象时确定的。

因此：静态方法中不能使用类的泛型,但可以定义为我泛型方法；使用泛型的数组，也不能初始化。

```java
public static void m1(T a){};//错误用法，静态方法是和类相关的，在类加载时对象还没有创建。而泛型在类创建对象的时候才会指定。
```

```java
T[] ts = new T[3]//错误用法，不允许初始化。
```

3. 创建对象时不指定的场合，默认 Object。建议还是写上 `<Object>`，大气，上档次

4. 自定义泛型接口

   ```java
   interface Name<T, R...> {...}
   ```

   泛型接口，其泛型在 继承接口 或 实现接口 时确定。

   ```java
   //继承接口：
   interface A extends Name<String,Intager>{...}//指定了泛型
   //实现接口：
   class B implements Name<String,Intager>{...}//指定了泛型
   ```

## 自定义泛型方法：

```java
修饰符 <T, R...> 返回类型 方法名(形参) {...}
```

1. 可以定义在普通类中，也可以定义在泛型类中

   ```java
   //在泛型中
   class F<T,R>{
       public<U,M> void eat(U u,M m){...}//调用时传入的类型指定了U，M
   }
   ```

2. 当泛型方法被调用时，类型会根据传入的数据确定

3. 以下场合

> ```java
> Class C<T> {
> 	public void cMethord(T t){...}
> }
> ```

注意！！！没有 `< >`，不是泛型方法，而是使用了泛型的普通方法

## 泛型继承：

1. 泛型不具有继承性

2. `<?>`：支持任意泛型类型

3. `<? extends A>`：支持 A 及 A 的子类，规定了泛型的上限（后续还可以增加传入的对象，只要继承了 A 类），只有 A 和 A 的子类可以传入

   ```java
   public static void printCollection(List<? extend A> c){...}//一个打印list集合数据的方法，List指定了只能传入A和A的子类。
   ```

4. `<? super A>`：支持 A 及 A 的父类，不限于直接父类，规定了泛型的下限（固定住了），只有 A 和 A 的父类可以使用泛型。

## 课后练习

```java
public class GenericExercise02 {
    public static void main(String[] args) {

        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("tom", 20000, new MyDate(1980,12,11)));
        employees.add(new Employee("jack", 12000, new MyDate(2001,12,12)));
        employees.add(new Employee("tom", 50000, new MyDate(1980,12,10)));

        System.out.println("employees=" + employees);


        employees.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee emp1, Employee emp2) {
                //先按照name排序，如果name相同，则按生日日期的先后排序。【即：定制排序】
                //先对传入的参数进行验证
                if(!(emp1 instanceof  Employee && emp2 instanceof Employee)) {
                    System.out.println("类型不正确..");
                    return 0;
                }
                //比较name
                int i = emp1.getName().compareTo(emp2.getName());
                if(i != 0) {
                    return i;
                }

                //下面是对birthday的比较，因此，我们最好把这个比较，放在MyDate类完成
                //封装后，将来可维护性和复用性，就大大增强.
                return emp1.getBirthday().compareTo(emp2.getBirthday());
            }
        });

        System.out.println("==对雇员进行排序==");
        System.out.println(employees);

    }
}
/**
 * 定义Employee类
 * 1) 该类包含：private成员变量name,sal,birthday，其中 birthday 为 MyDate 类的对象；
 * 2) 为每一个属性定义 getter, setter 方法；
 * 3) 重写 toString 方法输出 name, sal, birthday
 * 4) MyDate类包含: private成员变量month,day,year；并为每一个属性定义 getter, setter 方法；
 * 5) 创建该类的 3 个对象，并把这些对象放入 ArrayList 集合中（ArrayList 需使用泛型来定义），对集合中的元素进行排序，并遍历输出：
 *
 * 排序方式： 调用ArrayList 的 sort 方法 ,
 * 传入 Comparator对象[使用泛型]，先按照name排序，如果name相同，则按生日日期的先后排序。【即：定制排序】
 * 有一定难度 15min , 比较经典 泛型使用案例 GenericExercise02.java
 */
```

```java
public class MyDate implements Comparable<MyDate>{
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    @Override
    public int compareTo(MyDate o) { //把对year-month-day比较放在这里

        int yearMinus = year - o.getYear();
        if(yearMinus != 0) {
            return  yearMinus;
        }
        //如果year相同，就比较month
        int monthMinus = month - o.getMonth();
        if(monthMinus != 0) {
            return monthMinus;
        }
        //如果year 和 month
        return day - o.getDay();
    }
}
```

```java
    public List<T> list(){
        List<T> list = new ArrayList<>();
//        方法1：
//        Set<Map.Entry<String,T>> set = this.map.entrySet();
//        Iterator<Map.Entry<String,T>> iterator = set.iterator();
//        while (iterator.hasNext()) {
//            Map.Entry<String, T> next = iterator.next();
//              list.add(next.getValue());
//        }
//         方法2：
        Set<String> keyset = map.keySet();
        for (String key:
             keyset) {
            list.add(get(key));
        }
        //方法3：
//        Collection collections = map.values();
//        list.addAll(collections);
        return list;
    }
```
