1. Java集成开发环境(IDE) IntelliJ IDEA下载
- 先从官网上下载Ultimate版exe安装包(2022.3.2版):https://www.jetbrains.com/idea/download/
- 按步骤安装IDEA即可
2. Java开发工具包JDK的下载、安装与环境变量配置
- 进入Oracle下载JDK的官网:https://www.oracle.com/java/technologies/downloads/
- 下载和自己电脑适配的JDK安装包exe文件,安装即可;也可从Java archive中下载老版本JDK
- 新建
JAVA_HOME
系统变量,变量值为JDK安装路径 - 在Path系统变量中新建两个变量
%JAVA_HOME%\bin
与%JAVA_HOME%\jre\bin
- 打开cmd并输入
java -version
命令显示版本号
3. IDEA对远程Git项目的检出和提交
先确保已下载Git、IDEA已经配置好Git
- 打开Git官网https://git-scm.com/download/win,下载
64-bit Git for Windows Setup
并安装git(一路next)
创建Git账号和邮箱,打开Git Bash:git config --global --replace-all user.name "你的 git 的名称"
git config --global --replace-all uesr.email "你的 git 的邮箱"
- 打开IDEA,File->Settings->Version Control->Git->Path to Git executable:选择git安装后位于bin文件夹下的git.exe文件,点击Test按钮进行测试是否成功
3.1 IDEA对Git项目检出
- 克隆整个项目:File->New->Project from Version Control,输入URL和想要克隆到的文件夹,点击Clone即可
- 获取远程仓库中项目的某些修改:右键->Git->Fetch,点击右下角master,本地库右边若有箭头,则说明本地库和远程库不一样,则可点击本地库->Update对本地库进行更新
- 获取远程仓库中的新分支:右键->Git->Fetch,点击右下角master,点击远程库的新分支->Checkout即可
- 将分支合并到主干:在主干上右键->Git->Merge,选择要合并的分支即可,同时点击主干->Push同步到远程仓库
- 项目要发布版本:项目右键->Git->New Tag,写明Tag Name,再右键->Git->Push(勾选左下角Push Tags)即可
3.2 IDEA使用Git提交代码
- VCS->Create Git Repository->选择要提交的项目
- 选中要提交的项目,右键->Git->Commit Directory,再选择要提交的文件,并写上提交信息注释即可提交到本地仓库
- 对于同项目中的新的文件,可以先右键->Git->Add,再进行步骤(2)完成新文件的提交
- 前3步是将代码提交到本地仓库中,右键->Git->Push,再输入远程库的Name和URL,以及账户和密码即可提交到远程仓库
- 可以通过右键->Git->Branches创建新的分支(点击分支->Checkout可以切换分支),再通过步骤(3)和(4)将分支上传到Git远程仓库
4. Java基础语法
Java是一种混合型语言,先将.java文件编译成.class二进制字节码文件,然后再按行交给JVM进行解释
JDK:Java开发工具包,包含了JVM、核心类库、开发工具(如javac、java、jdb等)。
JRE:Java运行环境,包含JVM、核心类库、运行工具(如java)。
原码:最高位为符号位,0正1负,原码进行正数计算不会出现问题,负数计算的结果和预期结果相反。
反码:为了解决原码不能计算负数,负数反码:符号位不变,其余位取反;反码计算时跨0会出错,由于有-0和+0两个0。
补码:为了解决反码不能跨0,负数补码=反码+1,因此10000000规定为-128,计算机数字的存储和计算都是以补码的形式进行的。
4.1 Java数据类型

注:(1) long类型定义变量时,数据值最后应加上L或l;float类型定义变量时,数据值最后应加上F或f
(2) 基本数据类型数据值存储在自己的空间中,赋值给其它变量时赋的是真实值
(3) 引用数据类型变量在栈空间中存储的是地址值,数据值存储在该地址指向的堆空间中,赋值给其它变量时赋的是地址值
(4) ==号对于基本数据类型比较的是数据值,而引用数据类型比较的是地址值
(5) 基本数据类型作为实参传入方法的形参时,会重新生成一个值相等的新的变量,因此变量原型不会发生改变;而引用数据类型作为实参传入方法的
形参时,由于传递的是引用数据类型的地址值,在方法中改变参数变量的同时变量原型也会发生改变(String字符串由于是不可变的,故不作考虑)。
4.2 Java的输入与输出
一、Java的输入:Scanner类
- 导包
import java.util.Scanner;
- 创建Scanner对象
Scanner scan = new Scanner(System.in);
- 接收数据
int a = scan.nextInt();
注:Scanner类有针对不同类型的接收方法,如next()和nextLine()接收字符串、nextInt()接收整数、nextDouble()接收小数等
nextLine()是遇到回车就停止接收数据,其它都是遇到空格就停止接收数据
二、Java的输出
- System.out.println(“输出的内容(输出完换行)”);
- System.out.print(“输出的内容(输出完不换行)”);
- System.out.printf(); // 与C语言的用法一致,%d(整数占位符),%f(浮点数占位符),%s(字符串占位符),%c(字符占位符)
注:只要有字符串参与的+运算,就代表连接操作;对于多个+则从左到右依次执行,但只有遇到字符串后+才开始变成连接符
4.3 Java的判断与循环
一、Java的判断语句
- if语句(可嵌套、可单条件判断、双条件判断和多条件判断)
if (关系表达式) {
关系表达式为true时要执行的语句;
} else {
关系表达式为false时要执行的语句; //当不考虑关系表达式为false时的情况可以省略else
}
- switch语句(case后的值只能是字面量且不能重复;语句体结束后必须加break,否则当匹配后因case穿透仍会执行它下面的case语句体)
switch (表达式) {
case 值1: //表达式值为值1时,则执行语句体1。注:若对于值1、值2要执行的语句体一样,则可写为case 值1,值2:相同语句体;
语句体1;
break;
case 值2: //表达式值为值2时,则执行语句体2
语句体2;
break;
...
default: //表达式值与上述的值都不匹配时,则执行语句体n+1
语句体n + 1;
break;
}
- switch语句新特性(适用于JDK12及之后的版本,用{}代替了break)
switch (表达式) {
case 值1 -> {
语句体1;
}
case 值2 -> {
语句体2;
}
...
default -> {
语句体n + 1;
}
}
注:if语句的多条件判断:一般适用于对范围的判断,如考试成绩的分段
switch语句:一般适用于可以将数据一一列举出来,如星期几
switch语句新特性:数据类型 变量名 = switch(表达式){ }; 其中语句体的赋值可以省略
二、Java的循环语句(continue跳过当前层的本次循环;break结束当前层的整个循环;可在循环语句前加上loop:,再用break loop;
语句跳出指定层的循环(一般用于多层嵌套循环),loop只是一个标记,用别的字符也可以表示,continue同样适用)
- for循环
for (初始化语句; 条件判断语句; 条件控制语句) {
循环体语句;
}
- while循环
初始化语句;
while (条件判断语句) {
循环体语句;
条件控制语句;
}
- 增强for循环(针对数组和单列集合,底层是迭代器)
for(数组/集合的数据类型 变量名 : 数组名/集合名) {
循环体语句(只适合取数据,不能更改数据);
}
注:for循环和while循环的区别(for循环中的初始化语句只在for循环中可用,出了for循环则不可用)
for循环:知道循环次数或循环范围
while循环:只知道循环结束条件
4.4 Java数组
一、一维数组的定义与初始化
- 一维数组的定义
方式一:数据类型[] 数组名
如int[] array;
方式二:数据类型 数组名[]
如int array[];该方式不推荐(不符合阿里巴巴编码规范) - 一维数组的初始化
静态初始化(明确具体数据):数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3...};
//new 数据类型[]可省略,直接跟{}
动态初始化(只明确元素个数,不明确具体值):数据类型[] 数组名 = new 数据类型[数组长度];
- 拷贝数组
(1)for循环:基本数据类型拷贝的数据值;引用数据类型拷贝的是地址值(共享数据)。
(2)clone():新数组 = 源数组.clone()。与for循环结果一样。
(3)System.arraycopy(源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数):与for循环结果一样。(拷贝速度最快)
(4)新数组 = Arrays.copyOf(源数组,新数组长度):与for循环结果一样。(底层仍是用(3)实现的)
二、二维数组的定义与初始化
- 二维数组的定义
方式一:数据类型[][] 数组名
如int[][] array;
方式二:数据类型 数组名[][]
如int array[][];该方式不推荐(不符合阿里巴巴编码规范) - 二维数组的初始化
静态初始化(明确具体数据):数据类型[][] 数组名 = new 数据类型[][]{ {元素1,元素2,...},{元素3,元素4,...},...};
//new 数据类型[][]可省略,直接跟{};其中二维数组包含的一维数组元素的长度可以不同,建议把每一个一维数组单独写成一行便于阅读
动态初始化(只明确元素个数,不明确具体值):数据类型[][] 数组名 = new 数据类型[m][n];
其中m表示二维数组中一维数组的个数;n表示每个一维数组中元素的个数。注意:可以不指定n的值,后期再创建一维数组,再将一维数组的地址赋值给二维数组表示的一维数组,如array2D[0] = new int[5];
array2D[0]指的是第一个一维数组的地址。也可以指定n的值,后期再将地址覆盖也可实现变长二维数组。
三、数组的长度(array.length
,该length是数组的属性,不是方法,故不加括号)
4.5 Java方法(内含对权限修饰符的描述)
一、方法的定义
修饰符 返回值类型 方法名([参数类型 参数名,...]) {
...
方法体
...
[return 返回值;]
}
修饰符:定义了方法的访问类型
- 访问权限修饰符
default
(即默认,什么也不写):在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法
private
:在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
public
:对所有类可见。使用对象:类、接口、变量、方法
protected
:对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类) - 非访问权限修饰符
static
:用来修饰静态方法和静态变量(属于类的,不属于对象,不用实例化就可以通过类名.方法名
调用,静态方法只能调用静态方法和静态变量,非静态方法都可以访问,且静态方法没有this关键字),静态变量存储在堆内存的静态区中,是该类所有对象共享的,静态变量和方法是随着类的加载而加载的,优先于对象出现的。
final
:用来修饰类、方法和变量。final 修饰的类不能够被继承;final修饰的方法不能被继承类重新定义;final修饰的变量为常量,只能被赋值一次(常量命名规则:单个单词全部大写;多个单词全部大写,单词之间用下划线隔开)(final修饰的变量若为基本类型,那么变量存储的数据值不能发生改变;final修饰的变量若为引用类型,那么变量存储的地址值不能发生改变,对象的内部可以改变)。
abstract
:用来创建抽象类(不能用来实例化对象,目的为了对该类进行扩充)和抽象方法(没有任何实现的方法,该方法的具体实现由子类提供)。
synchronized
:声明的方法同一时间只能被一个线程访问。
volatile
:修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值;当成员变量发生变化时,会强制线程将变化值回写到共享内存;保证不同线程在任何时刻看到的成员变量都是相同的值。
返回值类型:方法可能会有返回值,若无返回值则用关键字void
方法名:所定义方法的名称,小驼峰命名法(首单词的首字母小写,其后的单词的首字母都大写),如getSum
可定义为求和方法名
参数类型:形参,像是一个占位符,当方法被调用时,将实参的值传递给该参数,可有多个形参也可一个没有
方法体:定义该方法功能的代码
二、方法的重载
定义:在同一个类中,方法名相同,参数不同(参数类型、参数个数、参数顺序),与返回值无关
优点:(1)定义端(方法的提供者):使用相同的方法名(一个方法)来表示功能相同的(多个)方法
(2)调用端(方法的使用者): 在调用的时候,可以使用相同名字(一个名字)的方法实现不同的功能
(3)重载也是多态性的体现:一个内容,可以实现多个功能
当方法出现重载现象,JVM会优先调用实参和形参类型一致的那个方法。
三、可变参数
方法的形参的个数是可以发生变化的,格式:属性类型...形参名
,如int…args,就可以传入不确定个数的int类型的数据,底层仍是通过数组实现的,args就相当于数组名。但是在方法中最多只能有一个可变参数,且如果还有其它形参,则可变参数必须写在最后。
5. Java面向对象
5.1 类与对象
一、类:类是对某一类事物的描述,是抽象的、概念上的意义
- 类的定义
public class 类名 {
//类名采用大驼峰命名格式,即全部单词的首字母大写;类不能用private进行修饰,即类不能是私有的
(1) 成员变量(属性,名词)
(2) 成员方法(行为,动词)
(3) 构造器
(4) 代码块
(5) 内部类 //见5.6节
}
- 构造方法
在创建对象的时候给成员变量进行初始化。构造方法名与类名完全一致,无返回值,可有多个构造方法(系统会根据传入的参数选择对应的构造器)。构造方法是在创建对象的时候由虚拟机调用
的,不能手动调用构造方法,若不写任何构造方法,虚拟机会给出一个无参构造方法,若写了一个有参构造,则必须写无参构造才可以无参实例化对象;每创建一次对象,就会调用一次构造方法。无论是否使用,建议无参构造和有参构造都写。若唯一的构造方法用private修饰了,证明该类无法被创建成对象,即无法实例化对象,则只能根据类名调用该类的静态成员和静态方法。
修饰符 类名(参数) {
方法体;
}
- 代码块用{}括起来的代码
(1)局部代码块:写在方法里用{}括起来的代码,可提前结束局部变量生命周期,提高内存利用率。
(2)构造代码块:写在类中方法外用{}括起来的代码,用以提取多个构造方法的重复代码,每次调用构造方法都会执行,并且在构造方法前执行。
(3)静态代码块:写在类中方法外用static{}
括起来的代码,随着类的加载而加载(即要使用这个类的时候),并且自动触发,只执行一次。可以在程序运行时提前完成一些只进行一次的数据初始化操作。 - 类的分类
JavaBean类:用来描述一类事物的类(无main方法),成员变量私有且都有对应set与get方法(Alt+Insert
),有参和无参构造方法,toString()方法以及其它成员方法,ptg插件可快速生成标准JavaBean。
测试类:用来检查其它类是否书写正确,带有main方法的类,是程序的入口(一个java源文件只能有一个public类,且类名与文件名相同)
工具类:帮我们做一些事情的类,类名要见名知意,构造方法私有化(防止创建该类对象),方法都定义为静态(方便调用)
二、对象:实际存在的该类事物的实例化个体
- 类对象的获取:
类名 对象名 = new 类名();
- 访问属性:
对象名.成员变量
- 访问行为:
对象名.方法名
三、封装
定义:封装是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。把类中的某些信息进行隐藏,从而使外部程序不能直接对这些信息进行直接的访问,只能通过类中定义的方法对这些隐藏的信息进行操作和访问。
目的:防止该类的代码和数据被外部类定义的代码随机访问;要访问该类的代码和数据,必须通过严格的接口控制。提高了程序的安全性和便利性,对象代表什么,就得封装对应的数据,并提供数据对应的行为,如get、set方法。
四、this关键字(this中存的是当前对象的地址值,对于非静态方法都有一个隐藏的当前类对象this形参,静态方法则没有)
this.属性名
:访问的是类的成员变量,用来区分成员变量和局部变量的重名问题this.方法名
:访问本类的成员方法this()
:访问本类的构造方法(可以有参数来指定对应的有参构造,只能在构造方法中且必须是第一条语句)
5.2 Java的继承
- 定义:继承就是子类继承父类的属性和方法,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的方法。当类与类之间,存在相同(共性)的内容,并满足子类是父类的一种,就可以考虑使用继承,来优化代码,提高代码的复用性。
- 继承的格式
public class 子类 extends 父类 {
} //一个子类只能继承一个父类,但可以多层继承(父类可以还有它的父类)
- 每一个类都直接或间接的继承于Object类(若自定义的类没有继承某个父类,JVM则会自动添加一个默认的继承Object父类)。
- 构造方法不能被子类继承,子类中的所有构造方法默认先访问父类中的无参构造(子类在初始化时可能用到父类的数据,如果父类没有完成初始化,子类将无法使用父类的数据),再执行自己的。子类构造方法第一句默认都是
super();
,不写也存在,且必须是第一句。也可以用super(参数…)调用父类的有参构造。 - 成员变量(无论私有还是非私有)都可以被子类继承,但是子类对继承的私有成员变量不能直接使用。
继承中成员变量访问特点:就近原则(先在局部位置找,本类成员位置找,父类成员位置找,逐级往上,没找到就报错),通过this.成员变量名
调用本类的成员变量(不存在就会从父类中找),通过super.成员变量名
调用父类的成员变量。 - 成员方法能被添加到虚方法表中的虚方法(非private,非static,非final) 可以被子类继承,子类从父类中拷贝一份虚方法表,使子类的虚方法表指针指向新的虚方法表,如果子类中覆写了父类的虚方法,则将方法表中覆写方法的方法指针替换为子类覆写的方法指针,如果子类中有新增的虚方法,则在该子类的虚方法表中追加新增的虚方法指针。对不能继承的方法,则会往父类向上一层一层的找,找到之后对于private和final修饰的方法仍不能调用,而static修饰的方法可以调用,但是不被子类继承。
继承中成员方法访问特点:同样是就近原则,this.方法名()
调用本类的成员方法(不存在就从父类中找),super.方法名()
调用父类的成员方法。
方法的重写(覆盖):当父类的方法不能满足子类的需求时,可以在子类中对继承的方法进行重写(方法名、形参一致;访问权限>=父类;返回值类型<=父类),本质是子类覆盖了从父类继承下来虚方法表中的虚方法,只有在虚方法表中的方法才可以被重写,在重写的方法上面加上@Override
注解可以验证重写是否存在语法错误。
5.3 Java的多态
- 定义:同一个行为具有多个不同表现形式或形态的能力;同一个接口,使用不同的实例而执行不同操作。
前提:继承/实现关系;方法的重写;父类引用指向子类对象,例如:父类类型 对象名称 = new 子类类型()
。
好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理,如使用父类类型作为参数,则可以接收所有子类对象,体现多态的扩展性与便利。 - 调用成员变量的特点:编译看左边,运行也看左边。左边代表父类,右边代表子类,即无论编译还是运行,判断编译是否正确和运行时的取值都是父类的成员变量。
调用成员方法的特点:编译看左边,运行看右边。即编译时先判断父类是否存在该成员方法;运行时由于子类重写了父类方法致使虚方法表的虚方法被覆盖,且多态本质是实例化的子类对象,因此运行时执行的是子类重写的成员方法。 - 多态的优点(多态本身是一种向上转型:子类对象赋值给一个父类引用)
(1)消除类型之间的耦合关系(解耦),
(2)可扩充性:新增子类不影响现在类的特性。
(3)定义方法的时候,使用父类类型作为参数,则可以接收所有子类对象,体现多态的扩展性与便利,例如StringBuilder容器正是由于多态才可以通过append()方法添加任何数据(由于任何类都是Object的子类)。 - 多态的缺点:无法直接访问子类特有的成员,可以向下转型:使用强制类型转换的格式,将父类引用转为子类引用,再调用特有子类的成员
(1)在强制转换之前可以先用对象名 instanceof 类名
来判断该对象是否是该类的类型,再进行强制转换。
(2)JDK14之后,可用对象名 instanceof 类名 新的对象名
将判断和强转合并为一句,若是则强转后的变量名为新的对象名
,若不是则返回false。
5.4 Java的抽象类
- 抽象方法:将共性的行为(方法)抽取到父类之后,由于每一个子类执行的内容是不一样的,因此在父类中就不能确定具体的方法体,该方法就可以定义为抽象方法。定义格式:
public abstract 返回值类型 方法名(参数列表);
- 抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类;而一个抽象类中不一定有抽象方法。定义格式:
public abstract class 类名{}
。抽象类不能被实例化;抽象类可以有构造方法(便于子类定义构造方法super,当创建子类对象时,给属性赋值);抽象类的子类要么重写抽象类中的所有抽象方法,要么子类也是抽象类;抽象类虽然不能实例化对象,但可以定义抽象类的引用,仍可以完成抽象的引用指向子类对象,即抽象类也可以实现多态。 - 抽象方法和抽象类的意义:强制子类必须按照定义的抽象方法的格式进行重写。
5.5 Java的接口
- 接口是一种规则,是对行为的抽象。若想要某个类拥有某行为,让该类实现对应类的接口即可。
- 接口的定义用
interface
实现:public interface 接口名 {}
- 接口不能被实例化,但是可以被实现;接口和类之间是实现关系,用
implements
关键字来实现,public class 类名 implements 接口名 {}
;一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类;一个类只能继承一个父类,但可以实现多个接口。 - 接口成员变量只能是常量,默认修饰符
public static final
;接口没有构造方法;接口成员方法只能是抽象方法(JDK7之前),默认修饰符public abstract
。 - 接口和接口之间是继承关系,可以单继承,也可以多继承。若实现类实现了子接口,则需要重写所有的抽象方法。
- JDK8新特性:接口中可以定义有方法体的方法(default,static),由于在接口中每次添加新的抽象方法(接口升级),该接口的全部实现类都要再重新这个新添加的抽象方法。
(1)允许在接口中定义默认方法(default修饰且不能省略),public default 返回值类型 方法名(参数列表) {}
。默认方法不是抽象方法,因此不强制实现类重写,但是如果默认方法被实现类重写,则重写时应去掉default关键字
;如果实现多个接口有同名的默认方法,那么实现类必须对该默认方法进行重写。
(2)允许在接口中定义静态方法(static修饰且不能省略),public static 返回值类型 方法名(参数列表) {}
。接口中的静态方法只能通过接口名调用,不能通过实现类名或对象名调用。 - JDK9新特性:接口中可以定义私有方法(private),为了提取接口中默认方法和静态方法重复代码,并且只为接口服务,不被外类访问。
private 返回值类型 方法名(参数列表) {}
为默认方法提供服务;private static 返回值类型 方法名(参数列表) {}
为静态方法提供服务(静态方法只能访问静态方法和静态变量)。 - 接口的多态:定义方法的时候,使用接口作为参数,则可以接收接口所有实现类对象,接口引用指向实现类对象。 例如:
接口类型 对象名称 = new 实现类类型()
- 适配器设计模式:由于实现类要实现接口的全部抽象方法,但可能我们只需要其中的几个方法,因此可以定义一个抽象类(抽象的目的是不让实例化这个类,没有意义)实现接口的全部抽象方法并重写为空,再由某个类来继承该抽象类,就可以重写需要的那几个方法,这个抽象类即适配器类(命名规则XXXAdapter)。若某个类有父类,则可以先让这个中间适配器类继承该父类,再让这个类继承这个中间适配器类。
5.6 Java的内部类($)
- 定义:在一个类(外部类)的里面,再定义一个类(内部类)。内部类表示的事物是外部类的一部分,内部类单独出现没有任何意义,例如汽车的发动机。
- 访问特点:内部类可以直接访问外部类的成员(包括私有);外部类要访问内部类的成员,必须创建对象。在内部类的方法中对成员的访问:当成员变量重名时,
this.成员变量名
调用内部类的成员变量;外部类名.this.成员变量名
调用外部类的成员变量,这是因为在执行内部类方法的时候会默认添加一个变量外部类名.this
即外部类的地址值。 - 成员内部类:写在成员位置的内部类,属于外部类的成员。JDK16之后才可以在成员内部类里定义静态变量。
(1)成员内部类对象创建方式一:直接创建外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
,相当于先创建一个外部类对象后再创建内部类对象。
(2)成员内部类对象创建方式二:当内部类用private修饰时,在外部类编写get方法,对外提供内部类对象,Object 内部类对象名 = 外部类对象.getXXX();
,由于内部类私有,外部其它类识别不了内部类,可用它的父类引用,但是这样的话对内部类特殊的成员和方法就无法使用了,因此在用内部类对象的时候直接使用外部类对象.getXXX()
来获取,不赋值给某变量。 - 静态内部类:用
static
修饰的内部类,属于成员内部类的一种。静态内部类只能访问外部类中的静态变量和静态方法,若要访问外部类的非静态成员则需要创建外部类对象。静态内部类对象创建方式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
,静态内部类中的非静态成员只有先创建对象后才可以调用;静态内部类中的静态成员可直接调用外部类名.内部类名.方法名()
。 - 局部内部类:将内部类定义在方法里,类似方法里的局部变量(不能用public修饰)。
- 匿名内部类:隐藏了名字(外部类名$序号)的内部类,可以写在成员位置(此时的匿名内部类就属于一个没有名字的成员内部类),也可以写在局部位置(此时的匿名内部类就属于一个没有名字的局部内部类),
new 类名或接口名(){ 重写的方法 };
定义了一个继承某类或实现某接口并重写方法的子类对象或实现类对象,这样就可以更加方便的使用多态类名或接口名 对象名 = new 类名或接口名(){ 重写的方法 };
而不用再重新定义一个继承某类的子类或实现某接口的实现类了,尤其是那些只使用一次的子类或实现类。
5.7 Lambda表达式
函数式编程(Functional programming)是一种思想特点。函数式编程思想,忽略面向对象的复杂语法,强调做什么,而不是谁去做。Lambda表达式就是函数式思想的体现,Lambda 表达式,也可称为闭包,它允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
格式:(形参) ->{ 方法体 }
(1)参数类型可省略不写。
(2)如果只有一个参数,参数类型可省略不写,()也可省略不写。
(3)如果方法体只有一行,{}、return和分号都可省略不写,这三个需同时省略。
Lambda表达式只能用于实现接口的匿名内部类,且该接口中只能有一个抽象方法,且它是作为函数的参数出现的,可以在接口上方用@FunctionalInterface
注解来验证是否是函数式接口。可以使代码更加简洁。
5.8 Java四大核心函数式接口
- 消费型接口:
Consumer<T>
,抽象方法void accept(T t);,接收一个参数但无返回值,常用于打印等消费动作。 - 供给型接口:
Supplier<T>
,抽象方法T get();,没有输入参数但返回一个T类型数据,常用于信息的获取。 - 函数型接口:
Function<T,R>
,抽象方法R apply(T t);,对传入参数T进行操作,返回数据R,类似于一个映射函数。 - 断言型接口:
Predicate<T>
,抽象方法boolean test(T t);,判断类型为T的对象是否符合约束条件,用于条件判断。
5.9 方法引用
方法引用:把已经有的方法拿过来用,当作函数式接口中抽象方法的方法体。::
方法引用符
条件:
(1)引用处必须是函数式接口。
(2)被引用方法必须已经存在,且形参和返回值必须和抽象方法保持一致,且被引用方法功能要满足当前需求。
- 引用静态方法:
类名::静态方法名
- 引用成员方法:
对象::成员方法
,本类可用this来代替本类对象(静态方法无this和super),父类可用super来代替父类对象。 - 引用构造方法:
类名::new
,用于对象的创建,如用Stream流的map将某数据映射为对象。 - 使用类名引用成员方法:
类名::成员方法
,该方法要保证成员方法的形参和抽象方法从第二个开始的形参保持一致即可。 - 引用数组的构造方法:
数据类型[]::new
5.10 Java的8大包装类
由于Java基本数据类型int、double、char等不是对象,不符合Java面向对象编程特点,因此每个基本数据类型都设计了一个对应的类进行代表,统称为包装类(Wrapper Class),均位于java.lang
包。
可通过静态方法来创建对象,如Integer integer = Integer.valueOf(int value);
,注:-128~127范围的Integer是提前创建好的。
自动装箱
:基本数据类型会自动转换为引用数据类型(底层仍是调用上边静态方法),如Integer integer = 10;
自动拆箱
:包装类会自动转换为对应的基本数据类型,如int i = integer;(JDK5之后出现的)
常用方法:
(1)将整数转换为二进制字符串 String str = Integer.toBinaryString(int value);
(2)将整数转换为八进制字符串 String str = Integer.toOctalString(int value);
(3)将整数转换为十六进制字符串 String str = Integer.toHexString(int value);
(4)字符串按进制转换为int整数 int value = Integer.parseInt(String str, int radix); //8大包装类除了Character都有对应的强转方法

5.11 泛型
泛型是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
泛型的格式:<数据类型>,注意:泛型只能支持引用数据类型,且可以传入该数据类型的子类类型,不写<>则默认是Object类型。
引入泛型的原因:对于集合来说,如果没有泛型,那么集合存储的是Object类型,可以存储全部类型对象(向上转型),但是在取出数据的时候,会无法使用子类特有的行为,虽然可以向下转型,但是每次还得判断该子类对象的类型instanceof,否则容易出现强转异常。引入泛型就可以统一数据类型,把运行时的问题提前到了编译期间,避免了强转异常。
Java泛型是伪泛型:由于泛型是JDK5引入的,为了向下兼容,这个泛型只是在添加的时候判断是否是指定的泛型类型,但在集合中存储的仍是Object类型,取出的时候会根据泛型类型向下转型。
- 泛型类: 当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类。
修饰符 class 类名<类型> {}
,类型常用T(type)、E(element)、K(key)、V(value)来代表,创建该类对象的时候,E等就会确定类型。 - 泛型方法:当方法中形参类型不确定时,可定义泛型方法。
修饰符 <E> 返回值类型 方法名(类型 变量名) {}
,当实参传入方法时,就会自动确定泛型的数据类型。 - 泛型接口:当接口中类型不确定时,可定义泛型接口。
修饰符 interface 接口名<类型> {}
,可在实现类定义时直接指定接口的类型,也可在实现类定义时延续该泛型。
注意:泛型不具备继承性,即泛型一旦指定具体数据类型,只能传递该类型的数据,如某方法的形参是指定数据类型的集合,则调用该方法只能传该种数据类型的集合,而不能是其子类数据类型的集合。但是数据具备继承性,如在父类集合种添加子类对象。
泛型的通配符:可以限定泛型种类型的范围,比如限定在某继承体系中。
(1)? :类似于E,表示任何不确定的类型,但是不用。
(2)? extends E :表示可以传递E类型或E的所有子类类型。
(3)? super E:表示可以传递E类型或E的父类类型和父类的父类等。
5.12 Java的内存管理
- 程序计数器(Program Counter Register): 程序计数器是一个较小的内存区域,它是线程私有的,它保存的是当前线程正在执行的字节码指令的地址。在多线程环境下,程序计数器能够保证线程切换后能恢复到正确的执行位置。
- Java虚拟机栈(Java Virtual Machine Stacks): Java虚拟机栈也是线程私有的,它用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法在执行时都会创建一个栈帧(Stack Frame),每个栈帧包含了方法的局部变量、操作数栈等信息。当一个方法被调用时,其对应的栈帧会被压入栈中,当方法执行完毕时,栈帧会被弹出栈。
- 本地方法栈(Native Method Stack): 本地方法栈与Java虚拟机栈类似,只不过它是为执行本地方法(Native Method)服务的。本地方法是使用其他语言(如C/C++)编写的,它们不使用Java虚拟机的字节码指令,而是直接调用底层操作系统的接口。
- Java堆(Java Heap): Java堆是Java虚拟机管理的最大的一块内存区域,它是线程共享的。Java堆用于存储对象实例和数组对象,由垃圾回收器负责管理内存的回收和分配。Java堆被划分为年轻代和老年代两部分。年轻代又被划分为Eden区和两个Survivor区(通常是一个较大的Eden区和两个较小的Survivor区),对象在年轻代中被创建和销毁,而在老年代中存活时间较长。
- 方法区(Method Area): 方法区也是线程共享的,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK8及以前的版本中,方法区是HotSpot虚拟机中的一个实现,从JDK8开始,方法区被替换为元空间(Metaspace)。
- 运行时常量池(Runtime Constant Pool): 运行时常量池是方法区的一部分,它用于存储编译期生成的各种字面量和符号引用。字面量包括字符串、数字常量等,符号引用包括类和方法的全限定名、字段的名称等。
- 直接内存(Direct Memory): 直接内存不是Java虚拟机规范中定义的一部分,但它是一块与Java堆外的内存区域,它可以通过Java的NIO(New IO)库直接操作。在使用NIO时,可以通过直接内存来减少数据在Java堆和操作系统之间的复制,提高IO操作的性能。直接内存是一种在Java堆外分配的内存空间,其生命周期不受Java垃圾回收的控制,因此可以更灵活地管理内存,并且避免频繁地将数据从Java堆复制到操作系统内核缓冲区。
6. Java的API
API(Application Programming Interface):应用程序编程接口
Java API:JDK中提供给我们使用的类,这些类将底层的代码实现封装了起来,我们不需要关心这些类是如何实现的,只要会使用即可。
Java API在线文档:JDK11API菜鸟教程中文版 JDK19API官方英文版
6.1 java.lang.Math
Math类是帮助我们进行数学计算的工具类,里面的方法都是静态的,直接用Math.方法名()
调用。
返回类型 方法名(参数)
(1)求绝对值 int abs(int a) ;
(2)向上取整 double ceil(double a); //向正无穷大方向获取距离最近的整数
(3)向下取整 double floor(double a); //向负无穷大方向获取距离最近的整数
(4)四舍五入 int round(float a);
(5)较大值 int max(int a, int b);
(6)较小值 int min(int a, int b);
(7)a的b次幂 double pow(double a, double b);
(8)a的平方根 double sqrt(double a);
(9)a的立方根 double cbrt(double a);
(10)随机值 double random(); //[0.0,1.0)
6.2 java.lang.System
System类也是一个工具类,提供了一些与系统相关的方法。
返回类型 方法名(参数)
(1)终止JVM void exit(int status); //status=0JVM正常停止;status非0JVM非正常停止
(2)系统时间毫秒 long currentTimeMillis();//时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数.
(3)数组拷贝 void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数); //基本数据类型数组类型必须一致;引用数据类型子类类型可以赋值给父类类型
6.3 java.lang.Runtime
Runtime表示当前虚拟机的运行环境。应用程序无法创建自己的此类实例,可以从getRuntime方法获得当前运行时实例对象,实例对象允许应用程序与运行应用程序的环境进行交互。
返回类型 方法名(参数)
(1)当前Runtime对象 Runtime getRuntime();
(2)停止JVM void exit(int status); //status=0JVM正常停止;status非0JVM非正常停止
(3)获取CPU线程数 int availableProcessors();
(4)JVM能获得内存大小 long maxMemory(); //byte为单位,JVM尝试使用的最大内存量
(5)JVM已获得内存大小 long totalMemory(); //JVM虚拟机中的内存总量
(6)JVM可用内存大小 long freeMemory();
(7)运行cmd命令 Process exec(String command);
6.4 java.lang.Object
Object类是Java中的顶级父类,所有的类都直接或间接的继承于Object类,Object类中方法可以被所有子类访问。
返回类型 方法名(参数)
(1)返回对象的字符串表现形式 String toString(); //在打印对象想要看到属性值,重写toString方法
(2)比较对象是否相等 boolean equals(Object obj); //比较的是两个对象的地址值,可重写equals方法比较属性值
(3)对象克隆(protected修饰) Object clone(); //把A对象的属性值完全拷贝给B对象。重写clone方法,javabean类实现Cloneable接口,最后克隆并强转
(4) 返回对象的运行时类 Class<?> getClass();
(5) 返回对象的哈希码值 int hashCode(); //默认使用地址值计算哈希值,一般会重写用属性值计算
(6) 唤醒此对象监视器上等待的单个线程 void notify();
(7) 唤醒等待此对象监视器的所有线程 void notifyAll();
(8) 使当前线程等待直到被唤醒 void wait(); //被notify或notifyAll唤醒
(9) 使当前线程等待直到被唤醒 void wait(long timeoutMillis);//被唤醒或到时间
(10)使当前线程等待直到被唤醒 void wait(long timeoutMillis, int nanos);//被唤醒或到时间
- 重写equals方法
(1)先判断this==o是否相等,就是先判断两者地址值是否相等即是否指向同一对象,若是则直接返回true;
(2)再判断o是否为空,或this和o是否属于同一类别,若o为空或不是同一类别,则直接返回false;
(3)最后将o强转为该类对象(强转是因为o可能是父类引用子类对象,因此要向下转型),再对每个属性进行比较是否相等。 - 重写hashCode方法
public int hashCode() { return Objects.hash(属性1, 属性2…); }
浅克隆:不管对象内部的属性是基本数据类型还是引用数据类型,都完全拷贝过来。Object类的clone方法是浅拷贝(拷贝的引用数据类型的地址值)
深克隆:基本数据类型拷贝,字符串在串池复用,引用数据类型会重新创建新的。可以重写clone方法,规定引用数据类型重新创建。
6.5 java.util.Objects
Objects类是一个工具类,用于操作对象或在操作前检查某些条件。包含的方法都是静态的,即可以Objects.方法名()
直接使用。
返回类型 方法名(参数)
(1)先做非空判断,再比较两个对象 boolean equals(Object a, Object b);
(2)判断对象是否为null boolean isNull(Object a); //若a为null则返回true
(3)判断对象是否不为null boolean nonNull(Object a); //若a不为null则返回true
6.6 java.math.BigInteger
BigInteger支持任意精度的整数,即该类型可以准确地表示任何大小的整数值而不会丢失任何信息。BigInteger对象一旦创建里面的数据不能发生改变,只要进行计算都会产生一个新的BigInteger对象,底层通过整型数组实现。
BigInteger构造方法
(1)public BigInteger(int numBits, Random rnd); //获取随机大整数,范围在[0~2^numBits-1]
(2)public BigInteger(String val); //将十进制字符串(必须是整数)表示形式转换为BigInteger
(3)public BigInteger(String val, int radix); //将radix进制字符串(必须与进制吻合)表示形式转换为BigInteger
BigInteger静态获取BigInteger对象方法
(4)public static BigInteger valueOf(long val); //返回一个值等于long的BigInteger对象。优化了-16~16已提前创建好BigInteger对象,多次获取不会重新创建新的
BigInteger常用方法
(5)加法:add() 减法:subtract() 乘法:multiply() 除法(商):divide() 较大值:max() 较小值:min() 次幂:pow()
(6)除法(商和余数) BigInteger[] divideAndRemainder(BigInteger val);
(7)比较是否相等(属性值,BigInteger重写了equals方法