Java基础知识体系
编程入门
定义
计算机包括硬件和软件
硬件
CPU、内存、存储设备、输入、输入、通信设备由总线(bus)联系起来,总线搭建在主板上
冯·诺伊曼体系结构

中央处理器(CPU):控制器:控制和协调其他组件的动作
运算器:完成数值运算和逻辑运算
1 Hz相当于每秒1个电子脉冲
一个CPU只有一个核,核是处理器中实现指令读取和执行的部分,多核可提高CPU的处理能力。
存储设备:磁盘、硬盘、可移动设备、U盘、光盘等
内存(RAM):由有序字节序列组成,用于存储数据
一个程序和它的数据在被CPU执行前必须移到计算机的内存中
内存存取数据的速度比硬盘的存取速度快10倍,CPU速度比内存还要快
每个字节都有一个唯一的地址

比特(bit)和字节(byte) :比特(bit)存0或者存1,最小的存储单位
字节(byte) :计算机中最基础的存储单元。每个字节由8个比特构成。
输入输出设备:键盘鼠标显示器等
显示器屏幕分辨率:指显示设备水平和垂直方向上显示的像素(px)数
分辨率越高,图像越锐化、越清晰 【1920×1080:指水平1920像素点,垂直1080像素点】ppi:像素密度越高,越好
通信设备:无线网、网卡等
计算机发展史鼻祖
图灵:计算机的基本概念,人工智能之父。图灵机,图灵奖
冯·诺依曼:计算机之父,博弈论之父。计算机采用二进制。
操作系统
OS管理和控制计算器的活动
操作系统主要任务:控制和监视系统的活动;分配和调配系统资源;调度操作
万维网
www分为Web客户端(浏览器)和Web服务器

互联网包含因特网包含万维网
跳槽攻略
github开源软件

Java语言概述
定义
Java基础是学习JavaEE、大数据、Android开发的基石

软件
一系列按照特定顺序组织的计算机数据和指令的集合。系统软件(操作系统)和应用软件
系统软件:ios,windows,Android。。。
应用软件:word,ppt。。。
应用程序 = 算法 + 数据结构
人机交互方式:图形化界面(GUI)[简单,易操作],命令行方式(CLI)[DOS指令]
常用命令行指令

计算机语言:人与计算机交流的方式(C、C++、Java、PHP、Python)
第一代语言:机器语言;二进制 第二代语言:汇编语言;助记符
第三代语言:高级语言;C面向过程、C++面向过程/面向对象、Java纯粹面向对象
JDK1.5是JDK5.0,以此类推JDK1.6是JDK6.0

Java语言的特点:1.面向对象[两个要素:类、对象;三大特征:封装、继承、多态]
- 健壮性[去掉指针、自动垃圾回收机制-->仍然会有内存溢出、内存泄漏问题]
- 跨平台性 可以在其他平台上运行
Java核心机制:JVM和垃圾回收


环境变量
java -version javac.exe java
JDK去github下载 安装路径不能包含中文、空格



cmd运行java代码
字节码文件xx.class的名字跟类名一致


注释
单行注释://注释一行代码
多行注释:/* */注释多行代码


文档注释(Java特有)/** @author等 */


API文档
API:应用程序编程接口是Java提供的基本编程接口;
将语言提供的类库成为API。
https://docs.oracle.com/javase/8/docs/api/

小结
IDE:开发环境

Java基本语法
定义
关键字与保留字
关键字:被java语言赋予了特殊含义,用做专门用途的字符串(单词)
特点:关键字中所有字母都为小写;如:public、private等
保留字:goto、const。自己命名标识符要避免使用(以后版本可能会用)
标识符
对各种变量、方法和类等要素命名时使用的字符[凡是自己起名都叫标识符:类名、包名、方法名、接口名、变量命]

如果不遵守命名规则,编译不通过。
起名要见名知意
变量
是程序中的最基本的存储单元,包含变量类型、变量名和存储的值
java是强类型语言,有些语言是var(弱)类型
- 定义变量的格式: 数据类型 变量名 = 变量值;
- 变量必须先声明,后使用。先赋值,才能输出
- 变量都定义在其作用域内( { } )。在作用域内有序,出了就失效
- 同一个作用域内,不能声明重名的变量
数据类型

整型:超范围编译不通过
浮点型:表示带小数点的数值;float表示数值的范围比long还大;定义浮点型变量时,通常用double。
字符型:定义char型变量,通常使用一对 ' ' ;内部只能[必须]写一个字符;占2字节。

布尔型:只能取true和false两个值,1个字节;在条件判断、循环结构中使用
类型转换



String(引用类型):


练习2中 1,3,5可以;2,4不行
进制(了解)
所有数字在计算机底层都以二进制形式存在

二进制转十进制


十进制转二进制

二进制与八进制与十六进制

运算符
算术运算符:

除( / )

取余( % )

自增:对于自增( ++ )来说,当不涉及运算时,只有自增1[自减与自增相同,前自增(自减)都是先自增(或自减)后运算(赋值);后自增(自减)都是先运算(赋值)后自增(或自减)]

连接符(+)只能用于String类型与其他类型之间[包括String类型]
赋值运算符:
+= 类似于 X = X + Y 但又有所不同[减,乘,除,模是一样的]


关系运算符:


逻辑运算符:
操作的都是布尔类型的变量,结果也是布尔型
开发中用&&,||
区分& 与 &&:
相同点:运算结果相同;当符号左边是true时,二者都会执行符号右边的运算
不同点:当符号左边时false时,&继续执行符号右边的运算,&&不再执行符号右边的运算
区分| 与 ||:
相同点:运算结果相同;当符号左边是false时,二者都会执行符号右边的运算
不同点:当符号左边时true时,|继续执行符号右边的运算,||不再执行符号右边的运算

位运算符:(都是基于二进制的)
位运算符操作的都是整型的数据
左移:(基于二进制)每向左移一位,数乘以2;当最高位为1时,变为负数。左移拿0补
右移是每向右移一位,数除以2,右移是负数拿1补,正数拿0补

三元运算符:

运算符优先级

想先算啥加小括号就行了
流程控制
顺序结构
程序从上到下,逐行执行
分支结构
if-else(条件判断结构)

Scanner类:
X.charAt(0):获取索引为0位置上的字符

注意点:
-
if语句里的判断条件不能直接写出来,得用&&(比如成绩在(80,90] )
-
else 结构式可选的
-
针对于条件表达式:
- 如果多个条件表达式之间是“互斥”关系(或没有交集的关系),哪个判断和执行语句声明在上面还是下面,无所谓
- 如果多个条件表达式之间有交集关系,需要根据实际情况,考虑清楚应该将哪个结构声明在上面
- 如果多个条件表达式之间有包含关系,通常情况下,需要将范围小的声明在范围大的上面。否则,范围小的就没机会执行
-
if-else结构是可以相互嵌套的
-
如果if-else结构中的执行语句只有一行时,对应的一对{}是可以省略的。但不建议省略,面试时知道就行
- 当{}省略时,else就近原则,跟最近的if配对

equals:当想判断String类型的字符串时,用equals可以判断输入的字符串是否和给定的相等equals([里面可以写东西])
Switch-case:


特点:
-
如果switch-case结构中的多个case的执行语句相同,则可以考虑进行合并
-
当输入某年的月份和日期求天数时,可以倒着写以此类推
-

case1:sumDays += day


循环结构
在某些条件满足的情况下,反复执行特定代码的功能
循环结构的4个要素:
- 初始化条件
- 循环条件 -->是Boolean类型
- 循环体
- 迭代条件
- 说明:通常情况下,循环结束都是因为循环条件返回false了
for循环:
print()不换行 println()换行
for循环中,当循环条件不满足的时候,可能for进不去,导致之前的未赋值的声明变量无法赋值,程序报错。变量在使用之前一定要赋值!
while循环:

do-while循环:

当读入不确定的数时,类似无限循环:

嵌套循环


时间复杂度:根据次幂才能改变时间复杂度
像O(2n)-->O(n),O(n/2)-->O(n),只要不改变幂,都是O(n)。


break和continue


return是结束方法的,break和continue是结束循环体的
衡量一个功能代码的优劣:
- 正确性
- 可读性
- 健壮性
- 高效率与低存储:时间复杂度、空间复杂度(衡量算法的好坏)
Java数组
定义
数组:是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理
数组要素:数组名、元素、下标、数组的长度(元素的个数)
数组的特点:
-
数组是有序排序的
-
数组属于引用数据类型的变量。数组的元素,既可以是基本数据类型,也可以是引用数据类型
-
创建数组对象会在内存中开辟一整块连续的空间
-
数组的长度一旦确定,就不能修改
数组的分类:
- 按照维数:一维数组、二维数组等等
- 按照数组元素的类型:基本数据类型元素的数组、引用数据类型元素的数组
一维数组:


总结:数组一旦初始化完成,其长度就确定了。

用.length获取数组的长度
数组元素的默认初始化值:
- 整形:0
- 浮点型:0.0
- char型:0或'\u0000'
- boolean型:false
- String(引用类型):null
内存的简化结构:

放在方法中的都是局部变量

多维数组:
从数组底层的运行机制来看,其实没有多维数组


当二维数组只定义了String[4] []时,如果直接调用会报空指针的错。
二维数组遍历需要用两层for循环


引用类型的变量,比如对象的对象名、数组的数组名strs,存null或地址值。基本类型变量,存本来存的值。[引用类型定义的变量放在常量池中,由地址值来调用]
数据结构:
- 数据与数据之间的逻辑关系:集合、一对一(链表)、一对多(树)、多对多(图)
- 数据的存储结构:
- 线性表:刻画一对一的关系:顺序表(比如:数组)、链表、栈、队列
- 树形结构:刻画一对多的关系:二叉树(检索)
- 图形结构:多对多的关系
算法:排序算法、搜索算法、动态规划、递归等
package yanghui;
/*
使用二维数组打印一个10行杨辉三角
【提示】
1.第一行有1个元素,第n行有n个元素
2.每一行的第一个元素和最后一个元素都是1
3.从第三行开始,对与非第一个元素和最后一个元素的元素,即:
yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];
*/
public class YangHuiTest {
public static void main(String[] args) {
//1.声明并初始化二维数组
int[][] yangHui = new int[10][];
//2.给数组的元素赋值
for (int i = 0; i < yangHui.length; i++) {
yangHui[i] = new int[i+1];
//2.1给首末元素赋值
yangHui[i][0] = yangHui[i][i] = 1;
//2.2给每行的非首末元素赋值
if(i>1){
for (int j = 1; j < yangHui[i].length-1; j++) {
yangHui[i][j] = yangHui[i-1][j-1] + yangHui[i-1][j];
}
}
}
//3.遍历二维数组
for (int i = 0; i < yangHui.length; i++) {
for (int j = 0; j < yangHui[i].length; j++) {
System.out.print(yangHui[i][j]+" ");
}
System.out.println();
}
}
}
数组常见算法
赋值题:如:杨辉三角,回形数、随机赋值





package suanfa;
/*
算法的考查:求数值型数组中元素的最大值、最小值、平均数、总和等
定义一个int型的一维数组,包含10个元素,分别赋一些随机整数
然后求出所有元素的最大值、最小值、和值、平均值,并输出出来。
要求:所有随机数都是两位数
[10,99]:获取范围的随机数
公式:(int)(Math.random() * (b - a + 1) + a)
*/
public class ArrayTest {
public static void main(String[] args) {
int[] arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random()*(99-10+1)+10);
}
//求数组的最大值
int maxValue = arr[0];
for (int i = 1; i < arr.length; i++) {
if(maxValue < arr[i]){
maxValue = arr[i];
}
}
System.out.println("最大值为:" + maxValue);
//求数组的最小值
int minValue = arr[0];
for (int i = 1; i < arr.length; i++) {
if(minValue > arr[i]){
minValue = arr[i];
}
}
System.out.println("最小值为:" + minValue);
//求数组的总和
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum = sum = arr[i];
}
System.out.println("总和为:" + sum);
//求数组的平均数
int avgValue = sum / arr.length;
System.out.println("平均数为:" + avgValue);
}
}
数组的赋值:
当两个数组有一个数组去给另一个数组赋值时(前提:类型一样,维度一样)
赋值的是地址值;修改一个数组的数值,另一个的数组的数值也会相应的改变。
数组的复制:需要将一个数组的值一一赋值给另一个数组的数值(用for循环赋值)
线性查找:最基本的查找

二分法查找:前提:所要查找的数组必须有序


排序:通常来说,排序的目的是快速查找
衡量排序算法的优劣:时间复杂度、空间复杂度、稳定性。
排序算法的分类:内部排序(内存);外部排序(硬盘)
内部排序算法:

算法5大特征:
- 输入
- 输出
- 有限性
- 确定性
- 可行性


快排的时间复杂度:O(nlogn)
冒泡时间复杂度:O(n^2)
Arrays工具类的使用

数组常见异常:
- 数组下标越界异常
- 数组空指针异常
- 一维数组给数组赋值null,调用数组里的值时会报
- 二维数组int[4] []后面没给定,调用里面会报。调用外层输出null
Java面向对象
“面向对象”的编程思想:当你想做某件事时,就是思想。编程有原则,编程原则是编程思想,之后用方法去做。
面向过程(POP):强调的是功能行为,以函数为最小单位,考虑怎么做
面向对象(OOP):强调具备了功能的对象,以类/对象为最小单位,考虑谁来做

对于一个问题可以简单理解成名词是类,动词是方法

内存解析的说明:引用类型的变量,只可能存储两类值:null,或地址值(含变量的类型)
匿名对象和匿名子类



由于Person类为抽象类,不能实例化该类,但子类可以重写该类中的方法然后去调用,为了省事,就匿名该子类【不单独创建一个子类】(用法:new Person(){ 重写的方法 };)
Java类及类的成员
类(Class):是抽象的、概念上的定义。对一类的事物的描述。例如:人
对象(Object):是实际存在的该类事物的每个个体,称为实例。例如:(实实在在的人)乔布斯
面向对象思想落地的实现:
-
面向对象程序设计的重点是类的设计,类的设计:其实就是类的成员的设计。
-
使用类:类的实例化,即创建类的对象【类名 对象名 = new 类名()】
-
调用对象的结构;对象是由类派生出来的
如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static)
创建类的对象 = 类的实例化 = 实例化类
内存解析:

编译完源程序以后,生成一个或多个字节码文件。
我们使用JVM中的类的加载器和解释器对生成的字节码文件进行解释运行。意味着,需要将字节码文件对应的类加载到内存中,涉及到内存解析。
虚拟机栈,即为平时提到的栈结构。我们将局部变量存储在栈结构中
堆,我们将new出来的结构(比如:数组、对象)加载在堆空间中。补充:对象的属性(非static的)加载在堆空间中。
方法区:类的加载信息、常量池、静态域
对象的内存解析:


属性
属性 = 成员变量 = field = 域、字段
调用属性:对象.属性
成员变量与局部变量:
-
相同点
- 定义变量的格式是一样的。数据类型 变量名 = 变量值
- 先声明,后使用
- 变量都有其对应的作用域
-
不同点:
-
在类中声明的位置不同
- 属性:直接定义在类的一对{}内
- 局部变量:声明在方法体内、方法形参、代码块内、构造器形参、构造器内部的变量
-
关于权限修饰符的不同
-
属性:可以在声明属性时,指明其权限,使用权限修饰符
常用权限修饰符:public、private、protected、default(缺省:没写修饰符)
-
局部变量:不可以使用权限修饰符
-
-
默认初始化的情况
-
属性:类的属性,根据其类型,都有默认初始化值
整型:0
浮点型:0.0
字符型:0(或'\u0000')
布尔型:false
引用类型:null
-
局部变量:没有默认初始化值
意味着,我们在调用局部变量之前,一定要显示赋值
特别注意:形参在调用时,才赋值。不要再方法声明中赋值
-
-
在内存中加载的位置
- 属性:加载到堆空间中(非static)
- 局部变量:加载到栈空间
-
方法
方法 = 成员方法 = 函数 = method
调用方法:对象.方法
描述类应该具有的功能
方法的声明:权限修饰符 返回值类型 方法名(形参列表[可有可无]){
方法体
}

-
方法也有权限修饰符
-
返回值类型
- 如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量。
- 如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return。但是,如果使用的话,只能 "return;" 表示结束此方法的意思。
-
定义的方法该不该有返回值?
- 看题目要求
- 经验,具体问题具体分析
-
方法名:属于标识符,遵循标识符的规则和规范。“见名知意”
-
形参列表:
- 方法可以声明0个,1个,或者多个形参。
格式:数据类型1 形参1,数据类型2 形参2,...
- 定义方法时,该不该定义形参?
- 题目要求
- 经验,具体问题具体分析
-
方法体:方法功能的体现
return关键字的使用:
-
使用范围:使用在方法体中
-
作用:
-
结束方法
-
针对有返回值类型的方法,使用"return 数据"方法返回所要的数据。
-
-
注意点:return关键字分号结束后面不可以声明执行语句
方法的使用:
-
可以调用当前类的属性或方法
特殊的:方法A中又调用了方法A:递归方法:得有终止条件
-
方法体中不可以再定义一个方法

对象数组:
package objectarray;
/*
对象数组的题:
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int).
创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
提示:
(1)生成随机数:Math.random(),返回值类型为double
(2)四舍五入取整:Math.round(double d),返回值类型为long
*/
public class ObjectArray {
public static void main(String[] args) {
//声明Student类型的数组
Student[] stu = new Student[20];
for (int i = 0; i < stu.length; i++) {
//给数组元素赋值
stu[i] = new Student();
//给Student对象的属性赋值
stu[i].number = i + 1;
//年级:[1,6]
stu[i].state = (int) (Math.random() * (6 - 1 + 1) + 1);
//成绩:[0,100]
stu[i].score = (int) (Math.random() * (100 - 0 + 0) + 1);
}
//遍历学生数组
for (int i = 0; i < stu.length; i++) {
System.out.println(stu[i].info());
}
System.out.println("============");
//问题一:打印出3年级(state值为3)的学生信息
for (int i = 0; i < stu.length; i++) {
if(stu[i].state == 3){
System.out.println(stu[i].info());
}
}
System.out.println("============");
//问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
for (int i = 0; i < stu.length - 1; i++) {
for (int j = 0; j < stu.length - 1 -i; j++) {
if(stu[j].score > stu[j+1].score){
Student temp = stu[j];
stu[j] = stu[j+1];
stu[j+1] = temp;
}
}
System.out.println(stu[i].info());
}
}
}
class Student{
int number;
int state;
int score;
//显示学生信息的方法
public String info(){
return "学号" + number + ",年级" + state + ",成绩" + score;
}
}
package objectarray;
/*
对象数组的题:
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int).
创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
提示:
(1)生成随机数:Math.random(),返回值类型为double
(2)四舍五入取整:Math.round(double d),返回值类型为long
改进代码,将操作数组的功能封装到方法中
*/
public class ObjectArray1 {
public static void main(String[] args) {
//声明Student类型的数组
Student1[] stu = new Student1[20];
for (int i = 0; i < stu.length; i++) {
//给数组元素赋值
stu[i] = new Student1();
//给Student对象的属性赋值
stu[i].number = i + 1;
//年级:[1,6]
stu[i].state = (int) (Math.random() * (6 - 1 + 1) + 1);
//成绩:[0,100]
stu[i].score = (int) (Math.random() * (100 - 0 + 0) + 1);
}
ObjectArray1 test = new ObjectArray1();
//遍历学生数组
test.print(stu);
//问题一:打印出3年级(state值为3)的学生信息
test.searchState(stu,3);
//问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
test.sort(stu);
}
//遍历Student1[]数组的操作
public void print(Student1[] stu){
for (int i = 0; i < stu.length; i++) {
System.out.println(stu[i].info());
}
}
//查找Student1数组中指定年级的学生信息
public void searchState(Student1[] stu,int state) {
for (int i = 0; i < stu.length; i++) {
if (stu[i].state == state) {
System.out.println(stu[i].info());
}
}
}
//给Student1[]数组排序
public void sort(Student1[] stu) {
for (int i = 0; i < stu.length - 1; i++) {
for (int j = 0; j < stu.length - 1 - i; j++) {
if (stu[j].score > stu[j + 1].score) {
Student1 temp = stu[j];
stu[j] = stu[j + 1];
stu[j + 1] = temp;
}
}
System.out.println(stu[i].info());
}
}
}
class Student1{
int number;
int state;
int score;
//显示学生信息的方法
public String info(){
return "学号" + number + ",年级" + state + ",成绩" + score;
}
}
方法的重载
同类同名不同参:在同一个类中,两个方法同一个名但形参不同
形参不同可以是:形参类型、形参顺序、形参个数
判断是否重载:跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系。
在通过对象调用方法时,如何确定某一个指定的方法:
方法名 ---> 参数列表

值传递机制
形参和实参是两个变量
方法内关于变量的赋值:
如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
方法的形参的传递机制:值传递
- 形参:方法定义时,声明的小括号内的参数
- 实参:方法调用时,实际传递给形参的数据
- 形参的改变不会影响实参,哪怕是用了返回值类型的也不会改变
Java方法中的参数传递机制的具体体现:值传递机制:
如果变量是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。

如果变量是引用数据类型,此时实参赋给形参的是实参存储数据的地址值(含变量的数据类型)。
【String引用类型是特殊的,它定义的都在常量池中,用地址值调用】

每个方法中形参的值不会影响其他方法中的值
递归recursion
一个方法体内调用它自身

main()方法详解
-
作为程序的入口 写好的程序都通过main()方法运行
-
是一个普通的静态方法
因为main()方法是静态的,无法调用非静态结构,所以要通过对象去调用非静态属性或方法
public是权限,让其他类可以调用main

-
可以作为我们与控制台交互的方式
运行时侯main()方法可以传入数据【不用Scanner类】


构造器
构造方法;constructor:建造者

构造器的作用:
-
创建对象 new + 构造器【类的对象: 类名 名字 = new 类的构造器】
-
初始化对象的信息
说明:
-
如果没有显示的定义类的构造器的话,则系统默认提供一个空参的构造器
这个空参的构造器的权限看类的权限
-
定义构造器的格式:权限修饰符 类名(形参列表){}
-
一个类中定义的多个构造器,彼此构成重载
-
一旦我们显示的定义了类的构造器之后,系统就不在提供默认的空参构造器
-
一个类中,至少会有一个构造器


代码块
类似于方法体【使用频率不高】

Root是Mid的父类,Mid是Leaf的父类
由父及子,静态先行:先去执行父类的东西,然后看静态和非静态【初始化块是代码块】
代码块的执行先于构造器
因为静态的代码块会随着类的加载而执行,所以就算main()方法想输出某些值也得等静态代码块执行完之后才开始执行

{
id = 4;
}
int id = 3;
此时输出的是3
因为在分配空间中,先把id给了一个空间,之后才进行的赋值操作。所以当id=4后再执行id=3输出的是3
**分配空间在前,赋值在后。**
int id = 3;
{
id = 4
}
输出的为4

内部类


对于内部类来说的三个问题:
- 如何实例化成员内部类的对象

- 如何在成员内部类中区分调用外部类的结构
class Person{
String name = 小明;
class Brid{
String name = 杜鹃;
public void display(String name){
System.out.println(name); //方法的形参
System.out.println(this.name); //内部类的属性
System.out.println(Person.this.name); //外部类的属性
}
}
}
输出语句:
黄鹂
杜鹃
小明
-
开发中局部内部类的使用
Thread。Integer
创建的类不在外部干涉。标准方式:

class A{
public void method(){
int num = 10;
class B{
//num = 20; 报错,num为final的,不可以修改
System.out.println(num); //但可以调用
}
}
}
通过副本变相地延长局部变量的生命周期让局部内部类可以一直能够访问该局部变量,同时final防止内部类的代码对局部变量产生修改;这么一来,可以推测这个副本应该是一个引用型的copy

面向对象的三大特征
封装性


封装性的体现:
- 用私有的(private)来封装属性,使得这个属性不可见,通过其他方法来调用这个属性,此方法需要公共的(public)来获取和设置
- 不对外暴露的私有的方法
- 单例模式 (构造器私有化)
封装性的体现,需要使用权限修饰符来配合
权限修饰符
- Java规定的4种权限:(从小到大排列):private、default(没有写权限修饰符)、protected、public

-
4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
-
具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public
总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
继承性




方法的重写
什么是方法的重写(override 或 overwrite):
子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
应用:
重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参的方法时,实际执行的是子类重写父类的方法。


特殊的:父类定义了private权限的方法,在子类中修改权限是不会重写的。
当在父类中的一个方法体内加入了调用的private私有的方法,就算子类"重写"(严格意义来讲其实没有重写)用子类对象调用该方法体时,结果还是父类的该私有方法,而如果是其他能够重写成功的,调用该方法体是子类重写后的结果
重载与重写:

多态性
多态性是运行时行为;实现代码的通用性
对象的多态性:父类的引用指向子类的对象
编译看左边,运行看右边
当调用子父类同名同参数的方法时,实际执行的是子类重写的方法。
多态性主要解决的问题是:
-
减少代码量
-
对于真实世界来说,可以是多样化的,而多态就体现这种多样化
比如:我定义了一个动物类,猫、狗类继承动物类,重写动物类中的方法。
只需要在方法体中定义动物类的方法,main方法中创建动物类对象,当调用方法时只需要输出你想要的匿名对象就可以调用对应的方法。(调用狗的方法,new狗的对象)




为什么要向下转型:为了调用子类中的属性和方法


public class InterviewTest {
public static void main(String[] args) {
Base base = new Sub();
base.add(1,2,3); //sub_1
Sub s = (Sub)base;
s.add(1,2,3); //sub_2
}
}
class Base{
public void add(int a,int... arr){
System.out.println("base1");
}
}
class Sub extends Base{
//一个类中,不能有int... arr 和 int[] arr 因为调用时编译器不知道调用哪个
//但可以重写,因为编译器认为他俩是一样的
public void add(int a,int[] arr){
System.out.println("sub_1");
}
public void add(int a,int b,int c){
System.out.println("sub_2");
}
}
关键字
this关键字
当我们的形参名字和属性名同名时,赋值时采用就近原则使得值没有赋上。
这时可以用this关键字this.属性 = 形参名来赋值


this.age是当前对象的年龄,girl.age是形参的年龄
package关键字
-
为了更好的实现项目中类的管理,提供包的概念
-
使用package声明类或接口所属的包,声明在源文件的首行
-
包,属于标识符,遵循标识符的命名规则、规范、''见名知意"
-
每"."一次,就代表一层文件目录
-
同一个包下,不能命名同名的接口、类
不同的包下,可以命名同名的接口、类

SpringMVC:

import关键字
import:导入使用:
-
在源文件中显示的使用import结构导入指定包下的类、接口
-
声明在包的声明和类的声明之间
-
如果需要导入多个结构,则并列写出即可
-
可以使用"xxx.*"的方式,表示可以导入xxx包下的所有结构
-
如果使用的类或接口时java.lang包下定义的,则可以省略import结构
-
如果使用的类或接口是本包下定义的,则可以省略import结构
-
如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。例如:
com.ma.exer3.Account acct = new com.ma.exer3.Account();
-
如果使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显示import结构
-
import static:导入指定类或接口中的静态结构:属性或方法
例如:
import static java.lang.System.*;
这样,输出语句System.out
中Sysetem
可以省略

super关键字
当子父类中有重名的属性:用super可以调用父类的属性,this是当前(子类)的属性

super调用构造器:
- 我们可以在子类的构造器中显示的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
- "super(形参列表)"的使用,必须声明在子类构造器的首行!
- 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
- 在构造器的首行,没有显示的声明"this(形参列表)"或"super(形参列表)",则默认调用的是父类中空参的构造器:super()
- 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
- 对于子父类来说,当子类定义构造器时用了super(空参),而父类没有显示定义空参构造而定义了有参构造,程序报错;必须父类中显示定义无参构造。
无论通过哪个构造器创建子类对象,需要保证先初始化父类
目的:当子类继承父类后,"继承"父类中所有的属性和方法,因此子类有必要知道父类如何为对象进行初始化。
instanceof关键字
相同父类的子类之间无法强转
new的是父类对象,无法强转成子类对象,因为父类没有子类特有的功能,无法调用


static关键字
某些特定的数据在内存空间里只有一份
static是共享的,主要用来修饰类的内部结构
用static修饰的属性或方法随着类的加载而加载,但没有执行
【静态代码块会随着类的加载而执行一次】
存在于方法区的静态域



生命周期:类先出生,同时类的静态属性和静态方法也出生;【此时对象还没有出生】
之后是对象出生,同时非静态的方法和属性也出生;【可以用对象去调静态属性方法。省略的是"类."】
对象消亡,完了当结束程序时,类才消亡。

单例设计模式

package singleton;
/**
* @author MaYunLong
* @date 2021/9/20 - 19:13
*/
public class SingletonTest1 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2); //true
}
}
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创建类的对象
// private Bank instance = new Bank();
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
package singleton;
/**
* @author MaYunLong
* @date 2021/9/20 - 19:18
* 懒汉式
*/
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2); //true
}
}
class Order{
//1.私有化类的构造器
private Order(){
}
//2.先声明当前类对象,没有初始化
//4.此对象必须声明为static的
private static Order instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null) {
instance = new Order();
}
return instance;
}
}
解决懒汉式线程安全问题:



单例模式的应用场景:

final关键字

final修饰属性时:
- 不能用默认初始化
- 可以显示初始化
final int WIDTH = 0
- 可以代码块初始化:因为代码块中赋值类似于显示赋值
- 可以在构造器中初始化:对于无参构造器初始化值了,但有参构造器没有初始化值会报错的情况:所有构造器里必须都初始化,不然调带参的一样没给值
- 不能在方法中赋值:因为构造器是对象出生的最后一道关卡,而此时构造器里的属性必须有值了;而final修饰的又不能默认初始化,所以不行。
三个赋值的选择:
当好几个对象的属性值都一样,就可以用显示初始化
当赋的值是一个方法,而此方法又抛异常得处理,就必须在代码块中初始化
多个对象的属性值不一样,可以在有参构造中赋
final修饰局部变量时:
-
在方法体内的局部变量加final表明这个局部变量是个常量,不能进行数值操作。
-
在形参中修饰,不会直接报错,因为形参的定义是在方法调用时才赋值,不影响。
static final 只能修饰属性和方法:修饰属性是全局常量,修饰方法就是不能重写

题目一:return ++x 不行,因为对x进行操作改值了,导致x变了,就不行【final不可变】
return x + 1 可以,因为x没变,只是返回的是x大于1的值
题目二:o.i++ 可以,此时的o是相当于一个地址去调用里面的i,而i不是final,i可以变,所有i++没问题
而 o = new Other() 变了对象了,o变了,所以不行。
abstract关键字
抽象类和抽象方法的应用场景:对于一些不好写的方法,例如可能不需要写方法体,就可以用该关键字,子类重写该方法就好了。
抽象的使用前提:---> 继承性


abstract不能修饰静态方法:原因:
- 静态方法不能够被重写
- 静态方法是类在加载时就被加载到内存中的方法,在整个运行过程中保持不变,因而不能重写
- abstract和static不能共用,因为static方法可以被类直接调用,而abstract修饰的类本身没有方法体,不希望被调用而是希望被重写
抽象类与抽象方法


举例:IO流等


接口
打破单继承的局限性



接口的使用:
- 接口使用上也满足多态性
- 接口,实际上就是定义了一种规范
- 开发中,体会面向接口编程!

JDK8以后接口的新特性:





代理模式
代理模式是用两个类实现接口的方法,用一个类实现基本的主题逻辑。另一个缝缝补补,调用主体类实现的方法。相当于主题部分是不变的,细枝末节可以变。

/**
* @author MaYunLong
* @date 2021/9/23 - 16:36
*/
public class StaticProxyTest {
public static void main(String[] args) {
Star s = new Proxy(new RealStar());
s.confer();
s.signContract();
s.bookTicket();
s.sing();
s.collectMoney();
}
}
interface Star{
void confer(); //面谈
void signContract(); //签合同
void bookTicket(); //订票
void sing(); //唱歌
void collectMoney(); //收钱
}
//被代理类
class RealStar implements Star{
@Override
public void confer() {
System.out.println("sss");
}
@Override
public void signContract() {
}
@Override
public void bookTicket() {
}
@Override
public void sing() {
System.out.println("明星:歌唱~~~");
}
@Override
public void collectMoney() {
}
}
//代理类
class Proxy implements Star{
private Star real;
public Proxy(Star real){
this.real = real;
}
@Override
public void confer() {
System.out.println("经纪人面谈");
}
@Override
public void signContract() {
System.out.println("经纪人签合同");
}
@Override
public void bookTicket() {
System.out.println("经纪人订票");
}
@Override
public void sing() {
real.sing();
}
@Override
public void collectMoney() {
System.out.println("经纪人收钱");
}
}
//输出结果
经纪人面谈
经纪人签合同
经纪人订票
明星:歌唱~~~
经纪人收钱
工厂设计模式

简单工厂模式
XXX Factory 就是创建者理解为工厂,设计思想就是将创建者和调用者分离,不写在一个方法体内。

工厂方法模式

//工厂接口
interface Factory {
Car getCar();
}
//两个工厂类
class AudiFactory implements Factory{
public Audi getCar(){ //重写的类型可以比父类小
return new Audi();
}
}
class BydFactory implements Factory{
public BYD getCar(){
return new BYD();
}
}
public class Client {
public static void main(String[]args){
Car a new AudiFactory().getCar( );
Car b new BydFactory().getCar();
a.run();
b.run();
}
}

抽象工厂模式

Java中一些常用技巧
针对Eclipse中的单元测试
在项目中较大时使用测试类来看每一块的问题

针对Eclipse的Debug
如何调试程序:
- 在一些关键语句后面加上输出语句
- Debug
- Debug的一些操作

ieda中Debug看"idea操作"文件夹中的md
异常

异常:在Java语言中,将程序执行中发生的不正常情况称为"异常"
(开发过程中的语法错误和逻辑错误不是异常)
Java程序在执行过程中所发生的异常事件可分为两类:
Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。
Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
√空指针访问
√试图读取不存在的文件√网络连接中断
√数组角标越界
Error:
//1.栈溢出:java.lang.stackOverflowError
main(args);
//2.堆溢出:java.lang.OutOfMemoryError
Integer[]arr = new Integer[1024*1024*1024];
这种只能改代码
Exception:
对于这些错误,一般有两种解决方法:一是遇到错误就终止程序的运行。
另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。
捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。
比如:除数为0,数组下标越界等
分类:编译时异常和运行时异常


编译时异常 不会生成字节码文件
IOException //文件流等异常
运行时异常 会生成字节码文件
NullPointerException //空指针
ArrayIndexOutOfBoundsException //数组角标越界
StringIndexoutOfBoundsException //字符串角标越界
ClassCastException //类型转换异常
NumberFormatException //数值类型不匹配
InputMismatchException //输入不匹配
ArithmeticException //算数异常
异常处理方式




try - catch - finally
运行时异常不用try - catch
编译时异常一定要用try - catch
try-catch-finally的使用
结构:
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2变量名2){
//处理异常的方式2
}catch(异常类型3变量名3){
//处罪异常的方式3
finally{
//一定会执行的代码
}

try - catch 结构可以相互嵌套 写在finally语句中 FileStream fis = null; finally{ try{ if(fis != null) //加了这句判断,就防止它出现空指针 //因为当fis为null就去另一个try中处理异常,就传不过来了 fis.close //对于文件流来说,可能会出现空指针异常 }catch(Exception e){ e.printStackTrace(); }
体会1:
使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。
相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。体会2:
开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了.
针对于编译时异常,我们说一定要考虑异常的处理。体会3:
方法重写的规则之—:
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
finally

throws

throw
关于异常对象的产生: 系统自动生成的异常对象
手动的生成一个异常对象,并抛出(throw)
throw是手动抛出一个类型异常 方式:throw + new + 异常类型("想输出的话【可不写】")
这样程序必须处理此异常,到时候输入错误的值会报你写的值。
自定义异常类

写法:
public class MyException extends RuntimeException{
static final long serialVersionUID = -7034897193246939L;
public MyException(){
}
public MyException( String msg){
super(msg);
}
}
总结

throw 和 throws 的区别:
throw是生成一个异常对象
throws是处理这个异常对象



多线程
基本概念




线程创建
创建线程在5.0之前是两种方式 ,JDK5.0之后又加了两种创建线程的方式,所以现在创建线程的方式有4种

多线程的创建,方式一:继承于Thread
1.创建一个继承于Thread类的子类
2.重写Thread类的run()方法 --> 将此线程执行的操作声明在run方法中
3.创建Thread类的子类的对象
4.通过此对象调用start()方法 //start()方法在父类Thread中子类继承了该方法
//statr()方法 ①启动当前线程 ②调用当前线程的run()
//1.创建一个继承于Thread类的子类
class MyThread extends Thread {
//2.重写Thread类的run()方法
@Override
public void run() {
//方法体 方法运行时才是独立运行的线程
}
}
public class ThreadTest {
public static void main(String[] args){
//3.创建Thread类的子类的对象 造对象还是主线程做的
MyThread t = new MyThread();
//4.通过此对象调用start()方法 调用start对象也是主线程做的
t.start();
//问题一:我们不能通过直接调用run()的方式启动线程。
//t.run 不能直接调用run方法,这样还是在主线程运行的,没有多开线程
//问题二:再启动一个线程,就再造个对象。不可以还让已经start()的线程去执行。会报 1llegalThreadstateException()异常
//如下操作仍然是在main线程中执行的。
for (int i = o; i < 100; i++) {
if(i % 2 == 0){
system.out.println(i +"***********main()************");
}
}
}
}
Thread.currentThread().getName() + ":" + i //这个是调用显示一个名字的方法用来区分线程的
//创建Thread类的匿名子类的方式
new Threrd() {
@override
public void run( ) {
//重写的方法体
}
}.start();
创建多线程的方式二:实现Runnable接口
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法: run()
3.创建实现类的对象
4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
//2.实现类去实现Runnable中的抽象方法: run()
@override
public void run() {
//方法体
}
}
}
}
public class ThreadTest1 {
public static void main(string[] args) {
//3.创建实现类的对象
MThread mThread = new MThread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
//5.通过Thread类的对象调用start( )
//①启动线程
//②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
}
}

Thread类也实现了Runnable接口
线程的方法


注意:yield释放完可能CPU又给该线程了,故又执行此线程【一般都是执行另一个线程】
线程通信: wait() / notify() / notifyAll() :此三个方法定义在Object类中的。
线程的分类

线程的调度


线程的生命周期
线程的状态:



线程安全问题
假设一个数据被共享,a和b都想获得这个数据,取出的值一样,但数据却不够两份的,那么就会出现安全问题。

线程同步
方式一:同步代码块

补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器。可以考虑使用当前类充当同步监视器
共享数据不能包含代码多了【题意描述可能不对(一个线程可能拿完所有数据),另外就是包多效率低了】
包含代码也不能少【出现安全问题】
同步的方式,解决了线程的安全问题。---> 好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。---> 有局限性
对于用实现Runnable接口的类来说,在synchronized中的同步监视器可以造个对象也可以用this
class Window implements Runnable{
//object obj = new object(); 也可以造个对象把对象放进synchronized(obj)中
@Override
public void run() {
//object obj = new object(); 这样造是不行的,因为每个线程会造个对象,锁不是一个
while(true){
synchronized (this){//此时的this:唯一的window的对象 w
//同步代码
}
}
class WindowTest {
public static void main(String[] args){
Window w = new Window();
Thread t1 = new Thread(w);
...
}
}
对于继承Thread类的来来说,就不能用this了,并且类也可以充当对象,并且类只加载一次
class MyThread extends Thread {
private static object obj = new object(); //造的对象必须是静态的
@Override
public void run() {
synchronized (obj){ //synchronized (MyThread.class)
//同步代码
}
}
}
public class ThreadTest {
public static void main(String[] args){
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
方式二:同步方法

使用同步方法解决实现Runnable接口的线程安全问题
@Override
public void run(){
show();
}
private synchronized void show(){//同步监视器: this
//方法体
}
使用同步方法处理继承Thread类的方式中的线程安全问题
@Override
public void run(){
show();
}
private static synchronized void show(){//同步监视器:当前类
//方法体
}

方式三:Lock(锁)
JDK5.0新增
用Lock更灵活,建议使用

//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock(); //ReentrantLock(fair:true) 加true是公平锁:就是1线程执行完会等2,3...线程执行完之后才继续执行。默认就是false,1线程可能会多次执行。
try{
//2.调用锁定方法Lock()
lock.lock();
//同步代码
}finally{
//3.调用解锁方法: unLock()
lock.unlock();
}

面试题:用锁来解决。3种:同步代码块,同步方法,Lock锁

对于一个问题要进行分析:
1.是否是多线程问题? 是,两个储户线程
2.是否有共享数据? 有,账户(或账户余额)
3.是否有线程安全问题? 有
4.需要考虑如何解决线程安全问题? 同步机制:有三种方式。
线程死锁

我们使用同步时,要避免死锁
不是说程序能成功执行完就没有死锁了,只是出现死锁的概率小。
public class StaticProxyTest implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("主线程");//调用a对象的foo方法
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run() {
Thread.currentThread().setName("副线程");//调用b对象的bar方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
StaticProxyTest d1 = new StaticProxyTest();
new Thread(d1).start();
d1.init();
}
}
class A {
public synchronized void foo(B b) { //同步监视器:A类的对象: a
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ "进入了A实例的foo方法"); //①
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名:" + Thread.currentThread().getName()
+ "企图调用B实例的last方法"); //③
b.last();
}
public synchronized void last() { //同步监视器:A类的对象: a
System.out.println("进入了A类的last方法内部");
}
}
class B {
public synchronized void bar(A a) {
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ "进入了B实例的bar方法"); //②
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ "企图调用A实例的1ast方法"); //④
a.last();
}
public synchronized void last() { //同步监视器: b
System.out.println("进入了B类的last方法内部");
}
}
线程通信


释放锁


线程池
实现多线程方式三:

创建线程:

public class ThreadPool {
public static void main( String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool( nThreads: 10);
//2.执行指定的线程的操作。需要提供实现Runnable接口或callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute( new NumberThread1());//适合适用于Runnable
//service.submit(Callable callable);//适合使用于callable
//3.关闭连接池
service.shutdown();
}
}
Callable接口
实现多线程方式四:


//1.创建一个实现calLabLe的实现类
class NumThread implements callable{
//2.实现caLL方法,将此线程需要执行的操作声明在calL()中
@Override
public object call( ) throws Exception {
int sum = ;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum; //不想返回值就return null
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try{
//6.获取callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (Exception e){
e.printStackTrace();
}
}
}

常用类
Object类
- 如果我们没有显示的声明一个类的父类话,则此类继承于java.lang.Object类
- 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
- 意味着,所有的java类具有java.lang.Object类声明的功能

数组可以看成一个特殊的类

equals()方法
当我们自定义equals判断一个对象中的内容是否相等,如果该方法中定义了一个对象的属性。做判断时也得把该对象中的方法equals重写。


== 详解
==比较基本数据类型时是比较数值大小,比较引用数据类型是具体地址值
比较具体内容用equals
==符号使用时,必须保证符号左右两边的变量类型一致

toString()方法
如果值为null,调用toString方法就会出现空指针异常


包装类(wrapper)
Java中的基本数据类型中的变量也具有类的一些特征和一些功能,通过包装类来获取



对于布尔类型的包装类:忽略了true的大小写,以及只要不是null,通通为false
当定义boolean[基本数据类型]类型值时,输出默认值为false;
当定义的时Boolean[类]类型值时,输出的默认值为null。
Boolean b1 = new Boolean("TrUe") //true
Boolean b2 = new Boolean("true123") //false
新特性:(针对基本数据类型和包装类的)自动装箱与自动拆箱

引用类型强转必须保证子父类关系

包装类面试题

1,三元运算符优先级比赋值高
2,三元运算符会进行自动类型提升
3,包装类重写了tostring方法输出实体内容而不是地址
对于三元运算符来说,得保证所有的数据类型一致,因为三元运算符会执行每一条语句,所以第一个会自动类型提升成double类型,输出1.0。因为是true必走第一个语句
第二个是1因为不用考虑自动类型提示,只需要执行这一条语句就行

第一个false是因为new的对象地址值不相同
字符串相关的类
String


创建String的方式

String的字面量方式对空间来说:
对于更改String类型的元素,都是重新造个值,而不更改原有的值

/*
String的实例化万式:
方式一:通过字面量定义的方式
方式二:通过new +构造器的方式
*/
@Test
public void test2(){
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new +构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值
String s3 = new String( original: "javaEE");
String s4 = new String( original: "javaEE");
System.out.println(s1 == s2);//true
System.out.print1n(s1 == s3); //false
System.out.print1n(s1 ==s4);//false
System.out.print1n(s3 ==s4);//false
System.out.println("**************");
Person p1 =new Person( name: "Tom" , age: 12);
Person p2 = new Person( name: "Tom" , age: 12);
System.out.print1n(p1.name.equals(p2.name));//true
System.out.print1n(p1.name ==p2.name);//true
}
字符串常量存储在字符串常量池,目的是共享
字符串非常量对象存储在堆中。
面试题: String s = new String("abc");方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据: "abc"


@Test
public void test3(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2; //变量都放在堆空间中
String s7 = s1 + s2; //变量
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
//加final变为常量放在常量池中而不是变量了
final String s4 = "javaEE"; //s4:常量
String s5 = s4 + "hadoop" ;
System.out.println(s1 == s5); //true
String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
//intern()方法表示将变量指向堆空间的地址转变成指向常量池中的地址
}

//一道面试题
public class stringTest {
String str = new String("good");
char[] ch = {'t', 'e', 's', 't'};
public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
StringTest ex = new StringTest();
ex.change(ex.str, ex.ch);
System.out.print1n(ex.str);//good
System.out.println(ex.ch);//best
}
}
String常用方法



String类型转换

String类型转换成基本数据类型
String str1 = "123";
int num = Integer.parseInt(str1);
基本数据类型转换成String类型
String str2 = String.valueOf(num);

//String --> char[]:调用String的toCharArray方法
String str1 = "abc123";
char[] charArray = str1.toCharArray();
for(int i = 0; i < charArray.length;i++){
System.out.println(charArray[i]);
}
//char[] --> String:调用String的构造器
char[] arr = new char[]{'h','e','l','l','o'};
String str2 = new String(arr);
System.out.println(str2);

//编码:String --> byte[]:调用String的getBytes方法
String str1 = "abc123中国";
byte[] bytes = str1.getBytes(); //使用默认的字符集,进行转换
System.out.println(Arrays.toString(bytes));
//解码:byte[] --> String:调用String的构造器
String str2 = new String(bytes); //使用默认的字符集,进行解码
注意:编码用的什么字符集解码就用什么字符集,否则会出现乱码
假如是GBK字符集
String str3 = new String(gbks,"gbk");
编码:字符串-->字节(看得懂--->看不懂的二进制数据)
解码:(编码的逆过程,字节-->字符串(看不懂的二进制数据--->看得懂)UTF-8占3个字节 是全世界的语言转成汉字
GBK占2个字节
String --> StringBuffer、StringBuilder:
调用StringBuffer或StringBuilder的构造器
StringBuffer、StringBuilder --> String:
①调用String构造器
②StringBuffer、StringBuilder的toString()
常量池存放

String的一些算法题
将一个字符串进行反转。将字符串中指定部分进行反转。比如:"abcdefg"反转为"abfedcg"
public class StaticProxyTest {
//放式一:转换成char[]
public String reverse(String str,int starIndex,int endIndex) {
if (str != null) {
char[] arr = str.toCharArray();
for (int x = starIndex, y = endIndex; x < y; x++, y--) {
char temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
return new String(arr);
}
return null;
}
@Test
public void testReverse(){
String str = "abcdefg";
String reverse = reverse(str, 2, 5);
System.out.println(reverse);
}
//方式二:使用String的拼接
public String reverse1(String str,int starIndex,int endIndex) {
if (str != null) {
//第1部分
String reverseStr = str.substring(0, starIndex);
//第2部分
for (int i = endIndex; i >= starIndex; i--) {
reverseStr += str.charAt(i);
}
//第3部分
reverseStr += str.substring(endIndex + 1);
return reverseStr;
}
return null;
}
@Test
public void testReverse1(){
String str = "abcdefg";
String reverse = reverse1(str, 2, 5);
System.out.println(reverse);
}
//方式三:使用StringBuffer/StringBuilder优化方式二
public String reverse2(String str,int starIndex,int endIndex){
if(str != null) {
StringBuilder builder = new StringBuilder(str.length());
//第1部分
builder.append(str.substring(0, starIndex));
//第2部分
for (int i = endIndex; i >= starIndex; i--) {
builder.append(str.charAt(i));
}
//第3部分
builder.append(str.substring(endIndex + 1));
return builder.toString();
}
return null;
}
@Test
public void testReverse2(){
String str = "abcdefg";
String reverse = reverse2(str, 2, 5);
System.out.println(reverse);
}
}
获取一个字符串在另一个字符串中出现次数。
比如:获取"ab"在"abkkcadkabkebfkabkskab"中出现的次数
public class StaticProxyTest {
//获取subStr在mainStr中出现的次数
public int getCount(String mainStr,String subStr){
int mainLength = mainStr.length();
int subLength = subStr.length();
int count = 0;
int index;
if(mainLength >= subLength){
//方式一:
// while((index = mainStr.indexOf(subStr)) != -1){
// count++;
// mainStr = mainStr.substring(index + subStr.length());
// }
//方式二:对方式一的改进
while ((index = mainStr.indexOf(subStr,0)) != -1){
count++;
index += subLength;
}
return count;
}else {
return 0;
}
}
@Test
public void testGetCount(){
String mainStr = "abkkcadkabkebfkabkskab";
String subStr = "ab";
int count = getCount(mainStr,subStr);
System.out.println(count);
}
}
获取两个字符串中最大相同字串。比如:str1 = "abcwerthelloyuiodefabcdef";
str2 = "cvhellobnmabcdef"
提示:将短的那个串进行长度依次递减的子串与较长的串比较。
public class StaticProxyTest {
//前提:两个字符串中只有一个最大相同子串
public String getMaxSameString(String str1,String str2){
if(str1 != null && str2 != null) {
String maxStr = (str1.length() >= str2.length()) ? str1 : str2;
String minStr = (str1.length() < str2.length()) ? str1 : str2;
int length = minStr.length();
for (int i = 0; i < length; i++) {
for (int x = 0, y = length - i; y <= length; x++, y++) {
String subStr = minStr.substring(x, y);
if (maxStr.contains(subStr)) {
return subStr;
}
}
}
}
return null;
}
@Test
public void testGetMaxSameString(){
String str1 = "abcwerthelloyuiodef";
String str2 = "cvhellobnm";
String maxSameString = getMaxSameString(str1,str2);
System.out.println(maxSameString);
}
//可以输出多个
public String[] getMaxSameString1(String str1,String str2) {
if (str1 != null && str2 != null) {
StringBuilder sBuilder = new StringBuilder();
String maxString = (str1.length() > str2.length()) ? str1 : str2;
String minString = (str1.length() > str2.length()) ? str2 : str1;
int len = minString.length();
for (int i = 0; i < len; i++) {
for (int x = 0, y = len - i; y < len; x++, y++) {
String subString = minString.substring(x, y);
if (maxString.contains(subString)) {
sBuilder.append(subString + ",");
}
}
System.out.println(sBuilder);
if (sBuilder.length() != 0) {
break;
}
}
String[] split = sBuilder.toString().replaceAll(",$", "").split(("\\,"));
return split;
}
return null;
}
@Test
public void testGetMaxSameString1(){
String str1 = "";
String str2 = "";
String[] maxSameStrings = getMaxSameString1(str1,str2);
System.out.println(Arrays.toString(maxSameStrings));
}
}
StringBuffer


尽可能避免扩容,影响效率

StringBuffer常用方法
String Builder的方法和String Buffer一样



日期和时间的API
JDK 8之前
System类

//1.System类中的currentTimeMillis()
public void test(){
long time = System.currentTimeMillis();
//返回当前时间与1970年1月1日0时0分秒之间以毫秒为单位的时间差。
//称为时间戳
System.out.println(time);
}
Date类

/*
java.util.Date类
|---java.sqL. Date类
1.两个构造器的使用
>构造器一: Date():创建一个对应当前时间的Date对象
>构造器二: 创建指定毫秒数的Date对象
2.两个方法的使用
>toString():显示当前的年、月、日、时、分、秒
>getTime():获取当前Date对象对应的毫秒数。(时间戳)
3.java.sql.Date对应着数据库中的日期类型的变量
>如何实例化
>如何将java.util.Date对象转换为java.sql.Date对象
*/
@Test
public void test(){
//构造器一: Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//Sat Feb 16 16:35:31 GMT+08:00 2019
System.out.println(date1.getTime());//1550306204104
//构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(155030620410L);
System.out.println(date2.toString());//创建java.sqL.Date对象
java.sql.Date date3 = new java.sql.Date(35235325345L);
System.out.print1n(date3);//1971-12-13
//如何将java.util.Date对象转换为java.sql.Date对象
//情况一:
Date date4 = new java.sql.Date(2343243242323L);
java.sql.Date date5 = (java.sql.Date) date4;
//情况二:
Date date6 = new Date();
java.sql.Date date7 = new java.sq1.Date(date6.getTime());
}
SimpleDateFormat类

2.解释何为编码?解码?何为日期时间的格式化?解析?
编码:字符串→字节
解码:字节→字符串
格式化:日期→字符串解析:字符串→日期
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author MaYunLong
* @date 2021/9/29 - 18:45
*/
public class DateTest {
//SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析
/*
1.两个操作
1.1 格式化: 日期 --> 字符串
1.2 解析: 格式化的逆过程,字符串 --> 日期
2.SimpleDateFormat的实例化: new + 构造器
*/
@Test
public void testSimpleDateFormat() throws ParseException {
//实例化SimpleDateFormat:使用默认的构造器
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化:日期 --> 字符串
Date date = new Date();
System.out.println(date);
String format = sdf.format(date);
System.out.println(format);
//解析:格式化的逆过程,字符串 --> 日期
String str = "21-9-29 下午7:06";
Date date1 = sdf.parse(str);
System.out.println(date1);
//*********按照指定的方式格式化和解析:调用带参的构造器***************
//SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy.MMMM.dd GGG hh:mm aaa");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
String format1 = sdf1.format(date);
System.out.println(format1);//2021-09-29 07:08:57
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
//否则,就会抛异常
Date date2 = sdf1.parse("2021-02-18 11:48:47"); //注意:要与之前造的格式一致
System.out.println(date2);
}
//练习1:字符串"2020-09-08"转换为java.sql.Date
@Test
public void testExer() throws ParseException {
String birth = "2020-09-08";
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf1.parse(birth);
//System.out.println(date);
java.sql.Date birthDate = new java.sql.Date(date.getTime());
System.out.println(birthDate);
}
//练习2:三天打鱼两天晒网 1990-01-01 xxxx-xx-xx 打鱼?晒网?
//举例:2020-09-08 ? 计算总天数 总天数 % 5 == 1 2 3打鱼
//总天数 % 5 == 4,0 晒网
//总天数的计算? date2.getTime() - date1.getTime() / 1000*60*60*24 + 1
}
Calendar类
是可变性的

import org.junit.Test;
import java.util.Calendar;
import java.util.Date;
/**
* @author MaYunLong
* @date 2021/9/29 - 18:45
*/
public class DateTest {
/*
Calendar日历类(抽象类)的使用
*/
@Test
public void testCalendar(){
//1.实例化
//方式一:创建其子类(GregorianCalendar)的对象
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getClass());
//2.常用方法
//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
//set():改的是calendar本身
calendar.set(Calendar.DAY_OF_MONTH,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add():calendar本身做加操作,改成负数可以减
calendar.add(Calendar.DAY_OF_MONTH,3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//getTime():日历类 --> Date
Date date = calendar.getTime();
System.out.println(date);
//setTime():Date --> 日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
}
}
JDK 8中新增



/* 提供本地时间和日期
localDate. LocaLTime. LocaLDateTime的使用说明:
1.LocalDateTime相较于LocalDate. LocalTime,使用频率要高
2.类似于Calendar
*/
@Test
public void test1(){
//now():获取当前的日期、时间、日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
//of():设置指定的年、月、日、时、分、秒,没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2020,10,2);
System.out.println(localDateTime1);
//getXxx():获取相关的属性
System.out.println(localDateTime.getDayOfMonth());
System.out.print1n(localDateTime.getDayOfweek());
System.out.println(localDateTime.getMonth());
System.out.println(localDateTime.getMonthValue());
System.out.println(localDateTime.getMinute());
//体现不可变性
//withXxx():设置相关的属性
Loca1Date locaiDate1 = localDate.withDayOfMonth(22);
System.out.println(localDate);
System.out.println(localDate1);
LocalDateTime localDateTime2 = localDateTime.withHour(4);
System.out.println(localDateTime);
System.out.print1n(localDateTime2);
//不可变性
//加操作
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
System.out.println(localDateTime);
System.out.println(localDateTime3);
//减操作
LocalDateTime localDateTime4 = localDateTime.minusDays(6);
System.out.println(localDateTime);
System.out.println(localDateTime4);
}
Instant

//类似于 java.util.Date类
public class DateTest {
@Test
public void test2() {
//now():获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant); //伦敦时间
//添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime); //东八区时间
//toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数 ---> Date类的getTime()
long milli = instant.toEpochMilli();
System.out.println(milli);
//ofEpochMilli():通过给定的毫秒数,获取Instant实例 --> Date(Long millis)
Instant instant1 = Instant.ofEpochMilli(1550475314878L);
System.out.println(instant1);
}
}
DateTimeFormatter
①格式化或解析日期、时间
②类似于SimpleDateFormat

java比较器
在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。
对象比较大小
Java实现对象排序的方式有两种:
自然排序:java.lang.Comparable
定制排序: java.util.Comparator
Comparable接口的使用举例: 自然排序默认考虑Comparable
1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
3.重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数, 如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
4.对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)
在compareTo(obj)方法中指明如何排序
/* 一、说明:
Java中的对象,正常情况下,只能进行比较: == 或 != 。
不能使用 > 或 < 的;
但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
如何实现?使用两个接口中的任何一个: Comparable或Comparator
二、Comparable接口的使用
*/
@Test
public void test1(){
String[] arr = new String[]{"AA","CC", "KK", "MM", "GG","JJ", "DD"}
Arrays.sort(arr);
System.out.println(Arrays.tostring(arr));
}
Comparator接口的使用: 定制排序
1.背景:
当元素的类型没有实现java.Lang.Comparable接口而又不方便修改代码, 或者实现了java.Lang.Comparable接口的排序规则不适合当前的操作, 那么可以考虑使用Comparator的对象来排序
2.重写compare(0bject o1,object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2 ;
如果返回0,表示相等;
返回负整数,表示o1小于o2。
@Test
public void test3(){
String[] arr = new String[]{"AA","CC","KK","MM", "GG","33","DD"};
Arrays.sort(arr, new Comparator(){
//按照字符串从大到小的顺序排列
@Override
public int compare(object o1,object o2){
if(o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 =(String) o2;
return -s1.compareTo(s2);
}
//return 0;
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.print1n(Arrays.toString(arr));
}

其他常用类
System类


String javaversion = System.getProperty("java.version");
System.out.println("java的version:" + javaVersion);
String javaHome = System.getProperty("java.home");
System.out.println( "java的home:" + javaHome);
String oSName = system.getProperty("os.name");
System.out.print1n( "os的name: " + osName);
String osversion = System.getProperty("os.version");
System.out.print1n("os的version: " + osVersion);
String userName = system.getProperty("user.name" );
System.out.print1n("user的name:" + userName);
String userHome = system.getProperty ( "user.home" );
System.out.print1n("user的home: " + userHome);
String userDir = system.getProperty( "user.dir");
System.out.print1n("user的dir:" + userDir);
Math类

BigInteger类
整型


BigDecimal类
浮点型

枚举类与注解
可以看作5.0的两个新特性
枚举类

一、枚举类的使用
①枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
②当需要定义一组常量时,强烈建议使用枚举类
③如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
二、如何定义枚举类
方式一:JDK5.0之前,自定义枚举类
方式二:JDK5.0之后,可以使用enum关键字定义枚举类

自定义枚举类
//自定义枚举类
class Season{
//1.声明Season对象的属性:要用private final修饰 [定义一组常量]
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值 [只有有限个,确定的]
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3.提供当前枚举类的多个对象:public static final
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","寒风凛冽");
//4.其他诉求1: 获取枚举类对象的属性
public String getSeasonName(){
return seasonName;
}
public String getSeasonDesc(){
return seasonDesc;
}
//4.其他诉求2:提供toString()
@override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\''+
",seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
使用关键字enum
//使用enum定义枚举类
/*
注意点: ①使用enum关键字必须先声明对象
②不能用public static final和new + 构造器方式
③多个对象之间用 "," 最后一个结尾用 ";"
说明: 定义的枚举类默认继承于java.lang.Enum类
*/
enum Season{
//1.提供当前枚举类的多个对象: 多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","寒风凛冽");
//2.声明Season对象的属性:要用private final修饰 [定义一组常量]
private final String seasonName;
private final String seasonDesc;
//3.私有化类的构造器,并给对象属性赋值 [只有有限个,确定的]
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求: 获取枚举类对象的属性
public String getSeasonName(){
return seasonName;
}
public String getSeasonDesc(){
return seasonDesc;
}
//4.不会重写toString()了,默认用Enum类的toStrig
//要想打印自己信息就重写
}
Enum类的方法

主要方法
values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常llegalArgumentException。
tostring():返回当前枚举类对象常量的名称
//values( ):
Season[ ] values = Season.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);
}
//valueof(String objName):返回枚举类中对象名是objName的对象。
//如果没有objName的枚举类对象,则抛异常: IllegalArgumentException
Season winter = Season.valueof("WINTER");
System.out.print1n(winter);
枚举类实现接口
使用enum关键字
情况一:实现接口,在enum类中实现抽象方法
情况二:让枚举类的对象分别实现接口中的抽象方法
interface Info{
void show();
}
enum Season implements Info{
@override
public void show(){
System.out.println("这是一个季节");
}
}
//或者每一个对象都去实现抽象方法show
enum Season implements Info{
SPRING("春天","春暖花开"){
@override
public void show(){
System.out.println("这是一个季节");
}
},
SUMMER("夏天","夏日炎炎"){
...
},
AUTUMN("秋天","秋高气爽"){
...
},
WINTER("冬天","寒风凛冽"){
...
};
}
注解
概述


常见的注解
使用注解要在前面加@符号,并把该注解当成一个修饰符使用

@override:限定重写父类方法,该注解只能用于方法
@Deprecated:用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings:抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能
Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。Spring框架关于"事务"的管理

自定义注解

/*
自定义注解: 参照@Suppresswarnings定义
*①注解声明为: @interface
*②内部定义成员,通常使用value表示
*③可以指定成员的默认值,使用default定义
*如果自定义注解没有成员,表明是一个标识作用。如果注解有成员,在使用注解时,需要指明成员的值。
*自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
*自定义注解通过都会指明两个元注解: Retention、 Target
*/
public @interface MyAnnotation {
String value(); //default "hello";
}
@MyAnnotation(value = "hello")
class A{
}
元注解
元数据:String name = "Tom"是最重要的,其他两个是用来修饰的
JDK提供的4种元注解
元注解:对现有的注解进行解释说明的注解
Retention:指定所修饰的 Annotation 的生命周期:SOURCE 编译时\CLASS(默认行为)字节码文件\RUNTIME
只有声明为RUNTIME生命周期的注解,才能通过反射获取。Target:用于指定被修饰的Annotation能用于修饰哪些程序元素
Documented:表示所修饰的注解在被javadoc解析时,保留下来。Inherited:被它修饰的Annotation将具有继承性。
package com.ma.annotation;
import java.lang.annotation.*;
//测试元注解
public class Test02 {
@MyAnnotation
public void test(){
}
}
//定义一个注解
//@interface 注解定义的方式
//@Target 表示我们的注解可以用在哪些地方;method只能在方法上使用 type是放在类上的
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//@Retention 表示我们的注解在什么地方才有效
//runtime运行时 > class(javac编译时的.class) > source源码
//大于关系的意思是,当我定义了runtime其他两个都有效,定义source只有在source有效
@Retention(value = RetentionPolicy.RUNTIME)
//@Documented 表示是否将我们的注解生成在javadoc中
@Documented
//@Inherited 子类可以继承父类的注解
@Inherited
@interface MyAnnotation{
}
注解新特性[JDK 8]
可重复注解
①在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
②MyAnnotation的Target和Retention和MyAnnotations相同。③MyAnnotation的元注解要与MyAnnotations相同。
@Repeatable(MyAnnotations.class)
//注意:Repeatable里声明的@Retention()和@Target()要与定义的一致
public @interface MyAnnotation {
String value(); //default "hello";
}
//JDK 8之前的写法
//@MyAnnotations({@MyAnnotation(value = "hello"),@MyAnnotation(value = "h1")})
//JDK 8之后的
@MyAnnotation(value = "hello")
@MyAnnotation(value = "h1")
class A{
}
public @interface MyAnnotations {
MyAnnotation[] value();
}
类型注解
ELementType.TYPE_PARAMETER表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ELementType.TYPE_USE表示该注解能写在使用类型的任何语句中。
@Target({TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}
class Generic<@MyAnnotation T> {
//注解加在这些上面就可以看具体的情况,比如:值是多少,什么类型异常等
public void shou() throws @MyAnnotation RuntimeException {
ArrayList<@MyAnnotation String> list = new ArrayList<>();
int num = (@MyAnnotation int) 10L;
}
}
集合
概述


集合存储的优点:
解决数组存储数据方面的弊端。
Collection接口

单列数据,定义了存取一组对象的方法的集合
√List接口:元素有序、可重复的集合
√Set接口:元素无序、不可重复的集合
Collection的常用方法:
对于某些方法,如contains() / remove() / retainsAll() 方法要重写equals()方法
使用Collection集合存储对象,要求对象所属的类满足:
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
import org.junit.Test;
import java.util.*;
//Collection接口中的抽象方法
//向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals
public class CollectionTest {
@Test
public void test(){
Collection coll = new ArrayList();
//add(Object e): 将元素e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123);
coll.add(new String("Tom"));
coll.add(new Date());
//size(): 获取添加的元素的个数
System.out.println(coll.size()); //5,如果没添加就为0
//addAll(Collection coll1): 将coll1集合中的元素添加到当前的集合中
Collection coll1 = Arrays.asList(123,456);
coll.addAll(coll1);
System.out.println(coll.size());//7
System.out.println(coll);
//clear(): 清空集合元素
coll.clear();
//isEmpty(): 判断当前集合是否为空
System.out.println(coll.isEmpty());
//contains(Object obj): 判断当前集合中是否包含obj[判断的是内容,调用equals方法]
//我们在判断时会调用obj对象所在类的equals()。
boolean contains = coll.contains(123);
System.out.println(contains);//true
System.out.println(coll.contains(new String("Tom")));//true
//containsAll(Collection coll1): 判断形参coll1中的所有元素是否都存在于当前集合中。
System.out.println(coll.containsAll(coll1));
//remove(Object obj): 移除一个当前集合obj中的值
coll.remove(123);
System.out.println(coll);
//removeAll(Collection coll1): 差集: 从当前集合中移除coll1中的所有元素
coll.removeAll(coll1);
System.out.println(coll);
//retainAll(Collection coll1): 交集: 获取当前集合和coll1集合的交集,并返回给当前集合
//保留他们两个一样的,删掉不一样的
coll.retainAll(coll1);
System.out.println(coll);
//equals(Object obj): 判断当前集合和形参集合元素的异同
//要想返回true: 需要当前集合和形参集合元素都相同
//list是有序的,顺序也必须相同,set是无序的,顺序可以不同。前提是内容都得相同
System.out.println(coll.equals(coll1));
//hashCode(): 返回当前对象的哈希值
System.out.println(coll.hashCode());
//集合 --> 数组: toArray()
Object[] array = coll.toArray();
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
//数组 --> 集合: 调用Arrays.类的静态方法asList()
//注意: 基本数据类型得用包装类或者用Object类型的
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);
//iterator(): 返回Iterator接口的实例,用于遍历集合元素,放在IteratorTest.java中进行测试
}
}
Iterator接口
迭代器不是容器,只是用来遍历的

import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
//集合元素的遍历操作,使用迭代器Iterator接口
//内部的方法: hasNext() 和 next()
public class IteratorTest {
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("AA");
coll.add(new String("ww"));
Iterator iterator = coll.iterator();
//方式一:
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// //报异常: NoSuchElementException[角标越界]
// System.out.println(iterator.next());
//方式二:不推荐
for (int i = 0; i < coll.size(); i++) {
System.out.println(iterator.next());
}
//方式三:推荐
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}

- 迭代器的remove()方法:

public class IteratorTest {
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("AA");
coll.add(new String("ww"));
Iterator iterator = coll.iterator();
//删除集合中的"AA"
while(iterator.hasNext()){
Object o = iterator.next();
if("AA".equals(o)){
iterator.remove();
}
}
iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}

foreach循环
原理:取对象的第一个元素赋给局部变量,之后进行方法体操作。以此类推,之后取对象第二个赋给局部变量,之后进行方法体操作......
本质上还是迭代器方法
用于遍历集合、数组
@Test
public void test(){
Collection coll = new ArrayList();
coll.add("AA");
coll.add("BB");
coll.add(123);
coll.add(new String("Tom"));
coll.add(new Date());
//for(集合元素的类型 局部变量 : 集合对象)
//或for(数组元素的类型 局部变量 : 数组对象)
//内部仍然调用迭代器
for (Object obj:coll) {
System.out.println(obj);
}
}
//笔试题
@Test
public void test2() {
String[] arr = new String[]{"MM", "MM", "MM"};
//方式一:普通for赋值
for (int i = 0; i < arr.length; i++) {
arr[i] = "GG"; //这个打印输出的是GG
}
//方式二:增强for循环
for (String s : arr) {
s = "GG"; //这个打印输出的是MM。因为是对新的变量进行赋值操作,不影响原来的值
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
List接口

"动态"数组,替换原有的数组
对于remove()方法,需要重写equals()方法
添加的对象,所在的类要重写equals()方法

三个实现类:ArrayList、LinkedList、Vector

ArrayList

JDK 8 中的变化是为了避免扩容而造成的效率低下
LinkedList


Vector

数据结构中的栈Stack是Vector的一个子类。
List接口中的常用方法
因为List接口是Collection接口的子接口,所以Collection的15个方法List接口也可以用
添加了一些根据索引操作集合元素的方法

@Test
public void test(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(456);
System.out.println(list);
//void add(int index,object ele):在index位置插入ele元素
list.add(1,"BB");
System.out.println(list);
//boolean addAlL(int index,collection eles):从index位置开始将eLes中的所有元素添加进来
List list1 = Arrays.asList(1, 2, 3);
list.addAll(list1);
System.out.println(list.size()); //8
//object get(int index)∶获取指定index位置的元素
System.out.println(list.get(0));
//int indexof(object obj):返回obj在集合中首次出现的位置
int index = list.indexOf(456); //有就返回具体位置,不存在返回-1
System.out.println(index);
//int lastIndexof(object obj):返回obj在当前集合中末次出现的位置
System.out.println(list.lastIndexOf(456)); //有就返回具体位置,不存在返回-1
//0bject remove(int index):移除指定index位置的元素,并返回此元素
Object o = list.remove(0);
System.out.println(o); //返回删除位置上的元素
System.out.println(list); //返回被删后的集合
//0bject set(int index,object ele):设置指定index位置的元素为eLe
list.set(0,"CC"); //更改这个位置的值
System.out.println(list);
//list sublist(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开的子集合
List subList = list.subList(0, 4);
System.out.println(subList); //返回选中区域的集合
System.out.println(list); //对集合本身不会有影响
}
总结:常用方法
增:add(Object obj) 忘末尾添加
删:remove(int index) / remove(Object obj)
改:set(int index,object ele)
查:get(int index)
插:add(int index,object ele)
长度:size()
遍历:① Iterator迭代器方式
② 增强for循环
③ 普通的循环
- 一道面试题:
//区分list中remove(int index)和remove(Object obj)
@Test
public void testListRemove() {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list); //[1,2]
}
private static void updateList(List list) {
list.remove(2); //这种是删除的索引位置,删除数值3。因为没有实现自动装箱
//list.remove(new Integer(2)); //这种是删除具体内容,删除数值2。
}
Set接口

-
Set接口是Collection的子接口,Set接口没有提供额外的方法
-
Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。
-
Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法
存储的数据特点:无序性和不可重复性:

我的理解:
某个数据 某个数据 无序性:
每一个数据都有一个哈希值,此哈希值在集合【底层还是数组】中按照某种算法将该数据放到某个位置,因为每个数据的哈希值不同,所以放的位置不同。体现了无序性。
不可重复性:
又因为哈希值不同,所以每个数据也就不同,但因为有的数据哈希值可能会相同或者通过某种算法算出的位置与之前某一个数据的位置一样。
这时候就要去比较了:
哈希值不同的:根据JDK版本:JDK 7 的是放在原来数据的上面,通过链表的方式存储,就是新数据链旧数据,JDK 8 的是旧数据链新数据。
哈希值相同的:通过equals判断内容是否相同,相同内容就不存储。不同的还是按照链表存。
- 重写hashCode()方法的基本原则
在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值。
当两个对象的equals()方法比较返回true时,这两个对象的 hashCode()方法的返回值也应相等。
对象中用作 equals()方法比较的Field,都应该用来计算 hashCode值。

- 重写equals()方法的基本原则
以自定义的Customer类为例,何时需要重写equals()?
当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写hashCode(),根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,它们仅仅是两个对象。
因此,违反了“相等的对象必须具有相等的散列码”。
结论:复写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。
三个实现类:HashSet、LinkedHashSet、TreeSet

HashSet

HashSet底层原理是HashMap
Set中add方法是用了map中的put方法,存放的是key值,而value定义成了一个静态常量,让key都指向了这个常量,这个常量定义成了Object类型,方便后续调用,(value没有具体值,所以不会显示)
一道面试题:
public class Person {
int id;
String name;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (id != person.id) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
@Test
public void test(){
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
System.out.println(set); //[Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
p1.name = "CC"; //将p1的name改成了CC
set.remove(p1); //remove索引p1的哈希值,而此时的哈希值是name为CC的,与之前的name为AA的哈希值不同,所以删了个寂寞
System.out.println(set); //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"CC")); //同理,name为CC的哈希值还没有,所以可以添加
System.out.println(set); //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"AA")); //因为重写了equals方法,所以就算与之前哈希值一样,equals判断内容不同,就在后面加链表
System.out.println(set); //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
}
LinkedHashSet
LinkedHashSet是HashSet的子类


TreeSet
底层用的是TreeMap的红黑树

@Test
public void test() {
TreeSet set = new TreeSet();
//TreeSet类型必须一致,否则报ClassCastException异常
set.add(123);
set.add(456);
set.add("AA");
}
自然排序
重写compareTo进行比较
public class User implements Comparable{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//按照姓名从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User) o;
return this.name.compareTo(user.name);
}else {
throw new RuntimeException("输入类型不匹配");
}
}
}
public class IteratorTest {
@Test
public void test() {
TreeSet set = new TreeSet();
set.add(new User("Tom",12));
set.add(new User("Mike",65));
set.add(new User("Jim",2));
set.add(new User("Alan",56));
set.add(new User("Susan",18));
for(Object obj : set){
System.out.println(obj);
}
}
}
定制排序
@Test
public void test2(){
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User) o1;
User u2 = (User) o2;
return Integer.compare(u1.getAge(),u2.getAge())
}else {
throw new RuntimeException("输入类型不匹配")
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Mike",65));
set.add(new User("Jim",2));
set.add(new User("Alan",56));
set.add(new User("Susan",18));
}
Map接口
双列数据,保存具有映射关系“key-value对”的集合
HashMap的底层:数组 + 链表 (JDK 7及之前)
数组 + 链表 + 红黑树 (JDK 8)
key - value

Map接口中存放的是一个Entry,Entry里面有key和value两个数据;而不是看成存放两个
Entry是无序的,并且不可重复 (看成 Set存放)
Key是无序的,并且不可重复 (可以看成 Set存放)
value是无序的,可重复的 (看成Collection存放)
图示

Map接口的常用方法


以HashMap为例
@Test
public void test(){
Map map = new HashMap();
//添加、删除、修改操作:
//添加
//Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
map.put("AA",123);
map.put(45,123);
map.put("BB",56);
//修改
map.put("AA",87);
System.out.println(map);
Map map1 = new HashMap();
map1.put("CC",123);
map1.put("DD",123);
//void putAll(Map m):将m中的所有key-value对存放到当前map中
map.putAll(map1);
System.out.println(map1);
//Object remove(Object key):移除指定key的key-value对,并返回value
Object value = map.remove("CCC");
System.out.println(value); //没有key的返回null
System.out.println(map);
//void clear():清空当前map中的所有数据
//map.clear(); //与map = null操作不同
System.out.println(map.size());
System.out.println(map);
//元素查询的操作:
//int size():返回map中key-value对的个数
//boolean isEmpty():判断当前map是否为空
//boolean equals(Object obj):判断当前map和参数对象obj是否相等
//Object get(Object key):获取指定key对应的value
System.out.println(map.get(455));//没有就返回null
//boolean containsKey(Object key):是否包含指定的key
System.out.println(map.containsKey("BB"));
//boolean containsValue(Object value):是否包含指定的value
System.out.println(map.containsValue(123));
//元视图操作的方法:
//遍历所有的key集
//Set keySet():返回所有key构成的Set集合
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//遍历所有的value集
//Collection values():返回所有value构成的Collection集合
Collection values = map.values();
for(Object obj : values){
System.out.println(obj);
}
//Set entrySet():返回所有key-value对构成的Set集合
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator.next();
//entrySet集合中的元素都是entry
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
}

HashMap

作为Map接口的主要实现类
线程不安全的,效率高
可以存储null的key和value
底层实现原理:
- JDK 7

- JDK 8

形成链表时,七上八下(JDK 7:新的元素指向旧的元素。JDK 8:旧的元素指向新的元素)
默认因子为什么为0.75
如果默认因子小了数组利用率低,大了链表结构多,效率低



linkedHashMap
HashMap的子类;
保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素

- Entry能够记录添加元素的顺序
TreeMap
保证按照添加的key-value对进行排序,实现排序遍历
此时考虑key的自然排序或定制排序。按照key来排
底层使用红黑树
//向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
//因为要按照key进行排序:自然排序、定制排序
Hashtable
作为Map接口的古老实现类
线程安全的,效率低
不能存储null的key和value,健壮性较差
Properties
常用来处理配置文件。
key和value都是String类型的

public static void main(String[] args) throws Exception {
Properties properties = new Properties();
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Marcus YL\\Desktop\\java\\案例(项目)\\src\\da\\jdbc.properties");
properties.load(fileInputStream);//加载流对应的文件
String username = properties.getProperty("name");
String password = properties.getProperty("password");
System.out.println("name = " + username + "password = " + password);
fileInputStream.close();
}
Collections工具类

- Collections工具类的常用方法
@Test
public void test(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(-97);
list.add(0);
System.out.println(list);
//排序操作:(均为static方法)
//reverse(List):反转List中元素的顺序
Collections.reverse(list);
System.out.println(list);
//shuffle(List):对List集合元素进行随析排序
Collections.shuffle(list);
System.out.println(list);
//sort(List):根据元素的自然顺序对指定1List集合元素按升序排序
Collections.sort(list);
System.out.println(list);
//sort(List, Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
Collections.swap(list,1,2);
System.out.println(list);
//swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换
//查找、替换
//Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
//Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
//Object min(Collection)
//Object min(Collection,Comparator)
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
int frequency = Collections.frequency(list, 123);
System.out.println(frequency);
//void copy(List dest,List src):将src中的内容复制到dest中
//错误的,报异常
//List dest = new ArrayList();
List dest = Arrays.asList(new Object[list.size()]);//造了个list大小的值为null的集合
Collections.copy(dest,list);
System.out.println(dest);
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
/*
collections类中提供了多个synchronizedXxx().方法,
该方法可使将指定集合包装成线程同步的集合,
从而可以解决多线程并发访问集合时的线程安全问题
*/
//返回的list1即为线程安全的
List list1 = Collections.synchronizedList(list);
System.out.println(list1);
}

泛型
可以把泛型看作是一个标签
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

//在集合中使用泛型之前的情况:
@Test
public void test1(){
ArrayList list = new ArrayList();
//需求:存放学生的成绩
list.add (78);
list.add( 76);
list.add(89);
list.add(88);
//问题一:类型不安全
list.add( "Tom" );
for(object score : list){
//问题二:强转时,可能出现CLasscastException
int stuscore = ( Integer) score;
System.out.print1n(stuScore);
}


声明的泛型不能为基本数据类型,要用它的包装类
//在集合中使用泛型的情况:
@Test
public void test2(){
ArrayList<Integer> list =new ArrayList<Integer>();
list.add( 78);
list.add(87);
list.add(99);
list.add(65);
//编译时,就会进行类型检查,保证数据的安全
//list.add( "Tom" );
for(Integer score : list){
//避免了强转操作
int stuscore = score;
System.out.print1n(stuscore);
}
自定义泛型结构:
//泛型类、泛型接口
public class Order<T> {
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT;
public Oreder(T orderT){
this.orderT = orderT;
}
}




在使用这个类时,需要对这个泛型进行实例化。不定义就默认为Object类型
如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为object类型
要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
子类在继承时也可以不去声明类型,那么实例化时就需要指明
//泛型方法
//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
//测试泛型方法
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4};
//泛型方法在调用时,指明泛型参数的类型。
List<Integer> list = order.copyFromArrayToList(arr);
System.out.print1n(list);
}
通配符



?super Number可以写入数据,extends不能写入数据
IO流
File类


- 路径分隔符

- File类的实例化

public class FileTest{
/*
1.如何创建File类的实例
FiLe(String filePath)
File(String parentPath,String chiLdPath)
File(File parentPath,String chiLdPath)
2.
相对路径:相较于某个路径下,指明的路径。
说明:
IDEA中:
如果大家开发使用JUnit中的单元测试方法测试,相对路径即为当前Module下。如果使用main()测试,相对路径即为当前的Project下。
Eclipse中:
不管使用单元测试方法还是使用main()测试,相对路径都是当前的Project下.
绝对路径:包含盘符在内的文件或文件目录的路径
3.路径分隔符
windows: \\ lunix: /
*/
@Test
public void test(){
//构造器1
File file1 = new File( pathname: "hello.txt" );//相对于当前module
File file2 = new File( pathname: "D:\\xxx\\xxx\\xxx\\xx.xx");
System.out.println(file1);
System.out.orintln(fi1e2);
//构造器2:
File file3 = new File( parent: "D:\ \workspace_idea1", child:"JavaSenior");
System.out.println(file3);
//构造器3:
File file4 = new File(file3,child: "hi.txt" );
System.out.println(file4);
}
}
- File类的常用方法



/*
public boolean renameTo(File dest):把文件重命名为指定的文件路径比如: file1.renameTo(fiLe2)为例:
要想保证返回true,需要fiLe1在硬盘中是存在的,且fiLe2不能在硬盘中存在
*/
@Test
public void test(){
File file1 = new File( pathname: "hello.txt");
File file2 = new File( pathname: "D: \\io\\hi.txt" );
boolean renameTo = file2.renameTo(file1);
System.out.println(renameTo);
}
IO原理


流的分类


/*
说明点:
1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
2.异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
3.读入的文件一定要存在,否则就会报FileNotFoundException.
*/
@Test
public void testFileReader() throws IOException {
FileReader fr = null;
try {
//1.实例化File类的对象,指明要操作的文件
File file = new File( pathname: "hello.txt");//相较于当前Modulel
//2.提供具体的流
fr = new FileReader(file);
//3.数据的读入
//read():返回读入的一个字符。如果达到文件末尾,返回-1
/*方式一
int data = fr.read( );
while(data != -1){
System.out.print((char)data);
data = fr.read( );
}
*/
//方式二:语法上针对于方式一的修改
int data;
while((data = fr.read()) != -1){
System.out.print( ( char)data);
}
} catch (IOException e) {
e.printStackTrace();
}
} finally {
//4.流的关闭操作
try{
if(fr != null)
fr.close();
} catch (IOException e){
e.printStackTrace();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!