赞
踩
public class 类名{ }
类名必须与源文件名保持一致
一个源文件中至多存在一个公开类
package 包名;
作用为整理归纳字节码文件
建议三层起(com.名字缩写.xxx)
必须写在源文件有效代码第一行
一个源文件可以有0-1个package语句
\字符
将普通字符转义为特殊字符
\n 换行 \t 生成一段制表符距离
必须写进字符串
将特殊字符转义为普通字符
System.out.println("一二\t三四五,\n上山打老虎,\n老虎没打着,\n打着小松鼠."); //输出一个" System.out.println("\"");
指明某个或某些类的来源
import 包名.类名;
必须写在第一个类的上方,package语句的下方
路径必须截止至类
import java.util;//错误!
一个源文件中可以存在多个导包语句
可以通过*通配符一次性引入包下所有类
import java.util.*;
只能引入直属的类, 无法引入子包下的类
一个导包语句至多存在一个*
import java.util.*.*;//错误!
char:
A-Z: 65-90
a-z: 97-122
在满足某个条件的前提下使操作语句反复执行
for(循环初始值;循环条件;迭代语句){ //操作语句 }
//需求:让张三绕着操场跑5圈 /* i=圈数 初始值:从1开始 循环条件: 小于等于5圈继续执行 迭代语句:圈数每次+1 * */ for (int i = 1; i <= 5; i++) { //System.out.println("张三正在跑第i圈"); 错误 System.out.println("张三正在跑第"+i+"圈"); }
初始值–>判断循环条件–>为true–>执行操作语句–>执行迭代–>判断循环条件–>直到为false–>循环结束
只有当循环结束之后程序才会继续向下执行
死循环: 循环永远满足循环条件,程序无法继续向下
//计算1-100的和 //1+2+3+4+5+6+7+8+9...+100 //累加器,用来接收结果 int sum=0; for (int i = 1; i <= 100; i++) { //将i的值加给累加器 sum+=i;//0+1+2+3+..100 } System.out.println(sum);
//计算1-100之间偶数的和 2+4+6+8+10+12..+100 int sum=0;//累加器 /*for (int i = 1; i <= 100; i++) { if (i%2==0) {//判断i是否为偶数 sum += i; } }*/ for (int i = 2; i <= 100; i += 2) { sum+=i; } System.out.println(sum);
定义在方法内部的变量
作用范围:从定义行开始,到直属代码块{}结束
命名冲突:同一作用范围内局部变量之间不可重名
使当前循环停止,跳出当前所有循环
//让张三跑100圈,跑至第50圈时,停止跑圈 for (int i = 1; i <= 100; i++) { //判断是否跑至第50圈 if (i == 50) { break;//跳出循环 } System.out.println("张三正在跑第"+i+"圈"); }
使本次循环停止直接开始下一次,跳过本次循环
//让张三跑100圈,逢10跳过一圈 for (int i = 1; i <= 100; i++) { //判断是否逢10 if (i % 10 == 0) { continue;//跳过本次 } System.out.println("张三正在跑第"+i+"圈"); }
break是跳出所有循环,continue是跳出本次循环
break可以作用于switch分支,continue无法作用于分支
while(布尔表达式){//循环条件 //操作语句 }
//有一段路总长88米,小明一步0.8米,问多少步可以走完 double sum=0.0;//米数 int num=0;//累加器,表示步数 /*while(sum<88){//循环条件:米数<88 //步数+1 num++; //米数+0.8 sum+=0.8; }*/ while(true){//死循环,假定一直走 //步数+1 num++; //米数+0.8 sum+=0.8; //判断是否走到终点 if (sum >= 88) { break;//停止循环 } } System.out.println("步数:"+num);
判断循环条件–>为true–>执行操作语句–>判断循环条件–>直到为false—>循环结束
都是先判断再执行,执行次数为0-n次
for循环适用于循环次数或范围确定的场景
while循环适用于循环次数或返回不确定的场景
while和for之间的操作可以互等
for(;;){ } 相当于 while(true){ }
//while-计算1-100的和 /* int i=1;//初始值 int sum=0;//累加器 while(i<=100){//循环条件 sum+=i; //迭代语句 i++; } System.out.println(sum);*/ //for-有一段路总长88米,小明一步0.8米,问多少步可以走完 double sum=0.0;//米数 int num=0;//累加器,表示步数 for(;;){//while(true) //步数+1 num++; //米数+0.8 sum+=0.8; //判断是否走到终点 if (sum >= 88) { break;//停止循环 } } System.out.println(num);
do{ //操作语句 }while(布尔表达式);//循环条件
先执行,再判断.执行次数为1-n次
执行操作语句–>判断循环条件–>为true–>执行操作语句–>判断循环条件–>直到为false–>循环结束
//让张三跑圈,跑完一圈对其打分,分数>=80停止跑圈,否则继续跑圈 Scanner sc = new Scanner(System.in); int score=0; do{ System.out.println("张三正在跑圈..."); System.out.println("跑圈结束,请打分:"); //接收分数 score = sc.nextInt(); }while (score<80);
一个循环结构中包含另一个循环结构
执行流程: 外层循环执行一次,内层循环执行一遍
图形输出: 外层循环表示行,内层循环表示列
break和continue只能作用于直属的循环层次
只要逻辑完整,嵌套层数无限制,但是通常不超过2层 (效率过慢)
//用*输出一个长10宽3的长方形 /* ********** ********** ********** */ for (int i = 1; i <= 3; i++) {//行 for (int j = 1; j <= 10; j++) {//列 System.out.print("*"); } //输出完一行换行 System.out.println(); }
/* * 1-1 ** 2-2 *** 3-3 **** ***** * */ for (int i = 1; i <= 5; i++) {//行 for (int j = 1; j <= i; j++) {//列 System.out.print("*"); } System.out.println(); }
/* * i=1-1j *** 2-3 ***** 3-5 ******* 4-7 ********* 5-9 j=i*2-1 */ for (int i = 1; i <= 5; i++) { for (int j = 1; j <= i * 2 - 1; j++) { System.out.print("*"); } System.out.println(); }
局部变量的特点
break和continue的区别
for和while的关系
while和do-while的区别
嵌套循环的执行流程
是一段具有特定功能的代码, 特点为可以多次执行.通常情况下一个函数对应一个功能
访问修饰符 static 返回值类型 函数名(形参列表){ //操作语句 }
public static void 函数名(){ }
类以内,其他函数以外,与主函数平级
自定义函数必须经过调用才有可能执行
函数名(实参列表) 函数名()
只要在函数内,想在哪执行,就在哪调用
表示函数执行中不确定的内容,特点为值可变
形式上的参数. 特点为只有声明部分没有具体值,写在函数声明处的小括号内
实际上的参数. 特点为是一个具体值或拥有具体值结果的表达式,写在函数调用处的小括号内
public static void 函数名(数据类型 参数名){ } 调用:函数名(值|拥有结果的表达式)
有参数时,必须通过实参给形参赋值
public static void 函数名(数据类型 参数名1,数据类型 参数名2,..){ } 调用:函数名(值1,值2,..)
实参列表与形参列表必须保持一致
参数列表: 个数 顺序 数据类型
public class Test1 { public static void main(String[] args) { //需求:输出一首打油诗,要求每行下方输出做分割 System.out.println("太阳当空照"); printLine(15,"-");//15 - System.out.println("花儿对我笑"); printLine(20,"*");//20 * System.out.println("小鸟说早早早"); printLine(25,"^");//25 ^ System.out.println("你为什么背上java包"); } /** * 输出指定数量的分隔符 printLine(int,String) * @param num 数量 * @param str 分隔符内容 */ public static void printLine(int num,String str){//int num=15 for (int i = 1; i <= num; i++) { System.out.print(str); } System.out.println(); } }
表示函数的执行结果
public static 返回值类型 函数名(形参列表){ //操作语句 return 值; }
作用: 向上返回 返回值
上:调用者
void表示无返回值
如果声明了返回值,则在函数内部必须通过return关键字将值向上返回
调用有返回值的函数时,必须对返回值做出处理
先定义变量接收返回值,其后再操作变量
数据类型 变量名=函数名(实参列表);
变量类型由函数声明处的返回值类型决定
直接操作函数调用结果
public static void main(String[] args) { //接收调用结果 int sum=add(1, 2); System.out.println(sum % 2 == 0 ? "偶数" : "不是偶数"); System.out.println(sum *10); //直接操作调用结果 System.out.println(add(2, 4) % 2 == 0 ? "偶数" : "不是偶数"); System.out.println(add(2, 4) *10); } //定义一个函数,传入整型参数a和b,计算参数之和. 要求在主函数中调用测试并判断和是否为偶数 public static int add(int a,int b){ //System.out.println(a+b); return a+b; }
必须保证每种分支情况都有可被执行的return语句
函数内存在分支时建议的书写套路:
在函数最上方定义用来返回的变量并赋初始值
在操作过程中根据情况更改变量的值
在函数最下方return该变量
执行流程:
同一作用范围内,return语句的下方不能存在其他有效语句
无法执行
扩展: 可以直接使用return;
强制截止当前程序的执行
嵌套调用时调用顺序与向上返回顺序相反
最先被调用的最后执行结束,最后调用的最先执行结束
栈的特点:永远操作栈顶元素. 存从栈顶存,取从栈顶取
每调用一次函数都会生成一条对应的栈帧, 函数的执行流程与栈的执行特点一致
函数的完整语法
形参和实参的区别
return的使用
参数列表都有哪些内容
是计算机内存中的一块连续的存储空间,特点为可以同时存放多个相同数据类型的值
先声明,后指明长度
数据类型[] 数组名; 数组名=new 数据类型[长度];
数据类型[] 数组名;
数据类型 []数组名;
数据类型 数组名[];
创建的时候必须指明长度, 方便内存分配空间
长度必须为正整型
声明的同时直接指明长度
数据类型[] 数组名=new 数据类型[长度];
创建的同时直接赋值
数据类型[] 数组名=new 数据类型[]{值1,值2,..};
数组长度由值的个数决定
中括号指明长度不可与大括号赋值同时存在
创建的同时直接赋值
数据类型[] 数组名={值1,值2,..};
无法先声明后大括号直接赋值
//先声明 int[] arr5; //后直接大括号赋值 arr5 = new int[]{10, 20, 30}; //arr5 ={10, 20, 30}; 错误
//先声明,后指明长度 int[] arr1; arr1 = new int[3]; //声明的同时直接指明长度 int[] arr2 = new int[3]; //创建的同时直接赋值 int arr3[] = new int[]{20, 23, 21}; int[] arr4 = {20, 21, 22};
必须通过下标操作数组元素
从0开始,至数组长度前一位结束
使用:
取值: 数组名[下标] 赋值: 数组名[下标]=值;
当下标使用超出界限时, 运行时会报出数组下标越界异常
java.lang.ArrayIndexOutOfBoundsException
可以通过数组名.length
获取数组长度
for(int i=0;i<数组名.length;i++){ //数组名[i]就代表当前正在被遍历的元素 }
//创建一个长度为3的数组 int[] arr = new int[3]; //赋值 arr[0] = 10; arr[1] = 20; arr[2] = 30; for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); }
属于引用类型
内存存放:
引用中存放着对应的堆地址
程序员在操作时必须先进入栈,再由栈中存储的堆地址进入堆操作数据
引用类型之间相互赋值传递的是堆地址
int[] arr1 = {10, 20, 30, 40, 50}; int[] arr2 = arr1; arr2[0] = 66; int[] arr3 = arr1; arr3 = new int[]{11, 22, 33}; System.out.print("arr1: "); for (int i = 0; i < arr1.length; i++) { System.out.print(arr1[i]+"\t"); }// 66 20 30 40 50 System.out.println(); System.out.print("arr2: "); for (int i = 0; i < arr2.length; i++) { System.out.print(arr2[i]+"\t"); }// 66 20 30 40 50 System.out.println(); System.out.print("arr3: "); for (int i = 0; i < arr3.length; i++) { System.out.print(arr3[i]+"\t"); }//11 22 33
逢new必开: 只要执行到new关键字,就一定会有新的堆空间被开辟创建
数组存在默认值,作用为方便内存分配空间
默认值:编译器赋予
初始值:程序员第一次手动赋值
int: 0
double: 0.0
String: null (所有引用类型都是null)
boolean: false
创建一个长度更大的数组,推荐扩容两倍
将原数组的数据复制到新数组中
将原数组的地址转换为新数组地址
int[] a = {10, 20, 30, 40, 50}; //创建一个长度为2倍的数组 int[] newA = new int[a.length * 2];//{0, 0, 0, 0, 0,0,0,0,0,0} //{10,20,30,40,50,0,0,0,0,0} //将原数组数据复制到新数组中 for (int i = 0; i < a.length; i++) { newA[i]=a[i];//newA[1]=a[1] } //将原数组的地址转换为新数组地址 a = newA;
System.arraycopy(原数组名,原数组复制起始下标,新数组名,新数组存放起始下标,复制长度)
int[] a = {10, 20, 30, 40, 50}; //创建一个长度为2倍的数组 int[] newA = new int[a.length * 2];//{0, 0, 0, 0, 0,0,0,0,0,0} //{10,20,30,40,50,0,0,0,0,0} //将原数组数据复制到新数组中 System.arraycopy(a,0,newA,0,a.length); //将原数组的地址转换为新数组地址 a = newA;
新数组地址 java.util.Arrays.copyOf(原数组名,预期的数组长度)
int[] a = {10, 20, 30, 40, 50}; //对a进行扩容并将返回的新数组地址赋值给原数组 a=Arrays.copyOf(a, a.length * 2);
思路:让相邻两个位置的值进行比较,根据比较结果决定是否换位
特点:每轮比较之后,都会从后往前确定一个位置的值
实现:外层循环代表比较轮数,内层循环代表比较次数
int[] a = {3, 17, 88, 66, 99}; /* 从大到小 第1轮: 第1次(0-1):17 3 88 66 99 第2次(1-2):17 88 3 66 99 第3次(2-3):17 88 66 3 99 第4次(3-4):17 88 66 99 3 第2轮: 第1次(0-1):88 17 66 99 3 第2次(1-2):88 66 17 99 3 第3次(2-3):88 66 99 17 3 第3轮: 第1次(0-1):88 66 99 17 3 第2次(1-2):88 99 66 17 3 第4轮: 第1次(0-1):99 88 66 17 3 * */ for (int i = 1; i < a.length; i++) {//轮数 for (int j = 0; j < a.length-i; j++) {//次数 j=下标 //让当前的a[j]与a[j+1]进行值的比较 //从大到小 if (a[j + 1] < a[j]) {//从小到大:替换为<即可 int temp = a[j + 1]; a[j + 1] = a[j]; a[j] = temp; } } }
思路: 固定一个下标位置,让其他位置与该位置的值进行比较,根据比较结果决定是否换位
特点:每轮比较结束之后,固定下标位置的值能被确定
实现:外层循环表示固定下标,内层循环表示与其比较的下标
int[] a = {3, 17, 88, 66, 99}; /* 从大到小 固定下标0: 下标1: 17 3 88 66 99 下标2: 88 3 17 66 99 下标3: 88 3 17 66 99 下标4: 99 3 17 66 88 固定下标1: 下标2: 99 17 3 66 88 下标3: 99 66 3 17 88 下标4: 99 88 3 17 66 固定下标2: 下标3:99 88 17 3 66 下标4:99 88 66 3 17 固定下标3: 下标4: 99 88 66 17 3 * */ for (int i = 0; i < a.length - 1; i++) {//固定下标的范围 for (int j = i + 1; j < a.length; j++) {//与其比较的下标 if (a[j] > a[i]) {//从大到小:> 从小到大:< int temp = a[j]; a[j] = a[i]; a[i] = temp; } } }
java.util.Arrays.sort(数组名)
int[] a = {3, 17, 88, 66, 99}; Arrays.sort(a);
数组的创建语法
下标的使用
数组的数据类型
引用类型之间相互赋值的特点
数组扩容的步骤
冒泡排序与选择排序的思路特点+JDK排序的写法
从java的角度: “万物皆对象”.对象是一个操作单位, 所有内容都可以被认定为是一个对象(一个操作单位)
从程序的角度: 对象是内存中的一块存储空间, 存放着现实生活中对象特征和行为的具体信息
特征: 都有什么
行为: 会做什么
is a: 一个对象继承自另一个对象
狗是一个动物 Dog is a Animal
猫是一个动物 Cat is a Animal
has a: 一个对象是另一个对象的组成部分
键盘是一个对象
屏幕是一个对象
电脑是一个对象
电脑包含键盘和屏幕
use a: 一个对象使用另一个对象
电脑是一个对象
程序员是一个对象
程序员使用电脑
存放对同一批对象相同的特征和行为的描述,约束同一批对象所具有的内容
测试类: 特点为拥有主函数,可以直接执行
描述型的类:特点为没有主函数,无法执行, 作用为描述对象特征和行为
类是对象的模板
对象是类的实例
一个模板可以创建多个相同相似的实例
属性: 描述对象特征. 也称为全局变量|成员属性|成员变量等
1. 数据类型 属性名=值; 2. 数据类型 属性名; 更常用
测试类: com.xxx.test
描述类: com.xxx.entity
位置: 类以内,其他内容以外, 通常写在类的最上方
特点: 有默认值,作用为空间占位,保证会分配空间
属性和局部变量的区别:
局部变量 | 属性 | |
---|---|---|
位置 | 方法内部 | 方法外部 |
默认值 | 没有 | 有 |
作用范围 | 定义行开始至直属代码块结束 | 至少整个类 |
命名冲突 | 同一作用范围内不可重名 | 可以与局部变量重名,局部变量优先级更高 |
方法 : 描述对象行为. 也称为成员方法|普通方法等
函数就是加了static的方法
访问修饰符 返回值类型 方法名(形参列表){ //操作语句 //return 语句; }
位置: 类以内,方法以外,和属性平级
package com.by.entity; public class Student { String name; int age; String sex; double score; // score=90.0; 错误!! //吃饭 public void eat(){ System.out.println("吃饭"); } //睡觉 public void sleep(){ System.out.println("睡觉"); } //学习 public void study(){ System.out.println("学习"); } }
类名 对象名=new 类名();
使用属性
取值: 对象名.属性名; 赋值: 对象名.属性名=值;
使用方法
对象名.方法名(实参列表);
//创建一个学生对象 Student stu1 = new Student(); Student stu2 = new Student(); //给stu1对象属性赋值 stu1.name = "zhangsan"; stu1.age = 20; stu1.sex = "男"; stu1.score = 90; //获取stu1对象属性的值 System.out.println(stu1.name); System.out.println(stu1.age); System.out.println(stu1.sex); System.out.println(stu1.score); //获取查看stu2属性的值 System.out.println(stu2.name); System.out.println(stu2.age); System.out.println(stu2.sex); System.out.println(stu2.score); //调用stu1中的方法 stu1.eat(); stu1.sleep(); stu1.study();
特殊的方法,作用为创建对象
无参构造
访问修饰符 类名(){ }
有参构造
访问修饰符 类名(数据类型 参数名1,数据类型 参数名2,..){ 属性名1=参数名1; 属性名2=参数名2; .. }
package com.by.entity; public class Student { public String name; public int age; public String sex; public double score; // score=90.0; 错误!! //无参构造 public Student() { } //有参构造 public Student(String n, int a, String s, double sc){ //用对应形参给对应属性赋值 name = n; age = a; sex = s; score = sc; } //吃饭 public void eat(){ System.out.println("吃饭"); } //睡觉 public void sleep(){ System.out.println("睡觉"); } //学习 public void study(){ System.out.println("学习"); } }
语法特点:
没有返回值类型部分
方法名必须与类名一致
使用特点:
构造方法只能创建对象,创建对象也只能使用构造
创建对象的语法延伸:
类名 对象名=new 类名();
第一个类名: 用来声明创建的是哪个类的对象
第二个类名: 用来指明调用构造方法
构造方法必须通过new关键字调用
根据创建对象时的小括号内的参数列表决定调用的是哪个构造
语法补充: 利用有参构造创建对象
类名 对象名=new 类名(实参列表);
//利用有参构造创建一个学生对象 Student stu = new Student("zhangsan", 20, "男", 100.0);
无参构造与有参构造的区别:
无参构造: 只负责创建对象
有参构造: 在创建对象的同时可以给属性赋初始值
无参构造至多存在一个,有参构造可以存在0-n个
每个类中都会默认提供一个无参构造, 该构造具有时效性,当类中显式定义构造后,默认给予的构造将会失效
对象包含案例代码:
package com.by.entity; /** * 班级类 */ public class Clazz { public String className; public int classNumber; //添加一个老师对象属性 public Teacher tea; //无参构造 public Clazz(){ } //有参构造 public Clazz(String cName, int cNumber) { className = cName; classNumber = cNumber; } } /** * 老师类 */ public class Teacher { public String name; public String sex; public Teacher(){ } //有参构造 public Teacher(String n,String s){ name = n; sex = s; } //行为方法-教学 public void teach(){ System.out.println("正在教java..."); } }package com.by.test; import com.by.entity.Clazz; import com.by.entity.Teacher; public class Test3 { public static void main(String[] args) { //创建一个老师对象 Teacher t = new Teacher("张三", "男"); //创建一个班级对象 Clazz c = new Clazz("1班", 20); //将对象t赋值给c的tea属性 c.tea = t; //查看班级对象c的所有信息: 班级名 班级人数 所包含的老师姓名 所包含的老师性别 System.out.println("班级名: "+c.className+ " 班级人数:"+c.classNumber+ " 所包含的老师姓名:"+c.tea.name+ " 所包含的老师性别:"+c.tea.sex); } }
为了方便程序员书写和调用功能相同或相似的方法
方法名必须相同
参数列表必须不同
与访问修饰符,返回值类型,异常没有关系
代表当前对象
this.属性名: 用来指明当前类的属性 this.方法名(实参列表):用来指明调用当前类的方法
有参构造标准语法:
public 类名(数据类型 属性名1,数据类型 属性名2,..){ this.属性名1=属性名1; this.属性名2=属性名2; .. }示例:
public Student(String name, int age, String sex, double score){ //用对应形参给对应属性赋值 this.name = name;//属性=形参 this.age = age; this.sex = sex; this.score = score; } public Student(String name, int age) { this.name = name; this.age = age; }
作用为调用本类的其他构造内容
根据实参列表决定调用的是哪个构造内容
必须写在构造方法有效代码第一行
无法循环调用
public Student() { //this();循环调用,错误! this("lisi", 20, "男", 90.0); } //有参构造 public Student(String name, int age, String sex, double score){//name="张三" age=22 sex="男" score=90.5 //用对应形参给对应属性赋值 this(name, age); this.sex = sex; this.score = score; } public Student(String name, int age) { //this(); 循环调用,错误! this.name = name; this.age = age; }
又名属性的三个赋值时期
开辟空间,赋默认值
赋初始值
执行构造时再次赋值
对象间的关系
类和对象间的关系
属性和局部变量的区别
构造方法的特点
方法重载的规则
this的含义和this.属性名
的使用
对象的创建过程
面向对象的核心思想: 封装,继承,多态
是一种屏障,作用为保护对象数据不被外界任意访问
属性私有化
private 数据类型 属性名;
含义 | 作用范围 | |
---|---|---|
public | 公共的,公开的 | 任意位置都可访问 |
private | 私有的 | 本类内部可以访问 |
可以选择性的对属性进行私有化操作,但是通常情况下所有属性都应该参与私有化
提供取值赋值对应的getter和setter方法
getter方法-取值
public 数据类型 getXxx(){ return 属性名; } 注:Xxx对应的是属性名,首字母大写
有返回值,无参数
返回值类型与对应属性类型一致
如果获取的属性类型为布尔类型,则方法名为isXxx
setter方法-赋值
public void setXxx(数据类型 属性名){ this.属性名=属性名; } 注:Xxx对应的是属性名,首字母大写
可以选择性的为私有属性添加getter和setter方法,但是通常情况下都应该具备相应方法
package com.by.entity; /** * 银行卡: 卡号 密码 余额 */ public class BankCard { private String cardID; private String password; private double balance; //取值 public String getCardID(){ return cardID; } //赋值 public void setCardID(String cardID){ this.cardID = cardID; } //密码-取值 public String getPassword(){ return password; } //赋值 public void setPassword(String password) { this.password = password; } //余额-取值 public double getBalance() { return balance; } //赋值 public void setBalance(double balance) { this.balance = balance; } public BankCard(){} public BankCard(String cardID,String password,double balance){ this.cardID = cardID; this.password = password; this.balance = balance; } }
取值:对象名.getXxx() | 对象名.isXxx() 赋值:对象名.setXxx(实参); 注:Xxx对应的是属性名,首字母大写
package com.by.test; import com.by.entity.BankCard; public class TestBankcard { public static void main(String[] args) { BankCard bc = new BankCard("6288888888888888", "411381", 100.0); //赋值 bc.setCardID("6277777777777777"); bc.setPassword("123456"); bc.setBalance(10000.0); //查看属性值 System.out.println(bc.getCardID()); System.out.println(bc.getPassword()); System.out.println(bc.getBalance()); } }
封装的步骤
getter和setter方法的区别
封装后属性的使用
将子类之间的共性进行抽取生成父类, 在继承关系下, 子类能够直接拥有父类可被继承的内容
继承的作用之一就是解决子类之间的代码冗余问题
public class 子类类名 extends 父类类名{ }
package com.by.entity; /** 动物类-父类 */ public class Animal { //名字 年龄 性别 public String name; public int age; public boolean sex; //吃饭 public void eat(){ System.out.println("吃饭"); } //睡觉 public void sleep(){ System.out.println("睡觉"); } }
public class Dog extends Animal{ }
public class Cat extends Animal{ }
package com.by.test; import com.by.entity.Cat; import com.by.entity.Dog; public class Test { public static void main(String[] args) { Dog d = new Dog(); d.name = "小黑"; d.eat(); d.sleep(); Cat c = new Cat(); c.eat(); c.sleep(); } }
继承双方必须满足is a的关系结构
一个子类只能有一个直接父类,一个父类可以拥有多个直接子类(单继承)
一个子类在身为子类的同时也可以是其他类的父类
子类可以拥有独有内容
子类可以继承拥有所有父类所有可被继承的内容
父类无法使用和访问子类的独有内容
父类的私有内容子类无法直接使用
父类的构造子类
子类内存由父类内容和子类独有独有内容组成
package com.by.entity; /** 动物类-父类 */ public class Animal { //名字 年龄 性别 private String name; private int age; private boolean sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public boolean isSex() { return sex; } public void setSex(boolean sex) { this.sex = sex; } //吃饭 public void eat(){ System.out.println("吃饭"); } //睡觉 public void sleep(){ System.out.println("睡觉"); } private void method(){ } }
package com.by.test; import com.by.entity.Dog; public class Test { public static void main(String[] args) { Dog d = new Dog(); d.setName("小黑"); System.out.println(d.getName()); } }
子类对从父类继承过来的方法进行方法体的重新书写,简称方法重写, 也称为方法覆盖
建立在继承关系之上
方法名、参数列表、返回值类型必须与父类保持一致
访问修饰符与父类相同或更宽
不允许抛出比父类更大或更多的异常
子类重写之后,优先执行子类重写内容
父类的作用:
解决子类之间的冗余问题
强制约束子类必须拥有某些内容
本类 | 同包 | 非同包子类 | 非同包非子类 | |
---|---|---|---|---|
private-私有的 | √ | |||
default-默认的 | √ | √ | ||
protected-受保护的 | √ | √ | √ | |
public-公共的,公开的 | √ | √ | √ | √ |
只有default和public可以修饰类
default无法显式声明
以上四个都可以修饰属性、方法、构造
以上四个都不能修饰局部变量
给父子类属性分配空间,赋默认值
给父类属性赋初始值
执行父类构造再次赋值
给子类属性赋初始值
执行子类构造再次赋值
总结: 先构建父类内容,才能构建子类内容
代表父类对象
作用为调用父类构造内容
必须写在子类构造方法有效代码第一行
和this()无法同时显式存在
通过实参列表决定调用的是哪个父类构造
子类构造第一行默认存在无参的super()
子类有参构造写法:
public 子类类名(父类的形参列表,自己独有的属性){ //先调用super()给父类声明属性赋值 super(对应的父类的形参); //再this.给自己的独有属性赋值 this.自己的属性名=属性名; }package com.by.entity; /** 动物类-父类 */ public class Animal { //名字 年龄 性别 private String name; private int age; private boolean sex; public Animal(){} public Animal(String name, int age, boolean sex) { this.name = name; this.age = age; this.sex = sex; } //省略getter\setter }public class Cat extends Animal{ private String color; public Cat(){} public Cat(String name, int age, boolean sex,String color){ //先调用super()给父类声明属性赋值 super(name, age, sex); //再this.给自己的独有属性赋值 this.color = color; } }
用来指明调用父类的属性或方法
super.属性名 super.方法名(实参)
public class Dog extends Animal{ //从animal继承过来了名字,年龄,性别,吃饭(),睡觉() //重写父类方法 public void eat(){ //调用父类的eat方法 super.eat(); System.out.println("狗吃大骨头"); } }
继承的语法
继承的规则
方法重写的规则
访问修饰符及其作用范围
有继承关系的对象创建过程
super()在子类构造中的运用
父类引用指向子类对象
父类引用=子类对象; 父类类名 引用名=new 子类类名();
Animal a1 = new Dog();//狗对象是一个动物 Animal a2 = new Cat();//猫对象是一个动物
实际创建的是子类对象
优先执行子类内容
父类引用无法访问子类独有内容
编译器关注的是引用类型
解释器关注的是实际对象类型
右侧类型决定了是什么,左侧类型决定了能做什么
父类-大类型
子类-小类型
父类引用=子类对象|子类引用
子类类名 引用名=(子类类名)父类引用名;
//利用多态创建一个子类对象 Animal a = new Dog(); a.eat(); a.sleep(); //调用看门方法 Dog类的引用.lookDoor() //将a引用类型强转为Dog类型 Dog dog = (Dog) a; dog.lookDoor();
在多态的前提下,必须先将父类引用强转为子类类型才能访问子类独有内容
只能转向父类引用原本指向的子类类型. 编译不报错,运行报错
Animal a=new Dog(); Cat cat=(Cat)a; //会报出ClassCastException异常:类型转换出错
同级子类之间不可进行强转. 编译报错
Dog d=new Dog(); Cat c=(Cat)d;//错误!
用于容器. 将容器类型声明为大类型, 则内部可以存放不同的小类型对象
//创建数组存放若干个狗对象和猫对象 Dog d1 = new Dog(); Dog d2 = new Dog(); Dog d3 = new Dog(); Cat c1 = new Cat(); Cat c2 = new Cat(); Cat c3 = new Cat(); Animal[] as = {d1, d2, d3, c1, c2, c3}; //Animal as[0]=new Dog(); //Animal as[3]=new Cat(); //遍历数组 for (int i = 0; i < as.length; i++) { as[i].eat(); }
用于参数: 将形参类型声明为大类型, 则实参可以为不同的小类型对象
public static void main(String[] args) { Dog d1 = new Dog(); Cat c1 = new Cat(); method(d1);//狗吃大骨头 method(c1);//猫吃小鱼干 } //定义一个函数, 传入参数,要求可以执行出"狗吃大骨头"或者"猫吃小鱼干" public static void method(Animal a) {//Animal a=new Dog(); Animal a=new Cat(); a.eat(); }
用于返回值: 将返回值类型声明为大类型, 则可以return返回不同的子类对象
public static void main(String[] args) { Animal a= method3(11);//a=new Dog() a.eat(); } //定义一个函数, 传入一个整型参数n,要求n为偶数返回Dog对象,否则返回Cat对象 public static Animal method3(int n) { if (n % 2 == 0) { return new Dog(); } return new Cat(); }
作用为判断引用是否与指定类型兼容
引用名 instanceof 类名 注:执行结果为boolean类型
Animal a= method3(12); //如果返回的是狗对象,则调用看门方法,如果是猫对象,则调用吃饭方法 //判断a是否与Dog兼容 if (a instanceof Dog) { //将a引用强转为Dog类型引用 Dog d = (Dog) a; //调用看门方法 d.lookDoor(); } else { //调用吃饭 a.eat(); }
大类型可以兼容小类型,但是小类型无法兼容大类型
同级子类之间不可兼容
Dog jm = new JinMao(); System.out.println(jm instanceof JinMao);//t System.out.println(jm instanceof Dog);//t System.out.println(jm instanceof Animal);//t //System.out.println(jm instanceof Cat); Animal a = new Dog(); System.out.println(a instanceof Dog);//t System.out.println(a instanceof Animal);//t System.out.println(a instanceof JinMao);//f System.out.println(a instanceof Cat);//f
多态的语法
引用类型间的强制转换
多态的使用场景
instanceof关键字的语法和作用
抽象的,不是真实存在的
public abstract class 类名{} abstract public class 类名{} 注:修饰符之间的顺序不做要求
无法实例化对象
通常情况下,父类应为抽象类
抽象父类中仍然存在构造,作用为供子类使用
抽象类中可以存在非抽象内容
抽象父类仍然可以参与多态
访问修饰符 abstract 返回值类型 方法名(参数列表);
没有方法体
抽象方法必须存在于抽象类
通常情况下,父类中的行为方法都应该是抽象方法
子类必须对父类中的抽象方法提供重写,除非子类自身也是抽象类
通常子类都应该选择重写
抽象父类的完整作用:
解决子类之间的冗余问题
强制约束子类必须拥有某些内容
强制约束子类必须重写某些方法
更符合现实逻辑
使父类单纯的为子类服务,更贴合程序设计
静态属性,也称为静态变量 类变量等
static 数据类型 属性名;
静态内容独立存放在方法区
静态内容在内存中只有一份,被该类所有对象共享
普通属性所有对象在对象内容中都有一份
可以通过类名.静态属性名
的方式直接访问静态属性
静态属性封装之后,必须调用getter|setter方法才能访问
getter|setter是静态的:直接通过类名.getter()|setter()
访问
getter|setter不是静态的: 必须通过任一对象名.getter()|setter()
访问
package com.by.entity; public class ClassA { private static int count;//累加器属性 public static int getCount() { return count; } public static void setCount(int count) { ClassA.count = count; } /* public int getCount() { return count; } public void setCount(int count) { ClassA.count = count; }*/ public ClassA(){ count++; } }
package com.by.test; import com.by.entity.ClassA; public class Test2 { public static void main(String[] args) { ClassA ca1 = new ClassA(); ClassA ca2 = new ClassA(); ClassA ca3 = new ClassA(); /*System.out.println(ca3.count);//3 System.out.println(ca2.count);//3 System.out.println(ca1.count);//3*/ // System.out.println(ClassA.count);//3 System.out.println(ca1.getCount()); System.out.println(ClassA.getCount()); } }
静态方法,也就是函数
访问修饰符 static 返回值类型 方法名(形参列表){ //操作语句 }
不能访问非静态内容
什么是类加载?
在第一次使用类内容时,通过ClassPath类路径找到对应的字节码文件,将字节码文件中的内容加载到虚拟机内存中的过程,称之为类加载, 通常只会发生一次.
触发类加载的时机
第一次创建类的对象
第一次访问静态内容时
通过
Class.forName("全限定名")
强制触发类加载
全限定名:类的完整路径,也就是
包名.类名
子类类加载也会触发父类的类加载
只声明引用不会触发类加载
静态内容是在类加载时进入内存,但是非静态内容是在创建对象时进入内存
非静态方法可以访问任何内容
无法使用this和super关键字
使用静态内容时对象可能并未创建
static无法修饰局部变量
局部变量的作用范围使其无法成为类变量
无法修饰构造
构造无法通过类名直接调用
子类可以继承和重写父类静态方法,但是在多态的前提下,仍然执行父类内容
静态内容的执行关注的是引用类型
{ //初始代码块 }
通常写在属性之下,构造之上
作用为构造方法共有内容的提炼,通常用于给属性赋值
在创建对象时执行,先执行初始代码块再执行构造内容,可以执行多次
static{ //静态初始代码块 }
内部内容的限制与静态方法相同
在类加载的时候执行,通常只会执行一次
作用为给静态属性赋值
final 数据类型 属性名;
常量属性, 值不可改
在创建时必须赋值, 两次赋值时机:
声明的时候赋值
在构造中赋值
每个构造中都存在赋值语句(也可将赋值语句提炼至初始代码块)
可以被继承,不可被重写
无法被继承
变成常量,值不可改
对象内容可改,引用地址不可改
abstract无法与private static final结合使用
private static final之间可以任意结合
抽象类和抽象方法的关系
抽象类中可以存在非抽象方法
抽象方法必须存在于抽象类
静态内容的特点
类加载的概念和时机
静态方法的使用特点
final都能修饰什么及修饰后的特点
从功能上看, 实现接口就意味着扩展了某些功能
从抽象上看,接口对于类而言就是特殊的抽象父类
从规则上看, 是接口定义者和接口实现者都必须遵守的某些规则
[public] interface 接口名{ }
写在com.xxx.dao包下
属性必须为公开静态常量(public static final)
由于修饰符固定,所以可以省略或者缺失,编译器会自动填充
方法为公开抽象方法(public abstract)
不存在构造方法
不能被实例化对象
package com.by.dao; public interface IA { public static final int A = 100; final int B = 200; int C = 300; public abstract void m1(); public void m2(); void m3(); } public interface IB { public void ma(); }
[public] class 实现类名 implements 接口名{ }
实现类写在com.xxx.dao.impl包下
package com.by.dao.impl; import com.by.dao.IA; import com.by.dao.IB; import com.by.entity.Super; public class IA_IB_Impl extends Super implements IA, IB{ @Override public void m1() { System.out.println("IA_IB_Impl-m1"); } @Override public void m2() { System.out.println("IA_IB_Impl-m2"); } @Override public void m3() { System.out.println("IA_IB_Impl-m3"); } @Override public void ma() { System.out.println("IA_IB_Impl-ma"); } public void method() { System.out.println("IA_IB_Impl-method"); } }
必须对接口的抽象方法提供方法实现
除非实现类本身是抽象类(不推荐)
一个实现类可以实现多个接口,一个接口也可以拥有多个实现类(多实现)
语法补充:
[public] class 实现类名 implements 接口名1,接口名2,..{ }
实现类如果实现了多个接口,必须对所有接口中的抽象方法都提供方法实现
类可以同时继承父类和实现接口,先继承后实现
[public] class 类名 extends 父类类名 implements 接口名1,接口名2,..{ }
接口名仍然可以参与多态
接口名 引用名=new 实现类名();
接口参与多态时,仍然不可访问实现类独有内容
需要访问独有内容时,必须进行类型强转
只能转向原本指向的实现类类型|实现类的其他引用类型
同级实现类之间不可强转
//利用多态创建实现类对象 IA ia = new IA_IB_Impl(); ia.m1(); ia.m2(); ia.m3(); /*ia.ma(); ia.method(); 错误,编译失败*/ //将ia引用类型强转为IB接口 IB ib = (IB) ia; ib.ma(); //将ia引用强转为实现类类型 IA_IB_Impl ia_ib = (IA_IB_Impl) ia; // IAImpl iaimpl = (IAImpl) ia; 运行报错 // IAImpl iaImpl = (IAImpl) ia_ib; 编译报错
仍然可以使用instanceof关键字
引用 instanceof 接口名
IA ia = new IA_IB_Impl(); System.out.println(ia instanceof IA_IB_Impl);//t System.out.println(ia instanceof IA);//t System.out.println(ia instanceof IB);//t
一个接口可以继承多个父接口(多继承)
[public] interface 子接口名 extends 父接口名1,父接口名2,..{ }
子接口可以继承拥有所有父接口中的内容
抽象类 | 接口 | |
---|---|---|
关键字 | abstract class | interface |
属性 | 不做要求 | 公开静态常量 |
方法 | 不做要求,可以存在非抽象方法 | 公开抽象方法 |
构造 | 有 | 无 |
继承性 | 单继承 | 多继承 |
默认方法
public default 返回值类型 方法名(形参列表){ }
当类继承父类又实现接口, 父类和接口中的内容出现冲突时,优先执行父类内容(类优先原则)
当同时实现多个接口, 接口之间的方法出现冲突时, 实现类必须对冲突方法提供重写,使用自身重写内容
静态方法
public static 返回值类型 方法名(形参列表){ }
可以通过接口名.方法名(实参)
的方式直接调用
私有方法
private 返回值类型 方法名(形参列表){ }
实现类无法扩展该方法
“开闭原则”: 扩展开放,修改关闭
允许在当前代码的基础上扩展功能,但是前提是不修改已有内容
实现: 将方法的形参声明为接口类型, 实参传入不同的实现类对象 (多态的第二个应用场景)
优势: 将方法定义与方法的具体执行分离, 定义者负责使用接口定义,使用者根据需求提供实现类具体执行,以此提高代码复用度
需求: 利用比较器对数组进行指定规则的排序
书写实现类,实现Comparator接口,重写compare方法
public class 实现类名 implements Comparator<要排序的类名> { @Override public int compare(要排序的类名 o1, 要排序的类名 o2) { } }
书写排序规则:
从小到大:
o1的值>o2的值,return正数
o1的值<o2的值, return负数
相等return0
从大到小:
o1的值>o2的值,return负数
o1的值<o2的值, return正数
相等return0
package com.by.dao.impl; import com.by.entity.Student; import java.util.Comparator; public class StudentComparator implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { //根据学生年龄从小到大 if (o1.getAge() > o2.getAge()) { return 1; } else if (o1.getAge() < o2.getAge()) { return -1; } else { return 0; } } }
在测试类中调用Arrays.sort()传入数组和实现类对象
package com.by.test; import com.by.dao.impl.StudentComparator; import com.by.entity.Student; import java.util.Arrays; public class TestStudent { public static void main(String[] args) { //创建一个学生数组 Student[] ss = {new Student("zhangsan", 21, 98.0), new Student("lisi", 20, 88.5), new Student("wangwu", 22, 97.0)}; //根据学生年龄从小到大的排序 Arrays.sort(ss,new StudentComparator()); for (int i = 0; i < ss.length; i++) { System.out.println(ss[i].getName()+" "+ss[i].getAge()+ " "+ss[i].getScore()); } } }
使项目结构更加清晰
更符合现实逻辑
提供接口回调方便提高代码复用度
接口和实现类的语法
接口和抽象类的区别
接口和实现类的规则
在类的内部再次声明定义类
打破封装又不破坏封装
成员内部类
静态内部类
局部内部类
匿名内部类 (掌握)
位置: 类以内,方法以外,和属性,方法平级
[public] class 外部类类名{ [public] class 内部类类名{ } }
当外部类属性,内部类属性,内部类局部变量重名时:
局部变量: 变量名
内部类属性: this.属性名
外部类属性: 外部类类名.this.属性名
创建成员内部类的对象必须借助外部类的对象
外部类类名.内部类类名 对象名=外部类对象名.new 内部类类名();
//创建一个外部类的对象 Outer1 o1 = new Outer1(); //创建内部类的对象 Outer1.Inner1 i1 = o1.new Inner1(); i1.mb();
8.0版本及前后的JDK成员内部类中不允许定义静态内容,只可访问.
后期高版本的JDK中成员内部类中也可以定义静态内容,并且可以通过
外部类类名.内部类类名.静态内容
的方式直接访问
位置: 与成员内部类相同
[public] class 外部类类名{ [public] static class 内部类类名{ } }
可以定义静态内容,但是无法访问外部类非静态内容
当外部类属性与内部类属性重名时:
在内部类的静态方法中重名
内部类属性: 内部类类名.属性名
外部类属性: 外部类类名.属性名
在内部类的非静态方法中重名:
内部类属性: 内部类类名.属性名 | this.属性名
外部类属性: 外部类类名.属性名
静态内部类的静态内容可以直接通过外部类类名.内部类类名.静态内容
的方式访问
静态内部类对象的创建必须基于外部类类名
外部类类名.内部类类名 对象名=new 外部类类名.内部类类名();
package com.by.entity; /** * 静态内部类 */ public class Outer2 { int a=100;//非静态属性 static int b = 200;//静态属性 static String str = "外部类静态属性"; public static class Inner2{ static String str = "内部类静态属性"; public static void method(){ String str = "内部类局部变量"; System.out.println(str);//局部变量 System.out.println(Inner2.str);//内部类属性 System.out.println(Outer2.str);//外部类属性 //System.out.println(a); System.out.println(b); } public void ma(){ String str = "内部类局部变量"; System.out.println(str); System.out.println(this.str);//静态内部类中的实例方法执行时一定存在当前对象 System.out.println(Outer2.str);//外部类的实例对象this无法进入静态内部类使用,无法使用Outer2.this // System.out.println(a); System.out.println(b); } } }
//直接访问内部类的静态内容 Outer2.Inner2.method(); //访问静态内部类的非静态内容 Outer2.Inner2 i2 = new Outer2.Inner2(); i2.ma();
位置: 外部类方法内部,与外部类局部变量平级
[public] class 外部类类名{ 访问修饰符 返回值类型 方法名(形参列表){ class 内部类类名{ } } }
无法在声明类的时候添加访问修饰符
作用范围: 与局部变量一致
无法访问外部类的局部变量,可以访问局部常量
JDK7.0之前: 访问内容必须通过final修饰
JDK7.0之后: 事实上的常量即可(未二次更改值)
局部内部类对象的创建只能在所属方法内部完成
package com.by.entity; /** * 局部内部类 */ public class Outer3 { public void method(){ String str = "外部类的局部变量"; // str = "正在更改外部类局部变量的值"; //局部内部类 class Inner{ public void get(){ System.out.println(str); } } //创建内部类对象 Inner inner = new Inner(); inner.get(); } }
创建一个接口的实现类对象或者父类的子类对象
接口名|父类类名 引用名=new 接口名|父类类名(){ //实现类内容 };
必须实现一个接口或者继承一个父类
对象创建必须使用多态
一个匿名内部类只能创建一个对象
存在一个默认的无参构造
类中无法显式定义构造
类中可以定义独有内容,但是只能在类中使用,无法在类外通过引用调用
匿名内部类无法参与类型强转
//利用匿名内部类创建一个IA接口的实现类对象 IA ia2=new IA() { @Override public void ma() { m1(); System.out.println("这是匿名实现类中的ma"); } public void m1(){ System.out.println("这是匿名实现类的独有方法"); } }; ia2.ma();
接口的分类:
标记式接口: 无任何内容
常量式接口: 只定义属性,未定义方法
函数式接口: 只有一个需要重写的方法
普通接口: 拥有多个需要重写的方法
只能作用于函数式接口
用来简化部分匿名内部类的书写,可以创建一个接口实现类对象
(形参列表)->{操作语句} 结合引用: 接口名 引用名=(形参列表)->{操作语句};
参数数据类型可省(要省则都省)
参数只有一个时,小括号可省
操作语句只有一条时,大括号可省
操作语句只有一条并且为return语句时,大括号和return都可省(要省则都省)
/* * 定义几个接口: * IA: void ma():输出1-100的和 * IB: void mb(int n): 输出n的奇偶性 * IC: void mc(int a,int b): 输出b是否为a的因子 * ID: int md(int a,int b): 计算参数之和并返回 */ public class TestLambda { public static void main(String[] args) { //利用lambda表达式创建IA实现类对象 IA ia = () -> { int sum = 0; for (int i = 0; i < 101; i++) { sum += i; } System.out.println(sum); }; //IB: IB ib = n ->System.out.println(n % 2 == 0 ? "偶数" : "奇数"); //IC: IC ic = (a,b) -> System.out.println(a % b == 0 ? "是因子" : "不是因子"); //ID: ID id = (a, b) -> a + b; } }
内部类的分类
匿名内部类的语法
lambda表达式的
最大父类,也称为祖宗类. 所有类都直接或间接的继承自Object
Class getClass() : 获取引用的实际对象类型
Animal a1 = new Dog(); Animal a2 = new Cat(); Dog dog = new Dog(); //获取实际对象类型 System.out.println(a1.getClass());//com.by.entity.Dog System.out.println(a2.getClass());//com.by.entity.Cat //判断a1和a2是否相同 System.out.println(a1.getClass() == a2.getClass());//f System.out.println(a1.getClass() == dog.getClass());//t
int hashCode() : 获取当前对象的哈希码值
重写原因: 该方法默认关注对象地址,地址不同则哈希码值不同. 但是开发过程中某些情况下我们需要关注的是对象内容,内容一致则哈希码值必定一致,所以需要对其重写
重写规则:
整型: 直接相加
小数类型: 强转int后相加
引用类型: 属性名.hashCode()
相加
类库中的引用类型: 该方法已经提供过重写,所以直接调用方法即可
自定义的引用类型: 需要先将自定义类型的该方法重写,再进行调用
boolean equals(Object o): 判断当前对象与参数对象是否相同
重写原因: 该方法默认比较对象地址,但是在开发过程中,某些情况下需要比较双方内容,所以需要提供重写
重写规则:
public boolean equals(Object o) { //自反性 if (this == o) { return true;//自己和自己比,一定相同 } //非空判断 和 类型比较 if (o == null || this.getClass()!=o.getClass()) { return false;//与null值比或者实际对象类型不一致,一定不同 } //类型强转 当前类名 引用名 = (当前类名) o; //比较属性值 return 让当前对象的属性与引用名的属性进行比较,多个属性值之间的比较&&连接; }
比较属性值:
基本类型: ==直接比较
引用类型:
类库中的引用类型: 对equals已经提供过重写,所以可以直接调用
自定义的引用类型: 对自定义类型中的equals也进行重写,然后调用
package com.by.entity; public class Student { private String name; private int age; private double score; private Teacher tea; @Override public int hashCode() { return age+(int)score+name.hashCode()+ tea.hashCode(); } public boolean equals(Object o) {//Object o=new Student(); //自反性 if (this == o) { return true;//自己和自己比,一定相同 } //非空判断 和 类型比较 if (o == null || this.getClass()!=o.getClass()) { return false;//与null值比或者实际对象类型不一致,一定不同 } //类型强转 Student s = (Student) o; //比较属性值 return this.age==s.age && this.score==s.score && this.name.equals(s.name) && this.tea.equals(s.tea); } //省略getter\setter\构造 }
package com.by.entity; public class Teacher { private String name; public int hashCode(){ return name.hashCode(); } public boolean equals(Object o) { //自反性 if (this == o) { return true; } //非空判断+类型比较 if (o == null || this.getClass() != o.getClass()) { return false; } //类型强转 Teacher t = (Teacher) o; //属性值比较 return this.name.equals(t.name); } //省略getter\setter\构造 }
String toString() : 用来返回当前对象的信息
重写原因: 该方法默认获取对象地址返回,但是实际开发中,通常查看的是对象的内容,所以需要重写
重写规则: 根据查看需求拼接属性值返回即可
使用:
在直接查看引用时可以自动调用
void finalize() : 用来进行垃圾回收
垃圾回收机制: 当内存满到不足以支撑新对象的创建时,虚拟机会调用垃圾对象的该方法对其进行回收销毁,以此来释放空间
垃圾对象判断标准: 没有任何引用指向的对象 (零引用机制)
手动垃圾回收: 可以借助垃圾回收期GC,通过在代码中书写System.gc()
来实现手动的垃圾回收
作用为将八大基本类型包装为引用类型
byte | short | int | long | float | double | char | boolean |
---|---|---|---|---|---|---|---|
Byte | Short | Integer | Long | Float | Double | Character | Boolean |
利用构造
包装类型 引用名=new 包装类名(基本类型);
利用valueOf
包装类型 引用名=包装类名.valueOf(基本类型);
利用xxxValue
基本类型 变量名=包装类型引用名.xxxValue(); 注:xxx对应的是基本类型名
JDK5.0之后,为了方便操作,提供了自动封箱与拆箱,使得基本类型与包装类型之间可以自动转换
封箱: 基本类型转包装类型
拆箱: 包装类型转基本类型
int a1=10; //1.构造 Integer i1 = new Integer(a1); //2. valueOf Integer i2 = Integer.valueOf(a1); //包转基 int a2 = i2.intValue(); //自动转换 Integer i3 = a1; int a3 = i3;
String 引用名=基本类型+""; String 引用名=""+基本类型;
利用valueOf
String 引用名=String.valueOf(基本类型);
利用parseXxx方法
基本类型 变量名=对应的包装类名.parseXxx(String类型); 注:Xxx对应的是基本类型名,首字母大写
必须包装String中的数据是基本类型能够盛放的数据,否则执行时会触发NumberFormatException数据类型转换异常
int a1 = 100; //字符串拼接 String s1 = a1 + "abc"; String s2 = "" + a1; String s3=String.valueOf(a1); //String转基 int a2 = Integer.parseInt(s2); // int a3 = Integer.parseInt(s1);//100abc 报错
字符串拼接
String 引用名=包装类型+""; String 引用名=""+包装类型;
利用toString
String 引用名=包装类型引用名.toString();
与基转包一致
必须保证String的值为包装类型能够盛放的值
Integer i1 = 100; //字符串拼接 String s1 = i1 + "qwe"; String s2 = "" + i1; //toString String s3 = i1.toString(); //String转包 Integer i2 = new Integer(s2); Integer i3 = Integer.valueOf(s3); // Integer i4 = new Integer(s1); 报错
官方认定-128至127是最常用的256个数字,为了减少包装类型使用反复数字带来的空间浪费,所以在方法区中设立了整数缓冲区,用来存放这256个数字,当包装类型使用的数字在此范围内,则直接引用缓冲区地址,不再额外开辟对象空间,以此达到节省空间的目的
==比较引用类型地址时,优先使用堆中的对象地址
Integer i1 = 200; Integer i2 = 200; System.out.println(i1 == i2);//f Integer i3 = 100; Integer i4 = 100; System.out.println(i3 == i4);//t Integer i5 = new Integer(100); Integer i6 = new Integer(100); System.out.println(i5 == i6);//f System.out.println(i3 == i5);//f
特点: 是一个内存中常量, 在内存中值一旦确定则不可更改
String s1 = "abc"; String s2 = s1; s2 = "edf"; System.out.println("s1: "+s1);//abc System.out.println("s2: "+s2);//edf
双引号直接赋值
String 引用名="值";
利用构造
String 引用名=new String("值");
串池:
全程字符串常量池, 由于String是实际开发中使用频率最高的数据类型,且开发过程中对一个字符串的复用率远远高于对其的更改频率, 为了减少字符串重复使用带来的空间浪费,所以在方法区中设立了串池,保证相同字符串内容只会占用一块串池空间,以此来减少空间浪费,节约空间资源.
第一种创建方式: 当创建时,会先去串池中寻找对应字符串内容,如果存在,则引用直接指向,如果不存在, 则先在串池中创建对应字符串内容然后引用指向
第二种创建方式: 无论如何都会开辟对象空间. 如果串池中存在对应内容,则对象空间直接存放对应串池地址,如果串池中不存在对应内容,则先在串池中创建,再让对象空间存放对应串池地址
String s1 = "abc"; String s2 = "abc"; String s3 = new String("edf"); String s4 = new String("edf"); System.out.println(s1 == s2);//t System.out.println(s3 == s4);//f String s5 = new String("abc"); System.out.println(s1 == s5);//f
StringBuffer: JDK1.0 线程安全,效率低
StringBuilder: JDK5.0 线程不安全,效率高
必须通过构造创建
无法使用串池
内容操作必须调用对应方法实现
//拼接26个大写字母并输出 StringBuilder sb = new StringBuilder(); for (char c = 'A'; c <= 'Z'; c++) { //将当前遍历内容追加至可变长字符串末尾 sb.append(c); } System.out.println(sb); //将可变长字符串转变为String类型 String s = sb.toString();
由于字符串的复用率高于更改频率,所以使用更简单且能用串池的String应用场景要远远多于可变长字符串
String引用名.方法名(实参列表)
char charAt(下标): 获取指定下标位置的字符
boolean contains(字符串): 判断当前字符串中是否包含指定内容
boolean endsWith(字符串): 判断字符串是否以指定内容结尾
boolean startsWith(字符串): 判断字符串是否以指定内容开头
boolean equals(字符串): 判断与指定字符串内容是否相同, 区分大小写
boolean equalsIgnoreCase(字符串): 判断与指定字符串内容是否相同, 不区分大小写
byte[] getBytes(): 以byte数组的形式返回字符串内容
char[] toCharArray(): 以char数组的形式返回字符串内容
下标 indexOf(字符串): 获取指定字符串第一次出现的下标, 不存在返回-1
下标 lastIndexOf(字符串): 获取指定字符串最后一次出现的下标, 不存在返回-1
boolean isEmpty(): 判断字符串内容是否为空,无法判比null值
int length(): 获取字符串长度
新字符串 replace(旧字符串,新字符串): 将符合条件的旧字符串替换为新字符串内容
String[] split(分隔符): 对字符串根据分隔符进行分割, 不保留分隔符
新字符串 substring(下标): 将原字符串从指定下标位置截取至末尾
新字符串 substring(起始下标,结束下标): 将原字符串从起始下标截取至结束下标前一位
新字符串 toLowerCase(): 转全小写
新新字符串 toUpperCase(): 转全大写
新字符串 trim(): 去除前后空格
package com.by.test.string; public class Test4 { public static void main(String[] args) { String s = "hello"; //char charAt(下标): 获取指定下标位置的字符 System.out.println(s.charAt(2));//l //boolean contains(字符串): 判断当前字符串中是否包含指定内容 System.out.println(s.contains("el"));//t System.out.println(s.contains("eo"));//f //boolean endsWith(字符串): 判断字符串是否以指定内容结尾 System.out.println(s.endsWith("o"));//t System.out.println(s.endsWith("hello"));//t //boolean startsWith(字符串): 判断字符串是否以指定内容开头 System.out.println(s.startsWith("h"));//t //boolean equals(字符串): 判断与指定字符串内容是否相同, 区分大小写 System.out.println(s.equals("hello"));//t System.out.println(s.equals("HeLLo"));//f //boolean equalsIgnoreCase(字符串): 判断与指定字符串内容是否相同, 不区分大小写 System.out.println(s.equalsIgnoreCase("HELLO"));//t //byte[] getBytes(): 以byte数组的形式返回字符内容 byte[] bs = s.getBytes(); for (int i = 0; i < bs.length; i++) { System.out.print((char) bs[i]+" "); } System.out.println(); //char[] toCharArray(): 以char数组的形式返回字符串内容 char[] cs = s.toCharArray(); for (int i = 0; i < cs.length; i++) { System.out.print(cs[i]+" "); } System.out.println(); //下标 indexOf(字符串): 获取指定字符串第一次出现的下标, 不存在返回-1 System.out.println(s.indexOf("l"));//2 //下标 lastIndexOf(字符串): 获取指定字符串最后一次出现的下标, 不存在返回-1 System.out.println(s.lastIndexOf("l"));//3 //boolean isEmpty(): 判断字符串内容是否为空,无法判比null值 System.out.println(s.isEmpty());//f String s2 = ""; System.out.println(s2.isEmpty());//t String s3 = " "; System.out.println(s3.isEmpty());//f String s4 = null; //System.out.println(s4.isEmpty()); 空指针 //int length(): 获取字符串长度 System.out.println(s.length());//5 //新字符串 replace(旧字符串,新字符串): 将符合条件的旧字符串替换为新字符串内容 System.out.println(s.replace("l", "m"));//hemml //String[] split(分隔符): 对字符串根据分隔符进行分割, 不保留分隔符 String str = "2000-01-01"; //通过-进行分割 String[] ss = str.split("-");//[2000 01 01] for (int i = 0; i < ss.length; i++) { System.out.print(ss[i]+" "); } System.out.println(); //直接分割 String[] ss2 = str.split(""); for (int i = 0; i < ss2.length; i++) { System.out.print(ss2[i]+" "); } System.out.println(); //新字符串 substring(下标): 将原字符串从指定下标位置截取至末尾 String s5 = "abcdefg"; System.out.println(s5.substring(3));//edfg //新字符串 substring(起始下标,结束下标): 将原字符串从起始下标截取至结束下标前一位 System.out.println(s5.substring(1, 6));//bcdef //新字符串 toLowerCase(): 转全小写 String s6 = "ABCD"; System.out.println(s6.toLowerCase());//abcd //新新字符串 toUpperCase(): 转全大写 System.out.println(s5.toUpperCase());//ABCDEFG //新字符串 trim(): 去除前后空格 String s7 = " a b c "; System.out.println(s7.trim());//a b c } }
getClass方法和instanceof的区别
equals方法的重写规则
自动封箱和拆箱的概念
String转基本类型的写法
串池的概念
String的两种创建方式及区别
String的重点常用方法
是一个容器,作用为存放多个数据,通常用来替代数组
只能存放引用类型
所有集合都来自于java.util包
List , Set , Map都是接口
有序,有下标,元素可以重复
ArrayList (常用)
JDK1.2 底层数组实现 查询快,增删慢 线程不安全,效率高
LinkedList
JDK1.2 底层链表实现 查询慢,增删快 线程不安全,效率高
Vector
JDK1.0 底层数组实现 都慢 线程安全,效率低
建议使用多态
List 集合名=new 实现类();
集合名.方法名(实参列表)
boolean add(元素): 往集合末尾添加一个元素
void add(下标, 元素): 将元素添加至指定下标位置
boolean addAll(集合名):将指定集合元素添加至当前集合末尾
boolean addAll(下标,集合名):将指定集合元素添加至当前集合指定下标位置
int size():获取集合长度
元素 get(下标):获取指定下标位置的元素
下标不可超出使用范围,否则报出下标越界异常
boolean contains(元素):判断是否包含指定元素
boolean containsAll(集合名):判断当前集合中是否包含指定集合的所有元素
下标 indexOf(元素):获取指定元素第一次出现的下标
下标 lastIndexOf(元素):获取指定元素最后一次出现的下标
boolean isEmpty():判断集合元素是否为空,不可判比null值
被删除的元素 remove(下标):删除指定下标位置的元素
boolean remove(元素):删除指定元素
如果集合元素为整数值,则会优先认定值为下标。所以删除整数值元素时只能通过下标删除
旧元素 set(下标,新元素):将指定下标位置的值替换为新元素值
Object[] toArray():将集合转换为数组
下标遍历
for(int i=0;i<集合名.size();i++){ //通过集合名.get(i)的方式获取当前元素 }
List list = new ArrayList(); list.add(10); list.add(20); list.add(30); list.add(40); list.add(50); //下标遍历 for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)+" "); } System.out.println();
迭代器遍历
获取集合的迭代器:集合名.iterator()
操作迭代器:
boolean hasNext():判断是否存在下一元素
元素 next():使指针后移一位,获取下一元素
使用:
迭代过程中不可增删元素
一次迭代只能调用一次next方法,否则迭代与操作元素数量不一致
//获取迭代器 Iterator it = list.iterator(); //利用循环操作迭代器 while(it.hasNext()){ Object o = it.next(); if (o != null) { System.out.print(o+" "); } } System.out.println();
外遍历forEach
for(数据类型 元素名:集合名){ //元素名就代表正在被遍历的元素 }
for (Object o : list) { System.out.print(o+" "); } System.out.println();
遍历过程中无法增删元素
JDK5.0
自遍历forEach
JDK8.0
集合名.forEach(Consumer接口实现类对象)
list.forEach(new Consumer() { @Override public void accept(Object o) { //参数o就表示当前元素 System.out.print(o+" "); } }); System.out.println(); //自遍历forEach-lambda list.forEach(o -> System.out.print(o + " "));
由于创建Consumer实现类对象的匿名内部类也是局部内部类,所以无法使用累加器和标识变量
用于集合: 约束集合可存放的数据类型
List<泛型类型> 集合名=new 实现类名<泛型类型>();
存放类型为基本类型时,需要将泛型类型声明为对应包装类型
前后泛型声明必须一致
前边泛型不可省略
后边泛型<>内容可省
List<泛型类型> 集合名=new 实现类名<>();
package com.by.test; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; public class Test4 { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(10); list.add(20); list.add(30); list.add(40); list.add(50); /* list.add(5.5); list.add("abc");*/ //四种遍历方式 //下标遍历 for (int i = 0; i < list.size(); i++) { //接收当前元素 Integer n = list.get(i); System.out.print(n+" "); } System.out.println(); //迭代器遍历 Iterator<Integer> it = list.iterator(); while (it.hasNext()) { Integer n = it.next(); System.out.print(n+" "); } System.out.println(); //外遍历 for (Integer i : list) { System.out.print(i+" "); } System.out.println(); //自遍历 list.forEach(new Consumer<Integer>() { @Override public void accept(Integer i) { System.out.print(i+" "); } }); System.out.println(); //lambda list.forEach(i-> System.out.print(i+" ")); } }
集合的特点
List的存储特点
List的常用实现类和特点
List的遍历方式
泛型在集合创建中的作用
是List和Set的父接口
所有集合都是由Collection或者Map派生
内部存放List和Set的共性方法
没有直接实现类
无序,无下标,元素不可重复
HashSet
JDK1.2 底层哈希表(数组+链表)实现 线程不安全,效率高
LinkedHashSet
JDK1.2 使HashSet的子类,底层哈希表实现 线程不安全,效率高
TreeSet
JDK1.2 底层红黑树实现,是SortedSet的实现类 线程不安全,效率高
红黑树:树状结构存放数据,使用的是二分查找法,特点为查询效率快
建议使用多态
Set<泛型> 集合名=new 实现类名<>();
所有方法都继承自Collection,无独有方法
迭代器遍历
外遍历forEach
自遍历forEach
先调用元素的hashCode方法获取哈希码值
通过哈希码值%数组长度(16)得到存放下标
如果下标位置未存有元素,则直接存放
如果下标位置存有元素, 则调用当前元素的equals方法与下标位置元素进行值的比较
都不相同, 在下标位置上继续链表存放
有相同,则舍弃添加当前元素
HashSet和LinkedHashSet如果存放的是自定义类型,则必须重写hashCode和equals方法才能实现去重
LinkedHashSet可以保证元素存入与取出的顺序一致
TreeSet可以实现对元素进行默认的升序排序
如果TreeSet中存放的是自定义类型,则必须自定义排序规则
排序方式:
实现Comparable接口,重写CompareTo方法
思路:让当前对象this和参数对象o进行比较
实现:对谁排序,就让谁实现
规则:
从小到大:
this的值>o的值,返回正数
this的值<o的值,返回负数
从大到小:
this的值>o的值,返回负数
this的值<o的值,返回正数
相等返回0
package com.by.entity; public class Student implements Comparable<Student>{ private String name; private int age; private double score; //省略getter、setter、构造 @Override public int compareTo(Student o) { //根据学生成绩从高到底排序 if (this.score > o.score) { return -1; } else if (this.score < o.score) { return 1; } return 0; } }
实现Comparator接口,重写compare方法
思路: 让参数o1和o2进行比较
实现:在集合创建处的小括号内传入实现类对象
Set<Student> set = new TreeSet<>((o1,o2)->{ //根据学生成绩从低到高 if (o1.getScore() > o2.getScore()) { return 1; } else if (o1.getScore() < o2.getScore()) { return -1; } return 0; });
默认识别Comparable,但是Comparator优先级更高
Comparator更能保护类的单一职责,有利于后期代码的维护, 集合排序扩展性更高,更推荐该方式
TreeSet去重规则:当compareTo或compare方 法返回值为0时去重
Collection的特点
Set的存储特点
Set的常用实现类和特点
Set的遍历方式
哈希表的去重原理
TreeSet自定义排序规则的方式
TreeSet的去重规则
以键值对的形式存储数据
键(key): 无序,无下标,元素不可重复
值(value): 无序,无下标,元素可以重复
HashMap
JDK1.2 底层哈希表实现 线程不安全,效率高
LinkedHashMap
JDK1.2 是HashMap的子类,底层哈希表实现 线程不安全,效率高
TreeMap
JDK1.2 是SortedMap的实现类,底层红黑树实现 线程不安全,效率高
Hashtable
JDK1.0 底层哈希表实现 线程安全,效率低
Properties
JDK1.0 是Hashtable的子类,底层哈希表实现 线程安全,效率低
建议使用多态
Map<键的泛型,值的泛型> 集合名=new 实现类名<>();
K: 键
V: 值
V put(K,V):向集合中添加一个键值对
如果键已经存在,则进行值的覆盖
int size(): 获取集合长度
boolean containsKey(K): 判断集合是否包含指定键
boolean containsValue(V): 判断集合是否包含指定值
V get(K): 根据键获取值
键遍历
Set<K的泛型> keySet(): 获取集合所有的键放入Set集合返回
//先获取所有的键 Set<String> set1 = map.keySet(); //遍历键 set1.forEach(k -> System.out.println(k + " " + map.get(k)));
值遍历
Collection<V的泛型> values(): 获取集合所有的值放入Collection集合返回
//获取所有的值 Collection<String> coll = map.values(); //遍历值 for (String s : coll) { System.out.println(s); }
键值对遍历
Set< Map.Entry<K的泛型,V的泛型> > entrySet(): 将所有的键值对对象放入Set集合返回
entry对象.getKey(): 获取entry对象中的键
entry对象.getValue():获取entry对象中的值
//获取到所有的entry对象 Set<Map.Entry<String, String>> set2 = map.entrySet(); //遍历entry对象 set2.forEach(entry-> System.out.println(entry.getKey()+","+entry.getValue()));
自遍历forEach
集合名.forEach(BiConsumer接口实现类对象)
System.out.println("自遍历-->匿名内部类:"); map.forEach(new BiConsumer<String, String>() { @Override public void accept(String k, String v) { System.out.println(k+" , "+v); } }); System.out.println("自遍历-->lambda:"); map.forEach((k,v)->System.out.println(k+" , "+v));
除去TreeMap之外, 如果键为自定义类型,则必须重写hashCode和equals方法才能实现去重
HashMap可以存放null值,键只能有一个null,值可以存在多个null
LinkedHashMap在HashMap的基础上可以保证元素存入和取出的顺序一致
TreeMap可以根据键进行默认的升序排序
如果键为自定义类型,则需要自定义排序规则,方式与TreeSet一致
键不可为null,否则排序会导致空指针
仍然在compareTo或者compare方法返回值为0时去重
Hashtable无法存放null值
Properties只能存放String类型的数据
不推荐使用多态
无法使用泛型
setProperty(String,String): 向集合中添加一个键值对
String getProperty(String): 根据键获取值
load(输入流): 通过输入流将配置文件的内容读取到集合中
Map的存储特点
Map的常用实现类和特点
Map的创建语法
Map的遍历方式
程序执行过程中不正常的情况
Throwable: 总父类,表示不正常的情况
Error: 错误
无法解决也无法提前避免的问题
通常由硬件设备或内存等问题导致
Exception:异常
可以解决或提前避免的问题
通常由代码导致
分类:
RuntimeException:运行时异常,也称为未检查异常、未检异常等
编译不报错,运行报错
可以处理也可以不处理
java.lang.ArrayIndexOutOfBoundsException:数组下标越界
java.lang.StringIndexOutOfBoundsException:字符串下标越界异常
java.lang.IndexOutOfBoundsException:下标越界异常
java.lang.NullPointerException:空指针异常
java.lang.ClassCastException:类型转换异常
java.lang.NumberFormatException:数据类型转换异常
java.lang.Arithm eticException:数学运算异常
…
都来自于java.lang包
非RuntimeException: 非运行时异常,也称为已检查异常、已检异常等
编译就会报错
必须处理
只要不是运行时异常,就一定是非运行时异常
自动产生: 当运行到有问题的代码时会自动产生异常,程序终止
手动产生:
throw 异常对象;
位置:方法内部
作用:当程序执行到该语句,则会抛出一个异常对象,使程序强制终止
使用: 同一直属范围内,下方不可存在其他有效语句
无法执行
程序终止执行:
代码正常运行结束
执行到异常
执行到return;
上抛异常: 消极处理
访问修饰符 返回值类型 方法名(形参列表)throws 异常类名1,异常类型2,..{ }
作用: 将当前方法的内部异常向上抛至调用者,当前方法无法处理该异常. 此时异常并不会被根治,如果异常最终上抛至虚拟机,则仍然会导致程序终止
使用:
上抛父类异常也可以处理内部的子类异常
调用者需要处理解决的异常类型由throws上抛的类型决定
throw和throws的区别:
位置:throw写在方法内部,throws写在方法声明处
作用:throw是抛出制造异常,throws是上抛解决异常
捕捉并解决异常:积极处理
try{ //有可能出现异常的代码 }catch(有可能匹配的异常类名 引用名){ // 对应的处理方案 }
特点:可以根治异常问题,如果异常正常匹配成功,则程序会继续向下执行
使用:
当try中内容出现异常时,会立即开始匹配catch块,如果catch块匹配成功则程序继续执行,如果失败,则异常仍会出发程序仍然终止
可以存在多个catch块,自上而下的进行匹配
一个try-catch结构至多执行一个catch块
父类异常类型可以兼容匹配子类异常
父类异常需要写在子类异常之下
为了保证匹配一定成功,通常会在catch最下方匹配一个Exception
如果发生的是非运行时异常,则catch块无法捕捉与其无关的非运行时异常,编译报错
异常信息处理:
String getMessage():获取异常的详细信息
void printStackTrace():打印输出异常的追栈(追踪)信息(异常类型+异常的详细信息+异常的触发位置)
finally块
特点:无论如何都会执行,通常用来关闭资源
语法:
try{ }catch(){ }finally{ }
使用;
必须与try结合使用,无法与catch单独结合
当try-catch-finally中出现return语句冲突时,优先执行finally
运行时异常:继承RuntimeException
需要提供有参构造给最大父类Throwable中的详细信息属性赋值
package com.by.exception; /** * 自定义运行时异常类 */ public class MyRuntimeException extends RuntimeException{ public MyRuntimeException(){ } public MyRuntimeException(String message) { super(message); } }
非运行时异常:继承Exception
构造要求与运行时异常一致
package com.by.exception; /** * 自定义非运行时异常 */ public class MyException extends Exception{ public MyException(){} public MyException(String message) { super(message); } }
异常的完整分类
throw和throws的区别
try-catch-finally的使用
自定义异常的写法
将数据在虚拟机内存和本地磁盘之间进行传输
I:input 输入
O:output 输出
相当于管道,作用为进行数据传输
从传输方向上看
输入流:本地磁盘的数据向JVM传输
输出流:JVM数据向本地磁盘传输
从传输单位上看
字节流:以字节为单位进行数据传输。可以传输任意类型的数据,如文本、图片、视频、音频等
字符流:以字符为单位进行数据传输。只能传输文本类型的数据,如.txt、.java、.html等
从传输功能上看
节点流:具有传输功能和意义的流
过滤流:没有传输功能,用来给节点流增强传输能力或增加附加功能
输入流:InputStream 抽象父类
输出流:OutputStream 抽象父类
节点流:FileInputStream
FileInputStream fis=new FileInputStream("本地的文件路径");
路径:
绝对路径:以电脑磁盘为基点的完整路径
FileInputStream fis = new FileInputStream("D:\\test\\a.txt"); FileInputStream fis = new FileInputStream("D:/test/a.txt");
相对路径:以当前项目路径为基点的路径,前提是文件必须在项目下
FileInputStream fis = new FileInputStream("file\\a.txt"); FileInputStream fis = new FileInputStream("file/a.txt");
路径书写必须截至至文件
文件必须存在,否则抛出异常
void close():关闭流链接,释放相关资源。(每个流中都有)
int read(): 读取一个字节返回,读取到达末尾,返回-1
int read(byte[] b): 尝试读取数组长度的数据至数组中, 返回实际读取个数.读取到达末尾,返回-1
package com.by.test; import java.io.FileInputStream; public class TestFIS { public static void main(String[] args)throws Exception { // FileInputStream fis = new FileInputStream("D:\\test\\a.txt"); // FileInputStream fis = new FileInputStream("D:/test/a.txt"); // FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\IdeaProjects\\Chp16\\file\\a.txt"); FileInputStream fis = new FileInputStream("file\\a.txt"); //FileInputStream fis = new FileInputStream("file/a.txt"); //读取一个字节 //利用循环读取文件所有内容 while (true) { //先接收本次读取内容 int n = fis.read(); //判断读取是否到达末尾 if (n == -1) { break; } //未到达末尾,输出查看 System.out.println((char) n); } //利用read(byte[])+循环读取文件所有内容 while (true) { byte[] bs = new byte[3]; //接收本次读取结果 int n = fis.read(bs); //判断读取是否到达末尾 if (n == -1) { break; } //遍历数组查看本次读取结果 for (int i = 0; i < bs.length; i++) { System.out.println(bs[i]); } } //关流 fis.close(); System.out.println("执行成功!"); } }
节点流:FileOutputStream
FileOutputStream fos=FileOutputStream("本地的文件路径",true|false);
如果文件不存在,会自动创建
无法创建文件夹
true表示数据追加,false表示数据覆盖
默认是false
void flush(): 刷新缓冲区,所有输出流中都具备该方法
void write(int ): 向目标文件写入一个字节
void write(byte[] ): 向目标文件写入一个数组的数据
package com.by.test; import java.io.FileOutputStream; public class TestFOS { public static void main(String[] args)throws Exception { FileOutputStream fos=new FileOutputStream("file/b.txt"); //写入一个字节 fos.write(65); fos.write(66); fos.write(67); //写入一个数组 String str = "abcdefg123456"; byte[] bs = str.getBytes(); fos.write(bs); //关流 fos.close(); System.out.println("执行成功!"); } }
JDK7.0之后,提供了自动关流的语法结构,简化了finally的工作内容
try( //需要自动关流的创建语句 ){ }catch(){ }
原理: JDK7.0之后,所有的流都默认实现了AutoCloseable接口,该接口中提供了自动关流所需的close方法
package com.by.test; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class TestFOS2 { public static void main(String[] args) { try ( FileOutputStream fos = new FileOutputStream("file/b.txt"); ){ //写入一个字节 fos.write(65); fos.write(66); fos.write(67); //写入一个数组 String str = "abcdefg123456"; byte[] bs = str.getBytes(); fos.write(bs); } catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败"); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } System.out.println("执行成功!"); } }
原理: 先将文件A中的内容读取到JVM中,再从JVM中将读取内容写入到文件B, 借助JVM来实现A与B之间的数据复制
先读后写
package com.by.test; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class TestFileCopy { public static void main(String[] args) { copy1(); copy2(); } /** * 一次复制一个字节 */ public static void copy1(){ try ( //创建输出节点流-复制到的文件路径 FileOutputStream fos=new FileOutputStream("d:/test/2.pdf"); //创建输入节点流-被复制的文件路径 FileInputStream fis=new FileInputStream("d:/test/1.pdf") ) { //先循环读取文件所有内容 while (true) { int n = fis.read(); if (n == -1) { break; } //将本次读取的字节写入到目标文件 fos.write(n); } System.out.println("复制成功!"); } catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败!"); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } } /** * 一次复制一个字节数组 */ public static void copy2(){ try ( //创建输出节点流-复制到的文件路径 FileOutputStream fos=new FileOutputStream("d:/test/3.pdf"); //创建输入节点流-被复制的文件路径 FileInputStream fis=new FileInputStream("d:/test/1.pdf") ) { //先循环读取文件所有内容 while (true) { //创建数组 byte[] bs = new byte[1024]; //读取一个数组的数据 int n = fis.read(bs); if (n == -1) { break; } //将数组中的数据写入到目标文件 fos.write(bs); } System.out.println("复制成功!"); } catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败!"); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } } }
BufferedInputStream: 输入缓冲过滤流
BufferedOutputStream: 输出缓冲过滤流
BufferedInputStream bis=new BufferedInputStream(fis对象); BufferedOutputStream bos=new BufferedOutputStream(fos对象);
拥有一个内置的数据缓冲区, 文件数据将对接至数据缓冲区中,在缓冲区刷新或关闭时再将内部内容一并的给到目标文件
/** * 一次复制一个字节+缓冲过滤流 */ public static void copy3(){ try ( //创建输出节点流-复制到的文件路径 FileOutputStream fos=new FileOutputStream("d:/test/4.pdf"); //创建输入节点流-被复制的文件路径 FileInputStream fis=new FileInputStream("d:/test/1.pdf"); //添加缓冲过滤流 BufferedOutputStream bos=new BufferedOutputStream(fos); BufferedInputStream bis=new BufferedInputStream(fis) ) { //先循环读取文件所有内容 while (true) { int n = bis.read(); if (n == -1) { break; } //将本次读取的字节写入到目标文件 bos.write(n); } System.out.println("复制成功!"); } catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败!"); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } }
如果先写后读,需要在写入完成后刷新缓冲区才能保证读取的正常进行
调用flush():强刷缓冲区 (推荐)
调用close(): 关流之前也会刷新缓冲区
关流时外层过滤流关闭内层节点流会一并关闭
package com.by.test; import java.io.*; public class TestBuffered { public static void main(String[] args) { try( //输出 BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("file/a.txt")); //输入 BufferedInputStream bis=new BufferedInputStream(new FileInputStream("file/a.txt")) ){ //先写 bos.write("abcd".getBytes()); //刷新缓冲区 bos.flush(); //bos.close(); //再读 while (true) { int n = bis.read(); if (n == -1) { break; } System.out.println((char) n); } }catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败!"); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } } }
ObjectInputStream 对象输入过滤流
ObjectOutputStream 对象输出过滤流
附加功能1: 读写基本类型
附加功能2: 读写引用类型
读取: ois.readXxx() 写入: oos.writeXxx(值); 注: Xxx对应的为基本类型名,首字母大写
由于对象过滤流底层嵌套了缓冲区,所以先写后读操作时,仍然需要在写入完成后刷新缓冲区
为了保证数据的安全性,所以在写入时数据会根据魔数机制对其加密,在读取时在进行解密
package com.by.test; import java.io.*; public class TestObject_Double { public static void main(String[] args) { try ( //输出流 ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("file/c.txt")); //输入流 ObjectInputStream ois=new ObjectInputStream(new FileInputStream("file/c.txt")) ) { //先写 oos.writeDouble(10.5); //强刷缓冲区 oos.flush(); //读取 System.out.println(ois.readDouble()); } catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败!"); e.printStackTrace(); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } } }
读取: Object ois.readObject() 读取到达末尾,抛出EOFException异常 写入: oos.writeObject(对象) 可以自动刷新缓冲区,所以先写后读时无需进行flush
package com.by.test; import java.io.*; public class TestObject_String { public static void main(String[] args) { try ( //输出流 ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("file/c.txt")); //输入流 ObjectInputStream ois=new ObjectInputStream(new FileInputStream("file/c.txt")) ) { //先写 oos.writeObject("床前明月光"); oos.writeObject("疑是地上霜"); oos.writeObject("举头望明月"); oos.writeObject("低头思故乡"); //后读 while (true) { try { String s =(String) ois.readObject(); System.out.println(s); } catch (EOFException e) { //读取到达末尾,跳出循环 break; } } } catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败!"); e.printStackTrace(); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } } }
自定义类必须实现Serializable接口,表示允许被序列化,否则IO流没有读写权限
序列化:拆分对象信息的过程
反序列化:通过信息组装对象的过程
将属性通过transient修饰符修饰则可以防止其参与序列化
如果对象中有自定义类型的属性,则必须使该属性类型也实现序列化接口或者通过transient修饰符对其修饰
package com.by.entity; import java.io.Serializable; public class Student implements Serializable { private String name; private int age; //防止被序列化 private transient double score; // private Teacher tea; //省略getter、setter、构造、toString }
package com.by.test; import com.by.entity.Student; import java.io.*; public class TestObject_Student { public static void main(String[] args) { try ( //输出流 ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("file/c.txt")); //输入流 ObjectInputStream ois=new ObjectInputStream(new FileInputStream("file/c.txt")) ) { //先写 oos.writeObject(new Student("zhangsan", 20, 88.5)); //后读 System.out.println((Student)ois.readObject()); } catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败!"); e.printStackTrace(); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } } }
怎么理解输出语句?
System是类库中的工具类,out为该工具类中的静态属性,类型为标准输出流类型,print和println系列方法是该流中提供的写入方法,作用为向控制台写入一个内容
流的分类
文件复制的源码(字节复制+缓冲过滤流)
对象过滤流读写自定义类型
传输char和String类型的数据
抽象父类:Reader
节点流:FileReader
int read():读取一个字符,读取到达末尾,返回-1
package com.by.test2; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class TestReader { public static void main(String[] args) { try( //字符输入节点流 FileReader fr=new FileReader("file/d.txt") ){ while (true) { int n = fr.read(); if (n == -1) { break; } System.out.println((char) n); } }catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败!"); e.printStackTrace(); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } } }
抽象父类:Writer
节点流:FileWriter
write(int ):向目标文件写入一个字符
write(String ):向目标文件写入一个字符串
package com.by.test2; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class TestWriter { public static void main(String[] args) { try( //字符输出节点流 FileWriter fw=new FileWriter("file/e.txt") ){ fw.write(65); fw.write(66); fw.write(67); //写入字符串 fw.write("一二三四五"); fw.write("上山打老虎"); System.out.println("写入成功!"); }catch (FileNotFoundException e) { System.out.println("文件路径不正确"); } catch (IOException e) { System.out.println("读写失败!"); e.printStackTrace(); } catch (Exception e) { System.out.println("未知异常!"); e.printStackTrace(); } } }
BufferedReader
String readLine(): 一次读取一行数据,读取到达末尾返回null
package com.by.test2; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class TestBufferedReader { public static void main(String[] args) { try( //字符输入节点流 FileReader fr=new FileReader("file/d.txt"); //添加缓冲过滤流 BufferedReader br=new BufferedReader(fr) ){ while (true) { //接收本次读取内容 String s = br.readLine(); //判断读取是否到达末尾 if (s == null) { break; } System.out.println(s); } }catch ... } }
PrintWriter
BufferedWriter中的方法没有PrintWriter更多更实用
print(值):向目标文件写入一个数据,默认不换行
println(值): 向目标文件写入一个数据,默认换行
println(): 向目标文件写入一个空行
package com.by.test2; import com.by.entity.Student; import java.io.*; public class TestPrintWriter { public static void main(String[] args) { try( //字符输出节点流 FileWriter fw=new FileWriter("file/e.txt"); //添加缓冲过滤流 PrintWriter pw=new PrintWriter(fw) ){ pw.print(5.5); pw.print(true); pw.println(); pw.println("一二三四五"); pw.println("上山打老虎"); Student s = new Student("zhangsan", 20, 99.5); pw.println(s); System.out.println("写入成功!"); }catch... } }
pw对象中的print|println方法写对象与对象过滤流写对象的区别?
pw只是在读写对象引用的toString方法的结果,并未读写对象的完整信息,所以无法对对象进行序列号及反序列化
对象过滤流是在读写对象的完整信息,所以可以对对象进行序列号及反序列化
pw中的print|println方法与输出语句中的有何不同?
pw是将数据写入到目标文件长久保存
标准输出流是将数据写入到控制台临时查看
编码: 原内容–>加密–>数字
解码: 数字–>解密–>原内容
GBK: 简体中文
Big5: 繁体中文
ASC||: 美国
ISO-8859-1: 西欧
Unicode:
UTF-16: java默认编码集,所有内容统一占用2个字节
UTF-8: 行业标准, 所占字节由内容大小决定,通常为1-3个字节
不同编码集拥有独立的编解码方式,之间互不相通
输入: InputStreamReader
输出: OutputStreamWriter
基于字节节点流对象. 将字节流中的内容转换为字符流,并在转换过程中设置数据传输的编码集
InputStreamReader isr=new InputStreamReader(fis对象,"编码集"); OutputStreamWriter osw=new OutputStreamWriter(fos对象,"编码集");
创建字节节点流,声明被操作的文件路径
创建桥转换流,声明操作文件时所用的编码集
添加缓冲过滤流,提高数据操作的便捷性
对同一文件的输入输出操作必须保证编解码集一致
package com.by.test2; import java.io.*; public class Test_ISR_OSW { public static void main(String[] args) { try( //创建输出流 //创建字节输出节点流 FileOutputStream fos=new FileOutputStream("file/m.txt"); //创建桥转换流 OutputStreamWriter osw=new OutputStreamWriter(fos,"GBK"); //添加缓冲过滤流 PrintWriter pw=new PrintWriter(osw); //创建输入流 BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("file/m.txt"),"GBK")) ){ //先写 pw.println("一二三四五"); pw.println("上山打老虎"); pw.println("老虎没打着"); pw.println("打着小松鼠"); //刷新缓冲区 pw.flush(); //后读 while (true) { String s = br.readLine(); if (s == null) { break; } System.out.println(s); } }catch... } }
缓冲过滤流的使用
桥转换流的创建和使用
操作系统(OS)中并发(同时)执行的多个程序任务
宏观并行,微观串行
在一个时间段内,CPU会将时间段划分为若干个时间片,一个时间片是能被一个程序拥有,且只有拥有时间片的程序才能执行自身内容,所以当时间片的划分足够细小,交替频率足够快,就会形成并行的假象,时间上仍然是串行.
是进程的基本组成部分
是进程中并发执行的多个任务
宏观并行,微观串行
一个时间片只能被一个进程拥有,一个进程一次又只能执行一个线程. 由于进程之间交替执行,所以线程之间必定也是交替执行
只存在多线程,不存在多进程
正在执行中的程序才叫进程,其他的都是等待执行的程序
无论是否拥有时间片,线程任务都叫线程
时间片
CPU调度分配, 线程争抢拥有
数据
堆: 堆共享
栈: 栈独立
代码
书写逻辑
继承Thread, 重写run方法
public class MyThread extends Thread { @Override public void run() { for (int i = 1; i <=100 ; i++) { System.out.println(i); } } }
package com.by.test; import com.by.thread.MyThread; public class Test1 { public static void main(String[] args) { Thread t1 = new MyThread(); Thread t2 = new MyThread(); t1.start(); t2.start(); /*t1.run(); t2.run();*/ System.out.println("main结束"); } }
实现Runnable,重写run方法. 在Thread对象的构造中传入任务对象
package com.by.dao.impl; /** * 线程任务 */ public class MyRunnable implements Runnable{ @Override public void run() { for (int i=1;i<=100;i++) { System.out.println(i); } } }
package com.by.test; import com.by.dao.impl.MyRunnable; import javax.print.attribute.standard.RequestingUserName; public class Test2 { public static void main(String[] args) { /*//先创建任务对象 Runnable r = new MyRunnable(); //将任务对象传入线程对象 Thread t1 = new Thread(r);*/ //任务只会执行一次时,可以通过匿名内部类或者lambda简化书写 Thread t1=new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <=100 ; i++) { System.out.println("t1:: "+i); } } }); Thread t2=new Thread(()->{ for (int i = 101; i <=200 ; i++) { System.out.println("t2> "+i); } }); t1.start(); t2.start(); } }
更推荐使用第二种创建方式: 更符合类的单一职责,将线程对象的创建与线程任务的书写分离,更有利于后期的维护
当开启多个线程之后, 线程之间会争抢时间片,拿到时间片的线程执行自身内容,其他线程无法执行,只能继续尝试争抢时间片,直到线程内容执行结束,才会脱离争夺队列
主函数也成为主线程,其一定是首个拥有时间片的线程
当开启多个线程之后,JVM执行结束的标志将从主函数执行结束转换为所有线程执行结束
开启线程需要调用线程对象.start()
方法
也称为阻塞状态
sleep()
Thread.sleep(毫秒数): 使当前线程释放自身时间片, 进入有限期休眠状态,在休眠时间内,该线程无法争抢时间片,休眠结束之后,才可以进入到就绪状态
1秒=1000毫秒
该方法需要处理非运行时异常, run方法不可上抛异常,所以必须通过try-catch处理解决
Thread t1=new Thread(new Runnable() { @Override public void run() { //让当前线程休眠3秒钟 try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("休眠异常"); } for (int i = 1; i <=100 ; i++) { System.out.println("t1:: "+i); } } });
join()
线程对象.join(): 使调用者线程在当前线程之前执行, 当前线程只有等调用者线程执行结束进入死亡状态之后才有可能回到就绪状态.
该方法需要处理非运行时异常,run方法无法上抛,必须通过try-catch处理
package com.by.test; public class Test3 { public static void main(String[] args) { //以下代码执行顺序:t1->t2->t3 Thread t1=new Thread(()->{ for (int i = 0; i < 30; i++) { System.out.println("t1: "+i); } }); Thread t2=new Thread(()->{ //使t1线程在t2线程之前执行 try { t1.join(); } catch (InterruptedException e) { System.out.println("join失败"); } for (int i = 101; i < 130; i++) { System.out.println("t2>"+i); } }); Thread t3=new Thread(()->{ try { t2.join(); } catch (InterruptedException e) { System.out.println("join失败"); } for (char i = 65; i <=90; i++) { System.out.println("t3::"+i); } }); t1.start(); t2.start(); t3.start(); } }
sleep和join的区别?
sleep方法进入的是有限期等待状态,join方法进入的是无限期等待状态
sleep是静态方法,可以直接通过类名调用,join是非静态方法,必须通过线程对象调用
前言: 当一个任务需要多次执行时,如果将任务放置于线程对象Thread中,会浪费内存空间导致不合理的并发,线程池可以解决该问题
管理盛放线程任务, 将需要执行的任务提交执行,任务结束之后池与任务并不会立即销毁,任务对象会回到池中等待下次执行,直到线程池关闭,内部任务才会失效
ExecutorService: 线程池接口
submit(线程任务对象): 提交线程任务使其执行
shutdown(): 关闭线程池
Executors: 线程池工具类,用来获取线程池对象
newCachedThreadPool(): 获取一个不固定并发数量的线程池对象
newFixedThreadPool(int ):获取一个固定并发数量的线程池对象
不固定并发数量的线程池: 所有提交到池中的任务都会同时并发
固定并发数量的线程池: 对应并发数量的任务先并发执行,超出的任务需要等待执行,等池中执行的任务结束让位之后,超出部分的任务才会进入池中执行
Runnable: run()
无返回值,不能上抛异常
package com.by.test; import com.by.dao.impl.MyRunnable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test4 { public static void main(String[] args) { //获取一个不固定并发数量的线程池 // ExecutorService es1 = Executors.newCachedThreadPool(); ExecutorService es1 = Executors.newFixedThreadPool(2); Runnable r1=new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("r1>>"+i); } } }; Runnable r2=new Runnable() { @Override public void run() { for (int i = 50; i < 100; i++) { System.out.println("r2:::"+i); } } }; //提交任务执行 es1.submit(r1); es1.submit(r2); es1.submit(r2); //关闭线程池 es1.shutdown(); } }
Callable: call()
有返回值,可以上抛异常,默认上抛Exception
返回值会存放于一个Future对象中,可以通过Future对象.get()
获取内部的返回值
Callable<返回值类型> c1=new Callable<返回值类型>() { @Override public 返回值类型 call() throws Exception { //... return 值; } };
package com.by.test; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Test5 { public static void main(String[] args) throws Exception { //创建线程池 ExecutorService es = Executors.newCachedThreadPool(); //线程任务1: 计算1-100的和并返回 Callable<Integer> c1=new Callable<Integer>() { @Override public Integer call() throws Exception { int sum=0; for (int i = 0; i < 101; i++) { sum += i; } return sum; } }; //提交任务执行并接收 Future<Integer> f =es.submit(c1); System.out.println(f.get()); System.out.println(es.submit(c1).get()); //关闭线程池 es.shutdown(); } }
当多个线程同时访问同一个临界资源时,原子操作可能被破坏,会导致数据丢失, 就会触发线程安全问题
临界资源: 被多个线程同时访问的对象
原子操作: 线程访问临界资源的过程中不可更改和缺失的操作
每个对象都默认拥有互斥锁, 该锁默认不开启.
当开启互斥锁之后,线程想要访问对象,则在需要拥有时间片的基础上也拥有锁标记,锁标记只能被一个线程拥有,拥有时间片和锁标记的线程才能执行自身内容,在此期间,其他线程只能等正在执行的线程执行结束释放锁标记和时间片之后才能进入就绪状态
synchronized: 开启互斥锁的关键字
思路: 在被线程同时访问的方法上加锁
访问修饰符 synchronized 返回值类型 方法名(参数列表){ }
package com.by.util; import java.util.ArrayList; import java.util.List; /** * 工具类-操作集合属性 */ public class MyList { private List<Integer> list = new ArrayList<>(); /** * 给集合属性添加元素 * @param n 添加的元素值 synchronized: 同步方法 */ public synchronized void insert(int n){ list.add(n); } /** * 查看集合内容 */ public void query(){ System.out.println("集合长度: " + list.size()); for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)+" "); } } }
思路: 让参与临界资源对象访问的线程自身加锁
synchronized(临界资源对象){ //需要被认定为原子操作的代码 }
使用: 所有访问同一临界资源的线程都需要同时添加同步代码块
package com.by.test2; import com.by.util.MyList; public class TestMyList { public static void main(String[] args)throws Exception { //创建两个线程,同时操作工具类,线程1负责往集合中添加元素1-5,线程2负责往集合中添加元素6-10 //添加结束之后查看集合内容 //创建工具类对象 MyList m = new MyList(); Thread t1=new Thread(()->{ for (int i = 1; i <=5 ; i++) { synchronized (m) { m.insert(i); } } }); Thread t2=new Thread(()->{ for (int i = 6; i <=10 ; i++) { synchronized (m) { m.insert(i); } } }); t1.start(); t2.start(); //使t1和t2线程先进行添加操作 t1.join(); t2.join(); //查看集合元素 m.query(); } } /* * 张三上厕所 * 李四上厕所 * * 原子操作: 脱裤子-->蹲下来-->上厕所-->擦屁股-->穿裤子-->冲水-->走人 * *临界资源: 厕所-坑位 * *解决方式1:给厕所大门加锁 *解决方式2:自己给坑位加锁 * * * */
同步方法: 线程执行需要同时争抢时间片和锁标记,写法简单但效率较慢
同步代码块: 线程只需要争抢时间片, 开启互斥锁的线程默认拥有锁标记, 效率较快但写法相对繁琐
悲观锁: 悲观的认为集合一定会出现线程安全问题,所以直接加锁
乐观锁: 乐观的认为集合一定不会出现线程安全问题,如果安全问题发生,再利用算法解决问题(无锁机制)
JDK5.0,发布了一批无锁机制的线程安全的集合类
都来自于java.util.concurrent包
ConcurrentHashMap: CAS算法
compare and swap: 比较并交换
原有值,预期值,结果值: 当原有值与预期值相等时才会将结果值放入内存
int i=1;
i++;
原有值: 1 预期值: 1 结果值:2
CopyOnWriteArrayList:
当集合进行写(增删改)操作时,会先复制出一个副本,在副本中进行写操作,如果过程中出现线程安全问题,则舍弃当前副本,重新复制新的副本重复操作,直至副本中无异常,再将集合引用地址转换向副本地址,一次确保原集合中一定不会发生安全问题
特点: 舍弃写的效率提高读的效率,适用于读操作远多于写操作时
CopyOnWriteArraySet:
原理与CopyOnWriteArrayList一致, 在写时会对元素进行去重
进程和线程的特点
线程的两种创建方式
线程的基础状态及触发时机
sleep和join的区别
线程池的作用的使用
Runnable和Callable的区别
什么是线程安全问题?
如何解决线程安全问题
同步方法和同步代码块的区别
线程安全的集合类及原理
是一种底层技术,通常用于底层框架的编写
类的对象: 是类实例化的结果,可以存在多个
类对象: 是类加载的产物, 通常只有一个, 内部存放类的所有信息(包,属性,方法,构造,父类信息,接口信息等)
类名.class
Class<类名> 对象名=类名.class
对象名.getClass()
Class 对象名=对象名.getClass();
Class.forName
Class 对象名=Class.forName("类的全限定名");
public static void main(String[] args) throws Exception{ Class<Student> c1 = Student.class; Student stu = new Student(); Class c2 = stu.getClass(); Class c3 = Class.forName("com.by.entity.Student"); }
newInstance(): 利用类对象中的无参构造构建一个类的实例对象
Constructor<类名> getDeclaredConstructor(参数列表的类对象1,参数列表的类对象2,..): 获取指定参数列表的有参构造器对象
newInstance(实参列表): 利用指定有参构造器对象构建类的实例对象
public static void main(String[] args) throws Exception{ Class<Student> c1 = Student.class; Student stu1 = c1.newInstance(); Class c3 = Class.forName("com.by.entity.Student"); //获取的对像默认为Object类型,需要进行类型强转 Student stu2 = (Student) c3.newInstance(); stu1.setName("张三"); stu2.setName("李四"); System.out.println(stu1); System.out.println(stu2); //获取全属性的有参构造器对象 //Constructor<Student> con = c1.getDeclaredConstructor(String.class, int.class, double.class); Constructor<Student> con = c3.getDeclaredConstructor(String.class, int.class, double.class); //利用有参构造器对象构建类的实例 Student stu3 = con.newInstance("张三", 20, 99.5); System.out.println(stu3); }
打破封装
提升代码扩展性
打破封装
降低对象属性的安全性
类对象和类的对象的区别
获取类对象的三种方式
通过反射构建类的对象的两种方式
是广大程序员们总结的编码套路
一个类只能被实例化一个对象
无论如何直接创建唯一实例
package com.by.entity; /** * 单例-饿汉式 */ public class ClassA { /* private: 保证外界无法直接访问该属性 static: 1. 保证newClassA方法可以访问 2. 使属性成为静态属性,内存中只会存在一份,如此就能保证实例化操作只会执行一次 * */ private static ClassA ca = new ClassA(); /** * 供外界调用获取该类的唯一实例 * static: 方便外界通过类名直接调用该方法 * @return 当前类的唯一实例对象 */ public static ClassA newClassA(){ return ca; } //构造私有化: 防止外界通过调用构造创建不同对象 private ClassA(){ } }
缺点:有可能浪费对象空间
需要对象时再实例化对象
package com.by.entity; /** * 单例-懒汉式 */ public class ClassB { /* private: 保证外界无法直接访问该属性 static: 1. 保证newClassA方法可以访问 2. 使属性成为静态属性,内存中只会存在一份,如此就能保证实例化操作只会执行一次 * */ private static ClassB cb = null; /** * 供外界调用获取该类的唯一实例 * static: 方便外界通过类名直接调用该方法 * @return 当前类的唯一实例对象 * synchronized: 同步方法,解决线程安全问题 */ public static synchronized ClassB newClassB(){ if (cb==null) {//空值判断: 确定是第一次获取实例 cb = new ClassB(); } return cb; } //构造私有化: 防止外界通过调用构造创建不同对象 private ClassB(){ } }
缺点:线程效率慢
在懒汉式的基础上,利用二次校验+同步代码块尽可能提高线程效率
package com.by.entity; /** * 单例-懒汉式进阶版 */ public class ClassB { /* private: 保证外界无法直接访问该属性 static: 1. 保证newClassB方法可以访问 2. 使属性成为静态属性,内存中只会存在一份,如此就能保证实例化操作只会执行一次 * */ private static ClassB cb = null; /** * 供外界调用获取该类的唯一实例 * static: 方便外界通过类名直接调用该方法 * @return 当前类的唯一实例对象 */ public static ClassB newClassB(){ //判断是否有可能出现线程安全问题,以此决定是否需要加锁 //二次校验:尽可能减少加锁的时机 if (cb==null) { //同步代码块: 防止线程安全问题,临界资源对象:当前类的类对象 synchronized (ClassB.class) { if (cb==null) {//空值判断: 确定是第一次获取实例 cb = new ClassB(); } } } return cb; } //构造私有化: 防止外界通过调用构造创建不同对象 private ClassB(){ } }
在懒汉式的基础上将内容交于一个静态内部类完成
package com.by.entity; /** * 单例-懒汉式-懒加载 */ public class ClassB2 { /* private: 保证外界无法直接访问该属性 static: 1. 保证newClassB2方法可以访问 2. 使属性成为静态属性,内存中只会存在一份,如此就能保证实例化操作只会执行一次 * */ private static ClassB2 cb = null; //private : 防止外界访问该内部类 private static class Inner{ /** * 供外界调用获取该类的唯一实例 * static: 方便外界通过类名直接调用该方法 * @return 当前类的唯一实例对象 */ public static ClassB2 get(){ //判断是否有可能出现线程安全问题,以此决定是否需要加锁 //二次校验:尽可能减少加锁的时机 if (cb==null) { //同步代码块: 防止线程安全问题,临界资源对象:当前类的类对象 synchronized (ClassB2.class) { if (cb==null) {//空值判断: 确定是第一次获取实例 cb = new ClassB2(); } } } return cb; } } public static ClassB2 newClassB(){ return Inner.get(); } //构造私有化: 防止外界通过调用构造创建不同对象 private ClassB2(){ } }
对象的创建和销毁工作交由工厂完成,以此实现在数据操作量较大的情况下分担数据操作的压力提高数据操作的效率,通常用于框架底层
目的: 练习配置文件+IO流+Properties集合+反射的使用
案例: 利用工厂获取学生类的实例对象
在com.xxx.entity包下新建Student学生类
package com.by.entity; public class Student { private String name; private int age; private double score; @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", score=" + score + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getScore() { return score; } public void setScore(double score) { this.score = score; } public Student() { } public Student(String name, int age, double score) { this.name = name; this.age = age; this.score = score; } }
在当前项目下新建xxx.properties
配置文件
内容: 被工厂管理的类的全限定名
书写:
内容以键=值
的方式存放
键值都不能添加双引号
语句末尾不加分号
语句之中不可存在多余字符
一行只能存在一个键值对
StudentClassName=com.by.entity.Student
新建工厂类.提供方法获取实例对象
package com.by.util; import com.by.entity.Student; import java.io.FileInputStream; import java.util.Properties; /** * 工厂类 */ public class MyFactory { /** * 获取学生类的实例对象 * @return 学生对象实例 */ public static Student newStudent(){ Student stu=null; try ( //创建字节输入流 FileInputStream fis=new FileInputStream("classNames.properties") ) { //将配置文件中的内容读取存放至Properties集合 Properties p = new Properties(); p.load(fis); //获取学生类的全限定名 String studentClassName = p.getProperty("StudentClassName"); //通过Class.forName获取类对象 Class c = Class.forName(studentClassName); //通过类对象构建学生实例 stu = (Student) c.newInstance(); } catch (Exception e) { System.out.println("未知异常"); e.printStackTrace(); } return stu; } }
测试类进行测试
package com.by.test; import com.by.entity.Student; import com.by.util.MyFactory; public class TestMyFactory { public static void main(String[] args) { //利用工厂获取学生实例对象 Student s = MyFactory.newStudent(); Student s2 = MyFactory.newStudent(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。