CGBTN2108汇总复习
复习思路:
先抓知识结构主干,再去补充细节
先跟着老师的复习思路走,遇到会的,快速回顾
遇到忘记或者是不会的,先记录,后面自己复习的时候着重回顾
一阶段学习路径:
1 基础语法Basic
1.环境的配置
- 安装JDK
注意:可以安装多个JDK,我们使用的是1.8,环境变量配置哪个,哪个就生效 - 环境变量的配置
JAVA_HOME : 配置的是JDK安装的目录
Path : 配置的是JDK的bin目录,不新建的
CLASS_PATH:配置的是JDK的lib目录 - win+R 打开 ,命令:java -version,出现版本号即为成功
- 开发工具eclipse
- 开发工具IDEA–设置字体大小/颜色
注意: 安装的路径不要出现中文
2.JDK JRE JVM
JDK:Java开发工具包–开发的最小单位
JRE:Java运行时环境–运行的最小单位
JVM:Java虚拟机–负责加载并运行对应的字节码文件.class
- 运行过程: 我们编写的源码是.java为后缀的,通过编译生成的是.class字节码文件,交给JVM来执行
- 跨平台: 只要在不同的操作系统上安装对应的JVM,就可以实现跨平台:一份代码 处处运行
3. 语法基础
1.关键字 :
50个全小写的单词,在Java有特殊的意义,还包含2个保留字const goto
2.标识符 :
字母 数字 下划线 美元符号组成,不能以数字开头,区分大小写,关键字+(true false null)也不可以用作标识符,见名知意
- 类名: Upper驼峰命名:每一个单词的首字母都要大写
- 方法名: Lower驼峰命名: 除了第一个单词以外,其他单词的首字母都要大写
3.注释 :
单行注释 多行注释 文档注释(还可以添加一些额外的信息:作者 时间 版本 参数 返回值类型)
注释可以注释内容,被注释的内容不执行,所以我们可以利用注释的手段做代码的测试
4 变量:
成员变量:类里方法外,类消失,成员变量才会消失
成员有自己的默认值,可以不手动赋值
局部变量:方法里/代码块里,当局部代码结束,局部变量也随之释放
局部变量使用的时候,必须赋值,可以:
声明的时候并且赋值 Cat cat = new Cat();
先声明再赋值 Cat cat; cat = new Cat();
注意:基本类型保存的是值,引用类型保存的是地址值
变量的就近原则:离谁近 就使用谁
- 如果想指定本类的成员变量,使用this.变量名来指定
- 如果想指定父类的成员变量,使用super.变量名来指定
5 八大基本类型:
Java的数据类型分为两类:基本类型 + 引用类型
6.字面值规则:
- 整数类型的字面值类型是int
- 浮点类型的字面值类型是double
- byte short char 三种比int小的类型,可以在范围内直接赋值
- 三种字面值后缀 : L D F
- 三种字面值前缀: 0b-二进制 0-八进制 0x-十六进制
练习题:进制的前缀
package cn.tedu.basic;
/*本类用于测试类型的前缀*/
public class TestTypePre {
public static void main(String[] args) {
//10-2 10-1 10-0
System.out.println(100);//100-10的平方
//操作二进制-0b
//2-2 2-1 2-0
System.out.println(0b100);//4-2的平方
//操作八进制
//8-2 8-1 8-0
System.out.println(0100);//64-8的平方
//操作十六进制
//16-2 16-1 16-0
System.out.println(0x100);//256-16的平方
}
}
7.运算规则:
- 运算结果的数据类型与参与运算的最大类型保持一致 int+int->int double/int->double
- 整数运算会出现溢出的现象,一旦溢出,数据就不正确了(光年案例)
- byte short char三种比int小的类型,运算的时候需要先自动提升int类型,再参与运算
- 浮点数的特殊值:Infinity NaN
- 浮点数运算不精确的问题
8.类型转换
口诀:小转大,直接转 大转小,强制转 浮变整,小数没
- 注意:布尔类型不参与类型转换
- 注意:基本类型之间能否转换,不取决于字节数,字节数只能做参考,取决于类型的取值范围
- 注意:我们这里所说的是基本类型之间的转换,引用类型之间的转换取决于是否有继承关系
比如:你可以说小猫是小动物,但是不能说小猫是小汽车,不然后面的这种错误的情况会报:类型转换异常
练习题:类型之间的转换与字面值规则
package cn.tedu.basic;
/*本类用来测试类型转换
* 1.byte1--short char2--int4--long8--float4--double8
* 2.小到大 直接转:隐式转换,可以直接转换
* 大到小 强制转:显式转换,需要强转,注意发生数据溢出的问题
* 浮变整 小数没:小数部分直接被舍弃
* 3.强制类型转换的格式:目标类型 变量名 = (目标类型)要转换类型的数据;
* */
public class TestTypeChage {
public static void main(String[] args) {
byte a = 10;
short b = a;//不会报错,小转大
int c = 1;
long d = c;//不会报错,小转大
float f = 3.1415f;
double e = f;//不会报错,小转大
long g = 8274624867L;
float h = g;//不会报错,小转大
System.out.println(h);
char i = 'a';
int j = i;//不会报错,小转大
System.out.println(j);//97
int a1 = 1;
byte b1 = 2;
//byte c1 = a1+b1;会报错,大转小
byte c1 = (byte) (a1+b1);//需要强制类型转换
byte d1 = (byte) 128;
System.out.println(d1);//-128,需要强转,注意发生数据溢出的问题
short e1 = 'a';
char f1 = 120;
System.out.println(e1);//97,打印的是编码值
System.out.println(f1);//'x',打印是根据编码找到的字符
float h1 = 32874.435F;
int i1 = (int) h1;//大转小 强制转
System.out.println(i1);//32874 浮变整 小数没
}
}
9.运算符
- 普通的四则运算符 + - * / ,普通的四则运算,并不能直接改变变量本身的值,除非 i = i*10+8
- 取余 % 6%4=2 6%3=0(余数为0表示整除)
- 自增自减运算符
- 可以改变变量本身的值
2)前缀式: 符号在前,先改变变量本身的值(+1/-1),再使用(打印/参与运算…)
3)后缀式: 符号在后,先使用(打印/参与运算…),再改变变量本身的值(+1/-1)
4)注意:不管是前缀式还是后缀式,一定是会改变变量本身的值,区别在于执行的时机不同
- 比较运算符
- 比较运算符最终的结果是布尔类型的
< >= <=
- == 比较的是左右两边的值是否相等 !=比较的是左右两边的值是否不相等
练习题:==比较的练习
package cn.tedu.basic;
public class TestOperator {
public static void main(String[] args) {
//引用类型Cat类型的变量c1 c2 保存的是对象的地址值
Cat c1 = new Cat();
Cat c2 = new Cat();
int[] a1 = {1, 2, 3};
int[] a2 = {1, 2, 3};
int b1 = 4;
int b2 = 4;
boolean f1 = true;
boolean f2 = true;
/* == 如果比较的是基本类型,比较的是值,字面值,具体存的那个数*/
System.out.println(b1 == b2);//true
System.out.println(f1 == f2);//true
/* == 如果比较的是引用类型,比较的也是值,地址值*/
System.out.println(c1 == c2);//false
System.out.println(a1 == a2);//false
}
}
class Cat {
String name;
int age;
public void bark() {
System.out.println("小猫喜欢喵喵叫");
}
}
- 逻辑运算符
- 双与/短路与/&& :判断逻辑与&一致,增加了短路的功能全真才真,有假则假
2)双或/短路或/|| :判断逻辑与|一致,增加了短路的功能全假才假,有真则真
注意:我们这里所说的短路,是指在某些情况下,表达式后半部分就不用计算了,因为我们已经知道了结果,也就是被短路了,短路可以提高程序的性能,但是短路不一定会用到
- 三目运算符: 1 ? 2 : 3; 1是表达式,1真取2,1假取3
- 复合赋值运算符:+= -= *= /=是一种简写的形式,比较方便,运算时会自动进行类型转换
- 赋值运算符: = ,右边给左边
- 拼接功能:+
- 位运算符 : 主要参与的是二进制的运算
&与:全真才真
| 或:全假才假
^异或:相同为0 不同为1
~ 非: 非0为1,非1为0 - 优先级控制:如果表达式的运算比较复杂,需要控制优先级,可以使用小括号
- 拓展:instanceof
10.流程控制
1.顺序结构
是指程序按照顺序一行一行向下执行,可以用来进行输入 输出 计算等的操作
但是,顺序结构不可以完成先做判断,再做选择的流程,因为顺序结构中所有的代码都会被执行到
2.分支结构
- 单分支结构
if(判断条件){
如果判断条件的结果为true,就执行此处代码,不符合条件,此处跳过
}
- 多分支结构
if(判断条件){
如果判断条件的结果为true,就执行此处的代码
}else{
如果不符合条件,执行else处的代码
}
- 嵌套分支结构
if(判断条件1){
符合判断条件1,执行此处代码,不符合,继续向下判断
}else if(判断条件2){
符合判断条件2,执行此处代码,不符合,继续向下判断
}else if(判断条件3){
符合判断条件3,执行此处代码,不符合,继续向下判断
}else{
保底选项,以上条件均不符合的情况下,执行此处代码
}
练习题:嵌套分支的练习
需求:提示并接收用户输入的月份,判断并输出属于哪个季节
1-12月是合法数据 3~5春天 6~8夏天 9~11秋天 其他情况冬天
package cn.tedu.basic;
import java.util.Scanner;
/*本类用于复习分支结构*/
public class TestIf {
public static void main(String[] args) {
//1.提示并接收用户输入的月份
System.out.println("请输入您要查看的月份:");
int month = new Scanner(System.in).nextInt();
//2.对用户输入的数据进行合法性检测
/*如果if后的语句只有一句,大括号可以省略不写*/
/*return关键字除了可以帮我们返回方法的返回值以外
* 还可以直接结束当前的方法,如果遇到了return,本方法会直接结束*/
// if(month <0 || month >12) return;
// System.out.println("今天天气真不错");//这句话是用来检测main()有没有结束
if(month <= 0 || month >12){
System.out.println("您输入的月份不正确!应该是1-12月以内");
}else{
//3.判断接收到的合法数据属于哪个季节,并将结果输出
if(month >=3 && month <=5){
System.out.println(month+"月是春天");
}else if(month >=6 && month <=8){
System.out.println(month+"月是夏天~");
}else if(month >=9 && month <=11){
System.out.println(month+"月是秋天");
}else{
System.out.println("冬天就要来啦,春天还会远吗?");
}
}
}
}
3.选择结构
- 小括号中变量支持的类型:byte short char int String
- 注意: 如果配置了default默认选项,而且没有任何的case被匹配到,就会执行default后的操作
- case的个数 是否加break 是否加default全部都是可选的,根据自己的具体业务做决定
- 小括号中变量的类型必须与case后value的类型一致
- 执行顺序:先拿着变量的值,依次与每个case后的值做比较,如果相等,就执行case后的语句
若这个case后没有break,就会继续向下执行下一个case,如果一直没有遇到break,就会发生穿透现象,包括default
switch (变量名){
case value1 : 操作1;break;//可选
case value2 : 操作2;break;//可选
case value3 : 操作3;break;//可选
case value4 : 操作4;break;//可选
default:保底选项;//可选
}
练习题:根据颜色推荐菜品switch
package cn.tedu.basic;
import java.util.Scanner;
/*本类用于复习switch*/
public class TestSwitch {
public static void main(String[] args) {
//需求:接收用户今天输入的颜色,推荐菜品
//1.提示并接收用户输入的颜色
System.out.println("请输入您今天心情的颜色:");
String color = new Scanner(System.in).nextLine();
//2.完成选择结构
switch (color) {
case "红":
System.out.println("红烧鱼");
break;//为了避免穿透
case "黄":
System.out.println("菠萝炒饭");
break;
case "橙":
System.out.println("水煮肉片");
break;
default:
System.out.println("哎呀,没有识别到这个功能呢,正在开发中...");
}
}
}
4.循环结构
可以帮我们多次重复的做某一件事
1.for循环
for(开始条件;循环条件;更改条件){
如果符合循环条件,就会执行循环体里的内容
}
注意:我们定义的循环变量,用来控制循环,循环变量可以取到几个值,循环就执行几次
所以开始条件只会执行一次
注意:从哪开始 到哪结束 如何变化
for(开始条件;循环条件;更改条件){//外层循环
for(开始条件;循环条件;更改条件){//内层循环
循环体
}
}
注意:外层循环控制的是行数,内层循环控制的是列数
注意:外层循环控制的是轮数,内层循环控制的是在这一轮中执行的次数
2.while循环
while(判断条件){
如果符合判断条件,继续循环
}
注意:常用来完成死循环,但死循环必须设置出口!
练习题:while循环练习
package cn.tedu.basic;
/*本类用作while复习*/
public class TestWhile {
public static void main(String[] args) {
//需求1:通过while循环打印10次"小可爱们中午好~"
f1();
//需求2:通过while循环打印1 2 3 ... 10
f2();
//需求3:通过while循环打印1 3 5 7 9 ... 99
f3();
//需求4:通过while计算:1+2+3+4+...+10
f4();
//需求5:通过while计算:2+4+6+8...+100
f5();
}
private static void f1() {
//需求1:通过while循环打印10次"小可爱们中午好~"
//1.定义一个变量用来控制次数
int count = 1;
//2.定义一个循环,结束条件是count>10
while (count <= 10) {
System.out.println("小可爱们中午好~");
count++;//注意count的值需要自增,不然就是一个死循环
}
System.out.println(count);//11
}
private static void f2() {
//需求2:通过while循环打印1 2 3 ... 10
int i = 1;
while (i <= 10) {
System.out.println(i);
i++;
}
}
private static void f3() {
//需求3:通过while循环打印1 3 5 7 9 ... 99
int num = 1;
while (num < 100) {
System.out.println(num);
//num++;//12345...
//num += 2;
num = num + 2;//13579...
}
}
private static void f4() {
//需求4:通过while计算:1+2+3+4+...+10
int i = 1;//用于控制循环,相当于循环变量
int sum = 0;//用来保存求和的结果
while(i <= 10){
sum += i;
//sum = sum + i;
i++;
}
System.out.println("1到10累加的结果为:"+sum);
}
private static void f5() {
//需求5:通过while计算:2+4+6+8...+100
int i = 2;//循环变量控制循环
int sum = 0;//用来保存求和的结果
while(i <= 100){
sum += i;//累加
i += 2;//循环变量递增
}
System.out.println("2到100累加的结果为:"+sum);
}
}
3.do-while循环
do{
循环体
}while(判断条件);
11. 方法
- 格式: 修饰符 返回值类型 方法名(参数列表){ 方法体 }
- 如何确定我们要调用哪个方法呢?方法名+参数列表
- 如果一个方法被调用,就会执行这个方法里的内容,执行完毕,再返回到调用的位置,从哪里调用,就返回到哪里
如果这个方法有返回值,我们有两种选择:
- 选择只执行功能,不接收返回值,不再继续使用这个方法的结果
- 选择在方法调用的位置接收这个方法的返回值,接收到的返回值可以在方法外继续使用
- 方法的重载:在同一个类中出现多个方法名相同,但参数列表不同的方法
注意:方法是否构成重载,取决于参数列表中参数的个数与参数的类型,与参数的名字无关
重载的意义: 重载不是为了程序员方便,而是为了方便外界调用这个名字的方法时
不管传入什么类型的参数,都可以匹配到对应的方法来执行,程序会更加的灵活 - 方法的传值 : 如果方法的参数类型是基本类型,传入的是实际的字面值,如果是引用类型,传入的是地址值
形参:形式意义上的参数,比如方法参数列表的参数名,光看参数名是无法确定这个变量的值是多少的
实参:实际意义上的参数,比如我们的局部变量,比如成员变量,比如调用方法时传入的数字 - 方法的重写: 子类继承了父类以后,想要在不改变父类代码的情况下,实现功能的修改与拓展
重写遵循的规则:两同 两小 一大
一大: 子类方法的修饰符权限 >= 父类方法的修饰符权限
两同: 方法名与参数列表与父类方法保持一致
两小: 子类方法的返回值类型 <= 父类方法的返回值类型,注意:这里的<=说的是继承关系,不是值的大小
子类方法抛出的异常类型 <= 父类方法抛出的异常类型 - 拓展 : 方法的递归
练习题:方法调用的顺序
package cn.tedu.basic;
/*本类用于练习方法的调用*/
public class MethodDemo {
//1.创建入口函数
public static void main(String[] args) {
System.out.println("main() is start...");
m1();
System.out.println("main() is stop...");
}
//2.创建m1()
private static void m1() {
System.out.println("m1() is start...");
m2();
System.out.println("m1() is stop...");
}
//3.创建m2()
private static void m2() {
System.out.println("m2() is start...");
}
}
12. 数组
- 数组的创建方式:
静态创建 int[] a = {1,2,3,4,5};
静态创建 int[] a = new int[]{1,2,3,4,5};
动态创建 int[] a = new int[5]; 后续可以动态的给数组中的元素赋值
注意:不管是什么样的创建方式,都需要指定数组的类型与长度 - 我们可以通过数组的下标来操作数组中的元素
数组下标从0开始,最大下标是数组的长度-1,如果访问到了不是这个数组的下标,会出现数组下标越界异常
比如:a[5]表示的就是数组中的第6个元素 - 数组的长度:数组一旦创建,长度不可改变,长度指的是数组中元素的个数a.length,并且数组的长度允许为0:[ ]
- 数组的创建过程:
- 根据数组的类型与长度开辟一块连续的内存空间
- 对数组中的每个元素进行初始化,比如int数组的默认值就是0
- 生成数组唯一的一个地址值,交给应用类型变量a来保存
- 后续可以根据数组的下标再来操作数组中的具体元素
注意: 数组名a这个变量,保存的是数组的地址,不是数组中的具体元素
- 数组的工具类Arrays
- toString(数组名) : 除了char类型的数组以外,其他类型的数组想要查看具体元素,需要使用本方法,否则打印的是地址值
- copyOf(原数组名,新数组的长度) : 用来实现数组的复制 扩容 缩容
如果新数组的长度 > 原数组长度,就扩容,反之,则缩容,如果两者长度一致,就是普通的复制数组
注意:一定是创建了新数组,而不是对原数组的长度做操作
- 数组的遍历
一般习惯使用for循环,循环变量代表的就是数组的下标,从0开始,最大值是a.length-1 - 冒泡排序 :
外层循环控制比较的轮数,所以循环变量从1到n-1轮,代表的是轮数
内层循环控制的是这一轮中比较的次数,而且是数组中两个相邻元素的比较,所以循环变量代表的是数组的下标
练习题: 数组练习1
需求1: 求数组中所有元素之和
需求2: 求数组中所有元素的最大值
package cn.tedu.basic;
/*本类用于复习数组的操作*/
public class TestArray1 {
public static void main(String[] args) {
//需求1:求出数组中所有的元素之和
f1();
//需求2:求出数组中所有元素的最大值
f2();
}
private static void f2() {
//需求2:求出数组中所有元素的最大值
//1.定义一个数组
int[] a = {45, 8, 90, 34, 65, -9};
//2.定义一个变量,用来存储结果,也就是数组中所有元素的最大值
int max = a[0];//这个位置不能写0,应该是数组中第一个元素的值
//3.遍历数组,比较出最大值
for (int i = 0; i <= a.length - 1; i++) {
//4.判断max与a[i]的大小
if(a[i] > max){
max = a[i];//让max保存的一直都是目前遍历到的最大值
}
}
//4.循环结束,输出数组中的最大值
System.out.println("最大值为:"+max);
}
private static void f1() {
//需求1:求出数组中所有的元素之和
//1.定义一个数组
int[] a = {1, 2, 3, 4, 5};
//2.定义一个变量用来保存最终的结果
int sum = 0;
//3.用数组的遍历来进行数据的累加
//i:下标 0 a.length-1 ++
for (int i = 0; i <= a.length - 1; i++) {
sum += a[i];
}
System.out.println("数组元素累计的和为:" + sum);
}
}
练习题: 数组练习2
需求: 将数组中的所有元素逆序输出(3种)
package cn.tedu.basic;
import java.util.Arrays;
/*需求: 将数组中的所有元素逆序输出(3种)*/
public class TestArray2 {
public static void main(String[] args) {
//f1();//实现思路1
//f2();//实现思路2
f3();//实现思路3
}
private static void f3() {
//1.准备原数组
int[] a = {100, 200, 300, 400, 500, 600};
//2.遍历数组,将数组中对应位置上的元素交换位置
for (int i = 0; i < a.length / 2; i++) {
//3.准备一个第三方变量协助数组中的两个元素互换值
//a[0] <-> a[a.length-1-0]
//a[1] <-> a[a.length-1-1]
//a[2] <-> a[a.length-1-2]
//a[i] <-> a[a.length-1-i]
int temp = a[i];
a[i] = a[a.length-1-i];
a[a.length-1-i] = temp;
}
System.out.println(Arrays.toString(a));
}
private static void f2() {
//1.准备原数组
int[] a = {10, 20, 30, 40, 50};
//2.准备一个新数组,数组的长度与原数组长度相同
int[] b = new int[a.length];
//3.准备循环,遍历原数组,并将遍历到的元素复制到新数组中
//i=0,a[0]->b[4]
//i=1,a[1]->b[3]
//i=2,a[2]->b[2]
//i=3,a[3]->b[1]
//i=4,a[4]->b[0]
for (int i = 0; i < a.length; i++) {
b[b.length - 1 - i] = a[i];
}
System.out.println(Arrays.toString(b));
a = b;//把b保存的地址值交给a,这样a b两个引用类型变量保存的是同一个数组的地址值
System.out.println(Arrays.toString(a));
}
private static void f1() {
//1.准备数组
int[] a = {1, 2, 3, 4, 5};
//2.倒着输出
//i:下标 从哪开始a.length-1 到哪结束0 如何变化--
for (int i = a.length - 1; i >= 0; i--) {
//3.把当前遍历到的元素输出
System.out.println(a[i]);
}
}
}
2 面向对象OOP
2.1 面向对象与面向过程
- 两者都是一种编程的思想
- 面向对象强调的是事情的结果,我们通过对象完成对应的功能
- 面向过程强调的是事情的过程,我们做任何事情,都要亲力亲为,经过每一个步骤
- Java是一门面向对象的语言
2.2 类与对象
- 定义类通过关键字class来定义,类是一类事物的抽象,它是抽象的,它是模板
- 创建对象通过new关键字触发构造函数生成,对象是根据类创建出来的具体的内容
- 一个类可以创建出多个对象,对象是根据类的设计来创建的,所以对象具有类的所有属性与功能
- 对象之间是相互独立的,互不影响。我们把创建对象也称作“实例化”
2.3 面向对象的三大特性:封装
- 前提:为了保证数据的安全,也为了程序的使用者能够按照我们预先设计好的方式来使用资源
- 封装属性:用private修饰我们的属性
然后为属性提供对应的getXxx()【获取属性值】与setXxx()【设置属性值】 - 封装方法:用private修饰方法,被修饰的方法只能在本类中使用,所以我们在本类的公共方法里调用这个私有方法
外界如果想要使用这个私有方法的功能,只需要调用这个公共方法就可以了
2.4 面向对象的三大特性:继承
- 前提 :继承可以实现程序的复用性,减少代码的冗余
- 我们通过extends关键字建立子类与父类的继承关系:格式:子类 extends 父类
- 继承相当于子类把父类的功能复制了一份,包括私有资源
注意:虽然私有资源继承了,但是私有资源不可用,原因是被private限制了访问,私有资源只能在本类使用
注意:构造方法不能继承,原因是:构造方法要求名字是本类的类名,我们不能在子类中出现父类名字的构造方法 - 继承是可以传递的:爷爷的功能会传给爸爸,爸爸的功能会传给孙子
注意:爸爸从爷爷那里继承的功能,也会一起传给孙子 - Java的类是单继承的:一个子类只能有一个父类,但是一个父类可以有多个子类
- 子类在继承了父类以后,如果对父类的功能不满意
可以在不修改父类功能的【满足OCP原则】前提下,在子类中,重写继承过来的这个方法
重写需要满足的规则:两同 两小 一大,我们可以在重写的方法上加@Override注解验证是否写对 - 继承是一种is a的关系,强耦合,关联性特别强,而且类的继承机会只有一次,要谨慎使用
- 子类可以直接使用父类的所有非私有资源
2.5 面向对象的三大特性:多态
- 前提:为了忽略子类型之间的差异,统一看作父类类型,写出更加通用的代码
比如:把Cat看作Animal,把Dog看作Animal,把Bird看作Animal,如果方法需要设置传入的参数,可以buy(Animal a)
比如:把算术异常、输入不匹配异常都看作是Exception,统一捕获处理,只写一个解决方案 - 概念:在同一时刻,同一个对象,代表的类型不同,拥有多种形态
- 多态的要求:继承 + 重写
- 多态的口诀1:父类引用指向子类对象:父类型的引用类型变量保存的是子类对象的地址值
- 多态的口诀2:编译看左边,运行看右边:
父类中定义的功能,子类才能使用,否则报错
多态中,方法的定义看的是父类的,方法的实现看的是子类重写后的功能 - 多态中资源的使用:
1)成员变量:使用的是父类的
2)成员方法:对于方法的定义看的都是父类的,对于方法实现,重写后使用的是子类的
3)静态资源:静态资源属于类资源,不存在重写的概念,在哪个类中定义的,就属于哪个类 - 向上造型与向下造型
1)这两种都属于多态,只不过是多态的两种不同的表现形式
2)向上造型【最常用】
可以把不同的子类型都看作是父类型,比如Parent p = new Child();
比如:花木兰替父从军,被看作是父类型,并且花木兰在从军的时候,不能使用自己的特有功能,比如化妆
3)向下造型
前提:必须得先向上造型,才能向下造型
子类的引用指向子类的对象,但是这个子类对象之前被看作是父类类型,所以需要强制类型转换
Parent p = new Child(); 然后:Child c = (Child) p;
比如:花木兰已经替她爸打完仗了,想回家织布,那么这个时候,一直被看作是父类型的花木兰必须经历“解甲归田”【强制类型转换】这个过程,才能重新被看作成子类类型,使用子类的特有功能
为什么有向下造型:之前被看作是父类类型的子类对象,想使用子类的特有功能,那就需要向下造型
2.6 构造方法
- 格式 :修饰符 类名当做方法名(){ } 注意:与类同名且没有返回值类型
- 构造方法作用:用于创建对象,每次new对象时,都会触发对应的构造函数,new几次,触发几次
- 一个类中默认存在无参构造,如果这个构造不被覆盖的话,我们可以不传参数,直接创建这个类的对象
- 如果这个类中提供了其他的构造函数,默认的无参构造会被覆盖,所以记得手动添加无参构
- 构造方法也存在重载的现象:无参构造 含参构造 全参构造【创建对象+给所有的属性赋值】
2.7 this与super
- this代表的是本类,super代表的是父类
- 当本类的成员变量与局部变量同名时,我们可以通过this.变量名指定本类的成员变量
当父类的成员变量与子类的变量同名时,我们可以通过super.变量名指定父类的成员变量 - 我们可以在本类构造函数的第一行
使用this();调用本类的无参构造 / 使用this(参数); 调用本类对应参数的构造方法
构造函数的调用只有这一种方式,或者创建对象时被动触发,不能在外面自己主动调用
构造函数直接不能互相调用,否则会死循环 - 我们可以在子类构造函数的第一行
使用super();调用父类的无参构造 / 使用super(参数); 调用父类对应参数的构造方法
注意:子类默认调用super();父类的无参构造,如果父类没有无参构造,需要手动指定调用哪个含参构造
2.8 对象创建的过程
- 前提:对象是根据类的设定来创建的,目前我们可以在类中添加很多的元素:
属性 方法 静态方法 构造代码块 静态代码块 局部代码块 构造方法…所以不限制类里具体写什么,取决于业务 - 对象创建的过程:Phone p = new Phone();
- 需要在堆内存中开辟一块空间,用来存放对象
- 对象需要完成初始化,比如对应的属性都有自己的对应类型的默认值
- 对象创建完毕后,会生成一个唯一的地址值用于区分不同的对象
- 将这个地址值交给引用类型变量来保存
- 后续如果想要使用这个类的功能,可以从引用类型变量中保存的地址值找到对应的对象做进一步的操作
- 匿名对象:new Phone();
匿名对象是没有名字的对象,所以创建过程:
- 需要在堆内存中开辟一块空间,用来存放对象
- 对象需要完成初始化,比如对应的属性都有自己的对应类型的默认值
- 对象创建完毕后,会生成一个唯一的地址值用于区分不同的对象
那么我们使用匿名对象只能使用一次,并且一次只能使用一个功能
new Phone().video();//创建匿名对象1,调用看直播的方法
new Phone().message();//创建匿名对象2,调用看发短信的方法
2.9 方法的重写与重载
方法的重写:
继承后,子类想在不改变父类代码的前提下,修改或者拓展功能
重写的规则:两同 两小 一大
1. 子类与父类的方法名要保持一致
2. 子类与父类的方法的参数列表要保持一致
3. 子类的返回值类型小于等于父类方法的返回值,这里说的是继承关系,不是值的大小
4. 子类的抛出异常类型小于等于父类方法抛出的异常类型
5. 子类方法的修饰符大于等于父类方法的修饰符
6. 注意:我们可以使用@Override注解标记这是一个重写的方法
方法的重载:
- 在同一个类中,存在两个或者两个以上的方法,方法名相同,但参数列表不同的现象
- 作用:重载提高程序的灵活性,只要用户调用这个方法,不管传入什么参数,都能匹配的上
- 注意:方法是否构成重载,除了前两项以外,看的是参数的个数与类型,与方法的参数名字没关系
3 面向对象的其他知识点
3.1 代码块与它们的执行顺序
静态代码块 static { }
位置:类里方法外
执行时机:随着类的加载而加载,最先加载到内存,优先于对象进行加载,直到类小消失,它才会消失
作用:一般用来加载那些只需要加载一次并且第一时间就需要加载资源,称作:初始化
构造代码块 { }
位置:类里方法外
执行时机:创建对象时执行,创建几次,执行几次,并且优先于构造方法执行
作用:用于提取所有构造方法的共性功能
局部代码块 { }
位置:方法里
执行时机:当其所处的方法被调用时才会执行
作用:用于限制变量的作用范围,出了局部代码块就失效
代码块之间的顺序:
静态代码块 -> 构造代码块 -> 构造方法 -> 普通方法【如果普通方法里有局部代码块,局部代码块才会执行】
3.2 static
- 被static修饰的资源统称为静态资源,可以用来修饰变量、方法、代码块、内部类
- 静态资源属于类资源,随着类的加载而加载,优先于对象进行加载,只加载一次
- 静态资源可以不通过对象,使用类名直接调用,不需要创建对象
- 静态资源只有一份,被全局所有对象共享
- 静态的调用关系:静态资源只能调用静态资源
- 静态资源是优先于对象的,所以静态资源不能与this和super共用
3.3 final
- final表示最终
- 被final修饰的类是最终类,也称作叶子结点,所以不能被继承
- 被final修饰的方法是这个方法的最终实现,不能被重写
- 被final修饰的是常量,值不可以被修改,注意常量定义时必须赋值
3.4 抽象
- 抽象的关键字是abstract
- 被abstract修饰的方法是抽象方法,抽象方法没有方法体
- 如果一个类中出现了一个抽象方法,那么这个类必须被abstract修饰
- 关于抽象类的特点:
1)抽象类中的方法不做限制 : 全普 / 全抽 / 半普半抽
2)如果一个类中的方法都是普通方法,还要声明成抽象类,为什么?
为了不让外界创建本类的对象
3)抽象类不可以创建对象,所以常用于多态
4)抽象类中包含构造方法,但是不是为了自己创建对象时使用,而是为了子类的super()
5)抽象类中也是可以定义成员变量的 - 如果一个子类继承了一个抽象父类,有两种解决方案:
1)作为抽象子类:不实现/实现部分 抽象父类中的抽象方法 : ”躺平”
2)作为普通子类:实现抽象父类中的所有的抽象方法 : “父债子偿” - 面向抽象进行编程:后天重构的结果
3.5 接口
- 接口不是类,定义接口的关键字interface
- 如果一个类想要实现接口中定义的规则,需要使用implments与接口建立实现关系
注意:如果有任何一个抽象方法没有实现,那么这个类就是一个抽象子类 - Java8中接口里的所有方法都是抽象方法
- 接口中只有静态常量,没有普通变量,会自动拼接public static final
- 接口中的方法也可以简写,会自动拼接public abstract
- 接口不可以实例化
- 接口中也没有构造方法,实现类调用的是它自己父类的构造方法,如果没有明确指定父类,那就是Object的
- 接口更多的是规则的制定者,不做具体的实现
- 接口降低了程序的耦合性,更加方便项目的维护与拓展
- 接口是先天设计的结果,这样可以省去后续的多次重构,节省资源
3.6 接口与类的复杂关系
类与类的关系
Java的类只支持单继承,类与类就是继承关系,并且一个子类只能有一个父类
class Son extends Father{ }
接口与接口的关系
Java的接口是不做限制的,可以多继承
interface Inter1 extends Inter2{ } – Inter1是子接口 Inter2 是父接口
interface Inter1 extends Inter2,Inter3{ } – Inter1 是子接口 Inter2 和 Inter3 都是父接口
注意:如果是情况2的话,接口1的实现类需要实现这三个接口(Inter1,2,3)的所有抽象方法
接口与类的关系
Java中的类对于接口而言是多实现的,所以一个类可以实现多个接口
class InterImpl implements Inter1{}
class InterImpl implements Inter1,Inter2{}
3.7 接口与抽象类的区别
- 接口是一种用interface定义的类型
抽象类是一种用class定义的类型 - 接口中的方法都是抽象方法
抽象类中的方法不做限制 - 接口中的都是静态常量
抽象类中可以写普通的成员变量 - 接口没有构造方法,不可实例化
抽象类有构造方法,但是也不可以实例化 - 接口是先天设计的结果,抽象是后天重构的结果
- 接口可以多继承
抽象类只能单继承
3.7 异常
- 异常的继承结构
异常层次结构中的根是Throwable
Error:目前我们编码解决不了的问题
Exception:异常
编译异常:未运行代码就报错了,强制要求处理
运行时异常RunTimeException:运行代码才报错,可以通过编译,不强制要求处理 - 异常的解决方案
- 捕获处理try-catch–自己解决
- 格式:
try{
可能会出现异常的代码
}catch(预测的异常类型 异常的名字){
预先设计的,捕获到异常的处理方案
}finally{
异常处理结构中一定会被执行到的代码块,常用来关流
}
- 向上抛出throws–交给别人解决,在方法定义的两个小括号之间throws,可抛出多个异常,用逗号隔开
- 不能直接把异常抛给main(),因为调用main()是JVM,没人解决了
注意:是否抛出异常取决于自己的业务,比如暂时不处理或者处理不了需要交给别人处理
3.8 内部类
- 我们可以把内部类看作是外部类的一个特殊的资源
- 内部类可以直接使用外部类的所有资源,包括私有资源
- 外部类如果想要使用内部类的资源,需要创建内部类的对象才能使用
- 对象的普通创建方式:
/*外部类名.内部类名 对象名 = 外部类对象.内部类对象*/
Outer.Inner oi = new Outer().new Inner();
成员内部类
位置:类里方法外
1)被private修饰
被私有化的内部类在main()中是没有办法直接创建其对象的
可以在私有内部类所处的外部类中,创建一个公共的方法供外界调用,这个方法用来返回创建好的私有内部类对象
2) 被static修饰
静态内部类可以不创建外部类对象,直接创建静态内部类对象,格式:Outer3.Inner3 oi = new Outer3.Inner3();
如果静态内部类中还有静态方法,那么我们可以不创建对象
直接通过链式加载的方式调用:Outer3.Inner3.show2();//表示通过外部类名直接找到静态内部类,再找到静态方法
局部内部类
位置:方法里
直接创建外部类对象,调用局部内部类所处的方法,并不会触发局部内部类的功能
需要在外部类中创建局部内部类的对象并且进行调用局部内部类的功能,才能触发内部类的功能
匿名内部类
位置:可运行代码中,比如 main()中
匿名内部类通常与匿名对象【没有名字的对象】一起使用
格式:new Inter1(){ 我这个大括号其实是一个匿名内部类,我来实现方法 }.eat();
如果只是想使用一次接口/抽象类的某个功能,可以使用匿名内部类
匿名内部类+匿名对象的功能:创建实现类+实现方法+方法功能的一次调用【功能三合一】
3 基础API
3.1 Object
- 是所有类的超类,Java中的类都直接或者间接的继承了Object
- 如果一个类没有明确指定父类,那么默认继承Object
- Object处于java.lang包之下,不需要导包可以直接使用
- toString()–我们日常使用最频繁的打印语句底层就调用了这个方法
如果没有重写这个方法,使用的是Object的默认实现,打印的是对象的地址值
如果重写以后,以重写的逻辑为准,比如String打印的是串的具体内容,比如ArrayList,打印的是[集合元素] - hashCode()–用于返回对象对应的哈希码值
如果是一个对象多次调用这个方法,返回的是同一个哈希码值
如果是不同的对象调用这个方法,应该返回的是不同的哈希码值 - equals()–用于比较当前对象与参数对象是否相等
重写之前的默认实现比较的是两个对象的地址值
重写之后取决于重写的逻辑,比如String比较的是两个串的具体内容,比如自定义对象比较的是类型+属性值 - equals()与hashCode()应该保持一致【要重写都重写】
解释:equals()底层默认实现比较的是==比较,地址值,重写后我们一般比较的是对象的类型+属性值
hashCode()不同的对象生成的哈希码值不同,那么与equals()的逻辑不匹配,所以也应该重写
重写后,是根据对象的类型与属性值来生成哈希码值,这样二者就一致了
3.2 String
- String底层维护的是一个char[],而且String不可变,因为源码中的数组被final修饰了
- 创建方式:
char[] vlaues = {‘a’,‘b’,‘c’}; String s = new String(values);
String s = “abc”;有高效的效果,因为串存在堆中的常量池,第二次使用时就不再新建了 - 常用方法:
int hashCode() 返回此字符串的哈希码。
boolean equals(Object anObject) 将此字符串与指定的对象比较,比较的是重写后的串的具体内容
String toString() 返回此对象本身(它已经是一个字符串!)。int length() 返回此字符串的长度。
String toUpperCase() 所有字符都转换为大写。
String toLowerCase() 所有字符都转换为小写
boolean startsWith(String prefix) 测试此字符串是否以指定的元素开头。
boolean endsWith(String suffix) 测试此字符串是否以指定的字符串结束。char charAt(int index) 返回指定索引/下标处的 char 值/字符
int indexOf(int ch) 返回指定字符在此字符串中第一次出现处的索引。
int lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引。
String concat(String str) 将指定字符串连接/拼接到此字符串的结尾,注意:不会改变原串
String[] split(String regex) 根据给定元素来分隔此字符串。String trim() 返回去除首尾空格的字符串
byte[] getBytes() 把字符串存储到一个新的 byte 数组中
String substring(int beginIndex) 返回一个新子串,从指定下标处开始,包含指定下标
String substring(int beginIndex, int endIndex) 返回一个新子串,从执定下标开始,到结束下标为止,但不包含结束下标
static String valueOf(int i) 把int转成String
3.3 StringBuilder与StringBuffer
String的:
- 特点:
创建之后长度内容是不可变的,每次拼接字符串,都会产生新的对象 - 优缺点:
优点:String类提供了丰富的关于操作字符串的方法,比如:拼接、获取对应下标处的字符、截取子串等等
缺点:在进行字符串拼接+=的时候,效率比较低 - String转StringBuilder:
String s = “abc”; StringBuilder sb = new StringBuilder(s);
StringBuilder的:
- 特点:
StringBuilder是一个长度可变的字符串序列,在创建的时候,会有一个长度为16的默认空间
当拼接字符串的时候,是在原对象的基础之上进行拼接,如果长度不够就扩容
所以StringBuilder在创建之后,对应的操作一直是用一个对象 - 创建方式:
StringBuilder sb = new StringBuilder();//创建一个长度为16的StringBuilder对象
StringBuilder sb = new StringBuilder(“abc”);//以指定字符串内容为“abc”的方式创建一个StringBuilder对象 - 优缺点:
优点:在拼接的时候,不会产生新的对象,就避免了因为拼接频繁生成对象的问题,提高了程序的效率,使用的是append()
缺点:对于字符串的操作,不太方便 - StringBuilder转String:
StringBuilder sb = new StringBuilder();
sb.append(“abc”);
String s = sb.toString();
总结一句话,拼接多用StringBuilder,用完转回String用String丰富的方法
3.4 包装类
- 基本类型只存值,也没有丰富的功能
所以包装类型是对基本类型做了包装,并提供了很多方便的方法,所以包装类的对象是引用类型的对象 - 创建方式:
Integer i1 = Integer.valueOf();数据只要在-128~127有一个高效的效果
Integer i2 = new Integer(4);没有高效的效果,只是创建了一个包装类的对象
3.5 自动装箱与自动拆箱
- 自动装箱:
编译器会自动把基本类型int5,包装成包装类型Integer
然后交给Integer类型的引用类型变量i3来保存
自动装底层发生的代码:Integer.valueOf(5)
valueOf()的方向:int–>Integer - 自动拆箱:
编译器会自动把包装类型的i1拆掉”箱子“,变回基本类型的数据127
然后交给基本类型int的变量i4来保存
自动拆箱底层发生的代码:i1.intValue();
intValue()的方向:Integer-> int
package cn.tedu.api;
/*本类用于测试自动装箱与自动拆箱*/
public class TestNumber2 {
public static void main(String[] args) {
//1.定义包装类型的数据
Integer i1 = new Integer(127);
Integer i2 = Integer.valueOf(127);
//2.现在的方式:
Integer i3 = 5;//不会报错,这个现象就是自动装箱
int i4 = i1;//不会报错,这个现象就是自动拆箱
}
}
3 高级API
4.1 IO流
- 学习方式:学习抽象父级的公共方法 学习子类流对象的创建方式
- 流的分类
根据方向:输入流 输出流
根据操作单位:字节流 字符流 - 字节输入流InputStream:
InputStream--抽象父类--不能实例化
FileInputStream--文件字节输入流-FIS
BufferedInputStream--高效字节输入流-BIS
FIS in = new FIS(new File(路径));
FIS in = new FIS(路径);
BIS in = new BIS( new FIS(new File(路径)));
BIS in = new BIS(new FIS(路径));
字节输出流OutputStream:
OutputStream--抽象父类,不能实例化
FileOutputStream--文件字节输出流--FOS
BufferedOutputStream--高效字节输出流-BOS
FOS out = new FOS(new File(路径));
FOS out = new FOS(路径);
BOS out = new BOS(new FOS(new File(路径)));
BOS out = new BOS(new FOS(路径));
字符输入流Reader:
Reader--抽象父类--不能实例化
FileReader--文件字符输入流-FR
BufferedReader--高效字符输入流-BR
FR in = new FR(new File(路径));
FR in = new FR(路径);
BR in = new BR(new FR(new File(路径)))
BR in = new BR(new FR(路径));
字符输出流Writer:
Writer--抽象父类,不能实例化
FileWriter--文件字符输出流--FW
BufferedWriter--高效字符输出流--BW
FW out = new FW(File/File,append/String pathname/String pathname,append);
BW out = new BW(Writer--所以传的是子类FW(上面那4种));
注意:这里的append参数表示流向文件输出数据的时候是追加还是覆盖,如果不写,默认false是覆盖,如果改为true,表示追加
4. 序列化与反序列化
序列化与反序列化的作用就是对象的保存与传输
序列化:把内存中的对象通过序列化流输出到磁盘中(比如文件里),使用的流是ObjectOutputStream【把数据写出到文件】
反序列化:通过反序列化流将磁盘中的数据恢复成对象,使用的流是ObjectInputStream【把之前写到文件里的数据读到程序中】
注意1:一个类的对象如果想被序列化,那么这个类必须实现可序列化接口
实现这个接口的目的是相当于给这个类做了一个标记,标记可以序列化
注意2:序列化时会自动生成一个UID,表示当前序列化输出的对象的版本信息
反序列化时会拿着当前的UID与之前序列化输出的UID做比较,一致,反序列化成功,不一致,报错
注意3: 所以,标准操作是一次序列化对应一次反序列化
如果目标对象所在的类没有做任何修改,一次序列化也可以对应多次反序列化(根本原因是UID没变)
4.2 集合
- 泛型
泛型通常与集合一起使用,用来约束集合中元素的类型
泛型< type >必须写引用类型而不是基本类型
泛型方法 public static ==< E > == void get(E[] e){},两处位置都出现了泛型,缺一不可
2.集合被称作Collection,是一个可以存放多个数据的容器,而且集合中提供了丰富的方法来操作集合中的元素
是集合层次的根接口,学习抽象父级的公共方法 - Collection集合方法总结
单个集合的操作:
boolean add(E e) 将指定元素添加到集合中
void clear() 清空集合
boolean contains(Object o) 判断本集合是否包含指定的元素
boolean equals(Object o) 比较集合对象与参数对象o是否相等
int hashCode() 返回本集合的哈希码值
boolean isEmpty() 判断本集合是否为空
boolean remove(Object o) 从本集合中移除指定元素o
int size() 返回本集合中元素的个数
Object[] toArray() 将本集合转为数组
集合间的操作:
boolean addAll(Collection<> c) 将c集合中的所有元素添加到本集合中
boolean containsAll(Collection<> c) 判断本集合是否包含c集合的所有元素
boolean removeAll(Collection<> c) 移除本集合中属于参数集合c的所有元素
boolean retainAll(Collection<> c) 保留本集合与参数集合c的公共元素
集合的迭代:
Iterator iterator() 返回本集合的迭代器
- List接口的特点
- List集合是有下标的
- List集合是有顺序的
- List集合可以存放重复的数据
单个集合的操作:
void add(int index, E element) 在集合的指定下标index处插入指定元素element
E get(int index) 返回本集合中指定下标index处的元素
E remove(int index) 移除本集合中指定下标index处的元素
E set(int index, E element) 用参数元素element替换集合中指定下标index处的元素
int indexOf(Object o) 判断指定元素o在本集合中第一次出现的下标,如果不存在,返回-1
int lastIndexOf(Object o) 判断指定元素o在本集合中最后一次出现的下标,如果不存在,返回-1
List subList(int fromIndex, int toIndex) 截取子集合,包含formidex处的元素,不包含toIndex处的元素
集合间的操作与集合的迭代
boolean addAll(int index, Collection<> c) 将参数集合c中的所有元素,插入到本集合中指定的下标index处
ListIterator listIterator() 返回此列表元素的迭代器,这个是List自己的,不太常用,可以逆序迭代
- List接口的两个常用实现类
ArrayList的特点:
1)底层的数据结构是数组,内存空间是连续的
2)元素有下标,通常可以根据下标进行操作
3)增删操作比较慢,查询操作比较快【数据量大时】
LinkedList的特点:
1)底层的数据结构是链表,内存空间是不连续的
2)元素有下标,但是通常首尾节点操作比较多
3)增删操作比较快,查询操作比较慢【数据量大时】
注意:LinkedList查询慢也不是都慢,首尾操作还是比较快的
简单方法:
void addFirst(E e) 添加首元素
void addLast(E e) 添加尾元素
E removeFirst() 删除首元素
E removeLast() 删除尾元素
E getFirst() 获取首元素
E getLast() 获取尾元素
E element() 获取首元素
功能一致但是名字不太好记的方法:
boolean offer(E e) 添加尾元素
boolean offerFirst(E e) 添加首元素
boolean offerLast(E e) 添加尾元素
E peek() 获取首元素
E peekFirst() 获取首元素
E peekLast() 获取尾元素
E poll() 返回并移除头元素
E pollFirst() 返回并移除头元素
E pollLast() 返回并移除尾元素
- Map接口
Map接口的特点
- map集合的结构是:键值对、KEY与VALUE、Map.Entry<K,V>的映射关系
- map中key值不允许重复,如果重复,对应的value会被覆盖
- map中的映射关系是无序的
- map没有自己的迭代器,所以迭代时通常需要转成set集合来迭代
简单方法:
void clear() 清空集合
boolean equals(Object o) 判断集合对象与参数o是否相等
int hashCode() 返回本集合的哈希码值
boolean isEmpty() 判断集合是否为空
int size() 返回本集合中键值对的个数
map单个集合间的操作
boolean containsKey(Object key) 判断map中是否包含指定的key
boolean containsValue(Object value) 判断map中是否包含指定的value
V get(Object key) 根据指定的key返回对应的value,如果不存在,返回null
V remove(Object key) 删除本集合中参数key对应的键值对
V put(K key, V value) 向集合中添加映射关系(键值对)
void putAll(Map<> m) 向本集合中添加m集合的所有映射关系(键值对)
map的迭代
Collection values() 把本map中的Value值取出放入一个Collection中并返回这个Collection
Set keySet() 把本map中的Key值取出放入一个Set集合中并返回这个Set集合
Set<Map.Entry<K,V>> entrySet()
把本map中的每一对KV都看成是一个Entry,把所有的Entry取出放入一个Set集合中并返回这个Set集合
- Set接口
Set接口的特点
- set集合没有重复的元素
- set集合的元素是无序的
- set集合可以存null值,并且null最多有一个
- 我们自定义对象如果想去重,需要在自定义类中添加重写的equals()与hashCode()
- 集合学习的方法
学习父级的公共方法,学习子类的创建方式,学习各种集合的特点
- 关于List大多都是与下标有关的操作
- 关于Set通常都是去重的操作
- 关于map通常都是映射关系,也就是键值对
- API要常练习,方法互相之间没有任何关系,用哪个,查哪个
4.3 进程与线程
- 程序:数据与指令的集合,而且程序是静态的
- 进程:运行中的程序,给程序加入了时间的概念,不同时间进程有不同的状态,进程是动态的,代表OS中正在运行的程序
进程有独立性,动态性,并发性 - 并行:相对来说资源比较充足,多个CPU同时并发处理多个不同的进程
- 并发:相对来说资源不太充足,多个资源同时抢占公共资源,比如CPU
- 线程:线程是OS能够进行运算调度的最小单位
一个进程可以拥有多个线程,当然,也可以只拥有一个线程,只有一个线程的进程称作单线程程序
注意:每个线程也有自己独立的内存空间,当然也有一部分共享区域用来保存共享的数据 - 线程的几种状态以及线程状态之间的切换
1)新建状态:创建线程对象,申请PCB,对应的是new线程对象
2)就绪状态/可运行状态:万事俱备,只欠CPU,刚刚创建好的线程对象所有资源已经准备好,并且加入到了就绪队列之中
唯有等待操作系统的调度,只要分配了CPU,也就是时间片,当前线程可立即执行,对应的是start()
注意:调用start()并不会立即执行线程对象,这个是由OS的调度规则决定的。我们控制不了
3)执行/运行状态:就绪队列中的线程对象被OS选中,分配了时间片,正在执行
注意:只有就绪状态才能变成运行状态
4)阻塞状态:线程在执行过程中遇到了问题,比如锁阻塞、休眠阻塞、等待阻塞…
注意:我们的阻塞状态,等问题解决了以后/获取了临界资源【要抢占的公共资源】后
是加入到就绪队列中的,转为就绪状态,而不是转为运行状态直接执行
5)终止状态:线程成功执行完毕,释放资源,归还PCB
6)线程的挂起:正在运行中的线程,由于CPU分配的时间片已经用完,所以需要冻结当前线程运行的状态与各项信息
把它插入到就绪队列中,直到下次这个线程被调度执行时,重新恢复现场,继续执行 - 多线程编程实现方案一:extends Thread继承方式
1)自定义一个多线程类用来继承Thread类
2)重写run()里的业务【这个业务是自定义的】
3)创建线程对象【子类对象】
4)通过刚刚创建好的自定义线程类对象调用start()
注意1:不能调用run(),因为这样调用只会把run()看作一个普通的方法,并不会以多线程的方式启动程序
而且调用start()时,底层JVM会自动调用run(),执行我们自定义的业务
注意2:我们除了可以调用默认的父类无参构造以外,还可以调用Thread(String name),给自定义的线程对象起名字,相当于super(name); - 多线程编程实现方案二:implements Runnable 实现方式
1)自定义一个类实现接口Runnable
2) 实现接口中唯一一个抽象方法run()
3) 创建接口实现类的对象,这个对象是作为我们的目标业务对象【因为这个自定义类中包含了我们的业务】
4)创建Thread类线程对象,调用的构造函数是Thread(Runnable target)
5)通过创建好的Thread类线程对象调用start(),以多线程的方式启动同一个业务target
注意1:由于Runnable是一个接口,无法创建对象,所以我们传入的目标业务类,也就是接口实现类的对象
注意2:只有调用start()才能把线程对象加入到就绪队列中,以多线程的方式启动,但是:
接口实现类与接口都没有这个start(),所以我们需要创建Thread类的对象来调用start(),并把接口实现类对象交给Thread(target);
大家可以理解成“抱大腿”,创建的是Thread类的线程对象,我们只需要把业务告诉Thread类的对象就好啦
使用方式二的优势:
1)耦合性不强,没有继承,后续仍然可以继承别的类
2)采用的是实现接口的方式,后续仍然可以实现别的接口
3)可以给所有的线程对象统一业务,业务是保持一致的
4)面向接口编程,有利于我们写出更加优雅的代码 - 多线程编程实现方案三:Executors 创建线程池的方式
1)创建线程池的工具类:Executors.newFixedThreadPool(int n);可以创建包含最多n个线程的线程池对象
2)创建好的线程池对象:ExecutorService
使用pool.excute()来讲线程池中的线程以多线程的方式启动,每次调用都会将一个线程对象加入到就绪队列之中
这个线程池对象负责: 新建/启动/关闭线程,而我们主要负责的是自定义的业务
注意:线程池是不关闭的,实现的效果就是线程池中线程对象的随取随用,这样就避免了频繁的创建与销毁线程,不会造成资源浪费
3)合理利用线程池可以拥有的优势:
1. 降低系统的资源消耗:减少系统创建与销毁线程对象的次数,每个线程都可以重复利用,执行多次任务
2. 提高响应速度:当任务到达时,任务可以不用等待线程创建就能立即执行
3. 提高线程的可管理性:可以根据系统的承受能力,调整线程池中线程的数目
防止因为创建多个线程消耗过多的内存导致服务器的崩溃
【每个线程大约需要1MB的内存,线程开的越多,消耗的内存也就越大,最后死机】 - 多线程数据安全隐患的解决方案
1)出现数据安全问题的原因:多线程程序 + 多个线程拥有共享数据 + 多条语句操作共享数据
2)解决:加锁synchronized同步关键字
1. 同步方法【不太常用】,格式:在方法的定义上加synchronized
2. 同步代码块,格式:
synchronized(唯一的锁对象){
可能会出现数据不安全问题的所有代码
}
注意1:锁对象必须唯一!!!
比如:如果是实现接口的方式,只需要创建一个接口实现类对象。而这个对象当做的是目标业务对象,类中定义的锁对象自然也只有一个
比如:如果是继承Thread类的方式,我们需要创建多个子类对象作为线程对象
那这个时候不同的线程对象间应该共享同一把锁,所以需要把锁设置为静态,被全局所有对象共享
而且建议,此种方式使用的锁对象是本类的字节码对象:类名.class
注意2:加锁时,同步代码块的范围需要考虑, 不能太大,太大效率太低;也不能太小,太小锁不住
注意3:加锁时,锁对象的类型不做限制,只要保证锁对象唯一即可
同步与异步
异步:是多个线程抢占资源的效果,不排队,所以效率高,但是数据不安全
同步:每次只有一个线程独占资源,排队,所以效率低,但是安全,为了安全必要应该牺牲一部分资源
4.4 注解
1.JDK自带的注解(5个)
要求大家掌握的是@Override注解,这个注解可以加在方法上,用来表示这是一个重写的方法
2.元注解5个
元注解是用来定义其他注解的注解,也就是说,注解的语法与JAVA不同,是靠注解来定义的
1. 定义注解的格式:@interface 注解名
2. 可以根据元注解对注解进行设置:
要求大家掌握的是
表示被描述的注解可以使用的位置:值可以多选
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
表示被描述的注解的声明周期:注意值只能3选1
@Retention(RentionPolicy.RUNTIME/SOURCE/CLASS)
3.自定义注解
- 我们也可以根据自己的需求来定义个性化的注解:使用@interface 注解名来定义的,主要使用的就是上面的两个元注解
- 除此之外,我们还可以给注解加功能,比如注解的属性:
格式:属性类型 属性名(); 比如:int age();
注意:定义了注解的普通属性以后,使用注解时必须给属性赋值,格式:@Rice(age=10)
如果给属性设置了默认值,那么使用注解时就不需要给属性赋值了,格式:int age() default 0; - 我们还可以给注解添加特殊的属性value,注意这个属性名字必须是value,类型不作限制
注意:特殊属性如果不设置默认值,使用注解时也需要赋值,不过赋值可以简写,比如@Rice(“apple”)
特殊属性赋予默认值后,就可以直接使用注解了,赋予默认值的格式:String value() default “apple”;
注意:如果有多个属性,并且都没有赋予默认值,那么使用注解时的格式:@Rice(value=“apple”,age=10)
4.5 设计模式
- 概念:是一些前人总结出来的值得学习的编程“套路”,设计模式一共有23种
- 单例设计模式:确保代码中本类的实例只有一个
- 实现思路:
方案一:饿汉式
1)把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
2)通过本类的构造方法创建对象,并把这个对象也私有化,为了防止外界调用
3)提供公共的全局访问点向外界返回本类的唯一的一个对象
注意:公共方法需要设置成静态
–需要跳过对象,通过类名直接调用这个返回本类对象的公共方法
对象也需要设置成静态
的–这个对象需要在静态方法中被返回,而静态只能调用静态
方案二:懒汉式
==延迟加载的思想:==我们有的时候有些资源并不是需要第一时间就创建出来,所以需要延迟到需要时再创建,这样既可以提升性能,又可以节省资源
1)把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
2)创建了一个本类类型的引用类型变量
【这个变量后续用来保存创建出来的对象的地址值】
3)提供公共的全局访问点向外界返回本类的唯一的一个对象
注意:这个公共的方法里,需要做判断
如果引用类型的变量值为null,说明:之前没有创建过本类对象–创建后再赋值给引用类型变量,并把它返回
如果引用类型的变量值不为null,说明:
之前有创建过本类对象,这个引用类型变量保存就是地址值,本次不再新建对象,直接返回
4.6 反射
1. 反射的概念
反射是Java这门语言中比较有特点的一个特征,反射非常强大,我们可以通过反射获取目标类当中的资源,甚至是私有资源
不仅仅如此,我们甚至还可以使用资源,并且创建对象,所以反射是一个经常被使用到的技术
开发过程中,我们有的时候并不能拿到源代码,但是又需要使用资源,那这个时候反射的出现就很有必要了
2. 反射需要用到的API
2.1 获取字节码对象
Class.forName(“类的全路径”); 注意:传入的是类的全路径名,包含包名.类名,而且会抛出异常
类名.class 注意:这个写法需要自己手动接一下获取到的字节码对象,不能用快捷方式的
对象.getClass(); 注意:经常与匿名对象一起使用
2.2 常用方法
获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.getConstructor(int.class,String.class)//要先获取构造方法
clazz.newInstance(666,”海绵宝宝”);//再执行含参构造创建对象
反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
field.setAccessible(true);//使私有成员允许访问
field.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
field.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
3. 关于反射的学习方式
如果能够直接操作源码,就不需要使用反射
反射经常用于源码与三大框架底层之中,熟练掌握有助于编程思想的提高
反射的思路与正常使用略有区别,所以需要多练习,掌握这些写法
重点参考笔记中的8个单元测试方法