本文介绍了Java语言的基本特性,如数据类型、数组、类和对象、接口、异常等,是步入Java编程语言的第一步。
第 1 章 java 语言概述
1.0 计算机基础
1.0.1 基本知识
- 现代计算机基础:冯诺依曼体系结构
- IT 定律(硬件):
- 摩尔定律:18-24 个月,计算机性能提升一倍。
- 反摩尔定律:如果现在和 18 个月前卖掉同样多的同样的产品,营业也就会下降一半。
- 安迪-比尔定律:硬件性能的提高,很快会被软件消耗掉。
- DOS 命令:
md
:创建文件夹rd
:删除文件夹del
:删除文件cd
:切换路径dir
:查看当前路径下的文件和文件夹cd
:切换路径cd \
:切换到根路径。
tree
:指定目录下的所有文件(显示文件树)cls
:清屏exit
:退出 dos
- 编码:
- ASCII:一个字节表示一个字符,共 128 个。
- Unicode:两个字节表示一个字符,字母和汉字都用 2 个字符表示,存在浪费空间问题。兼容 ASCII 编码
- UTF-8:大小可变编码,字母用一个字节,汉字用 3 个字节
- gbk:字母用 1 个字节,汉字用 2 个字节。
- gb2312:同 gbk,表示范围小于 gbk
- big5:繁体中文
1.0.2 整数的四种表示方式
- 二进制:0 和 1,以 0b 或 0B 开头。
- 八进制:0-7,以 0 开头。
- 十进制:0-9。
- 十六进制:0-9 以及 A-F(a-f),以 0x 或 0X 开头。
1.0.3 进制转换
- 其他进制转十进制:
- 二进制转十进制:
每个位上的数 * 2^indx
的和,index 从右向左依次为 0 至 n。 - 八进制转十进制:
每个位上的数 * 8^indx
的和,index 从右向左依次为 0 至 n。 - 十六进制转十进制:
每个位上的数 * 16^indx
的和,index 从右向左依次为 0 至 n。
- 二进制转十进制:
- 十进制转其他进制:
- 十进制转二进制:该数不断除 2,直到商为 0,将余数倒序拼接。
- 十进制转八进制:该数不断除 8,直到商为 0,将余数倒序拼接。
- 十进制转十六进制:该数不断除 16,直到商为 0,将余数倒序拼接。
- 二进制转其他进制:
- 二进制转八进制:从右向左,3 个数一组,对应的十进制数组合后即为八进制。
- 二进制转十六进制:从右向左,4 个数一组,对应的十进制数组合后即为十六进制。
- 其他进制转二进制:
- 八进制转二进制:将八进制的每一个数,转成对应的 3 位数二进制即可。
- 十六进制转二进制:将十六进制的每一个数,转成对应的 4 位数二进制即可。
1.1 java 简介
1.1.1 Java 的常见概念及关系
- java SE、Java EE、Java ME 的关系
- JDK 和 JRE:
- JDK: Java Development Kit,把 Java 源码编译成 Java 字节码。
- JDK=JRE+Java 开发工具
- JRE: Java Runtime Environment,运行 Java 字节码的虚拟机。
- JRE=JVM+核心类库
- JDK: Java Development Kit,把 Java 源码编译成 Java 字节码。
- JSR 和 JCP:
- JSR 规范:Java Specification Request
- JCP 组织:Java Community Process,负责 JSR。
- JDK 的可执行文件:
- java:这个可执行程序其实就是 JVM,运行 Java 程序,就是启动 JVM,然后让 JVM 执行指定的编译后的代码;
- javac:这是 Java 的编译器,它用于把 Java 源码文件(以.java 后缀结尾)编译为 Java 字节码文件(以.class 后缀结尾);
- jar:用于把一组.class 文件打包成一个.jar 文件,便于发布;
- javadoc:用于从 Java 源码中自动提取注释并生成文档;
- jdb:Java 调试器,用于开发阶段的运行调试。
- Java 的特点:
- 面向对象(oop)
- 健壮性:强类型机制、异常处理、垃圾自动收集等。
- 跨平台的。
- 解释性的:
- 解释性语言编译后不能直接被机器运行,需要解释器执行。
- 编译性语言编译后的代码可以直接被机器执行,如 C,C++
1.1.2 下载 JDK 程序
- 由于发展原因:推荐下载 jdk8U201 版本(最后一稳定版本、长期支持版本)
- 安装 jdk8 时,已经带了 jre,但安装程序最后还会额外安装 jre8,为了避免开发工具配置出错(如 Eclipse),建议安装。(如果不装有什么问题?)
- 安装 jdk8 时,已经带了 jre,但安装程序最后还会额外安装 jre8,为了避免开发工具配置出错(如 Eclipse),建议安装。(如果不装有什么问题?)
- 本机的安装参考:
1.1.3 JDK 环境变量配置
- 虽然安装 java 时,环境变量
Path
下已经有默认配置,能够在任何位置的 cmd 终端执行java
命令。 - 但依赖 JDK 环境变量的其他程序配置仍然需要重新配置
JAVA_HOME
,如 Tomcat,不配置会出现执行 Tomcat 的开启命令会出现一闪而过的情况,Tomcat 也不会运行起来。 - 配置参数:
JAVA_HOME
:CLASSPATH
:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
——JDK5 之后不需要配置了,jre 会自动搜索当前路径下的 jar 包,并加载 dt.jar 和 tools.jar,这是 Oracle 公司的改进设计。这个路径表示 class 文件的路径,即 JVM 去哪里寻找要运行的 class 文件。Path
:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
1.1.4 java 命令基本使用
- 运行 Java 程序:
javac 文件名.java
编译代码,生成文件名.class
字节码文件。java 文件名
自动查找对应的文件.class
文件并执行。- java11 后,
**java 文件名.java**
可以直接运行一个单文件源码,但多数情况下,无法直接这样操作,因为**文件名.java**
文件需要依赖其他库。
1.2 Java 程序基础
1.2.1 Java 程序基本结构
- Java 编程的特点:
- Java 源码的缩进不是必须的。
- Java 代码保存为文件时,文件名和 main 方法外部的类名要完全一致。
- 一个源文件最多只能有一个 public 类,其他类的个数不限。
- 也可以没有 public 类。
- 编译后每一个类都对应生成一个
.class
文件。 - 每个类都可以有 main 方法,且都可以被执行。
- 被执行的前提是同文件内有一个 public 类。
- 执行方式是
**java 类名**
。 - main 方法必须按 java 程序规定的入口语法编写,即
public static void main(String[] args)
- 类名必须以英文字母开头(习惯首字母大写),后接字母、数字、下划线的组合。
public static void main(String[] args)
规定的 java 程序入口- main 方法名必须以英文字母开头(习惯首字母小写),后接字母、数字、下划线。
- Java 的每一行语句必须以分号结束。
- java 注释
- 单行注释:
//
- 多行注释:
- 多行注释不能嵌套多行注释。
- 单行注释:
1 | /* |
- 文档注释:注释内容可以被 JDK 提供的 javadoc 所解析,生成一套以网页文件形式体现在该程序
1 | /** |
- 转义字符
\t
:一个制表符,实现对齐功能。\n
:换行符\\
:\\r
:回车(光标移至本行前面)霜冷长河\r寒冬
--------->输出后寒冬长河
- Java 代码规范:
- 类、方法的注释,要以 javadoc 的方式书写。
- 非 javadoc 的注释,写给代码维护者看,为什么这样写、如何修改、注意什么问题。
- 使用 tab 缩进。
- 运算符“±*/=”两边各使用一个空格增加代码的可读性。
- 源文件使用 UTF-8 编码。
- 每行字符不要超过 80 字符。
- 代码编写行尾风格(起始大括号在同一行)或次行风格(起始大括号在下一行)
1.1.2 标识符规范
- 包名:多单词组成时全部写成小写。
- 文件名、类名、接口名:使用大驼峰形式。
- 变量名、方法名:小驼峰。
- 常量名:所有字母都大写,多个单词用下划线连接。
1.1.3 键盘输入语句
- 使用步骤:
- 导入
Scanner
所在的包:import java.util.Scanner;
- 创建
Scanner
实例对象:Scanner myScanner = new Scanner(System.in);
- 使用
myScanner
的实例对象方法:- 接收字符串:
String str = myScanner.next();
- 接收整数:
int num = myScanner.nextInt();
- 接收浮点数:
double sth = myScanner.netDouble();
- 接收字符:
char charNum = myScanner.next().charAt(0);
- 接收字符串:
- 导入
第 2 章 基本语法
2.1 变量和数据类型
2.1.1 变量
- 概念:相当于内存中一个数据存储空间的表示。
- 不同类型的变量,占用的内存空间大小不同。
- 该存储空间的值,可以在同种类型范围内不断变化。
- 三个基本要素:类型、名称、值。
- java 中变量的声明和初始化可以是两条语句,但使用前必须必须赋值进行初始化。
**int num; num = 10;**
- java 中变量的声明和初始化可以是两条语句,但使用前必须必须赋值进行初始化。
- 注意点:
- 变量必须先声明,后使用。
- 同一作用域内,变量不能重名。
int a = b = c;
这种初始化方式会报错。int a, b, c;
这种初始化方式不会报错。
- 输出语句中
+
的含义:- 左右两边都是数值时,做加法运算。
- 左右一边有字符串时,做拼接运算。
2.1.2 数据类型
-
基本数据类型
-
整数型:整数型默认使用 int 大小的存储空间,使用 long 大小的存储空间时结尾加 L。
类型 占用存储空间 表示范围 byte 1 字节 -218-1~218-1-1 short 2 字节 -228-1~228-1-1 int 4 字节 -248-1~248-1-1 long 8 字节 -288-1~288-1-1 -
浮点类型:
- 浮点类型都是近似值,因为存在小数位丢失的问题。
- 内存中的存放形式:浮点数=符号位+指数位+尾数位。
- 浮点型默认使用 double 大小的存储空间,使用 float 大小的存储空间时末尾加 f 或 F。
- 浮点型除以整形存在精度问题,在编程中,应当注意对运算结果是小数的运算、判断!应该以两个数差值的绝对值在某个精度范围内判断。
类型 占用存储空间 表示范围 float 4 字节 double 8 字节 -
字符:
- 字符型要用单引号,不能使用双引号。
- 字符型也可以直接赋值(整数),不需要用单引号,输出时显示对应的 Unicode 编码。
- 字符型本质上是一个整数,可以参与运算。
- 字符型还可以赋值为转义字符,需要单引号。
- 字符型也可以赋值为单个汉字,需要单引号。
-
布尔类型:
- Java 对布尔类型的存储占用空间大小没有做规定,理论上只需要 1bit(0 或者 1),但由于计算机中最小存储单元为字节(Byte),所以占用应为 1 字节,同时又因为编译后虚拟机(JVM)中布尔类型的值会被转换为 int 类型的数据代替,所以会占用 4 个字节。
- Java 中不可以用 0 或者非 0 的整数代替 false 或 true,即 Java 中布尔类型的值只能为 true 或 false。
-
-
自动类型转换:
- Java 中程序在进行赋值或运算时,精度小的类型会自动转换为精度大的类型。
char→int→long→float→double
byte、short→int→long→float→double
- 几点规律:
- 多种类型的数据混合运算时,系统会将所有数据自动转换为容量最大的那种数据类型,然后再进行计算。
- (byte、short)和 char 之间不会相互自动转换。
- 无法进行容量大的类型向容量小的类型自动转换,会报错。
- byte、short、char 在进行运算时,会自动转换为 int,所以需要存储空间大于等于 int 的类型。
- int=byte+byte(对),short=byte+byte(错),其他同理。
- 布尔类型不参与自动类型转换。
- Java 中程序在进行赋值或运算时,精度小的类型会自动转换为精度大的类型。
-
强制类型转换
- 自动类型转换的逆过程。
- 转换过程中会造成精度降低或溢出。
- char 类型可以保存 int 的常量值(自动类型转换,int 自动转 char),但不能保存 int 的变量值。【原因:类型转换问题】
-
基本数据类型和 String 类型的转换
- 基本数据类型—>String 类型:
基本数据类型 + ""
- String 类型—>基本数据类型:
Byte.parseByte()
Short.parseShort()
Integer.parseInt()
Long.parseLong()
Float.parseFloat()
Double.parseDouble()
Boolean.parseBoolean()
char
类型没有上述的方法,s.charAt(n)
可以获取字符串s
指定位置的字符。- 注意点:
- String 类型转换为基本类型时,必须先确保可以转换为对应的类型。如
123hello
不能转换为int
- 如果格式不正确,会抛出异常,程序终止。
- String 类型转换为基本类型时,必须先确保可以转换为对应的类型。如
- 基本数据类型—>String 类型:
-
变量的赋值:
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
2.2 运算符
2.2.1 算术运算符
%
本质是a % b = a - a / b *b
,所以**10 % -3** = 10 - 10 / (-3) * (-3) = **1**
++
的经典面试题:i = i++
:(1)temp=i,(2)i=i+1,(3)i=temp,结果为 ii = ++i
:(1)i=i+1,(2)temp=i,(3)i=temp,结果为 i+1
- 进行
b++
时不会进行强制类型转换,如:byte b=127;b++
不会报错,输出-128。
- 注意:前减减/前加加的优先级高于=,后加加/后减减的优先级低于=
- 整数运算在除数为 0 时会报错,而浮点数运算在除数为 0 时,不会报错,但会返回几个特殊值:
- NaN 表示 Not a Number,
0.0 / 0
- Infinity 表示无穷大,
1.0 / 0
- -Infinity 表示负无穷大,
-1.0 / 0
- NaN 表示 Not a Number,
2.2.2 关系运算符
- 关系运算符的结果都是 boolean 类型,要么是 true,要么是 false。
==
判断引用数据类型时,比较的是地址值。- 左右两侧数据类型要一致,否则编译报错。
==
判断基本数据类型时,比较的是具体值。- 左右两侧不一定类型要相同,会进行自动数据类型提升
- boolean 除外,boolean 与不是 boolean 的比较时会报错。
- 左右两侧不一定类型要相同,会进行自动数据类型提升
- 判断字符串时:
- 字符串以字面量形式声明:比较内容
- 字符串以 new 关键字声明:比较地址值
- 规范情况:须使用 isequals()方法
2.2.3 逻辑运算符
- 逻辑运算符的结果都是 boolean 类型,要么是 true,要么是 false。
&&
:第一个条件为 false 时,立即得出结果为 false,效率高,而且后面部分不会被执行。&
:即使第一个条件为 false,第二个条件的计算还会执行。||
:第一个条件为 true 时,立即得出结果为 true,效率高,而且后面部分不会被执行。|
:即使第一个条件为 true,第二个条件的计算还会执行。- 开发中常用
&&
和||
。
2.2.4 赋值运算符
- 赋值运算符左边只能是变量,右边可以是变量、表达式、常量值。
- 复合运算符会进行强制类型转换。
2.2.5 三元运算符
- 本质实际上是 if-else 语句。
条件表达式1 ? 表达式1 : 表达式2
- 表达式 1 和表达式 2 要求类型一致,所以会自动类型提升。
2.2.6 位运算符
- 原码、反码、补码:
- 二进制数的最高位为符号位,0 表示正数,1 表示负数。
- 正数的原码、反码、补码都一样。
- 负数的反码 = 原码符号位不变,其他位的数取反。
- 负数的补码 = 反码 + 1,负数的反码 = 补码 - 1。
- 0 的反码,补码都是 0。
- java 没有无符号数 ,即 java 中的数都是有符号的。
- 0 在 java 中表示正数。
- 计算机运算的时候,都是以补码进行运算。
- 使用运算结果的时候,需要看它的原码。
- 算数右移
>>
:符号位不变,低位溢出,高位补 0。m >> n
本质是m / 2^n
- 注意:负数跨越 0 时输出均为-1。
- 负数的算数右移?
- 算术左移
<<
:符号位不变,高位溢出,低位补 0。m << n
本质是m * 2^n
- 逻辑右移或无符号右移
>>>
:低位溢出,高位补 0(符号位也跟着移动)。 - 没有
<<<
。
2.2.7 运算符优先级
++
优先级的问题:- 算术运算(赋值):
- 逻辑运算:
- 三元运算:
2.3 流程控制
2.3.1 顺序控制
- java 程序默认的执行流程,从上到下逐行执行,所以 变量必须先声明后使用。
2.3.2 分支控制
- 单分支【if】:
- 即使执行的代码块语句只有一条,也建议加上
{}
- 当只有一条语句时,可以不用
{}
直接换行写,也可以不用换行,与if
写在同一行。
- 即使执行的代码块语句只有一条,也建议加上
- 双分支【if……else】:
- 多分支【if……else if……else】:
- 多分支可以没有最后的 else。
- 多分支只有一个执行入口,即从这个入口执行了语句,其余入口的语句不会被执行 。【先入为主】
- 如下述的伪代码,最高分为 80 时,考了 90 分的只会从第一个入口进,输出 A,而不会再去判断后面的 B、C、D
1 | if(成绩成绩>=最高分-10){ |
- 嵌套分支:
- 一个分支结构中嵌套了另一个完整的分支结构。
- 为了保证可读性,不要超过三层。
- 一个分支结构中嵌套了另一个完整的分支结构。
1 | if(){ |
- switch 分支:
- 穿透:如果 case 语句没有 break 语句,程序会继续执行下一个 case 的语句,而不会进行 case 判断。
- switch 表达式的数据类型,应和 case 后面的常量类型一致,或者表达式的数据类型可以自动转换为常量类型。
- switch 表达式的返回值必须是 byte、short、int、char、enum【枚举】、String。
- case 语句的值必须是常量或常量表达式,不能是变量或带变量的表达式。
- 穿透:如果 case 语句没有 break 语句,程序会继续执行下一个 case 的语句,而不会进行 case 判断。
1 | switch(表达式){ |
- switch 和 if 的选择:
- 运算的结果数值不多,且是 byte、short、int、char、enmu、String 的某种类型,建议使用 swtich。
- 对于区间判断、结果为 boolean 的判断,建议使用 if。
2.3.3 循环控制
-
for 循环:
- 基本语法:
1
2
3for(循环变量初始化;循环条件;循环变量迭代){
循环操作;
}- 四要素:循环变量初始化、循环条件、循环操作、循环变量迭代。
- 循环初始值可以有多条初始化语句,但要求类型一样,并用逗号隔开
int i = 0, j = 0
- 循环变量迭代可以有多条变量迭代语句,中间用逗号隔开
i++, j++
- 循环初始值可以有多条初始化语句,但要求类型一样,并用逗号隔开
- 如果循环操作只有一条语句,可以省略
{}
,建议不要省略。 for(;循环条件;)
- 循环变量初始化和循环变量迭代可以写到其他地方,但是
;
不能省略。 - 循环变量初始化写到其他地方时,作用域再循环体外。否则只能作用域循环体内。
- 循环变量初始化和循环变量迭代可以写到其他地方,但是
for(;;)
:表示无限循环。
-
while 循环:
- 基本语法:
1
2
3
4
5循环变量初始化;
while(循环条件){
循环体;
循环变量迭代;
}- 四要素:同 for 循环。
-
do…while 循环
- 基本语法
1
2
3
4
5循环变量初始化;
do{
循环体;
循环变量迭代;
}while(循环条件);- 四要素:同 for 循环。
- 注意 while 后面的
;
。
-
增强 for 循环
1
2
3for(int i : nums) {
System.out.println("i=" + i);
}- 依次从 nums 数组、枚举类等中取出数据,赋给 i
2.3.4 多重循环控制
-
建议循环嵌套不要超过 2 层,否则可读性非常差。
-
外层循环 m 次,内层循环 n 次,则内层循环体实际上需要执行 m*n 次。
-
break:结束当前循环。
1
2
3
4
5
6
7
8
9
10label1:
for(...){
label2:
for(...){
label3:
for(...){
break label1;
}
}
}- break 配合 label 标签可以指定跳出哪层循环。
- label 是标签名,由程序员指定,实际开发中,尽量不要使用标签。
- 如果 break 后没有指定的 label 标签名,则表示跳出最近的循环体.
-
continue:结束当次循环
1
2
3
4
5
6
7
8
9
10label1:
for(...){
label2:
for(...){
label3:
for(...){
continue label1;
}
}
}- continue 配合 label 标签可以指定跳出哪层循环。
- label 是标签名,由程序员指定,实际开发中,尽量不要使用标签。
- 如果 contineu 后没有指定的 label 标签名,则表示跳出最近的循环体。
- continue 不能用于 switch-case 语句中
-
return:
- return 用在方法内时,表示跳出方法,用在 main 方法中表示退出程序。本质上不是退出循环。
第 3 章 数组和数组操作
3.1 数组介绍
3.1.1 一维数组
- 定义:存放同一类型数据的组合。是一种引用数据类型。
- 动态初始化——方式一:
- 声明、创建、定义:
数据类型 数组名[] = new 数据类型[大小]
数据类型[] 数组名 = new 数据类型[大小]
- 初始化:
a[0] = 1;……
- 声明、创建、定义:
- 动态初始化——方式二:
- 声明数组:
数据类型 数组名[];
数据类型[] 数组名;
- 声明数组时,[]内不能写数组大小。
- 创建数组(分配空间):
数组名 = new 数据类型[大小];
- 初始化:
a[0] = 1;……
- 声明数组:
- 静态初始化——方式一:
数据类型 数组名[] = {值1, 值2,……}
数据类型[] 数组名 = {值1, 值2,……}
- 静态初始化时,[]内不能写数组大小。
- 静态初始化——方式二:
数据类型 数组名[] = new 数据类型[]{值1, 值2,……}
数据类型[] 数组名 = new 数据类型[]{值1, 值2,……}
- 静态初始化时,[]内不能写数组大小。
- 注意事项:
- 数组创建后,如果没有进行初始化,默认初始值如下:
- byte、short、int、long:0
- float、double:0.0
- char:\u0000(空字符)
- boolean:false
- String:null
- 数组是引用类型,是对象类型的一种。
- 数组定义后不能直接改变大小(容量),但可以通过以下两种方式改变大小
- 数组的拷贝:将一个大容量的数组赋值给小容量的数组
- 通过 new 关键字重新确定容量大小。
动态初始化情况下改变传入变量的大小控制数组的大小。
- 数组创建后,如果没有进行初始化,默认初始值如下:
3.1.2 二维数组
- 动态初始化——方式一:
- 声明、创建、定义:
数据类型 数组名[][] = new 数据类型[大小][大小]
数据类型[][] 数组名 = new 数据类型[大小][大小]
数据类型[] 数组名[] = new 数据类型[大小][大小]
- 初始化:
a[0][0] = 1;……
- 或者
a[0] = {1,3……}
- 未初始化的默认值:
a[0]
:指向内存地址。a[0][0]
:输出该数组对应数据类型的默认值。
- 声明、创建、定义:
- 动态初始化——方式二:
- 声明数组:
数据类型 数组名[][];
数据类型[][] 数组名;
数据类型[] 数组名[];
- 创建数组(分配空间):
数组名 = new 数据类型[大小][大小];
- 初始化:
a[0][0] = 1;……
- 或者
a[0] = {1,3……}
- 未初始化的默认值:
a[0]
:指向内存地址。a[0][0]
:输出该数组对应数据类型的默认值。
- 声明数组:
- 动态初始化——列数不确定:
- 声明数组:
数据类型 数组名[][] = new 数据类型[大小][]
数据类型[][] 数组名 = new 数据类型[大小][]
数据类型[] 数组名[] = new 数据类型[大小][]
数据类型 数组名[][];数组名 = new 数据类型[大小][];
数据类型[][] 数组名;数组名 = new 数据类型[大小][];
数据类型[] 数组名[];数组名 = new 数据类型[大小][];
- 初始化:
a[0] = new int[1];……
- 或者
a[0] = {1,3……}
- 注意点:
数据类型 数组名[][] = new 数据类型[a][]
声明了具有 a 个一维数组的二维数组,但一维数组内部的元素个数还不确定。数据类型 数组名[][] = new 数据类型[][a]
这种声明方式非法。
- 未初始化的默认值:
a[0] = new int[3];a[0]
:指向内存地址。没有a[0] = new int[3];直接a[0]
:null。a[0] = new int[3];a[0][0]
:输出该数组对应数据类型的默认值,本例为 0。没有a[0] = new int[3];直接a[0]
:报错。
- 声明数组:
- 静态初始化——方式一:
数据类型 数组名[][] = {{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
数据类型[][] 数组名 = {{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
数据类型[] 数组名[] = {{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
- 静态初始化时,[]内不能写数组大小。
- 静态初始化——方式二:
数据类型 数组名[][] = new int[][]{{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
数据类型[][] 数组名 = new int[][]{{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
数据类型[] 数组名[] = new int[][]{{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
- 静态初始化时,[]内不能写数组大小。
- 特殊写法情况:int[] x,y[]; x 是一维数组,y 是二维数组。
3.2 数组操作
3.2.1 基本操作
- 拷贝数组:
- 基本数据类型:
b=0;a=b;
,两个数值互不影响,拷贝的是数据。 - 引用数据类型:
int[] arr1 = {1,2……};arr2 = arr1;
- 默认情况下:拷贝的是内存中的地址,arr1 和 arr2 不论哪个的值变化,都会引起另外一个的变化。
- 值拷贝方式:给 arr1 和 arr2 分别开辟独立的内存空间。
int[] arr1 = {1,2……};int[] arr2 = new int[arr1.length];
- 基本数据类型:
- 反转数组:
- 交换数据法:
- 逆序赋值法:
- 数组扩容:
- 创建新数组,新数组添加元素,将新数组赋值给旧数组。
- 数组缩减:
创建新数组,新数组长度比旧数组少 1,循环赋值。
- 数组排序:
- 内部排序:将所有数据加载到内存中进行排序。
- 冒泡排序:
- 外部排序:数据量过大,无法全部加载到内存中,借助外部的存储空间进行排序。
- 内部排序:将所有数据加载到内存中进行排序。
- 数组查找:
- 顺序查找:
- 二分查找:
3.2.2 Arrays 工具类(常用)
Arrays.equals(int[] a, int[] b)
:- 返回
boolean
,boolean isEquals = Arrays.equals(a, b);
- 返回 true 的条件:两数组指向同一个内存地址(a=b)、a 和 b 内部的元素值相等
- 返回 false 的条件:两数组均为 null、a 和 b 内部的元素值不想等
- 除 int 外,其余各种类型的对象都可判断
- 返回
Arrays.toString(int[] a)
:- 返回
String
,String aStr = Arrays.toString(a);
- 输出为
[值1,,……]
的形式,实际为字符串的拼接
- 返回
Arrays.fill(int[] a,10)
- 返回为空,所以不能赋值给其他语句。
- 表示将数组 a 的所有元素替换为 10。
Arrays.sort(int[] a)
- 返回为空,所以不能赋值给其他语句。
- 表示将数组 a 按照从小到大的顺序排序,底层排序方式为快速排序。
Arrays.binarySearch(int[] a, 10)
:- 返回
int
,int index = Arrays.binarySearch(a, 10);
- 返回值为正数时,为目标数据(10)所在的元素下标。返回值为负数时,表示没找到。
- 底层排序方式为二分法查找。
- 返回
- 使用上述方法需要引入对应的工具类:
import java.util.Arrays;
1.4.5 数组常见异常
- 编译时不报错,运行出错。
ArrayIndexOutOfBoundsExcetion
:- 角标越界:
- 角标为负数
- 角标为数组长度(应为数组长度-1)
- 角标越界:
NullPointerException
:- 数组指向 null
- 数组只声明、未初始化
- 数组赋值为 null
- 不定列二维数组某个一维数组未初始化,访问了该一维数组的具体值。
- 一维字符串数组的某个值赋值为空。
- 数组指向 null
3.2.4 数组输出问题
- 详见(详见 Java 问答,1.4.14).
- 一维数组:
- 声明未创建(没有指定数组大小):编译报错
- 声明并初始化(指定数组大小):输出数组显示内存地址、输出元素显示默认值或指定值
- 二维数组:
- 声明未创建(没有指定数组大小):编译报错
- 声明并初始化(指定数组大小):
- 指定两个大小:输出数组显示内存地址、输出一维元素显示内存地址或一维数组、输出二维元素为默认值或指定值
- 指定一个大小:输出数组显示内存地址、输出一位元素显式为空、输出二维元素编译不报错,运行报错
第 4 章 面向对象
4.0 面向对象的学习内容
4.1 类和对象
- 类和对象是面向对象的核心概念。
- 类是对一类事物的描述,是抽象、概念上的定义。
- 对象是实际存在的该类事物的每个个体,因而也被称为实例。
- 类和对象的书面理解:
- Java 语言范畴中,将功能、结构等封装到类中,通过类的实例化,来调用具体的功能、结构。
- Scanner、String 等
- 文件:File
- 网络资源:URL
- 涉及到 Java 语言与 HTML、后端数据库交互时,前后端的结构在 java 层面交互时,都体现为类、对象。
- Java 语言范畴中,将功能、结构等封装到类中,通过类的实例化,来调用具体的功能、结构。
4.1.1 类
- 类的语法格式:
1 | 修饰符 class 类名{ |
1 | public class Person{ |
- 类的成员:
- 属性:对应类中的成员变量
- Field = 属性 = 成员变量
- 行为:对应类中的成员方法
- Method = 方法 = 函数
- 属性:对应类中的成员变量
- 类权限修饰符只能缺省或 public
4.1.2 类的成员——属性
- 属性的定义(声明):语法同变量:
访问修饰符 属性类型 属性名;
- 细节和注意事项:
- 访问修饰符:public、proctected、默认、private。
- 属性的类型:可以是任意类型,包括基本数据类型和引用数据类型。
- 属性的默认值:属性不赋值时,有默认值,规则同数组
- byte、byte、short、int、long:0
- float、double:0.0
- char:\u0000(空字符)
- boolean:false
- String:null
- 属性赋值:遵循变量赋值的规定,即:
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
4.1.3 类的成员——方法
- 方法的定义:
1
2
3
4访问修饰符 返回数据类型 方法名(形参列表……){
语句;
return 返回值;
}- 形参列表:表示成员方法的输入
- 返回数据类型:表示成员变量的输出类型。void 表示没有返回值。
{}
:表示方法体,实现某一功能的代码块- return 语句:不是必须的。
- 无返回值类型的方法也可以用 return,用于结束方法。
- 细节和注意事项
- 访问修饰符:
- public、proctected、默认、private
- 控制方法的使用范围。
- 返回数据类型:
- 一个方法只能有一个返回值。返回多个值可以使用数组接收。
- 返回类型可以是任意类型包含基本类型或引用类型(数组、对象)
- 如果要求有返回值,则必须有 return 语句,且 return 语句的数据类型与方法的返回数据类型一致或兼容。
- 如果方法时 void,则方法体中可以没有 return 语句,或者只写 return。
- 方法名:
- 见名知义
- 小驼峰命名方法,遵循标识符的规则、规范
- 形参列表:
- 一个方法可以有 0 个参数,也可以有多个参数,多个参数用逗号分开。
- 参数类型可以是任意类型,包括基本类型和引用类型。
- 调用带参数的方法时,必须传入对应类型或兼容类型的实参。
- 调用方法时,实参和形参的数量、类型、顺序必须一致。
- 方法体:
- 方法体的语句可以为输入、输出、变量、运算、分支、循环、方法调用,但是不能嵌套定义——方法中不能再声明方法。
- 访问修饰符:
- 方法调用的细节
- 同一个类中的方法 A(非 main 方法)中调用方法 B 时,可以直接调用
方法名()
- main 方法需要调用同一个类中的方法 B 时,需要通过实例化类,
对象名.B方法名()
调用
- main 方法需要调用同一个类中的方法 B 时,需要通过实例化类,
- 跨类中的方法 A 调用方法 B 时,需要通过对象名(实例化对象)调用
类名 对象名 = new 类名();对象名.方法名()
- 跨类调用的方法和它的访问修饰符有关。
- 同一个类中的方法 A(非 main 方法)中调用方法 B 时,可以直接调用
- 方法的调用机制:
- 当程序执行(main 方法)到方法时,会在 jvm 的栈内存内开辟一个独立的空间
- 当方法执行完毕时,或执行到 return 语句时,就会将方法的运行结果返回给栈内存中调用方法的地方。同时销毁开辟的独立空间。
- 返回后,程序继续执行后面的代码
- 当 jvm 内存内的 main 方法执行完毕,整个程序退出。
- 使用方法的优点:
- 提高了代码的复用性。
- 将实现的细节封装起来,供其他用户调用。
- 方法的传参机制(值传递):
- 基本数据类型:传递的是值(值拷贝),形参的任何改变不影响实参。
- 引用数据类型:传递的是地址,可以通过形参影响实参。
- 形参不影响实参的情况:形参在方法内开辟了新的内存空间(需要在方法体内的语句实现)。
- 字符串的传参?
- 克隆对象:创建两个独立的对象,只是属性、方法一致。
- 利用引用数据类型传参的机制(在方法体内创建新对象,逐一赋值)。
4.1.4 类的实例化——对象
- 创建对象:
- 方式一:
类名 对象名 = new 类名();
- 方式二:
类名 对象名;对象名 = new 类名();
Person p1 = new Person();
- p1 是对象的名(对象的引用),保存实例化对象的内存地址
- new Person()创建的对象空间(数据)才是真正的对象
- 方式三:创建对象数组
类名[] 对象数组名 = new 类名[n];
- 方式一:
- 访问成员:
对象名.对象成员
- 访问属性:
对象名.属性
- 访问方法:
对象名.方法()
- 访问属性:
- 匿名对象:
- 含义:创建对象时,如果没有显式地给对象起名,只是进行了
new 类名()
,则new 类名()
为匿名对象。 - 特征:匿名对象只能调用一次。
- 第二次调用时已经是新对象了,跟前一个对象没有关系。
- 使用:
- 临时使用、不需要保留
- 作为方法的参数
- 含义:创建对象时,如果没有显式地给对象起名,只是进行了
4.1.5 方法重载(OverLoad)
- 定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
- 判断是否是重载:
- “两同一不同”:
- 在同一个类中
- 相同方法名
- 参数列表不同:
- 参数个数不同
- 参数类型不同
- 同类型参数顺序不同
- 跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
- “两同一不同”:
- 在通过对象调用方法时,如何确定某一个指定的方法:
- 判断方法名是否一致
- 判断参数列表是否一致:参数类型、参数个数、参数顺序一致
- 注意点:子类可以重载父类同名不同参的方法。
4.1.6 方法递归(Recrusion)
- 含义:方法自己调用自己,每次调用时传入的参数不同
- 调用规则:
- 递归执行一次,会在栈内存内创建一个受保护的独立空间(栈空间)
- 方法的局部变量相互独立,不会相互影响
- 方法中使用引用数据类型的变量时,就会共享该引用类型的数据。
- 递归必须向退出递归的条件逼近,否则就是无限递归。
- 当一个方法执行完毕,或者遇到 return 语句,就会返回,遵循谁调用,结果就返回给谁。同时该方法执行完毕或返回时,该方法也就执行完毕。
4.1.7 构造方法/构造器(Constructor)
- 基本语法:
[修饰符] 方法名(形参列表){}
- 语法说明:
- 修饰符可以默认(无),也可以用 private、protected、public。
- 修饰符为默认(无)时,与类的修饰符一致。
- 构造器没有返回值,也不可以写
void
- 方法名必须和类名一模一样
- 参数列表和方法的规则一致
- 在创建对象时,系统会自动调用该类的对象完成属性的初始化。
- 完成对象的初始化不是创建对象
- 不能被 static、final、synchronized、abstract、native 修饰,不能有 return 语句返回值
- 修饰符可以默认(无),也可以用 private、protected、public。
- 主要作用:完成新对象(属性)的初始化。
- 构造器重载:
- 一个类可以定义多个构造器
- 如果一个构造器没写,系统会自动生成一个无参、默认的构造器(该构造器的修饰符与类一致)。
- 一旦定义了一个构造器,默认的无参构造器就不能使用了,使用了会报错。
- 如果想继续使用,需要再重新显式定义一下。
- 对象创建流程:
- 加载类信息,加载到方法区,只会加载一次
- 在堆空间中分配空间(地址)
- 完成对象的初始化
- 默认初始化
- 显式初始化
- 构造器初始化
- 将对象在堆空间的地址返回给创建对象时定义的对象
- 父类的构造器不可被子类继承
- 注意点:使用了构造器后,实例化对象要用构造器的方法,否则会报错。
- 原因为:显式声明构造器后,默认的无参构造器会不可用。
4.1.8 方法重写(override/overwrite)
- 定义:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
- 应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
- 语法:
权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{}
- 方法名:父子一致。
- 形参列表:父子一致。
- 权限修饰符:子类不小于父类
- 不能重写父类中 private 修饰的方法,因为 private 修饰的方法对外部不可见。
- 返回值类型:
- void:父子一致。
- 基本数据类型:父子一致。
- 引用数据类型:子类不大于父类
- 异常类型:子类不大于父类
- 注意点:子类和父类中的同名同参数的方法要么都声明为非 static 的(考虑重写),要么都声明为 static 的(不是重写)。
4.1.9 代码块
- 作用:用来初始化类、对象
- 修饰:
- 权限修饰:只能缺省。
- 关键字:只能用 static 或没有。
- 分类:静态代码块与非静态代码块,
- 相同点:
- 都可以用于对类的属性、声明初始化
- 都可以声明多个代码块,但一般每种最多写一个
- 多个代码块默认执行顺序都是先上后下
- 不同点:
- 静态代码块:
- 只能调用静态的属性和方法
- 随类的加载而执行,只执行一次
- 类加载的三个时机:
- 创建对象实例时(new)
- 创建子对象实例时,父类会被加载
- 使用类的静态成员时。
- 类加载的三个时机:
- 非静态代码块:
- 既可以调用静态的属性和方法,也可以调用非静态的属性方法
- 随对象的创建而执行,创建一次执行一次。先于构造器执行
- 可以将构造器相同的部分写到代码块内,减少不同构造器间的代码冗余。
- 静态代码块:
- 相同点:
- 创建对象时,类的调用顺序:
- 静态代码块和静态属性。取决于书写顺序。
- 普通代码块和普通属性。取决于书写顺序。
- 构造器。
- 创建子类对象时,类的调用顺序:
- 父类的静态代码块和静态属性。取决于书写顺序。
- 子类的静态代码块和静态属性。取决于书写顺序。
- 父类的普通代码块和普通属性。取决于书写顺序。
- 父类的构造器。
- 子类的普通代码块和普通属性。取决于书写顺序。
- 子类的构造器。
4.1.10 内部类(InenerClass)
- 含义:定义在类内部的一个类,包含内部的类叫外部类
- 外部引用时需要完整写出类名称(含包名)
- 编译以后生成 OuterClass$InnerClass.class 字节码文件
- 分类:
- 局部内部类:定义在方法、代码块、构造器中。
- 成员内部类(static 修饰和无修饰):定义在成员位置,类内可定义类的五大组成部分(属性、方法、构造器、代码块、内部类)
- 可以被 final 修饰,表示此类不能被继承。
- 可以被 abstract 修饰,表示不能被实例化
- 局部内部类:
- 可以直接访问外部类的所有成员,包含私有的
- 但是如果调用局部内部类所在方法中的局部变量时,要求该方法中的局部变量为 final 修饰,JDK8 之前显式声明,JDK8 之后可省略。
- 不能添加权限修饰符(只能缺省,同局部变量的修饰符范围),可以被 final 修饰,修饰后不可被继承
- 不能被 static 修饰,也不能包含 static 成员
- 内部类的成员与外部类的成员重名时,内部类调用遵循就近原则,需要调用外部成员时,需要通过
外部类.this.成员名
的方式 - 由于局部内部类定义在方法、代码块、构造器中,实际上是一个局部变量,只能在定义它的位置生效,即只能在这个位置实例化。
- 外部类需要访问内部类的成员时,需要通过上述流程,在外部类的方法中,将内部类实例化。
- 外部其他类不能访问。
- 可以直接访问外部类的所有成员,包含私有的
- 成员内部类:
- 可以直接访问外部类的所有成员,包含私有的
- 使用 static 修饰时,只能调用外部类声明为 static 的结构
- 非静态内部类,内部不能声明静态成员
- 前面省略了
外部类.this.
,不能使用this.
- 可以使用四种权限修饰符修饰
- 实例化内部类:
- 静态内部类:
外部类.内部类 变量名 = new 外部类.内部类();
- 非静态内部类:
外部类.内部类 变量名 = 外部类的引用.new 内部类();
外部类.内部类 变量名 = 外部类的引用.new 外部类.内部类();
- 静态内部类:
- 内部类的成员与外部类的成员重名时,内部类调用遵循就近原则,需要调用外部成员时,需要通过
外部类.this.成员名
的方式,调用内部类自身的属性,this.成员名
- 可以直接访问外部类的所有成员,包含私有的
- 匿名内部类:
- 语法:
new 父类构造器(实参列表)|实现接口(){ //匿名内部类的类体部分 };
- 可以基于接口实现、也可基于父类实现
- 分号不能少
- 可以是成员内部类、也可以是局部内部类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51public class Cellphone{
// 1.1 匿名内部类是成员内部类
double b = new Calculator() {
double c = 9.0;
public double work() {
// TODO Auto-generated method stub
return 0;
}
}.c;
// 1.2 匿名内部类是成员内部类
double a = new Calculator() {
public double work() {
// TODO Auto-generated method stub
return 0;
}
}.work();
public double testWork(Calculator c) {
double result;
result = c.work();
// 2.1 匿名内部类是局部内部类
double b = new Calculator() {
double c = 9.0;
public double work() {
return 0;
}
}.c;
// 2.2 匿名内部类是局部内部类
double a = new Calculator() {
public double work() {
// TODO Auto-generated method stub
return 0;
}
}.work();
return result;
}
public static void main(String[] args) {
Cellphone cp = new Cellphone();
// 2.3 匿名内部类是局部内部类
double a = cp.testWork(new Calculator() {
public double work() {
return 1 + 1;
}
});
System.out.println(a);
}
}- 匿名内部类本身也是一个对象,因此它也可以调用内部类内部的方法,执行时遵循多态的规则(匿名内部类重写了就执行内部类里的方法)
- 其他有关问题参见 4.4.8 第 6 部分。
- 语法:
- 外部类不能被 static 修饰
- 成员内部类和局部内部类,在编译以后,都会生成字节码文件。
- 成员内部类:外部类$内部类名.class
- 局部内部类:外部类$数字 内部类名.class
4.2 jvm 内存分析
4.2.1 内存结构
- 图结构:
- 引用类型的变量只能存储两类值:
- null
- 包含变量类型的地址值。
4.2.2 内存分配
- 栈:一般指虚拟机栈,存储局部变量。
- 堆:存放对象(含对象的属性)、数组。
- 方法区:常量池(常量、字符串)、静态变量、即时编译后的代码。
4.2.3 成员变量与局部变量的异同
- 相同点:
- 定义变量的格式相同:
数据类型 变量名 = 变量值
- 先声明,后使用
- 都有其对应的作用域以及生命周期
- 定义变量的格式相同:
- 不同点:
- 在类中声明的位置的不同
- 属性:直接定义在类的一对
{}
内 - 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
- 属性:直接定义在类的一对
- 作用域范围不同:
- 属性:可以被本类使用、也可以被其他类使用(通过对象调用)
- 局部变量:只能在本类中对应的方法使用。
- 关于权限修饰符的不同
- 属性:可以在声明属性时,指明其权限,使用权限修饰符。
- 局部变量:不可以使用权限修饰符。
- 默认初始化值的情况不同:
- 属性:类的属性,根据其类型,都有默认初始化值。
- 局部变量:没有默认初始化值。在调用局部变量之前,一定要显式赋值。形参在调用时赋值即可。
- 在内存中加载的位置不同:
- 属性:加载到堆空间中(非 static),因为对象加载到了堆空间
- 局部变量:加载到栈空间
- 生命周期不同:
- 属性:生命周期长,伴随对象的创建而创建,伴随对象的销毁而销毁。
- 局部变量:生命周期短,伴随代码块的执行而创建,伴随代码块的执行结束而销毁。
- 在类中声明的位置的不同
- 属性和局部变量可以重名,访问时遵循就近原则。
- 属性赋值过程:
- 默认初始化
- 显式初始化 / 代码块
- 取决于类中书写的顺序
- 构造器中初始化
- 通过“对象.属性“或“对象.方法”的方式赋值
4.2.4 可变个数形参
- 语法:
- JDK 5.0 以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a, String[] books);
- JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a, String… books);
- JDK 5.0 以前:采用数组形参来定义方法,传入多个同一类型变量
- 注意点:
- 调用方法时,可传入的参数个数可以是 0 个,1 个或多个
- 可变参数的方法可与其他方法构成重载,调用时,优先调用匹配度高的
- 用
...
和[]
作为方法参数的同名方法视为同一个类里视为重复定义(编译报错),父子类内视为重写。
- 用
- 可变参数方法的使用与方法参数部分使用数组是一致的,方法体内使用 for 循环
- 方法的参数部分有可变形参时,需要放在形参声明的最后
- 一个方法最多只能声明一个可变个数形参
4.3 封装、继承和多态
2.3.1 封装与隐藏
-
含义:将类中的属性(数据)私有化,并提供外部可访问的类进行属性操作。
-
好处:
- 对(变量)数据进行验证,保证数据安全
- 隐藏实现细节
- 便于修改,增强代码可维护性
-
体现:
- 将属性使用 private 修饰,进行私有化,并提供公共的 set 和 get 方法获取和设置此属性的值
- 提供 public 修饰的 setter,用于判断并修改属性,无返回值
- 提供 public 修饰的 getter,用于判断并读取属性,有返回值
- 不对外暴露私有方法
- 单例模式(将构造器私有化)
- 如果不希望类在包外调用,可以将类设置为缺省的
- 将属性使用 private 修饰,进行私有化,并提供公共的 set 和 get 方法获取和设置此属性的值
-
权限修饰符:
修饰符 类内部 同一个包 不同包的子类(其父类是 protected) 同一个工程 private √ 缺省 √ √ 但修饰的属性子类访问不到 protected √ √ √ public √ √ √ √ - 类的权限修饰符只能用 public 或缺省
- 使用 public 时,本工程下使用
- 需要导入全类名
- 使用缺省时,只可以被同一个包内部的类访问
- 使用 public 时,本工程下使用
- 局部变量(方法内、方法形参等)的修饰符只能缺省。
- protected 修饰的属性、方法需要在不同包内访问时,一是需要父子继承关系,且 protected 修饰的内容是父类,二是需要在子类中导入包
- 类的权限修饰符只能用 public 或缺省
4.3.2 继承(inheritance)
- 作用:
- 减少代码的冗余,提高代码的复用性,提高开发效率
- 有利于功能扩展
- 使类与类之间产生联系,为多态提供了前提
- 格式:
class A extends B{}
A
:子类、派生类、subclassB
:父类、超类、superclas
- 体现:
- 子类 A 继承父类 B 以后,子类 A 中就自动获取了父类 B 中声明的所有的属性和方法。
- 直接父类、间接父类、自身都有同名属性的情况下,访问时执行就近原则。
- 方法按重写规定执行。
- 父类中声明为 private 的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。需要通过 setter 和 getter 调用属性。
- 通过
super.属性
的方法也访问不到。
- 通过
- 直接父类、间接父类、自身都有同名属性的情况下,访问时执行就近原则。
- 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
- 子类必须调用父类的构造器,完成父类的初始化。
- 实例化过程中会一直调用至 Object 对象。
- 注意点: 子类 A 继承自父类 B 后,如果父类 B 中声明了带参的构造器,则必须在子类 A 中声明 A 的构造函数,且必须在首行通过
super(形参列表)
调用 B 的构造函数。否则会编译报错。- 原因为如果子类 A 中不写构造器,其默认构造器为无参构造器,无参构造器中默认会调用 B 的无参构造器
super()
。由于父类 B 中显式地声明了构造器,导致默认的无参构造器不可用,从而会报错。而如果不在首行写super(形参列表)
语句,则表明调用的是super()
,同样会报错。- 解决方案一:父类 B 中显式声明无参构造器。
- 解决方案二:子类 A 中调用父类 B 中指定的构造器。声明 A 的构造器方法体内使用
super(形参列表)
- 原因为如果子类 A 中不写构造器,其默认构造器为无参构造器,无参构造器中默认会调用 B 的无参构造器
- 子类 A 继承父类 B 以后,子类 A 中就自动获取了父类 B 中声明的所有的属性和方法。
- 规定:
- 一个父类可以被多个子类继承。
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
- 如果我们没有显式的声明一个类的父类的话,则此类继承于 java.lang.Object 类。
- 所有的 java 类(除 java.lang.Object 类之外)都直接或间接的继承于 java.lang.Object 类
- 所有的 java 类具有 java.lang.Object 类声明的功能。
- 子类对象的实例化过程
- 从结果上来看:(继承性)
- 子类继承父类以后,就获取了父类中声明的属性或方法。
- 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 从过程上来看:
- 通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了 java.lang.Object 类中空参的构造器为止。
- 正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
- 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为 new 的子类对象。
- 从结果上来看:(继承性)
4.3.3 多态性(Polymorphism)
- 理解:方法和对象的多种形态。实现代码的通用性。
- 体现:
- 方法多态:
- 重载多态(参数不同,方法体现出多重形态)。
- 重写多态。
- 对象多态:
- 对象的编译类型和运行类型可以不一致,编译类型在定义时确定,不能变化。
- 对象的运行类型可以是变化的,可以通过
getClass()
查看运行类型。
- 常将父类对象作为方法形参,执行方法时根据具体实例化的父/子类对象进行传入。
- 方法多态:
- 使用:虚拟方法调用
- 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
- 编译期只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法。
- 不能调用子类中特有的成员。
- 需要调用子类特有成员时,需要使用向下转型。
- 总结:编译,看左边;运行,看右边。
- 使用前提:
- 类的继承关系
- 方法的重写
- 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)。
- 使用向下转型声明的父类对象,调用父子类同名的属性时,实际调用的是父类的属性。
- 属性不具有多态性。
- 由于父类的属性通常设置为 private,所以需要 getter 方法才能获取到。
- 使用向下转型声明的父类对象,调用父子类同名的属性时,实际调用的是父类的属性。
instance of
操作符:x instanceof A
:检验 x 的运行类是否为类 A 或其子类的对象,返回值为 boolean 型。- 如果 x 是类 B 的对象,而 B 是 A 的子类,则仍返回 true。
1
2
3
4AA aa = new BB();
System.out.println(aa instanceof AA);
System.out.println(aa instanceof BB);
//设BB继承自AA- 强制转型后的类型判断:这里均返回true,因为运行的是BB类
x instanceof Object
:总是返回 true。- 检验的 A 类必须与 x 的类有父子类关系,否则编译会报错。
- 造型:对 java 类型的强制类型转换
- 子类到父类可以自动类型转换——多态、向上转型
- 父类到子类必须通过造型
()
实现——向下转型。 - 无继承关系的引用类型造型非法,运行报错:
ClassCastException
- 通常首先使用
instanceof
操作符进行判断后进行造型操作,避免报错。
- java 的动态绑定机制:
- 当调用对象的方法时,该方法会和对象的内存地址/运行类型绑定。
- 当调用对象的属性时,没有动态绑定机制,哪里声明,哪里使用。
4.4 关键字
4.4.1 this
- 作用:
- 方法内部使用时,代表方法所属对象的引用
- 构造器内部使用时,代表该构造器正在初始化的对象。【子类构造器初始化时,父类构造器中的 this 指向的是子类正在实例化的对象】
- 使用范围:属性、方法、构造器
- 时机:方法内需要调用该方法的对象时,可以使用 this。
- 在任意方法或构造器内,如果使用当前类的成员变量或成员方法可以在其前面添加 this,增强程序的阅读性。不过,通常我们都习惯省略 this。
- 当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加 this 来表明该变量是类的成员变量
- 使用 this 访问属性和方法时,如果在本类中未找到,会从父类中查找
- this 调用构造器
- 可以在类的构造器中使用
this(形参列表)
的方式,调用本类中重载的其他的构造器! - 明确:构造器中不能通过
this(形参列表)
的方式调用自身构造器 - 如果一个类中声明了 n 个构造器,则最多有 n-1 个构造器中使用了
this(形参列表)
this(形参列表)
必须声明在类的构造器的首行!- 在类的一个构造器中,最多只能声明一个
this(形参列表)
- 可以在类的构造器中使用
- 有参的构造器如果没有显式地调用 this(),则不会调用无参的构造器。因为如果没写 this(),也没写 super(0,默认调用的是 super()。super()不能使用
this.super()
4.4.2 package
- 作用:package 语句作为 Java 源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。
- 格式:
package 顶层包名.子包名
- 一般:
com.公司名.项目名.业务模块名
- 一般:
- 使用规范:
- 包对应于文件系统的目录,package 语句中,用
.
来指明包(目录)的层次 - 包通常用小写单词标识。通常使用所在公司域名的倒置
- 包对应于文件系统的目录,package 语句中,用
- 作用:
- 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC 的设计模式
- 包可以包含类和子包,划分项目层次,便于管理
- 解决类命名冲突的问题
- 控制访问权限
- 注意点:
- 同一个包下,不能命名同名的接口、类。
- 不同的包下,可以命名同名的接口、类。
- 常见包:
- java.lang----包含一些 Java 语言的核心类,如 String、Math、Integer、 System 和 Thread,提供常用功能
- java.net----包含执行与网络相关的操作的类和接口。
- java.io ----包含能提供多种输入/输出功能的类。
- java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
- java.text----包含了一些 java 格式化相关的类
- java.sql----包含了 java 进行 JDBC 数据库编程的相关类/接口
- java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S C/S
4.4.3 import
- 作用:为使用定义在不同包中的 Java 类,需用 import 语句来引入指定包层次下所需要的类或全部类(.*)。
- 语法格式:
import 包名.类名;
- 使用细节:
- 在源文件中使用 import 显式的导入指定包下的类或接口
- 声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个 import 语句即可
- 可以使用 java.util.*的方式,一次性导入 util 包下所有的类或接口。
- 如果导入的类或接口是 java.lang 包下的,或者是当前包下的,则可以省略此 import 语句。
- 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
- 如果已经导入 java.a 包下的类。那么如果需要使用 a 包的子包下的类的话,仍然需要导入。
- import static 组合的使用:调用指定类或接口下的静态的属性或方法
4.4.4 super
- 用途:子类调用父类的属性、方法、构造器
- 解决子父子类属性、方法冲突的问题。
- 在合适的位置调用父类的属性、方法、构造器。
- 操作对象:属性、方法、构造器
- 调用属性和方法:
- 子类的方法和构造器中,使用父类的属性和方法时,默认使用了
super.
的方式调用属性、方法。编程中习惯不写super.
。- super 不能调用父类的私有属性。
- 当子类和父类中定义了同名的属性时,使用
super.属性
调用的是父类的属性;如果属性没有重名,使用super.属性
同this.属性
作用一样。 - 在子类中重写的方法中调用父类被重写的方法时,必须使用
super.方法
的方式。- super 不能调用父类的私有方法。
- super 调用父类时,不局限于直接父类,有多个上级类有重名方法、属性时,遵循就近原则。
- 子类的方法和构造器中,使用父类的属性和方法时,默认使用了
- 调用构造器:
- 可以在子类的构造器中,显式地使用
super(形参列表)
的方式调用父类的构造器。 super(形参列表)
必须声明在首行。- 由于
this(形参列表)
也必须出现在首行,所以一个构造器中this(形参列表)
或super(形参列表)
只能二选一,不能同时出现。 - 在构造器的首行,没有显式的声明
**this(形参列表)**
或**super(形参列表)**
,则默认调用的是父类中空参的构造器:**super()**
。 - 在类的多个构造器中,至少有一个类的构造器中使用了
super(形参列表)
,调用父类中的构造器。- 因为所有的对象都是继承来的,都必然具有父类的特征。
- 可以在子类的构造器中,显式地使用
- 默认的无参构造器默认调用的
super()
- 无论哪个构造器创建子类对象,需要先保证初始化父类,当子类继承父类以后,继承了父类中的属性和方法,因此子类有必要知道父类如何对对象初始化。
4.4.5 static
- 设计思想:
- 属性:在各个对象间共享,不因对象的不同而改变。
- 方法:方法的调用与对象无关,不需要创建对象就可调用方法。
- 修饰范围:
- 可修饰:属性、方法、代码块、内部类
- 被 static 修饰的属性叫:静态属性、静态变量、类变量。
- 没有被 static 修饰的属性叫非静态属性、实例变量。
- 被 static 修饰的方法叫:静态方法。
- 不能修饰:局部变量、形参、构造器、外部类
- 不能修饰构造器的原因:static 随着类的加载而加载,根据构造器的加载时机区分 static 和非 static,先于构造器加载的为 static,后于构造器加载的为非 static
- 可修饰:属性、方法、代码块、内部类
- 特点:
- 属性和方法随着类的加载而加载,与是否创建对象无关。
- 由于类只会在 JVM 的内存里加载一次,所以属性会只有一份。
- 存在于方法区的静态域中。
- 无论创建多少次对象,使用多少次类,都只有一份
- 属性和方法优先于对象而存在。
- 被 static 修饰的成员,被所有对象所共享。
- 访问权限允许时,可不创建对象,直接被类调用。
- 被 static 修饰的内部类实例化的特点呢?以及与多线程的关系?
- 属性和方法随着类的加载而加载,与是否创建对象无关。
- 注意点:
- 静态属性和静态方法可以通过
类名.静态属性
、类名、静态方法名()
的方式直接调用,也可以通过对象名.静态属性
、对象名、静态方法名()
的方式调用。 - 静态方法中,只能调用静态的方法或属性;
- 同类中的静态方法调用静态方法,可以直接使用
方法名();
或类名、静态方法名()
,不能使用this
- 实例化当前类后,可以通过
对象名.静态属性
、对象名、静态方法名()
的方式调用非静态的属性和方法。
- 同类中的静态方法调用静态方法,可以直接使用
- 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
- 在静态的方法内,不能使用 this 关键字、super 关键字。
- 也不能通过
this.
、super.
的方式调用静态属性、静态方法,只能通过类名.
的方式调用。
- 也不能通过
- static 修饰的方法不能被重写,但可以被继承。
- 不能被重写:父类、子类的同名、同参静态方法都会被加载。
- 可以被继承:子类可以直接通过
类名.方法名()
的方式调用。
- static 修饰内部类,类中的属性、方法、代码块可以是非静态的
- static 可与四种权限修饰符搭配使用,控制可见范围。
- 设置为 private 时,只能被
类名.静态属性
、类名.静态方法名()
调用,不能被对象的引用调用。
- 设置为 private 时,只能被
- 静态属性和静态方法可以通过
- 使用时机:
- 属性:
- 属性需要被多个对象共享
- 常量
- 方法:
- 操作静态属性
- 工具类中的方法。如 Math、Collection、Arrays
- 属性:
4.4.6 main
- main()说明:
- main 方法的权限修饰符必须是 public
- main 方法的修饰符必须是 static,因为 main 方法也是一个内中的方法,需要加载对象的时候执行,而不是创建 main 方法所在类的对象时候执行。
- 由于 main 方法使用 static 修饰了,所有不能直接访问本类中的非 static 属性和方法,必须通过实例化创建本类对象的方式进行调用。
- main 方法可以作为与控制台交互的方式 - 在命令行中执行(运行时,先要生成字节码文件)
java 文件名 参数
- 参数可以带引号,也可以不带,多个参数使用空格分开
- 在 eclipse 工具中执行:
- main 方法中访问直接访问本类中的静态方法和静态静态属性可以不需要
类名.
的方式调用。
4.4.7 final
- 用途:修饰类、变量(成员变量及局部变量)、方法、内部类
- 变量分为局部变量和成员变量
- final 修饰类:不能被继承,提高安全性、可读性。
- 通常 final 修饰类后,内部的方法没必要在用 final 修饰
- String 类、StringBuffer 类、System 类
- final 修饰方法:不能被子类重写
- Object 的 getClass()
- 不能修饰构造器
- final 修饰属性(成员变量):表示常量,且必须在定义时初始化。
- 赋值的位置可以是类中显式初始化、代码块、构造器内。
- 搭配 static 使用时,初始化位置只能是显示初始化和静态代码块内
- final 修饰局部变量:。
- final 修饰形参时,表示给常量赋值,方法体内只能使用该常量,不能修改该形参。
- 修饰方法体内局部变量时,称为局部常量。
- final 可以和 static 搭配使用,
static final
:只能用于修饰属性、普通方法,修饰属性时,表示全局常量。
4.4.8 abstract(抽象类与抽象方法)
- 修饰结构:类、方法、内部类
- 不能修饰属性、私有(private)方法、静态(static)方法、final 方法、final 类
- 通常用于处理父类方法不缺定时的需求。
- 先有抽象方法,后有抽象类。
- 不可修饰结构:属性、构造器等。
- 修饰类:
- 此类不能实例化
- 开发中一般提供抽象类的子类,该子类使用多态的方式实例化父类。否则匿名化。
- 修饰方法:
- 抽象方法只有方法名,没有方法体,用
;
结束 - 包含抽象方法的类,一定是抽象类;但抽象类不一定包含抽象方法。
- 抽象类不能被 private 修饰(因为要对外可见,需要被重写)
- 若子类重写了父类所有的抽象方法,则该子类可实例化;
- 若子类没有重写或部分重写了父类的所有抽象方法,则该子类也是一个抽象类,需要使用 abstract 修饰。
- 抽象方法只有方法名,没有方法体,用
- 抽象类的应用:模板方法的设计模式。(见 2.6.2)
- 匿名类(不一定为抽象类)与匿名对象:共有四种格式
- 非匿名类非匿名对象:
Worker worker = new Worker();
- 非匿名类匿名对象:
new Worker();
- 匿名类非匿名对象:
Person p = new Person(){重写Person的虚拟方法};
- 注意分号不能省略。
- Person 是一个虚拟类,按理不能实例化,实际上也正是通过方法体中的重写,将 Person 类变成了其他类,正常来说应该单独定义一个非虚拟具名的类继承自 Person,再将方法体写入其中,但正由于没有这样做,不知道这个子类叫什么名字,所以就是匿名类。
- 在匿名类非匿名对象的方法体中,如果声明了属性 a,通过
对象引用.p.a
的方式访问属性 a 的值,如果 Person 类中没有定义过 a,则报错,如果 Person 中定义了 a,则返回的是 Person 类中的 a 的值。即,匿名类非匿名对象中声明的自有属性访问不到。 - 如果要能访问到,可采用
int p = new Person(){int a = 1;重写Person的虚拟方法}.a;
int p = new Person(){int a = 1;重写Person的虚拟方法}.a;
改变返回值类型、.a
为.方法名()
可以获取.方法名()
的返回值。但是仅限于有返回值类型的方法(含自有方法)。
- 在匿名类非匿名对象的方法体中,如果声明了属性 a,通过
- 匿名类匿名对象:
new Person(){重写Person的虚拟方法}
- 注意分号不能省略。
- 非匿名类非匿名对象:
4.5 面向对象补充知识
4.5.1 JavaBean
- JavaBean 是一种 Java 语言编写的可重用组件。
- 特征:
- 类是公共的
- 有一个无参的公共构造器
- 有属性
- 属性一般定义为 private,有对应的 getter、setter
- 可以使用 JavaBean 将功能、处理、值、数据库访问和其他任何可以用 Java 代码创造的对象进行打包,并且其他的开发者可以通过内部的 JSP 页面、Servlet、其他 JavaBean、applet 程序或者应用来使用这些对象。用户可以认为 JavaBean 提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
4.5.2 UML 类图
- Accout:类名
+
表示 public,-
表示 private,#
表示 protected- 属性:
:
前表示属性名,:
后表示属性类型 - 方法:方法的
()
外面有:
表示有返回值,:
后面为返回值类型 - 方法有下划线表示构造器
4.5.3 MVC 设计模式
- 内容:常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。
- 优点:这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
- 模型层(model):主要处理数据
- 数据对象封装:model.bean/domain
- 数据库操作类:model.dao
- 数据库:model.db
- 控制层(controller):处理业务逻辑
- 应用界面相关:controller.activity
- 存放 fragment:controller.fragment
- 显示列表的适配器:controller.adapter
- 服务相关的:controller.service
- 抽取的基类:controller.base
- 视图层(view ):显示数据
- 相关工具类:view.utils
- 自定义 view:view.ui
4.5.4 Object 类的使用
- Object 类是所有 Java 类的根父类。
- 如果在类的声明中未使用 extends 关键字指明其父类,则默认直接父类为 java.lang.Object 类。
- Object 类中的属性:无
- Object 类中的方法:
equals()
:- 只能比较引用数据类型,作用与
==
相同,比较是否指向同一对象。 - 类
File、String、Date及包装类
由于重写了equals()
,所以比较的时内容是否相同。 - 重写
equals()
原则:- 对称性:如果 x.equals(y)返回是 true,那么 y.equals(x)也应该返回是 true。
- 自反性:x.equals(x)必须返回是 true。
- 传递性:如果 x.equals(y)返回是 true,而且 y.equals(z)返回是 true,那么 z.equals(x)也应该返回是 true。
- 一致性:如果 x.equals(y)返回是 true,只要 x 和 y 内容一直不变,不管重复 x.equals(y)多少次,返回都是 true。
- 任何情况下,x.equals(null),永远返回是 false,null。equals(x)会空指针异常。
- x.equals(和 x 不同类型的对象)永远返回是 false。
- 只能比较引用数据类型,作用与
toString()
:- 返回值为 String,返回类名和它的内存地址——全类名+@+哈希值的十六进制。
- 全类名:包名+类名
- 类
File、String、Date及包装类
由于重写了toString()
,返回"实体内容"信息。 - 使用 String 类型的数据与
+
进行连接操作时,自动调用toString()
- 返回值为 String,返回类名和它的内存地址——全类名+@+哈希值的十六进制。
getClass()
:获取当前对象所处的类hashCode()
:返回该对象的哈希值,用于提高哈希表的性能。- 两个引用指向同一对象,哈希码值一定一样。
- 两个引用指向不同对象,哈希码值一般不一样,(极少数情况一样)。
- 哈希值主要根据地址值生成,但与地址值不同。
clone()
finalize()
:- 当对象被回收时,系统会自动调用该方法。
- 当某个对象没有任何引用,则 jvm 认为该对象是一个垃圾对象。在销毁该对象前,会先调用该方法。
- 可以通过 System.gc()主动出发垃圾回收机制。
wait()
notify()
notifyAll()
- Object 类只声明了一个空参的构造器
- final、finally、finalize 的区别?
4.5.5 JUnit 单元测试
- 步骤:warning
- 选中当前工程 - 右键选择:build path - add libraries - JUnit 5.4 - 下一步
- 创建 Java 类,进行单元测试。
- 简便方式:在测试方法上一行写上
**@Test**
,然后 Ctrl+1 修复。
- 此时的 Java 类要求:① 此类是 public 的 ② 此类提供公共的无参的构造器
- 此类中声明单元测试方法:方法的权限是 public,没有返回值,没有形参。
- 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
- 声明好单元测试方法以后,就可以在方法体内测试相关的代码。
- 写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
- 说明:
- 如果执行结果没有任何异常:绿条
- 如果执行结果出现异常:红条
- 测试方法没有返回值,也没有参数
4.6 设计模式
- 概念:开发过程中经过大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。
- 分类:
- 创建型(5 种):
- 结构型(7 种):
- 行为型(11 种):
4.6.1 单例模式(Singleton)
- 定义:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
- 实现原理:
- 构造器私有化,防止 new
- 类的内部创建静态实例对象
- 向外暴露一个静态的公共方法。
- 实现方式:
- 饿汉式:
1
2
3
4
5
6
7
8
9
10
11class Singleton {
// 1.私有化构造器
private Singleton() {}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}- 懒汉式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Singleton {
// 1.私有化构造器
private Singleton() {}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
} - 优缺点:
- 饿汉式:
- 优点:线程安全。
- 缺点:对象加载时间过长。全生命周期。
- 懒汉式:
- 优点:延迟对象的创建。
- 缺点线程不安全。
- 饿汉式:
- 应用场景:
- 网站计数器:
- 应用程序的日志程序:
- 数据库的连接池
- 读取配置文件的类:
- Application:
- windows 系统的任务管理器
- windows 系统的回收站
4.6.2 模板方法设计模式(TemplateMethod)
- 体现:就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
- 也是多态行的一种体现。
- 解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。
- 应用:模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
- 数据库访问的封装
- Junit 单元测试
- JavaWeb 的 Servlet 中关于 doGet/doPost 方法调用
- Hibernate 中模板程序
- Spring 中 JDBCTemlate、HibernateTemplate 等
- 实现过程:
- 定义抽象类
- 抽象类中写明确定的流程,定义在一个方法(模板方法)内,模板方法调用各个流程
- 将不确定的内容插入到方法中的合适位置调用
- 将不确定的内容定义成抽象方法。
- 子类继承抽象类,重写抽象方法,在方法中完成需要工作的代码。
- 实例化子类,子类由于继承了父类的方法,通过子类对象的引用调用父类的模板方法的那个方法。
- 定义抽象类
4.6.3 代理模式(Proxy)
- 体现:类 A 实现了接口,实际需要类 A 去调用(操作)接口时,表面上通过实例化类 B 去操作,而将 A 作为一个参数调用。
- 步骤:
- 类 A 实现接口 C
- 类 B 实现接口 C
- 类 B 构造函数传入接口 C
- 在类 B 内定义检查、校验等方法,并在实现的接口 C 的方法内调用
- 创建
C 变量名 = new B(new A())
变量名.C的虚拟方法
- 应用场景
- 安全代理
- 远程代理
- 延迟加载
- 分类:
- 静态代理(上述描述)
- 动态代理(JDK 自带的静态代理,需要反射实现)
4.6.4 工厂模式(略)
4.7 Interface(接口)
- 概述:接口是一组规则的集成,是抽象方法和常量值的集合。
- 修饰符:public、缺省。
- public 修饰的接口需要单独建一个文件
- 级别:接口是和类并列的一类结构
- 语法:
1
2
3
4
5
6
7
8
9interface 接口名{
属性;
抽象方法;
}
class 类名 extends 父类 implements 接口1, 接口2{
类的自有属性;
类的自有方法;
必须实现的接口的抽象方法;
}- 属性为全局常量,
public static final
修饰,不写时仍然为public static final
,一般不写。 - 可以省略任意多个修饰符。
- 声明时必须初始化,因为为 final 修饰。
- 接口中的抽象方法可以不用
public abstract
修饰,不写时仍然为public abstract
,一般不写**(不写时注意不是缺省,override 方法时注意权限修饰符范围,即实现类的重写方法权限修饰符必须为 public,重写方法不能写 abstract)**。 - 可以省略任意多个修饰符。
- 不能 new
- 接口名有没有 abstract 修饰不影响
- JDK7 之前:只能定义全局常量和抽象方法:没有方法体
- JDK8 及之后:还可以定义静态方法、默认方法:有方法体
- 静态方法:static 修饰
- 可以通过接口直接调用并执行方法体。
- 实现类可以不用重写接口的静态方法
- 默认方法:default 修饰
- 可以通过类对象来调用
- 实现类可以不用重写接口的默认方法
- 属性为全局常量,
- 注意点:
- 接口不能被实例化(不能声明构造器),匿名接口的方式可以 new
- 普通类实现接口,必须实现接口中的所有方法(重写接口中的所有抽象方法)
- 抽象类实现接口,可以不用实现(重写)接口中的方法
- 接口可以相互继承,子类接口含有了直接父类、间接父类的所有属性和方法,其实现接口的类必须实现所有方法
- 接口中属性的访问形式:
接口名.属性名
或实现接口的类名.属性名
、对象名.属性名
(同虚拟类访问静态属性)类名.属性名
、对象名.属性名
的方式不能和继承的父类中的属性名冲突,否则会报错,原因为不明确- 继承+实现情况下属性名重复会报错
- 实现+实现情况下属性名重复会报错
- 接口也具有多态性
接口名 变量名 = new 实现接口的类名()
- 类优先原则:
class C extends A implements B
中,如果 A、B、C 中均定义了同名的属性,实例化 C 后访问该属性时,返回 C 中的,如果 C 中没有,则会报错。- 如果 A 中实现了 B 中的抽象方法,而 C 中什么也没写,这时根据继承原则,默认 C 中有了 A 的方法,从而重写了 B 中的方法。
- 两个接口同时定义了同名同参的默认方法:
- 如果返回值不一致(权限修饰符均一致), 一个类都实现了接口时,会发生接口冲突(报错),无法通过重写两个同名同参不同返回值类型的方法,因为两个接口不知道谁是它的重写。
- 如果返回值一致,则一个实现类可只写一个重写方法,表示对两个接口方法的重写。
- 一个接口定义了默认方法,一个父类定义了同名同参数的非抽象方法,不会发生冲突,且类优先,接口中定义的同名同参方法会被忽略。 - 调用指定接口的方法:
接口名.super.默认方法名
类 内部类 属性 局部变量 方法 构造器 代码块 接口 public 是 是 是 否 是 是 否 是 protected 否 是 是 否 是 是 否 否 缺省 是 是 是 否 是 是 是 是 private 否 是 是 否 是 是 否 否 - public 修饰类和接口时,必须单独设置一个文件。
类 内部类 属性 局部变量 方法 构造器 代码块 接口 static 否 是 是 否 是 否 是 否 final 是 是 是 是 是 否 否 否 abstract 是 是 否 否 非 private 的方法 否 否 是 - static、final、abstract 要写在语句的开头,但与权限修饰符的先后顺序没关系。
- final 和 abstract 修饰符永远二选一(类、内部类、方法)。
- final 和 static 永远可搭配(内部类、属性、方法)
- static 和 abstract 可同时修饰内部类,不可同时修饰方法。
第 5 章 常用类
5.1 包装类
5.1.1 来源原因
- Java 定义了 8 种数据类型对应的引用类型(包装类、封装类),使得可以调用类中的方法。
5.1.2 分类:
5.1.3 基本数据类型、包装类、String 类型的相互转换:
- 基本数据类型转换为包装类:
- 装箱:基本数据类型包装成包装类的实例。
- 使用包装类的构造器:
new Integer()
。- 可传入基本数据类型。
- 可传入字符串。
- 使用包装类的方法:
Integer.valueOf();
- 包装类转换为基本数据类型:
- 拆箱:获得包装类对象中包装的基本类型变量。
- 调用包装类的
.intValue()
方法。
- 基本数据类型转换为字符串:
- 调用字符串重载的
valueOf()
方法。 - 使用
+
拼接""
空字符串。
- 调用字符串重载的
- 字符串转换为基本数据类型:
- 使用包装类的构造器:
new Integer()
,传入字符串。 - 使用包装类的
parseInteger(String s)
静态方法。
- 使用包装类的构造器:
- 字符串转换为包装类:
- 调用包装类的构造器:
new Integer()
。
- 调用包装类的构造器:
- 包装类转换为字符串:
- 调用包装类的
tostring()
方法。
- 调用包装类的
- JDK1.5 之后支持自动装箱、自动拆箱,但类型必须匹配
- Integer a = 10; 自动封箱
- int b = a; 自动拆箱
- 基本数据类型和包装类型使用功能
==
号比较时,比较的是数值值。
5.2 String 类
5.2.1 String 对象的特点
- 继承自 Object
- 使用双引号包裹。
- 字符串的字符使用 Unicode 字符编码,一个字符(不区分字母和汉字)占用 2 个字节。
- 常见创建方式(常用构造器):
new String()
:空参new String(String str)
:字符串字面量new String(char[] a)
:字符型数组new String(byte[] b)
:btye 型数字(一个 byte 数占 1 个字节)new String()
:
- String 由 final 修饰,不可被继承。源码
**public final class String{}**
- String 内定义了
private final char value[]
,用于存放字符串内容。- 由于 value 数组被 final 修饰,所以不能改变 value 的地址值。
- 如果改变字符串变量的字面量值,如将
String str = s;
改为String str = str;
则表示 str 指向常量池中新的地址。而不是对常量池中 s 所在的地址空间重新填充内容。 - 体现: - 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的 value 进行赋值。
当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。 - 当调用 String 的 replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的 value 进行赋值。
5.2.2 创建 String 对象细节
- 比较对象:
- 字面量方式:
String str = "string";
- 构造器方式:
String str = new String(string);
- 字面量方式:
- 字面量方式:
- 从常量池检查是否已有该字符串
- 常量池中有,str 指向常量池中该字符串的地址
- 常量池中没有,先创建该字符串,然后 str 指向该字符串在常量池中的地址
- 构造器方式:
- new String 在堆空间创建了地址空间,str 指向该地址
- new String 开辟的内存空间中的属性 value 数组,在常量池中查找字符串
- 常量池中有,value 指向常量池中该字符串的地址
- 常量池中没有,先创建该字符串,然后 value 指向该字符串在常量池中的地址
- 字面方式创建注意点:
String d = "helloabc";
与String e = "hello" + "abc";
等价,指向常量池同一地址。String a = "hello";String b = "abc";String c = a + b;
与String e = "hello" + "abc";
不等价。String c = a + b;
底层会先进行StringBuilder c = new StringBuilder()
,然后调用 append()方法将 a 和 b 添加进去,此时创建的 StringBuilder()对象在堆内存中,所以 c 保存的是堆空间的地址。- 如果再执行
c.intern();
则与String e = "hello" + "abc";
等价。- 原因为 intern()方法会在常量池中查找是否存在该字符串,存在时返回常量池中的对象(含地址),不存在则创建。
String a = "hello";String b = "abc" + a;
只要有变量参与,就会调用 StringBuilder()创建对象。- 但假如
final String a = "hello"
,则结果仍在常量池,因为此时 a 变成了常量
- 但假如
- 结论:
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
- 只要其中有一个是变量,结果就在堆中。
- 如果拼接的结果调用 intern()方法,返回值就在常量池中
5.2.3 JVM 中涉及字符串的存放位置
【参见 JVM 虚拟机】
- jdk 1.6:字符串常量池存储在方法区(永久区)
- jdk 1.7:字符串常量池存储在堆空间
- jdk 1.8:字符串常量池存储在方法区(元空间)
5.2.4 String 常见方法
- 操作:
int length()
:返回字符串的长度: return value.lengthchar charAt(int index)
: 返回某索引处的字符 return value[index]boolean isEmpty()
:判断是否是空字符串:return value.length == 0String toLowerCase()
:使用默认语言环境,将 String 中的所有字符转换为小写String toUpperCase()
:使用默认语言环境,将 String 中的所有字符转换为大写String trim()
:返回字符串的副本,忽略前导空白和尾部空白boolean equals(Object obj)
:比较字符串的内容是否相同boolean equalsIgnoreCase(String anotherString)
:与 equals 方法类似,忽略大小写String concat(String str)
:将指定字符串连接到此字符串的结尾。 等价于用“+”int compareTo(String anotherString)
:比较两个字符串的大小
- 查找:
boolean endsWith(String suffix)
:测试此字符串是否以指定的后缀结束boolean startsWith(String prefix)
:测试此字符串是否以指定的前缀开始boolean startsWith(String prefix, int toffset)
:测试此字符串从指定索引开始的子字符串是否以指定前缀开始boolean contains(CharSequence s)
:当且仅当此字符串包含指定的 char 值序列时,返回 trueint indexOf(String str)
:返回指定子字符串在此字符串中第一次出现处的索引int indexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始- 如果
fromIndex
为负数,表示从尾部开始,到头部的顺序查找,仍然是正序查找。
- 如果
int lastIndexOf(String str)
:返回指定子字符串在此字符串中最右边出现处的索引int lastIndexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
- 替换:
String replace(char oldChar, char newChar)
:返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。String replace(CharSequence target, CharSequence replacement)
:使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。String replaceAll(String regex, String replacement)
:使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。String replaceFirst(String regex, String replacement)
:使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
- 匹配:
boolean matches(String regex)
:告知此字符串是否匹配给定的正则表达式。
- 切片:
String[] split(String regex)
:根据给定正则表达式的匹配拆分此字符串。String[] split(String regex, int limit)
:根据匹配给定的正则表达式来拆分此字符串,最多不超过 limit 个,如果超过了,剩下的全部都放到最后一个元素中。String substring(int beginIndex)
:返回一个新的字符串,它是此字符串的从 beginIndex 开始截取到最后的一个子字符串。String substring(int beginIndex, int endIndex)
:返回一个新字符串,它是此字符串从 beginIndex 开始截取到 endIndex(不包含)的一个子字符串。
- String 与 char[]之间的转换
- String --> char[]:调用 String 的 toCharArray()
- char[] --> String:调用 String 的构造器
- String 与 byte[]之间的转换
- 编码:String --> byte[]:调用 String 的 getBytes()
- 解码:byte[] --> String:调用 String 的构造器
- 解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
- String 与基本数据类型、包装类之间的转换。
- String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
- 基本数据类型、包装类 --> String:调用 String 重载的 valueOf(xxx)
- 注意: 由于字符串是不可变的,所以操作字符串的方法基本都有返回值,而原字符串不会发生改变。
5.2.5 StringBuffer
- 特性:
- 可变的字符序列,对字符串进行增删,不会产生新的对象
- 作为参数传递时,方法内部可以改变值。
- 构造器:
StringBuffer()
:初始容量为 16 的 char 型字符串数组StringBuffer(int size)
:指定 size 容量的字符串StringBuffer(String str)
:将内容初始化为指定字符串内容。
- 常用方法:
- 增:
StringBuffer append(xxx)
: - 删:
StringBuffer delete(int start,int end)
: - 改:
StringBuffer replace(int start, int end, String str)
:public void setCharAt(int n ,char ch)
:
- 查:
public char charAt(int n)
: - 插:
StringBuffer insert(int offset, xxx)
: - 遍历:
for() + charAt()
或者toString()
- 其他:
StringBuffer reverse()
:把当前字符序列逆转public int indexOf(String str)
public String substring(int start,int end)
:public int length()
:
- 注意点:返回值类型为 StringBuffer 的方法会对原数据进行修改,返回值类型为 String 类型的方法不会对元数据进行修改。
- 底层原理:StringBuffer 的方法底层返回了 this,既是对当前数据的返回,也表明支持链式操作
- 增:
5.2.6 StringBuilder
- 同 StringBuffer,JDK5.0 新增,只是线程不安全,效率优于 StringBuffer。
- 开发中建议使用:
StringBuffer(int capacity)
或StringBuilder(int capacity)
。
5.3 时间 API
- JDK8 之前:
- java.lang.System 类:
currentTimeMillis()
,获取当前时间距离 1970 年 1 月 1 日 0 时 0 分 0 秒的毫秒数 - java.util.Date 类 :
- 构造器:
Date()
Date(long date)
- 方法:
getTime()
:获取当前时间距离 1970 年 1 月 1 日 0 时 0 分 0 秒的毫秒数toString()
:【重写方法】按照 down mon dd hh:mm:ss zzz yyy 的格式输出- down:星期几
- zzz:时间标准
- java.sql.Date 类:
- java.util.Date 类的子类
- 创建时使用全包名:即
new java.sql.Date()
- 相互转换:
- 方式一:创建 Date 对象的多态 java.sql.Date 对象;创建 java.sql.Date 对象,使用向上转型(强转)——注意:强转的前提是 Date 对象是由 java.sql.Date 创建的。
- 方式二:通过共有方法
getTime()
获取时间戳
- 构造器:
- java.text.SimpleDataFormat 类:
- 用于对 Date 类的格式化和解析
- 创建指定格式的对象:
SimpleDateFormat()
:默认格式SimpleDateFormat("yyyy-MM-dd")
:指定格式
- 格式化:将日期输出为字符串
- 调用
foramt(Date date)
方法:
- 调用
- 解析:将字符串转换为日期
- 调用
parse(String sourse)
方法:- 这里 sourse 的格式必须与创建
SimpleDateFormat()
对象的格式一致 - 有异常问题
- 这里 sourse 的格式必须与创建
- 调用
- java.util.Calendar(日历)类 :
- 用于日期字段(fileld)间的相互操作
- field:Calendar 类的静态属性:YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、 MINUTE、SECOND
- 获取 Calendar 实例:
Calendar calendar = Calendar.getInstance();
- Calendar 时抽象类,无法实例化,调用静态方法
getInstance()
实际上会调用其子类GregorianCalendar
创建对象。
- Calendar 时抽象类,无法实例化,调用静态方法
- 常用方法:
int get(int field,int value)
void set(int field,int value)
void add(int field,int amount)
Date getTime()
:日历类转换 Date 类void setTime(Date date)
:Date 类转换日历类
- 用于日期字段(fileld)间的相互操作
- java.lang.System 类:
- 日期时间 API 的迭代:
- 第一代:jdk 1.0 Date 类
- 第二代:jdk 1.1 Calendar 类,一定程度上替换 Date 类
- 第三代:jdk 1.8 提出了新的一套 API
- 前两代存在的问题举例:
- 可变性:像日期和时间这样的类应该是不可变的。
- 偏移性:Date 中的年份是从 1900 开始的,而月份都从 0 开始。
- 格式化:格式化只对 Date 用,Calendar 则不行。
- 此外,它们也不是线程安全的;不能处理闰秒等。
- JDK8 新增 API
- java.time – 包含值对象的基础包
- java.time.chrono – 提供对不同的日历系统的访问
- java.time.format – 格式化和解析时间和日期
- java.time.temporal – 包括底层框架和扩展特性
- java.time.zone – 包含时区支持的类
- JDK8 新增时间类(构造方法私有的,不能实例化):
- LocalDate:获取 ISO 格式(yyyy-MM-dd)的日期
- LocalTime:获取时间
- LocalDateTime:获取日期和时间
- 常用方法:
now()
(静态方法):获取当前的日期、时间、日期+时间,创建类对象of()
:设置指定的年、月、日、时、分、秒。没有偏移量getXxx()
:获取相关的属性withXxx()
:设置相关的属性
- Instant(类似于 Calendar,不可实例化):
now()
(静态方法):获取本初子午线对应的标准时间。Instant instant = Instant.now()
atOffset()
:添加时间的偏移量toEpochMilli()
:获取自 1970 年 1 月 1 日 0 时 0 分 0 秒(UTC)开始的毫秒数ofEpochMilli()
:通过给定的毫秒数,获取 Instant 实例
- DateTimeFormatter:格式化或解析日期、时间
- 预定义的标准格式:
DateTimeFormatter.ISO_LOCAL_DATE_TIME
DateTimeFormatter.ISO_LOCAL_DATE
DateTimeFormatter.ISO_LOCAL_TIME
- 本地化相关格式:
DateTimeFormatter._ofLocalizedDateTime_(FFormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT)
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT)
- 自定义格式:
DateTimeFormatter._ofPattern_("yyyy-MM-dd hh:mm:ss")
- 格式化:
时间格式.format(时间)
,返回字符串 - 解析:
时间格式.parse(符合时间格式的字符串)
,返回 TemporalAccessor 类型日期
- 预定义的标准格式:
5.4 其他类
- Math 类:
- 常用方法:
double floor(double a)
:——向坐标轴左侧取整,如 1.2、1.6 得 1.0;-1.2、-1.6 得-2.0。int round(double a)
:——本质是(int)Math.floor(a + 0.5f)
。- 正数四舍五入;负数不可记按正数四舍五入加负号,因为
round(-1.5)
得-1
- 正数四舍五入;负数不可记按正数四舍五入加负号,因为
- 常用方法:
- System 类
- 构造器是 private 的,无法创建该类的对象,内部的成员变量和成员方法都是 static 的,可以直接进行调用。
- 方法:
native long currentTimeMillis()
void exit(int status)
:退出当前程序- status:0 表示正常状态。
arrayCopy()
:- 参数 1:源数组
- 参数 2:源数组开始索引(包含)
- 参数 3:目标数组
- 参数 4:目标数组位置开始索引(包含)
- 参数 5:源数组拷贝数量
void gc()
String getProperty(String key)
- BigInteger 类
- BigInteger 可以表示不可变的任意精度的整数
- 不能使用
+、-、*、/
,需要使用相应的方法。
- BigDecimal 类
- Array 类:
toString()
:重写 Object 方法,默认调用,输出数组元素- 对象数组时,输出的时数组内每个对象的地址,如果还要显示每个对象的内容,就需要重写类的 toString 方法。
sort()
:默认从小到大排序binarySearch()
:通过二分法查找指定数,返回索引,要求必须排好序,无序数组不能使用。如果不存在,返回-(low+1)
copyOf()
:拷贝指定数量的数组元素。- 新数组比就数组长,最后几位为 null
- 拷贝长度小于 0,抛出异常,等于 0,新数组为空
fill()
:替换数组的全部元素equals()
:比较数组元素是否一致asList()
:将括号内数据转换为 List 集合。
- Scanner 类:
next()
:接收数据,输入空格、回车、英文引号表示结束
5.5 比较器
- 作用:比较两个对象,用于对象数组排序。
- 自然排序:
java.lang.Comparable
包中的Comparable
接口- String、包装类等实现了 Comparable 接口,重写了 compareTo(obj)方法,给出了比较两个对象大小的方式。
- 像 String、包装类重写 compareTo()方法以后,进行了从小到大的排列
- 重写 compareTo(obj)的规则:
- 如果当前对象 this 大于形参对象 obj,则返回正整数
- 如果当前对象 this 小于形参对象 obj,则返回负整数
- 如果当前对象 this 等于形参对象 obj,则返回零
- 对于自定义类来说,如果需要排序,可以让自定义类实现 Comparable 接口,重写 compareTo(obj)方法。在 compareTo(obj)方法中指明如何排序
- 定制排序:
- 当元素的类型没实现 java.lang.Comparable 接口而又不方便修改代码,或者实现了 java.lang.Comparable 接口的排序规则不适合当前的操作,那么可以考虑使用 匿名的 Comparator 的对象来排序
- 重写 compare(Object o1,Object o2)方法,比较 o1 和 o2 的大小:
- 如果方法返回正整数,则表示 o1 大于 o2;
- 如果返回 0,表示相等;
- 返回负整数,表示 o1 小于 o2。
- 补充:
- 包装类具有
compare
、compareTo
两个方法,其中compareTo
是静态方法。 - String 具有
compareTo
、conpareToIngoreCase
方法
- 包装类具有
- 比较:
- Comparable 位于 java.lang 包, Comparator 位于 java.util 包。
- 实现 Comparable 接口需要重写 compareTo()方法,实现 Comparator 方法需要重写 compare()方法,这两个方法的类型都是 int
- Comparable 是排序接口,相当于内部比较器,称为自然排序,Comparator 是比较器,相当于外部比较器,称为比较器排序,外部比较器可以对内部比较器进行扩充。
- int test = s1.compareTo(s2); result = 0 , 则 s1=s2 ; result < 0, 则 s1 0 , 则 s1 > s2 。
- int test= compare(T o1, T o2); 结果同上。 (若是 o2-o1,则反之。)
- 如果对象的排序需要基于自然顺序,使用 Comparable 比较合适,如果需要按照对象的不同属性进行排序,使用 Comparator 比较合适。
第 6 章 枚举和注解
6.1 枚举类型
6.1.1 枚举的概念
- enumeration、enum
- 一组常量类型的集合。
- 是一种特殊的类,只包含有限的特定的对象,与 Object 并列
- 特点:
- 属性不允许被改动:属性使用 private final 修饰、且不设置 setter
- 为了能够初始化属性,使用构造器传参的方式,在构造器内赋值。
- 在枚举类内创建实例。
6.1.2 枚举类型的实现
- 自定义类(JDK1.5 之前):
- 私有化属性,只配置 getter,不配置 seter:防止属性被修改
- private final 修饰
- 私有化构造器:防止 new
- private 修饰
- 在类内部,创建公开的枚举对象
- 命名规范:属性名使用大写形式
- 对象使用 public static final 修饰,进行底层优化
- 三个可省略任意多个
- 私有化属性,只配置 getter,不配置 seter:防止属性被修改
- enum 关键字(JDK1.5 之后):
1
2
3
4
5
6
7
8
9
10
11enum Season{
SPRING("春天", "温暖"), SUMMER("夏天", "炎热");
private String name;
private String desc;
private Season(String name, String desc){
this.name = name;
this.desc = desc;
}
nam getter;
desc getter;
} - 使用 enum 关键字的注意点:
- 枚举类:
- 枚举类前默认有 final 关键字,所以枚举类不能被继承
- final 的特性导致
- 默认继承自 java.lang.Enum 类,所以也不能继承其他类
- 枚举类前默认有 final 关键字,所以枚举类不能被继承
- 枚举对象:
- 实例化的枚举类对象必须声明在枚举类的首行
- 实例化的枚举类对象必须与构造器的参数对应,且枚举对象必须传入参数
- 实例化的枚举对象前默认有 public static final,可以省略任意多个
- 构造器:
- 使用无参构造器实例化枚举类对象时,枚举对象的括号可以省略
- 构造器默认使用 private 修饰,可以省略
- 属性:
- 默认使用 private final 修饰,可以省略
- 枚举类:
6.1.3 常用方法
- enum 声明的类会隐式继承 Enum 类,因此可以使用 Enum 中的相关方法:
toString()
:返回当前对象名,声明 enum 类时可重写,用于返回类中的属性内容name()
:返回当前对象名,不能重写。- 有限使用
toString()
- 有限使用
ordinal()
:返回当前对象的位置号,从 0 开始values()
:返回当前枚举类中的所有实例化对象,返回一个 enum 声明的枚举类型数组valueOf()
:返回指定字符串的实例化对象,如果传入的字符串枚举类中没有,会报错- 传入的字符串名称要跟实例化对象名一致
compareTo()
:比较两个枚举常量的位置编号,返回常量值。- 调用的 - 参数的
6.1.4 enum 实现接口
- 语法:
enum 类名 implements 接口 1,接口 2{}
6.1.5 枚举配合 switch-case
- switch()中传入一个枚举对象,case 后是枚举对象的值
6.2 注解(Anotation)
6.2.1 概念
- 含义:又称为元数据(Meatdata),用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息。
- 特点:和注释一样,不影响程序运行,但注解可以被编译或运行,相当于嵌入代码的补充信息。
- 功能:
- javase 中,注解主要用于标记过时功能、忽略警告等
- Javaee 中,可以用于配置应用程序的任何切面,代替旧版 javaee 中的繁冗代码和 XML 配置
6.2.2 常见类型及基本使用
@Override
:方法前使用,表示重写父类方法- 只能用在方法前。
- 重写的方法前不写该注解不影响编译运行
- 如果写了该注解,程序在编译阶段检查是否真正重写。
- 非重写时会报错。
@Deprecated
:表示程序元素(类、方法等)已过时。- 可以用在类、方法(构造器)、变量(成员变量、局部变量)、包等
- 用于新旧版本的兼容和过渡
@SuppressWarnings()
:抑制编译器警告- 可以用在类、方法(构造器)、变量(成员变量、局部变量)、包等
- 主要用在方法、类前
()
:传入字符串值,表示抑制的警告类型- 多种类型时,可以传入数组字面量
{}
- 多种类型时,可以传入数组字面量
- 作用范围与书写位置有关
@SuppressWarning 中的属性介绍以及属性说明
- all,抑制所有警告
- boxing,抑制与封装/拆装作业相关的警告
- cast,抑制与强制转型作业相关的警告
- dep-ann,抑制与淘汰注释相关的警告
- deprecation,抑制与淘汰的相关警告
- fallthrough,抑制与 switch 陈述式中遗漏 break 相关的警告
- finally,抑制与未传回 finally 区块相关的警告
- hiding,抑制与隐藏变数的区域变数相关的警告
- incomplete-switch,抑制与 switch 陈述式(enum case)中遗漏项目相关的警告
- javadoc,抑制与 javadoc 相关的警告
- nls,抑制与非 nls 字串文字相关的警告
- null,抑制与空值分析相关的警告
- rawtypes,抑制与使用 raw 类型相关的警告
- resource,抑制与使用 Closeable 类型的资源相关的警告
- restriction,抑制与使用不建议或禁止参照相关的警告
- serial,抑制与可序列化的类别遗漏 serialVersionUID 栏位相关的警告
- static-access,抑制与静态存取不正确相关的警告
- static-method,抑制与可能宣告为 static 的方法相关的警告
- super,抑制与置换方法相关但不含 super 呼叫的警告
- synthetic-access,抑制与内部类别的存取未最佳化相关的警告
- sync-override,抑制因为置换同步方法而遗漏同步化的警告
- unchecked,抑制与未检查的作业相关的警告
- unqualified-field-access,抑制与栏位存取不合格相关的警告
- unused,抑制与未用的程式码及停用的程式码相关的警告
- 可以用在类、方法(构造器)、变量(成员变量、局部变量)、包等
- 源码中
@interface xxx
表示 xxx 是一个注解类。 - 元注解:修饰其他注解的注解
@Retention(RetentionPolicy.xxx)
:指定注解的作用时机- SOURCE:只在源码中显示,编译后丢弃这种注解策略
- CLASS:在字节码文件中显示,运行时不会保留。是注解的默认值。
- RUNTIME:运行时仍然保留,可以通过反射获取该注解
@Target(ElementType.xxx)
:指定注解用于修饰哪些元素。- 作用时机是 RUNTIME
- 只能用于修饰注解
@Documented
:被修饰的注解会在生成文档注释时保留。- 作用时机时 RUNTIME
@Inherited
:被修饰的注解具有继承性,子类自动具有该注解
- 与文档相关的注解:
- @author 标明开发该类模块的作者,多个作者之间使用,分割
- @version 标明该类模块的版本
- @see 参考转向,也就是相关主题
- @since 从哪个版本开始增加的
- @param 对方法中某参数的说明,如果没有参数就不能写
- 标记方法
- 可以并列多个
- @return 对方法返回值的说明,如果方法的返回值类型是 void 就不能写
- 标记方法
- 格式要求:@return 返回值类型 返回值说明
- @exception 对方法可能抛出的异常进行说明 ,如果方法没有用 throws 显式抛出的异常就不能写
- 标记方法
- 格式要求:@exception 异常类型 异常说明
- 可以并列多个
@WebServle()
:@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)
:
6.3.3 自定义注解warning
- 自定义注解使用@interface 关键字声明
- 自定义注解自动继承了 java.lang.annotation.Annotation 接口
- 自定义注解的成员变量以无参数方法的形式来声明。方法名和返回值类型称为配置参数。返回值类型只能是八种基本数据类型、String 类型、Class 类型、enum 类型、Annotation 类型、 及以上所有类型的数组。
- 自定义注解的成员变量声明时可使用 default 关键字指定初始值
- 如果只有一个参数成员,通常使用 value
- 使用自定义注解时,如果定义注解时声明了成员变量(配置参数),那么使用时必须指定参数值,除非它有默认 值。格式是“参数名 = 参数值” ,如果只有一个参数成员,且名称为 value, 可以省略“value=”
- 自定义注解通过都会指明两个元注解:Retention、Target
第 7 章 异常处理
7.1 异常理解
- 概念:程序运行过程中发生的不正常情况。
7.2 异常的体系结构
- Error:Java 虚拟机无法解决的严重问题。一般不做处理。
- StackOverflowError:栈溢出
- OutOfMemoryError(OOM):堆溢出
- Exception:
- 编译时异常:
- 运行时异常:
java.lang.Throwabe> java.lang.Throwable|-----java.lang.Error:一般不编写针对性的代码进行处理。
|-----java.lang.Exception:可以进行异常的处理
|------编译时异常(checked)
|-----IOException
|-----FileNotFoundException
|-----ClassNotFoundException
|------运行时异常(unchecked,RuntimeException)
|-----NullPointerException:空指针异常
|-----ArrayIndexOutOfBoundsException:数组下标越界
|-----ClassCastException:类型不匹配
|-----NumberFormatException:数字格式错误
|-----InputMismatchException:输入不匹配
|-----ArithmeticException:运算错误
7.3 异常处理机制
7.3.1 抓抛模型:
- “抛”:程序执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并会将此对象抛出给调用它的方法,直到 main 方法,如果 main 方法没能处理,则程序运行终止,发生异常位置后面的代码也不会执行。
- 异常产生有两种形式:一是系统自动产生,二是手动生成并抛出(throw)
- “抓”:即异常的处理方式:
- try-catch-finally
- throws
7.3.2 try-catch-finally
- 语法:
1
2
3
4
5
6
7
8
9
10
11
12try{
...... //可能产生异常的代码
}
catch( ExceptionName1 e ){
...... //当产生ExceptionName1型异常时的处置措施
}
catch( ExceptionName2 e ){
...... //当产生ExceptionName2型异常时的处置措施
}
[ finally{
...... //无论是否发生异常,都无条件执行的语句
} ]- try 不能单独使用
- catch 和 finally 是可选的。
- try-finally:没有 catch 语句,相当于捕获到了异常但没有处置,会先输出 finally 中的语句,再报异常,程序也会中止。
- try-catch-finally:先报异常,再输出 finally 语句,程序不会中止
- 在 try 结构中声明的变量,出了 try 结构以后,就不能再被调用
- try-catch-finally 结构可以嵌套
- 使用:IDE 快捷键: 选中->ctrl + alt + t
- 执行流程:
- 如果异常没有发生,则顺序执行 try 的代码块,不会进入到 catch。
- 如果异常发生了,则异常发生后 try 块中的代码不会执行,直接进入到 catch 块。执行完 catch 块后再执行 try-catch 语句后的代码。
- 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用 finally
- 即使 try、catch 语句里有 return(正常情况 return 结束方法),执行完之后仍然会执行 finally 中的语句,再返回结束方法。但当 finally 中有 return 语句,则不再执行 try 或 catch 的 return。
- 可以使用多个 catch 分别捕获不同的异常,要求子类异常写在前面,父类异常写在后面
- try 中有多个异常,且父类一致(如 Exception),如果 catch 只匹配父类,则只捕获第一个异常,try 中后面的代码(所有代码)不会被执行
- try 中有多个异常,catch 匹配多个异常,但只捕获第一个异常,try 块中后面的代码(所有代码)不会被执行,异常也不会捕获到
- 常用的异常对象处理的方式:
- getMessage() :打印异常信息
- printStackTrace(): 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值 void。
7.3.3 throws + 异常类型
- "throws + 异常类型"写在方法的声明处,指明此方法执行时,可能会抛出的异常类型。
- 抛出多个异常使用
,
分隔
- 抛出多个异常使用
- 一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足 throws 后异常类型时,就会被抛出。不满足则相当于异常没有处理。异常代码后续的代码,就不再执行!
- throws 的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉,方法调用者再抛出时,异常类型要不小于接收到的异常类型。
- 运行异常的默认处理方式为 throws,可不处理,编译异常没有默认处理方式,必须处理
- 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
- 在 throws 过程中,如果有方法 try-catch , 就相当于处理异常,就可以不必 throws
7.4 try-catch-finally 和 throws 的选择
- 拥有继承关系的父子类,如果父类中被重写的方法没有 throws 方式处理异常,则子类重写的方法也不能使用 throws,意味着如果子类重写的方法中有异常,必须使用 try-catch-finally 方式处理。
- 执行的方法 a 中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。建议这几个方法使用 throws 的方式进行处理。而执行的方法 a 可以考虑使用 try-catch-finally 方式进行处理。
7.5 手动抛出异常
- 在可能发生异常的位置生成异常类对象,然后通过 throw 语句实现抛出操作
- 可以抛出的异常必须是 Throwable 或其子类的实例,否则会报错。
7.6 自定义异常类
- 继承于现有的异常结构:RuntimeException 、Exception
- 继承 exception 时为编译异常
- 一般使用 RuntimeException,且需要方法再 thows 异常
- 提供全局常量(static final 修饰):serialVersionUID
- 提供重载的构造器,传入
String msg
参数 - 构造器内执行
super(msg);
原文链接: https://sk370.github.io/2022/05/28/javase/Java语言基础(上)/
版权声明: 转载请注明出处。