首页 / JAVA / Java基础学习笔记
Java基础学习笔记
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java基础学习笔记,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含116880字,纯文字阅读大概需要167分钟。
内容图文
系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 系列文章目录
- 前言
- 一、Java基础
- 二、基本语法
- 三、数组
- 四、面向对象
- 五、其他关键字的使用
- 六、异常处理
- 七、多线程
- 八、常用类
- 九、枚举类
- 十、注解
- 十一、集合
- 十二、泛型
- 十三、File
- 十四、IO流
前言
学习java基础部分的笔记总结
一、Java基础
1.Java语言概述
Java基础是JavaEE、大数据和安卓开发的基石
2.常用的DOS命令行指令
3.Java语言的历史及技术体系平台
3.1 Java简史
4.Java在各领域的应用
- 企业级应用
- Android平台应用开发
- 大数据开发
5.Java语言的特点
(1)健壮性
吸收了C\C++的优点,并且了C和C++中影响程序健壮性的部分(指针、内存申请与释放),提供了一个相对安全的内存管理和访问机制
(2)跨平台(Write once Run anywhere)
(3)面向对象
两个基本概念:类和对象
三大特征:封装、继承、多态
6.Java两种核心机制
(1)Java虚拟机
Write once run anywhere
(2)垃圾回收机制
在C/C++中由程序员对无用的内存进行释放,而Java中垃圾回收机制自动进行。Java由自动垃圾回收机制,还会由内存泄漏和内存溢出的问题吗?
答案是:有
7.Java语言运行机制和运行过程
8.Java语言的环境搭建
8.1什么是JDK、JRE、JVM以及他们之间的关系
JDK(JavaDevelopment Kit):java开发工具包,JDK包括JRE、开发工具(如编译工具javac.exe,打包工具jar.exe)
JRE(Java Runtime Environment):Java运行时环境,JRE包括JVM和一些核心类库
JVM(Java Virtual Machine):java虚拟机
8.2安装JDK
参照JDK8的安装与配置.pdf
8.3配置环境变量
参照JDK8的安装与配置.pdf
9.开发体验——HelloWord
步骤:
- 编写Java代码存储在以.java结尾的源文件中
- 编译:java xxx.java—>生成字节码文件
- 运行 java xxx
10.注释
- 单行注释://
- 多行注释:/…/
- 文档注释(java特有):/**。。。。。*/
文档注释的使用:注释的内容可以被JDK提供的工具javadoc所解析,生成一套以网页形式体现的该程序的说明文档。
/**
文档注释
@author lhstart
@version v1.0
这是我的第一个java程序,非常开心
*/
操作方式
11.Java API 文档
二、基本语法
1.关键字和保留字
1.1关键字
关键字中所有字母都小写
1.2保留字
现有Java版本尚未使用,但以后版本可能会作为关键字使用:goto、const
注意:自己命名标识符时要尽量避免使用这些保留字
2.标识符
凡是可以自己起名字的都可以叫做标识符
2.1标识符的命名规则
2.2标识符的命名规范
3.变量
java定义变量的格式:数据类型 变量名 = 变量值
3.1变量的分类
(1)按变量的数据类型来分:
(2)变量按照在类中声明的未知来分
成员变量
局部变量
3.2整型
byte\short\int\long
java整型常量默认值为int型,声明为long型常量须后加l
或L
3.3浮点型
float\double
java的浮点型常量默认为double型,声明为float型常量,须后加’f’或’F’.
float:单精度,尾数可以精确到7为有效数字。很多情况下,精度很难满足要求。
double:双精度,精度时float的两倍。通常采用此类型
3.4字符型
char型数据用来表示通常意义上“字符”(2字节)
字符型变量的三种表示形式:
- 字符常量是用单引号(’ ')括起来的单个字符。例如:char c1 = ‘a’;char c2 = ‘中’,char c3 = ‘9’;
- Java中还允许使用转义字符’‘来将其后的字符转变为特殊字符型常量。例如:char c3 = ‘\n’; //’\n’表示换行符
- 直接使用Unicode值来表示字符型常量:’\uXXXX’。其中XXXX代表一个十六进制整数。如:\u000a表示\n
3.5布尔型
boolean类型数据只允许取值true和false,无null。
3.6基本数据类型之间的运算规则
基本数据类型之间的运算布尔型不参与运算
(1)自动类型提升(转换)
容量小的类型自动转换为容量大的数据类型。数据类型按容量大小排列为:
byte、short、char之间不会相互转换,他们三者在计算时首先转换为int类型。
(2)强制类型转换
- 使用时要强制加上转换符:()
- 强制类型转换可能出现的问题:导致精度损失
- 整型常量默认为int型,浮点型常量默认为double型
3.7字符串类型:String
3.8进制与进制之间的转换(可暂时不看)
计算机以二进制补码的形式保存所有的整数
- 正数的源码反码和补码都相同
- 负数的补码是其反码加1
(1)二进制转十进制
(2)十进制转二进制:除2取余的逆
(3)二进制与八进制、十六进制之间的转换
4.运算符
4.1 算数运算符
对于取余运算,结果的符号与被模数符号相同。
除数不能为0,否则编译可以通过,但是运行会报错。
4.2赋值运算符
=、+=、-+、*=、/=、%=
运算的结果不会改变本身的数据类型。
例如:
short s1 = 10;
s1+=2; 不会编译失败
则 s1=s1+2; 会编译失败
4.3比较运算符(关系运算符)
比较运算符的结果都是boolean型,也就是要么是true,要么是false
4.4逻辑运算符
-
&和&&的区别
-
|和||的区别
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=20210325183838882.png
4.5位运算符
- 最高效的方式计算8*2
8<<1或2<<3
4.6三元运算符
4.程序流程控制
5.1使用Scanner从简餐获取数据
- 如何从键盘获取不同类型的变量,需要使用Scanner类
- 具体实现步骤:
- 导包:import java.util.Scanner
- Scanner的实例化:Scanner scan = new Scanner(System.in);
- 调用scanner类的相关方法,来获取指定类型的变量。
对于char型的获取,Scanner没有提供相关的方法。只能获取一个字符串。
5.2 分支语句1——if-else语句
- if语句的三种情况
(1)
if(条件表达式){
执行代码块;
}
(2)
if(条件表达式){
执行代码块1;
}else{
执行代码块2;
}
(3)
if(条件表达式1){
执行代码块1;
}else if (条件表达式2){
执行代码块2;
}
……
else{
执行代码块n;
}
5.3 分支语句2——switch-case语句
- switch-case语句结构
switch(表达式){
case 常量1:
语句1;
// break;
case 常量2:
语句2;
// break;
… …
case 常量N:
语句N;
// break;
default:
语句;
// break;
} - seitch-case语句有关规则:
5.4循环结构1——for循环
- 循环结构的四个要素:
①初始化条件
②循环条件—>必须是boolean类型
③循环体
④迭代条件 - for循环的结构
for(①;②;④){
③
} - 执行过程
①-②-③-④-②-③-④-…-②
5.5 循环结构2——while循环
- 循环结构的四个要素
①初始化条件
②循环条件—>必须是boolean类型
③循环体
④迭代条件 - while循环的结构
①
while(②){
③;
④;
} - 执行过程:①-②-③-④-②-③-④-…-②
- 注意:写while循环时千万要小心不要丢了迭代条件。一旦丢了,就可能导致死循环
5.6循环结构——do-while循环
- 循环结构的四个要素
①初始化条件
②循环条件—>必须是boolean类型
③循环体
④迭代条件 - do-while循环结构
①
do{
③
④
}while(②) - 执行过程:①-③-④-②-③-④-…②
- 说明:do-while循环至少会执行一次循环体。
5.7嵌套循环
将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for ,while ,do…while均可以作为外层循环或内层循环。
说明:
5.8break和continue关键字的使用
三、数组
1.数组的概述
(1)数组相关的概念:
数组名
元素
角标、下标、索引
数组的长度:元素的个数
(2)数组的特点:
数组是有序排列的
数组属于引用数据类型的变量。数组的元素,既可以是基本数据类型也可以是引用数据类型。
创建数组对象会在内存中开辟一整块连续的空间
数组的长度一旦确定,就不能修改。
(3)数组的分类
①按照维数:一维数组、二维数组
②按照数组元素的类型:基本数据类型的元素的数组、引用数据类型的元素的数组
2.一维数组的使用
1)一维数组的声明和初始化
声明:int[] ids;
①静态初始化:数组的初始化和数组元素的赋值操作同时进行
ids = new int[]{1001,1002,1003,1004};
②动态初始化:数组的初始化和数组元素的赋值分开进行
String[] names = new String[5]
③类型推断写法:
String[] str = {1001,1002,1003,1004};
数组一旦初始化完成,长度就确定了
(2)如何调用数组的指定位置的元素:通过角标的方式调用
数组的角标从0开始,到数组的长度-1结束
(3)如何获取数组的长度
属性:length
(4)如何遍历数组
for(int i =0;i<name.length;i++){
System.out.println(name[i]);
}
(5)数组元素的默认初始化值:
数组元素是整型:0
数组元素是浮点型:0.0
数组元素是char型:0或’\u000’,而非’0’
数组元素是boolean型:false
数组元素是引用数字类型时:null
(6)数组的内存解析
- 内存的简化结构
- 一维数组的额内存解析
3.二维数组的使用
(1)理解:对于二维数组的理解,我们可以看成是一维数组array1又作为另一个一维数组array2的元素而存在。其实,从数组底层的运行机制来看,其实没有多维数组。
(2)二维数组的声明和初始化
- 静态初始化
int[][] arr = new int[][]{{1,2,3},{4,5,6},{7.8.9}};
- 动态初始化
String[][] arr = new String[3][2];
String[][] arr = new String[3][];
(3)如何调用数组的指定位置的元素
arr[2][3];
(4)如何获取数组的长度
arr.length;
arr[0].length;
(5)数组元素的默认初始化值
(6)二维数组的内存解析
4.数组中涉及到的常见算法
(1)数组元素的赋值(杨辉三角、回型数等)
(2)求数值型数组中元素的最大值、最小值、平均数、总和等
public class ArrayTest1 {
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);
}
//遍历
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + "\t");
}
System.out.println();
//求数组元素的最大值
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 += arr[i];
}
System.out.println("总和为:" + sum);
//求数组元素的平均数
int avgValue = sum / arr.length;
System.out.println("平均数为:" + avgValue);
}
}
(3)数组的复制、反转、查找(线性查找、二分查找)
①数组的复制
- 试问如下代码是否实现数组的复制?不是
(1)创建一个名为ArrayTest的类,在main方法()中声明array1和array2两个变量,他们是int[]类型的数组、
(2)使用大括号{},把array1初始化为8个素数,2,3,5,7,11,13,17,19。
(3)显示array1的内容
(4)赋值array2变量等于array1,修改array2中的偶索引元素,使其等于索引值(如array[0]=0,array[2]=2)。打印出array1。
public class ArrayTest1 {
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);
}
//遍历
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + "\t");
}
System.out.println();
//求数组元素的最大值
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 += arr[i];
}
System.out.println("总和为:" + sum);
//求数组元素的平均数
int avgValue = sum / arr.length;
System.out.println("平均数为:" + avgValue);
}
}
思考:array1和array2是什么关系?array1和array2地址值相同,都指向了对空间的唯一的一个数组实体。所以上述代码中当array2的数组元素修改时,array1中的数组元素也会跟着修改。就相当于在桌面创建快捷方式。
拓展:修改题目,实现array2对array1数组的复制,复制之后,当array2中的数组元素修改时,array1中的数组元素不会跟着修改。相当于复制一个文件夹到桌面。
②数组的反转
//数组的反转
//方法一:
// for(int i = 0;i < arr.length / 2;i++){
// String temp = arr[i];
// arr[i] = arr[arr.length - i -1];
// arr[arr.length - i -1] = temp;
// }
//方法二:
for(int i = 0,j = arr.length - 1;i < j;i++,j--){
String temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//遍历
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + "\t");
}
③查找(搜索)
- 线性查找
String dest = "BB";
dest = "CC";
boolean isFlag = true;
for(int i = 0;i < arr.length;i++){
if(dest.equals(arr[i])){
System.out.println("找到了指定的元素,位置为:" + i);
isFlag = false;
break;
}
}
if(isFlag){
System.out.println("很遗憾,没有找到的啦!");
}
- 二分法查找
int[] arr2 = new int[]{-98,-34,2,34,54,66,79,105,210,333};
int dest1 = -34;
dest1 = 35;
int head = 0;//初始的首索引
int end = arr2.length - 1;//初始的末索引
boolean isFlag1 = true;
while(head <= end){
int middle = (head + end)/2;
if(dest1 == arr2[middle]){
System.out.println("找到了指定的元素,位置为:" + middle);
isFlag1 = false;
break;
}else if(arr2[middle] > dest1){
end = middle - 1;
}else{//arr2[middle] < dest1
head = middle + 1;
}
}
if(isFlag1){
System.out.println("很遗憾,没有找到的啦!");
}
}
}
(4)数组元素的排序算法
通常来说,排序的目的是为了快速查找
- 快排时间复杂度:O(nlogn)
- 冒泡时间复杂度:O(n^2)
- 堆排序、归并排序
冒泡排序的实现:
public class BubbleSortTest {
public static void main(String[] args) {
int[] arr = new int[]{43,32,76,-98,0,64,33,-21,32,99};
//冒泡排序
for(int i = 0;i < arr.length - 1;i++){
for(int j = 0;j < arr.length - 1 - i;j++){
if(arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + "\t");
}
}
}
5.Arrays工具类的使用
6.数组使用中的常见异常
(1)数组角标越界异常:ArrayIndexOutBoundsException
(2)空指针异常:NullPointerException
public class ArrayExceptionTest {
public static void main(String[] args) {
//数组角标越界的异常
int arr[] = new int[]{12,23,34,45,56,67};
for(int i=0;i<=arr.length;i++){ //此处数组角标越界异常
System.out.println(arr[i]);
}
System.out.println(arr[-2]);//此处数组角标越界异常
//空指针异常
//情况1:
int[] arr1 =new int[]{1,2,3};
arr1=null;
System.out.println(arr1[0]);
//情况2:
int[][] arr2 =new int[4][];
System.out.println(arr2[0]);//null
System.out.println(arr2[0][0]);
//情况3:
String[] arr3 = new String[]{"AA","BB"};
arr3[0]=null;
System.out.println(arr[30].toString());
}
}
四、面向对象
Java面向对象学习的三条主线:
1、Java类及类的成员:属性、方法、构造器、代码块、内部类
2、面向对象的三大特征:封装性、继承性、多态性、(抽象性)
3、其他关键字:this、super、static、final、abstract、interface、package、import等
1.Java类及类的成员
1.1面向对象的两个要素:类和对象
类:对一类事物的描述,是抽象的、概念上的
对象:是实际存在的该类事务的每个个体,因而也称为实例(instance)
面向对象程序设计的重点是类的设计
设计类就是设计类的成员
(1)类和对象的使用
①创建类,设计类的成员
②创建类的对象
③通过“对象.属性”或“对象.方法”调用对象的结构
(2)如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)。意味着:如果我们修改了一个对象的属性a,则不影响另外一个对象属性a的值。
(3)对象的内存解析
(4)匿名对象
①理解:我们创建对象,没有显示的赋给一个变量名。即为匿名对象
②特征:匿名对象只能调用一次
③使用:如下
public class InstanceTest {
public static void main(String[] args) {
Phone p = new Phone();
System.out.println(p);
p.sendEmail();
p.playGame();
//匿名对象
// new Phone().sendEmail();
// new Phone().playGame();
new Phone().price = 1999;
new Phone().showPrice();//0.0
//**********************************
PhoneMall mall = new PhoneMall();
// mall.show(p);
//匿名对象的使用
mall.show(new Phone());
}
}
class PhoneMall{
public void show(Phone phone){
phone.sendEmail();
phone.playGame();
}
}
class Phone{
double price;//价格
public void sendEmail(){
System.out.println("发送邮件");
}
public void playGame(){
System.out.println("玩游戏");
}
public void showPrice(){
System.out.println("手机价格为:" + price);
}
}
1.2类的成员之一——属性
1.属性(成员变量)VS 局部变量
(1)相同点:
- 定义变量的格式:数据类型 变量名 = 变量值
- 先声明,后使用
- 变量都有其对应的作用域
(2)不同点: - ①在类中声明的位置不同:
属性:直接定义在类的一对{}内
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量 - ②关于权限修饰符的不同:
属性:可以在声明属性时,指明其权限,使用权限修饰符。
常用权限修饰符:private 、public、缺省、protected---->封装性
局部变量:不可以使用权限修饰符 - ③默认初始化值的情况
属性:类的属性,根据其类型,都有默认初始化值。
整型:类的属性,根据其类型,都有默认初始化值。
浮点型(byte\short\long):0
浮点型(float\double):0.0
字符型(char):0(或’\u000’)
布尔型(boolean):false
局部变量:没有默认初始化值
意味着,我们在调用局部变量之前,一定要显示赋值
特别的,形参在调用时,我们赋值即可。 - ④在内存中加载的位置:
属性:加载到堆空间中(非static)
局部变量:加载到栈空间中
2.属性赋值的先后顺序
①默认初始化
②显示初始化
③构造器中初始化
④通过“对象.方法”或“对象.属性”的方式,赋值
⑤在代码块中赋值
以上操作的先后顺序:①-②/⑤-③-④
1.3类的成员之二——方法
1.类中方法的声明和使用
方法:描述类应该具有的功能。
比如:Math类:sqrt()\random() …
Scanner类:nextXxx() …
Arrays类:sort() \ binarySearch() \ toString() \ equals() \ …
(1)举例:
public void eat(){}
public void sleep(int hour){}
public String getName(){}
public String getNation(String nation){}
(2)方法的声明:
权限修饰符 返回值类型 方法名(形参列表){
方法体
}
注意:static、final、abstract 来修饰的方法,后面再说。
(3) 说明:
①关于权限修饰符:默认方法的权限修饰符先都使用public
Java规定的4种权限修饰符:private、public、缺省、protected -->封装性再细说
②返回值类型: 有返回值 vs 没有返回值
如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量:“return 数据”。
如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return.但是,如果使用的话,只能“return;”表示结束此方法的意思。
- 我们定义方法该不该有返回值?
① 题目要求
② 凭经验:具体问题具体分析
③方法名:属于标识符,遵循标识符的规则和规范,“见名知意”
④形参列表: 方法可以声明0个,1个,或多个形参。
格式:数据类型1 形参1,数据类型2 形参2,…
我们定义方法时,该不该定义形参?
- 题目要求
- 凭经验:具体问题具体分析
⑤方法体:方法功能的体现。
(4)return关键字的使用:
①使用范围:使用在方法体中
②作用:
- 结束方法
- 针对于有返回值类型的方法,使用"return 数据"方法返回所要的数据。
③注意点:return关键字后面不可以声明执行语句。
(5)方法的使用中,可以调用当前类的属性或方法。
特殊的:方法A中又调用了方法A:递归方法。
方法中,不可以定义方法
2.方法的重载
(1)定义:在同一个类中,允许存一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
【辅助记忆】两同一不同:同一个类、相同方法名
参数列表不同:参数个数不同,参数类型不同
(2)举例:Arrays类中重载的sort()/binarySearch()
(3)判断是否是重载:跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系
- 如下四个方法构成了重载
public void getSum(int i,int j){
System.out.println("1");
}
public void getSum(double d1,double d2){
System.out.println("2");
}
public void getSum(String s1,int i1){
System.out.println("3");
}
public void getSum(int i,String s){
System.out.println("4");
}
注意:当我们调用方法的时候,xxxxx.getSum(1,4);如果第一个方法注释掉,默认调用第二个方法,这里涉及到自动类型提升。
(4)在通过对象调用方法时,如何确定某一个指定的方法:方法名—>参数列表
3.可变个数形参
(1)jdk5.0新增方法
(2)具体使用
①可变个数形参的格式:数据类型…变量名
②当调用可变个数形参的方法时,传入的参数可以是:0个,1个,2个,。。。
③可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
④可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
- jdk5.0之前使用数组作为可变个数形参,和新增方法相比,在传参方式上有所不同,jdk5.0之前使用一个数组作为实参,jdk5.0之后只需要传入变量值作为参数即可。
- 如下两种方式是相同的
//方式一:JDK5.0之前
public class MethodArgsTest {
public static void main(String[] args) {
MethodArgsTest m1 = new MethodArgsTest();
m1.sum(new int[]{123,456,789});
m1.sum(new int[]{12});
}
public void sum(int[] num){
int sum=0;
for(int i=0;i<num.length;i++){
sum+=num[i];
}
System.out.println(sum);
}
}
//方式二:JDK5.0之后
public class MethodArgsTest {
public static void main(String[] args) {
MethodArgsTest m1 = new MethodArgsTest();
m1.sum(123,456,789);
m1.sum(12);
}
public void sum(int...num){
int sum=0;
for(int i=0;i<num.length;i++){
sum+=num[i];
}
System.out.println(sum);
}
⑤可变个数形参在方法的形参中,必须声明在末尾
public void show(String …strs,int i){}
⑥可变个数形参在方法的形参中,最多只能声明一个可变形参。
4.方法参数的值传递机制
(1)关于变量的赋值:
如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
(2)方法的形参的传递机制:值传递
①形参:方法定义时,声明的小括号内的参数。
实参:方法调用时,实际传递给形参的数据。
②值传递机制:
如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值
以交换两个数为例:代码如下:
int temp=m;
m=n;
n=temp;
封装成方法之后,发现无法交换两个数,因为值传递的是基本数据类型,实参赋给形参的是实参真实存储的数据值,代码如下:
public class ValueTransferTest1 {
public static void main(String[] args) {
int m=10;
int n=20;
System.out.println("交换前:m="+m+",n="+n); //交换前:m=10,n=20
ValueTransferTest1 v1 = new ValueTransferTest1();
v1.swap(m,n);
System.out.println("交换后:m="+m+",n="+n); //交换后:m=10,n=20
}
public static void swap(int m,int n){
int temp=m;
m=n;
n=temp;
}
}
将要交换的两个值作为属性放进类中,以引用数据类型作为参数传递,就会交换成功,代码如下:
public class ValueTransferTest2 {
public static void main(String[] args) {
Data data =new Data();
data.m=10;
data.n=20;
System.out.println("交换前:m="+data.m+",n="+data.n);//交换前:m=10,n=20
ValueTransferTest2 v2 = new ValueTransferTest2();
v2.swap(data);
System.out.println("交换后:m="+data.m+",n="+data.n);//交换后:m=20,n=10
}
public void swap(Data data){
int temp =data.m;
data.m=data.n;
data.n=temp;
}
}
class Data{
int m;
int n;
}
5.递归方法
递归方法的使用(了解)
1.递归方法:一个方法体内调用它自身。
2. 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
例1:计算1-100之间所有自然数的和
public int getSum(int n) {// 3
if (n == 1) {
return 1;
} else {
return n + getSum(n - 1);
}
}
例2:计算1-n之间所有自然数的乘积:n!
public int getSum1(int n) {
if (n == 1) {
return 1;
} else {
return n * getSum1(n - 1);
}
}
例3:已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),其中n是大于0的整数,求f(10)的值。
public int f(int n){
if(n == 0){
return 1;
}else if(n == 1){
return 4;
}else{
return 2*f(n - 1) + f(n - 2);
}
}
例4:斐波那契数列
例5:汉诺塔问题
例6:快排
6.方法的重写(Override/Overwrite)
(1)重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
(2)应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
(3)重写的规定:
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
//方法体
}
约定俗成:子类中的叫重写的方法,父类中的叫被重写的方法
① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
>特殊情况:子类不能重写父类中声明为private权限的方法
③ 返回值类型:
>父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
>父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
>父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
**********************************************************************
子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
面试题:区分方法的重载与重写
7.理解main方法的语法
main()方法的使用说明:
(1)main()方法作为程序的入口
(2)main()方法也是一个普通的静态方法
(3)main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
1.4类的成员之三——构造器
1.构造器的作用:
(1)创建对象
(2)初始化对象的信息
2.说明:
(1)如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
(2)定义构造器的格式:权限修饰符 类名(形参列表){}
(3)一个类中定义的多个构造器,彼此构成重载
(4)一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
(5)一个类中,至少会有一个构造器。
1.5类的成员之四——代码块
1.代码块的作用:用来初始化类、对象
2.代码块如果有修饰的话,只能使用static.
3.分类:静态代码块 vs 非静态代码块
4.静态代码块
-
内部可以有输出语句
-
随着类的加载而执行,而且只执行一次
-
作用:初始化类的信息
-
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
-
静态代码块的执行要优先于非静态代码块的执行
-
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
5.非静态代码块 -
内部可以有输出语句
-
随着对象的创建而执行
-
每创建一个对象,就执行一次非静态代码块
-
作用:可以在创建对象时,对对象的属性等进行初始化
-
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
-
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
1.6类的成员之五——内部类
1.Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
2.内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
3.成员内部类:
一方面,作为外部类的成员:
- 调用外部类的结构
- 可以被static修饰
- 可以被4种不同的权限修饰
另一方面,作为一个类: - 类内可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
- 可以被abstract修饰
4.关注如下的3个问题
(1)如何实例化成员内部类的对象
//创建Dog实例(静态的成员内部类):
Person.Dog dog = new Person.Dog();
dog.show();
//创建Bird实例(非静态的成员内部类):
Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
(2)如何在成员内部类中区分调用外部类的结构
(3)开发中局部内部类的使用 见下面代码:
public class InnerClassTest1 {
//开发中很少见
public void method(){
//局部内部类
class AA{
}
}
//开发中常见
//返回一个实现了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;
}
};
}
}
注意:
①在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,要求此局部变量声明为final的。
jdk 7及之前版本:要求此局部变量显式的声明为final的
jdk 8及之后的版本:可以省略final的声明
②成员内部类和局部内部类在编译以后,都会生成字节码文件。
格式:
成员内部类:外部类$
内部类名.class
局部内部类:外部类$
数字 内部类名.class
2.面向对象的三大特征
2.1面向对象特征之一:封装与隐藏
1.封装性
在不使用封装之前,使用者对类的内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。例如:我们对类中的属性要通过方法的方式进行限制,规定属性的属性值范围,防止用户随便赋值,比如,动物腿的个数不应该是一个负数,我们就要对其加以限制。
(1)问题的引入:
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs())同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private). 此时,针对于属性就体现了封装性。
(2)封装性的体现:
我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
- 拓展:封装性的体现:①如上 ②不对外暴露私有的方法 ③单例模式(将构造器私有化)…
(3)封装性的体现,需要权限修饰符来配合
①Java规定的4种权限(从小到大排列):private、缺省、protected 、public
②种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
③具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
④修饰类的话,只能使用:缺省、public
总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
2.JavaBean的使用
JavaBean是一种Java语言写成的可重用组件。
所谓javaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
2.2面向对象的特征之二:继承
1.继承性的好处
① 减少了代码的冗余,提高了代码的复用性
② 便于功能的扩展
③ 为之后多态性的使用,提供了前提
2.继承性的格式:
class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
体现:
①一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
②子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。
extends:延展、扩展
3.Java中关于继承性的规定:
(1)一个类可以被多个子类继承。
(2)Java中类的单继承性:一个类只能有一个父类
(3)子父类是相对的概念。
(4)子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
(5)子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
4.其他
(1)如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
(2) 所有的java类(除java.lang.Object类之外)都直接或间接的继承java.lang.Object类
(3) 意味着,所有的java类具有java.lang.Object类声明的功能。
5.子类对象实例化的过程
(1) 从结果上来看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
(2)从过程上来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,…
直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有
父类中的结构,子类对象才可以考虑进行调用。
明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
2.3面向队形的特征之三:多态
1.多态性细节
(1)理解多态性:可以理解为一个事物的多种形态。
(2)何为多态性:
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
(3)多态的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
总结:编译,看左边;运行,看右边。
(4)多态性的使用前提: ① 类的继承关系 ② 方法的重写
(5)对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
2.向下转型
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
如何才能调用子类特有的属性和方法?
向下转型:使用强制类型转换符。
使用强转时,可能出现ClassCastException的异常。
五、其他关键字的使用
1.this的使用
1.this可以用来修饰、调用:属性、方法、构造器
2.this修饰属性和方法:this理解为:当前对象 或 当前正在创建的对象
(1)在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
(2)在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
3. this调用构造器的细节说明
(1)我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
(2)构造器中不能通过"this(形参列表)“方式调用自己
(3)如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)”
(4)规定:"this(形参列表)“必须声明在当前构造器的首行
(5)构造器内部,最多只能声明一个"this(形参列表)”,用来调用其他的构造器
2.package的使用
1.为了更好的实现项目中类的管理,提供包的概念
2.使用package声明类或接口所属的包,声明在源文件的首行
3.包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”
4.每"."一次,就代表一层文件目录。
补充:
①同一个包下,不能命名同名的接口、类。
②不同的包下,可以命名同名的接口、类。
3.import的使用
import:导入
1. 在源文件中显式的使用import结构导入指定包下的类、接口
2. 声明在包的声明和类的声明之间
3. 如果需要导入多个结构,则并列写出即可
4. 可以使用"xxx."的方式,表示可以导入xxx包下的所有结构
5. 如果使用的类或接口是java.lang包下定义的,则可以省略import结构
6. 如果使用的类或接口是本包下定义的,则可以省略import结构
7. 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。
8. 使用"xxx."方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
9. import static:导入指定类或接口中的静态结构:属性或方法。
4.super的使用
1.super理解为:父类的
2.super可以用来调用:属性、方法、构造器
3.super的使用:调用属性和方法
①我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
②特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
③特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的
④使用"super.方法"的方式,表明调用的是父类中被重写的方法。
4.super调用构造器
①我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
②"super(形参列表)"的使用,必须声明在子类构造器的首行!
③我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)“只能二选一,不能同时出现
④在构造器的首行,没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super()
⑤在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)”,调用父类中的构造器
5.instanceof关键字的使用
a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false
使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
如果 a instanceof A返回true,则 a instanceof B也返回true.其中,类B是类A的父类。
6.static关键字
1.static:静态的
2.static可以用来修饰:属性、方法、代码块、内部类
3.使用static修饰属性:静态变量(或类变量)
(1)属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
- 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
- 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
(2)static修饰属性的其他说明:
① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
② 静态变量的加载要早于对象的创建。
③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
④ 类变量 实例变量
类 yes no
对象 yes yes
(3)静态属性举例:System.out; Math.PI;
4.使用static修饰方法:静态方法
① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
② 静态方法 非静态方法
类 yes no
对象 yes yes
③ 静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
5.static注意点:
(1)在静态的方法内,不能使用this关键字、super关键字
(2)关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
6.开发中,如何确定一个属性是否要声明为static的?
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
- 类中的常量也常常声明为static
开发中,如何确定一个方法是否要声明为static的?
- 操作静态属性的方法,通常设置为static的
- 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
7.final关键字
final:最终的
1.final可以用来修饰的结构:类、方法、变量
2.final 用来修饰一个类:此类不能被其他类所继承
比如:String类、System类、StringBuffer类
3.final 用来修饰方法:表明此方法不可以被重写
比如:Object类中getClass();
4.final 用来修饰变量:此时的"变量"就称为是一个常量
①final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
②final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
③static final 用来修饰属性:全局常量
8.abstract关键字的使用(抽象方法、抽象类)
1.abstract:抽象的
2.abstract可以用来修饰的结构:类、方法
3. abstract修饰类:抽象类
(1)此类不能实例化
(2)抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
(3)开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
4. abstract修饰方法:抽象方法
(1)抽象方法只有方法的声明,没有方法体
(2)包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
(3)若子类重写了父类中的所有的抽象方法后,此子类方可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
5.abstract使用上的注意点:
(1)abstract不能用来修饰:属性、构造器等结构
(2)abstract不能用来修饰私有方法、静态方法、final的方法、final的类
9.interface关键字的使用(接口)
1、接口
(1)接口使用interface来定义
(2)Java中,接口和类是并列的两个结构
(3)如何定义接口:定义接口中的成员
①JDK7之前:只能定义全局常量和抽象方法
- 全局常量:public static final的
- 抽象方法:public abstract的
②除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
(4)接口中不能定义构造器!意味着接口中不可以实例化
(5)java开发中,接口通过让类去实现(implements)的方式来使用
如果实现类覆盖了接口中所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
(6)java类可以实现多个接口—>弥补了java单继承的局限性
格式:class AA extends BB implements CC,DD,EE
(7)接口与接口之间可以继承,而且是多继承
(8)接口的具体使用体现多态性
- 接口的使用上也满足多态性
- 接口,实际上就是定义了一种规范
- 开发中,体会面向对象编程
(9)接口,实际上可以看作是一种规范
2、创建接口匿名实现类的对象
Computer com = new Computer();
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//2. 创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
//3. 创建了接口的匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
com.transferData(phone);
//4. 创建了接口的匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("mp3开始工作");
}
@Override
public void stop() {
System.out.println("mp3结束工作");
}
});
3.JDK8中关于接口的新特性:
(1)JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
(2)知识点1:接口中定义的静态方法,只能通过接口来调用。
(2)知识点2:通过实现类的对象,可以调用接口中的默认方法。
如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
(3)知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。–>类优先原则
(4)知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。–>接口冲突。
这就需要我们必须在实现类中重写此方法
(5)知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
六、异常处理
1.异常概述与异常体系结构
1.异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
2.Java程序在执行过程中所发生的异常事件可分为两类:
- Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。
- Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
3.捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等
4.分类:编译时异常和运行时异常
- 运行时异常:是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。
对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。 - 编译时异常:是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。对于这类异常,如果程序不处理,可能会带来意想不到的结果。
2.常见异常
3.异常处理机制一:try-catch-finally
1.异常的处理:抓抛模型
过程一:“抛”:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。
一旦抛出对象以后,其后的代码就不再执行。
关于异常对象的产生:
① 系统自动生成的异常对象
② 手动的生成一个异常对象,并抛出(throw)
过程二:“抓”:可以理解为异常的处理方式:① try-catch-finally ② throws
2.try-catch-finally的使用
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}
....
finally{
//一定会执行的代码
}
(1)说明:
①finally是可选的。
②使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
③一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码
④catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
⑤catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
(2)常用的异常对象处理的方式:
① String getMessage()
② printStackTrace()
(3)在try结构中声明的变量,在出了try结构以后,就不能再被调用
(4)try-catch-finally结构可以嵌套
体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对于编译时异常,我们说一定要考虑异常的处理。
3.try-catch-finally中finally的使用
(1)finally是可选的
(2)finally中声明的是一定会被执行的代码。即使catch中又出现了异常,try中有return语句,catch中有return语句等情况
(3)像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动进行资源的释放。此时的资源释放,就需要声明在finally中。
4.异常处理机制二:throws
异常处理的方式二:throws + 异常类型
1."throws+异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦方法体执行时,出现异常,仍会在异常代码处生成一个异常类对象,此对象满足throws后异常类型时。异常代码后续的代码就不会执行了。
2.体会:try-catch-finally:真正的将异常给处理掉了。
throws的方式只是将异常抛给了方法的调用者。 并没有真正将异常处理掉。
3.开发中如何选择使用try-catch-finally 还是使用throws?
①如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
②执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
5.手动抛出异常
在程序执行中,除了自动抛出异常对象的情况之外,我们还可以手动的throw一个异常类对象。
【面试题】
throw和throws区别
throw表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws属于异常处理的一种方式,声明在方法的生命处。
6.用户自定义异常类
/*
* 如何自定义异常类
* 1、继承于现有的异常结构:Exception\RuntimeException
* 2、提供serialVersionUID
* 3、提供重载的构造器
* */
public class MyException extends RuntimeException{
static final long serialVersionUID = -7034897190745566939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
七、多线程
1.基本概念:程序、线程、进程
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现
2.线程的创建和使用
2.1多线程的创建方式一:继承于Thread类
1.步骤
(1)创建一个继承于Thread类的子类
(2)重写Thread类的run() --> 将此线程执行的操作声明在run()中
(3)创建Thread类的子类的对象
(4)通过此对象调用start():①启动当前线程②调用当前线程run()
2.例子:遍历100以内的所有的偶数
//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
//2. 重写Thread类的run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
t1.start();
//问题一:我们不能通过直接调用run()的方式启动线程。
// t1.run();
//问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
// t1.start();
//我们需要重新创建一个线程的对象
MyThread t2 = new MyThread();
t2.start();
//如下操作仍然是在main线程中执行的。
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
}
}
}
}
2.2创建多线程的方式二:实现Runnable接口
1.步骤:
(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() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3. 创建实现类的对象
MThread mThread = new MThread();
//4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.setName("线程1");
//5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
2.比较创建线程的两种方式。
开发中:优先选择:实现Runnable接口的方式
原因:
①实现的方式没有类的单继承性的局限性
②实现的方式更适合来处理多个线程有共享数据的情况。
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
2.3Thread类中的常用方法
(1)start():启动当前线程;调用当前线程的run()
(2)run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
(3)currentThread():静态方法,返回执行当前代码的线程
(4)getName():获取当前线程的名字
(5)setName():设置当前线程的名字
(6)yield():释放当前cpu的执行权
(7)join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
(8)stop():已过时。当执行此方法时,强制结束当前线程。
(9)sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
(10)10. isAlive():判断当前线程是否存活
2.4线程的优先级
1.线程的优先等级
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -->默认优先级
2.如何获取和设置当前线程的优先级
getPriority():获取线程的优先级
setPriority(int p):设置线程的优先级
说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
2.5线程的分类
3.线程的生命周期
4.线程的同步
1.发现问题
(1)问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
(2)问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
(3)如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
(4)在Java中,我们通过同步机制,来解决线程的安全问题。
2.线程同步的三种方式:
(1)方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
①操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
②共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
③同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
如下代码使用同步代码块解决实现Runnable接口的方式的线程安全问题:
class Window1 implements Runnable{
private int ticket = 100;
// Object obj = new Object();
// Dog dog = new Dog();
@Override
public void run() {
// Object obj = new Object();//这种方法是错误的,因为三个线程start()就相当于调用了三次run(),创建了三个obj对象,即3把锁
while(true){
synchronized (this){//此时的this:唯一的Window1的对象 //方式二:synchronized (dog) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Dog{
}
如下代码使用同步代码块解决继承Thread类的方式的线程安全问题:
说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
class Window2 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while(true){
//正确的
// synchronized (obj){
synchronized (Window2.class){//Class clazz = Window2.class,Window2.class只会加载一次
//错误的方式:this代表着t1,t2,t3三个对象
// synchronized (this){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 t1 = new Window2();
Window2 t2 = new Window2();
Window2 t3 = new Window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
(2)方式二:同步方法。
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
使用同步方法解决实现Runnable接口的线程安全问题:
class Window3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show(){//同步监视器:this
//synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
//}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
使用同步方法处理继承Thread类的方式中的线程安全问题:
class Window4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){//同步监视器:Window4.class
//private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
关于同步方法的总结:
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
(3)解决线程安全问题的方式三:Lock锁— JDK5.0新增
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2.调用锁定方法lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁方法:unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
3.面试题:synchronized 与 Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
4.优先使用顺序:
Lock 同步代码块 >(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)
5.同步的优缺点:
同步的方式,解决了线程的安全问题。—好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性
3.单例设计模式之懒汉式(线程安全)
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
//方式一:效率稍差
// synchronized (Bank.class) {
// if(instance == null){
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率更高
if(instance == null){
synchronized (Bank.class) {
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
4.线程的死锁问题
说明:
(1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
(2)我们使用同步时,要避免出现死锁。
5.线程的通信
5.1线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印
class Number implements Runnable{
private int number = 1;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
obj.notify();
if(number <= 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
1.涉及到的三个方法:
①wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
②notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
③notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
①wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
②wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
③wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
2.面试题:sleep() 和 wait()的异同?
(1)相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
(2)不同点:
①两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
②调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
③关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
5.2经典例题:生产者/消费者问题
class Clerk{
private int productCount = 0;
//生产产品
public synchronized void produceProduct() {
if(productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if(productCount > 0){
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
productCount--;
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//生产者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始生产产品.....");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{//消费者
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始消费产品.....");
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者1");
Consumer c2 = new Consumer(clerk);
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
6.JDK5.0新增线程创建方式
6.1创建线程的方式三:实现Callable接口。 — JDK 5.0新增
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
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 (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
①call()可以有返回值的。
②call()可以抛出异常,被外面的操作捕获,获取异常的信息
③Callable是支持泛型的
6.2创建线程的方式四:使用线程池
好处:
①提高响应速度(减少了创建新线程的时间)
②降低资源消耗(重复利用线程池中线程,不需要每次都创建)
③便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
八、常用类
1.Object类的使用
1.1java.lang.Object类
1.Object类是所有java类的根父类
2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
public class ObjectTest {
public static void main(String[] args) {
Order order = new Order();
System.out.println(order.getClass().getSuperclass());//class java.lang.Object
}
}
class Order{
}
3.Object类中的功能(属性、方法)就具有通用性。
属性:无
方法:equals() / toString() / getClass() /hashCode() / clone() / finalize() / wait() 、 notify()、notifyAll()
4.Object类只声明了一个空参的构造器
1.2equals()方法
1.==和equals()的区别
(1)回顾==
的使用
==
:运算符
①可以使用在基本数据类型变量和引用数据类型变量中
②如果比较的是基本数据类型:比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
③补充:==
符号使用时,必须保证符号左右两边的变量类型一致。
public class EqualsTest {
public static void main(String[] args) {
//基本数据类型
int i=10;
int j=10;
double d =10.0;
System.out.println(i==j);//true
System.out.println(i==d);//true
boolean b=true;
// System.out.println(i==b);
char c =10;
System.out.println(i==c);//true
char c1 = 'A';
char c2=65;
System.out.println(c1==c2);//true
//引用数据类型
Customer cust1 =new Customer("Tom",21);
Customer cust2 = new Customer("Tom",21);
Customer cust3 =cust2;
System.out.println(cust1 == cust2);//true
System.out.println(cust2==cust3);//false
String str1 =new String();
String str2 = new String();
System.out.println(str1==str2);//false
}
}
2.equals方法的使用
(1)是一个方法,而非运算符
(2)只能适用于引用数据类型
(3)Object类中equals()的定义:
public boolean equals(Object obj){
return (this==obj);
}
说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
(4)像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
3.重写equals()
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写.
重写的原则:比较两个对象的实体内容是否相同.
public boolean equals(Object obj) {
if(this==obj){
return true;
}
if(obj instanceof Customer){
Customer customer = (Customer)obj;
return this.age==customer.age && this.name.equals(customer.name);
}else{
return false;
}
}
2.包装类
2.1基本类型、包装类与String类间的转换
(1)基本数据类型---->包装类:调用包装类的构造器
(2)包装类---->基本数据类型:调用包装类的xxxValue
(3)jdk5.0新特性自动装箱与自动拆箱
- 自动装箱
int i1 = 10;
Integer in1 = i1;
System.out.println(in1.toString());
- 自动拆箱
int i2 = in1;
System.out.println(i2);
(4)基本数据类型、包装类---->String类型
- 方式一:连接运算
- 方式二:调用String的valueOf(Xxx xxx)
(5)String类型—>基本数据类型、包装类:包装类的parseXxx(String)方法
3.字符串相关的类
3.1String
1.String:字符串,使用一对""引起来表示。
String声明为final的,不可被继承
String实现了Serializable接口:表示字符串是支持序列化的;实现了Comparable接口:表示String可以比较大小
String内部定义了final char[] value用于存储字符串数据
2.String的不可变性
String:代表不可变的字符序列。简称:不可变性
体现:
(1)当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
(2)当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
(3)当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
3.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
字符串常量池中是不会存储相同内容的字符串的。
4.String的实例化方式
方式一:通过字面量定义的方式
通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
方式二:通过new + 构造器的方式
通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
5.面试题:
String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
答案:两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
System.out.println("***********************");
Person p1 = new Person("Tom",12);
Person p2 = new Person("Tom",12);
System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.name == p2.name);//true
p1.name = "Jerry";
System.out.println(p2.name);//Tom
6.String使用陷阱:
(1)常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
(2)只要其中有一个是变量,结果就在堆中。
(3)如果拼接的结果调用intern()方法,返回值就在常量池中
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
String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
}
7.涉及到String类与其他结构之间的转换
①String 与 byte[]之间的转换
编码:String --> byte[]:调用String的getBytes()
解码:byte[] --> String:调用String的构造器
编码:字符串 -->字节 (看得懂 —>看不懂的二进制数据)
解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 —> 看得懂)
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
②String 与基本数据类型、包装类之间的转换。
String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)
③String 与 char[]之间的转换
String --> char[]:调用String的toCharArray()
char[] --> String:调用String的构造器
8.String常用方法:
3.2StringBuffer和StringBuilder
1.String、StringBuffer、StringBuilder三者的异同?
String:不可变的字符序列;底层使用char[]存储
StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
2.源码分析:
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
//默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。
指导意义:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
3.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):返回一个从start开始到end索引结束的左闭右开区间的子字符串
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
总结:
增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length();
遍历:for() + charAt() / toString()
4.对比String、StringBuffer、StringBuilder三者的效率:
从高到低排列:StringBuilder > StringBuffer > String
4.JDK8之前的日期时间API
4.1System类中的currentTimeMillis()
public void test1(){
long time = System.currentTimeMillis();
//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
//称为时间戳
System.out.println(time);
}
4.2java.util.Date类
java.util.Date类
|- - -java.sql.Date
1.两个构造器的使用
(1)构造器一:Date():创建一个对应当前时间的Date对象
(2)构造器二:创建指定毫秒数的Date对象
//构造器一: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());
2.两个方法的使用
(1)toString():显示当前的年、月、日、时、分、秒
(2)getTime():获取当前Date对象对应的毫秒数。(时间戳)
3.java.sql.Date对应着数据库中的日期类型的变量
(1)如何实例化
//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(35235325345L);
System.out.println(date3);//1971-02-13
(2)如何将java.util.Date对象转换为java.sql.Date对象
//如何将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.sql.Date(date6.getTime());
4.3SimpleDateFormat
SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析
1.两个操作
(1)格式化:日期 —>字符串
(2)解析:格式化的逆过程,字符串 —> 日期
//格式化:日期 --->字符串
Date date = new Date();
System.out.println(date);
String format = sdf.format(date);
System.out.println(format);
//解析:格式化的逆过程,字符串 ---> 日期
String str = "19-12-18 上午11:43";
Date date1 = sdf.parse(str);
System.out.println(date1);
//*************按照指定的方式格式化和解析:调用带参的构造器*****************
// SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
String format1 = sdf1.format(date);
System.out.println(format1);//2019-02-18 11:48:27
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
//否则,抛异常
Date date2 = sdf1.parse("2020-02-18 11:48:27");
System.out.println(date2);
2.SimpleDateFormat的实例化
//实例化SimpleDateFormat:使用默认的构造器
SimpleDateFormat sdf = new SimpleDateFormat();
4.3Calendar日历类(抽象类)的使用
1.实例化
方式一:创建其子类(GregorianCalendar)的对象
方式二:调用其静态方法getInstance()----->一般都用这个
Calendar calendar = Calendar.getInstance();
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.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);
5.JDK8中新日期时间API
5.1LocalDate、LocalTime、LocalDateTime 的使用
说明:
①LocalDateTime相较于LocalDate、LocalTime,使用频率要高
②类似于Calendar
③它们的实例是不可变的对象
1.常用方法:
5.2Instant的使用
类似于 java.util.Date类
5.3DateTimeFormatter:格式化或解析日期、时间
类似于SimpleDateFormat
public void test3(){
// 方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime localDateTime = LocalDateTime.now();
String str1 = formatter.format(localDateTime);
System.out.println(localDateTime);
System.out.println(str1);//2019-02-18T15:42:18.797
//解析:字符串 -->日期
TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
System.out.println(parse);
// 方式二:
// 本地化相关的格式。如:ofLocalizedDateTime()
// FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str2 = formatter1.format(localDateTime);
System.out.println(str2);//2019年2月18日 下午03时47分16秒
// 本地化相关的格式。如:ofLocalizedDate()
// FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
//格式化
String str3 = formatter2.format(LocalDate.now());
System.out.println(str3);//2019-2-18
// 重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09
//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);
}
5.4其它API
6.Java比较器
说明:Java中的对象,正常情况下,只能进行比较:== 或 != 。不能使用 > 或 < 的。但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
如何实现?
使用两个接口中的任何一个:Comparable 或 Comparator。
1.Comparable接口与Comparator的使用的对比:
Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
Comparator接口属于临时性的比较。
6.1Comparable接口的使用举例: 自然排序
1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
3. 重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
4. 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。
在compareTo(obj)方法中指明如何排序
如下代码是下面举例使用到的测试类
public class Goods implements Comparable{
private String name;
private double price;
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
//指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从高到低排序
@Override
public int compareTo(Object o) {
// System.out.println("**************");
if(o instanceof Goods){
Goods goods = (Goods)o;
//方式一:
if(this.price > goods.price){
return 1;
}else if(this.price < goods.price){
return -1;
}else{
// return 0;
return -this.name.compareTo(goods.name);
}
//方式二:
// return Double.compare(this.price,goods.price);
}
// return 0;
throw new RuntimeException("传入的数据类型不一致!");
}
}
public void test1(){
String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
//
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
@Test
public void test2(){
Goods[] arr = new Goods[5];
arr[0] = new Goods("lenovoMouse",34);
arr[1] = new Goods("dellMouse",43);
arr[2] = new Goods("xiaomiMouse",12);
arr[3] = new Goods("huaweiMouse",65);
arr[4] = new Goods("microsoftMouse",43);
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
6.2Comparator接口的使用:定制排序
1.背景:
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序
2.重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
如果方法返回正整数,则表示o1大于o2;
如果返回0,表示相等;
返回负整数,表示o1小于o2。
@Test
public void test3(){
String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","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.println(Arrays.toString(arr));
}
@Test
public void test4(){
Goods[] arr = new Goods[6];
arr[0] = new Goods("lenovoMouse",34);
arr[1] = new Goods("dellMouse",43);
arr[2] = new Goods("xiaomiMouse",12);
arr[3] = new Goods("huaweiMouse",65);
arr[4] = new Goods("huaweiMouse",224);
arr[5] = new Goods("microsoftMouse",43);
Arrays.sort(arr, new Comparator() {
//指明商品比较大小的方式:按照产品名称从低到高排序,再按照价格从高到低排序
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Goods && o2 instanceof Goods){
Goods g1 = (Goods)o1;
Goods g2 = (Goods)o2;
if(g1.getName().equals(g2.getName())){
return -Double.compare(g1.getPrice(),g2.getPrice());
}else{
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
7.System类
8.Math类
9.BigInteger和BigDecimal
九、枚举类
枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
当需要定义一组常量时,强烈建议使用枚举类
如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
1.如何自定义枚举类
1.方式一:jdk5.0之前,自定义枚举类
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.其他诉求1:提供toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
2.方式二:jdk5.0,可以使用enum关键字定义枚举类
说明:定义的枚举类默认继承于java.lang.Enum类。
enum Season1{
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
2.如何使用关键字enum定义枚举类
上面的方式二
3.Enum类的主要方法
(1)values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
(2)valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
(3)toString():返回当前枚举类对象常量的名称,如果枚举类不重写toString()的话
4.实现接口的枚举类
1.情况一:实现接口,在enum类中实现抽象方法
interface Info{
void show();
}
//使用enum关键字枚举类
enum Season1 implements Info{
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//3.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//重写接口中的方法
@Override
public void show() {
System.out.println("这是一个季节");
}
}
2.情况二:让枚举类的对象分别实现接口中的抽象方法
interface Info{
void show();
}
//使用enum关键字枚举类
enum Season1 implements Info{
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里?");
}
},
SUMMER("夏天","夏日炎炎"){
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天","秋高气爽"){
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天","冰天雪地"){
@Override
public void show() {
System.out.println("大约在冬季");
}
};
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
// //4.其他诉求1:提供toString()
//
// @Override
// public String toString() {
// return "Season1{" +
// "seasonName='" + seasonName + '\'' +
// ", seasonDesc='" + seasonDesc + '\'' +
// '}';
// }
// @Override
// public void show() {
// System.out.println("这是一个季节");
// }
}
十、注解
1.注解(Annotation)概述
1.理解Annotation:
① jdk 5.0 新增的功能
② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
2.常见的Annotation示例
3.自定义Annotation
1.如何自定义注解:参照@SuppressWarnings定义
① 注解声明为:@interface
② 内部定义成员,通常使用value表示
③ 可以指定成员的默认值,使用default定义
④ 如果自定义注解没有成员,表明是一个标识作用。
或
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通过都会指明两个元注解:Retention、Target
4.JDK中的元注解
元注解:对现有的注解进行解释说明的注解
1.Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为)\RUNTIME
只有声明为RUNTIME生命周期的注解,才能通过反射获取。
2.Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
下面的出现的频率较低
3.Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的 Annotation 将具有继承性。
5.利用反射获取注解信息(在反射部分涉及)
—>通过反射获取注解信息 —到反射内容时系统讲解
6.JDK8中注解的新特性
jdk 8 中注解的新特性:可重复注解、类型注解
1.可重复注解:
① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
② MyAnnotation的Target和Retention等元注解与MyAnnotations相同
2.类型注解:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
十一、集合
1.Java集合框架概述
1.集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
2.数组在存储多个数据方面的特点:
①一旦初始化以后,其长度就确定了。
②数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
3.数组在存储多个数据方面的缺点:
①一旦初始化以后,其长度就不可修改。
②数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
③获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
④数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。比如:String[] arr;int[] arr1;Object[] arr2;
4.集合框架
2.Collection接口方法
结论:
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
3.Iterator迭代器接口
1.集合元素的遍历操作,使用迭代器Iterator接口
(1)内部的方法:hasNext() 和 next()
(2)集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
(3)内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove().
2.迭代器的执行原理
3.具体使用方式
4.要注意错误的使用方式
5.Iterator接口remove()方法
如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
6.增强for循环
jdk 5.0 新增了foreach循环,用于遍历集合、数组
for(集合元素的类型 局部变量 : 集合对象)
内部仍然调用了迭代器。
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
for(Object obj : coll){
System.out.println(obj);
}
练习,下面这道题尤其要注意:
4.Collection子接口一:List
4.1ArrayList的源码分析:
1.jdk 7情况下
ArrayList list = new ArrayList();
//底层创建了长度是10的Object[]数组elementData
list.add(123);
//elementData[0] = new Integer(123);
...
list.add(11);
//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
2.jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();
//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);
//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
...
后续的添加和扩容操作与jdk 7 无异。
3.小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
4.2LinkedList的源码分析
LinkedList list = new LinkedList();
//内部声明了Node类型的first和last属性,默认值为null
list.add(123);
//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
4.3Vector的源码分析
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
- 面试题:ArrayList、LinkedList、Vector三者的异同?
同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
不同:见上
4.4 List接口中的常用方法
(1)void add(int index, Object ele):在index位置插入ele元素
(2)boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
(3)Object get(int index):获取指定index位置的元素
(4)int indexOf(Object obj):返回obj在集合中首次出现的位置
(5)int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
(6)Object remove(int index):移除指定index位置的元素,并返回此元素
(7)Object set(int index, Object ele):设置指定index位置的元素为ele
(8)List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
总结:常用方法
增: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循环
③ 普通的循环
5.Collection子接口二:Set
1.Set:存储无序的、不可重复的数据
2.Set接口的框架
3.Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
4.要求:
①向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
②重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
5.1对无序性和不可重复性的理解
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
- 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
5.2HashSet添加数据的过程(可看作为源码分析)
添加元素的过程:以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 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构。
5.3 LinkedHashSet
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
5.4TreeSet
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
TreeSet底层使用红黑树结构存储数据
两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
- 一道面试题
- 答案:
[Person{name=‘CC’, id=1001}, Person{name=‘BB’, id=1002}]
[Person{name=‘CC’, id=1001}, Person{name=‘CC’, id=1001}, Person{name=‘BB’, id=1002}]
[Person{name=‘CC’, id=1001}, Person{name=‘CC’, id=1001}, Person{name=‘AA’, id=1001}, Person{name=‘BB’, id=1002}]
6.Map接口
6.1Map的实现类的结构
HashMap的底层:
数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
6.2Map结构的理解
Map中的key:无序的、不可重复的,使用Set存储所有的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value —>value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
6.3HashMap的底层实现原理?以jdk7为例说明
6.4LinkedHashMap的底层实现原理(了解
源码:
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);
}
}
6.5Map中定义的方法
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
总结:常用方法:
添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet() / values() / entrySet()
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
public void test5(){
Map map = new HashMap();
map.put("AA",123);
map.put(45,1234);
map.put("BB",56);
//遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println();
//遍历所有的value集:values()
Collection values = map.values();
for(Object obj : values){
System.out.println(obj);
}
System.out.println();
//遍历所有的key-value
//方式一:entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
//entrySet集合中的元素都是entry
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
System.out.println();
//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
while(iterator2.hasNext()){
Object key = iterator2.next();
Object value = map.get(key);
System.out.println(key + "=====" + value);
}
}
6.6TreeMap
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要按照key进行排序:自然排序 、定制排序
//自然排序
@Test
public void test1(){
TreeMap map = new TreeMap();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
}
//定制排序
@Test
public void test2(){
TreeMap map = new TreeMap(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());
}
throw new RuntimeException("输入的类型不匹配!");
}
});
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
}
6.7Properties处理属性文件
7.Collections工具类
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 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):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
//报异常:IndexOutOfBoundsException("Source does not fit in dest")
// List dest = new ArrayList();
// Collections.copy(dest,list);
//正确的:
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());//list.size();
Collections.copy(dest,list);
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程不安全,我们可以将ArrayList、HashMap转换成线程安全的。
使用sychrinizedList(List list)和sychonizedMap(Map map)
十二、泛型
1.为什么要有泛型
1.泛型的概念:所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时确定(即传入实际的类型参数,也称为类型实参)。
2.泛型的引入背景:集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。
3.为什么要有泛型:
①解决元素存储的安全性问题,好比商品、药品标签,不会弄错。
②解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。
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.println(stuScore);
}
}
2.在集合中使用泛型
1.jdk 5.0新增的特性
2.在集合中使用泛型:
① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型
③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
比如:add(E e) —>实例化以后:add(Integer e)
④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
(1)在集合中使用泛型(以ArrayList为例)
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.println(stuScore);
//
// }
//方式二:
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
int stuScore = iterator.next();
System.out.println(stuScore);
}
}
(2)在集合中使用泛型(以HashMap为例)
public void test3(){
// Map<String,Integer> map = new HashMap<String,Integer>();
//jdk7新特性:类型推断
Map<String,Integer> map = new HashMap<>();
map.put("Tom",87);
map.put("Jerry",87);
map.put("Jack",67);
// map.put(123,"ABC");
//泛型的嵌套
Set<Map.Entry<String,Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
while(iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + "----" + value);
}
}
3.自定义泛型结构
3.1自定义泛型类、接口
1.自定义泛型类举例:
泛型类:
public class Order<T> {
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT;
public Order(){
//编译不通过
// T[] arr = new T[10];
//编译通过
T[] arr = (T[]) new Object[10];
}
public Order(String orderName,int orderId,T orderT){
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
//如下的三个方法都不是泛型方法
public T getOrderT(){
return orderT;
}
public void setOrderT(T orderT){
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
//静态方法中不能使用类的泛型。静态方法不能使用泛型,泛型方法可以使用静态
// public static void show(T orderT){
// System.out.println(orderT);
// }
public void show(){
//编译不通过
// try{
// }catch(T t){
//
// }
}
//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
实例化代码:
public void test1(){
//如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型
//要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
Order order = new Order();
order.setOrderT(123);
order.setOrderT("ABC");
//建议:实例化时指明类的泛型
Order<String> order1 = new Order<String>("orderAA",1001,"order:AA");
order1.setOrderT("AA:hello");
}
2.子类继承泛型类时的情况
//【SubOrder.java】
public class SubOrder extends Order<Integer> {//SubOrder:不是泛型类
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
//实例化时,如下的代码是错误的
SubOrder<Integer> o = new SubOrder<>();
【SubOrder1.java】
public class SubOrder1<T> extends Order<T> {//SubOrder1<T>:仍然是泛型类
}
实例化代码
public void test2(){
SubOrder sub1 = new SubOrder();
//由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
sub1.setOrderT(1122);
SubOrder1<String> sub2 = new SubOrder1<>();
sub2.setOrderT("order2...");
}
注意点:
public void test3(){
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<Integer>();
//泛型不同的引用不能相互赋值。
// list1 = list2;
Person p1 = null;
Person p2 = null;
p1 = p2;
}
3.2自定义泛型方法
自定义泛型方法在上面的代码中已经定义过了,下面只进行测试
//测试泛型方法
@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.println(list);
}
4.泛型在继承上的体现
虽然类A是类B的父类,但是G 和G二者不具备子父类关系,二者是并列关系。
补充:类A是类B的父类,A
public void test1(){
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
//编译不通过
// Date date = new Date();
// str = date;
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//此时的list1和list2的类型不具有子父类关系
//编译不通过
// list1 = list2;
/*
反证法:
假设list1 = list2;
list1.add(123);导致混入非String的数据。出错。
*/
show(list1);
show1(list2);
}
public void show1(List<String> list){
}
public void show(List<Object> list){
}
@Test
public void test2(){
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
List<String> list4 = new ArrayList<>();
}
5.通配符的使用
通配符:?
类A是类B的父类,G和G是没有关系的,二者共同的父类是:G<?>
@Test
public void test3(){
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//编译通过
// print(list1);
// print(list2);
//
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
//添加(写入):对于List<?>就不能向其内部添加数据。
//除了添加null之外。
// list.add("DD");
// list.add('?');
list.add(null);
//获取(读取):允许读取数据,读取的数据类型为Object。
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
有限制条件的通配符的使用:
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
list1 = list3;
list1 = list4;
// list1 = list5;编译不通过
// list2 = list3;编译不通过
list2 = list4;
list2 = list5;
//读取数据:
list1 = list3;
Person p = list1.get(0);
//编译不通过
//Student s = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
编译不通过
// Person obj = list2.get(0);
//写入数据:
//编译不通过
// list1.add(new Student());
//编译通过
list2.add(new Person());
list2.add(new Student());
}
}
6.泛型应用场景举例
【DAO.java】:定义了操作数据库中的表的通用操作。 ORM思想(数据库中的表和Java中的类对应)
public class DAO<T> {//表的共性操作的DAO
//添加一条记录
public void add(T t){
}
//删除一条记录
public boolean remove(int index){
return false;
}
//修改一条记录
public void update(int index,T t){
}
//查询一条记录
public T getIndex(int index){
return null;
}
//查询多条记录
public List<T> getForList(int index){
return null;
}
//泛型方法
//举例:获取表中一共有多少条记录?获取最大的员工入职时间?
public <E> E getValue(){
return null;
}
}
【CustomerDAO.java】:
public class CustomerDAO extends DAO<Customer>{//只能操作某一个表的DAO
}
【StudentDAO.java】:
public class StudentDAO extends DAO<Student> {//只能操作某一个表的DAO
}
十三、File
1.File类的使用
1.File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
2.File类声明在java.io包下
3.File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
4.后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".
2.创建File实例的常用构造器
3.相对路径与绝对路径
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径
4.路径分隔符
5.常用方法
十四、IO流
1.IO流原理及流的分类
1.1IO流原理
1.2流的分类
1.3流的体系结构
2.节点流(文件流)
2.1FileReader读入数据的基本操作
1.说明
①read()的理解:返回读入的一个字符,如果达到文件末尾,返回-1
②异常的处理:为了保证流资源一定可以执行关闭操作,需要使用try-catch-finally处理
③读入的文件一定要存在,否则会报FileNotFoundException。
④read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
2.代码演示
@Test
public void testFileReader(){
FileReader fr = null;
try {
//1.实例化File类的对象,要指明操作的文件
File file = new File("hello.txt");
//2.提供具体的流
fr = new FileReader(file);
//3.数据的读入
char[] cbuf= new char[5];
int len;
while((len=fr.read(cbuf))!=-1){
//方式一:
/*for(int i=0;i<len;i++){
System.out.print(cbuf[i]);
}*/
//方式二:
String str = new String(cbuf,0,len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流的关闭操作
try {
if(fr!=null)//为避免空指针异常
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2FileWriter写出数据的操作
1.说明
①输出操作,对应的File可以不存在。并不会报异常
②File对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容
2.代码演示
@Test
public void testFileWriter() {
FileWriter fw = null;
try {
//1.提供File类的对象,指明写出到的文件
File file = new File("hello1.txt");
//2.提供FileWriter的对象,用于数据的写出
fw = new FileWriter(file,false);
//3.写出的操作
fw.write("I have a dream!\n");
fw.write("you need to have a dream!");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流资源的关闭
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.3使用FileReader和FileWriter实现文本文件的复制
说明:不能使用字符流来处理图片等字节数据
@Test
public void testFileReaderFileWriter() {
FileReader fr = null;
FileWriter fw = null;
try {
//1.创建File类的对象,指明读入和写出的文件
File srcFile = new File("hello.txt");
File destFile = new File("hello2.txt");
//不能使用字符流来处理图片等字节数据
// File srcFile = new File("爱情与友情.jpg");
// File destFile = new File("爱情与友情1.jpg");
//2.创建输入流和输出流的对象
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3.数据的读入和写出操作
char[] cbuf = new char[5];
int len;//记录每次读入到cbuf数组中的字符的个数
while((len = fr.read(cbuf)) != -1){
//每次写出len个字符
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流资源
//方式一:
// try {
// if(fw != null)
// fw.close();
// } catch (IOException e) {
// e.printStackTrace();
// }finally{
// try {
// if(fr != null)
// fr.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
//方式二:
try {
if(fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.4FileInputStream、FileOutputStream实现对图片的复制操作
1.对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
2.对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,…),使用字节流处理
@Test
public void testFileInputOutputStream() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//
File srcFile = new File("爱情与友情.jpg");
File destFile = new File("爱情与友情2.jpg");
//
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//复制的过程
byte[] buffer = new byte[5];
int len;
while((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
//
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.5指定路径下文件复制的操作
//指定路径下文件的复制
public void copyFile(String srcPath,String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//复制的过程
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
//
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
测试方法:
@Test
public void testCopyFile(){
long start = System.currentTimeMillis();
String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
String destPath = "C:\\Users\\Administrator\\Desktop\\02-视频.avi";
// String srcPath = "hello.txt";
// String destPath = "hello3.txt";
copyFile(srcPath,destPath);
long end = System.currentTimeMillis();
System.out.println("复制操作花费的时间为:" + (end - start));//618
}
3.缓冲流
1.缓冲流
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
2.作用:提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区
3.处理流,就是“套接”在已有的流的基础上。
3.1用缓冲流实现非文本文件的复制
/*
实现非文本文件的复制
*/
@Test
public void BufferedStreamTest() throws FileNotFoundException {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File("爱情与友情.jpg");
File destFile = new File("爱情与友情3.jpg");
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.复制的细节:读取、写入
byte[] buffer = new byte[10];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
// bos.flush();//刷新缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
// fos.close();
// fis.close();
}
}
3.2使用缓冲流实现文件复制的方法
public void copyFileWithBuffered(String srcPath,String destPath){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.复制的细节:读取、写入
byte[] buffer = new byte[1024];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
// fos.close();
// fis.close();
}
}
测试类:
@Test
public void testCopyFileWithBuffered(){
long start = System.currentTimeMillis();
String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
String destPath = "C:\\Users\\Administrator\\Desktop\\03-视频.avi";
copyFileWithBuffered(srcPath,destPath);
long end = System.currentTimeMillis();
System.out.println("复制操作花费的时间为:" + (end - start));//618 - 176
}
3.3使用BufferedReader和BufferedWriter实现文本文件的复制
/*
使用BufferedReader和BufferedWriter实现文本文件的复制
*/
@Test
public void testBufferedReaderBufferedWriter(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
//创建文件和相应的流
br = new BufferedReader(new FileReader(new File("dbcp.txt")));
bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
//读写操作
//方式一:使用char[]数组
// char[] cbuf = new char[1024];
// int len;
// while((len = br.read(cbuf)) != -1){
// bw.write(cbuf,0,len);
// // bw.flush();
// }
//方式二:使用String
String data;
while((data = br.readLine()) != null){
//方法一:
// bw.write(data + "\n");//data中不包含换行符
//方法二:
bw.write(data);//data中不包含换行符
bw.newLine();//提供换行的操作
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4.转换流
1.转换流:属于字符流
InputStreamReader:将一个字节的输入流转换为字符的输入流
OutputStreamWriter:将一个字符的输出流转换为字节的输出流
2.作用:
①提供字节流与字符流之间的转换。字节流中的数据都是字符时,转成字符流操作更高效。
②很多时候我们使用转换流来处理文件乱码问题。实现编码和 解码的功能。
3. 解码:字节、字节数组 —>字符数组、字符串
编码:字符数组、字符串 —> 字节、字节数组
4.1字符集
ASCII:美国标准信息交换码。 用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表用一个字节的8位表示。
GB2312:中国的中文编码表。最多两个字节编码所有字符
GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
4.2InputStreamReader的使用
实现字节的输入流到字符的输入流的转换
/*
此时处理异常的话,仍然应该使用try-catch-finally
InputStreamReader的使用,实现字节的输入流到字符的输入流的转换
*/
@Test
public void test1() throws IOException {
FileInputStream fis = new FileInputStream("dbcp.txt");
// InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集
//参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用系统默认的字符集
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
String str = new String(cbuf,0,len);
System.out.print(str);
}
isr.close();
}
4.3OutputStreamReader的使用
4.4综合使用InputStreamReader和OutputStreamWriter
@Test
public void test2() throws Exception {
//1.造文件、造流
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
//2.读写过程
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
//3.关闭资源
isr.close();
osw.close();
}
5.标准输入输出流
1.标准的输入、输出流
System.in:标准的输入流,默认从键盘输入
System.out:标准的输出流,默认从控制台输出
2.System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。
3.练习:
从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。
方法一:使用Scanner实现,调用next()返回一个字符串
方法二:使用System.in实现。System.in —> 转换流 —> BufferedReader的readLine()
public static void main(String[] args) {
BufferedReader br = null;
try {
InputStreamReader isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);
while (true) {
System.out.println("请输入字符串:");
String data = br.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
System.out.println("程序结束");
break;
}
String upperCase = data.toUpperCase();
System.out.println(upperCase);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
6.打印流
7.数据流
1.DataInputStream 和 DataOutputStream
2.作用:用于读取或写出基本数据类型的变量或字符串
3.练习:将内存中的字符串、基本数据类型的变量写出到文件中。
注意:处理异常的话,仍然应该使用try-catch-finally.
@Test
public void test3() throws IOException {
//1.
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
//2.
dos.writeUTF("刘建辰");
dos.flush();//刷新操作,将内存中的数据写入文件
dos.writeInt(23);
dos.flush();
dos.writeBoolean(true);
dos.flush();
//3.
dos.close();
}
4.将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。
注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!
@Test
public void test4() throws IOException {
//1.
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
//2.
String name = dis.readUTF();
int age = dis.readInt();
boolean isMale = dis.readBoolean();
System.out.println("name = " + name);
System.out.println("age = " + age);
System.out.println("isMale = " + isMale);
//3.
dis.close();
}
8.对象流
1.ObjectInputStream 和 ObjectOutputStream
2.作用:
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
3.对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
8.1对象流的使用
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的。为了让某个类是可序列化的该类必须实现Serializable接口。
总之:Person需要满足如下的要求方可序列化:
①需要实现接口Serializable
②当前类提供一个全局常量:serialVersionID
public static final long serialVersionID= 45615648545L
9.随机存取文件流
10.NIO2中Path、Paths、File类的使用
内容总结
以上是互联网集市为您收集整理的Java基础学习笔记全部内容,希望文章能够帮你解决Java基础学习笔记所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。