Java小记(完整版)觉得有用的话去GitHub点个小星星❤️❤️❤️❤️❤️❤️❤️

JAVASE

Java简史

计算机语言发展历史

image-20230701011211372

  1. 机器语言:简单理解就是0和1,但是由于其复杂难懂所以进化

  2. 汇编语言:将一些常用的机器语言整合,用助记符来代替机器语言那一大串0和1,但是流程控制都是用GOTO来进行,所以很混乱,需要继续进化

  3. 最后就是现在的高级语言了。

    当然高级语言也分为强类型语言和弱类型语言,静态语言和动态语言

    初学者不太需要知道这个概念,以后写多了慢慢了解。

    简略讲一下就是

    强类型就是一旦指定了类型,比如int i;之后要将i转换成byte类型或者String类型那么就需要强制转换。而弱类型则不用,会自动给转换。

    静态类型就是一旦指定了一个类型那么他就是那个类型,动态类型就是比如JS中的var它会根据你给他的值来进行变换。

JAVA体系结构

image-20230701235902651

==JavaSE:标准版,定位在个人计算机上的应用==

这个版本是Java平台的核心,他提供非常丰富的API来开发一般个人计算机上的应用程序。

==JavaEE:企业版,定位在服务器端的应用==

JavaEE是JavaSE的扩展,增加了用于服务器开发的类库。比如JDBC,在Java中使用SQL语法来访问数据库的数据,还有Servlet能够眼神服务器的功能,通过请求响应的模式来处理客户端的请求;JSP是一种可以将Java程序代码内嵌在网页内的技术等等。

==JavaME:微型版,定位在消费性电子产品的应用==

JavaME是JavaSE的内伸,包含J2SE的一部分核心类,也有自己的扩展类,增加了适合微小装置的类库:javax.microedition.io.*等。

Java核心机制

JAVA跨平台原理

image-20230703170325798

首先有一个源文件.java文件(一写代码就是.java文件)然后用JDK编译之后就是.class文件然后就用JDK开始翻译到各个系统中。(虽说用java.exe,但实际上是用各个系统的JVM将字节码文件一行一行的解释成为当前操作系统认识的可执行文件)

数据类型

字面常量

一旦指定就不能被修改,一般用于通俗意义上的常量,比如一年有12个月这种类型,已经固定的量,这个时候我们的代码是用关键字final来修饰这个变量的,然后这个变量就成为了常量。(通常,常量用大写)

1
fianl int PI = 3.14;

变量

变量声明格式:

数据类型 变量名 = 初始值;

1
int a = 10;

==注意==如果只定义一个变量,没有给变量进行赋值的话,那么其实这个变量相当于没有定义,变量如果没有进行赋值的话,那么使用的时候就会出错,告诉你:尚未初始化变量。

变量不可以重复定义,但可以更改值

变量的作用域:作用域是指作用范围,变量在什么范围中有效,作用范围就是离他最近的{}

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test{
int b = 20;
public static void main(String[] args){
System.out.println(a);//错误
int a = 10;
System.out.println(a);
System.out.println(b);
{
int a = 50;//属于变量的重复定义
}
public void eat(){
int a = 30;//不是变量的重复定义
}
}
}

基本数据类型

image-20230704094938483

整数类型

整数类型有:

十进制整数

八进制整数:要求以0开头,比如015

十六进制整数:要求以0x开头或者0X开头

二进制:要求以0b或者0B开头:0b11

image-20230704095851318

==注意==超范围会报错

浮点类型

十进制数形式:3.14,0.314

科学计数法形式:314e2(e大小写都行)

1
2
double f = 314e2;//314*10^2-->31400.0
double f2 = 3143(-2);//314*10^(-2)-->3.14

image-20230704113913888

字符类型

java中使用单引号表示字符常量,字符型在内存中占用2个字节。

image-20230704114109548

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test{
public static void main(String[] args){
char ch1 = 'A';
System.out.println(ch1+90);//155
char ch2 = '\n';
System.out.println("aaa"+ch2+"bbb");
/*aaa换行了
bbb*/
char ch3 = '2' + 2;
System.out.println(ch3); //4
char ch5 = ' ';
System.out.println(ch5);//空格类似于换行了
}
}

扩展:什么是编码?

生活中的案例就是初中早恋的场景

男生: 女生:

我想你–》1001 1001–》我想你

我爱你–》1011 1011–》我爱你

而这个1011和1001就是编码或者字符集

而有权威机构形成的编码表才可以称之为:字符集

常见的有ASCII,GB2312,GBK,Unicode,UTF-8

乱码现象就是由于选择编码方法的不匹配所导致的

布尔类型

boolean类型有两个常量值,true和false,在内存中占一位

一般用于判断逻辑条件。

基本数据类型的转换

类型转换就是在赋值运算或者算术运算的时候,要求数据类型一致,就要进行类型的转换。

类型转换费为两种:一种是自动转换,一种是强制转换。

内存演示:

image-20230704205555552

1
2
3
4
5
6
7
8
public class test {
//这是一个main方法:是程序的入口:
public static void main(String[] args) {
double d = 6;//自动转换:int转换成double
int i = (int) 6.5;//强制转换:double-->int
}
}

Scanner的运用

1
2
3
4
5
6
7
8
9
10
11
12
13
public class test01_If {
//这是一个main方法:是程序的入口:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请录入年龄");
int age = sc.nextInt();
double height = sc.nextDouble();
String name = sc.next();
String sexStr = sc.next();
char sex = sexStr.charAt(0);
//上面两句可以合并成一句 char sex = sc.next().charAt(0);
}
}

运算符

算术运算符

+,-,*,/,%,++(自增), - -(自减)

赋值运算符

=,+=,-=,*=,/=

关系运算符

大鱼号,小于号,==,大于等于,小于等于,!=

逻辑运算符

&,|,&&,||,!,^(异或)

位运算符

&,|,^,~,>>,<<,>>>(无符号右移,就是0顶替,有符号是 跟原来一样的顶替)

与,或,异或,非,右移,左移,无符号右移

流程控制

分支结构

IF

image-20230704211608250

1
2
3
4
5
6
7
8
public class test01_If {
//这是一个main方法:是程序的入口:
public static void main(String[] args) {
if(2>1){
System.out.println("单分支");
}
}
}
if ,else if,else

image-20230704211756805

1
2
3
4
5
6
7
8
9
10
11
12
13
public class test01_If {
//这是一个main方法:是程序的入口:
public static void main(String[] args) {
int a = 10;
if(a<2){
System.out.println("单分支");
}else if(a>10){
System.out.println("你好");
}else{
System.out.println("成功");//成功
}
}
}
随机数

直接用java中依靠的一个类:Math类生成,

这个类中专门用来生成随机数,返回的是【0.0,1.0)

所以一般我们用的时候都是用整数,所以需要强制转换,(int)(Math.random()*6+1)[1,6]

switch

image-20230705091942401

语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class test01 {
//这是一个main方法:是程序的入口:
public static void main(String[] args) {
switch (10/10){
case 10:
case 9:
System.out.println("=9");
break;
case 1:
System.out.println("=1");
break;
default:
System.out.println("算不出来");
}
}
}

1、==switch后面是一个(),()表达式中返回的结果是一个等值,这个等值可以是int,byte,short,char,String,枚举类型==

2、==这个()中的等值会依次跟case后面的值比较,如果匹配成功就会执行:后面的代码(所有代码除非遇到break或者其他)

3、为了防止代码的穿透效果,每个分支后面会加上一个关键词break,遇到break这个分支就结束了

4、default类似于else如果没有匹配的值就会进行default后面的代码,若果在最后一行break可以不写,因为后面没有代码了。

5、swtich一般用于等值判断,并且等值情况比较小的情况。

循环结构

while

image-20230705093114173

循环的四要素:1、条件初始化,2、条件判断,3、循环体4、迭代

1
2
3
4
5
6
7
8
9
10
11
12
public class test01_If {
//这是一个main方法:是程序的入口:
public static void main(String[] args) {
int num = 1;//条件初始化
int sum =0;
while(num<=5){//条件判断
sum += num;//循环体
num++;//迭代
}
System.out.println(sum);
}
}
do while

image-20230705093650429

通常我会理解为先do后while,先把循环体执行一次,再开始循环,所以循环体至少被执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class test01 {
//这是一个main方法:是程序的入口:
public static void main(String[] args) {
int i = 101;
int sum = 0;
do{
sum += i;
i++;
}while(i<=100);
System.out.println(i);
System.out.println(sum);
}
}

什么场合适合do-while

while(考试是否通过){

​ 考试;

}//这种就不适合用while,因为还没考试怎么能知道考试是否同通过呢?

而这时候适合用do-

for循环

image-20230705104039899

1
2
3
4
5
6
7
8
9
10
11
public class test01 {
//这是一个main方法:是程序的入口:
public static void main(String[] args) {
int sum = 0;
for(int i = 0;i<=100;i++){
sum += i;
}
System.out.println(sum);
}
}

关键字

break,continue,return

break关键字的作用是停止循环,停止的是最近的循环(作用域)

continue关键字的作用是:结束本次循环,继续下一次循环

return结束当前正在执行的方法,有时候会充当传输值的关键字。

方法的定义/调用/重载

方法的定义

方法就是用来完成一段特定功能的代码块

形式参数:在方法声明时用于接受外界传入的数据

实参:调用方法时实际传给方法的数据

返回值:方法执行完后返回调佣他的环境的数据

返回值类型:事先约定的返回值的数据类型,如果没有返回值,需要事先指定为void

1
2
3
4
5
6
7
8
9
10
public class test01 {
//这是一个main方法:是程序的入口:
public static int add(int num1,int num2){
return num1+num2;
}
public static void main(String[] args) {
System.out.println(add(12,12));
}
}

方法的作用:可以把重复运用的代码块抽取出来,提高代码的复用性

==注意==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class test01 {
//这是一个main方法:是程序的入口:
public static void changeNum(int num1,int num2){
int t ;
t = num1;
num1 = num2;
num2 = t;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("交换前两个数:"+a+b);//1020
changeNum(a,b);
System.out.println("交换后两个数:"+a+b);//1020
}
}

并没有实现数的交换,方法里只是实现了num1和num2的转换,但是在方法结束后,内存中的changNum方法就删除了,并没有实现数字的交换。

内存分析:首先内存中有栈,堆,方法区,一开始是进入到main方法然后内存会开辟一个main方法的栈帧,然后给a一个内存空间,给b一个,然后在栈里面开一个空间给changeNum,在changeNum里面完成了num1,和num2的交换但是并没有将值传回去,并没有实际修改a和b的数值,所以交换失败。

image-20230706083514921

那么如何修改捏?

有两种方法:第一种就是直接在方法里打印交换的结果

​ 第二种方法需要用到Integer了,后面再说~~😊

image-20230912185618194

方法的重载

方法的重载是指一个类中可以定义多个方法名相同,但是参数不同的方法。调用时,会根据参数,自动匹配对应的方法。本质上是完全不同的方法,只是名称相同而已。

==条件:==

不同的含义:形参类型,形参个数,形参顺序,

返回值不同不能构成方法的重载,形参的名称不同也不能构成方法的重载。

数组

数组的定义:数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。其中,每一个数据称作一个元素,每个元素可以通过一个索引(下标)来访问它们。

数组的四个基本特点:

1、长度是确定的

2、元素的类型必须是相同的类型,不允许出现混合的类型

3、数组的类型可以使==任何==的数据类型,包括基本数据类型和引用数据类型、

4、数组有索引:从0开始,到数组.length-1结束

5、数组变量属于引用类型,数组也是对象

扩展:数组变量属于引用类型,数组也是对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他类型对象,数组对象本身是在堆中存放的。

数组学习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class test01 {
//这是一个main方法:是程序的入口:

public static void main(String[] args) {
//数组的声明
int[] arr;
//如果数组只声明,没有后续操作
//int[] arr = null;空 判别:数组赋值为null和什么都没有赋值是不一样的效果
//创建
arr = new int[4];//给数组开辟了一个长度为4的空间
//编译器声明可以合并成一句话:int[] arr = new int[4];
//赋值
arr[0] = 4;
//使用
System.out.println(arr[0]);//4
System.out.println("数组的长度是:"+arr.length);//4
}
}

数组的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
//对数组进行遍历
int[] scores = {12, 23, 43};
//1、普通for循环
for (int i = 0; i <= scores.length; i++) {
System.out.println(scores[i]);
}
//增强for循环
//对scores数组进行遍历,比那里出来的每个元素都用int类型接受
int count = 0;
//一定要在括号里面int num 不能在外面int 然后将num放进去!
for(int num : scores){
count ++;
System.out.println(num);
}
}
}

数组的三种初始化方式

1、静态初始化

直接在定义数组的同时就为数组元素分配空间并且赋值

1
2
3
4
5
6
7
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
//静态初始化
int[] arr = {12,12,12};
}
}

2、动态初始化

数组定义与为数组元素分配空间并且赋值的操作分开进行

1
2
3
4
5
6
7
8
9
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
//动态初始化
int[] arr ;
arr = new int[3];
arr[0] = 12;
}
}

3、默认初始化 将数组进行初始化默认赋值为0

1
2
3
4
5
6
7
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
//默认初始化
int[] arr = new int[23];
}
}

数组是引用类型,他的元素相当于类的实例变量,因此数组一经分配空间,其中每个元素也被按照实例变量同样的方式被隐式初始化。

image-20230714100131664

数组的最值问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
//找到数组的最大值
//原理类似于打擂
int[] arr = {12,2,34,21,23,4234,34,23,4};
int maxNum = arr[0];
for(int i = 0;i<arr.length;i++){
if(arr[i]>maxNum){
maxNum = arr[i];
}
}
}
}

将最大值的方法提取出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
//找到数组的最大值
//原理类似于打擂
int[] arr = {12,2,34,21,23,4234,34,23,4};
int num = getMaxNum(arr);
System.out.println(num);

}
public static int getMaxNum (int[] arr){
int maxNum = arr[0];
for(int i =0;i<arr.length;i++){
if(arr[i]>maxNum){
maxNum = arr[i];
}
}
return maxNum;
}
}

数组最大值的内存分析

==方法的的实参传递给形参的时候一定要注意,一切都是值传递:

如果是基本数据类型,那么传递的就是字面值

如果是引用数据类型,那么传递的就是地址值。

首先是分为三个栈,堆,方法区

image-20230714104323059

走到main方法里,然后会在栈里面给main方法开辟一个栈帧。

image-20230714104511996

然后就到了arr的静态初始化,开辟了内存空间顺便也给里面的元素开辟了空间顺便进行了赋值操作。

image-20230714105018261

然后就到了getMaxNum方法了,然后就需要在栈里面开辟内存空间,里面开辟了一个maxNum和arr的空间,因为arr是引用数据类型,所以传入的是arr的地址,在栈里面的也是arr的地址。

image-20230714105557942

最后maxNum找到了最大的数4234然后将数值传给num之后这个方法就结束了,然后getNum这个方法就在内存中消失了。

数组查询问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
int[] arr = {2,21,232,32,2,34234,1232};
int index = -1;
for(int i = 0;i<arr.length;i++){
if(arr[i] == 21){
index = i;
break;
}
}
if(index != -1){
System.out.println(index);
}else{
System.out.println("达咩");
}
}
}

添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
int[] arr = {12, 23, 324, 3, 34, 32, 12};
System.out.println("增加前的数组");
for (int i = 0; i < arr.length; i++) {
//这一步是为了输出逗号
if (i != arr.length - 1) {
System.out.print(arr[i] + ",");
} else {
//i==arr.length-1 最后一个元素不用加,
System.out.print(arr[i]);
}
}
//在这个指定位置增加元素
int index = 1;
//将index后面的数后移一位
for (int i = arr.length - 1; i >= (index + 1); i--) {
arr[i] = arr[i - 1];
}
arr[index] = 66;

//增加元素后的数组
for(int i = 0;i<arr.length;i++){
if(i!=arr.length-1){
System.out.print(arr[i]+",");
}else{//i==arr.length-1 最后一个元素不用加,
System.out.print(arr[i]);
}
}
}
}

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
int[] arr = {12,2,3,23,2,2};
System.out.println("删除前的数组");
Arrays.toString(arr);
//找到要删除数字的索引
int index = -1;
for(int i =0;i<arr.length;i++){
if(arr[i] == 2){
index = i;
break;
}
}
//删除
if(index != -1){
//将要删除的元素用后面的元素一个个赋值前移一格,然后将最后一个下标赋值为0
for(int i = index;i<arr.length-2;i++){
arr[i] = arr[i+1];
}
arr[arr.length-1] = 0;
}else{
System.out.println("没有要删除的数组");
}
System.out.println(Arrays.toString(arr));
}
}

详细描述main方法

1、main方法:程序的入口,在同一个类中,如果有多个方法,那么虚拟机就会识别 main方法,从这个方法作为程序的入口

2、main方法格式严格要求:public static void main(String[] args){}

public static :是修饰符

void:代表方法没有返回值,对应的类型为void

main:见名知意名字

String[] args 形参 不确定的因素

3、程序中是否可以有其他的方法也叫main方法?

可以,构成了方法的重载(重载就是方法名相同,形参列表不同,其实是另一个方法了只是名字一样罢了)。

4、形参为String[] 那么实参是什么?(实际传入的是什么呢?)

通过对于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestArray10{
public static void main(String[] args){
//从侧面验证:
//int[] arr1; //如果对数组只声明,没有后续操作,那么相当于 白定义了。
//int[] arr2 = null;
//System.out.println(arr2.length);//Exception in thread "main" java.lang.NullPointerException
//int[] arr3 = new int[0];
//System.out.println(arr3.length);
//int[] arr4 = new int[4];
//System.out.println(arr4.length);

//System.out.println(args.length);//0
//从这个结果证明,参数是String[],实参是 new String[0]
//默认情况下,虚拟机在调用main方法的时候就是传入了一个长度为0的数组

System.out.println(args.length);
for(String str:args){
System.out.println(str);
}
}
}

==//默认情况下,虚拟机在调用main方法的时候就是传入了一个长度为0的数组==

可变参数

可变参数就是提供了一个方法,参数的个数是可变的

int…num

double…num

boolean..,num类似于这样定义

可变参数是在JDK1.5之后加入的新特性

方法的内部对于可变参数处理的跟数组一样

可变参数和其他数据一起作为形参时,可变参数一定要放在最后

1
2
3
4
5
6
7
8
9
10
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {

}
//这个就是可变参数放在最后的意思
public static void method01(int num2,int...num){

}
}

在写代码是建议不要使用可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
method01(30,212,12,21,211,22,2,3,1);
}
public static void method01(int num2,int...num){
for(int i : num){
System.out.println(i+"\t");
}
System.out.println();

System.out.println(num2);
}
}

Arrays工具类

为了方便我们对数组进行操作,系统提供一个类Arrays,我们将它当做工具来使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
int[] arr = {12,2,21,2,3,43,43};
//toString:对数组进行遍历查看,返回的是一个字符串,这个字符串比较好看
System.out.println(Arrays.toString(arr));
//binarySearch:二分法查找:找到指定数组元素对应的下标,使用的前提是一定要查看的是一个有序的数组
//sort:排序,升序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
System.out.println(Arrays.binarySearch(arr,4));
//copyOf:完成数组的复制
int[] arr2 = {2,2,23,3,4};
//复制的是哪一个数组并且复制的长度是多少。
int[] newArr = Arrays.copyOf(arr2,4);
//copyOfRange:区间复制
int[] newArry2 = Arrays.copyOfRange(arr2,1,4);
//equals:比较两个数组的值是否一样
//false ==比较左右两侧的值是否相等,比较的是左右的地址值,返回结果一定是false
//数组的填充
int[] arr5 = {1,2,23,3,2};
Arrays.fill(arr5,10);
System.out.println(Arrays.toString(arr5));
//填充是指所有元素都变成10
}
}

数组的复制操作

我们平时利用Arrays类的数组复制的方法来模仿他进行数组复制,所以简单了解一下。

从API那里看出Arrays类里面有一个arraycopy的静态方法就是用来进行数组复制的

static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)

参数

src —原数组

srcPos – 原数组中的起始位置

dest —目标数组

destPos —目标数组中的起始位置

length – 要复制的数组元素的数量

二维数组

==本质上全部都是一维数组==其实就是数组里面放的东西就是数组

image-20230719091819868

1
2
3
4
5
6
7
8
9
10
11
12
13
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
//定义一个二维数组
// 有3行,n列
int[][] arr= new int[3][];//本质上定义了一个一维数组
int[] a1 = {1,2,3};
arr[0] = a1;
arr[1] = new int[]{12,23,4};
arr[2] = new int[]{43,342,2};
}
}

二维数组四种遍历方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
//定义一个二维数组
// 有3行,n列
int[][] arr = new int[3][];//本质上定义了一个一维数组
int[] a1 = {1, 2, 3};
arr[0] = a1;
arr[1] = new int[]{12, 23, 4};
arr[2] = new int[]{43, 342, 2};
//方式1:外层普通for循环,内层普通for循环
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.println(arr[i][j] + "\t");
}
System.out.println();
}
//方式2:外层普通for循环,内层增加for循环
for(int i = 0;i<arr.length;i++){
for(int num : arr[i]){
System.out.println(num +"\t");
}
System.out.println();
}
//方式3:外层增加墙for循环,内层增强for循环
for(int[] a : arr){
for(int num : a ){
System.out.println(num +"\t");

}
System.out.println();
}

//方式4:外层增强for循环,内层普通for循环
for(int[] a : arr){
for(int i =0;i<a.length;i++){
System.out.println(a[i] + "\t");
}
System.out.println();
}
}
}

二维数组的初始化

还是三种 1、静态初始化 2、动态初始化 3、默认初始化

1、静态初始化

1
2
int[][] arr ={{12,32},{123,34,4}};
int[][] arr = new int {{3,23}}

2、动态初始化

1
2
3
int[][] arr = new int[3][];
arr[0] = new int[]{12234,324,43};
arr[1] = new int[]{23,33,4,3};

3、默认初始化

1
int[] arr = new int[3];

面向对象

面向对象和面向过程主要的区别是考虑模式的区别,二者并不是对立的。

面向对象主要注重于谁来做

面向过程主要注重于怎么做

类和对象的关系

1、万事万物皆对象

2、

对象:具体的事物,模版下具体的产品

类:对 对象向上提取出抽象的部分,公共的部分,形成类,类是抽象的,是一个模板

3、一般写代码的时候先写类,再根据类创建对应的对象

面向对象的三个阶段

【1】面向对象分析OOA

对象:张三,李四,王五

抽取出一个类:人类

类里面有什么:

动态特性–》方法

静态特性-》属性

【2】面向对象设计OOD

先有类,再有对象

类:人类:Person

对象:zhangssan,lisi,wangwu

【3】面向对象编程OOP

创建类

1、 属性 2、 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Person {
//属性
int age;//年龄
String name;//姓名
double height;//身高
double weight;//体重
//方法
public void eat(){
int num = 10;
System.out.println("我在吃饭");
}

}

创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
Person zs = new Person();
//创建一个人类的具体对象
//Person 属于引用数据类型
//第一次加载类的时候,会进行类的加载,初始化创建对象的时候,对象的属性没有给赋值,有默认的初始化的值
zs.name = "张三";
//再次创建对象的时候,就不会进行类的加载了,类的加载只有在第一次需要的时候加载一次
Person ls = new Person();
System.out.println(ls.age);
}
}

局部变量和成员变量

区别1:

成员变量:类中方法外定义的变量

局部变量:方法中定义的变量 代码块中定义的变量

区别2:代码中的作用范围

成员变量:当前类中的很多方法

局部变量:当前一个方法(当前代码块)

区别3:

成员变量有默认值

局部变量没有默认值

区别4:

成员变量不需要初始化而且并不建议初始化。

局部变量一定需要初始化,不然直接使用的时候报错

区别5:内存中的位置不同

成员变量在堆内存中

局部变量在栈内存中

区别6:作用时间不同

成员变量从对象从创建到销毁

局部变量:当前方法从开始到执行完毕

1
2
3
4
5
6
7
8
9
10
public class Student {
int age ; //成员变量在类中方法外
public void study (){
int num = 10;//局部变量在方法中
{
int a ; //局部变量在代码块中

}
}
}

构造器

在创建对象的时候:1、第一次遇到Person的时候,进行类的加载(只加载一次)

2、然后创建对象,为这个对象在堆中开辟空间(new出来的东西一般放在堆中)

3、为对象进行属性的初始化动作

new关键字实际上是在调用一个方法,这个方法叫构造方法(构造器)

调用构造器的时候,如果类中没有写构造器,那么系统会默认给你分配一个构造器,只是我们看不见罢了,建议自己显示的写出来

构造器和方法的区别:

1、没有方法的返回值类型

2、方法体内部不能有return语句

3、构造器的名字必须跟类名一样

构造器的作用:不是为了创建对象,因为在调用构造器之前,这个对象就已经创建好了,并且属性有默认初始化的值。==调用构造器的目的是给属性进行赋值的操作==

注意:我们一般不会在空参构造器中进行初始化操作,因为那样的话每个属性的值就一样了, 实际上我们只用保证空参考构造器的存在就可以了,里面的东西不用写。

构造器的重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Student {
int age ; //成员变量在类中方法外
public void study (){
int num = 10;//局部变量在方法中
{
int a ; //局部变量在代码块中

}
}
public Student(){

}
public Student(int age){
this.age = age;
}
}

面向对象内存分析

1、

1
2
3
4
5
6
7
8
public class test {
int id;
int age;
//这是一个main方法,是程序的入口
public static void main(String[] args) {
Person p1 = new Person();
}
}

image-20230719164553990

第一次创建对象的时候会进行类的加载,然后将字节码信息放在方法区,然后如图所示了。

2、

1
2
3
4
5
6
7
8
9
public class test {
int id;
int age;
//这是一个main方法,是程序的入口
public static void main(String[] args) {
Person p1 = new Person(1,20,"oyy");
}
}

1
2
3
4
5
6
7
8
9
10
11
public class Person {
//属性
int id;
int age;
String school;
public Person(int a ,int b,String c){
id =a;
age = b;
school = c;
}
}

image-20230719170145184

首先是main方法开辟栈帧然后因为这次构造器不是空参构造器了 ,所以会开辟一个Person构造器栈帧。首先先加载字节码信息然后到堆里面有个默认值,然后就到了构造器栈帧给堆里面的属性赋值,赋值完毕后,构造器就被回收了,字符串第一次出现会放在字符串常量池,以后就会直接在字符串常量池里面取了。

this

1、在创建对象的过程中

(1)在第一次遇到一个类的时候,对这个类要进行加载,只加载一次。

(2)创建对象,在堆中开辟空间

(3)对对象进行初始化操作,属性赋值都是默认的初始值

(4)new关键字调用构造器,执行构造方法,在构造器中对属性重新进行赋值。

this指代的就是当前对象。

2、this的用法

(1)this 可以修饰属性

当属性名字和形参发生重名的时候,或者属性名字和局部变量重名的时候,就会发生就近原则,所以如果我要是直接使用变量名字的话就是离得近的那个形参或者局部变量,这个时候要是想要表示属性的话就加上this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person {
//属性
int age;
String name;
double height;
//空构造器
public Person(){
}
//有参构造器
public Person(int age,String name,double height){
this.age = age;
this.name = name;
this.height = height;
}
//方法:
public void eat(){
int age = 10;
System.out.println(age);//就近原则,age指的是离它近的age--》局部变量的age
System.out.println(this.age);//这里指代的就是属性的age
System.out.println("我喜欢吃饭");
}
}

(2)this 可以修饰方法

在同一个类中,方法可以互相调用,this可以省略不写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Person {
//属性
int age;
String name;
double height;
//空构造器
public Person(){
}
//有参构造器
public Person(int age,String name,double height){
this.age = age;
this.name = name;
this.height = height;
}
//方法:
/*public void eat(){
int age = 10;
System.out.println(age);//就近原则,age指的是离它近的age--》局部变量的age
System.out.println(this.age);//这里指代的就是属性的age
System.out.println("我喜欢吃饭");
}*/
public void play(){
/*this.*/eat();
System.out.println("上网");
System.out.println("洗澡");
}
public void eat(){
System.out.println(/*this.*/age);
System.out.println("吃饭");
}
}

(3)this可以修饰构造器

同一个类中的构造器可以相互用this调用,this修饰的构造器必须放在第一行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Person {
//属性
int age;
String name;
double height;
//空构造器
public Person(){
}
//有参构造器
public Person(int age,String name,double height){
this(age,name);
this.height = height;
}
public Person(int age,String name){
this(age);
this.name = name;
}
public Person(int age){
this.age = age;
}
//方法:
/*public void eat(){
int age = 10;
System.out.println(age);//就近原则,age指的是离它近的age--》局部变量的age
System.out.println(this.age);//这里指代的就是属性的age
System.out.println("我喜欢吃饭");
}*/
public void play(){
/*this.*/eat();
System.out.println("上网");
System.out.println("洗澡");
}
public void eat(){
System.out.println(/*this.*/age);
System.out.println("吃饭");
}
}

为什么要用this 来修饰构造器呢?我认为在一个类中如果有很多个构造器的话,在调用构造器的时候,其实已经给我们调用了其他的构造器,我们写this的时候只是将他显示的写出来

static

1、static可以修饰:属性、方法、代码块、内部类

在类加载的时候,会将静态内容也加载到方法区的静态域中,静态的内容先于对象存在这个静态内容被所有该类的对象共享

static 修饰属性:一般官方的推荐访问方式:类名.属性名去访问

1
2
3
4
5
6
7
8
9
10
11
12
public class test {
int id;
static int sid;
//这是一个main方法,是程序的入口
public static void main(String[] args) {
test t1 = new test();
t1.id = 10;
test.sid = 10;

}
}

static 应用场景:某些特定的数据想要在内存中共享,只有一块,这个情况就可以用staitc修饰属性

属性分为:

静态属性

非静态属性

2、static修饰方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Demo {
int id;
static int sid;
public void a(){
System.out.println(id);
System.out.println(sid);
System.out.println("------a");
}
//1.static和public都是修饰符,并列的没有先后顺序,先写谁后写谁都行
static public void b(){
//System.out.println(this.id);//4.在静态方法中不能使用this关键字
//a();//3.在静态方法中不能访问非静态的方法
//System.out.println(id);//2.在静态方法中不能访问非静态的属性
System.out.println(sid);
System.out.println("------b");
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//5.非静态的方法可以用对象名.方法名去调用
Demo d = new Demo();
d.a();
//6.静态的方法可以用 对象名.方法名去调用 也可以 用 类名.方法名 (推荐)
Demo.b();
d.b();

}
}

静态的东西是最先加载的,所以非静态的东西在静态的东西里面调用不了,因为还没加载出来调用不了(我是这么想的)😊😼

代码块

1、类的组成:属性,方法,构造器,代码块,内部类、

2、代码块分为:普通块、构造块、静态块、同步块(多线程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Test {
//属性
int a;
static int sa;
//方法
public void a(){
System.out.println("-----a");
{
//普通块限制了局部变量的作用范围
System.out.println("这是普通块");
System.out.println("----000000");
int num = 10;
System.out.println(num);
}
//System.out.println(num);
//if(){}
//while(){}
}
public static void b(){
System.out.println("------b");
}
//构造块
{
System.out.println("------这是构造块");
}
//静态块
static{
System.out.println("-----这是静态块");
//在静态块中只能方法:静态属性,静态方法
System.out.println(sa);
b();
}
//构造器
public Test(){
System.out.println("这是空构造器");
}
public Test(int a){
this.a = a;
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Test t = new Test();
t.a();
Test t2 = new Test();
t2.a();
}
}

普通块的目的是限制局部变量的作用范围,放在方法中,主要是为了方便看清作用域

构造块是写在外面的里面有具体的功能,主要是为了分区域吧我认为

静态块中只能调用静态属性和静态方法,也是为了分清静态区域

代码执行的顺序:

最先静态块,然后到构造块,然后到构造器,然后到方法中的普通块

包,import

==包的作用是为了解决重名问题(实际上包对应的就是盘符上的目录)解决权限问题==

创建包

名字全部小写

中间用.隔开

一般用公司域名倒着写

加上模块的名字

不能使用系统中的关键字

包的声明位置一般在非注释的第一行

1
2
3
4
5
//声明包:
package com.msb7;
import com.msb2.Person; //导包:就是为了进行定位
import java.util.Date;

(1)==使用不同包下的类要需要导包: import *..*; 例如:import java.util.Date;==
(2)在导包以后,还想用其他包下同名的类,就必须要手动自己写所在的包。
(3)同一个包下的类想使用不需要导包,可以直接使用。
(4)==在java.lang包下的类,可以直接使用无需导包:==

静态导入

1
2
import static java.lang.Math.*;
//导入:java.lang下的Math类中的所有静态的内容

在静态导入后,同一个类中有相同的方法的时候,会优先走自己定义的方法

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
System.out.println(random());
System.out.println(PI);//这里这个PI如果不用静态导入就需要Math.PI来调用,所以静态导入一定程度上减少了代码量
System.out.println(round(5.6));
}
//在静态导入后,同一个类中有相同的方法的时候,会优先走自己定义的方法。
public static int round(double a){
return 1000;
}
}

三大特性

封装、继承、多态

封装:就是public private protected,将属性私有化,其他人不能随意获取这个属性,增加了安全性,即便我们可以通过方法可以访问属性,但是也不能随意访问,因为我们可以在方法中加入限制条件。(就比如家里装了一把锁,虽然也能撬开,但是并不能随意让别人进出家门,增加了安全性)

继承:关键字extends

继承的好处就是提高代码的复用性

父类定义的内容,子类直接拿过来用就可以了,不用代码上的反复重复定义了

一个父类可以有多个子类,一个子类只能有一个直接父类但是可以间接继承自其他类

Object类是所有类的根基父类,所有类都直接或者间接继承自Object类

image-20230720100707260

==权限范围图==

方法的重写(区别于重载)

重写:子类和父类中,当子类的方法对父类的方法不满意的时候,要对父类的方法进行重写。

重写有严格的格式要求:子类方法名字和父类必须一致,参数列表也要和父类一致。

super

super可以修饰属性和方法

在子类方法中可以通过super.属性,super.方法,显示的去调用父类提供的属性,方法。在通常情况,super.可以省略不写。

super修饰构造器:

其实我们平时写构造器的时候第一行都有:super() –>作用是调用父类构造器,只是我们一般省略不写(在子类中写)

在构造器中,super调用的父类构造器和this调用子类构造器只能存在一个,两者不能共存,因为super修饰构造器要放在第一行,this修饰构造器也要放在第一行;。

Object类

所有类都直接或者间接继承自Object类,Object类是所有Java类的根基类,也就意味着所有的java对象都拥有Object类的属性和方法。

如果在类的声明未使用extends关键字声明其父类,则默认继承Object类。

Object类中的toString()的作用。

image-20230720151102653

image-20230720151259440

1
2
3
4
5
6
7
8
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}

Object类中的equals()方法

equlas()比较的是内容是否相等

==比较的是地址是否相等

我们一般不会直接使用父类的方法,而是在子类中对这个方法进行重写

instanceof

instanceof是用来判断是否是这个类的实例

a instanceof b //判断a对象是否是b这个类的实例,如果是返回true,不是返回false

类和类的关系

1、继承关系通常用extends

2、实现关系 implements

3、依赖关系

4、关联关系

5、聚合关系

6、组合关系

前面两种关系是代码层面的关系,后面四种是语义层面的关系并不是很好的可以准确的区分,了解即可。

多态

多态指的是方法的多态,而不是属性的多态

多态就是多种状态:同一个行为,不同的子类表现出来不同的形态。

比如动物有叫的方法,猫类继承动物类,狗类继承动物类,但是猫和狗教的方法是不一样的。

多态的好处:提高代码的扩展性,符合面向对象的原则:开闭原则,扩展式开放的,修改是关闭的。

重点理解

1
2
Pig p = new Pig();
Animal an = p;

将上面的代码合为一句话:
Animal an = new Pig();
=左侧:编译期的类型
=右侧:运行期的类型

多态使用非常常见的场合:父类当方法的形参,传入的是具体的子类对象

然后调用同一个方法,根据传入的不同子类展现不同的效果,这样就构成了多态。

1
2
3
public void play(Animal an){//Animal an = an = new Pig();
an.shout();
}
内存分析

image-20230725090927510

首先加载new出来的Pig放在方法区,然后给p一个内存空间在栈里开辟空间。

然后又给an在栈里面开辟空间,因为左边是编译时期的类型是动物,后面是运行期的类型,所以实际上他是一只猪,然后运行的时候条用的是shout()方法,子类pig重写了Animal的方法,所以调用的时候走的是子类PIG的喊叫方法。

向下转型,向上转型

image-20230725091912367

报错原因:

1、an在编译器的时候是Animal类型,在Animal中没有eat方法,也没有weight属性,这个属性是在Pig中。

2、an只能看到0x99空间中的Animal中的部分内容

image-20230725092227227

编译器的时候p其实还是Animal所以它的地址其实还是0x99,但是运行的时候,可以调用Pig的方法(前提是Animal里面和Pig是相同的,即Pig重写了Animal的的方法。

但是这个时候实在是想要访问Pig的属性和方法该怎么办捏?

这个时候就需要转型啦~。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Pig p = new Pig();
Animal an = p;//转型:向上转型(子类转型成父类)
an.shout();
//加入转型的代码:
//将Animal转为Pig类型:
Pig pig = (Pig)an ;//转型:向下转型(又从父类变成子类Pig了)
pig.eat();
pig.age = 10;
pig.weight = 60.8;
}
}

之前的equals方法也是利用了向下转型的方法来获取子类中特有的内容进行比较。

扩展:

多态的使用有很多种情况,不仅可以使用父类方法做形参,还可以使用父类方法做方法的返回值类型,其返回值类型可以是任意一个子类对象

简单工厂模式的实现,他是解决大量对象创建的一个解决方案,将创建和使用分开,工厂负责创建,使用者直接调用即可。简单工厂模式的使用要求是:

定义一个static方法,通过类名直接调用

返回值是父类类型,返回的可以是其任意子类类型

传入一个字符串型的参数,工厂根据参数创建对应的子类产品。

final

用法1:final修饰的值,变量值不可以改变,这个变量也变成了一个字符常量,预定俗称,常量大写

用法2:final修饰引用数据类型,那么地址值不可以改变

1
2
final Dog d = new Dog();
//d = new Dog(); 这是不行的,但是d的对象属性依旧可以改变

用法3:

1
2
3
4
5
6
7
8
9
10
11
12
    //第3种情况:
final Dog d2 = new Dog();
a(d2);
//第4种情况:
b(d2);
}
public static void a(Dog d){//方法里的d改变了但是实际上main方法里面的d还是没有改变。
d = new Dog();
}
public static void b(final Dog d){//d被final修饰 ,指向不可以改变,方法里的d不可以改变
//d = new Dog();
}

final修饰方法:用final修饰的方法,那么这个方法不可以被子类重写。

final修饰类:final修饰类,代表没有子类,该类不可以被继承:

一旦一个类被final修饰,那么里面的方法也没有必要用final修饰(final省略不写)

==那为什么要用final修饰类呢==

举个例子你就知道了:

JDK提供的Math类:使用Math类的时候无需导包,直接使用就可以了,Math没有子类,不被其他类继承。

里面的属性全部被final修饰,方法也是被fianl修饰的,只是省略不写了,因为子类没有必要进行重写,因为比如PI什么的是固定的,就是不希望别人去修改它。

其次外界不可以创建对象,,Math类中的所有属性,方法都被static修饰,那么不用创建对象去调用,只能通过类名.属性名,类名.方法名 去调用。

这里的话static哪里也有提到静态导入

抽象类,抽象方法

抽象类和抽象方法的关系就像类和方法的关系差不多。

抽象类的作用:

在抽象类中定义抽象方法,目的是为了为子类提供一个通用的模板,子类可以在模板的基础上进行开发,先重写父类的抽象方法,然后可以扩展子类自己的内容。抽象类设计避免了子类设计的随意性,通过抽象类,子类的设计变得更加严格,进行某些程度上的限制。使子类更加的通用。

抽象类中可以有抽象方法,和正常的方法。

在一个抽象类中,子类会有一类方法,子类对这个方法非常满意,无需重写,直接使用(正常的方法),但是总有一些方法是子类对这个方法永远不满意,就会对这个方法进行重写,那么这个时候就需要用抽象方法了,一个方法的方法体去掉,然后被abstract修饰,那么这个方法就变成了抽象方法。

注意事项:

1、如果这个类中有方法是抽象方法,那么这个类也要变成一个抽象类。

2、抽象类可以被其他类继承,一个类继承抽象类,那么这个类可以变成抽象类

3、一般子类不会加abstract修饰,一般会让子类重写父类中的方法,子类继承抽象类必须重写全部的抽象方法。

4、如果子类没有重写全部的抽象方法,那么子类也可以变成一个抽象类。

5、抽象类不可以创建对象。

6、既然抽象类不可以创建对象,但是可以创建继承抽象类的子类,然后利用多态的写法,父类是编译时期类型,子类是运行期类型。

==子类创建对象的过程中一定要调用父类的构造器==

所以由于这个原因,从抽象类即使不能创建对象,也要有构造器给子类调用!!!!!!!!!!!!

抽象类是否可以被final修饰?

不能被final修饰,因为抽象类的初衷就是给予子类继承用的。要是被final修饰了这个抽象类,就不存在继承了,就没有子类。

接口

类和接口是同一概念

接口中没有构造器

接入如何声明:interface

在JDK1.8之前,接口之中只有两个部分内容:

(1)常量:固定修饰符:public static final

(2)抽象方法:固定修饰符:public static final

注意:修饰符可以省略不写,IDE会帮你自动补全,但是初学者建议写上,防止遗忘

1
2
3
4
5
6
7
8
9
10
11
12
public interface TestInterface{
//常量
/*public static final*/ int NUM = 10;
//抽象方法
/*public abstract*/ void a();
}

interface TestInterface{
void e();
void f();
}

类和接口的关系是什么?

实现关系 类实现接口:

一旦实现一个接口,那么实现类要重写接口中的全部的抽象方法:

如果没有重写全部方法,那么这个类可以成为一个抽象类

java 只有单继承,java还有多实现

一个类继承其他类,可以实现多个接口

1
2
3
4
5
6
 public class student extends Person implements TestInterface{
@Override
public void a() {
System.out.println("---1");
}
}

写法通常是先继承再实现

接口不能创建对象

当然也还有多态的写法就是

1
TestInterface02 t = new Student();//接口指向实现类 ---》多态

接口中的常量如何访问?

1
2
3
4
5
6
7
//11.接口中常量如何访问:
System.out.println(TestInterface01.NUM);
System.out.println(Student.NUM);
Student s = new Student();
System.out.println(s.NUM);
TestInterface01 t2 = new Student();
System.out.println(t2.NUM);

接口的作用是什么?

定义规则!那么他跟抽象类不同的地方在哪?他是接口不是类

接口定义好规则之后,实现类负责实现即可。(不需要继承还要重写方法)

继承:子类对父类的继承

实现:实现类对接口的实现

多态的应用场合:

(1)父类当做方法的形参,传入具体的子类对象

(2)父类当做方法的返回值,返回的是具体的子类的对象

(3)接口当做方法的形参,传入的是具体的子类的对象

(4)接口当做方法的返回值,返回的是具体的实现类对象

接口和抽象类的区别:

抽象类:1、用abstract修饰 2、抽象类不能实例化,即不能用new关键字来实例化对象 3、含有抽象方法的类是抽象类必须用abstract修饰 4、抽象类可以含有抽象方法也可以不包含抽象方法,抽象类中可以有具体的方法 5、如果一个子类实现父类(抽象类)的所有抽象方法,子类可以不是抽象类,否则就是抽象类 6、抽象类中的抽象方法只有方法体,没有具体实现。

接口:1、接口用interface修饰 2、接口不能被实例化 3、一个类只能继承一个类,但是可以实现多个接口 4、接口中都是抽象方法 5、接口中不能包含实力域或静态方法(静态方法必须实现,接口中的方法是抽象方法,不能实现)

JDK1.8之后,接口中多加了一个内容:

1、被public default 修饰的非抽象方法

==default必须加上==,实现类中要是想要重写接口中的非抽象方法,那么default必须不能加,否则会出错

实现类中要是想重写接口中的非抽象方法,那么default修饰符必须不能加否则出错。

(抽象方法就是给别人重写的,如果写了个非抽象方法,直接给别人的话,那么就不要加default)

(2)静态方法:

接口中的静态方法static 不能省略不写,其次静态方法不能重写

为什么要在接口中加入非抽象方法呢?

因为如果接口中只能定义抽象方法的话,那么我要是修改接口中的内容,那么对实现类的影响太大了,所有实现类都会受到影响

现在在接口中加入非抽象方法,对现实类没有影响,想调用就去调用即可。

内部类

成员内部类

类的组成有:属性,方法,构造器,代码块(普通块,静态块,构造块,同步块),内部类

一个类中内部还有一个类,那么这个类叫内部类,当然还有外部类(就是内部类的外面一层就是外部类)

内部类:成员内部类(静态的,非静态的)和 局部内部类 (位置:方法内,块内,构造器内)

成员内部类:里面有属性,方法,构造器等。

局部内部类

在局部内部类中访问到的变量必须是被final修饰的

如果一个类在整个项目中只使用一次,那么就没必要单独创建一个类,使用内部类就可以了。

匿名内部类,本质上就是一个没有名字的局部内部类,定义在代码块中,方法中

作用:方便创建子类对象,最终目的是简化代码的编写

格式:

1
2
3
new 类|抽象类名或者接口名(){
重写方法;
}
1
2
3
4
5
6
7
8
9
public Comparable method3(){
//匿名内部类
return new Comparable(){
@Override
public int compareTo(Object o){
return 200;
}
};
}

异常

Exception:在程序运行过程中,发生了不正常的现象,阻止了程序的运行,我们称之为发生异常

可以用if-else来弥补漏洞但是代码臃肿,业务代码和异常代码混在一起,可读性差,程序员需要花费大量的时间来维护这个漏洞,程序员很难堵住所有漏洞。

由于if-else处理异常缺点太多,所以java专门处理可一个异常处理机制:try-catch-finally

异常出现了之后会报错

image-20230801165842411

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class test {

//这是一个main方法,是程序的入口
public static void main(String[] args) {
//实现一个功能:键盘录入两个数,求商
try{
Scanner sc = new Scanner(System.in);
int num1 = sc.nextInt();
int num2 = sc.nextInt();
}catch(Exception ex){
System.out.println("对不起程序出现异常");
}
}

}

原理:

把可能出现的异常代码放在try代码块中,然后将异常封装为对象,被catch后面的()中的那个异常对象接收:接受以后执行catch后面的代码,然后后面的代码并不会影响

详细说:

1、try中没有异常,catch中的代码不执行

2、try中有异常。catch进行捕获:

如果catch中异常类型和你出的类型匹配的话:走catch中的代码—》进行捕获

如果catch中的异常类型和你出的异常类型不匹配:不走catch中的代码–》没有捕获成功,程序相当于遇到异常了,中断了,后面的代码不执行

catch中如何处理异常

第一种:异常捕获成功后,catch中什么都不写,什么都不做

第二种:输出自定义异常信息

第三种:打印异常信息

(1)调用toString方法,显示异常的类名

1
2
System.out.println(ex);
System.out.println(ex.toString());

(2)显示异常描述信息对应的字符串,如果没有显示就是null

1
System.out.println(ex.getMessage());

(3)显示异常的堆栈信息:将异常信息捕获后,在控制台将异常信息给我们展示出来,方便我们查看异常。

1
ex.printStackTrace();

(4)抛出异常

1
throw ex;

try-catch-finally

怎么样才可以将try-catch后面的代码必须执行?

只要将必须执行的代码放入finally中,那么这个代码无论如何一定执行。

return和finally执行顺序

先执行finally最后执行return

什么代码会放在finally中呢

关闭数据库资源,关闭IO流资源,关闭socket资源

有一句代码很厉害,他可以让finally中代码不执行!

1
System.exit(0);//终止当前虚拟机进行
#### 多重catch

1、try中出现异常后,将异常类型跟catch后面的类型依次比较,按照代码的顺序进行比对,执行第一个与异常类型匹配的catch语句

2、一旦执行其中一条catch语句之后,后面的catch语句就会被忽略了!

3、在安排catch语句的顺序的时候,一般会将特殊异常放在前面(并列),一般化异常放在后面

先写子类异常,在写父类异常

在JDK1.7之后,异常处理方式:可以用并用|符号:

1
2
3
catch(IllegalArgumentException | NullPointerException){

}

异常的分类

image-20230803175114686

异常我没写全,懒得写啦嘿嘿

注意:程序中语法错误,逻辑错误,都不属于上面的Error,Exception

正常检查异常有三种方法:

1、try-catch嵌套try-catch

2、多重catch

3、throws

throw和throws的区别

1、位置不同:

throw在方法内部

throws在方法签名处,方法声明处

2、内容不同:

throw + 异常对象(检查异常,运行时异常)

thows + 异常类型(可以多个类型,用,拼接)

3、作用不同:

thow:异常出现的源头,制造异常

thows:在方法声明处,告诉方法的调用者,告诉方法的调用者,这个方法中可能出现我声明的这些异常。然后调用者对这个异常进行处理:要么自己处理要么在继续向外抛出异常。

重载和重写的异常

image-20230803180934590

复习一下:重载是在同一个类中有相同的方法名,但是形参列表不同,这种方法叫重载

重写是子类对于父类不满意的方法进行重写,要求形参列表和名字必须是一样的

样例:

1、重载

1
2
3
4
5
6
7
8
public class Demo{
public void a() throws Exception{

}
public void a(int age) throws ArithmeticException{

}
}

2、重写

1
2
3
4
5
6
7
8
9
10
11
12
public class Person{
public void eat() throws RuntimeException{
System.out.println("父类方法")
}
}

class Student extends Person{
public void eat() throws Exception{
System.out.println("子类方法")
}
}
//当然啦这种写法是错误的哦!因为子类要小于等于父类

自定义的异常可以继承:运行时异常

1
2
3
4
5
6
7
8
9
10
public class MyException extends RuntimeException{
static final long serialVersionUID = -70348971907L;

public MyException(){

}
public MyException(String msg){
super(msg);
}
}

也可以继承检查异常:

1
2
3
4
5
6
7
8
9
public class MyException extends Exception{
static final long serialVersionUID = -70348971907L;
public MyException(){

}
public MyException(String msg){
super(msg);
}
}

如果继承的是运行时异常,那么在使用中无需额外处理

如果继承的是检查异常,那么使用的时候需要try-catch-捕获或者throws向上抛

常用类

包装类

什么是包装类:以前定义变量经常使用基本数据类型

对于基本数据类型来说,他就是一个数,加点属性,加点方法,加点构造器,将基本数据类型对应进行了一个封装,产生了一个新的类 –》包装类。

image-20230804171619581

已经有了基本数据类型,为什么要封装为包装类?

1、java语言 买你想对象的语言,最擅长操作各种各样的类

2、以前学习装数据的–》数组

​ 以后学习的装数据的—》集合,有一个特点,只能装引用数据类型的数据

4、是不是有了包装类以后就不用基本数据类型了?不是

这边用Integer类进行案例讲解:

1、直接引用,无需导包:

java.lang包下的类Integer

image-20230804172020508

实现了Comparable接口

这个类被final修饰,那么这个类不能有子类,不能被继承

包装类是对基本数据类型的封装,对int类型封装产生了Integer

里面的属性:

1
2
3
4
5
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);
//“物极必反”原理:
System.out.println(Integer.MAX_VALUE+1);
System.out.println(Integer.MIN_VALUE-1);

构造器(发现没有空参构造器)

(1)int类型作为构造器的参数:

1
Integer i1 = new Integer(12);

里面的构造器是这么写的

1
2
3
public Integer(int value){
this.value = value ;
}

当然你传入String类型的时候会给你自动转换成Int类型但是转换不行的话就会报错了

包装类特有的机制:自动装箱,自动拆箱

1
2
3
4
5
6
7
//自动装箱  int --->Integer
Integer i = 12;
System.out.println(i);
//自动拆箱:Integer -->int
Integer i2 = new Integer(12);
int num = i2;
System.out.println(num);

(1)自动装箱 自动拆箱 是从JDK1.5后新出的特性

(2)自动装箱 自动拆箱 : 将基本数据类型和包装类进行快速的类型转换

类里面肯定是有方法的:

常用的方法:

compareTo(),equals(),intValue():作用将Integer—>int

parseInt(String s) : String –>int:

toString():Integer –>String

日期相关的类(后面再写)

java,util,Date类

Math类

也是在java.lang包下的一个类,直接使用,无需导包

用final修饰的类,这个类不能被继承

其次 构造器被私有化,不能创建Math类的对象:

image-20230806151435346

Math内部的所有属性,方法都被static修饰:类名.直接调用,无需创建对象

常用方法:

Math.random随机数、Math.abs绝对值、Math.ceil向上取值、Math.floor向下取值、Math.round四舍五入

详细说说Random类

Math.random方法是返回带正号的double值、该值大于0.0并且小于1.0

==利用带参数的构造器创建对象==

1
2
Random r1 = new Random(System.currentTimeMillis());
int i = r1.nextInt();

==利用空参构造器创建对象==

1
2
3
Random r2 = new Random();//表面是在调用无参数构造器,实际底层还是调用了带参构造器
r2.nextInt(10);//在 0(包括)和指定值(不包括)之间均匀分布的 int 值。
r2.nextDouble();//在 0.0 和 1.0 之间均匀分布的 double 值。

image-20230806152956205

String类

也是在java.lang包下的一个类,直接使用,无需导包。

形象的说一下字符串:

image-20230806153229816

“abc”就是String类下的一个具体的对象

字符串是不可变的:image-20230806153924201

这个String类不可以被继承,不能有子类:image-20230806154453203

String底层就是一个char类型的数组image-20230806154537980

StringBuilder类

字符串的分类:

1、不可变字符串:String

2、可变字符串:StringBuilder、StringBuffer

什么叫可变什么是不可变,并且有什么区别呢?

StringBuilder底层:非常重要的两个属性

1
2
char[] value;//value 就是StringBuilder底层的存储
int count;//count指的是value数组中被使用的长度

对应内存分析:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test01 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//创建StringBuilder的对象:
StringBuilder sb3 = new StringBuilder();
//表面上调用StringBuilder的空构造器,实际底层是对value数组进行初始化,长度为16
StringBuilder sb2 = new StringBuilder(3);
//表面上调用StringBuilder的有参构造器,传入一个int类型的数,实际底层就是对value数组进行初始化,长度为你传入的数字
StringBuilder sb = new StringBuilder("abc");
System.out.println(sb.append("def").append("aaaaaaaa").append("bbb").append("ooooooo").toString());;//链式调用方式:return this
}
}
1
StringBuilder sb = new StringBuilder("abc");

image-20230806162823913

可变和不可变

String就是不可变:在地址不变的情况下,想把“abc”变成“abcdef”是不可能的

StringBuilder可变:可变,在StringBuilder这个对象的地址不变的情况下,想把“abc”变成“abcdf”是可能的,直接追加就可以了

StringBuilder常用方法:

.append增加、.delete删除.deleteCharAt()、.insert(3,”,”)插入、replace(3,5,“我好累”)替换、.substring(2,4)查找

StringBuffer常用方法:

差不多,懒得写了

特别重要!!String、StringBuilder、StringBuffer 区别和联系

1、String类是不可变类、即一旦一个String对象被创建后,包含在这个对象中的字符序列是不可改变的,制止这个对象销毁。

2、StringBuffer类则代表一个字符序列可变的字符串,可以通过append、insert、reverse、setCharAt、setLength等方法改变其内容。一旦生成了最终的字符串,调用toString方法将其转变为String

3、JDK1.5新增了一个StringBuilder类和StringBuffer相似,构造方法和方法基本相同。不同的是StrtingBuffer是线程安全的,而StringBuilder是线程不安全的,所以性能略高,通常情况下,创建一个内容可变的字符串,应该优先考虑使用StringBuilder。

​ StringBuilder:JDK1.5开始 效率高 线程不安全

​ StringBuffer:JDK1.0开始 效率低 线程安全

集合

什么是算法和数据结构

1、算法

(1)可以解决具体问题:例如 从1加到100

解题流程=算法

(2)有设计解决的具体的流程

算法1:一个一个加

算法2:高斯算法

(3)有评价这个算法的具体的 指标 –》时间复杂度和空间复杂度

2、数据结构:就是在计算机的缓存,内存和硬盘如何组织管理数据的。重点在结构上,是按照什么结构来组织管理我们的数据。

数据结构分为:

(1)逻辑结构:–》思想上的结构–》卧室、厨房、卫生间–》线性表(数组、链表),图,树、栈、队列

(2)物理结构:–》真是结构–》钢筋混凝土+牛顿力学—-》紧密结构(顺序结构),跳转结构(链式结构)

线性表:是几个n个类型相同数据元素的有序序列

特点:1、相同数据类型 2、序列(顺序性) 3、有限

逻辑结构和物理结构的关系:

线性表逻辑结构,对应真是结构如果是紧密结构–》典型就是数组

数组的优点:查找元素快 缺点:删除和增加元素效率低

线性表逻辑结构,对应的真是结构如果是跳转结构—》链表:

优点:删除元素,插入元素效率高

缺点:查询效率低

集合引入

1、数组,集合都是对多个数据进行存储操作的,简称为容器。

PS:这里的存储指的是内存层面的存储,而不是持久化存储

2、数组:特点:

(1)数组一旦指定了长度,那么长度就被确定,不可以更改。

(2)删除,增加元素 效率低

(3)数组中实际元素的数量是没有办法获取的,没有提供对应的方法或者属性来获取

(4)数组存储:有序,可重复,对于无序,不可重复的数组不能满足需求。

4、正因为上面的缺点,引入了一个新的存储数据的结构—》集合

有很多集合,每个集合底层的数据结构不一样。集合不一样,特点也不一样。

集合分为两大阵营image-20230807145542512

集合应用场景:

前后端数据交互的时候:需要将相同数据的个体整合到一起的时候需要集合。

Collection接口的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Test01 {
//这是main方法,程序的入口
public static void main(String[] args) {
/*
Collection接口的常用方法:
增加:add(E e) addAll(Collection<? extends E> c)
删除:clear() remove(Object o)
修改:
查看:iterator() size()
判断:contains(Object o) equals(Object o) isEmpty()
*/
//创建对象:接口不能创建对象,利用实现类创建对象:
Collection col = new ArrayList();
//调用方法:
//集合有一个特点:只能存放引用数据类型的数据,不能是基本数据类型
//基本数据类型自动装箱,对应包装类。int--->Integer
col.add(18);
col.add(12);
col.add(11);
col.add(17);
System.out.println(col/*.toString()*/);
List list = Arrays.asList(new Integer[]{11, 15, 3, 7, 1});
col.addAll(list);//将另一个集合添加入col中
System.out.println(col);
//col.clear();清空集合
System.out.println(col);
System.out.println("集合中元素的数量为:"+col.size());
System.out.println("集合是否为空:"+col.isEmpty());
boolean isRemove = col.remove(15);
System.out.println(col);
System.out.println("集合中数据是否被删除:"+isRemove);
Collection col2 = new ArrayList();
col2.add(18);
col2.add(12);
col2.add(11);
col2.add(17);
Collection col3 = new ArrayList();
col3.add(18);
col3.add(12);
col3.add(11);
col3.add(17);
System.out.println(col2.equals(col3));
System.out.println(col2==col3);//地址一定不相等 false
System.out.println("是否包含元素:"+col3.contains(117));
}
}

总结一下:首先是接口不能创建对象,利用实现类创建对象,

集合有一个特点:只能存放引用数据类型的数据,不能是基本数据类型

基本数据类型放入到集合里面会自动装箱。

集合的遍历

迭代器!!!!!!

1
2
3
4
Iterator it = col.iterator();
while(it.hasNext()){
sout(it.next());
}

image-20230807152345181

List接口

List接口的常用方法和遍历方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Test03 {
//这是main方法,程序的入口
public static void main(String[] args) {
/*
List接口中常用方法:
增加:add(int index, E element)
删除:remove(int index) remove(Object o)
修改:set(int index, E element)
查看:get(int index)
判断:
*/
List list = new ArrayList();
list.add(13);
list.add(17);
list.add(6);
list.add(-1);
list.add(2);
list.add("abc");
System.out.println(list);
list.add(3,66);
System.out.println(list);
list.set(3,77);
System.out.println(list);
list.remove(2);//在集合中存入的是Integer类型数据的时候,调用remove方法调用的是:remove(int index)
System.out.println(list);
list.remove("abc");
System.out.println(list);
Object o = list.get(0);
System.out.println(o);
//List集合 遍历:
//方式1:普通for循环:
System.out.println("---------------------");
for(int i = 0;i<list.size();i++){
System.out.println(list.get(i));
}
//方式2:增强for循环:
System.out.println("---------------------");
for(Object obj:list){
System.out.println(obj);
}
//方式3:迭代器:
System.out.println("---------------------");
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}

ArrayList实现类(动态数组):

ArrayList实现List接口的失误:集合创始人 承认了这个失误,但是在后续的版本中没有删除,觉得没必要(JDK1.7)。(实现了两次List接口,这个是失误,但是没大问题)

底层:是一个数组,数组类型是Object类型,size是数组中的有效数据

image-20230808105930449

在JDK1.7中:调用构造器的时候给底层数组elementData初始化,数组初始化长度为10:

对应内存:

image-20230808110519005

调用add方法:

1
2
3
ArrayList al = new ArrayList();
System.out.println(al.add("abc"));
System.out.println(al.add("def"));

image-20230808122240601

当数组中10个位置都满了的时候就开始进行数组的扩容,扩容长度为原数组的1.5倍,将elementData的指向从老数组指向新数组,然后将老数组的内容赋值到新数组中,然后返回新数组。

JDK1.8的ArrayList实现类

JDK1.8底层依旧是Object类型的数组,size数组中的有效长度

在JDK1.8中,在调用空构造器的时候,底层elementData数组初始化为{}.

Vector实现类(也是动态数组)

底层是Object数组,int类型属性表示数组中的有效长度,底层数组长度是10,但是在扩容的时候底层数组扩容长度是2倍

泛型

什么是泛型(Generic)

泛型相当于标签

形式:<>

集合容器类在设计阶段、声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object类型,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他部分是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型

如果不是用泛型的话:

一般我们在使用的时候基本上往集合中存入相同类型的数据–》便于管理,所以现在引用数据类型都可以存入集合,不方便!

1
2
3
4
5
6
7
8
9
public class Test01 {
public static void main(String[] args){
//创建一个ArratList集合,想这个集合中存入学生的成绩:
//加入泛型的优点:在编译时期就会对类型进行检查,不是泛型对应的类型就不可以添加这个集合
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(98);

}
}

使用了泛型以后,可以确定集合中存放数据的类型,在编译时期就可以检查出来

使用泛型的时候你可能觉得麻烦,实际使用了泛型才会简单,后续遍历等操作简单

泛型的类型:都是引用数据类型,不能是基本数据类型。

(6)ArrayList al = new ArrayList();在JDK1.7以后可以写为:
ArrayList al = new ArrayList<>(); —<> —-钻石运算符

自定义泛型结构

泛型类、泛型接口

现在创造一个泛型类,Test就是一个泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test<E>{
int age;
String name ;
E sex;
public void a (E n){

}

}
class Test{
public static void main(String[] args){
//将test实例化“
//1实例化的时候不指定泛型:如果实例化的时候不指定泛型,那么认为泛型为Object类型
Test t = new Test();
t.a("abc");

//2、实例化的时候指定泛型:---》推荐方式
Test<String> t2 = new Test<>();
t2.sex = "男";
t2.a("abc");
}

}

继承情况:

1、父类指定泛型

1
2
3
4
5
6
7
8
9
10
class SubTest extend Test<Integer>{

}
class Demo{
public static void main(String[] args){
//指定父类泛型,那么子类就不需要在指定泛型了,可以直接使用
SubTest t = new SubTest();
t.a(19);
}
}

2、父类不指定泛型

如果父类不指定泛型,那么子类也会变成一个泛型类,那这个E的类型可以在创建子类对象的时候可以确定

1
2
3
class SubTest2<E> extends Test<E>{

}
1
2
3
4
5
6
7
class Demo2{
public static void main(String[] args){
SubTest<String> s = new SubTest<>();
s.a("abc");
s.sex="女";
}
}

3、细节:

泛型类可以定义多个参数类型

1
2
3
4
5
6
7
8
public class Test<A,B,C>{
A age;
B name;
C sex;
public void a(A m,B n,C x){

}
}

PS:泛型类的构造器的写法

这么写

1
2
3
public Test/*<A,B,C>*/(){
//就跟普通的构造器一样,不能带上泛型类型
}

3、不同的泛型的引用类型不可以相互赋值

1
2
3
4
5
public void b(){
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = null;
list1 = list2;//这样是不行的,如果不是泛型的话是可以的,相当于一个地址的改变。
}

4、泛型如果不指定,那么就会被擦除,反应对应的类型为Object类型:

1
2
3
Test t1 = new Test();
t1.a("abc");
t1.a(17);

5、泛型类的静态方法不能使用这个类的泛型

image-20230812155910793

6、不能直接使用E[]的创建:

image-20230812160002789

泛型方法

什么是泛型方法: 不是带泛型的方法就是泛型方法

==泛型方法也有要求:这个方法的泛型的参数要和当前的类的泛型无关==

换个角度:泛型方法对应的那个泛型参数类型 和当前所在的这个类 是否是泛型类 ,泛型是啥 无关

==泛型方法定义的时候,前面要加上==

==原因:如果不加的话,会把T当做一种数据类型,然而代码中没有T就会报错==

3、==T的类型是在调用方法的时候确定的==

4、泛型方法是否可以是静态方法?可以是静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//<>里面是ABCDE都行但是源码里面经常用E所以用E了
public class Test<E>{
public static void a(E e){

}
//这个才是泛型方法
//要求和类的数据类型不一致
public static <T> void b(T t){

}
}

class Demo{
public static void main(String[] args){
Test<String> t = new Test<>();
t.a("abc");

}
}

泛型参数存在继承关系的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test{
public static void main(String[] args){
Object obj = new Object();
String s = new String();
obj = s;//多态的一种形式

Object[] objArr = new Object[10];
String[] strArr = new String[10];
objArr = strArr;//多态的一种形式

List<Object> list1 = new ArrayList<>;
List<String> list22 = new ArrayList<>;
list1 = list2;

//总觉A和B是父类和子类的关系,但是G<A>和G<B>不存在继承关系。是并列关系
}
}

泛型不具备继承性,但是数据具备。

通配符

1、在没有通配符的时候:下面的a 方法,相当于方法的重复定义,报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test{
/*
public void a(List<Object> list){

}
public void a(List<String> list){

}
public void a(List<Integer> list){

}

*/
}

引入通配符:

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo{
public static void main(String[] args){
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<Integer> list3 = new ArrayList<>();

List<?> list = null;
list = list1;
list = list2;
list = list3;
}
}

发现:A和B是子类父类的关系,G 和G不存在子类父类关系,是并列的加入通配符 ? 后,G<?>就变成了G和G的父类。

通配符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Test{
/*
public void a(List<Object> list){

}
public void a(List<String> list){

}
public void a(List<Integer> list){

}
*/
public void a(List<?> list){
//内部遍历的时候用Object即可,不用?
for(Object a : list){
System.out.println(a);
}
}
}
class T{
public static void main(String[] args){
Test t = new Test();
//这样实例化的时候就可以任何数据类型都可以了
t.a(new ArrayList<Integer>());
t.a(new ArrayList<String>());
t.a(new ArrayList<Object>());
}
}

使用通配符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test{
public void a(List<?> list){
//1、遍历
for(Object a : list){
System.out.println(a);
}
//2、数据写入操作
//list.add("abc");这里会出错,不能随意的添加数据,因为你还不知道是什么类型的,在实例化的时候在使用最好。
list.add(null);
//3、数据的读取操作
Object s = list.get(0);
}
}
class T{
public static void main(String[] args){
Test t = new Test();
t.a(new ArrayList<Integer>());
t.a(new ArrayList<String>());
t.a(new ArrayList<Object>());
}
}

泛型受限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Test{
public static void main(String[] args){
//a,b,c三个集合是并列的关系:
List<Object> a = new ArrayList<>();
List<Person> b = new ArrayList<>();
List<Student> c = new ArrayList<>();
/*开始使用泛型受限:泛型的上限
List<? extends Person>:
就相当于:
List<? extends Person>是List<Person>的父类,是List<Person的子类>的父类
*/
List<? extends Person> list1 = null;
/*list1 = a;
list1 = b;
list1 = c;*/
/*开始使用泛型受限:泛型的下限
List<? super Person>
就相当于:
List<? super Person>是List<Person>的父类,是List<Person的父类>的父类
*/
List<? super Person> list2 = null;
list2 = a;
list2 = b;
list3 = c;
}
}

LinkedList实现类的使用(链表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

public class Test {
//这是main方法,程序的入口
public static void main(String[] args) {
/*
LinkedList常用方法:
增加 addFirst(E e) addLast(E e)
offer(E e) offerFirst(E e) offerLast(E e)
删除 poll()
pollFirst() pollLast() ---》JDK1.6以后新出的方法,提高了代码的健壮性
removeFirst() removeLast()
修改
查看 element()
getFirst() getLast()
indexOf(Object o) lastIndexOf(Object o)
peek()
peekFirst() peekLast()
判断
*/
//创建一个LinkedList集合对象:
LinkedList<String> list = new LinkedList<>();
list.add("aaaaa");
list.add("bbbbb");
list.add("ccccc");
list.add("ddddd");
list.add("eeeee");
list.add("bbbbb");
list.add("fffff");
list.addFirst("jj");
list.addLast("hh");
list.offer("kk");//添加元素在尾端
list.offerFirst("pp");
list.offerLast("rr");
System.out.println(list);//LinkedList可以添加重复数据
System.out.println(list.poll());//删除头上的元素并且将元素输出
System.out.println(list.pollFirst());
System.out.println(list.pollLast());
System.out.println(list.removeFirst());
System.out.println(list.removeLast());
System.out.println(list);//LinkedList可以添加重复数据
/*list.clear();//清空集合
System.out.println(list);*/
/*System.out.println(list.pollFirst());*/
/*System.out.println(list.removeFirst());报错:Exception in thread "main" java.util.NoSuchElementException*/
//集合的遍历:
System.out.println("---------------------");
//普通for循环:
for(int i = 0;i<list.size();i++){
System.out.println(list.get(i));
}
System.out.println("---------------------");
//增强for:
for(String s:list){
System.out.println(s);
}
System.out.println("---------------------");
//迭代器:
/*Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}*/
//下面这种方式好,节省内存
for(Iterator<String> it = list.iterator();it.hasNext();){
System.out.println(it.next());
}
}
}

模拟LinkedList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class  Node {
int val ; //储存节点的值
Node next ; //表示下一个节点的地址
}
public class MyLinkedList{
//链表中有一个首节点
Node first;
//链表中有一个尾节点
Node last;
//计数器
int count = 0;
//提供一个构造器
public MyLinkedList(){

}
//添加元素方法
public void add(Object o){
if(first == null){//证明你添加的元素是第一个节点
//将添加的元素封装为Nod对象:
Node n = new Node();

}
}
}

Linked源码解析

JDK1.7和1.8的源码是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
   public class LinkedList<E>{//E是一个泛型,具体的类型要在实例化的时候才会最终确定
transient int size = 0;//集合中元素的数量
//Node的内部类
private static class Node<E> {
E item;//当前元素
Node<E> next;//指向下一个元素地址
Node<E> prev;//上一个元素地址
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
transient Node<E> first;//链表的首节点
transient Node<E> last;//链表的尾节点
//空构造器:
public LinkedList() {
}
//添加元素操作:
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {//添加的元素e
final Node<E> l = last;//将链表中的last节点给l 如果是第一个元素的话 l为null
//将元素封装为一个Node具体的对象:
final Node<E> newNode = new Node<>(l, e, null);
//将链表的last节点指向新的创建的对象:
last = newNode;

if (l == null)//如果添加的是第一个节点
first = newNode;//将链表的first节点指向为新节点
else//如果添加的不是第一个节点
l.next = newNode;//将l的下一个指向为新的节点
size++;//集合中元素数量加1操作
modCount++;
}
//获取集合中元素数量
public int size() {
return size;
}
//通过索引得到元素:
public E get(int index) {
checkElementIndex(index);//健壮性考虑
return node(index).item;
}

Node<E> node(int index) {
//如果index在链表的前半段,那么从前往后找
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {//如果index在链表的后半段,那么从后往前找
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
}

面试题:iterator(),iterable,iterator关系

ArraList集合 实现 List接口 继承 Collection接口 继承 Iterable接口(接口里面有一个抽象方法iterator()。(抽象方法要在具体的实现类(ArrayList)中实现)而在ierator()方法里面返回值为Iterator接口

2、hasNext(),next()具体实现:

image-20230829170959008

3、增强for循环 底层也是通过迭代器熟实现的

ListIterator迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test2 {
//这是main方法,程序的入口
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//在"cc"之后添加一个字符串"kk"
Iterator<String> it = list.iterator();
while(it.hasNext()){
if("cc".equals(it.next())){
list.add("kk");
}
}
}
}

这行代码看着没什么错但是还是报错了!

出错原因:就是迭代器和list同事对集合进行操作:

image-20230829171431204

list指针指到相应位置可是it的位置还是没变。

解决办法:事情让一个人做:引入新的迭代器ListIterator迭代和添加都是靠ListIterator完成的:

Set接口

HashSet实现类应用(哈希表)

1、放入Integer类型数据:

1
2
3
4
5
6
7
8
9
10
11
public class test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
HashSet<Integer> hs = new HashSet<>();
System.out.println(hs.add(19));//true
hs.add(5);
hs.add(20);
System.out.println(hs.size());

}
}

哈希表的特点是唯一,无序

2、HashSet原理图:(简要原理图)

image-20230829173955780

疑问:
1.数组的长度是多少。
2.数组的类型是什么?
3.hashCode,equals方法真的调用了吗?验证
4.底层表达式是什么?
5.同一个位置的数据 向前放 还是 向后放?
6.放入数组中的数据,是直接放的吗?是否封装为对象了?

LinkedHashSet使用

其实就是在HashSet的基础上,多了一个总的链表,这个总的链表将放入的元素串在一起,方便有序的遍历:

image-20230829180108191

特点也是唯一无序

比较器的使用

1、以int类型为案例:

比较思路:将比较的数据作差,然后返回一个int 类型的数据,将这个int类型的数据 按照 =0,》0 《0来比较判断

2、比较string 类型的数据:

string类实现了comparable接口,这个接口中有一个抽象方法compareTo, string类中重写这个方法即可

3、比较double类型的数据:

也是利用compareTo这个方法。

4、比较自定义的数据类型:

(1)内部比较器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Student implements Comparable<Student>{
private int age;
private double height;
private String name;

puiblic int getAge(){
return age;
}
//...写很多set和get方法省略不写了
@Override
public int compareTo(Student o) {
//按照年龄进行比较:
/*return this.getAge() - o.getAge();*/
//按照身高比较
/*return ((Double)(this.getHeight())).compareTo((Double)(o.getHeight()));*/
//按照名字比较:
return this.getName().compareTo(o.getName());
}
}

2、外部比较器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BiJiao01 implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
//比较年龄:
return o1.getAge()-o2.getAge();
}
}
class BiJiao02 implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
//比较姓名:
return o1.getName().compareTo(o2.getName());
}
}

外部比较器和内部比较器 谁好呀?
答案:外部比较器,多态,扩展性好

TreeSet实现类(树)

特点:唯一、无序(没有按照输入顺序进行输出,有序(按照生序进行遍历)

原理:底层:二叉树(数据结构中的一个逻辑结构)

TreeSet底层的二叉树的遍历是按照升序结果出现的,这个升序是靠中序遍历得到的。

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

image-20230829181908824

Map接口(图)

1、常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class Test01 {
//这是main方法,程序的入口
public static void main(String[] args) {
/*
增加:put(K key, V value)
删除:clear() remove(Object key)
修改:
查看:entrySet() get(Object key) keySet() size() values()
判断:containsKey(Object key) containsValue(Object value)
equals(Object o) isEmpty()
*/
//创建一个Map集合:无序,唯一
Map<String,Integer> map = new HashMap<>();
System.out.println(map.put("lili", 10101010));
map.put("nana",12345234);
map.put("feifei",34563465);
System.out.println(map.put("lili", 34565677));
map.put("mingming",12323);
/*map.clear();清空*/
/*map.remove("feifei");移除*/
System.out.println(map.size());
System.out.println(map);
System.out.println(map.containsKey("lili"));
System.out.println(map.containsValue(12323));
Map<String,Integer> map2 = new HashMap<>();
System.out.println(map2.put("lili", 10101010));
map2.put("nana",12345234);
map2.put("feifei",34563465);
System.out.println(map2.put("lili", 34565677));
map2.put("mingming2",12323);
System.out.println(map==map2);
System.out.println(map.equals(map2));//equals进行了重写,比较的是集合中的值是否一致
System.out.println("判断是否为空:"+map.isEmpty());
System.out.println(map.get("nana"));
System.out.println("-----------------------------------");
//keySet()对集合中的key进行遍历查看:
Set<String> set = map.keySet();
for(String s:set){
System.out.println(s);
}
System.out.println("-----------------------------------");
//values()对集合中的value进行遍历查看:
Collection<Integer> values = map.values();
for(Integer i:values){
System.out.println(i);
}
System.out.println("-----------------------------------");
//get(Object key) keySet()
Set<String> set2 = map.keySet();
for(String s:set2){
System.out.println(map.get(s));
}
System.out.println("-----------------------------------");
//entrySet()
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for(Map.Entry<String, Integer> e:entries){
System.out.println(e.getKey()+"----"+e.getValue());
}
}
}

2、TreeMap

1、key的类型为string类型:

1
2
3
4
5
6
7
public static void main(String[] args)P{
Map<String,Integer> map = new TreeMap<>();
map.put("blili",1234);
map.put("alili",2345);
System.out.println(map.size());
System.out.println(map);
}

2、key类型是一个自定义的引用数据类型

(1)内部比较器:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test03 {
//这是main方法,程序的入口
public static void main(String[] args) {
Map<Student,Integer> map = new TreeMap<>();
map.put(new Student(19,"blili",170.5),1001);
map.put(new Student(18,"blili",150.5),1003);
map.put(new Student(19,"alili",180.5),1023);
map.put(new Student(17,"clili",140.5),1671);
map.put(new Student(10,"dlili",160.5),1891);
System.out.println(map);
System.out.println(map.size());
}
}

Student类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Student implements Comparable<Student>{
private int age;
private String name;
private double height;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public Student(int age, String name, double height) {
this.age = age;
this.name = name;
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", height=" + height +
'}';
}
@Override
public int compareTo(Student o) {
/* return this.getAge()-o.getAge();*/
return this.getName().compareTo(o.getName());
}
}

(2)外部比较器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test03 {
//这是main方法,程序的入口
public static void main(String[] args) {
Map<Student,Integer> map = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return ((Double)(o1.getHeight())).compareTo((Double)(o2.getHeight()));
}
});
map.put(new Student(19,"blili",170.5),1001);
map.put(new Student(18,"blili",150.5),1003);
map.put(new Student(19,"alili",180.5),1023);
map.put(new Student(17,"clili",140.5),1671);
map.put(new Student(10,"dlili",160.5),1891);
System.out.println(map);
System.out.println(map.size());
}
}

这里的外部比较器有点丑。

3、源码部分

Collections工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Test {
//这是main方法,程序的入口
public static void main(String[] args) {
//Collections不支持创建对象,因为构造器私有化了
/*Collections cols = new Collections();*/
//里面的属性和方法都是被static修饰,我们可以直接用类名.去调用即可:
//常用方法:
//addAll:
ArrayList<String> list = new ArrayList<>();
list.add("cc");
list.add("bb");
list.add("aa");
Collections.addAll(list,"ee","dd","ff");
Collections.addAll(list,new String[]{"gg","oo","pp"});
System.out.println(list);
//binarySearch必须在有序的集合中查找:--》排序:
Collections.sort(list);//sort提供的是升序排列
System.out.println(list);
//binarySearch
System.out.println(Collections.binarySearch(list, "cc"));
//copy:替换方法
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2,"tt","ss");
Collections.copy(list,list2);//将list2的内容替换到list上去
System.out.println(list);
System.out.println(list2);
//fill 填充
Collections.fill(list2,"yyy");
System.out.println(list2);
}
}

Map部分整体结构图

image-20230830110721449

集合补充

image-20230830111833337

数据结构:栈

数据结构分为:

(1)逻辑结构

IO流

File类

在java中要操作文件/目录怎么办?

盘符上的文件封装为对象,对象属于File类的对象,有了这个对象我们就可以直接操纵这个对象。

对文件进行操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Test01 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) throws IOException {
//将文件封装为一个对象后进行操作
File f1 = new File("d://test.txt");
//File.separator属性帮我们获取当前操作系统的路径拼接符号
//在windows,dos下,系统默认用“\”作为路径分隔符 ,在unix,url中,使用“/”作为路径分隔符。
File f2 = new File("d:"+File.separator+"test.txt");//建议使用这种
//常用方法
System.out.println("文件是否可读:"+f1.canRead());
System.out.println("文件是否可写:"+f1.canWrite());
System.out.println("文件的名字:"+f1.getName());
System.out.println("上级目录:"+f1.getParent());
System.out.println("是否是一个目录:"+f1.isDirectory());
System.out.println("是否是一个文件:"+f1.isFile());
System.out.println("是否隐藏:"+f1.isHidden());
System.out.println("文件的大小:"+f1.length());
System.out.println("是否存在:"+f1.exists());
// if(f1.exists()){
// f1.delete();
// }else{
// f1.createNewFile();
// }
//跟路径相关的:
System.out.println("绝对路径:"+f1.getAbsolutePath());
System.out.println("相对路径:"+f1.getPath());
System.out.println("toString:"+f1.toString());


}


}
对目录进行操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Test01 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) throws IOException {
File f = new File("d://test");
System.out.println("文件是否可读:"+f.canRead());
System.out.println("文件是否可写:"+f.canWrite());
System.out.println("文件的名字:"+f.getName());
System.out.println("上级目录:"+f.getParent());
System.out.println("是否是一个目录:"+f.isDirectory());
System.out.println("是否是一个文件:"+f.isFile());
System.out.println("是否隐藏:"+f.isHidden());
System.out.println("文件的大小:"+f.length());
System.out.println("是否存在:"+f.exists());
System.out.println("绝对路径:"+f.getAbsolutePath());
System.out.println("相对路径:"+f.getPath());
System.out.println("toString:"+f.toString());
//跟目录相关的方法:
File f2 = new File("D:\\test\\a\\b\\c");
//创建目录:
//f2.mkdir();//创建单层目录
//f2.mkdirs();//创建多层目录

//删除:如果是删除目录的话,只会删除一层,并且前提:这层目录是空的,里面没有内容,如果内容就不会被删除
f2.delete();

String[] list = f.list();
for(String name : list){
System.out.println(name);
}
System.out.println("=========================");
File[] files = f.listFiles();//作用更加广泛
for(File file:files){
System.out.println(file.getName()+","+file.getAbsolutePath());
}
}


}

IO流

1、File类:封装文件、目录信息,对文件、目录进行操作,但是我们不可以获取文件、目录中的内容。

2、所以我们需要 IO流

I/O:Input/Output 的缩写,用于处理设备之间的数据传输。

3、形象理解:IO流 当做一根管子:

image-20230916094655686

image-20230916094736868

image-20230916094749722

案例通过java程序完成文件的复制

一个字符一个字符的将文件中的内容读取到程序中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Test01 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) throws IOException {
//先将文件读入道程序中
File f = new File("d:\\test.txt");
//然后利用FileReader这个流,将这个管子怼到源文件上去,创建一个FileReader的流的对象
FileReader fr = new FileReader(f);
//然后进行吸的操作
//方式1:
/*int n = fr.read();
while(n!=-1){
System.out.println(n);
n = fr.read();
}*/
//中文也会变成对应的数字,符号也是
//方式2:
int n;
while((n=fr.read())!=-1){
System.out.print((char)n);
}


//4.“管”不用了,就要关闭 ---》关闭流
//流,数据库,网络资源,靠jvm本身没有办法帮我们关闭,此时必须程序员手动关闭:
fr.close();


}


}

想一次性读取五个字符,不够的话下次再读五个字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Test01 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) throws IOException {
//将文件包装为对象
File f = new File("d:\\test.txt");
//将管子怼到源文件上去
FileReader fr = new FileReader(f);
//创建缓冲数组
char[] ch = new char[5];
//将吸取的字符倡导缓冲数组里并吸取字符的个数/长度
int len = fr.read(ch);
while (len != -1) {
System.out.println("长度为:"+len);
for (int i = 0; i < len; i++) {
System.out.print(ch[i]);

}
System.out.println();
len = fr.read(ch);
}
//关闭流
fr.close();


}


}

多线程

分清概念(程序 、进程、线程)

  • 程序:是一段静态的代码(程序是静态的)
  • 进程:是一次完整的执行过程,。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。(进程是动态的)它包括了一个从产生,存在到消亡的过程
  • 线程:进程可以进一步细化为线程,是一个程序内部的一条执行的路径,如果一个进程同一时间并行执行多个线程,就是支持多线程的

通俗理解:一个人为了完成吃饭的任务(一个进程),一个人先吃菜(一个线程)再吃饭(另一个线程),然后另一个人是狂吃!!!饭和菜我全部一口给吃下去!!(多线程同时执行)!!!!

单核cpu和多核cpu的任务执行

单核cpu:cpu在执行的时候是按照时间片执行的,一个时间片只能执行一个线程,因为时间片时间特别短,感受的是“同时”执行多个线程,实际上这个多线程是假的!

多核cpu:多个cpu就能实现真正意义上的一个时间片多线程同时执行

并行和并发

并行:多个cpu同时执行多个任务

并发:一个cpu“同时”执行多个任务(采用时间片切换)

创建线程的三种方式

第一种 继承Thread类

在学多线程之前,以前的代码也不是单线程的。以前也是有三个线程同时执行的

一个处理异常的线程,main方法的线程,垃圾收集器的线程

那么现在我想自己创一个线程该怎么办

有一个线程类——》创造一个线程对象

image-20231216150432731

但是不是说名字中带着线程的名字就有抢线程的能力了,需要继承一个Tread类,并且需要重写Tread类中的run方法,逻辑卸载run方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package tread;

/**
* @Auther: oyy
* @Date: 2023/12/16 - 12 - 16 - 15:23
* @Description: tread
* @version: 1.0
*/
public class MyTread extends Thread {
@Override
public void run() {
//输出1-10
for (int i = 1; i <= 10; i++) {
System.out.println(i);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package tread;

/**
* @Auther: oyy
* @Date: 2023/12/16 - 12 - 16 - 15:24
* @Description: tread
* @version: 1.0
*/
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//主线程也输出1-10
for(int i =1;i<=10;i++){
System.out.println("main1----"+i);
}
//制造其他的子线程
MyTread mt = new MyTread();
//想要子线程真正起作用要启动线程而不是调用run方法
mt.start();
for(int i =1;i<=10;i++){
System.out.println("main2----"+i);
}
}
}

image-20231216152827248

设置读取线程的名字

利用this.getName()方法来读取线程名字

利用Tread.currentThread()来获取当前正在执行的线程

利用.setName()来获取线程的名字

或者利用类中父类中的有参构造器来设置线程的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package tread;

/**
* @Auther: oyy
* @Date: 2023/12/16 - 12 - 16 - 15:23
* @Description: tread
* @version: 1.0
*/
public class MyTread extends Thread {
@Override
public void run() {
//输出1-10
for (int i = 1; i <= 10; i++) {
System.out.println(this.getName()+i);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//主线程也输出1-10
for (int i = 1; i <= 10; i++) {
Thread.currentThread().setName("主线程1");
System.out.println(Thread.currentThread().getName() + i);
}
//制造其他的子线程
MyTread mt = new MyTread();
mt.setName("子线程");
//想要子线程真正起作用要启动线程而不是调用run方法
mt.start();
for (int i = 1; i <= 10; i++) {
System.out.println((Thread.currentThread().getName() + i));
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package tread;
//利用构造器来设置线程名字
/**
* @Auther: oyy
* @Date: 2023/12/16 - 12 - 16 - 15:23
* @Description: tread
* @version: 1.0
*/
public class MyTread extends Thread {
public MyTread(){
super("子线程");
}

@Override
public void run() {

//输出1-10
for (int i = 1; i <= 10; i++) {
System.out.println(this.getName()+i);
}
}
}

第二种 实现 Runnable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package tread;

/**
* @Auther: oyy
* @Date: 2023/12/16 - 12 - 16 - 15:41
* @Description: tread
* @version: 1.0
*/
public class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package tread;

/**
* @Auther: oyy
* @Date: 2023/12/16 - 12 - 16 - 15:24
* @Description: tread
* @version: 1.0
*/
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//主线程也输出1-10
for (int i = 1; i <= 10; i++) {
Thread.currentThread().setName("主线程1");
System.out.println(Thread.currentThread().getName() + i);
}
//制造其他的子线程
MyTread mt = new MyTread();
mt.setName("子线程");
//实现Runnable 接口和继承Thread类开启线程的方法是不一样的
MyThread2 mt2 = new MyThread2();
Thread t = new Thread(mt2,"子线程2");
t.start();
//想要子线程真正起作用要启动线程而不是调用run方法
mt.start();
for (int i = 1; i <= 10; i++) {
System.out.println((Thread.currentThread().getName() + i));
}
}
}
第三种:实现Callable接口

对比第一种和第二种方法,两种方法都需要一个run方法

但是run方法有不足:

1、没有返回值

2、不能抛出异常

实现Callable接口好处:有返回值,能抛出异常但是缺点就是创建线程比较麻烦

1
2
3
4
5
6
7
8
9
10
11
12
public class MyThread3 implements Callable<Integer> {
/*
1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型
2.如果带泛型,那么call的返回值就是泛型对应的类型
3.从call方法看到:方法有返回值,可以跑出异常
*/
@Override
public Integer call() throws Exception {
return new Random().nextInt(10);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) throws ExecutionException, InterruptedException {
//定义一个线程对象:
MyThread3 trn = new MyThread3();
FutureTask ft = new FutureTask(trn);
Thread t = new Thread(ft);
t.start();
//获取线程得到的返回值:
Object obj = ft.get();
System.out.println(obj);
}
}

具体callable接口的使用方法可以参考下面这个链接:

Callable接口详解_callable接口的作用-CSDN博客

线程的生命周期

image-20231216163348405

线程的常见方法

1、设置优先级

优先级越高与可能被调用

线程对象.setPriority(1到10);

2、join方法:

当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。

注意:必须先start,再join才有效。

3、sleep
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test02 {
//这是main方法,程序的入口
public static void main(String[] args) {
//2.定义一个时间格式:
DateFormat df = new SimpleDateFormat("HH:mm:ss");
while(true){
//1.获取当前时间:
Date d = new Date();
//3.按照上面定义的格式将Date类型转为指定格式的字符串:
System.out.println(df.format(d));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public class TestThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 1000 ; i++) {
System.out.println("子线程----"+i);
}
}
}
class Test{
//这是main方法,程序的入口
public static void main(String[] args) {
//创建并启动子线程:
TestThread tt = new TestThread();
tt.setDaemon(true);//设置伴随线程 注意:先设置,再启动
tt.start();
//主线程中还要输出1-10的数字:
for (int i = 1; i <= 10 ; i++) {
System.out.println("main---"+i);
}
}
}
4、stop
1
2
3
4
5
6
7
8
9
10
11
public class Demo {
//这是main方法,程序的入口
public static void main(String[] args) {
for (int i = 1; i <= 100 ; i++) {
if(i == 6){
Thread.currentThread().stop();//过期方法,不建议使用
}
System.out.println(i);
}
}
}

线程安全问题

方法1:同步代码块

反射

通过案例体会反射的好处

案例:美团外卖 —->付款 —-》要么用微信支付 要么用支付宝支付

美团外卖接口

1
2
3
4
5
//接口的制定方:美团外卖
public interface Mtwm {
//在线支付功能:
void payOnline();
}

微信类

1
2
3
4
5
6
7
public class WeChat implements Mtwm{
@Override
public void payOnline() {
//具体实现微信支付的功能:
System.out.println("我已经点了外卖,正在使用微信支付");
}
}

支付宝类

1
2
3
4
5
6
7
public class AliPay implements Mtwm {
@Override
public void payOnline() {
//具体的支付宝支付:
System.out.println("我已经点了外卖,我正在使用支付宝进行支付");
}
}

银行卡类

1
2
3
4
5
6
public class BankCard implements Mtwm{
@Override
public void payOnline() {
System.out.println("我已经定了外卖,我正在用招商银行信用卡支付");
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Test {
public static void main(String[] args) {
//定义一个字符串,用来模拟前台的支付方式:
String str = "微信";
if("微信".equals(str)){//str.equals("微信")---?避免空指针异常
//微信支付:
//new WeChat().payOnline();
pay(new WeChat());
}
if("支付宝".equals(str)){
//支付宝支付:
//new AliPay().payOnline();
pay(new AliPay());
}
if("招商银行".equals(str)){
pay(new BankCard());
}
}
//微信支付
public static void pay(WeChat wc){
wc.payOnline();
}
//支付宝支付
public static void pay(AliPay ap){
ap.payOnline();
}
//招商银行支付
public static void pay(BankCard bc){
bc.payOnline();
}
}

为了提高代码的扩展性—-》面向对象特性:多态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public static void main(String[] args) {
//定义一个字符串,用来模拟前台的支付方式:
String str = "微信";
if("微信".equals(str)){//str.equals("微信")---?避免空指针异常
//微信支付:
pay(new WeChat());
}
if("支付宝".equals(str)){
//支付宝支付:
pay(new AliPay());
}
if("招商银行".equals(str)){
pay(new BankCard());
}
}
//方法形参是接口,具体传入的是接口的实现类的对象---》多态的一种形式
public static void pay(Mtwm m){
m.payOnline();
}

}

多态确实可以提高代码的扩展性,但是:扩展性没有达到最好。
怎么没有达到最好:上面的分支,还是需要手动的删除或者添加。
解决办法:反射机制
利用反射实现上述功能:

1
2
3
4
5
6
7
8
9
10
11
public class Demo {
public static void main(String[] args) throws Exception {
//定义一个字符串,用来模拟前台的支付方式:
String str = "com.bitzh.test01.AliPay"; //字符串:实际上:就是微信类的全限定路径
//下面的代码就是利用反射:
Class cls = Class.forName(str);//cls-->Class类具体的对象--》AliPay字节码信息
Object o = cls.newInstance();
Method method = cls.getMethod("payOnline");
method.invoke(o);
}
}

反射概念

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,
都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

在编译后产生字节码文件的时候,类加载器子系统通过二进制字节流,负责从文件系统加载class文件。
在执行程序(java.exe)时候,将字节码文件读入JVM中—->这个过程叫做类的加载。然后在内存中对应创建一个java.lang.Class对象—>这个对象会被放入字节码信息中,这个Class对象,就对应加载那个字节码信息,这个对象将被作为程序访问方法区中的这个类的各种数据的外部接口。
所以:我们可以通过这个对象看到类的结构,这个对象就好像是一面镜子,透过镜子看到类的各种信息,我们形象的称之为反射
这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

说明:在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。
如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。

Class类的理解

image-20240814040728451

获取字节码信息的四种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//案例:以Person的字节码信息为案例
//方式1:通过getClass()方法获取
Person p = new Person();
Class c1 = p.getClass();
System.out.println(c1);
//方式2:通过内置class属性:
Class c2 = Person.class;
System.out.println(c2);
System.out.println(c1==c2);
//注意:方式1和方式2 不常用
//方式3:--》用的最多:调用Class类提供的静态方法forName
Class c3 = Class.forName("com.bitzh.test02.Person");
//方式4:利用类的加载器(了解技能点)
ClassLoader loader = Test.class.getClassLoader();
Class c4 = loader.loadClass("com.bitzh.test02.Person");
}
}

获取运行时 类的完整结构

获取构造器和对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Test01 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取字节码信息:
Class cls = Student.class;
//通过字节码信息可以获取构造器:
//getConstructors只能获取当前运行时类的被public修饰的构造器
Constructor[] c1 = cls.getConstructors();
for(Constructor c:c1){
System.out.println(c);
}
System.out.println("-------------------");
//getDeclaredConstructors:获取运行时类的全部修饰符的构造器
Constructor[] c2 = cls.getDeclaredConstructors();
for(Constructor c:c2){
System.out.println(c);
}
System.out.println("-------------------");
//获取指定的构造器:
//得到空构造器
Constructor con1 = cls.getConstructor();
System.out.println(con1);
//得到两个参数的有参构造器:
Constructor con2 = cls.getConstructor(double.class, double.class);
System.out.println(con2);
//得到一个参数的有参构造器:并且是private修饰的
Constructor con3 = cls.getDeclaredConstructor(int.class);
System.out.println(con3);
//有了构造器以后我就可以创建对象:
Object o1 = con1.newInstance();
System.out.println(o1);
Object o2 = con2.newInstance(180.5, 170.6);
System.out.println(o2);
}
}
获取属性和对属性赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Test02 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
//获取运行时类的字节码信息:
Class cls = Student.class;
//获取属性:
//getFields:获取运行时类和父类中被public修饰的属性
Field[] fields = cls.getFields();
for(Field f:fields){
System.out.println(f);
}
System.out.println("---------------------");
//getDeclaredFields:获取运行时类中的所有属性
Field[] declaredFields = cls.getDeclaredFields();
for(Field f:declaredFields){
System.out.println(f);
}
System.out.println("---------------------");
//获取指定的属性:
Field score = cls.getField("score");
System.out.println(score);
Field sno = cls.getDeclaredField("sno");
System.out.println(sno);
System.out.println("---------------------");
//属性的具体结构:
//获取修饰符
/*int modifiers = sno.getModifiers();
System.out.println(modifiers);
System.out.println(Modifier.toString(modifiers));*/
System.out.println(Modifier.toString(sno.getModifiers()));
//获取属性的数据类型:
Class clazz = sno.getType();
System.out.println(clazz.getName());
//获取属性的名字:
String name = sno.getName();
System.out.println(name);
System.out.println("-------------------------------");
//给属性赋值:(给属性设置值,必须要有对象)
Field sco = cls.getField("score");
Object obj = cls.newInstance();
sco.set(obj,98);//给obj这个对象的score属性设置具体的值,这个值为98
System.out.println(obj);
}
}
获取方法和调用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class Test03 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//获取字节码信息:
Class cls = Student.class;
//获取方法:
//getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰)
Method[] methods = cls.getMethods();
for(Method m:methods){
System.out.println(m);
}
System.out.println("-----------------------");
//getDeclaredMethods:获取运行时类中的所有方法:
Method[] declaredMethods = cls.getDeclaredMethods();
for(Method m:declaredMethods){
System.out.println(m);
}
System.out.println("-----------------------");
//获取指定的方法:
Method showInfo1 = cls.getMethod("showInfo");
System.out.println(showInfo1);
Method showInfo2 = cls.getMethod("showInfo", int.class, int.class);
System.out.println(showInfo2);
Method work = cls.getDeclaredMethod("work",int.class);
System.out.println(work);
System.out.println("-----------------------");
//获取方法的具体结构:
/*
@注解
修饰符 返回值类型 方法名(参数列表) throws XXXXX{}
*/
//名字:
System.out.println(work.getName());
//修饰符:
int modifiers = work.getModifiers();
System.out.println(Modifier.toString(modifiers));
//返回值:
System.out.println(work.getReturnType());
//参数列表:
Class[] parameterTypes = work.getParameterTypes();
for(Class c:parameterTypes){
System.out.println(c);
}
//获取注解:
Method myMethod = cls.getMethod("myMethod");
Annotation[] annotations = myMethod.getAnnotations();
for(Annotation a:annotations){
System.out.println(a);
}
//获取异常:
Class[] exceptionTypes = myMethod.getExceptionTypes();
for(Class c:exceptionTypes){
System.out.println(c);
}
//调用方法:
Object o = cls.newInstance();
myMethod.invoke(o);//调用o对象的mymethod方法
System.out.println(showInfo2.invoke(o,12,45));;
}
}
获取类的接口,所在包,注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Test04 {
public static void main(String[] args) {
//获取字节码信息:
Class cls = Student.class;
//获取运行时类的接口:
Class[] interfaces = cls.getInterfaces();
for(Class c:interfaces){
System.out.println(c);
}
//得到父类的接口:
//先得到父类的字节码信息:
Class superclass = cls.getSuperclass();
//得到接口:
Class[] interfaces1 = superclass.getInterfaces();
for(Class c:interfaces1){
System.out.println(c);
}
//获取运行时类所在的包:
Package aPackage = cls.getPackage();
System.out.println(aPackage);
System.out.println(aPackage.getName());
//获取运行类的注解:
Annotation[] annotations = cls.getAnnotations();
for(Annotation a:annotations){
System.out.println(a);
}
}
}