JavaSE
Java概述
Java语言的特点
- 面向对象
- 健壮性
- 跨平台性
Java两种核心机制
-
Java虚拟机 (Java Virtal Machine)
字节码文件运行在JVM上
-
垃圾收集机制 (Garbage Collection)
JDK、JRE、JVM
- JDK = JRE + 开发工具集
- JRE = JVM + JavaSE标准类库
配置环境变量Path
配置JAVA_HOME
HelloWorld
步骤:
- 编写java文件
- 通过javac命令对Java文件进行编译
- 通过java命令对class文件进行运行
注释
- 单行注释
- 格式: //注释
- 多行注释
- 格式: /* 注释 */
- 多行注释不可以嵌套使用
- 文档注释
- 格式: /** 注释 */
总结
对第一个java程序进行总结
-
java程序编写-编译-运行的过程
编写: 编写java文件
编译: javac命令编译java文件
运行: java命令运行字节码文件
-
在一个java源文件可以声明多个class,但是,只能最多有一个类声明为public。并且要求声明为public的类的类名必须与源文件名相同。
-
程序的入口是main方法,格式是固定的
-
输出语句:
System.out.println();先输出数据,然后换行
System.out.print();只输出数据
-
每一行执行语句都以“;”结束。
-
编译的过程:编译以后,会生成一个或多个字节码文件。字节码文件的文件名与java源文件中的类名相同。
Java的基本程序设计结构
关键字与保留字
标识符
标识符的使用
-
标识符: 自己命名的地方。比如:类名,变量名,方法名,接口名,包名
-
标识符的命名规则:
由26个英文字母大小写,0-9,_ 或 $ 组成
数字不可以开头
不可以使用关键字或保留字,但能包含关键字或保留字
java严格区分大小写,长度无限制
标识符不能包含空格
- Java中的名称命名规范:
- 包名:多单词组成时所有字母都小写: xxxyyyzzz
- 类名、接口名:多单词组成时,所有单词的首字母大写: XxxYyyZzz
- 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写: xxxYyyZzz
- 常量名:所有字母都大写。多单词时每个单词都用下划线连接: XXX_YYY_ZZZ
变量
变量的使用
- java定义变量的格式: 数据类型 变量名 = 变量值
- 说明
- 变量必须先声明,后使用
- 变量都定义在其作用域内。在作用域内有效
- 同一个作用域内,不可以声明两个同名的变量
常量
使用final关键字指示常量
数据类型
java定义的数据类型
-
变量按照数据类型来分:
基本数据类型:
整型: byte\short\int\long
浮点型: float\ double
字符型: char
布尔型: boolean
引用数据类型:
类(class)
接口(interface)
数组(array) -
变量在类中声明的位置:
成员变量 vs 局部变量
整型
byte(1字节) \ short(2字节) \ int(4字节) \ long(8字节)
byte范围: -128-127
声明long型变量,必须以"l"或"L"结尾
通常,定义整型变量时,使用int型。
浮点型
float(4字节) \ double(8字节)
浮点型: 表示带小数点的数值
float表示数值的范围比long还大
定义float类型变量时,变量要以"f"或"F"结尾
通常,定义浮点型变量时,使用double型。
字符型
char(1字符=2字节)
定义char型变量,通常使用一对’ ',内部只能写一个字符
表示方式:
- 声明一个字符
- 转义字符
- 直接使用Unicode值来表示字符型常量
布尔型
boolean
只能取两个值之一: true, false
基本数据类型之间的转换
前提: 这里讨论的是7种基本数据类型变量之间的运算,不包含boolean类型。
-
自动类型提升:
- 结论: 当数据范围小的数据类型的变量与数据范围大的数据类型的变量做运算时,结果自动提升为数据范围大的数据类型。
byte, char, short --> int --> long --> float --> double
- 特别的: 当byte, char, short 三种类型的变量做运算时,结果为int型
short s1 = 10; s1 = s1 + 1;//编译失败,等号右边是int,不能赋值给short
为了避免出现上述问题,使用自增运算符和二元运算符
s1 += 1; s1++;
-
boolean类型不能与其它数据类型运算。
-
当把任何基本数据类型的值和字符串(String)进行连接运算时(+),基本数据类型的值将自动转化为字符串(String)类型。
-
强制类型转换: 自动类型提升运算的逆运算。
-
需要使用强转符: ()
-
注意点: 强制类型转换,可能导致精度损失
进制
计算机以二进制补码的形式保存所有整数
运算符
算术运算符
赋值运算符
比较运算符
逻辑运算符
&& (短路与) & (逻辑与) | ||
位运算符
& | ^ ~ >> << >>>(无符号右移)
三元运算符
运算符优先级
单目运算符、三元运算符、 赋值运算符是从右向左运算的
控制流程
顺序结构
分支结构
if-else结构
if(条件表达式) {
执行表达式1
} else if(条件表达式) {
执行表达式2
} else if(条件表达式) {
执行表达式3
}
...
else {
执行表达式n
}
switch-case语句
- 格式:
switch(表达式) {
case 常量1:
执行语句1;
break;
case 常量2:
执行语句2;
break;
...
default:
执行语句;
break;
}
- 说明:
- 根据switch表达式中的值,依次匹配各个case中的常量,一旦匹配成功,则进入相应case中,调用执行语句。
- 当调用完执行语句之后,仍然继续向下执行其他case结构中的执行语句,遇到break关键字或switch-case结构末尾结束为止
- break, 表示跳出case结构
- switch结构中表达式,只能是如下6种数据类型: byte, short, char, int, 枚举类型, String类型
- case之后只能声明常量,不能声明范围
- default是可选的,位置是灵活的
- 凡是可以使用switch-case结构,都可以使用if-else,反之不成立。
- 既可以使用switch-case又可以使用if-else,优先使用switch-case,原因: switch-case执行效率稍高
循环结构
For循环
while循环
do-while循环
输入与输出
输入
使用Scanner类从键盘获取不同类型变量
具体实现步骤:
- 导包: import java.util.Scanner
- Scanner的实例化: Scanner scan = new Scanner(System.in);
- 调用Scanner类的相关方法(next() / nextXxx()),来获取指定类型的变量
注意:
如果输入数据类型与要求的类型不匹配时,会报异常: InputMismatchException
数组
一维数组
-
数组的理解
-
数组的相关概念
-
数组的特点
- 数组是有序排列的
- 数组属于引用数据类型,数组的元素可以是基本数据类型,也可以是引用数据类型
- 创建数组对象会在内存中开辟连续空间
- 数组长度一旦确定,就不能修改
-
数组的分类
-
一维数组的使用
-
一维数组的声明和初始化
静态初始化
动态初始化 -
如何调用数组指定位置的元素
数组的角标(或索引)从0开始的,到数组长度-1结束
-
如何获取数组的长度
属性: length
-
如何遍历数组
//fori for(int i = 0; i < arr.length; ++i) { ... } //foreach for(int i: a) { ... }
-
数组元素的默认初始化值
数组元素是整型: 0
数组元素是浮点型: 0.0
数组元素是char型: 0或’\u0000’
数组元素是boolean型: false
数组元素是引用数据类型: null -
数组的内存解析
内存的简化结构:
栈(stack): 局部变量
堆(heap): new出来结构(对象,数组)
方法区: 常量池,静态域
-
多维数组
数组中涉及的常见算法
排序算法
衡量排序算法的优劣:
- 时间复杂度:分析关键字的比较次数和记录的移动次数
- 空间复杂度:分析排序算法中需要多少辅助内存
- 稳定性:若两个记录A和B的关键字值相等,但排序后A、B的先后次序保 持不变,则称这种排序算法是稳定的。
排序算法分类:内部排序和外部排序。
- 内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排 序操作都在内存中完成。
- 外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排 序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最 常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。
Arrays工具类
常见方法
- equals
- toString
- fill
- sort
- binarySearch
- copyof
面向对象
一、学习面向对象的三条主线
- Java类及类的成员: 属性,方法,构造器,代码块,内部类
- 面向对象的三大特征: 封装,继承,多态
- 其他关键字: this, super, static, final, abstract, interface, package, import等
二、面向过程与面向对象
-
面向过程: 强调的是功能行为,以函数为最小单位,考虑怎么做
-
面向对象: 强调具备功能的对象,以类/对象为最小单位,考虑谁来做
三、面向对象的两个要素:
类: 对一类事物的描述,是抽象的,概念上的定义
对象: 是实际存在的该类事物的每个个体,因此也成为实例(instance)
一、设计类,其实就是设计类的成员
属性 = 成员变量 = field = 域, 字段
方法 = 成员方法 = 函数 = method
二、类和对象的使用
1. 创建类,设计类的成员
2. 创建类的对象
3. 通过"对象.属性"或"对象.方法"调用对象的结构
Java类及类的成员
方法
类中方法的声明和使用
-
方法的声明: 权限修饰符 返回值类型 方法名(形参列表){
方法体
} -
说明
3.1 权限修饰符
3.2 返回值类型 -
方法的使用中,可以调用当前类的属性或方法
特殊的,方法A又调用方法A: 递归调用
方法中,不可以定义方法
可变个数形参的方法
- 使用
2.1 可变个数形参格式: 数据类型… 变量名
2.2 当调用可变个数形参的方法时,传入参数个数为0, 1, 2, …
2.3 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
2.4 可变个数形参的方法与本类中方法名相同,形参相同的方法之间不构成重载
2.5 可变个数形参在方法的形参中,必须声明在末尾
2.6 可变个数形参在方法的形参中,最多只能声明一个可变形参
方法的重载(overload)
-
定义: 在同一类中,允许存在一个以上同名方法,只要参数个数或者参数类型不同即可
“两同一不同”: 同一类,相同方法名
参数列表不同,参数个数不同,参数类型不同 -
判断是否是重载
跟方法的权限修饰符,返回值类型,形参变量名,方法体没有关系 -
再通过对象调用方法时,如何确定某一指定的方法
- 方法名
- 参数列表
方法的形参的传递机制: 值传递
-
形参: 方法定义时,声明的小括号内的参数
实参: 方法调用时,实际传递给形参的数据 -
值传递机制:
如果实参是基本数据类型,此时实参赋给形参的是数据值
如果实参是引用数据类型,此时实参赋给形参的是地址值
关于变量的赋值
如果变量是基本数据类型,此时赋值的是变量所保存的数据值
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值
属性
总结:属性赋值的先后顺序
- 默认初始化
- 显式初始化
- 构造器中赋值
- 通过”对象.方法“或”对象.属性“的方式赋值
以上操作的先后顺序:1 - 2 - 3 -4
构造器
类的结构之三:构造器的使用
一、构造器的作用
1. 创建对象
2. 初始化对象的属性
二、说明
1. 如果没有显式的定义类的构造器,系统会默认提供一个空参的构造器
2. 定义构造器的格式:权限修饰符 类名(形参列表) {}
3. 一个类中定义的多个构造器,彼此构成重载
4. 一旦显式的定义类的构造器,系统不提供默认的空参构造器
5. 一个类至少有一个构造器
6. 默认构造器的权限与类的权限一致
代码块
-
代码块的作用:用来初始化类,对象
-
代码块如果有修饰的话,只能使用static
-
分类:静态代码块 vs 非静态代码块
-
静态代码块
内部可以有输出语句
随着类的加载而执行,而且只执行一次
作用:初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性、方法,不能调用非静态结构 -
非静态代码块
内部可以有输出语句
随着对象的创建而执行
每创建一个对象,就执行一次非静态代码块
作用:可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
非静态代码块内可以调用静态的属性、方法,或非静态的属性、方法
对属性可以赋值的位置:
- 默认初始化
- 显式初始化 / 5. 代码块中赋值
- 构造器初始化
- 有对象后,通过“对象.属性”或“对象.方法”进行赋值
执行的先后顺序: 1 - 2 / 5 - 3 - 4
2和5先写先执行
内部类
-
java允许将一个类A声明在另一个类B,则类A是内部类,类B是外部类
-
内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
-
成员内部类:
-
作为外部类的成员
调用外部类的结构
可以被static修饰
可以被4种不同的权限修饰 -
作为一个类:
类内可以定义属性、方法、构造器等
可以被final修饰,表示此类不能被继承
可以被abstract修饰
-
-
关注如下3个问题
4.1 如何实例化成员内部类对象//创建静态Dog实例(静态的成员内部类) Person.Dog dog = new Person.Dog(); //创建非静态Bird实例(非静态的成员内部类) Person p = new Person(); Person.Bird bird = p.new Bird();
4.2 如何在成员内部类种区分调用外部类的结构
class Person { String name="小明"; class Bird { String name="杜鹃"; public void display(String name) { System.out.println(name);//方法的形参 System.out.println(this.name);//内部类的属性 System.out.println(Person.this.name);//外部类的属性 } } }
4.3 开发中局部内部类的使用
//返回一个实现Comparable接口的类的对象
public Comparable getComparable() {
//创建一个实现Comparable接口的类: 局部内部类
//方式一
// class MyComparable implements Comparable {
// @Override
// public int compareTo(Object o) {
// return 0;
// }
// }
//
// return new MyComparable();
//方式二
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
总结:
成员内部类和局部内部类,在编译后都会生成字节码文件
格式:
成员内部类:外部类$内部类名.class
局部内部类:外部类$数字 内部类名.class
面向对象特征之一: 封装
二、封装性的体现
将类的属性xxx私有化(private), 同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性
三、封装性的体现,需要权限修饰符来配合
总结封装性:Java提供了4种权限修饰符来修饰类以及类的内部结构,体现类以及类的内部结构在被调用时的可见性大小
权限修饰符
-
Java规定的4种权限(从小到大排列):private、default、protected、public
-
4种权限可以用来修饰类以及类的内部结构:属性,方法,构造器,内部类
-
具体的,4种权限都可以用来修饰类的内部结构:属性,方法,构造器,内部类
修饰类的话,只能使用:default、public
JavaBean
JavaBean是一种Java写成的可重用组件
javaBean是符合如下标准的Java类:
1. 类是公共的
2. 有一个无参的公共的构造器
3. 有属性,且有对应的get、set方法
UML类图
this关键字
this关键字的使用
1. this可以用来调用:属性、方法、构造器
2. this修饰属性和方法:
this可以理解为当前对象
2.1在类的方法或构造器中,我们可以使用”this.属性“或”this.方法“调用当前对象的属性或方法。
但是,通常情况下可以省略”this.“。如果方法的形参和类的属性同名,必须显式的使用
”this.属性“表面该变量是属性而非形参。
- this调用构造器
3.1 在类的构造器中,可以显式的使用"this(形参列表)"方式,调用类的其他构造器
3.2 构造器中不能通过调用自己
3.3 如果一个类中有n个构造器,最多有n-1个构造器使用”this(形参列表)“
3.4 规定:”this(形参列表)”必须声明在当前构造器的首行
3.5 构造器内部最多只能声明一个“this.(形参列表)”,用来调用其他构造器
package
一、package的使用
1. 为了更好的实现项目中类的管理,提供包的概念
2. 使用package声明类或接口所属的包,声明在源文件的首行
3. 包,遵循标识符的命名规则、规范(xxxyyyzzz)
4. 每“.”一次,代表一层文件目录
5. 同一包下,不能命名同名的接口、类。不同包下,可以命名同名的接口、类。
MVC设计模式
import
二、import的使用
- 在源文件中显式的使用import结构导入指定包下的类、接口
2. 声明在包的声明和类的声明之间
3. 如果需要导入多个结构,则并列写出即可
4. 可以使用"xxx."的方式,表示可以导入xxx包下的所有结构
5. 如果使用的类或接口是java.lang包下定义的,则可以省略import
6. 如果使用的类或接口是本包下定义的,则可以省略import
7. 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名显示
8. 使用“xxx.”方式表明可以调用xxx包下的所有结构,如果使用xxx子包下的结构,仍然需要导包
9. import static: 导入指定类或接口中的静态结构:属性或方法
面向对象特征之二: 继承
面向对象的特征之二:继承性
-
继承性的好处
-
继承性的格式:class A extends B {}
A: 子类
B: 父类2.1 体现:一旦子类A继承父类B,子类A就获取父类B中声明的结构:属性、方法
特别地,父类中声明为private的属性和方法,子类继承父类后,仍然获取父类中私有的结构只是因为封装性的影响,子类不能直接调用父类的结构
(从继承的概念上,private不被继承,但内存的角度,存在父类变量的一份拷贝)
2.2 子类继承父类后,还可以声明自己特有的属性和方法,实现功能的扩展。
-
Java中关于继承的规定:
- 一个类可以被多个子类继承。
- java中类的单继承性:一个类只能有一个父类
- 子父类是相对的概念
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
- 子类继承父类后,获取直接父类以及所有间接父类的属性和方法
-
Object类
- 如果没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
- 所有的Java类(除java.lang.Object)都直接或间接继承与java.lang.object类
- 意味着所有的java类具有java.lang.object类声明的功能
方法重写
-
重写:子类继承父类后,可以对父类中同名参数的方法进行覆盖操作
-
应用:重写之后,创建子对象后,通过子类对象调用子父类的同名同参数方法时,实际执行的是子类重写父类的方法。
-
重写的规定:
方法的声明:权限修饰符 返回值类型 方法名(形参列表) throws 异常类型 {
//方法体
}
3.1 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
3.2 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
> 特殊情况,子类不能重写父类中声明为private权限的方法
3.3 返回值类型:
> 父类被重写的方法的返回值类型是void,则子类重写的方法返回值类型只能是void
> 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
> 父类重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型
3.4 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
子类和父类中同名同参数的方法要么都声明非static的(考虑重写),要么都声明为static(不是重写)
super关键字
- super理解为:父类的
2. super可以用来调用:属性,方法,构造器
3. super的使用
3.1 可以在子类的方法或构造器中,通过使用“super.属性”或“super.方法”的方式显式调用
父类的属性或方法。但是通常情况下,我们习惯省略“super.”
3.2 特殊情况:当子类和父类定义同名的属性时,要想在子类中调用父类中声明的属性,则必须显式
使用“super.属性”的方式,表明调用父类的属性
3.3 特殊情况:当子类重写父类的方法后,在子类的方法中调用父类被重写的方法,必须显式
使用“super.方法”的方式,表面调用的是父类的方法
4. super调用父类构造器
4.1 我们可以在子类的构造器显式的使用“super(形参列表)”的方式,调用父类声明的构造器
4.2 “super(形参列表)”的使用,必须声明在子类构造器的首行。
4.3 我们在类的构造器中,针对“this(形参列表)”或”super(形参列表)“只能二选一,不能同时出现
4.4 在构造器的首行,没有显式的声明”this(形参列表)“或”super(形参列表)“,则默认调用的是父类的空参构造器:super()
4.5 在类的多个构造器,至少有一个类的构造器中使用”super(形参列表)“,调用父类的构造器
子类对象实例化的全过程
-
从结果上看:(继承性)
子类继承父类后,就获取父类声明的属性和方法
创建子类的对象,在堆空间中,就会加载所有父类的属性 -
从过程上看:
当我们通过子类的构造器创建子类对象时,会直接或间接调用父类的构造器,进而调用父类的父类的构造器…
直到调用java.lang.Object类中空参构造器为止,正因为加载所有父类的结构,才可以看到内存中父类的结构,
子类对象才可以进行调用
明确,虽然创建子类对象,调用父类的构造器,但是只创建过一个对象,即为new的子类对象
面向对象特征之三: 多态
-
理解多态性,可以理解为一个事物的多种形态
-
多态性:父类的引用指向子类对象(或子类的对象赋给父类的引用)
-
多态的使用:虚拟方法调用
有了对象的多态性,在编译期只能调用父类的方法,但是运行期实际执行的是子类重写父类的方法
总结:编译,看左边;运行,看右边 -
多态的使用前提:
4.1 类的继承关系
4.2 方法的重写 -
对象的多态性:只适用于方法,不适用于属性(编译和运行都看左边)
有了对象的多态性后,内存中实际上加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用
如何才能调用子类特有的属性和方法?
向下转型:使用强制类型转换符
instanceof关键字
a instanceof A: 判断a对象是否是A类的实例。如果是,返回true;否则返回false
使用情境:为了避免向下转型时出现ClassCastException的异常,在向下转型之前使用instanceof进行判断
Object类结构
equals()
== 与 equals() 区别
==: 运算符
- 可以使用在基本数据类型和引用数据类型变量中
- 如果比较的是基本数据类型变量,比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量,比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
补充:==符号使用时,必须保证符号左右两边的变量类型一致。
equals(): 方法
-
是一个方法,而非运算符
-
只能适用于引用数据类型
-
Object类中equals()的定义:
public boolean equals(Object obj) {
return (this == obj);
}
说明:Object类中定义的equals()和==作用是相同的,即两个引用是否指向同一个对象实体。 -
像String, Date, File, 包装类等都重写了Object类的equals()方法,比较的不是两个引用地址是否相同,而是比较两个对象的”实体内容“是否相同
-
通常情况下,自定义类使用equals()方法需要重写
toString()
Object类中toString()的使用:
-
当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
-
Object类的toString()的定义:
public String toString() {
return getClass().getName() + “@” + Integer.toHexString(hashCode());
} -
像String, Date, File, 包装类等都重写了Object类的toString()方法
使得在调用对象的toString()时,返回”实体内容“信息 -
自定义类也可以重写toString()方法,当调用此方法时,返回对象的”实体内容“
单元测试
步骤:
- 创建Java类
此时Java类要求:1. 此类是public 2. 此类提供公共的无参构造器
2, 此类中声明单元测试方法
此时单元测试方法,方法权限是public,没有返回值,没有形参
-
此单元测试方法需要声明注解:@Test,并在单元测试类导入:import org.junit.Test
-
声明好单元测试方法后,可以在方法体内测试相关代码
包装类
包装类的使用:
1. java提供8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
2. 掌握:基本数据类型,包装类,String三者之间的相互转换
3. 基本数据类型<->包装类: 自动装箱拆箱
4. 基本数据类型/包装类 -> String: String类的valueof方法
5. String -> 基本数据类型/包装类: 包装类的parseXxx方法
static关键字
-
static:静态
-
static可以修饰:属性,方法,代码块,内部类
-
static修饰属性:静态变量(或类变量)
3.1 属性:按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
实例变量:创建了类的多个对象,每个对象都独立的拥有一套类的实例变量
静态变量:创建类的多个对象,多个对象共享同一个静态变量
3.2 static修饰属性的其他说明:
1. 静态变量随类的加载而加载。可以通过“类.静态变量”的方式进行调用
2. 静态变量的加载早于对象的创建
3. 由于类只会加载一次,则静态变量在内存只有一份,存在方法区的静态域中 -
static修饰方法:静态方法
- 随着类的加载而加载,可以通过“类.静态方法”的方式进行调用
- 静态方法中,只能调用静态方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态方法或属性
-
static注意点:
5.1 在静态的方法内,不能使用this关键字,super关键字 -
开发中,如何确定一个属性是否要声明为static?
属性被多个对象共享,不会随着对象的不同而不同
开发中,如何确定一个方法是否要声明为static?
操作静态属性的方法,通常设置为static
工具类的方法,习惯上声明为static
理解main方法的语法
main()方法的使用说明:
1. main()方法作为程序的入口
2. main()方法也是一个普通的静态方法
final关键字
-
final可以用来修饰的结构:类、方法、变量
-
final用来修饰一个类: 此类不能被其他类继承
比如:String类、System类、StringBuffer类 -
final修饰方法:表面此方法不可以被重写
-
final修饰变量:此时变量是一个常量
4.1 final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
4.2 final修饰局部变量:
尤其是使用final修饰形参时,表面此形参是一个常量,当调用此方法时,给常量形参赋一个实参,一旦赋值后,就只能在方法体内使用此形参,不能进行重新赋值
static final
1. 用来修饰属性,全局常量
2. 用来修饰方法,不能被重写静态方法
抽象类与抽象方法
-
abstract
-
abstract可以用来修饰的机构:类、方法
-
abstract修饰类:抽象类
此类不能实例化
抽象类中一定有构造器,便于子类实例化时调用
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作抽象类可以继承抽象类、非抽象类,非抽象类可以继承抽象类、非抽象类
-
abstract修饰方法:抽象方法
抽象方法只有方法的声明,没有方法体
包含抽象方法的类,一定是抽象类;反之,抽象类可以没有抽象方法
若子类重写父类中所有的抽象方法,此子类方可实例化
若子类没有重写父类中的所有抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
abstract使用上的注意点:
-
abstract不能用来修饰:属性、构造器等结构
-
abstract不能修饰私有方法、静态方法(因为static方法可以被类直接调用,而abstract修饰的类本身没有方法体,不希望被调用而是希望被重写)、final的方法、final的类
接口
接口的使用
1. 接口的使用interface来定义
2. Java中,接口和类是并列的结构
3. 如何定义接口,定义接口中的成员
3.1 JDK7之前,只能定义全局常量和抽象方法
> 全局常量:public static final,但是可以省略
> 抽象方法:public abstract
3.2 还可以定义静态方法、默认方法
-
接口不能定义构造器,意味接口不可以实例化
5. java开发中,接口通过类实现(implements)的方式来使用
如果实现类覆盖了接口中所有的抽象方法,则此实现类可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为抽象类-
java类可以实现多个接口 —> 弥补java单继承的局限性
格式:class AA extends BB implements CC, DD, EE -
接口与接口之间可以继承,可以多继承,
类:单继承性 接口:多继承 类与接口:多实现
-
接口的具体使用,体现多态性
-
接口,实际上可以看作一种规范
-
创建接口匿名实现类的对象
-
抽象类与接口异同?
区别点 抽象类 接口
定义 包含抽象方法的类 主要是抽象方法和全局常量的集合
组成 构造方法、抽象方法、对象方法、静态方法(已实现)、常量、变量 常量、抽象方法、默认方法、静态方法
使用 子类可以继承抽象类 子类可以实现接口
关系 抽象类可以实现多个接口 接口不能继承抽象类,但允许继承多个接口
-
接口的静态方法、默认方法
JDK8: 除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
-
接口定义的静态方法,只能通过接口来调用
-
通过实现类的对象,可以调用接口中的默认方法
如果实现类重写接口的默认方法,调用时,仍然调用的是重写后的方法 -
如果子类(或实现类)继承的父类和实现的接口声明同名同参数的默认方法
那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法 -> 类优先原则 -
如果实现类实现了多个接口,而多个接口中定义了同名同参数的默认方法
那么在实现类没有重写此方法的情况下,报错 -> 接口冲突
需要我们在实现类重写此方法 -
如何在子类(或实现类)的方法中调用父类被重写的方法、接口被重写的默认方法
method3();//自己定义的重写的方法 super.method3();//调用父类中声明的方法 //调用接口中的默认方法 CompareA.super.method3(); CompareB.super.method3();
异常
异常体系结构
java.lang.Error: 一般不编写针对性的代码进行处理。
java.lang.Exception: 可以进行异常处理
编译时异常(checked)
IOException
FileNotFoundException
运行时异常(unchecked)
NullPointerException
ArrayIndexOutOfBoundsException
ClassCastException
NumberFormatException
InputMismatchException
ArithmeticException
Error
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源
耗尽等严重情况。比如:StackOverflowError和OOM。
一般不编写针对性的代码进行处理
编译时异常和运行时异常
编译时异常:执行javac命令时,可能出现的异常
运行时异常:执行java命令时,出现的异常
java异常的处理的抓抛模型
过程一、“抛”:程序在正常执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象
关于异常对象的产生:
1. 系统自动生成异常对象
2. 手动生成一个异常对象,并抛出(throw)
过程二、“抓”,可以理解为异常的处理方式
1. try-catch-finally
2. throws
异常处理方式一:try-catch-finally的使用
try {
//可能出现异常的代码
} catch(异常类型1 变量名1) {
//处理异常的方式1
} catch(异常类型2 变量名2) {
//处理异常的方式2
}
…
finally {
//一定会执行的代码
}
说明:
- finally是可选的
- 使用try将可能出现异常的代码包装起来,在执行过程中,一旦出现异常,就会生成一个异常类的对象,根据此对象的类型,去catch中进行匹配
- 一旦try中的异常对象匹配到某个catch时,就进入catch进行异常的处理,一旦处理完成,就跳出当前的try-catch结构(在没有finally的情况),继续执行后面的代码
- catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类上面,否则报错
- 常用异常对象处理方式
- String getMessage()
- printStackTrace()
- 在try结构中声明的变量,再出了try结构后,就不能再被调用
- try-catch-finally结构可以嵌套
总结:如何看待编译时异常和运行时异常?
- 使用try-catch-finally处理编译时异常,使得程序在编译时不再报错,但是运行时仍报错。相当于使用try-catch-finally将编译时可能出现的异常,延迟到运行时出现。
- 开发中,由于运行时异常比较常见,所以通常不针对运行时异常编写try-catch-finally了。针对编译时异常,要考虑异常的处理。
finally说明
- finally是可选的
- finally中声明的是一定会被执行的代码。即使catch出现异常,try有return语句,catch有return语句等。
- 像数据库连接、输入输出流、网络编程socket等资源,JVM是不能自动回收,我们需要手动进行资源释放。此时资源释放声明在finally中。
异常处理方式二:throws + 异常类型
"throws + 异常类型"写在方法的声明处,指明方法执行时,可能会抛出的异常类型。一旦方法执行时,出现异常,仍会在异常代码处生成一个异常类对象,此对象满足throws后异常类型时,就会被抛出异常代码后续的代码,就不再执行。
对比两种处理方式
try-catch-finally:真正将异常给处理掉,throws的方式只是将异常抛给方法的调用者,并没有真正将异常处理掉。
体会两种处理方式
- 如果父类被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着子类重写方法有异常,必须使用try-catch-finally处理。
- 执行的方法中,先后又调用另外的几个方法,这几个方法是递进关系执行,建议这几个方法使用throws的方式处理,而执行的方法a可以考虑使用try-catch-finally方式进行处理。
子类重写的规则
子类重写的方法抛出的异常类型不大于父类被重写方法抛出的异常类型
手动抛出异常对象
自定义异常类
多线程
程序,进程,线程的理解
程序
是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
进程
是程序的一次执行过程,或是正在运行的一个程序。是一个动态 的过程:有它自身的产生、存在和消亡的过程。——生命周期
线程
进程可进一步细化为线程,是一个程序内部的一条执行路径。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享同一进程的结构:方法区,堆
并行与并发
并行
多个CPU同时执行多个任务。
并发
一个CPU(采用时间片)同时执行多个任务。
同步与异步
同步
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。
异步
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
创建多线程的方式
继承Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类的run() --> 将此线程执行的操作声明在run()中
- 创建Thread类的子类对象
- 通过对象调用start()
注意:
- 不能直接调用run()启动线程
- 如果再启动一个线程,需要重新创建一个线程对象,调用start()方法
实现Runnable接口
- 创建一个实现Runnable接口的类
- 实现类去实现runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类对象
- 通过Thread类对象调用start()
比较创建线程的两种方式:
开发中,优先选择实现Runnable接口的方式
- 实现的方式没有类的单继承的局限性
- 实现的方式更适合处理多个线程共享数据的情况
联系:Thread 实现 Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中
实现Callable接口
实现Callable接口的方式比实现Runnable接口的方式强大
- call()可以有返回值
- call()可以抛出异常,被外面的操作捕获,获取异常信息
- Callable是支持泛型的
Future接口
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是
否完成、获取结果等。 - FutureTask是Future接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为
Runnable被线程执行,又可以作为Future得到Callable的返回值
使用线程池
相关API: ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable
- Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable
- void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。
Thread中的常用方法
- start(): 启动当前线程,调用当前线程的run()
- run(): 要执行的操作声明在此方法
- currentThread(): 静态方法,返回当前线程
- getName(): 获取当前线程名字
- setName(): 设置线程名字
- yield()
- join(): 在线程a调用线程b的join()方法,此时线程a进入阻塞状态,直到线程b执行完,
线程a才结束阻塞状态 - stop()
- sleep(long millitime): 让当前线程睡眠指定millitime毫秒。在指定毫秒时间内,当前线程
处于阻塞状态 - isAlive(): 判断当前线程是否存活
线程通信:wait() / notify() / notifyAll() 这3个方法定义在Object类中
线程优先级
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5
2.
getPriority()
setPriority()
说明:高优先级的线程抢占低优先级线程的cpu执行权,但只是高优先级线程高概率的情况下执行。
线程的分类
- 用户线程
- 守护线程(用户线程结束,守护线程结束)
线程的生命周期
五种状态
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已 具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
线程安全问题
线程安全:在多线程同时访问一个资源时,线程间依照某种方式访问资源时,访问的结果总是能获取到正确的结果。
线程的同步
通过同步机制,来解决线程安全问题
同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明
- 操作共享数据的代码,即为需要被同步的代码
- 共享数据:多个线程共同操作的变量
- 同步监视器,即锁。任何一个类的对象,都可以充当锁
要求:多个线程必须共用同一把锁
同步方法
如果操作共享数据的代码完整的声明在一个方法中,不妨将此方法声明同步的
关于同步方法的总结:
-
同步方法仍然涉及同步监视器,只是不需要显式声明
-
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身 -
同步的方式,解决线程的安全问题。(好处)
解决同步代码时,只能有一个线程参与,其他线程等待。相当于是单线程的过程,效率低(局限性)
Lock锁(ReentrantLock)
synchronized与lock的异同?
相同:都可以解决线程安全问题
不同:synchronized在执行完同步代码后,自动释放锁
Lock需要手动启动,结束同步也需要手动释放
死锁问题
线程的通信
三个方法
wait(): 执行此方法,当前线程就进入阻塞状态,并释放锁
notify(): 执行此方法,就会唤醒被wait的一个线程
notifyAll(): 执行此方法,就会唤醒被wait的所有线程
说明
- wait(), notify(), notifyAll()三个方法必须使用在同步代码块或同步方法中
- wait(), notify(), notifyAll()三个方法调用者必须是同步代码块或同步方法的锁对象
- wait(), notify(), notifyAll()定义在Object类
sleep()和wait()异同
相同点:一旦执行方法,都使得当前线程进入阻塞状态
不同点:
- 两个方法声明位置不同:Thread类声明sleep(),Object类声明wait()
- 调用要求不同:sleep()可以在任何地方调用,wait()必须在同步代码块或同步方法调用
- 关于是否释放锁:sleep不会释放锁,wait会释放锁
常用类
字符串相关的类
String
概述
String: 字符串,使用一对""引起来表示
- String声明为final,不能被继承
- String实现Serializable接口:表示字符串支持序列化
实现Comparable接口:表示String可以比较大小 - String内部定义final char[] value用于存储字符串数据
- 通过字面量的方式(区别于new)给一个字符串赋值,此时字符串值声明在字符串常量池中
- 字符串常量池不会存储相同内容的字符串
String的不可变性
String实例化
方式一:通过字面量定义的方式
方式二:通过new + 构造器的方式
//此时s1和s2的数据在方法区的字符串常量池中
String s1 = "javaEE";
String s2 = "javaEE";
//此时s3和s4的数据是在堆空间中
String s3 = new String("javaEE");
String s4 = new String("javaEE");
面试题:String s = new String(“abc”);创建几个对象?
两个,堆中创建string对象,字符串常量池创建char[]对象
字符串拼接方式对比
- 常量与常量的拼接结果在常量池,且常量池不会存在相同内容的常量。
- 只要其中有一个是变量,结果就在堆中
- 如果拼接结果调用intern()方法,返回值在常量池中
常用方法
int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从
beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex)
String与基本数据类型、包装类之间的转换
String -> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类 -> String:调用String重载的valueOf(xxx)
String与char[]之间的转换
String -> char[]: 调用String的toCharArray()
char[] -> String: 调用String的构造器
String与byte[]之间的转换
编码:String -> byte[]: 调用String的getBytes()
解码:byte[] -> String: 调用String的构造器
说明:解码时,要求解码使用的字符集必须和编码使用的字符集一致
String与StringBuffer, StringBuilder之间的转换
String -> StringBuffer, StringBuilder: 调用StringBuffer, StringBuilder构造器
StringBuffer, StringBuilder -> String:
- 调用String构造器
- StringBuffer, StringBuilder的toString()
StringBuffer, StringBuilder
String, StringBuffer, StringBuilder三者对比
String: 不可变的字符序列;底层使用char[]存储
StringBuffer: 可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder: 可变的字符序列;线程不安全的,效率高;底层使用char[]存储
说明:
初始化: new StringBuffer() 底层创建长度16的char数组
length(): 返回字符串长度,不是char数组长度
扩容: 默认情况下,扩容为原来容量的2倍 + 2, 同时将原数组复制到新数组
StringBuffer, StringBuilder常用方法
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end)
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
日期时间API
java.lang.System类
public static long currentTimeMillis()
用来返回当前时 间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
Java比较器
-
说明:Java对象,只能进行比较:== 或 !=, 不能使用 > 或 <
要实现比较对象大小,使用两个接口中的任何一个:Comparable 或 Comparator -
Comparable接口与Comparator的使用
Comparable接口方式一旦确定,保证Comparable接口的实现类的对象在任何位置比较大小,像String,包装类等实现Comparable接口,重写compareTo方法(自然排序)
Comparator接口属于临时性的比较,重写compare(T o1, T o2)的方法,比较o1和o2的大小(定制排序)
枚举类与注解
枚举类
枚举类
类的对象只有有限个,确定的,称此类为枚举类
如何定义枚举类
使用enum关键字定义枚举类
Enum类中的常用方法(继承于 java.lang.Enum类)
values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的
枚举值。
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符
串必须是枚举类对象的“名字”。如不是,会有运行时异常:
IllegalArgumentException。
toString():返回当前枚举类对象常量的名称
注解
注解的理解
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,
程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
Annotation的使用示例
示例一:生成文档相关的注解
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告
如何自定义注解
参考 @SuppressWarnings 定义
元注解
元注解:对现有注解进行解释说明的注解
Retention
Target
Documented
Inherited
获取注解信息
通过反射
前提:此注解的元注解Retention中声明的生命周期是: RUNTIME
集合
集合框架
collection接口
list接口:有序的、可重复的数据
ArrayList, LinkedList, Vector
set接口:无序的、不可重复的数据
HashSet, LinkedHashSet, TreeSet
map接口:双列集合,用来存储一对(key - value)数据
HashMap, LinkedHashMap, TreeMap, Hashtable, Properties
Collection接口
Collection接口的方法
略
使用Collection集合存储对象,要求对象所属类满足:
向Collection接口的实现类对象添加数据obj时,要求obj所在类重写equals()
List: equals()方法
Set:
(HashSet, LinkedHashSet): equals(), hashCode()
(TreeSet):
Comparable: compareTo(Object obj)
Comparator: compare(Object o1, Object o2)
Collection集合与数组间的转换
//集合 -> 数组: toArray()
Object[] arr = coll.toArray();
for (Object o : arr) {
System.out.println(o);
}
// 数组 -> 集合: 调用Arrays类的静态方法asList()
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);
List arr1 = Arrays.asList(123, 456);
System.out.println(arr1.size());//2
List arr2 = Arrays.asList(new int[]{123, 456});//整体当成1个参数
System.out.println(arr2);//1
List arr3 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr3);//2
Iterator接口
集合元素的遍历操作,使用Iterator接口
- 内部方法:next()和hasNext()
- 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
- 内部定义remove()方法,可以在遍历时删除集合元素。此方法不同于集合直接调用remove()
List接口
存储数据的特点
ArrayList:作为List接口主要实现类,线程不安全,效率高,底层用Object[]存储
LinkedList:对于插入、删除操作,效率比ArrayList高,底层使用双向链表存储
Vector:作为List接口的古老实现类,线程安全的,效率低,底层用Object[]存储
常用方法
增: 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循环
- 普通的循环
源码分析
ArrayList的源码分析
jdk 7情况下
ArrayList list = new ArrayList();//底层创建长度是10的Object[]数组
list.add(123);
…
list.add(11);//此次添加导致底层数组容量不够,则扩容
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组的数据复制到新数组
jdk8 的变化
ArrayList list = new ArrayList();//底层数组初始化为{}
list.add(123);//第一次调用add(),底层才创建长度为10的数组
…
后续添加与扩容操作没变化
结论:udk8的ArrayList的创建懒汉式,延迟数组创建,节省内存
LinkedList的源码分析:
双向链表
ArrayList, LinkedList, Vector的异同
同:三个类都实现List接口,存储数据特点相同:有序的、可重复的数据
不同:
- ArrayList:作为List接口主要实现类,线程不安全,效率高,底层用Object[]存储
- LinkedList:对于插入、删除操作,效率比ArrayList高,底层使用双向链表存储
- Vector:作为List接口的古老实现类,线程安全的,效率低,底层用Object[]存储
Set接口
存储数据特点
set接口:无序的、不可重复的数据
HashSet:Set接口的主要实现类,线程不安全,可以存储null值
LinkedHashSet:LinkedHashSet作为HashSet的子类,可以按照添加的顺序遍历。在添加数据的同时,每个数据还维护两个引用,记录此数据的前一个数据和后一个数据。优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
TreeSet:可以按照添加对象的指定属性,进行排序
-
无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引顺序添加,而是按照数据的哈希值决定
-
不可重复性: 保证添加的元素按照equals()判断时,不能返回true,即:相同元素只能添加一个
添加元素的过程:以HashSet为例
向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值通过某种算法计算在Hashset底层数组中的存放位置(即为:索引位置),判断数组此位置
是否已有元素:
如果此位置没有其他元素,则元素a添加成功。 -->情况1
如果此位置有其他元素b,则比较元素a与元素b的hash值
如果hash值不相同,则元素a添加成功。 -->情况2
如果hash值相同,需要调用元素a的equals()方法
equals()返回true,元素a添加失败。
equals()返回false,则元素a添加成功。–>情况3
对于添加成功的情况2和情况3,元素a与已经存在指定索引位置上数据以链表方式存储
jdk8 添加方式:尾插法
HashSet底层:数组+链表
常用方法
Set接口没有额外定义新的方法,使用的都是Collection中声明的方法
存储对象所在类要求
要求:向Set(HashSet, LinkedHashSet)中添加数据,其所在的类一定要重写hashCode()和equals(),重写的hashCode()和equals()尽可能保持一致性,相等的对象必须具有相等的散列码,复写equals方法的时候一般都需要同时复写hashCode方法
TreeSet的使用
-
向TreeSet中添加的数据,要求是相同类的对象
-
两种排序方式: 自然排序 和 定制排序
-
自然排序中,比较两个对象是否相同的标准是:compareTo()返回0,不再是equals()
-
定制排序中,比较两个对象是否相同的标准是:compare()返回0,不再是equals()
Map接口
Map实现类
Map的实现类结构:
map接口:双列集合,用来存储一对(key - value)数据
HashMap:作为Map的主要实现类,线程不安全,效率高,存储null的key和value
LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历
原因:在原有的HashMap底层结构基础上,添加一对指针,指向前一个和后一个元素
对于频繁的遍历操作,此类执行效率高于HashMap
TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
底层使用红黑树,有序,查询速度比List快
Hashtable:过时,线程安全,效率低,不能存储null的key和value
Properties:常用来处理配置文件。key和value都是String类型
HashMap 的底层: 数组+链表(jdk7及之前)
数组+链表+红黑树(jdk8)
Map结构的理解
Map中的Key:无序的、不可重复的,使用Set存储所有的key --> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value --> value所在的类要重写equals()
一个键值对:key-value构成一个Entry对象
Map中的entry:无序的、不可重复的,使用Set存储所有的key
HashMap的底层实现原理
以jdk7为例说明:
HashMap map = new HashMap();
在实例化以后,底层创建了长度为16的一维数组Entry[] table
…执行多次put…
map.put(key1, value1);
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算后,得到Entry数组存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。–> 情况1
如果此位置上的数据不为空,(意味着此位置存在一个或多个数据(以链表形式存在)),比较key1和存在数据的哈希值:
如果key1的哈希值与存在数据的哈希值都不相同,此时key1-value1添加成功。–> 情况2
如果key1的哈希值与存在数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals()方法比较:
如果equals()返回false:此时key1-value1添加成功 --> 情况3
如果equals()返回true:使用value1替换value2
补充:关于情况2和情况3:此时key1-value1和原来数据以链表的方式存储
当超出临界值且要存放的位置非空时扩容,默认扩容方式:扩容为原来容量2倍,并将原数据复制过来
jdk8相较于jdk7在底层实现的不同:
1. new HashMap(): 底层没有创建长度为16的数组
2. 首次调用put()方法,底层创建长度为16的数组
3. jdk7底层结构只有:数组+链表。jdk8底层结构:数组+链表+红黑树。
当数组的某一个索引位置上的元素以链表形式存在的数据个数>8 且当前数组的长度 > 64时,此时此索引位置的所有数据改为使用红黑树存储,遍历查找效率高。(优先扩容再转树)
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量: 16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子: 0.75 (即兼顾数组利用率,又让链表结构少)
threshold:扩容的临界值,=容量填充因子:160.75 = 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
LinkedHashMap的底层实现原理(了解)
源码中:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;//能够记录添加元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
常用方法
添加: Object put(Object key,Object value)
删除: Object remove(Object key)
修改: Object put(Object key,Object value)
查询: Object get(Object key)
长度: int size()
遍历: keySet() / values() / entrySet()
TreeMap的使用
向TreeMap中添加key-value,要求key必须是同一类创建的对象
按照key进行排序:自然排序、定制排序
Collections工具类
作用:操作Collection, Map的工具类
泛型
如果实例化时,没有指明泛型的类型,默认类型是java.lang.Object类型
自定义泛型结构:泛型类、泛型接口、泛型方法
静态方法中不能使用类的泛型
静态方法的加载先于类的实例化,
也就是说类中的泛型还没有传递真正的类型参数时,
静态方法就已经加载完成。
泛型方法:在方法中出现泛型的结构,泛型参数与类的泛型参数没有关系
泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的,并非在实例化类时确定
泛型在继承方面的体现
类A是类B的父类,G < A > 和G < B > 不具备子父类关系
补充:类A是类B的父类,A < G >是B< G>父类
通配符的使用
通配符: ?
类A是类B的父类,G< A>和G< B>是没有关系,二者共同父类是:G<?>
写入数据:只能null值
有限制条件的通配符的使用
? extends Person:
G< B> B是A类及子类
? super Person:
G< B> B是A类及父类
IO流
一、流的分类
1. 操作数据单位:字节流、字符流
2. 数据的流向:输入流、输出流
3. 流的角色:节点流、处理流
二、流的体系结构
抽象基类 节点流 缓冲流(处理流的一种)
InputStream FileInputStream BufferedInputStream
OutputStream FileOutputStream BufferedOutputStream
Reader FileReader BufferedReader
Writer FileWriter BufferedWriter
结论:
- 对于文本文件(.txt, .java, .c, .cpp ), 使用字符流处理
- 对于非文本文件(.jpg, .mp3, .mp4, .avi, .doc, .ppt, …), 使用字节流处理
网络编程
网络编程的两个要素:
- IP和端口号
- 网络通信协议
IP:唯一的标识Internet上的计算机
在Java中使用InetAddress类代表Ip
端口号:正在计算机上运行的进程
要求:不同的进程有不同的端口号
端口号和IP组合得到一个网络套接字:socket
URL: 统一资源定位符,对应Internet上某一资源地址
反射
java.lang.Class类的理解
1.类的加载过程:
程序经过javac命令后,会生成一个或多个字节码文件,接着使用java命令对某个字节码文件进行解释运行。
相当于将某个字节码文件加载到内存中,此过程称为类的加载。加载到内存的类,称为运行时类,此运行时类,作为Class的一个实例。
-
换句话说,Class的实例对应一个运行时类
-
加载到内存中的运行时类,会缓存一定时间,可以通过不同方式获取此运行时类
获取Class的实例方式
方式一:调用运行时类的属性:.class
方式二:通过运行时类的对象,调用getClass()
方式三:调用Class的静态方法:forName(String classPath)
方式四:使用类的加载器:ClassLoader(了解)
Class实例可以是哪些结构的说明
(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void
Java8新特性
略