🍈作者:王甜甜(dabing)

Java 基础再从头整理一遍,更新中。。。。

视频:韩顺平 - 零基础 30 天学会 Java

# 计算机基础知识

计算机软件 是计算机中必不可少的东西,可以使计算机按照实现预定好的顺序完成特定的功能,分为系统软件和应用软件。

  • 系统软件 DOS(磁盘操作系统,单用户单任务操作系统;windows 系统问世之后,DOS 就以一个后台程序的形式出现。就是 windows 命令提示符,点击 cmd 进入)Windows、Linux、Unix、Mac、Android、IOS 等
  • 应用程序 Office、QQ、网易云…

软件开发 借助开发工具和计算机语言制作软件

计算机语言 人与计算机之间的信息交流沟通的语言,C、C++、java…

Java语言 sun 公司 1995 年推出的一门高级编程语言,Java 之父是詹姆斯・高斯林 (James Gosling),就是下面胡子头发倒着长的这位大神。

img

Java 是一种面向 Internet 的编程语言

随着 Java 技术想 web 方面的不断成熟,已经成为 Web 应用程序的首选开发语言

完全面向对象,安全可靠,与平台(操作系统)无关的编程语言

Java语言平台版本 在发展历程里有下面三种平台:

  • J2SE (Java 2 Platform Standard Edition) 标准版是为开发普通桌面和商务应用程序提供的解决方案,该技术体系是其他两者的基础,可以完成一些桌面应用程序的开发。比如 Java 版的扫雷。
  • J2ME (Java 2 Platform Micro Edition) 小型版是为开发电子消费产品和嵌入式设备提供的解决方案。该技术体系主要应用于小型电子消费类产品,如手机中的应用程序。
  • J2EE (Java 2 Platform Enterprise Edition) 企业版是为开发企业环境下的应用程序提供的一套解决方案。该技术体系中包含的技术如 Servlet、Jsp 等,主要针对于 Web 应用程序开发。

Java语言为什么跨平台 即程序可以在不同的操作系统运行

只要在各自的操作系统安装 JVM(用来解释并执行 Java 程序的一个应用软件)即可,有了 JVM,就可以让 Java 程序跨平台运行。但是注意只是 Java 语言跨平台,JVM 不是跨平台的,JVM 也是要区分版本的,JVM 需要调用平台底层的函数的。

JDK、JRE、JVM的关系 不过多说了,一张图说明

img

  • JDK 在包含 JRE 之外,提供了开发 Java 应用的各种工具,比如编译器和调试器。
  • JRE 包括 JVM 和 JAVA 核心类库和支持文件,是 Java 的运行平台,所有的 Java 程序都要在 JRE 下才能运行。
  • JVM 是 JRE 的一部分,Java 虚拟机的主要工作是将 Java 字节码(通过 Java 程序编译得到)映射到本地的 CPU 的指令集或 OS 的系统调用。JVM 会根据不同的操作系统使用不同的 JVM 映射规则,从而使得 Java 平台与操作系统无关,实现了跨平台的特性。

img

在实际的开发过程里面,我们首先编写 Java 代码,然后通过 JDK 的编译程序(javac)将 Java 文件编译称 Java 字节码文件,JRE 加载和验证 Java 字节码文件,JVM 解释字节码,映射到 CPU 指令集或 OS 的系统调用,完成最终的程序功能。

引用:陈国君.Java 程序设计基础 [M].6 版

"任何一种可以运行 Java 字节码的软件均可堪称 Java 的 “虚拟机”(JVM),如浏览器与 Java 的开发工具等均可被看作是一部 JVM。很自然地,可以把 Java 的字节码看成 JVM 上所运行的机器码(machine code),即 JVM 中的解释器负责将字节码翻译成机器码。所以从底层来看,JVM 就是以 Java 字节码为指令组的 “软 CPU”。可以说,JVM 是可运行 Java 字节码的假想计算机。它的作用类似于 Windows 操作系统,只不过在 Windows 上运行的是.exe 文件,而在 JVM 上运行的是 Java 字节码文件,也就是扩展名为.class 的文件。JVM 其实就是一个字节码翻译器"

# 语法基础

# 关键字

用于定义数据类型的关键字
class interface byte short int
long float double char boolean
void
用于定义数据类型值的关键字
true false null
用于定义流程控制的关键字
if else switch case default
while do for break continue
return
用于定义访问权限修饰符的关键字
private protected pubilc
用于定义类、函数、变量修饰符的关键字
abstract final static synchronized
用于定义类与类之间关系的关键字
extends implements
用于定义建立实例、引用实例、判断实例的关键字
new this super instanceof
用于异常处理的关键字
try catch finally throw throws
用于包的关键字
package
其他修饰符关键字
native strictfp transient volatile assert

# 标识符

在程序中自定义的一些名称,就是给类、接口、方法、变量等起名字时使用的字符序列

一些常见的命名规则:

  • 包名:多单词组成时所有字母都小写。例, xxxyyyzzz
  • 类名、接口名:多单词组成时,所有单词的首字母大写。例, XxxYyyZzz
  • 变量名和函数名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写。例, xxxYyyZzz
  • 常量名:所有字母都大写。多单词时每个单词用下划线连接。例, XXX_YYY_ZZZ

# 注释

注释格式如下:

  • 单行注释,格式:// 注释文字
  • 多行注释,格式:/* 注释文字 */
  • 文档注释,格式:/** 注释文字 */

# 数据类型

img

着重看下基本数据类型:

数据类型 说明 所占内存 备注
byte 字节型 1 byte -128~127
short 短整型 2 bytes
int 整型 4 bytes
long 长整型 8 bytes long 最后要有一个 L 字母(大小都可)
float 单精度浮点型 4 bytes float 最后要有一个 F 字母(大小都可)
double 双精度浮点型 8 bytes double 最后要有一个 D 字母(大小都可)
char 字符型 2 bytes 字符型数据只能是一个字符,由单引号包围
boolean 布尔型 1 bit true;false 注意:整数默认为 int,小数默认为 double。

** 注意:** 整数默认为 int,小数默认为 double。

# 类型转换:

自动类型转换、强制类型转换

  • 所有的 byte 型、 short 型和 char 型的值将被提升到 int 型。
  • 如果一个操作数是 long 型 /float 型 /double 型,计算结果就是 long 型 /float 型 /double 型。

# 运算符

  • 算术运算符: % 取模,当对负数取模的时候,可以把模数负号忽略不计,如:5 % -2 = 1;当是被模数是负数就另当别论了,如 - 5 % 2 = -1

除号 / ,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。字符串数据和任何数据使用 + 都是相连接,最终都会变成字符串

  • 赋值运算符
  • 比较运算符
  • 逻辑运算符:与、或、非 (!)、异或 (^) 注意:& 和 && 的区别以及 | 和 || 的区别:&& 和 || , 短路运算符,前面的满足就不算后面了
  • 位运算符:<<、>>、>>>>、&、|、^、~ (左移、右移、无符号右移、与、或、非、异或、反码)
  • 三元运算符:(条件) ? 表达式 1 : 表达式 2

# 进制

二进制:0b 或 0B 开头

十进制:都知道

八进制:0 开头

十六进制:0x 或 0X 开头

要记的:

1,2,4,8,16,32,64,128 (20~27)

256,512,1024,2048,4096,8192,16384,32768,65536(28~216)

进制之间的转换:

二、八、十六 —> 十进制

** 规则:** 从最低位(右边)开始,将每个位上的数提取出来,乘以 2 (8 或 16) 的(位数 - 1)次方,

然后求和。如下举例:

** 案例:** 请将 0b1011 转成十进制的数

// 看 0b 开头是二进制

0b1011 = 12^0 + 12^1 + 02^2 + 12^3 = 1+2+0+8=11

十进制 ----> 二、八、十六

** 规则:** 将该数不断除以 2 (8 或 16),直到商为 0 为止,然后将每步得到的余数倒过来,就是对应的二(八或十六)进制。 如下举例:

** 案例:** 请将 34 转成二进制 = 0B00100010

img

二进制 —> 八、十六进制

** 规则:** 从低位开始,将二进制数每三位一组,转成对应的八进制数。 每四位一组,转成十六进制。

** 案例:** 请将 ob11010101 转成八进制 ob11 (3) 010 (2) 101 (5) => 0325

案例:请将 ob11010101 转成十六进制 ob1101 (D) 0101 (5) = 0xD5

八、十六进制 ----> 二进制

** 规则:** 将八(十六)进制数每 1 位,转成对应的一个 3 位(4 位)的二进制数即可。

** 案例:** 请将 0237 转成二进制 02 (010) 3 (011) 7 (111) = 0b10011111

** 案例:** 请将 0x23B 转成二进制 0x2 (0010) 3 (0011) B (1011) = 0b001000111011

# 原码、反码、补码

对于 有符号 的而言:

1. 二进制的最高位是符号位:0 表示正数,1 表示负数

2. 正数的原码,反码,补码都一样(三码合一)

3. 负数的反码 = 它的原码符号位不变,其它位取反 (0->1,1->0)

4. 负数的补码 = 它的反码 + 1,负数的反码 = 负数的补码 - 1

5.0 的反码,补码都是 0

6.java 没有无符号数,换言之,java 中的数都是有符号的

7. 在计算机运算的时候,都是以补码的方式来运算的.

8. 当我们看运算结果的时候,要看他的原码(重点)

# 流程控制语句

判断结构:if else 、 (条件) ? 表达式 1 : 表达式 2

选择结构:

switch (表达式) {  
    case1
        语句体1;    
        break;  
    case2
        语句体2;    
        break;  
    default
        语句体n+1;     
        break; 
}

循环结构:for、 while 、do while、 break、 continue

# 位运算符

Java 中有 7 个位运算符(&、|、^、~、>>、<<和>>>)

分别是按位与 &、按位或、按位异或,按位取反~,它们的运算规则是:

按位与 & :两位全为 1,结果为 1,否则为 0

按位或 | :两位有一个为 1,结果为 1,否则为 0

按位异或^ : 两位相异,结果为 1;两位相同均为 0 或均为 1,结果为 0

按位取反~ :0->1,1->0

比如:2&3=?~2=?2|3=? 2^3=? 可以去代码里输出一下

img

记住计算机是以 补码 形式存储的,显示出来的数据是补码。并且 2、3 这些是十进制,得转换成二进制,再转成原码进行运算,运算结果再转成补码存回去,才是最终的结果。

算术右移 >>`:低位溢出,符号位不变,并用符号位补溢出的高位  `相当于除以2
 算术左移 <<`:符号位不变,低位补 0   `相当于乘以2

>>> 逻辑右移 :也叫无符号右移,运算规则是:低位溢出,高位补 0

特别说明:没有 <<< 符号

举例:

int a=1>>2; //1 => 00000001 => 00000000 本质 1 / 2 / 2 =0 
int c=1<<2; //1 => 00000001 => 00000100 本质 1 * 2 * 2 = 4

# 数组

小结

  1. 数组就是由若干个 相同类型 的变量按一定的顺序排列所组成的数据结构,它们用一个共同的名字来表示。数组的元素可以是 基本类型引用类型 。数组根据存放元素的复杂程度分为 一维数组二维数组
  2. 数组:(一组数据),也是一种数据类型,是引用类型。
  3. 要使用数组,必须要经过两个步骤:<1> 声明数组 <2 > 分配内存给数组
  4. 第 <1> 步完成只是在栈内存声明了一个数组的引用变量,等 < 2 > 之后才会在堆内存为数组对象分配空间,并使引用变量指向数组对象。
  5. 数组创建后,如果没有赋值,有默认值 int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null
  6. java.util.Arrays 类对数组操作。
  7. 二维数组,是数组的数组,两个 [] [] ,** 第一个 [ ] 是第几个数组,第二个 [ ] 是第几个数组的第几个元素。** 所以 Java 的二维数组不一定是矩形,因为每个数组的数量不一定。 如果画出来,可以先看行(哪个数组)在看列(这个数组的第几个元素),X [0] [1] 第一行第二列,即第一个数组的第二个元素。
  8. 二维数组的长度:x [0].length 第一个数组(第一行)的长度

# 数组的使用

1. 动态初始化

语法 :数据类型 数组名 [] = new 数据类型 [大小]

数据类型 数组名 [ ];

数组名 = new new 数据类型 [大小];

// 数据类型 数组名 [] = new 数据类型 [大小]
int a[]=new int[5];
// 或者
double b[];   // 这时只是在某个方法的栈空间声明了数组的引用变量
b=new double[5];	// 这才真正在堆内存分配了内存空间,可以存放数据
b=null;// 这时 b 不指向任何对象,在堆里的数组对象也不被任何引用对象引用,变成了垃圾,直到垃圾回收器将它释放掉
// 数组的引用(使用 / 访问 / 获取数组元素)
// 数组名 [下标 / 索引 l/index] 比如:你要使用 a 数组的第 3 个数 a [2]
// 数组的下标从 0 开始
// 循环输入
Scanner myScanner = new Scanner(System.in);// 键盘输入
for( int i = 0; i < b.length; i++) {
System.out.println("请输入第"+ (i+1) +"个元素的值");
b[i] = myScanner.nextDouble();
}
// 循环输出
System.out.println("==数组的元素/值的情况如下:===");
for( int i = 0; i < b.length; i++) {
System.out.println("第"+ (i+1) +"个元素的值=" + b[i]);
}

2. 静态初始化

语法 :数据类型 数组名 [ ] =

int a[]={2,5,6,7,8,89,90,34,56};
// 如果知道数组有多少元素,具体值上面的用法相当于:
int a[]=new int[9];
a[0]=2;
a[1]=5;
a[2]=6;
a[3]=7;
a[4]=8;
a[5]=89;
a[6]=90;
a[7]=34;
a[8]=56;

# 数组的内存图

img

此时,arr1 的值是 {10,2,3},因为 arr1 和 arr2 指向的是同一个数组对象

# 排序

排序是将多个数据,依指定的顺序进行排列的过程

内部排序法

指将需要处理的所有数据都加载到内部存储器中进行排序。包括 (交换式排序法、选择 式排序法和插入式排序法);

外部排序法

数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括 (合并排序法和直接合并排序法)。

冒泡排序

冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。

例子说明:使用冒泡排序将数组 [24,69,80,57,13] 进行顺序排序

img

代码示例:

// 数组 [24,69,80,57,13]
int[] arr = {24, 69, 80, 57, 13};
int temp = 0; // 用于辅助交换的变量
// 外层循环:轮数  (个数 - 1)轮
// 内层循环:比较和交换  (逐渐减少)
for(int i=0;i<arr.length -1 ;i++){  // 外层循环是四次
    for(int j=0;j<arr.length -1 -i ;j++){ // 比较的次数分别是:4 次,3 次,2 次 1 次
        // 如果前面的数字 > 后面的数字,就交换
        if(arr[j] > arr[j + 1]) {
        temp = arr[j];
        arr[j] = arr[j+1];
        arr[j+1] = temp;
        } 
    }
}

# 查找

查找 (Searching) 就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素 (或记录)。

静态查找 :只作查找操作

动态查找 :在查找的过程中同时作插入、删除操作。

顺序查找 :(Sequential Search) 又叫线性查找,是最基本的查找技术:从表中第一个 (或最后一个) 记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录;如果直到最后一个 (或第一个) 记录,其关键字和给定值比较都不等时,则表中没有所查的记录,查找不成功 。

代码示例:

/**
 * 顺序查找
 * @param a           数组
 * @param key         待查找关键字
 * @return 			  关键字下标
 */
public static int sequentialSearch(int[] a, int key) {
	for (int i = 0; i < a.length; i++) {
		if (a[i] == key)
			return i;
	}
	return -1;
}

但每次循环都需要判断对 i 进行是否越界判断,事实上还可以进行优化,就是使用哨兵模式。

注:哨兵模式是将要对比的关键字放在数组第一个位置或最后一个位置,因此数组要在第一个位置或最后一个位置留个位置,专门存放哨兵元素。

代码示例:

/**
 * 有哨兵顺序查找
 * @param a     数组 (下标为 0 存放哨兵元素)
 * @param key   待查询关键字
 * @return 关键字下标 返回 0 则未找到
 */
public static int sequentialSearch2(int[] a, int key) {
	int index = a.length - 1;
	a[0] = key;// 将下标为 0 的数组元素设置为哨兵
	while (a[index] != key) {
		index--;
	}
	return index;
}

二分查找 :折半查找 (Binary Search) 技术,又称为二分查找。它的前提是线性表中的记录必须是关键码有序 (通常从小到大有序) ,线性表必须采用顺序存储。

折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。

不断重复上 述过程,直到查找成功,或所有查找区域无记录,查找失败为止。

代码示例:

/**
 * 折半查找
 * @param a   	数组
 * @param key 	待查找关键字
 * @return 返回折半下标, -1 表示不存在该关键字
 */
public static int binarySearch(int[] a, int key) {
	int low, mid, high;
	low = 0;// 最小下标
	high = a.length - 1;// 最大小标
	while (low <= high) {
		mid = (high + low) / 2;// 折半下标
		if (key > a[mid]) {
			low = mid + 1; // 关键字比 折半值 大,则最小下标 调成 折半下标的下一位
		} else if (key < a[mid]) {
			high = mid - 1;// 关键字比 折半值 小,则最大下标 调成 折半下标的前一位
		} else {
			return mid; // 当 key == a [mid] 返回 折半下标
		}
	}
	return -1;
}

字符串 :String | StringBuilder…

  1. 两大类,一种是创建之后不会做修改和变动,做比较和搜索的之类的操作的,String 类对像;另一种是允许做修改的字符串变量,添加、插入、修改等操作,一般存在在 StringBuilder 对象中。
  2. 如果对 String 对象进行了追加操作,它会产生一个新的字符串,给新的字符串分配堆内存。原来的对象在推内存中已经不可用了,原来的对象引用名(在栈内存)指向新的 String 对象。(分配方式跟数组一样)
  3. java.lang.String 类对 String 操作

# 面向对象

《Java 编程思想》中提到 **“万物皆为对象”** 的概念。它能够直接反映现实生活中的事物,例如人、车、小鸟等,将其表示为程序中的对象。每个对象都具有各自的状态特征(也可以称为属性)及行为特征(方法),java 就是通过对象之间行为的交互来解决问题的。

一个程序就是一个世界,有很多事物(对象 [属性,行为])

面向对象就是把构成问题的事物分解成一个个对象,建立对象不是为了实现一个步骤,而是为了描述某个事物在解决问题中的行为。

类是面向对象中的一个很重要的概念,因为类是很多个具有相同属性和行为特征的对象所抽象出来的,对象是类的一个实例

类具有三个特性:封装、继承和多态。

# 类和对象的联系

  1. 类是抽象的,概念的,代表一类事物,比如人类,猫类…, 即它是数据类型.

  2. 对象是具体的,实际的,代表一个具体事物,即 是实例.

  3. 类是对象的模板,对象是类的一个个体,对应一个实例

# 对象的内存图

img

# 对象的使用

代码示例:

class Person {  // 定义一个类
    // 四个属性
    int age;
    String name;
    
    // 成员方法
    public void speak() {
        System.out.println("我是一个好人");
    }
}
public static void main(String[] args) {
    // 创建 Person 对象
    //p1 是对象名 (对象引用)
    //new Person () 创建的对象空间 (数据) 才是真正的对象
    Person p1 = new Person();  // 先加 Person 类信息(只会加载一次),在堆里分配空间,并将地址赋值给 p1
    p1.age=10;   // 进行指定初始化
    p1.name="wangtt";
    
    // 访问属性
    int age=p1.age;
    
    // 调用方法
    p1.speak();
    
    
}

# 方法的调用机制

img

# 成员方法的传参机制🌸

  1. 传参是 值传递
  2. 基本数据类型,传递的是值(值拷贝),形参的任何改变都不会影响实参
  3. 引用类型传递的是地址(传递的也是值,但是值是地址),可以通过形参影响实参

# 方法递归调用

简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量。递归有助于编程者解决复杂问题,同时可以让代码变得简洁

** 举个例子:** 阶乘

// 计算 n 的阶乘
public int factorial(int n) {  
    if (n == 1) {
        return 1;
    } else {
        return factorial(n - 1) * n;
    }
}

递归的规则:

  1. 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
  2. 方法的局部变量是独立的,不会相互影响,比如 n 变量
  3. 如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用类型的数据
  4. 递归必须向退出递归的条件逼近,否则就是无限递归,出现 StackOverflowError
  5. 当一个方法执行完毕,或者遇到 return, 就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。

# 方法重载

java 中允许同一个类中,多个同名方法的存在,但要求 形参列表 (个数或类型不同)不一致! 比如:

System.out.println(100);
 System.out.println("hello,world");
 System.out.println('h');
 System.out.println(1.1);
 System.out.println(true);
// 参数类型不同

注意:

  1. 方法名:必须相同

  2. 形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求

  3. 返回类型:无要求

# 可变参数

java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。 就可以通过可变参数实现

语法 : 访问修饰符 返回类型 方法名 (数据类型… 形参名)

注意事项和使用细节:

  1. 可变参数的实参可以为 0 个或任意多个。
  2. 可变参数的实参可以为数组。
  3. 可变参数的本质就是数组.
  4. 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
  5. 一个形参列表中只能出现一个可变参数

代码示例:

// 定义一个类
class T{
    // 可变参数  
    public void f1(int... nums) {
        System.out.println("长度=" + nums.length);
    }
    // 可变参数在最后   
    public void f2(String str, double... nums) {
        
    }
    // 下面是错误写法:一个参数列表只能有一个可变参数
    public void f3(int... nums1, double... nums2) {
        
    }
    
}
// 调用
public static void main(String[] args) {
   // 细节:可变参数的实参可以为数组
    int[] arr = {1, 2, 3};
    T t1 = new T();
    t1.f1(arr); 
}

# 作用域

java 中作用域的分类:

全局变量 :也就是属性,作用域为整个类体 Person 类:speak 等方法使用属性

局部变量 :也就是除了属性之外的其他变量,作用域为定义它的代码块中!

全局变量(属性)可以不赋值,直接使用,因为有默认值,局部变量必须赋值后才能使用,因为没有默认值。

注意事项和细节使用:

  1. 属性和局部变量可以重名,访问时遵循 就近原则
  2. 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。
  3. 属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变

量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。

即在一次方法调用过程中。

  1. 作用域范围不同

全局变量 / 属性:可以被本类使用,或其他类使用 (通过对象调用)

局部变量:只能在本类中对应的方法中使用

  1. 修饰符不同

全局变量 / 属性可以加修饰符

局部变量不可以加修饰符

# 构造函数

换个口味,先写其他的。。。。

# 枚举

  1. 类的对象只有有限个,确定的。我们称此类为枚举类。

  2. 当需要定义 一组常量 时,强烈建议使用枚举类

  3. 如果枚举类中只有一个对象,则可以作为 单例模型 的实现方式

枚举的两种实现方式:

  1. 自定义类实现枚举

  2. 使用 enum 关键字实现枚举

# 自定义方式

特点

  1. 构造器私有化
  2. 本类内部创建一组对象
  3. 对外暴露对象(使用 public final static 修饰符)
  4. 可以提供 get 方法,但是不提供 set 方法
//1. 不需要提供 setXxx 方法,因为枚举对象值通常为只读,
//2. 对枚举对象 / 属性使用 final+static 共同修饰,实现底层优化.
//3. 枚举对象名通常使用全部大写,常量的命名规范
//4. 枚举对象根据需要,也可以有多个属性
// 演示字定义枚举实现
class Season {  // 类
    private String name;
    private String desc;// 描述
    // 定义了四个对象,固定. 
    public static final Season SPRING = new Season("春天", "温暖");
    public static final Season WINTER = new Season("冬天", "寒冷");
    public static final Season AUTUMN = new Season("秋天", "凉爽");
    public static final Season SUMMER = new Season("夏天", "炎热");
    //1. 将构造器私有化,目的防止 直接 new
    //2. 去掉 setXxx 方法,防止属性被修改
    //3. 在 Season 内部,直接创建固定的对象
    //4. 优化,可以加入 final 修饰符
    private Season(String name, String desc) {
    this.name = name;
    this.desc = desc;
    }
    public String getName() {
    return name;
    }
    public String getDesc() {
    return desc;
    }
}
// 使用枚举类
public class Enumeration02 {
    public static void main(String[] args) {
        System.out.println(Season.AUTUMN);
        System.out.println(Season.SPRING);
    }
}

# enum 关键字实现枚举

代码示例:

注意

  1. 当我们使用 enum 关键字开发一个枚举类时,默认会继承 Enum 类,而且是一个 final
  2. 传统的 public static final Season2 SPRING = new Season2 (“春天”, “温暖”); 简化成 SPRING (“春天”, “温暖”)
  3. 如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略
  4. 当有多个枚举对象时,使用 , 间隔,最后有一个分号结尾
  5. 枚举对象必须放在枚举类的行首
enum Season2 {// 类
    SPRING("春天", "温暖"), WINTER("冬天", "寒冷"), AUTUMN("秋天", "凉爽"), SUMMER("夏天", "炎热"), What;
    private String name;
    private String desc;
    private Season2() {// 无参构造器
    }
    private Season2(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
    public String getName() {
        return name;
    }
    public String getDesc() {
        return desc;
    }
}
// 使用
public class Enumeration03 {
    public static void main(String[] args) {
        System.out.println(Season2.AUTUMN);
        System.out.println(Season2.SUMMER);
    }
}

# Enum 类常用方法:

使用 enum 关键字的时候,会隐式地继承 Enum 类,因此可以使用 Enum 类相关的方法,常用的方法有:

方法名 详细描述
valueOf 将字符串转换成枚举对象,要求字符串必须 为已有的常量名,否则报异常!
toString Enum 类已经重写过了,返回的是当前对象 名,子类可以重写该方法,用于返回对象的属性信息
name 返回当前对象名(常量名),子类中不能重写
ordinal 返回当前对象的位置号,默认从 0 开始
values 返回当前枚举类中所有的常量
compareTo 比较两个枚举常量,比较的就是编号!
equals 在枚举类型中可以直接使用来比较两个枚举常量是否相等。Enum 提供的这个 equs0 方法,也是直接使用实现的。它的存在是为了在 Set,List 和 Map 中使用。注意,equals () 是不可变的。
hashCode Enum 实现了 hashCode () 来和 equals () 保持一致。它也是不可变的。
clone 枚举类型不能被 Clone。为了防止子类实现克隆方法,Enum 实现了一个仅抛出 CloneNotSupportedException 异常的不变 Clone ()

# 注解

概述:

  1. 注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。
  2. 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
  3. 在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在 JavaEE 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替 java EE 旧版中所遗留的繁冗代码和 XML 配置等。

内置的注解:

作用在代码上的注解:

  1. @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  2. @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  3. @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

作用在注解上的注解(即元注解):

  1. @Retention - 标识这个注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在运行时可以通过反射访问。( SOURCE,CLASS,RUNTIME )
  2. @Documented - 标记这些注解是否包含在 javadoc 中
  3. @Target - 标记这个注解应该是哪种 Java 成员。(用于包?方法?类?。。。)
  4. @Inherited - 标记这个注解是继承于哪个注解类 (默认 注解并没有继承于任何子类)

# 异常

基本概念:

Java 语言中,将程序执行中发生的不正常情况称为 “异常”。(开发过程中的语法错误和逻辑错误不是异常)

执行过程中所发生的异常事件可分为两大类

  1. **Error (错误):**Java 虚拟机无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况。比如:StackOverflowError【栈溢出】和 OOM (out of memory),Error 是严重错误,程序会崩溃。
  2. **Exception:** 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,Exception 分为两大类: 运行时异常 【程序运行时,发生的异常】和 编译时异常 【编程时,编译器检查出的异常】。

异常体系图:

img

综上:

  1. 异常分为两大类,运行时异常和编译时异常
  2. 运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。 java.lang.RuntimeException 类及它的子类都是运行时异常
  3. 对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
  4. 编译时异常,是编译器要求必须处置的异常。

一些常见的异常:

# 运行时异常:

异常 描述
ArithmeticException 数学运算异常,当出现异常的运算条件时,抛出此异常。例如,一个整数 "除以零" 时,抛出此类的一个实例。
ArrayIndexOutOfBoundsException 数组下标越界异常,用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
ArrayStoreException 试图将错误类型的对象存储到一个对象数组时抛出的异常。
ClassCastException 类型转换异常,当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。
IllegalMonitorStateException 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
IllegalStateException 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
IllegalThreadStateException 线程没有处于请求操作所要求的适当状态时抛出的异常。
IndexOutOfBoundsException 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NegativeArraySizeException 如果应用程序试图创建大小为负的数组,则抛出该异常。
NullPointerException 空指针异常,当应用程序试图在需要对象的地方使用 null 时,抛出该异常
NumberFormatException 数字格式不正确异常,当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
SecurityException 由安全管理器抛出的异常,指示存在安全侵犯。
StringIndexOutOfBoundsException 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
UnsupportedOperationException 当不支持请求的操作时,抛出该异常。

# 编译时异常:

异常 描述
ClassNotFoundException 应用程序试图加载类时,找不到相应的类,抛出该异常。
CloneNotSupportedException 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
IllegalAccessException 拒绝访问一个类的时候,抛出该异常。
InstantiationException 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
InterruptedException 一个线程被另一个线程中断,抛出该异常。
NoSuchFieldException 请求的变量不存在
NoSuchMethodException 请求的方法不存在
SQLException 操作数据库时,查询表可能发生异常
IOException 操作文件时,发生的异常
FileNotFoundException 当操作一个不存在的文件时,发生异常
EOFException 操作文件,到文件末尾,发生异常
IllegalArguementException / 参数异常

# 异常处理:

异常处理的方式:

  1. try-catch-finally

在代码中捕获发生的异常,自行进行处理。

Idea 可以用快捷键 ctrl+alt+t

try{
    // 代码可能有异常
}catch(NullPointerException e){
    // 捕获到异常
    //1. 当异常发生时
    //2. 系统将异常封装成 Exception 对象 e, 传递给 catch
    //3 得到异常对象后,程序员,自己处理
    //4 注意,如果没有发生异常,catch 代码块不执行
}catch(Exception e){ 
    // 可以有多个 catch 语句,捕获不同的异常,把异常子类放前面,
    // 如果发生异常,只会匹配一个 catch
}finally{
    //1. 不管 try 代码块是否有异常发生,始终要执行 finally
    //2. 所以,通常将释放资源的代码,放在 finally 
}

imgimg

两个方法返回的结果均为 4

  1. throws

1)如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。

  1. 方法声明中throws 语句可以声明抛出异常的列表,throws 后面的 异常类型 可以是方法中产生的异常类型,也可以是它的父类。

# 自定义异常

当程序中出现了某些 “错误”,但该错误信息并没有在 Throwable 子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。

  1. 定义类:自定义异常类名(程序员自己写)继承 Exception 或 RuntimeException
  2. 如果继承 Exception, 属于编译异常
  3. 如果继承 RuntimeException,. 属于运行异常(一般来说,继承 RuntimeException)

代码示例:

接收 Person 对象年龄时,要求范围在 18-120 之间,否则抛出一个自定义异常要求继承(RuntimeException),并给出提示信息。

// 自定义一个年龄异常类
class AgeException extends RuntimeException {
    public AgeException(String message) {// 构造器
        super(message);
    }
}
// 使用
public class CustomException {
    public static void main(String[] args) /*throws AgeException*/ {// 获取抛出异常
        int age = 180;
        if(!(age >= 18 && age <= 120)) {
            // 这里我们可以通过构造器,设置信息
            throw new AgeException("年龄需要在 18~120 之间");
        }
        System.out.println("你的年龄范围正确.");
    }
}

# throw 和 throws 的区别

img

# 集合

集合 (位于 java.util 包中)、数组都是对多个数据进行存储操作的结构,简称 Java 容器。

此时的存储主要是指再内存层面的存储,不涉及到持久化的存储(即把数据保存到可永久保存的存储设备中,如磁盘。持久化的应用是将内存中的对象存储再数据库中,或者存储再磁盘文件中、xml 数据文件中等)

对于常用的数据结构,如栈、队列、链表、树、散列表等等,为了让这些数据结构能在使用时方便使用,Java 的设计者考虑把这些通用的数据结构做成 API 供程序员调用。除了数组,还有的是集合类。

数组 的操作是高效率的,但也有缺点。

  1. 长度开始时必须指定,而且一旦指定,不能更改。
  2. 保存的必须为同一类型的元素
  3. 使用数组进行增加 / 删除元素的代码比较麻烦。

而对于 集合类

  1. 可以动态保存任意多个对象,使用比较方便。(但是不能存储基本数据类型数据,只能存储对象的引用)
  2. 提供了一系列方便的操作对象的方法:add、remove、set、get 等
  3. 使用集合添加、删除新元素

# 集合的框架体系

Java 的集合类很多,主要分为两大类:一类是是实现 Collection 接口(单列集合);另一类是实现 Map 接口(双列集合)

img

img

# Collection 接口和常用方法

  1. Collection 实现子类可以存放多个元素,每个元素可以是 Object
  2. 有些 Collection 的实现类,可以存放重复的元素,有些不可以
  3. 有些 Collection 的实现类,有些是有序的 (List), 有些不是有序 (Set)
  4. Collection 接口没有直接的实现子类,是通过它的子接口 SetList

实现的

img

代码示例:

public class CollectionMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        //add: 添加单个元素
        list.add("jack");
        list.add(10);//list.add(new Integer(10))
        list.add(true);
        System.out.println("list=" + list); //list=[jack, 10, true]
        //remove: 删除指定元素
        //list.remove (0);// 删除第一个元素
        list.remove(true);// 指定删除某个元素
        System.out.println("list=" + list);//list=[jack, 10]
        //contains: 查找元素是否存在
        System.out.println(list.contains("jack"));//T
        //size: 获取元素个数
        System.out.println(list.size());//2
        //isEmpty: 判断是否为空
        System.out.println(list.isEmpty());//F
        //clear: 清空
        list.clear();
        System.out.println("list=" + list);//list=[]
        //addAll: 添加多个元素
        ArrayList list2 = new ArrayList();
        list2.add("红楼梦");
        list2.add("三国演义");
        list.addAll(list2);
        System.out.println("list=" + list);//list=[红楼梦,三国演义]
        //containsAll: 查找多个元素是否都存在
        System.out.println(list.containsAll(list2));//T
        //removeAll:删除多个元素
        list.add("聊斋");
        list.removeAll(list2);
        System.out.println("list=" + list);//[聊斋]    
    }
}

Collectin 接口遍历方式:

  1. 使用 Iterator(迭代器)
  2. 增强 for 循环

代码示例:

@SuppressWarnings({"all"})
public static void main(String[] args) {
    List list = new ArrayList();
    list.add(new Dog("小黑", 3));
    list.add(new Dog("大黄", 100));
    list.add(new Dog("大壮", 8));
    // 普通 for 循环 set 不能使用,无序 idea 快捷键:fori + 回车
    for(int i=0;i<list.size();i++){
       System.out.println("dog="+list.get(i));
    }
    
    // 使用 for 增强
    for (Object dog : list) {
        System.out.println("dog=" + dog);
    }
    // 使用迭代器  idea 快捷键:itit + 回车  
    System.out.println("===使用迭代器来遍历===");
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        Object dog = iterator.next();
        System.out.println("dog=" + dog);
    }
}

迭代器:Iterator — 一开始是指向头头的前一个的

img

hasNext() 判断是否还有下一个元素

next() 1. 下移 2. 将下移后集合位置上的元素返回

remove() next () 之后调用该方法,删除 next () 返回的元素。如果底层集合在迭代过程中以任何方式进行修改而不是通过调用此方法,则迭代器的行为是未指定的。

# List 接口和常用方法

List 接口是 Collection 接口的子接口

  1. List 集合类中元素 有序 (即添加顺序和取出顺序一致)、 可重复
  2. List 集合中每个元素都有其对应的顺序索引, 支持索引 ,索引从 0 开始
  3. List 容器中的元素都对应一个整数型的 序号 记载其再容器中的位置,可以根据序号存取容器中的元素
  4. JDK API 中 List 接口的实现类有:

imgimg

代码示例:

@SuppressWarnings({"all"})
public static void main(String[] args) {
    List list = new ArrayList();
    list.add("张三丰");
    list.add("贾宝玉");
    //void add (int index, Object ele): 在 index 位置插入 ele 元素
    // 在 index = 1 的位置插入一个对象
    list.add(1, "韩顺平");
    System.out.println("list=" + list);
    //boolean addAll (int index, Collection eles): 从 index 位置开始将 eles 中的所有元素添加进来 List list2 = new ArrayList ();
    list2.add("jack");
    list2.add("tom");
    list.addAll(1, list2);
    System.out.println("list=" + list);
    // Object get (int index): 获取指定 index 位置的元素
    //int indexOf (Object obj): 返回 obj 在集合中首次出现的位置
    System.out.println(list.indexOf("tom"));//2
    //int lastIndexOf (Object obj): 返回 obj 在当前集合中末次出现的位置
    list.add("韩顺平");
    System.out.println("list=" + list);
    System.out.println(list.lastIndexOf("韩顺平"));
    // Object remove (int index): 移除指定 index 位置的元素,并返回此元素
    list.remove(0);
    System.out.println("list=" + list);
    // Object set (int index, Object ele): 设置指定 index 位置的元素为 ele , 相当于是替换. list.set (1, "玛丽");
    System.out.println("list=" + list);
    // List subList (int fromIndex, int toIndex): 返回从 fromIndex 到 toIndex 位置的子集合 // 注意返回的子集合 fromIndex <= subList < toIndex
    List returnlist = list.subList(0, 2);
    System.out.println("returnlist=" + returnlist);
}

需要用到什么方法可以直接到帮助文档进行查看使用。

List 接口遍历方式:

  1. 普通 for 循环
  2. 增强 for 循环
  3. 使用 Iterator(迭代器)

上面已经代码展示过了,就不在重复示例嘞

# ArrayList - 底层重点!🐖

  1. ArrayList 可以加入 null,并且可以加入多个 null
  2. ArrayList 是由数组来实现数据存储的
  3. ArrayList 基本等同于 Vector,除了 ArrayList 是 线程不安全 (执行效率高)在多线程的情况下,不建议使用 ArrayList

**ArrayList 的底层操作机制源码分析:**🐷

  1. ArrayList 中维护了一个 Object 类型的数组 **elementData** .

  2. 当创建 ArrayList 对象时,如果使用的是无参构造器,则初始 elementData 容量为 **0** ,第 1 次添加,则扩容 elementData 为 **10** ,如需要再次扩容,则扩容 elementData 为 **1.5倍**

  3. 如果使用的是指定大小的构造器,则初始 elementData: 容量为 **指定大小** ,如果需要扩容,则直接扩容 elementData 为 **1.5倍**

可以去看源码:下面把一些重点的拿上来

transient Object[] elementData ;
//transient: 表示瞬间,短暂的,表示该属性不会被序列化
private static final int DEFAULT_CAPACITY = 10;
// 无参构造器:创建一个空数组
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 有参构造器:指定容量大小
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
// 构造一个包含指定集合的元素的列表,其顺序与集合的迭代器返回的顺序相同。
public ArrayList(Collection<? extends E> c);
private void grow(int minCapacity);// 扩容方法:1.5 倍扩容。
// 扩容里边就是使用 Arrays.copyOf () 方法
//elementData = Arrays.copyOf(elementData, newCapacity);

运行过程,可以用下面的代码到 idea 进行 debug

public static void main(String[] args) {
    // 老韩解读源码
    // 注意,注意,注意,Idea 默认情况下,Debug 显示的数据是简化后的,如果希望看到完整的数据 // 需要做设置. // 使用无参构造器创建 ArrayList 对象
    //ArrayList list = new ArrayList();
    ArrayList list = new ArrayList(8);
    // 使用 for 给 list 集合添加 1-10 数据
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
    // 使用 for 给 list 集合添加 11-15 数据
    for (int i = 11; i <= 15; i++) {
        list.add(i);
        }
        list.add(100);
        list.add(200);
        list.add(null);
    }
}

img

# Vector - 底层!🐖

  1. Vector 底层也是一个对象数组
  2. Vector 是线程同步,即线程安全,Vector 类的操作方法带有 **synchronized**
  3. 在开发中,需要线程同步安全时,考虑使用 Vector

可以去看源码,都恨不得把源码都给搬上来咯🐸

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    protected Object[] elementData;// 底层也是一个对象数组
    public Vector(int initialCapacity, int capacityIncrement) ;// 构造一个具有指定初始容量和容量增量的空 Vector。
    public Vector(int initialCapacity);// 构造一个具有指定初始容量且其容量增量等于零的空 Vector。
    public Vector() { // 构造一个空 Vctor,使其内部数据数组的大小为 10,其标准容量增量为零
        this(10);
    }
    public Vector(Collection<? extends E> c);// 同 ArrayList
    private void grow(int minCapacity);// 扩容方法:如果指定容量增量,就直接 + 增量,如果没有指定,那就 2 倍增加
    // 扩容里边就是使用 Arrays.copyOf () 方法
    //elementData = Arrays.copyOf(elementData, newCapacity);
}

Vector 和 ArrayList 的比较 :

img

# LinkedList - 底层 🐖

  1. LinkedList 底层实现了 **双向链表** **双端队列** 的特点
  2. 可以添加任意元素(元素可以重复),包括 null
  3. **线程不安全** ,没有实现同步

就是长这样:

img

  1. LinkedList 底层维护了一个双向链表
  2. LinkedList 中维护了两个属性 **first** **last** 分别指向 **首节点** **尾节点**
  3. 每个节点 (Node 对象),里面又维护了 **pred** **next** **item** 三个属性,其中通过 pred 指向前一个,通过 next 指向后一个节点。最终实现双向链表。
  4. 所以 LinkedList 的元素的 **添加和删除** ,不是通过数组实现的,相对来说 **效率较高**

一般 add 是默认添加到链尾的,中间位置的插入,就是修改前后节点和该节点的 前驱节点后继节点 。(看不懂的话可以百度,获取补习一下数据结构)

源码搬点我看的懂得过来嘻嘻:

transient Node<E> first;// 首节点
transient Node<E> last;// 尾节点
// 节点长这样:定义在 LinkedList 类里
private static class Node<E> {
        E item; 
        Node<E> next; 
        Node<E> prev;
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
// 无参构造
public LinkedList() {}// 就是一个空链表
private void linkFirst(E e){....}//e 作为第一个元素
void linkLast(E e)//e 作为尾节点
void linkBefore(E e, Node<E> succ)// 在非空节点 succ 前插入元素 e
    .....

ArrayList 和 LinkedList 的比较:

img

  1. 如果我们 改查 的操作多,选择 ArrayList
  2. 如果我们 增删 的操作多,选择 LinkedList
  3. 一般来说,在程序中,80%-90% 都是查询,因此大部分情况下会选择 ArrayList
  4. 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是 ArrayList,另

外一个模块是 LinkedList,也就是说,要根据业务来进行选择

# Set 接口和常用方法

  1. 无序 (添加和取出的顺序不一致),没有索引
  2. 虽然取出的顺序和添加的顺序不一致,但是因为是根据 hashCode计算 (不是简单计算)排序的,所以取出顺序是固定的。
  3. 不允许重复元素 ,所以最多包含一个 null
  4. JDK API 中 Set 接口的实现类有:

img

Set 接口的常用方法:

和 List 接口一样,Set 接口也是 Collection 的子接口,因此,常用方法和 Collection 接口一样

Set 接口的遍历方式:

跟 Collection 接口一样啦~~

  1. 增强 for 2. 迭代器

注意不能用索引的方式来获取

# HashSet - 底层

  1. HashSet 实际上是 HashMap
  2. key - 元素 value -PRESENT(一个空对象)(在 map 中 key 不能重复,因此在 set 中元素不能重复)
  3. 可以存放 null 值,但只能有一个 null
  4. HashSet 不保证元素有序,取决于 hash 后,再确定索引结果
  5. 不能有重复 元素 / 对象

部分源码看一下:备注信息要看哦🐸

private transient HashMap<E,Object> map;// 底层是 HashMap
private static final Object PRESENT = new Object();// 用来占位 value 的空对象
public HashSet() {   // 无参构造:构造一个新的空集;支持的 HashMap 实例具有默认的初始容量 (16) 和加载因子 (0.75)。加载因子决定 table 扩容的临界值
        map = new HashMap<>();
    }
// 可指定初始容量和加载因子
public HashSet(int initialCapacity, float loadFactor)
// 指定初始容量和加载因子默认为 0.75    
public HashSet(int initialCapacity)
// 迭代器,返回的是 keySet 的迭代器。在 HashSet 这 key 其实就是存储的元素 / 对象, value 是一个占位的值(PRESENT,一个 Object)而已都一样的
public Iterator<E> iterator() { return map.keySet().iterator(); }
// 添加一个元素,key 为元素值 e,value 为 PRESENT。因此 set 不能添加相同的元素
public boolean add(E e) {return map.put(e, PRESENT)==null;}
......
  1. HashSet 的底层是 HashMap,所以分析 HashSet 的底层即分析 HashMap 的底层。
  2. HashSet 以(key-e value-PRESENT)存入
  3. 而 HashMap 的底层是(数组 + 链表 + 红黑树), 长这样:table 是一个哈希表,类型是 Node []

img

  1. 添加一个元素的时候,会先得到 hash 值(hashCode 方法),将哈希值再进行运算得到一个索引值,即要存放在哈希表上的位置号
  2. 找到存储数据表 table,看这个索引值位置上有没有元素
  3. 没有就直接加入,有就调用 equals 比较,如果相同就放弃添加,不相同就添加到该位置后面的链表的链尾。
  4. 在 Java8 中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD**(默认是 8),** **并且** **table 的大小 >=***MIN_TREEIFY_CAPACITY* (默认是 64)就会进行树化

具体的源码可查看下面的 HashMap 的源码分析

# LinkedHashSet - 底层

  1. LinkedHashSetHashSet子类
  2. LinkedHashSet 底层是一个 LinkedHashMap ,底层维护了一个 数组+双向链表
  3. LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的 次序 ,这是的元素看起来是以插入顺序来保存的。
  4. LinkedHashSet 不允许添加重复元素

呐,单个结点 LinkedHashMap$Entry ,继承 HashMap$Node :多维护了 beforeafter 属性,形成双向链表。其他跟 HashMap$Node 一样(维护 hashkeyvaluenext

img

  1. 在 LinkedHashSet 中维护了一个 hash 表和双向链表(有 headtail 分别指向头节点和尾节点)
  2. 每个节点有 beforeafter 属性,形成双向链表。
  3. 在添加一个元素的时候,先求 hash 值,求索引。确定该元素在 table 上的位置,判断有没有相同 key,没有就根据双向链表的方式加进去,有就不加。[跟前面的 HashSet 一样的]
  4. 这样我们遍历 LinkedHashSet 也能确保插入顺序和遍历顺序一致

# Map 接口与常用方法

  1. Map 与 Collection 并列存在。用于保存具有 映射关系 的数据: Key-Value
  2. Map 中的 Key 和 Value 可以是任何 引用类型 的数据,会封装到 HashMap$Node 对象中。【下面会说 HashMap$Node 是个什么样的结构】
  3. Map 中 key 不允许重复。【重复的会直接把 value 替换了】
  4. Map 中的 value 可以重复
  5. Map 中的 key 可以为 null,但是只能有一个,value 也可以为 null,可以多个,原因可想而知。Hashtable 不行。
  6. 常用 String类 作为 Map 的 key
  7. key 和 value 之间存在 单向一对一 关系,即通过 指定的key 总能找到对应的 value
  8. Map 存放数据的 key-value 示意图

常用方法:

img

# Map 接口的遍历方法:重要!🌸

  1. keySet 获取所有的键 可通过 k 去 get 获取 v
  2. entrySet 获取所有的关系 k-v 可单独获取 k 和 v
  3. values 获取所有的值

按需选取,拿出来之后再用增强 for 或者迭代器进行遍历。

代码示例:

public static void main(String[] args) {
    Map map = new HashMap();
    map.put("邓超", "孙俪");
    map.put("王宝强", "马蓉");
    map.put("宋喆", "马蓉");
    map.put("刘令博", null);
    map.put(null, "刘亦菲");
    map.put("鹿晗", "关晓彤");
    // 第一组:先取出 所有的 Key , 通过 Key 取出对应的 Value
    Set keyset = map.keySet();
        //(1) 增强 for
    System.out.println("-----取出所有 key 第一种for-------");
    for (Object key : keyset) {
    System.out.println(key + "-" + map.get(key));
    }
        //(2) 迭代器
    System.out.println("----取出所有 key 第二种迭代器--------");
    Iterator iterator = keyset.iterator();
    while (iterator.hasNext()) {
        Object key = iterator.next();
        System.out.println(key + "-" + map.get(key));
    }
    // 第二组:把所有的 values 取出
    Collection values = map.values();
    // 这里可以使用所有的 Collections 使用的遍历方法     
        //(1) 增强 for
    System.out.println("---取出所有的 value 增强 for-------");
    for (Object value : values) {
        System.out.println(value);
    }
        //(2) 迭代器
    System.out.println("---取出所有的 value 迭代器----");
    Iterator iterator2 = values.iterator();
    while (iterator2.hasNext()) {
        Object value = iterator2.next();
        System.out.println(value);
    }
    // 第三组:通过 EntrySet 来获取 k-v
    Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
        //(1) 增强 for
    System.out.println("----使用 EntrySet 的 for 增强----");
    for (Object entry : entrySet) {
        // 将 entry 转成 Map.Entry
        Map.Entry m = (Map.Entry) entry;
        System.out.println(m.getKey() + "-" + m.getValue());
    }
        //(2) 迭代器
    System.out.println("----使用 EntrySet 的 迭代器----");
    Iterator iterator3 = entrySet.iterator();
    while (iterator3.hasNext()) {
        Object entry = iterator3.next();
        //System.out.println (next.getClass ());//HashMap$Node - 实现 -> Map.Entry (getKey,getValue)
        // 向下转型 Map.Entry
        Map.Entry m = (Map.Entry) entry;
        System.out.println(m.getKey() + "-" + m.getValue());
    }
}

# HashMap - 底层重要!🐷

  1. Map 接口的常用实现类: HashMapHashtableProperties

  2. HashMap 是 Map 接口使用频率最高的实现类。

  3. HashMap 是以 key-value 对的方式来存储数据 ( HashMaps$Node 类型)

  4. k 和 v 都可以是 null 值,key 为 null 的键值对永远都放在以 table [0] 为头结点的链表中。key 为 null 的索引值是 0

  5. 如果添加相同的 key,则会 覆盖 原来的 key-value, 等同于修改.(key 不会替换,value 会替换)

  6. 与 HashSet 一样,不保证映射的顺序,因为底层是以 hash 表的方式来存储的.(jdk8 的 hashMap 底层 数组+链表+红黑树 )

  7. HashMap 没有实现同步,因此是 线程不安全 的,方法没有做同步互斥的操作,没有 synchronized

底层结构长这样:两个图都好好,一个图说明了如果一条链表的元素个数到达 TREEIFY_THRESHOLD**(默认是 8),** **并且** **table 的大小 >=***MIN_TREEIFY_CAPACITY* (默认是 64)就会进行树化。

另一个图说明了一个 HashMaps$Node 结点的内部结构,所以就两个图都拿上来吧。

img

img

HashMap 底层机制:

  1. HashMap 底层维护了 **Node类型** **数组table** , 默认为 null

  2. 当创建对象时,将 **加载因子(loadfactor)** 初始化为 **0.75** ,决定 table 什么时候扩容

  3. 如果指定容量,构造方法会设定成不小于 2 的次方的数

  4. 当添加 key-val 时,通过 key 的哈希值计算得到在 tablel 的索引。看有没有元素,没有就直接加进去。有的话就继续对比 key 一不一样,一样就替换,不一样就追加到链尾。或者是树的追加。容量不够就扩容

  5. 第 1 次添加,则需要扩容 table 容量为 **16** ,临界值 (threshold) 为 12 (16*0.75) ,即 table 所需容量 > 12 时进行再次扩容

  6. 以后再扩容,则需要扩容 table 容量为原来的 **2倍** (32),临界值为原来的 2 倍,即 24,依次类推

  7. 在 Java8 中,* 如果一条链表的元素个数到达 TREEIFY_THRESHOLD*(默认是 8), **并且** **table 的大小 >=***MIN_TREEIFY_CAPACITY* (默认是 64)就会进行树化。

源码分析:🐸

resize() 扩容:新建以恶个 HashMap 的底层数组,调用 transfer 方法,将以前的全部元素都添加到新的 HashMap 里面去(要重新计算元素在新数组的位置),所以其实也是很耗时的

hash(key) 求索引值 return (key == null) ? 0 : (h = key.hashCode ()) ^ (h >>> 16);

treeifyBin(table, hash) 树化

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    //HashMap 的属性
    transient Node<K,V>[] table;
    transient Set<Map.Entry<K,V>> entrySet;// 装 k-v 的集合
    transient int modCount;// 记录 map 修改次数
    final float loadFactor;// 加载因子
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //table 的初始默认大小 16
    static final int MAXIMUM_CAPACITY = 1 << 30;// 带参指定 table 许可最大容量 2
    ....
    对不起摆烂了....自己看代码吧
}
// 对于 put 操作
HashMap map = new HashMap();
map.put("java", 10);//ok
map.put("php", 10);//ok
map.put("java", 20);// 替换 value
1. 执行构造器 new HashMap()
    初始化加载因子 loadfactor = 0.75
    HashMap$Node[] table = null
2. 执行 put,调用putVal()
public V put(K key, V value) {//K = "java" value = 10
    return putVal(hash(key), key, value, false, true);
} //hash (key) 计算 key 的 hash 值 (h = key.hashCode ()) ^ (h >>> 16)
3. 执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;// 辅助变量
    // 如果底层的 table 数组为 null, 或者 length =0 , 就扩容到 16
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把加入的 k-v
    //, 创建成一个 Node , 加入该位置即可
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {  // 不为 null,那就遍历对比
        Node<K,V> e; K k;// 辅助变量
        // 如果 table 的索引位置的 key 的 hash 相同和新的 key 的 hash 值相同,// 并 满足 (table 现有的结点的 key 和准备添加的 key 是同一个对象 || equals 返回真)
        // 就认为不能加入新的 k-v
        if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)// 如果当前的 table 的已有的 Node 是红黑树,就按照红黑树的方式处理
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 如果找到的结点,后面是链表,就循环比较
            for (int binCount = 0; ; ++binCount) {// 死循环
                if ((e = p.next) == null) {// 如果整个链表,没有和他相同,就加到该链表的最后 p.next = newNode (hash, key, value, null);
                    // 加入后,判断当前链表的个数,是否已经到 8 个,到 8 个,后 // 就调用 treeifyBin 方法进行红黑树的转换
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);  // 树化
                    break;
                }
                // 如果在循环比较过程中,发现有相同,就 break, 就只是替换 value
                if  (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value; // 替换,key 对应 value
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;// 每增加一个 Node , 就 size++
    if (++size > threshold)// 如 size > 临界值 [12-24-48],就扩容 
        resize();  // 扩容的方法
    afterNodeInsertion(evict);
    return null;
}
5. 关于树化(转成红黑树)
// 如果 table 为 null , 或者大小还没有到 64,暂时不树化,而是进行扩容. // 否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
}

**HashMap 中 Node 内部结构:** 维护了 hashkeyvaluenext 四个属性。

这里把这个类贴上来一看就懂了,它实现了 Map.Entry<K,V> , 就贴部分的上来好了.

static class Node<K,V> implements Map.Entry<K,V> { //K,V 表示泛型,可以是任意引用类型
    // 四个属性   
    final int hash;  //Node 中的成员变量是指 key 值对应的 hash 值
    final K key;
    V value;
    Node<K,V> next;
    // 构造器
    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
    // 接口实现方法
    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
   
    // 成员方法 hashCode 为 node 对象的 hash 值,就跟别的普通类一样有自己 hashCode 方法
    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }// ^ 异或运算
    public final V setValue(V newValue) { .... }    
}
//Entry<K,V > 接口
interface Entry<K,V> {
    K getKey();
    V getValue();
    V setValue(V value);
    ....
}

# Hashtable

  1. 存放的元素是键值对:即 k-v
  2. Hashtable 的键和值都不能为 null,否则会抛出 NullPointerException
  3. Hashtable 使用方法基本上和 HashMap 一样
  4. Hashtable 是 线程安全的 ,方法均有 synchronized 修饰,HashMap 是线程不安全的

Hashtable 和 HashMap 的不同:

HashMap 和 Hashtable 的区别 这篇博客写的很详细,可以去看看。

  1. ** 继承的父类不同:**HashMap 继承自 AbstractMap 类,Hashtable 继承自 Dictionary 类(父类现已废弃)
  2. HashMap 线程不安全(可使用 ConcurrentHashMap),HashTable 线程安全
  3. HashMap 允许 k 和 v 为 null,Hashtable 键值对都不允许为空
  4. 计算索引值方式不同:HashMap 计算索引值,是通过 hashCode () 后,再经过 hash 与右移 16 位后相 异或 得到新的索引值。而 Hashtable 是直接使用 hashCode () 后的结果。
  5. 扩容方式不同:HashMap 扩容是原来的 2 倍,Hashtable 是原来的 2 倍 + 1
  6. ** 解决 hash 冲突方式不同:**HashMap 在链表长度大于 8 时进行树化,又当数量小于 6 时又转化回链表模式;Hashtable 则始终以链表方式存储

# Properties

  1. Properties 类继承自 Hashtable 类并且实现了 Map 接口,也是使用一种键值对的形式来保存数据
  2. 他的使用特点和 Hashtable 类似
  3. Properties 还可以用于 xxx.properties 文件,通常作为配置文件

# 集合小结:

在开发中如何选择集合实现类,主要取决于 业务操作特点 ,然后根据集合实现类特性进行选择:

  1. 先判断存储的类型(一组对象【单例】 一组键值对【双列】)

  2. 一组对象【单列】:Collection 接口

    1. 允许重复:List
      1. 增删多: LinkedList 【底层维护了一个双向链表】
      2. 改查多: ArrayList 【底层维护了 Object 类型的可变数组】
    1. 不允许重复:Set
      1. 无序: HashSet 【底层是 HashMap,维护了一个哈希表(数组 + 链表 + 红黑树)】
      2. 排序: TreeSet 【 下面有代码示例】
      3. 插入与取出顺序一致: LinkedHashSet ,维护了数组 + 双向链表
  3. 一组键值对【双列】:Map 接口

    1. 键无序: HashMap 【底层是哈希表,jdk7:数组 + 链表 jdk8:数组 + 链表 + 红黑树】
    2. 键排序: TreeMap 【有代码示例】
    3. 键插入与取出顺序一致:LinkedHashMap
    4. 读取文件:Properties
//TreeSet 的底层是 TreeMap,而 TreeMap 的底层是红黑树
// 无参构造:仍然是无序的
public TreeSet() { this(new TreeMap<E,Object>()); }
// 排序:可以传入一个比较器 (匿名内部类)
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator));}
 
// 老韩解读
    /*
    1. 构造器把传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator
    public TreeMap (Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
    2. 在 调用 treeSet.add ("tom"), 在底层会执行到
    if (cpr != null) {//cpr 就是我们的匿名内部类 (对象)
        do {
            parent = t;
            // 动态绑定到我们的匿名内部类 (对象) compare
            cmp = cpr.compare (key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else // 如果相等,即返回 0, 这个 Key 就没有加入
                return t.setValue (value);
        } while (t != null);
    }*/
// 使用示例:
TreeSet treeSet = new TreeSet(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
    // 下面 调用 String 的 compareTo 方法进行字符串大小比较
    // 如果老韩要求加入的元素,按照长度大小排序
    //return ((String) o2).compareTo((String) o1);
        return ((String) o1).length() - ((String) o2).length();
    }
});
    // 添加数据. treeSet.add ("jack");
    treeSet.add("tom");//3
    treeSet.add("sp");
    treeSet.add("a");
    treeSet.add("abc");//3,长度跟 tom 一样,无法加入
    System.out.println("treeSet=" + treeSet);//treeSet=[a, sp, tom]

TreeMap 底层红黑树,可以看这这篇文章了解一下:TreeMap 底层实现原理

//TreeMap 的无参构造是无序的
// 排序:把传入的实现了 Comparator 接口的匿名内部类 (对象),传给给 TreeMap 的 comparator
public TreeMap(Comparator<? super K> comparator) {  this.comparator = comparator; }
// 使用示例:
TreeMap treeMap = new TreeMap(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        // 按照传入的 k (String) 的大小进行排序
        // 按照 K (String) 的长度大小排序
        //return ((String) o2).compareTo((String) o1);
        return ((String) o2).length() - ((String) o1).length();
    }
});
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("hsp", "韩顺平");//key 长度跟 tom 一样,替换
System.out.println("treemap=" + treeMap);//treemap={kristina = 克瑞斯提诺,smith = 斯密斯,jack = 杰克,tom = 韩顺平}

# Collections 集合工具类

  1. Collections 是一个操作 Set、List 和 Map 等集合的工具类
  2. Collections 中提供了一系列静态方法对集合元素进行排序、查询和修改等操作。

排序操作:

  1. reverse(List) :反转 List 中元素的顺序

  2. shuffle(List) :对 List 集合元素进行随机排序(shuffle 方法模拟了 “洗牌” 动作)

  3. sort(List) :根据元素的自然顺序对指定 List 集合元素按升序排序

  4. sort(List,Comparator) :根据指定的 Comparator 产生的顺序对 List 集合元素进行排序

  5. swap(List,int,int) :将指定 list 集合中的 i 处元素和 j 处元素进行交换

  6. void rotate(List list, int distance) :当 distance 为正数时,将 list 集合的后 distance 个元素 “整体” 移到前面;当 distance 为负数时,将 list 集合的前 distance 个元素 “整体” 移到后面。该方法不会改变集合的长度。

查找、替换:

  1. Object max(Collection) :根据元素的自然顺序,返回给定集合中的最大元素

  2. Object max(Collection,Comparator) :根据 Comparator 指定的顺序,返回给定集合中的最大元素

  3. Object min(Collection)

  4. Object min(Collection,Comparator)

  5. int frequency(Collection,Object) :返回指定集合中指定元素的出现次数

  6. void copy(List dest,List src) :将 src 中的内容复制到 dest 中

  7. boolean replaceAll(List list,Object oldVal,Object newVal) :使用新值替换 List 对象的所有旧值

  8. int binarySearch(List list, Object key) :使用二分搜索法搜索指定的 List 集合,以获得指定对象在 List 集合中的索引。如果要使该方法可以正常工作,则必须保证 List 中的元素已经处于有序状态。

  9. void fill(List list, Object obj) :使用指定元素 obj 替换指定 List 集合中的所有元素。

  10. int indexOfSubList(List source, List target) :返回子 List 对象在父 List 对象中第一次出现的位置索引;如果父 List 中没有出现这样的子 List,则返回 -1。

  11. int lastIndexOfSubList(List source, List target) :返回子 List 对象在父 List 对象中最后一次出现的位置索引;如果父 List 中没有岀现这样的子 List,则返回 -1。

** 复制:**🐨

copy(List dest,List src) :dest 目标集合,src 源集合,将指定集合中的所有元素复制到另一个集合中。

# 常用类

# 泛型

# 多线程

进程和线程

怎么启动线程?

Thread、Rannable、Callable

按我理解,这些继承了 Thread 类或者是实现了 Runnable 接口的类,就是被操作的对象,即资源。run () 就是它这个对象怎么被操作。

方式一、继承 Thread 类,重写 run () 方法,调用 start () 开启线程。

线程开启不一定立即执行,由 CPU 调度安排。跟 main 主线程交替执行。

run () 是线程的执行体

方式二、实现 Runnable 接口,重写 run () 方法,执行线程需要创建一个 Thread 类,将 runnable 接口实现类,调用 start ()。即 new Thread (ThreadRunnable).start ()

执行效果跟上面一样。

可以多个线程同时操作同一个对象,可以给线程起名字,小明,老师,小红

发现问题:多个线程操作同一个对象发生紊乱

Thread.sleep (10); 线程睡眠

方式三:Callable 接口(了解即可)

​ 实现 Callable 接口,需要有返回值,重写 call (),这个方法有返回值跟前面的返回值类型一样。

开启线程:1. 创建执行服务 2. 提交执行 3. 获取结果 4. 关闭服务

静态代理对比 Thread

静态代理:1. 真实对象和代理对象都要实现同一个接口 2. 代理对象,代理谁,代理真实的对象 3. 在主函数中使用代理对象进行操作

代理对象可以做很多真实对象做不了的事情,真实对象做自己的事情就可以了

对应 Thread,Thread 相当于代理类,然后刚刚前面讲到的实现类,实现了 Runnable 接口的实现类是真实对象。

** 疑问点:**Thread 是抽象类,怎么替代实现类做更多的事情?怎么实现做更多的事情?不是 run () 执行体都写在了 Runnable 实现类里了吗,然后才执行的 Thread 类里的 start (),这个 start () 内部方法是执行了 run () 吗,按静态代理是这样,但是好像没有,所以这应该只是底层实现原理,应该内部更加复杂。

lamda: 函数式编程

任何接口,如果只包含唯一一个抽象方法,那这个接口就是一个函数式接口。

一步步简化的过程:

1. 传统方式:函数式接口 —> 实现接口的实现类 ----> 在主程序中 new 这个实现类使用

2. 静态内部类的方式实现接口实现类

3. 局部内部类实现接口实现类

4. 匿名内部类实现接口实现类

ILike like=new ILike(){

public void method(){

}

}

5. 用 lambda 简化

ILike like=()->{

};

# IO 流

java.io
  1. 字节流:处理字节数据(基本抽象类:InputStream、OutputStream)(图片、音频、视频之类的二进制数据)
  2. 字符流:处理字符数据(基本抽象类:Reader、Writer)(文本文件)
  3. 因为前面四个类是抽象类,一般不会直接使用,而是使用其子类img

# 网络编程

# 反射

Reflection

Java 的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。反射被视为动态语言的关键 —— 陈国军.Java 程序设计基础(第六版):清华大学出版社,2019—01

这个字的意思是 “反射、影像、倒影”,用在 Java 身上指的是我们可以运行时加载、探知、使用编译期间完全未知的 classes。换句话说,Java 程序可以加载一个运行时才得知名称的 class。获悉其完整构造(但不包括 methods 定义)并生成其对象实体、或对其 fields 设值、或唤起其 methods。这种 **“看透 class”** 的能力被称为 introspection (内省、内观、反省)

# 反射机制

1、反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到

2、加载完类之后,在堆中就产生了一个对应的 Class 类型的对象(一个类只有一个 Class 对象),这个对象包含了类的完整结构信息。通过这个对象得到 JVM 中该类的结构。这个 Class 对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射

反射机制原理示意图:

img

有了反射,我们可以获取对应类的所有信息,包括私有属性,因此说在反射面前,一切都是纸老虎!使用反射我们可以:

1. 在运行时判断任意一个对象所属的类

2. 在运行时构造一个任意一个类的对象

3. 在运行时得到任何一个类具有的成员和方法

4. 在运行时调用任意一个对象的成员变量和方法

5. 生成动态代理

# Class 类

# 获取 Class 类对象

# 哪些类型有 Class 对象

# 类加载

# 通过反射获取类的结构信息

# 通过反射创建对象

# 通过反射访问类中成员

访问属性

访问方法

# GUI(图形用户接口)

# 正则表达式

# 内省

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Dabing-He 微信支付

微信支付