day01-day07学习

javaSE 学习

java介绍

​ java的起源:1991年 SUN(太阳微系统公司)公司接到了一个项目,组成了一个Green小组,组长就
是詹姆斯·高斯林(James Gosling)【java之父】(加拿大人,出生1955年---),刚开始写项目时先采
用C++,编写项目实发现c++语言在写词项目有问题,有很多的内容知识是用不到的,而且会拖慢整体的
项目效率。组长就取其精华,去其糟粕,发展了一种新语言,OAK语言(java前身)。
java的发展:1993年 第一个浏览器出现,高斯林发现OAK与浏览器结合和可以实现很好看的效果。java
从此开始提出来。
​ java的提出:1995年 SUN公司正式对外发布java语言。
​ java的名称和标识的由来: OAK被注册了,组员之一想到了一个在java岛(爪哇岛)喝的一种非常美味的
咖啡。

java跨平台原理

平台:平台指的是操作系统:windows、Mac、Linux等
跨平台:指的是Java程序可以运行在任意平台上

java是一个跨平台的语言(个人听到的都是跨操作系统),如下图所示

image-20250715212300643
1.简述JRE 2.简述JDK 3.简述JVM
1.	JVM 是java虚拟机,可以将java源代码编译而成的字节码文件实现跨平台运行(跨操作系统);
2.	JRE是运行环境,包含了JVM和核心类库,主要可以对java程序进行编译,可以运行简单的java程序;
3.	JDK是java开发工具包,包含了JRE和多种工具,可以对java程序进行编译。

JRE、JVM、JDK三者之间的关系

image-20250715212407699

day01、标识符和变量名等命名规范

1、包名

包名的命名格式直接全部小写即可,在 src目录下,下图中day01就是一个包名。通常情况下都会在包下创建类,方便代码整理。

image-20250715122554192

2、类名

类名采用的是大驼峰的形式(就是每个单词的首字母大写),如果只有一个单词的话那就直接首字母大写即可。该图中hello是不规范的命名格式,尽量做到大驼峰可达到见名知意的效果。

image-20250715123131907

注意:这个hello代表的就是类名,需要与文件名字相同

image-20250715123820306

3、变量名

变量名命名格式

1. 组成由字母,数字,下划线还有$组成(但是数字不能是开头);
2. 命名不能跟**关键字相同(就是public一类的)**
3. 区分大小写,大小写不同就是不同的变量,存放的位置也就不同。
4. 我感觉得小驼峰的形式(就是第二个单词首字母大写的意思)

命名时候就是见名知意即可,尽量用英语单词

     //正确的命名格式
        int a1_$ = 10;
        // 下面的 a和A不是同一个变量,区分大小写
        int a = 10;
        int A = 10;

     //错误命名格式 1、关键字为变量名  2、数字开头的变量名
        int public = 10;
        int 1b = 10;

4、转义字符

        \t 代表的是制表符 效果如同一个tab
        \r\n 代表的是换行符号
        System.out.println("\t谁不曾一意孤行,怒发冲冠过怕只怕少了那份执着\r\n" +
                            "\t世界上那些最容易的事情中,拖延时间最不费力\r\n" +
                            "\t你若不想做,会找一个或无数个借口;你若想做,会想一个或无数个办法");

day02、各种变量的定义以及范围

1Byte = 8bits(一个字节等于八位) 计算机最小存储单位是字节(Byte),计算机内存的最小存储单位是位(bit),内存中以二进制的方式进行存储

1、数据类型的分类

​ 数据类型分为两种:基本数据类型和引用数据类型

基本数据类型有:整数型、浮点型、字符型、布尔型;

引用数据类型有:类、数组、接口

数据类型 关键字 内存占用 取值范围
字节型 byte 1个字节 -128 至 127 定义byte变量时超出范围,废了
短整型 short 2个字节 -32768 至 32767
整型 int(默认) 4个字节 -231 至 231-1 正负21个亿
-2147483648——2147483647
长整型 long 8个字节 -263 至 263-1 19位数字
-9223372036854775808到9223372036854775807
单精度浮点数 float 4个字节 1.4013E-45 至 3.4028E+38
双精度浮点数 double(默认) 8个字节 4.9E-324 至 1.7977E+308
字符型 char 2个字节 0 至 216-1
布尔类型 boolean 1个字节 true,false(可以做判断条件使用)

数据类型的取值范围是根据二进制将字节换成位,首位是符号位主要是看正负,1为负,0为正

求byte数据类型的范围即可推导出其他数据类型的范围

byte数据类型占1个字节
1字节=8位
第一位是符号位
剩下7位最大时候是1-1111111,因此最大时是0-1111111(前面是符号)
最小时候是 0000000,因此最小是10000000
因为有正负之分,所以可以是 -2^7 ~ 2^7-1
其他的以此类推

2、变量的定义

1、变量定义时候相当于在内存中开辟一个空间,里面存放数值,存放的数据可以被修改。注意:存放的变量名 a不代表是一个内存地址
2、变量命名方式遵循小驼峰命名方式,第二个单词首字母需要大写。 如:myAge
3、常量的定义是用 final关键字来修饰,常量的值不可改变。 如 final double PI = 3.14;

  • 整数类型的定义
        //整数类型有四种:byte、short、int、long
        byte a1 = 10;
        short a2 = 20;
        //int类型的范围一般不超过21亿
        int a3 = 30;
        // long定义的数据需要在后面加上一个L或者是l,否则默认是int类型
        long a4 = 40L;
        System.out.println(a1);
        System.out.println(a2);
        System.out.println(a3);
        System.out.println(a4);
  • 浮点型的定义

            //浮点型:float是单精度 double双精度,不加后缀默认就是double类型,
            float f1 = 1.32f;		//不管小数点多长默认留7位,会四舍五入
            double d1 = 1.11;		//不管小数点多长默认留15位或16位,会四舍五入
            System.out.println(f1);		
            System.out.println(d1);		
    
    
    		float f2 = 1.11111189111111111111111f;
            double d2 = 1.11111189111111111111111;
            System.out.println(f2);
            System.out.println(d2);
    
    
  • 字符型

必须有单引号包裹着,内容只能有一个值:数字、字母、字符、汉字、转义字符。有且仅有一个。通过ASCII表可以转换成数字,是一个映射关系。

image-20250717092836327
        char c1 = 'a';		//声明变量时只有一个字符
        char c2 = 'b';		//单引号包裹
        System.out.println(c1);
        System.out.println(c2);
  • 布尔型
        //布尔型,占用字节是 1个
        boolean b1 = true;
        boolean b2 = false;
        System.out.println(b1);
        System.out.println(b2);
  • String 字符串(类中的一个分支,属于引用类型)

双引号包裹着,字符长度可以随意转换

String s = "";

3、进制之间转换

二进制八进制十六进制数在idea中表示方法如下

在idea中默认是十进制数 二进制数以 0b或者0开头如: 0b1010/01010
                       八进制数以 0开头,如: 0123
                       十六进制数以 0x开头 如:0x2A

十进制转换成二进制数是不断地除以2每次把余数放一边,直到剩下1把1还有上面的倒着写下来就ok了。

学到一个引用类型的数据类型:引用类型 String属于类中的一个类型,内容和长度不限(也可以是空的),需要用双引号包裹;本质上是一个字符的数组拼接而来

        //定义一个字符串
        String s = "你好";
        System.out.println(s);

day03、数据类型转换和运算符等

1、数据类型转换

目前本次笔记记录的数据类型转换主要是基本数据类型的转换,下图中没有boolean类型的数据类型转换(箭头代表着可以进行自动类型的转换)

image-20250716123643644

数据类型的转换分类:

  • 自动类型转换(隐式类型转换):

    • 从低精度数据类型向高精度数据类型转换,主要针对的是浮点型数据(精度)
            //定义一个float类型的数据转换成double类型,实现了低精度向高精度数据类型的转换
            float f1 = 1.23456789f;		//切记浮点型默认数据类型是double类型,需要加上f后缀
            double d1 = f1;
            System.out.println(f1);
            System.out.println(d1);
    
    		//定义一个int类型的整形数据,并将整形数据给float或double实现了从低精度到高精度的自动类型转换
            int i = 100;
            float f2 = i;			//将整型数据转换成了浮点型
            double d2 = i;
            System.out.println(f2);
            System.out.println(d2);
    
    • 从低空间数据类型转换成高空间数据类型(空间)
            //拓展空间的数据类型转换 
            byte b1 = 100;
            short s1 = b1;  //将b1转换成short类型存到容器中
            int i1 = b1;    //将小范围的数据赋值给大范围的数据类型可以自动转换,小的数据类型的值可以自动转换成大的数据类型
            System.out.println(b1);
            System.out.println(s1);
    
  • 强制类型转换(显式类型转换):

    • 从高精度到低精度的数据类型转换
           //定义一个double类型的数据类型转换成float数据类型,需要进行强数据类型转换
           double d3 = 1.2345678987654321;
            float f3 = (float)d3;       //在进行高到低的转换时候需要加上一个(高数据类型)代表强制转换
            int i3 = (int) d3;			//需注意在进行转换时高精度相对于低精度数据类型,多余的位会丢失
            System.out.println(d3);												也就是小数点后面太长的话会找不到
            System.out.println(f3);    
            System.out.println(i3);
    
    • 从大范围空间数据类型转换成小范围数据类型
            int i4 = 127;
            short s4 = (short)i4;       //需要注意数据类型的范围,否则会放不下
            byte b4 = (byte)i4;         //超出范围时候得到的数是截取在内存空间中存储的位,然后第一位是0或1 所以正负是不确
            System.out.println(s4);																		定的
            System.out.println(b4);
    

    整数类型大范围向小范围数据类型转换时需要注意数据的大小范围是否已经超出小空间数据类型的范围,下面是解释为何整数类型超出范围时数据的正负不确定。

    在计算机内存中数据存储都是按照二进制的方式存储的,因此大范围数据也是二进制形式存储
    在进行强制类型转换后,拿short和byte数据类型转换举例
    short是2个字节16位  byte是1个字节8位
    因此byte数据类型只能从最右边开始截取8位,随后进行补码、反码什么的操作,第一位代表的是符号位,因此0和1不确定,正负也就不确定
    

    注意:在进行强制类型转换时需要注意各个数据类型空间的大小。

    • 当空间和精度同时进行数据类型转换时候,转换标准按照精度来看
            //将整数赋值给浮点型数据类型,不管是double还是float都会变成浮点型。
            float f5 = 3;       //如果空间和精度同时发生转换的话,以精度为主
            double d5 = 5;      //两者都发生了自动化类型转换,将整形自动转换成了浮点型;
            System.out.println(f5);
    

2、算数运算符

补充:在java中有特殊含义的字符称为运算符

下面的是基本的算数运算符:

image-20250716211357826

算数运算符的应用

        int b1 = 31;
        int b2 = 6;
        System.out.println(b1 + b2);
        System.out.println(b1 - b2);
        System.out.println(b1 * b2);
        System.out.println(b1 / b2);    //整型相除取整数
        System.out.println(b1 % b2);   //取得是除不尽的余数

注意:

  • byte、short、char参与运算后会自动转换成 int 数据类型,所以接收的变量需要是 int类型的;
short s = 10;
byte b = 10;
int i = s + b; 		//必须由int数据类型来接收,参与的计算的转换成了int类型
  • 参与运算的数中有浮点型存在时,接收数据的数据类型必须是浮点型,并且接收变量的数据类型必须是范围大的浮点型类型;
 //case1:	定义一个int类型和一个float类型
 int a = 10;
 float f = 1.1;
 float result = a + f;  //有浮点型数据参与必须由浮点型数据类型接收
 
 //case2: 定义一个double类型和float类型
 double a = 10;			
 float f = 1.1;
 double result = a + f; 	//结果需要用double数据类型接收
  • 参与运算的都是整数类型的话,最后结果由参与运算的空间最大范围的数局类型接收
int a = 10;
short b = 10;
long l = 10;
long result = a + b + l;		//结果的数据类型是参与运算的最大类型。
  • 0不能作为除数参与算数运算,但是写0编译不会出错,计算结果会发生异常
        int i4 = 10;
        System.out.println(i4/0);

下面是报的异常

image-20250716212815103

今日误点:

//java程序运行顺序是从上到下,从左到右,
System.out.println("你的年龄:" + 20 + 30);		//从左到右进行字符串拼接,因此输出的结果是……2030
System.out.println("你的年龄:" + (20 + 30));	//括号提高优先级,因此输出的结果是……50

还有一些赋值运算符

image-20250716222324403

例如:

a += 1; 他的效果等同于 a = a + 1;

在这里要重视数据类型的转变,倘若上方定义的 a是byte类型,a = a + 1;后面的1实际上是 默认int类型,因此这样赋值的时候需要进行强制类型转换。

3、自增与自减

image-20250716222624208

自增与自减只要分为两种,前自增和后自增(前自减和后自减)

前自增意思就是在返回数值前就进行+1的操作,随后在进行算数运算等操作;

后自增就是先将自身的值给到算数运算中,然后再进行+1的操作,相当于每个元素豆油一个括号。

int a = 10;
int b = (++a) + (a++) + (a--) + (--a);		//实际上每个元素相当于自身有一个括号单独进行操作。
		11		11		12		10
        先加	  a=11    加号结果是12   随后后自减和前自减a = 10;
b = 44

4、逻辑运算符

逻辑运算符有以下几种,输出的结果都要用boolean类型的值来接收,也就是false或者是true

image-20250716223322567
  • 为什么说 && 效率比 & 高呢 或者说 || 效率比 | 效率高?

因为 & 不管符号左右执行的结果是true还是false,都会对右边部分进行判断,但是 && 左边是false的话会不管右边直接输出

false。(可以定义一个变量在右边定义一个前自增的方式进行验证)

int a = 10;
boolean b1 = a<10 & ++a>10;		
boolean b2 = a<10 && ++a>10;
再用输出语句输出两个a的值会发现第二种左边执行完后已经可以确定boolean值后已经不再运行后边那段。

5、三目运算符

  • 表达式: 条件判断?true的运行结果 :false的运行结果

输出的结果只有两种,如果有多种输出结果的话那就只能用 if-else条件判断语句进行,下面的代码是一个简单的应用。

int a= 450,b=56;
int max = (a>b)?a:b;
System.out.println("最大值是:"+ max);

里面用到了Scanner类和三目运算符的混合搭配

        // 定义一个数据,判断是否1--100之间的输入, 如果是打印 xx是1--100之内的数字,反之xx不是1--100之内的数字
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个整数");
        int a = sc.nextInt();

        String str = a >= 0 && a <= 100 ? a + "是1--100之内的数字" : a +  "不是1--100之内的数字";

        System.out.println(str);
  • 在使用时需要先创建一个对象
Scanner sc = new Scanner(System.in);
  • 随后再调用即可
int a = sc.nextInt();	//键盘输入的是整数类型
String s = sc.next();	//可以接收键盘输入的字符串,但是遇到空格就会中断输出
String s2 = sc.nextLine();		//接收键盘输入的字符串,不会被空格中断输出

使用其他类的时候需要先导包

6、Scanner类

1、创建一个Scanner类的对象(需要导入类的包)
	Scanner sc = new Scanner(System.in);
2、设置提示
	sout("请输入……");
3、获取数据(调用sc对象的内置方法):
	//整数类型
	byte b = sc.nextByte();
	short s = sc.nextShort();
	int i = sc.nextInt();
	//布尔型
	boolean b1 = sc.nextBoolean();
	//字符串
	String s1 = sc.next();	//遇到空格,空格后面的内容不会被识别
	String s2 = sc.nextLine();	//会完整的识别输入内容,空格也会被输出
	

7、运算符的优先级

通过小括号的方式改变优先级即可

以下了解即可:

​ 一目运算符 ++ --

​ 二目运算符 + - * / %

三目运算符 条件判断?true执行语句:false执行语句

day04、java分支和循环

1、if-else流程控制

java程序的运行顺序是 从上到下,从左到右进行的。(顺序结构)可以通过一些控制语句控制代码运行的顺序。

流程图:

image-20250717180430880

分类:

  • 顺序结构(从上到下的顺序)
顾名思义就是从上到下,从左到右的默认顺序,main是程序运行的开端
        //顺序结构:从main开始,执行到main方法中代码结束
        System.out.println("------------程序开始了-----------");
        int a = 10;
        a++;
        System.out.println(a);
        a +=10;
        System.out.println(a);
        System.out.println("------------程序结束了-----------");
  • 分支结构(选择结构)

就是一些 if 单分支、if-else双分支和多分支、还有switch选择语句

//	if单分支语句
if(条件判断){
	条件判断结果为 true执行的代码块
}

//	if-else多分支选择语句
if(条件判断语句1){
	执行代码块1
}else if(条件判断语句2){
	执行代码块2
}else if(条件判断语句3){
	执行代码块3
}else{
	执行代码块4
}

if-else案例:

  1. if-else嵌套使用。内部嵌套if-else语句
public static void main(String[] args) {
    //判断一周内什么时候上班
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入今天的日期:");
    int week = sc.nextInt();
    if (week > 0 && week <= 7){
        if (week == 7){
            System.out.println("周末休息,开始玩耍");
        }else {
            System.out.println("速速上班");
        }
    }else {
        System.out.println("医院才是你的归宿");
    }
}

案例2:if-else

//某体育彩票中心推出优惠充值活动,
// 充值50元送20元,充值100元送50元,充值500元送300元。
//如果账户余额是26元,请输入一个充值金额,计算最终账户余额

//余额
int money = 26;
//动态获取充值的钱
Scanner sc = new Scanner(System.in);
System.out.println("请输入充值金额:");
int add = sc.nextInt();


//
if (add >= 500){
money = money + add + 300;
//System.out.println("你的余额是" + money);
} else if (add >= 100) {
money = money + add + 50;
//System.out.println("你的余额是" + money);
} else if (add >= 50) {
money = money + add + 20;
//System.out.println("你的余额是" + money);
}else {
money += add;
//System.out.println("你的余额是" + money);
}
//重复代码中的money是作用域是整个main中,所以可以拿出来输出,减少代码的冗余度
System.out.println("你的余额是" + money);


sc.close();
System.out.println("--------------------------------");
}

重要案例:需要注意后续加上前面的钱数(没有将前面的里程数车费加上,只考虑到某一段的金额)

/*	出租车计费标准为:3公里以内10元,3公里以后每1公里加2元,超过15公里,每1公里加3元,行
	驶过程中每等待5分钟,需要增加2元(等待12分钟,加4元,非6元)。请输入一个最终公里数和
	行驶中的等待时间,计算应付的车费。
	提示:判断等待时间是5的几倍,就增加几倍的2元。*/
Scanner sc = new Scanner(System.in);
System.out.println("请输入公里数和时间:");
double s = sc.nextDouble();
double wait = sc.nextDouble();
double fare = 0;
int multiple = (int)wait/5;
//类似于第一题
if (s < 3){
    fare = 10;
}else if (s < 15){
    fare = (s-3) * 2 + 10;
} else if (s >= 15) {
    fare = (s-15) * 3 + (15-3) * 2 + 10;
}
fare += multiple * 2;
System.out.println("车费是" + fare + "元");


sc.close();

2、switch选择语句

流程图:

image-20250717180557992

switch语句的结构如下:

  • switch 结构具有穿透性,需要添加break来进行阻止穿透。
switch(变量){
		case 1:
        	执行语句1;
        	break;
		case 2:
        	执行语句2
            break;
		case 3:
        	执行语句3
            break;    
		......
		case n:
        	执行语句n;
        	break;
		default:
        	默认执行(等同于if-else中的else代码块)
            //最后这一部分不用加break,不管谁在最后都可以不加break
}
  • switch语句中接收的变量在java中只能是byte、short、int、char、String、enum(枚举)类型的数据。
  • 如果输入boolean等其他类型的话会出现 Incompatible一类的报错,adj、不兼容的
       // 1.switch里面每个情况的值只能是byte、short、int、char、String;
        // 千万别用boolean值
        // 2.switch具有穿透性,可以用break来结束switch结构
        // 3.switch里面的defult相当于if-else中最后的else;
        // 4.default里面的break可有可无

        //
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入今天是周几:");
        int week = sc.nextInt();
        //byte week = sc.nextByte();
        //short week = sc.nextShort();
        switch (week) {
            case 1:
                System.out.println("周一");
                break;
            case 2:
                System.out.println("周二");
                break;
            case 3:
                System.out.println("周三");
                break;
            case 4:
                System.out.println("周四");
                break;
            case 5:
                System.out.println("周五");
                break;
            case 6:
                System.out.println("周六");
                break;
            case 7:
                System.out.println("周日");
                break;
            default:
                //可以没有break,不会继续穿透
                System.out.println("你蒙蔽了");
                break;

        }

switch案例

/*
        * 从控制台输入一个成绩,判断其等级(优良中差)
	注意:输入数据有误如果不在1--100之间要单独提示数据有误	*/
Scanner sc = new Scanner(System.in);
System.out.println("请输入本次考试成绩");
int score = sc.nextInt();
if (score > 100) {
    System.out.println("想上天了你?");
} else if (score >= 90) {
    System.out.println("优秀");
}else if(score >= 80){
    System.out.println("良好");
} else if (score >= 70) {
    System.out.println("中等");
} else if (score >= 60) {
    System.out.println("及格");
} else if (score >= 0) {
    System.out.println("等着挨打吧");
}else {
    System.out.println("天堂有路你不走");
}
}

3、代码调试

  • 在idea中某一行代码前面点断点,如下图:

image-20250718093216329

  • 然后用debug的形式运行代码
image-20250718093256088

day05、循环结构

根据某些条件会重复执行代码块

循环体:是重复执行的代码块;

循环条件:是循环重复的判断条件,true就执行,false跳出循环

for循环时最常用的循环,for循环使用场景是明确知道循环次数的情形;

while循环是java重第二常用的循环,可以和for循环相互替换,while循环可以用于不知道循环次数的循环重;

do...while循环会比其他循环多执行一次

实现循环的方式有:

1、for循环

for(初始化表达式 ;条件判断 ;迭代表达式){
	重复执行的代码块
}

简单案例

  • 1-100之间数字的和
//1-100之间数字的和
int sum = 0;	//定义一个变量接收100以内数字的和
for (int i = 1; i <= 100; i++) {
    sum += i;
}
System.out.println("1-100之间数字的和是" + sum);
  • 分别输出1-100之间的奇数和偶数和
 //分别输出1-100之间的奇数和偶数和
        int oddSum = 0;
        int evenSum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i%2 == 0){
                oddSum += i;
            }else {
                evenSum += i;
            }
        }
        System.out.println("奇数和是:" + oddSum);
  • 逢7过游戏:个位为7,十位为7,7的倍数
        //逢7过游戏:个位为7,十位为7,7的倍数
        int num = 0;
        for (int i = 1; i <= 100; i++) {
            if (i%7 != 0 && i % 10 != 7 && i/10 != 7) {
                System.out.print(i + " ");
                num++;
                //每行输出10个数
                if (num == 10){
                    System.out.println();
                    num = 0;
                }
            }else{
                System.out.print("过 ");
            }
        }
  • 双层for循环的使用

    • 外层for循环遍历一次,内层循环遍历 n 次;
    • 时间复杂度是 n^2。
  • 双层for循环理解代码

            //双层循环嵌套
            for (int i = 0; i < 5; i++) {
                System.out.println("-------第" + (i+1) + "次循环-------");
                for (int j = 0; j < 5; j++) {
                    System.out.println("内层循环第" + (j+1) + "次");
                    }
            }
    

    实现的效果如下图所示(代码运行结果未展示完整):

    image-20250718151227287
  • 双层for循环输出一个等腰三角形

    讲解:外层循环进行一次的时候,内层循环完整的进行一次循环,将等腰三角形分成四个部分,第一部分是打印空格,第二部分打印直角三角形,第三部分打印直角三角形,第四部分打印空格

            //打印等腰三角形
            for (int i = 0; i < 8; i++) {
                for (int j = 8-i; j > 0; j--) {
                    System.out.print(" ");
                }
                for (int j = 0; j < i; j++) {
                    System.out.print("*");
                }
                for (int j = 0; j < i; j++) {
                    System.out.print("*");
                }
                for (int j = 8-i; j > 0; j--) {
                    System.out.print(" ");
                }
                System.out.println();
            }
    

    运行截图如下所示:

    image-20250718151634930
  • 输出一个九九乘法表

            //九九乘法表
            for (int i = 1; i <= 9; i++) {
                for (int j = 1; j <= i; j++) {
                //这里需要用print输出每一行的内容不会换行
                    System.out.print(j+ "*" + i + "=" + (j * i) + "\t");
                }
                //这里需要用println对每一行内容进行换行。
                System.out.println();
            }
    
  • 输出一个双向的九九乘法表:

            //九九乘法表
            for (int i = 1; i <= 9; i++) {
                //输出每行的空格
                for (int j = 9; j > i; j--) {
                    System.out.print("\t\t");
                }
                //输出乘法表左侧部分
                for (int j = 1; j <= i; j++) {
                    System.out.print(j+ "*" + i + "=" + (j * i) + "\t");
                }
                //输出乘法表右侧部分,第一行不输出,所以需要j<i才行
                for (int j = 1; j < i; j++) {
                    System.out.print(j+ "*" + i + "=" + (j * i) + "\t");
                }
                System.out.println();
            }
    

    运行截图如图所示:

    image-20250718161345283

2、while循环

结构如下:

int i = 1;	//初始化表达式
while (i <= 10){	//条件判断语句(如果定义的是true的话会陷入死循环)
	sout("执行语句");		//循环体
	i++;		//迭代语句
}

执行流程如下:

image-20250718165516302
  • while循环案例

            //使用while打印1-100之间所有的偶数
            int i = 1;
            while(i <= 100){
                if (i % 2 == 0){
                    System.out.print(i + "\t");
                }
                i++;
            }
            System.out.println();
            System.out.println("------------------------------");
            
            //计算1-100之间的偶数和以及奇数和
            int j = 1;
            int odd = 0;
            int even = 0;
            while(j <= 100){
                if (j % 2 == 0){
                    even += j;
                }else {
                    odd += j;
                }
                j++;
            }
            System.out.println("偶数和是:" + even);
            System.out.println("奇数和是:" + odd);
    
            System.out.println();
            System.out.println("------------------------------");
            
            //计算10的阶乘
            int n = 1;
            int mul = 1;
            while(n <= 10){
                mul *= n;
                n++;
            }
            System.out.println("阶乘是:" + mul);
    

    运行结果如下:

    image-20250718165334319

3、do......while循环

do...while至少会执行一次,先执行do里面的内容,然后再执行条件判断。

do...while循环的结构如下:

变量初始化
do{
	循环体语句;
    迭代语句
}while(条件表达式);	//条件表达式

4、随机数

学到了另一个工具类

Random:随机数

        //创建一个随机数对象
        Random r = new Random();
        //调用随机数方法,生成0-99之间的随机数,左闭右开
        int i = r.nextInt(100);
        //如果生成1-100之间的随机数,直接在后面加1就行
        int n = r.nextInt(100)+1;

作业案例:生成随机数判断猜大了还是猜小了

里面用到了随机数,并进行简单的逻辑判断,没有限定猜的次数。

        /*猜数字。产生1个[1, 100]的随机数,从键盘上输入你的猜测结果,如果猜测的数比随机数
大,提示"你猜的数据大了",如果猜测的数比随机数小,提示"你猜测的数据小了",如果猜测的数
和随机数一样大,提示"恭喜你猜对了",计算出猜了几次才猜对。
要求:每次输入的提示,都要缩小范围【请输入min--max之间的数字】  min和max根据你输入的数据随之发生改变*/

		//创建一个随机数对象
        Random r = new Random();
        //生成1-100之间的随机数
        int rand = r.nextInt(100)+1;
        //创建Scanner对象
        Scanner sc = new Scanner(System.in);

		//定义范围还有计数器
        int num = 0;
        int min = 1;
        int max = 100;
        //循环重复猜数字的过程
        while(true){
            num++;
            System.out.println("请输入你猜的值:");
            int guess = sc.nextInt();
            //比较猜的数和生成的数哪个大进行逻辑判断,并在内部缩小范围
            if (guess > rand){
                max = guess<max?guess:max;
                System.out.println("你猜的数大了,随机数在" + min + "-" + max + "之间");
            } else if (guess < rand) {
                min = guess>min?guess:min;
                System.out.println("你猜的数小了,随机数在" + min + "-" + max + "之间");
            } else if (guess == rand) {
                System.out.println("你猜对了,随机数是" + guess);
                //如果猜对了才会执行的部分,直接break退出循环
                break;
            }
        }
        System.out.println("你猜了" + num + "次");

5、求1-100之间的质数

1-100之间的质数,质数就是只能被1和自身整除,其他的不能被整除,这样的数被称为质数

        for (int i = 1; i < 101; i++) {
            //定义一个布尔类型的变量
            boolean flag = true;
            for (int j = 2; j <= i/2; j++) {
                //条件判断如果可以整除那就执行并跳过,不能满足内层循环跳到下一次循环中
                if (i%j == 0){
                    flag = false;
                    break;
                    }

            }
            //如果最终内层循环都没有干预,那么flag的值就是true,就会执行这个语句
            if(flag){
                System.out.print(i + " ");
            }

        }

6、break和continue的使用

break:

  • 在循环中,可以直接跳出本层循环,如果是双层循环的话,内层写break只会跳出内层循环,外层循环不受影响;
  • 在switch中可以避免case穿透;

continue:

  • 是跳出本次循环,直接执行下一次循环;

拓展,标识符

        // break和continue可以和标识符一起使用,标注一个结构,
        // 在这个代码结构里将标识符放在外侧循环结构里,可以与标识符互动
        aa:
        for (int i = 1; i <= 10; i++) {
            for (int j = 1; j <=10 ; j++) {
                if (j == 4){
                    break aa;
                }
                System.out.print(i + "---->" + j + "\t\t");
            }
            System.out.println();
        }

day06、数组

1、数组的特征

  • 数组一旦被定义的时候长度不可改变;
  • 数组是可以包裹多个相同数据类型的容器;
  • 数组是引用类型,容器里面可以存放多种数据类型(包含引用类型)
  • 可以同时保存多个相同数据类型的数据的容器

2、数组的定义方式

  1. 数组的声明

    数据类型[] 数组名;
    数据类型 数组名[];
    如: int arr[];	//定义一个数组
    
  2. 数组的初始化

    数组名 = new 数据类型[数组长度];
    如  arr = new int[5];	//在堆内存中开辟了5个内存空间
    

声明初始化同时进行

数据类型 数组名[] = new 数据类型[数组长度];
如: int arr[] = new arr[5];

声明初始化并且赋值

数据类型 数组名[] = new 数据类型[]{值1,值2...};
如下:
int arr[] = new int[]{1,2,3,4,5};

直接赋值

数据类型 变量名[] = {值1,值2...};	//数组的长度是固定的,不能继续增长。
例子: int arr[] = {1,2,3};

数组的默认值是:

image-20250721173545555

3、内存说明:

  • 在声明数组的时候会在栈内存中开辟一个空间,里面存放的是一个地址;也会在堆内存中开辟一个长度固定的数组空间;直接对数组名进行输出的话会输出一个地址,栈内存中存放的地址指向堆内存中开辟的第一个小格子的地址。

  • 如果将一个原有的数组赋值给另一个数组如:int bb[] = arr;那么会将地址给新数组,依旧对应的是堆内存中开辟的对应空间。

如下:

image-20250721162615458

注意:引用类型每new出一个对象就会在堆内存中开辟一个空间。

        //声明初始化一个数组
        int arr[] = new int[]{1,2,3,4};

        //将arr的地址给了arr2
        int arr2[] = arr;
        //遍历数组arr2
        for (int i = 0; i < arr2.length; i++) {
            System.out.println(arr[i]);
        }

4、数组越界

就是在遍历数组的时候,超出原本的数组范围;

        //声明初始化一个长度为5的数组
        int arr[] = new int[5];
        arr[0] = 1;
        arr[1] = 1;
        arr[2] = 1;
        arr[3] = 1;
        //没有定义arr[4],存放的是默认值

        arr[5] = 1; //内存空间中没有下标为5的部分
        System.out.println(arr[5]);
输出的结果如下所示

image-20250721165758056

5、二维数组

我的理解就是类似于数学中的集合,每个元素是另一个长度统一的集合

二维数组定义方式:

数据类型 数组名[][] = new 数据类型[一维长度][二维长度];

在内存空间中

  • 栈内存空间开辟一个空间,在堆内存中开辟长度固定的空间,栈内存中存储的是堆内存空间中数组的首个地址;
  • 二维数组相当于一维数组的每个元素还是一个数组元素,会在堆内存中继续开辟一个空间数组空间,所以他一维数组存储的数据是二维数组的首地址值。

案例

1、通过数组求最大值和最小值

        //求一个数组中的最大值和最小值
        //定义一个数组
        int arr[] = new int[]{99,88,66,44,1};
        //定义变量接收最大值或者最小值
        int max = arr[0];
        int min = arr[0];
        for (int i = 0; i < arr.length; i++) {
            //判断最大值
            if (arr[i] > max){
                max = arr[i];
            }
            //判断最小值
            if (arr[i] < min){
                min = arr[i];
            }
        }
        System.out.println("最大值是:" + max);
        System.out.println("最小值是:" + min);

2、从键盘录入数据存入数组中,求平均数

        //从控制台接受五个数,求平均值
        //声明初始化一个空间为5的数组
        int arr[] = new int[5];

        //接收键盘数据
        Scanner sc = new Scanner(System.in);

        //通过for循环对数组里面的内容进行填充
        for (int i = 0; i < arr.length; i++) {
            System.out.println("请输入第" + (i+1) + "个数");
            arr[i] = sc.nextInt();
        }
        //定义总和
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum +=arr[i];
        }
        //定义平均数
        double everAge = sum / arr.length;
        System.out.println("平均数是:" + everAge);


        sc.close();

3、冒泡排序

简单说明:

  • 冒泡排序是在一个无序的数组中,通过相邻两个数的比较,选出最大值或者是最小值,依次换位将最大值或者最小值放在最后一位;

代码实现:

    public static double[] getArr(double[] arr){
        //冒泡排序,两两比较
        for (int i = 0; i < arr.length -1; i++) {	
            //外层循环一共 n-1次,例如 -1,2,7,1,3中只需要找到n-1个最大的数或者最小的数即可,最后一个是默认的
            for (int j = 0; j < arr.length-i-1; j++) {
                //内层循环每一次会找到两个数进行比较,比较之后会根据升序降序要求与相邻元素换位
                //每完成一次完整的内层循环,都会找到一个最大值或者最小值,那么总的比较的次数就会 -i
                double tmp = arr[j];
                if (arr[j] < arr[j+1]){
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                }
            }
        }
        return arr;
    }

4、选择排序

简单说明:

  • 选择排序是选择数组第一个元素与后续的元素依次进行比较,选出最大值或者是最小值,比较一轮后会有一个最大值或者是最小值被选出。

day07、面向对象的思想(oop)

平时都是面向过程的思想(pop),高级语言都支持面向对象编程

类的定义格式:

修饰符 class 类名{
	
}

如:
    public class className{
        
    }

一个类中其他类的定义格式:
    class className{
        
    }

解释:一个文件中只能有一个用public修饰的类

1、面向过程编程:

  • 面向过程编程是一种以功能为中心来进行思考和组织的编程方法,它关注的是功能如何实现,就是分析出解决问题所需要的的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个调用就可以了。关注的是功能的实现。

2、面向对象编程:

  • 面向对象编程是一种以事物(对象)为中心的编程思想,它关注的是完成某件事情需要哪些对象参与,这些对象应该具有什么样的属性和方法。通过对象的协作完成某件事情。----关注的是参与者(类)怎么设计。
  1. 类是对具有相同特征和行为的对象的抽象
    • 类本质上也是一个自定义的数据类型;
    • 可以创建无数个具有相同属性和行为的对象。
  2. 属性是对象具有的各种特征。对象可以有很多个属性(成员变量)。

类与对象之间的关系

  • 类是对象的抽象化
  • 对象是类的具体化、对象是类的实例化
  • 类是对象的模板,一个类可以创建无数个相同属性和行为的对象

下面是创建一个类(自定义的数据类型)

public class Computer {
    //定义成员变量,类中有一个默认的无参构造器,供测试类创建对象
    String brand;
    double price;

    //定义方法//传入形式参数,是局部变量
    public void coding(String yy){
        System.out.println(brand + "电脑正在使用" + yy);
    }
    public void playGame(String game){
        System.out.println(brand + "电脑正在玩" + game);
    }

创建测试类main函数中创建对象

public class HomeComputer {
    public static void main(String[] args) {
        //创建电脑对象,实际上是调用类的无参构造器
        Computer computer = new Computer();
        computer.brand = "ThinkPad";
        computer.price = 7399;

        //调用方法
        computer.coding("java语言编程");

        Computer computer1 = new Computer();
        computer1.brand = "Acer";
        computer1.price = 5399;
        computer1.playGame("王者荣耀");

    }
}

(额外):内存空间分析

首先方法区会加载创建的类(Student为例子),创建对象并将对象赋值给变量s1(new Student();),new一个对象的时候,堆内存中会开辟一个空间,然后调用了构造器,将方法区加载的Student类中的内容放进堆内存并将对象初始化,栈内存中同时也会为s1开辟一个空间存放的是堆内存中创建对应对象的地址,通过使用s1.name并给堆内存中的内容赋值。

image-20250723110207291

3、成员变量(属性/成员变量/实例变量)

  • 属性是对类的静态描述

  • 属性的定义格式:

[修饰符] 数据类型 变量名;
  • 修饰符
    • private
    • 缺省(不写修饰符的情况下对应的修饰符)
    • protected
    • public
  • 类中的属性也是对类的静态描述,也是成员变量,实例变量
  • 成员变量存储在对象中,在堆内存中存储(具体的后续应该会学)
  • 数据类型可以是基本数据类型,也可以是数组类型,也可以是对象类型(可以在成员变量定义引用类型)

如果在定义的类中没有给属性赋值,在测试类中也没有给该属性赋值,那么输出的时候会有默认值(根据数据类型的不同,默认值也不同),默认值不会开辟空间

image-20250723150136349

下面这两种是定义成员变量时候有默认值的情况下的区别

    //在类中定义这样的变量不给值时,默认值会开辟空间(以字符串为例)
    String name = "";
    String color = "";
    
    //在类中定义这样的变量,默认值不会是上面表格的内容,不会开辟空间
    String name;
    String color;

定义的成员变量是全局变量(在整个类中都可以调用)作用域是整个类

方法中传入的参数是形参,也是局部变量,作用域是一个方法的范围。

4、方法

方法组成三要素:返回值类型、方法名、参数列表

  • 方法中会传入参数,传入的参数是形参,作用域是定义的这个方法范围;

  • 方法是将实现某一功能的代码块统一包装起来,需要的时候调用使用即可;

  • 方法不能独立存在,需要在类中定义方法。

1、有返回值方法定义格式:

没有方法重载的情况下方法名是不能相同的,方法执行到 return 就会结束

修饰符 返回值类型 方法名(形式参数列表){
	方法体
	return 返回值;
}

如果没有返回值的话返回值用void代替,而且不用写return

案例:

创建一个老师类Teacher:

package day07;

//定义一个老师类,实现方法的写法
public class Teacher {
    //定义成员变量(实例变量)
    String name = "";   //null不会进行空间分配,""会进行空间分配

    //定义方法
    //无参无返回值方法
    public void teach1(){
        System.out.println(name + "老师在讲课");
        //没有return,或者只写一个return也可以
        return;
    }

    //有参无返回值方法
    //yy是形参,调用的时候接收值,传进去的值是实参
    public void teach2(String yy){
        System.out.println(name + "老师正在讲" + yy);
    }
		
    public void teach3(String yy, double time){
        System.out.println(name + "老师正在教室讲" + yy + "讲了" + time + "小时");
    }

    //有参有返回值(必须在方法体中写return,一般写在最后),
    public double salary(double time){

        //返回值类型是一个double类型
        //return具有结束方法的作用,如果执行了return,后面还有代码,后面的代码不会执行,会编译错误
        //会返回一个数据
        return time*100;
    }

}

创建一个测试类创建对象调用方法

有返回值类型的需要有数据接收返回值,然后再进行后续代码

public class TestTeacher {
    public static void main(String[] args) {
        //创建Teacher对象
        Teacher t1 = new Teacher();
        t1.name = "张无忌";
        //调用有参无返回值方法
        t1.teach3("java",2.5);
        //无参无返回值
        t1.teach1();

        //有返回值的方法需要用变量接收
        double s = t1.salary(2);
        System.out.println(s);
    }
}

2、无返回值方法定义格式:

调用该方法时直接执行方法体里面的内容,直到运行结束。

修饰符 void 方法名(形式参数){
	方法体
}

案例:

创建一个学生类Student

package day07;

//创建一个学生类
public class Student {
    //定义成员变量(实例变量,全局变量)
    //[修饰符] 数据类型 变量名 [= 值];
    String name;
    int age;
    String sex;


    //定义方法(成员方法)
    //修饰符 返回值 方法名(参数列表){ 方法体 }
    public void study(){
        System.out.println(name + "正在学习");
    }

    //这里的addr是形参,在测试类中调用方法时传入的参数是实参
    public void eat(String addr){
        System.out.println(name + "在" + addr + "吃饭");
    }
    public void sleep(){
        System.out.println(name + "在厕所睡觉");
    }

    public static void play() {
        System.out.println("呵呵");
    }

}

创建一个测试类创建对象去调用无返回值类型的方法

    public static void main(String[] args) {
        //创建(new,实例化)一个学生对象,类可以看作是一个自定义的数据类型(引用类型)
        Student s1 = new Student();
        s1.name = "哈哈哈";
        s1.age = 18;
        //对象属性不赋值会有默认值
        System.out.println(s1.sex);
        //引用类型依旧是一个地址,实际上还是通过地址找到堆内存中
        System.out.println(s1);

        //调用方法,调用有参方法,传入参数
        s1.eat("厕所");
        s1.sleep();
        s1.study();
		
        //调用静态方法
        Student.play();
    }

根据题目要求对方法返回值进行判断,方法的返回值需要用用一个同种数据类型的值接收。

3、方法重载

在java中在一个类中方法名字相同的情况下,方法内部参数的不同会发生方法重载的情况, 方法重载的类型如下

  • 参数个数不同

  • 参数类型不同

  • 参数顺序不同

    注意:返回值类型与方法重载没有任何关系,只注意方法名是否相同,如果相同的话看参数列表是否相同就好

    //定义方法
    //实现两数和
    public int getSum(int a, int b){
        return a + b;
    }

    //传入两个小数求和
    public double getSum(double a,double b){
        return a + b;
    }
以上两个方法就是方法重载,与返回值类型、返回值、修饰符没有关系
        public int getSum(int b, int a){
        return a + b;
    }
与上面的情况不是重载,属于是同一个方法,因为参数数据类型相同
  • 与方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系(只跟方法名还有参数列表有关,JVM会通过参数列表的不同调用某个方法)

4、类中的数组

java数组中存储的数据类型可以是基本类型和引用类型两种,恰好我们创建的类正好属于引用类型的数据,可以通过创建对象后将对象存放在数组中。

        //new一个对象
        Student s1 = new Student();

        s1.id = 1001;
        s1.name = "李白";
        s1.age = 1000;
        s1.sex = "男";
        s1.show();

        System.out.println("-------------");
//创建第二个对象
        Student s2 = new Student();
	
        s2.id = 1002;
        s2.name = "武则天";
        s2.age = 1000;
        s2.sex = "女";
        s2.show();

        //创建一个数组,包含上面二个对象
        Student arr[] = new Student[]{s1,s2};

以上代码是通过数组存储了两个Student类的实例化对象,可以通过实例化对象去引用某一个属性如:sout(s1.name)对某个属性进行操作。

5、构造器

构造器在定义的一个类中,构造器名与方法名需要相同。主要有无参构造器和有参构造器两种。

  • 构造器的作用是初始化对象的属性,当通过 new 关键字搭配构造器调用时,才会真正创建对象

  • 构造器执行是为已经通过 new 开辟的对象内存空间,进行属性等初始化操作

  • 只能在构造器的第一行使用,用于在有参构造器中复用无参构造器的初始化逻辑

  • 无参构造器

    //无参构造器(跟类名必须相同)
    public Dog(){
        System.out.println("无参构造器执行");
    }
    //在测试类中 new 一个对象实则是调用一次无参构造器;
	//创建对象的时候,无参构造器中的内容会执行一次
  • 有参构造器
   //有参构造器
    public Dog(String name){
        // this() 表示调用本类的无参构造器,注意:
        // 1. 只能在构造器的第一行使用,用于在有参构造器中复用无参构造器的初始化逻辑
        // 2. 不能递归调用,即不能在无参构造器里又调用当前有参构造器,否则会编译报错
        this();	//相当于调用本个类中的无参构造器。
        this.name = name;
        System.out.println("一个参数构造器执行");
        //构造器的作用是初始化对象的属性,当通过 new 关键字搭配构造器调用时,才会真正创建对象
        // 这里的构造器执行是为已经通过 new 开辟的对象内存空间,进行属性等初始化操作
    }

测试类调用无参构造器

        //用有参构造器创建对象并初始化对象
        Dog dog1 = new Dog("哈士奇");
        Dog dog2 = new Dog("土豪", "棕色的");

构造器与new配合使用才是创建一个对象,在内存中,new一个对象时会在堆内存中开辟一个空间,随后会默认调用无参构造器,初始化对象的属性(不赋值有默认值的状态)

调用无参构造器一次不会创建一个对象,(有参构造器被调用时会默认调用一次无参构造)在有参构造器中第一行 this();实际上就是调用一次无参构造器,但并不会创建对象。

空指针异常,调用了null的属性或者方法

5、修饰符

可以修饰成员变量,成员方法,构造器,类(前面一般都是public,很少用其他的)。

类被public修饰的时候,只能将类名与文件名相同

访问修饰符

  • public
  • protected
  • 缺省
  • private

修饰符的限制访问:
1.在同一个类中,修饰符修饰的属性或是方法都可以被访问;
2.在同一个包下,不在同一个类中可以访问到非私有(private)修饰的属性或方法;
3.在不同包中不同类中,只能访问公共的(public)的属性和方法;

​ 4.在不同包的子类中,只能访问公共的(public)和受保护的(protected)属性和方法。

image-20250724115819306

访问权限大小:

public > protected > 缺省 > private

final可以修饰变量,修饰方法,修饰类

  • 修饰变量:将变量变成常量
  • 修饰类:类成为最终类,不能被继承
  • 修饰方法:方法成为最终方法,不能被重写

6、面向对象三大特性

三大特性:封装、继承、多态。

1、封装

Java 中的封装是面向对象编程的三大核心特性之一,它的主要作用是将数据(属性)和操作数据的方法绑定在一起,并通过控制访问修饰符(所有的属性都私有化更安全)限制外部对数据的直接访问,从而提高代码的安全性和可维护性。

  • 所有的属性都私有化,会更安全;

  • 提供公共的get,set方法,在方法中加入相应的逻辑代码(如下set方法中);

    //此时有参构造器中需要调用set方法(里面有代码逻辑)
    public Book(String name, double price){
            //这里虽然可以给对象赋值,但是对价格没有要求,
            // 还是会发生价格为负的情况
            this.name = name;
            setPrice(price);
        }
    
    //对象通过调用get方法可以获取属性的值
    public double getPrice() {
            return price;
        }
    //对象通过set方法可以给属性赋值(也可以给私有化属性赋值)
    public void setPrice(double price) {
            if (price < 0){
                System.out.println("价格不合法");
            }else {
                this.price = price;
            }
        }
    
    
    

对属性的封装

1、this指的是当前类

2、在测试类中通过set方法给私有属性赋值,通过get方法获取值

package day08.test;

public class Book {
    //创建成员变量    private可以提高数据的安全性
    private String name;
    private double price;

    public Book(){}
    public Book(String name, double price){
        //这里虽然可以给对象赋值,但是对价格没有要求,
        // 还是会发生价格为负的情况
        this.name = name;
        setPrice(price);
    }


    //定义方法
    public void show(){
        System.out.println(name + "\t" + price);
    }

    //getter setter方法,快捷键-->generate
    //获取指定的私有属性值
    public String getName() {
        return name;
    }

    //内部设置私有化属性
    public void setName(String name) {
        //this指代的是当前的类将属性name值定为当前传入参数
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    //不能加太多逻辑代码,加太多就只针对某一个场景
    public void setPrice(double price) {
        if (price < 0){
            System.out.println("价格不合法");
        }else {
            this.price = price;
        }
    }
}

被封装的属性可以在本类中调用,但是在其它类中使用时需要用有参构造器或者使用get/set方法赋值取值

2、继承

面向对象编程中,继承指的是一个类(派生类或子类)可以继承另一个类(父类或基类)的属性和方法,同时可以在类中添加自己特有的属性和方法,或者是重写父类的方法。

  • 继承的结构

    public class 子类名 extends 父类名{
    	//成员变量、实例变量、属性
        //方法
    }
    
    //子类会继承父类的非私有属性和方法
    //父类的private属性无法在子类中直接访问,需通过父类的公共方法(如getName())
    //super代表的是父类,一般用于子类代码中,通过super调用父类属性和方法
    
  • 父类:在继承关系里,被继承的类叫做父类,也叫基类或超类。
    子类:在继承关系里,继承别的类的类叫子类,也叫派生类

  • 子类可以从父类中继承属性和方法,无法继承构造方法,但子类可以调用父类中的构造方法让子类调用属性并赋值

  • 如果子类有的属性和父类属性同名,那么在创建子类对象时初始化属性值和定义1值的时候会就近原则,调用的是子类里面的属性名

  • 在子类构造器中,先执行的是父类构造器,等于说在创建父类对象的时候会调用父类构造器,随后创建子类对象,产生一一对应的关系

父类

//父类
public class Father {
    int a = 10;
    int b = 20;

    public void show(){
        System.out.println("父类show方法");
    }
}

子类

//继承Father
public class Son extends Father{
    //b与父类属性重名,调用的是子类的属性和方法
    int b = 100;
    int c = 200;

    public Son(){
        //在子类构造器中,先执行的是父类构造器,等于说在创建父类对象的时候会调用父类构造器,先创建父类对象,随后创建子类对象,产生一一对应的关系
        super();
        System.out.println("子类构造器");
    }

    public Son(String name){
        //在子类构造器中,先执行的是父类构造器,等于说在创建父类对象的时候会
        //调用父类构造器,先创建父类对象,随后创建子类对象,产生一一对应的关系
        //也可以调用父类的有参构造器,然后将里面的属性初始化
        super();
        System.out.println("子类构造器");
    }


    @Override
    public void show(){
        System.out.println("子类show方法!");
    }

    public void sh(){
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(super.b);
        show();
        super.show();   //在子类中调用父类的方法,可以通过super代替父类对象调用属性和方法
    }

    public static void main(String[] args) {
        //创建Son对象,就近原则
        Son s1 = new Son();
        s1.sh();
    }
}
  • 创建子类对象时,会先调用父类的构造器(从Object开始逐层向下),再调用子类构造器。

继承的特点:

	1. 单继承:一个类只能继承一个父类(子类),一个父类可以被多个子类继承;
	2. 一个类可以继承另一个类的所有**非私有属性和非私有方法;**
	3. 子类继承了父类的属性和方法,也可以拓展自己的属性和方法;
	4. 子类继承父类的方法,如果方法不能满足子类的业务需求,那么子类可以重写父类的方法(重写父类方法只改变方法体,不改变参数列表和返回值类型);
	5. 父类构造器不能被子类继承(子类可以调用父类构造器初始化父类的属性,并在子类中使用)。
   - 子类可以通过**super()**调用父类构造器来初始化从父类继承的属性,这些属性初始化后可在子类中直接使用。

重写:在继承关系中,子类中有方法名和参数列表都与父类方法相同,称为子类重写了父类方法

方法重写与方法重载的区别:

  • 先介绍重写和重载的概念

    • 方法重载:是在同一个类中两个方法的方法名相同,但是参数列表不同(形参数量,形参类型,形参顺序),与方法返回值和修饰符没有关系,那么就说这两个方法发生了重载
    • 方法重写:在继承关系中,父类的方法不能满足子类的需求,子类对方法体的内容修改(不能改参数列表和返回值)重新实现父类的方法,就是方法重写,需要加上@Override注解在方法上,说明这个方法是对父类方法进行重写

    概念好像已经区分开了,那就背吧哈哈哈。这两个概念感觉联系不起来,就是名字有点像而已。

虽然说继承关系中父类的构造器不能被继承,但是如果父类的属性是私有的不能被继承,那么可以在继承的子类中通过super()调用父类的构造器,初始化属性,而且super()必须写在第一位,可以定义在子类对象中。

  • super和this有什么区别呢

    • super关键字

      • 在子类构造器中可以通过super(参数列表)来对父类中私有化的数据进行初始化,可以在子类对象中使用这些数据。必须放在构造器的第一行;

      • 同时也可以在子类的方法中使用super.方法名 在子类中调用父类的方法,因为有方法重写的话创建的子类对象会直接调用子类中的方法。

    • this关键字(一般写在方法中)

      • 主要代表的是当前对象,在有参构造器中可以使用 this() 调用无参构造器,在无参构造器中也可以使用 this(参数列表) 调用有参构造器,用于初始化对象,都需要放在第一位。
      • this写在方法体中代表这个类,可以通过 this.方法名 来调用这个类中的其他方法。
image-20250726103300197

注意:

  • 如果子类构造方法中没有明确写出调用哪个父类构造方法,会默认调用父类的无参构造方法,即super();必须放在第一行
  • 如果父类不提供无参构造器,提供了一个有参构造器,在子类中直接调用父类的有参构造会编译错误;
  • 继承关系中子类对象想调用方法,如果子类对象中有这个方法会直接调用,如果没有的话会找到上级父类中找这个方法,如果找到就调用那个类中的方法,如果没有找到的话继续找上级父类,直到根类Object中,最终没有找到会报错;(看似是那个子类拥有了那个方法,实则那个方法还是父类的方法,一层一层找的关系)
  • 在继承中,子类对象的堆区内存里,既会有子类自身实例变量,也会有父类继承来的实例变量。如果子类和父类有同名的变量,堆内存会有2个变量,子类对象优先调用自身属性。

父类

//父类
public class Father {
    private int a = 10;
    int b = 20;

    public void show(){
        System.out.println("父类show方法");
    }
}

子类

//继承Father
public class Son extends Father{
    //b与父类属性重名,调用的是子类的属性和方法
    int b = 100;
    int c = 200;

    public Son(){
        //在子类构造器中,先执行的是父类构造器,等于说在创建父类对象的时候会调用父类构造器,先创建父类对象,随后创建子类对象,产生一一对应的关系
        super();
        System.out.println("子类构造器");
    }

    public Son(String name){
        //在子类构造器中,先执行的是父类构造器,等于说在创建父类对象的时候会
        //调用父类构造器,先创建父类对象,随后创建子类对象,产生一一对应的关系
        super();
        System.out.println("子类构造器");
    }


    @Override
    public void show(){
        System.out.println("子类show方法!");
        super.show();
    }

    public void sh(){
        System.out.println();
        System.out.println(b);
        System.out.println(c);
        System.out.println(super.b);
        show();
        super.show();   //在子类中调用父类的方法,可以通过super代替父类对象调用属性和方法
    }

    public static void main(String[] args) {
        //创建Son对象,就近原则
        Son s1 = new Son();
        s1.sh();
       
    }
}
继承时堆内存和栈内存分析:
  • Dog dog = new Dog();栈内存会开辟小空间(引用类型暂存null) ==> 堆内存中开辟空间存储Dog类在方法区加载的的属性和方法 ==> 栈内存中的引用指向堆内存中的对象
  • 调用父类构造器时不会创建两个对象,只是初始化一些属性而已,只是将属性方法加载。

3、多态

多态是面向对象编程的核心特性之一,它允许不同类的对象通过统一的接口进行调用(同一事物的多种形态),从而实现 “同一接口,多种实现” 的效果。多态通过继承方法重写父类引用指向子类对象来实现。

  • 多态的前提

    • 有子类继承父类(有继承关系)(接口与实现类的实现关系也可以看成是继承关系)
    • 有方法重写
    • 父类的引用指向子类对象(实际上指向的是子类对象在堆内存中的首地址)
  • 多态举例子

父类Animal

public class Animal {
	public void eat() {
		System.out.println("吃东西");
	}
}

子类Cat

public class Cat extends Animal{
    @Override
    public void eat() {//子类重写父类方法
        System.out.println("猫吃鱼");
}
    public void catchMouse() {
    	System.out.println("猫抓老鼠");
    }
}

子类Dog

public class Dog extends Animal{
    public void eat(){
        System.out.println("吃骨头");
    }

    public void lookHouse(){
        System.out.println("看见护院");
    }
}

测试类Test

    public static void main(String[] args) {
        //多态对同一个对象的多种形态的是实现
        //猫是猫
        Cat cat = new Cat();
        cat.name = "比鲁斯";
        cat.eat();
        System.out.println(cat.name);

        //动物是动物
        Animal animal = new Animal();

        //类似于类型转换中小类型转向大类型(自动类型转化),多态的一种形式
        //父类声明的变量值可以是父类实例的对象也可以是子类实例化的对象
        //编译看左,执行看右
        Animal animal2 = cat;//子类把首地址赋值给了父对象创建的变量
        //其实就是父类引用了子类对象

        //当子对象向上转化后,则对象中的扩展属性和方法会被隐藏,只能调用子类中重写的方法
        animal2.eat();

        //大类型转换到小类型需要用强转
        //如果从大类型转化到小类型,必须从小类型转换到大类型,转换后的扩展属性和方法就可以使用
        Cat cat2 = (Cat) animal2;
        cat2.eat();
        System.out.println(cat2.name);
        cat2.catchMouse();

        //ClassCastException	类型转换异常
        Cat cat3 = (Cat) new Animal();
        //不能直接将小数据类型转换成大数据类型


    }

报的异常数据类型转换异常(运行时异常RuntimeException)

image-20250726112817956

可以这么理解

image-20250726110813780

//通过多态父类的引用指向子类的对象时(实则可以理解成数据类型转换小类型转换成大类型数据)
    Animal animal = new Cat();  //同时调用方法  animal.方法

	animal.eat();
//eat()方法在父类和子类中都有,所以编译的时候不会报错,但是实现的是子类重写的方法

	animal.catchMouse();	
//catchMouse()方法时子类中独有的方法,在编译时因为父类里面没有这个方法,所以会报错,如果想用的话需要将animal的数据类型转换成Cat类型的,利用强制类型转换

	(Cat)animal.catchMouse();
//换成Cat后可以调用catchMouse()方法,编译时不会报错

所以说编译看左边数据类型里面有没有那个方法,如果没有编译报错,如果有那个方法,执行继承的方法。如果没有那个方法编译会报错。
    
    运行看右边是如果子类重写了那个方法,用的是哪个对象,就会调用哪个对象里面的那个方法


  • 参数的多态:可以将父类的引用设置为方法的参数,将子类对象传进去,直接实现多态

        //instanceof判断某个对象属于哪个类的实例,他的判断结果是boolean值,
        //如果某个对象属于一个类,那么对父类判断结果也是true
        public void target(Animal animal){
            if (animal instanceof Dog){
                Dog dog = (Dog)animal;
                dog.lookHouse();
            }else {
                Cat cat = (Cat)animal;
                cat.catchMouse();
            }
        }
    
  • 以数组的体现形式实现多态

       //数组的体现形式
        Animal animal[] = new Animal[]{new Cat(),  new Cat(), new Dog(), new Dog()};
        for (int i = 0; i < animal.length; i++) {
            if (animal[i] instanceof Dog){
                Dog d = (Dog)animal[i];
                d.lookHouse();
            }else if(animal[i] instanceof Cat){
                Cat c = (Cat)animal[i];
                c.catchMouse();
            }
        }
  • 多态的好处是:
    • 增加了代码的可拓展性,可以更容易设计出通用的代码
  • 多态缺点是:
    • 隐藏了子类独有的方法。需要强制类型转换才能调用独有的方法。

7、抽象类

  • 概念

    抽象类是一个特殊的类,被abstrat修饰符修饰,不能被实例化成对象,目的就只是被一个类继承,结构相当于是 抽象方法 + 普通类 = 抽象类(有构造器,给子类对象初始化属性,不能被实例化对象)

    abstract

    • 修饰方法就是一个抽象方法,没有方法体;
    • 修饰类就是一个抽象类(不能被实例化成对象)

    结构如下:

    public abstract class 类名(){
        //可以定义属性
        int a;
        private int b;
        //抽象方法,用abstract修饰,抽象方法没有方法体
    	public abstract void method();
        //普通方法
        public void method1(){
            //方法体
        }
    }
    

    注意,抽象方法必须写在抽象类中,抽象类中可以存放抽象方法和普通方法,还可以定义属性

  • 特点:

    • 抽象类被继承的时候,里面的抽象方法必须被继承的子类重写;
    • 如果一个类是抽象类,那他里面的方法不一定是抽象方法,相反的是如果一个类中有抽象方法,那这个类一定是抽象类
    • 抽象类不能被实例化,但是可以被继承;
  • 案例

定义一个抽象类

public abstract class Shape {
    //抽象类中可以写属性
    String name;

    //定义方法 1.求周长  2.求面积	
    public abstract void perimeter();	
    public abstract void area();
    
    public void testMethod(){
        System.out.println("非抽象方法");
    }

    public static void main(String[] args) {
        //不能被实例化
//        Shape s = new Shape();

    }
}

继承抽象类

/*
1.继承抽象类后,必须把抽象类里面的抽象方法重写
*/
public class Rect extends Shape{
    //定义属性
    int a;
    int b;

    @Override
    public void perimeter() {
        System.out.println((a+b)*2);
    }

    @Override
    public void area() {
        System.out.println(a*b);
    }


    //测试抽象类的实现类
    public static void main(String[] args) {
        Rect r = new Rect();
        r.a = 10;
        r.b = 20;
        r.name = "矩形";
        r.perimeter();
        r.area();
        System.out.println(r.name);
    }
}

抽象类的最佳使用场景就是多态,并不是为了创建对象

8、接口

接口是一种抽象类型,我的理解就是比抽象类还要抽象的类,只不过是用interface创建的这个就是接口;

image-20250726141137591
  • 结构

    接口不是类,所以接口里面没有构造器,只有抽象方法和静态方法和默认方法,属性有静态常量

    jdk1.8以后增加了静态方法和默认方法(也可以被重写),目的是增强接口的功能,同时保持对现有代码的兼容性

    //定义一个接口,接口用interface建立
    public interface Inter1 {
        //接口里面的变量都是静态常量   默认有final static修饰
        public final static int a = 1; //常量,不能改变
        
        
        //接口里面的方法默认都是有 public abstract 修饰的	抽象方法没有方法体。
        (public abstract) void method();
        
        //在jdk1.8之后接口里添加了静态方法(static修饰)和默认方法(default修饰)
        public default void methodDefault(){	//default修饰符只用于接口中的方法
            //默认方法有方法体
            System.out.println("我是接口中的默认方法,默认方法必须要加default关键字");
        }
        
        //静态方法是static修饰的方法,调用的时候用类名调用即可,可以用对象调,但是还是尽量用这个类名调用
        //子类中可以重写静态方法
        public static void methodStatic(){
            //方法体
            System.out.println("我是接口中的静态方法,需要有static修饰符修饰");
        }
       
    
    }
    

    接口里面在jdk1.8之前只能写抽象方法,在jdk1.8以后可以写静态方法和默认方法

  • 接口的实现(接口也可以继承接口extends)

    接口和抽象类一样不能创建对象,所以接口需要被实现才有意义,接口的实现是通过一个类在后面写 implements 接口名实现的,结构如下

    public class 类名 implements 接口1,接口2.....{	//不同于继承的是实现类可以同时实现多个接口
    	//接口的实现类需要重写接口中的所有抽象方法
        方法体
    }
    
    • 接口实现类实现接口同时继承父类
    public class 类名 extends 父类名 implements 接口1,接口2.....{
        方法体
    }
    

    弥补了继承的单继承,可以多实现。哈哈哈

  • 接口也可以继承接口

    结构如下:

    public interface Inter implements 接口1,接口2,接口3...{}
    

接口和抽象类的区别:

1.相同点:

  • 都不可以实例化对象
  • 都可以使用多态
  • 都包含了抽象方法(子类或实现类都需要将抽象方法重写)

2.不同点:

  • 抽象类是由abstract修饰的类,是由抽象方法和普通类(正常创建的类)组成的,接口里面在jdk1.8之前只能写抽象方法和静态常量,1.8之后增加了静态方法和默认方法。
  • 抽象类中可以包含构造方法,接口里面没有构造方法;
  • 类的继承只能是单继承,接口可以同时实现好几个接口

day08、异常

  • 概念

    • 在 Java 里,异常指的是程序运行期间出现的不正常状况,它会干扰程序的正常执行流程。Java 采用面向对象的方式对异常进行处理,把各种异常情况封装成不同的异常类。异常处理机制的存在,能够让程序在遭遇异常时,有计划地进行响应,增强程序的健壮性。
  • 异常的影响

    • 出现异常会终止整个JVM虚拟机;
    • 程序会直接终止运行
    • 异常后面的业务代码不会执行

1、Throwable

throwable是异常的大类,可以分成两个子类

  • error(系统级别的错误)
    • 在java程序的处理范畴之外
    • 主要有栈溢出(递归函数不断调用自身,并且在栈内存中开辟空间), 硬盘空间不足, 虚拟机损坏等。
  • exception(分为两大类)
    • RunTimeException(运行时异常)
    • 编译时异常

error

error通常就是系统级别的错误,目前只见到了栈溢出的情况

//定义一个方法不断递归调用自身,同时不断在栈内存中开辟空间
public class TestStack {
    public static void main(String[] args) {
        method();
    }
//Exception in thread "main" java.lang.StackOverflowError输出描述的异常
    public static void method(){
        int a = 1;
        method();
    }
}

运行截图如下:

image-20250728212548604

2、Exception

Exception通常是程序中代码引起的,主要分为两种

  • 编译时异常

    • java程序的编译时期就是 对java源代码进行编译 javac 变成字节码文件这个过程是编译;
    • 编译时异常在出现的时候就必须解决,否则java程序不能被编译成字节码文件;
    • 在idea中书写java代码爆红的时候就是编译错误,但是在记事本中 在javac环节才会报错的是编译;
    • 除了运行时异常其他的异常都是编译时异常。

    image-20250728213854421

  • RunTimeException运行时异常(运行时异常会直接终止代码的运行)

    • 编译不会出错,但是运行时会出错。

    • 下图是运行时异常的几种

      • 算数异常(拿除数为0举例,运行时会报异常)
      • 空指针异常(在一个引用类型的值为null时,对他进行操作会报出空指针异常)
      • 数组下标越界(访问的下标超出了数组的范围会爆出下标越界)
      • 字符串下标越界(和数组下标越界是同一个道理)
      • 类型转换异常(多态中,子类引用直接指向父类对象会报出类型转换异常)
    image-20250728213022123

3、异常处理方式(重中之重)

异常处理的方式有两种

  • try...catch处理异常, try代码块负责将怀疑有异常的代码放在try里面,然后catch捕获异常并处理;(简而言之就是try代码块里会出现的错误会根据错误异常类型,被对应的catch异常对象捕获,并执行对应的代码块)

            //如果没有用try...catch捕获并处理异常的话,那么会交给JVM处理JVM处理的方式就是运行到这里会终止代码运行
            try {
              //将可能会出错的代码写在try中,捕获错误,
                //遇到一个异常会直接从上到下跳进一个异常对象里
                int a = 10;
                int b = 0;
    			
                //这里将除数设置为0,捕获到异常后,异常后面的代码不会运行
                System.out.println(a/b);
    
                //这部分代码不会运行
                int[] arr = new int[3];
                System.out.println(arr[3]);
                /*分析代码发生的异常,与下面catch里面的异常类型对比,属于哪个异常
                在哪个代码块里处理就好,e是异常的对象*/
            } catch (ArithmeticException e) {  //算数异常
                //处理异常
                //System.out.println(e.getMessage()); //错误信息打印
                System.out.println(e);  //将错误对象打印出来
            } catch (ArrayIndexOutOfBoundsException e) {   //数组下标越界
                System.out.println(e);
            } catch (NullPointerException e) { //空指针异常
                System.out.println(e);
                
            } catch (RuntimeException e) {	//运行时异常
                System.out.println(e);
            } finally {
                //不管成功还是失败都会执行finally部分
                System.out.println("执行结束");
            }
    
    
    上面的异常都属于是运行时异常,每个catch()代码块里都创建了一个对象,他们都是同级的异常;
    如果有大的异常类需要写在最后一个,否则会直接把小范围的异常类覆盖掉,编译会报错;(上面的RunTimeException就属于是大范围的异常类,放在最后一个,如果放在第一个编译会直接报错)
    
    • 如果有大的异常类需要写在最后一个,否则会直接把小范围的异常类覆盖掉,编译会报错;
    • try...catch捕获到的异常就像switch分支结构一样, 对应到哪一部分就执行哪一部分处理就行
    • try...catch...finally结构里还有一个finally代码块, 它代表的是不管有没有捕获到异常, 这个代码块都会执行一次;

    上述代码运行截图如下: (直接输出异常对象会将异常类型和原因写出来)

    image-20250729123941370

异常处理的结果的几种方式:

  • 直接输出catch中创建的异常对象

    catch (RuntimeException e) {	//运行时异常
                System.out.println(e);
            }
    
    //结果模板  java.lang.ArithmeticException: / by zero
    

    会将异常的类型和错误信息打印出来 ;

  • 打印出错误信息 :

    System.out.println(e.getMessage());	//打印错误信息
    //输出结果模板  / by zero(算数异常除数为0)
    
  • 追踪错误的路径

    e.printStackTrace();	//打印出错误的路径,会在finally执行后打印,自带了打印机制
    
    //输出结果如右图所示  java.lang.ArithmeticException: / by zero
    //	at day10.test.Aaa.main(Aaa.java:12)
    

注意 :

  • 在try...catch...finally结构中, 不管前面catch是否处理异常, finally都会执行 ;

  • 关于try...catch...finally代码块中 return返回值的使用:

    • 如果在try中有return , 并且没有捕获到异常 , 那么会将try里 return 的值存储起来作为方法的返回值 , 但是依旧会执行finally里面的内容 (前提是catch和finally里都没有return,如果有的话会被覆盖(其实是执行finally里面的return终止了方法));
    • 不管try和catch里面有没有return语句,都会执行finally代码块。
       public static int test3(){
            int i = 1;
            try{
                i++;
                System.out.println("try block, i = "+i);
                //try里面的return的值会被记录,如果后续没有return i的值,最后会返回当前返回的值,如果有 return i 会把当前覆盖。
                return i;
            }catch(Exception e){
                i ++;
                System.out.println("catch block i = "+i);
                return i;
            }finally{
                //
                i = 10;
                System.out.println("finally block i = "+i);
                //finally里面的代码块不管如何都会执行,这里执行了return i;会把前面try里面返回的内容覆盖终止,最终返回这里i的值
                return i;
            }
        }
    

课件里面写的

如果在带有方法的返回值的方法中定义try.. catch语句时,执行顺序:
1、不管有没有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、如果在finally之前遇到了return语句(此时并不会返回结果,而是先把要返回的值保存起来,不管
finally中的代码怎么样,返回的值都return时刻的值);
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值
  • 通过throws将异常往上抛出去

如果一直抛,都不处理,最后会交给JVM处理,会终止代码运行;

其实就是为了快速解决不想处理异常,也能通过编译期的处理方式。

public void 方法名() throws 异常类名,异常类名,异常类名..... {
}

throw关键字可以抛出任意类型的异常, 无论是运行期异常还是编译期异常

day09、API文档

  • jdk中的类库:API, 里面包含了基本的操作类

  • 第三方类库:也叫API、先导入到项目(引入的 jar 包)---------> 导包或者是导类-------------->使用

API(Application Programming Interface):应用程序编程接口。它是厂商或者第三方提供好的一系列类库,这些类库能帮助你开发出你想要的项目;Java API指的是JDK中提供的各种功能的Java类库。这些类库已经提供了完整的实现,我们不需要关心它如何实现的,只需要学会如何使用这些类即可。

说那么多其实就是说明书,API文档里会详细的介绍类的功能,类如何使用,方法需要什么参数,会返回什么结果等等(对api类库中的类和方法的解释说明, 快速掌握类和方法的使用)

image-20250729202428081

1、Object类

所有类的父类(基类/超类),所有的类都直接或者间接的继承了Object类

构造函数 , 方法名 , 参数列表 , 返回值类型

2、Random类

生成随机数的类

  • 构造方法:

    • new Random()
  • 方法

    • nextInt();

    • nextFloat();

    • next();

    • nextDouble();

    • nextBoolean();

    • nextLine();

3、String类

String在内存中的存储模式,在方法区有一个常量池,String底层是final修饰的一个字符数组,后来用的是final修饰的byte数组,数组的长度不可改变,常量不可改变,所以说字符串的内容不可改变,在常量池中存储的是字符串里的内容,但是不同的创建方式存储的方式也不同

  • String str = "aaa";

    • 在栈内存中声明一个变量,变量的值是在常量池中的字符串的地址;
  • String str = new String();

    • 在栈内存中开辟一个空间,但是没有初始化对象,值是空的,类内部自动处理。
  • String str = new String("abc");

    • 在栈内存中开辟一个空间,指向在堆内存中的对象首地址,堆内存中存储的是在常量池中字符串的地址
  • new String(char[] chars);

  • new String(byte[] bytes);

    • 堆内存中存储的数组首地址给到堆内存中String对象,然后转换成字符串对象(只在堆和栈中有体现)

image-20250729205958787

image-20250729210010300

image-20250729210017757

equals比较的是引用类型的值是否相同;

==比较的是基本类型的值是否相同;

案例代码:

  1. 验证码
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Random r = new Random();
        String str = "qwertyuiopasdfghjklzxcvbnm1234567890QWERTYUIOPASDFGHJKLZXCVBNM";
        String ma = "";
        for (int i = 0; i < 4; i++) {
            int index = r.nextInt(str.length());
            ma += str.charAt(index);
        }
        System.out.println("请输入验证码" + ma);
        String tmp = sc.nextLine();
        if (tmp.equalsIgnoreCase(ma)){  //不区分大小写equalsIgnoreCase
            System.out.println("验证码正确");
        }else{
            System.out.println("验证码错误");
        }
    }

3、Date时间类

  • 构造方法:

    • new Date(); //返回当前系统时间
    • new Date(数值); 数值代表的是毫秒,时间对象存储的是距离基准时间的毫秒数
  • 方法:

    • getTime(); //获取的是当前时间对象到基准时间的毫秒数
    • getYear()+1900
    • setYear(year - 1900)

    .......

4、SimpleDateFormat类

对事件对象进行一些处理

  • 构造方法:

    • new SimpleDateFormat(pattern格式)
  • 方法:(都是SimpleDateFormat对象调用方法)

    • parse(); //将字符串时间转化成date对象里面的数据();
    • format(); //将date对象时间转化成pattern格式。

记住SimpleDateFormat的时间格式:

image-20250731220956151 image-20250731221022647

SimpleDateFormat类的基本使用:(反复强调)

  • parse负责将字符串按照格式转成一个Date对象;
  • format负责的是将一个Date对象按照定义的格式输出。
public static void main(String[] args) throws ParseException {  // 内置的有解决方法,抛出去就行了
    //创建一个对象                                    // pattern 格式,连接符号怎么写随自己,只要不和格式字符相同就行
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E");

    //日期对象,获取的是当前系统的时间
    Date date = new Date();
    System.out.println(date);

    //format()方法,将时间对象格式化,返回类型是StringBuffer类型(字符串类型)
    String dateTime = sdf.format(date);
    System.out.println(dateTime);

    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日");
    //返回的是一个Date类型      如果格式不对的话会出现异常==>ParseException---解析异常
    Date date2 = sdf2.parse("2025年07月29日");
    System.out.println(sdf2.format(date2));
    date2.setYear(2000-1900);   //可以在这里控制年
    System.out.println(date2);

}

5、包装类

包装类是对java中的8种基本数据类型进行类的封装

  • 主要是给他们提供了一些基本的方法可以调用(基本数据类型不能调用方法);
  • 可以实现包装类与字符串和基本类型之间的相互转换。

包装类的一一对应关系如下:

image-20250731221441863

  • Number类是Byte、Short、Integer、Long、Float、Double的父类。它是一个抽象类,内部定义了一些抽象方法,这些抽象方法由子类负责实现;
  • Character和Boolean的父类不是Number类而是Object类。

三种数据类型之间的转换:

image-20250731221605034

    public static void main(String[] args) {
        //jdk17不能用构造器创建Integer对象了
/*
        Integer i = new Integer("1");
        Integer i2 = new Integer(1);
*/

        Integer a = 1;  //自动装箱  从基本类型转化为对应的装饰器类型的对象
        //  i是一个Integer对象
        Integer i = Integer.valueOf(1);
        int b = a;  //自动拆箱  从装饰器类型向基本类型转换的过程

        double v = a.doubleValue();
        System.out.println(v);

        String str= a.toString();   //类型转化
        String str2 = a + "";   //发生了隐式转化,a先转化成了字符串,再拼接空字符串
    }
在开发中,包装类和基本数据类型的相互转换非常常见,因此在JDK 5.0中引入了自动装箱和自动拆箱的
功能。包装类和基本数据类型的转换,由系统自动进行,我们无需编写任何转换代码。
自动装箱:基本数据类型 转换到 包装类由系统自动进行。
自动拆箱:包装类 转换到 基本数据类型由系统自动进行。
在JDK1.5以后,java中提供了 自动装箱 和 自动拆箱 的概念
自动装箱就是基本数据类型可以自动转为包装类
自动拆箱就是包装类对象可以自动转为基本数据类型

day10、注解

jdk1.5 新增使用方式,可以在代码中使用,可以理解成标记,写在哪里(包,类,属性,方法,局部变量)都是以@开头

  1. 文档注解,生成api以及文档或者代码时会有提示作用;
  2. 编译时起到抑制警告相关的作用;
  3. 在框架中起到代码功能作用;

抑制警告的注解@SuppressWarnings

  • 用于告诉编译器忽略特定类型的警告

  • 可以指定具体的警告类型,例如:

    // 抑制未检查类型转换的警告
    @SuppressWarnings("unchecked")
    List<String> list = (List<String>) someObject;
    
    // 抑制所有警告
    @SuppressWarnings("all")
    public void someMethod() { ... }
    

标记过时元素的注解@Deprecated

  • 用于标记类、方法或字段已过时,不推荐使用

  • 编译器会对使用被此注解标记的元素发出警告

  • 可以添加说明文档说明替代方案:

    /**
     * 此方法已过时
     * @deprecated 请使用 newMethod() 替代
     */
    @Deprecated
    public void oldMethod() { ... }
    
    public void newMethod() { ... }
    

JDK5.0提供了4个标准的meta-annotation类型,分别是:

  • @Retention元注解
  • @Target元注解
  • @Documented元注解
  • @Inherited元注解

@Retention代表的是注解的生命周期

@Target代表的是注解的作用范围,是ElementType类型的,该类型是一个枚举类型,一共提供了10个值选择,我们最常用的几个:FIELD、TYPE、PARAMETER、METHOD意味着注解用在哪个位置(方法或者是变量等)

ElementType.FIELD:用于字段、枚举的常量
ElementType.TYPE:用于接口、类、枚举、注解
ElementType.PARAMETER:用于方法参数
ElementType.METHOD:用于方法

day11、集合框架

集合中只能存储引用数据类型,而且长度还是可变化的,不同的集合框架底层不同。

1.集合可以理解成是一个容器的存在 , 里面存储着元素, 可以直接通过sout将集合输出(集合内部重写了toString()方法) , 但是如果元素是一个对象的话 , 那就需要在对象对应的实体类中重写toString方法这样才能直接将集合输出出来,否则输出的元素是一些地址。
2.在HashSet中,如果元素都是一些对象类型,那么HashSet原有的去重机制远远不够看,因为对象对应的值是一些地址,就算对象属性相同,也不会被去除,所以需要重写hashCode()方法和equals()方法。
    - hashCode()方法判断他们的属性的哈希值是否相同,如果相同(哈希冲突),那就比较equals()方法,如果可以通过equals方法,那就将元素加入到集合中。
3.在集合框架中拿 List<String> list = new ArrayList<>();举例子,中括号代表的是泛型, 里面的值E代表的是泛型变量,只能存储引用类型(基本类型用包装类),里面规定了集合中存储的数据类型。

集合框架图

image-20250802101155911

Iterator是一个接口,集合框架的实现类可以创建一个迭代器对象,对集合列表进行遍历。(双列集合没有用过)

Collection(接口)

  • List(接口;有序,元素可重复)
    • ArrayList(底层是一个可变数组的方式,查询快,增删效率低)

      • new ArrayList() 创建一个默认10容量的ArrayList集合
      • new ArrayList(自定义容量),创建一个自定义容量的集合
    • LinkedList(底层是双向链表,查询效率低,增删快)

      • 存储时每个元素会有三个空间,每个元素空间会记录上一个和下一个元素空间的地址(双链表结构)LinkedList底层是靠双向链表来存放元素的。链表中的元素在逻辑上连续,但物理上不连续,下面是插入时的理解图

        image-20250802105224374
      • 构造方法同上

      • 方法同上

    • Vector(底层是数组实现,线程安全)

List集合框架中常用的方法:(ArrayList和LinkedList常用的方法)

List<E> list = new ArrayList<>();
size();			//计算集合中元素的数量;
//增
add(); 			//向集合中添加元素;
addAll();		//将一个集合中的元素都添加到另一个集合中
//删
remove();		//删除集合中的某个元素(在List中可以根据索引删)
removeAll();	//删除集合中与另一个集合框架的共有的元素(删除共同的,取差集)
//改
set();			//可以根据索引的值对集合某个元素的值改变
//查
contains();		//检查集合是否包含这个元素
containsAll();	//检查集合是否包含了其他集合中的全部元素
retain();		//检查与另一个集合共同的元素(直接对集合处理,剩下共有的元素)
indexOf();		//获取的是集合中元素第一次出现时的索引值
lastIndexOf();	//获取的是最后一次出现时的索引值
-------------------------------------------------
isEmpty();		//检查集合是否为空
toArray();		//将集合框架转换成一个数组对象
clear();		//可以清空集合
subList();		//截取列表中的元素并返回(与字符串相同)
-------------------------------------------------
//循环遍历的方式
方式1:
    Iterator();		//获取迭代器对象,可以根据迭代器对象对集合进行遍历
            it.HasNext();	//迭代器方法,判断这个集合位置是否为空
            it.next();		//迭代器方法,获取这个集合框架中本次遍历到的元素
方式2:
	也可以通过for循环获取索引根据get(i)的值遍历集合
方式3:
	可以通过加强for循环对集合中的元素进行遍历

ArrayList代码演示

        //创建一个ArrayList集合对象,无参创建默认包含10个空间
        List list = new ArrayList();    //向上类型转换,不能调用独有的方法
        //增
        list.add(1);
        list.add(15.6);
        list.add("稀释");
        list.add("豆腐");
        list.add(0, "喜喜");
        list.add(20);
        //size()方法获取的是元素的个数
        //System.out.println(list.size());
        System.out.println(list);   //可以直接输出集合,默认有一个toString
        System.out.println(list.isEmpty());
        //删
        //System.out.println(list.remove("稀释"));
        System.out.println(list);
        list.remove(0); //删除索引处的元素
        Integer a = 20; //自动装箱
        //System.out.println(list.remove(a)); //直接写数字会以为是下标
        // 改成包装类删除
        list.remove(Integer.valueOf(20));
        System.out.println(list);

        System.out.println("---------------------------");
        //改  属于是List接口里面的方法,List里面才有下标, Set没有
        list.set(3, "杨玉环");  //改集合中的内容
        System.out.println(list);

        //查
        System.out.println(list.contains(1));
        System.out.println(list.contains("杨玉环"));
        System.out.println(list.get(1));    //返回的是索引处的值
        System.out.println(list.indexOf("杨玉环"));    //找集合中第一个这个元素的下标
        List list2 = list.subList(2, 3);    //左闭右开,截取数组内容返回一个数组
        System.out.println(list2);

        //toArray()转换成数组
        Object[] array = list.toArray();    //用Object接收转换过来的数组
        System.out.println(Arrays.toString(array));


        System.out.println("---------------------------");
        //创建一个100个空间的ArrayList集合(尽量不要扩展空间,扩展空间浪费性能)
        List list1 = new ArrayList(100);
        //System.out.println(list1.size());   //输出为0,没有元素
        //System.out.println(list1.isEmpty());

        list1.addAll(list); //直接传进来一整个集合
        System.out.println(list1);
        list1.add("亚瑟王");
        System.out.println(list1);
        //测试是否包含这个集合
        System.out.println("是否包含这个集合" + list1.containsAll(list));
        list1.removeAll(list);  //移除里面包含的集合
        System.out.println(list1);
        list1.clear();  //清空集合里面的所有内容
        System.out.println(list1);

LinkedList代码演示

        //LinkedList集合框架, 双向链表, 保存的数据在内存空间中是不连续的
        //删除和增加元素的效率更高一点,双向地址链接,下一个地址保存着上一个数据的地址
        //每个数据可能被分成了三个部分,第一部分连接上一个元素的地址,第二部分保存该部分数据,第三部分保存下一个数据的地址
        //LinkedList是在内存中不连续的双向链表结构,增删效率高(只是元素保存其他元素的地址即可,改变元素之间的地址指向)
        //查找效率低(需要知道第一个元素的地址,指向下一个元素,再进行比较,如果不是的话继续下一个直到找到为止)
        //查找时间复杂度O(n)

        List<Object> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        System.out.println(list.size());    //元素个数
        System.out.println(list.isEmpty()); //是否为空
        System.out.println(list);   //打印集合

        list.set(1, 'a');   //修改值
        System.out.println(list);
        list.add(0, 9);
        System.out.println(list);
        list.remove(1); //删除索引1处的值
        System.out.println(list);
        System.out.println(list.contains(1));   //是否包含

        System.out.println("-------------");
        List<String> list1 = new LinkedList<>();
        list1.add("a");
        list1.add("b");
        list1.add("c");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        //迭代器迭代
        Iterator<String> iterator = list1.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        System.out.println("---------------------------");
        //自定义对象的使用
  • Set(接口;无序无下标,元素不能重复)
    • HashSet(底层是一个哈希表实现的)其实就是数组+单向链表+红黑树(可以根据哈希值找到在数组中的位置,可能会出现哈希冲突,导致存储在数组的同一个位置,那么就会在该位置元素下用尾插法单向链表连接根据重写的HashCode方法和equals方法遍历单向链表,如果没有重复值,那就添加到该数组元素连接的位置

      image-20250802102401276
      • new HashSet(); 默认创建一个空间16,加载因子默认0.75(加载因子决定了什么时候扩容,当容量达到 (容量*加载因子数)时会默认扩容)扩容后的新容量是原来的 2 倍
      • new HashSet(设置容量;设置加载因子); 可以设置创建对象时容器的容量和加载因子;
      • new HashSet(Collection c); 将其他集合转换成HashSet类型进行去重操作;

      LinkedHashSet是HashSet的子类,维持了元素写入时的顺序,输出时候可以按照插入时的顺序插入;

      • HashSet常用方法:(同时也是LinkedHashSet集合共有的方法)
      HashSet<E> set1 = new HashSet<>();
      LinkedHashSet<> set2 = new LinkedHashSet<>();
      //增
      add();			//向集合中增加元素
      addAll();		//向集合中添加其他元素
      //删
      remove();		//删除某一个元素
      removeAll();	//删除共有的元素
      //查
      contains();		//判断集合中是否包含了某个元素
      containsAll();	//判断集合是否包含了另一个集合的其他元素
      retain();		//对集合进行处理,剩下两个集合共有的元素
      -------------------------------------------------
      size();			//获取集合的长度
      clear();		//将集合清空
      isEmpty();		//判断集合是否为空
      toArray();		//将集合转换成数组对象
      -------------------------------------------------
      //循环遍历集合
      方式1:
          Iterator();		//通过创建一个迭代器对象对集合遍历
      		hasNext();	//迭代器对象的方法,判断本次遍历是否为空
      		next();		//迭代器对象的方法,获取的是本次遍历到的元素
      方式2:
      	通过加强版for循环对集合进行遍历
      
    • TreeSet(底层是红黑树)TreeSet是内容有序的集合,即它会对放入其中的数据按内容进行排序。TreeSet使用的是二叉树存储元素, 通过中序遍历的方式来遍历(检索)元素 ;

HashSet代码演示

        //创建一个 Set 集合。
        Set<String> set = new HashSet();
        //向集合中添加 5 个不同的元素。
        set.add("邓紫棋");
        set.add("哈哈哈");
        set.add("mzz");
        set.add("hhh");
        set.add("第五人格");
        System.out.println(set);
        System.out.println("-----------");
        //尝试添加一个已存在的元素,观察集合的变化。
        set.add("mzz");
        System.out.println("添加了重复元素后的set集合:"+set);
        System.out.println("-----------");
        //删除集合中的一个元素。
        set.remove("哈哈哈");
        boolean live = set.contains("哈哈哈");
        System.out.println("删除后的元素集合中是否还存在:"+live);
        System.out.println("删除一个已经存在的元素后的集合:"+set);
        System.out.println("-----------");
        //检查某个元素是否存在于集合中
        System.out.println("在集合中是否包含了mzz元素:"+set.contains("mzz"));
        System.out.println("在集合中是否包含了张哥元素:"+set.contains("张哥"));
        System.out.println("-----------");
        //遍历集合
        Iterator<String> it = set.iterator();
        while(it.hasNext()) {
            System.out.println(it.next());
        }
    }

Map

map集合是双列接口,无序,键值对组成,键是唯一的,值随便(反省规定)

  • HashMap(底层是哈希表)

    • new HashMap(); 默认容量16,加载因子0.75
    • new HashMap(int a, float f);自定义容量和加载因子
    • HashMap常用的方法:
    HashMap<E,E> map = new HashMap<>();
    //增
    put();			//添加键和值(只能是泛型规定的数据类型)
    putAll();		//将另一个Map集合添加进来,如果有重复的Key值,Value值会被覆盖
    //删
    remove();		//根据键删除对应的Value值,并将Value值返回
    //改
    replace();		//将key对应的value值修改了
    //查
    get();			//根据key的值查找value的值
    -------------------------------------------------
    keySet();		//将所有的Key值返回到一个Set集合中
    values();		//将所有的value值返回给一个Collection集合中
    containsKey();	//判断是否包含对应的Key值
    containsValue();//判断是否包含对应的value值
    clear();		//清空集合所有的键值对
    //map集合的遍历
    entrySet();		//将键值对全部以Map.Entry<K , V >的形式返回到一个Set集合中。Set的泛型是Map.Entry<K , V >
    		- 然后可以通过对Set集合进行遍历,可以通过getKey和getValue获取对应的Key和Value
                
    
    • LinkedHashMap();HashMap的子类,可以把存的顺序记录。

    HashMap代码

                //泛型遍历,16个空间,0.75的加载因子
            Map<String, String> map = new HashMap<>();
            //插入键值对,键不可以重复,
            map.put("宋祎龙", "鞠婧祎");
            map.put("杨帅坤", "蔡徐坤");
            map.put("穆壮壮", "卢昱晓");
            //key重复的时候,后便会覆盖前面的
            map.put("宋祎龙", "卢昱晓");
    
            //判断是否为空
            System.out.println(map.isEmpty());
            //输出长度,相同的键没有完全加进来
            System.out.println(map.size());
            //输出map集合
            System.out.println(map);
    
            //修改键的值, 键存在是修改的效果,不存在是添加
            map.put("宋祎龙", "西瓜妹妹");
            //删除, 返回值是key对应的value值
            System.out.println(map.remove("宋祎龙"));
            System.out.println(map);
            //获得键
            System.out.println(map.get("穆壮壮"));
            //跟键做对比,存在true,不存在false
            System.out.println(map.containsKey("穆壮壮"));
            //跟value做对比,存在返回true,不存在返回false
            System.out.println(map.containsValue("卢昱晓"));
            //清空集合
            //map.clear();
    
            //修改value值
            System.out.println(map.replace("杨帅坤", "困困"));
            System.out.println(map);
            System.out.println(map.replace("杨帅坤", "困困", "1"));
    
  • Hashtable是Map的实现类,它也是键值对容器,用法和HashMap一样,主要的区别是:

    1. Hashtable在多线程访问时,是安全的。HashMap是不安全的
    2. Hashtable继承于Dictionary,HashMap继承于AbstractMap
    3. Hashtable键和值都不能为null,HashMap的键和值都可以为null,但只能有一个key为null

Collections工具类

Collections是一个类似于Arrays的工具类,Arrays工具类提供了各种操作数组的方法,Collections工具类提供了各种操作集合的方法。

Collections类提供了向集合中添加元素,对集合排序,二分查找元素,列表拷贝,列表填充元素,获取列表最大/最小值,替换列表元素

public static <T> boolean addAll(Collection<? super T> c, T... elements); //将若干个元素添加到Collection中, Collection可以是List也可以是Set
public static <T> void sort(List<T> list,Comparator<? super T> c); //根据比较器的比较规则对list排序
public static <T> int binarySearch(List<? extends Comparable<? super T>> list,Tkey); //使用二分法从list表查找元素, 如果查到了返回下标,否则返回负值. list必须是有序的,否则结
果不对
public static <T extends Object & Comparable<? super T>> T max(Collection<?extends T> coll);//获取列表中的最大值.列表的元素要能自然排序.
public static <T extends Object & Comparable<? super T>> T min(Collection<?extends T> coll);//获取列表中的最小值,列表的元素要能自然排序
public static <T> boolean replaceAll(List<T> list, T oldVal,T newVal);//替换列表中
的元素
public static void reverse(List<?> list);//反转列表
public static void swap(List<?> list,int i,int j);//交换元素
public static void shuffle(List<?> list);//打乱列表
public static <T> void copy(List<? super T> dest,List<? extends T> src);//把src的元素复制给dest, dest的元素个数要大于等于src的元素个数.
public static <T> void fill(List<? super T> list,T obj);//用obj填充列表lis

测试代码

        List<Integer> list = new ArrayList<>();
        //批量将数据存入集合中(Collection类型或子类)
        Collections.addAll(list,1,2,3,4,5,100,3);
        System.out.println(list);
        //排序
        Collections.sort(list);
        System.out.println(list);
        //二分查找(数据多的时候效率高)
        int index = Collections.binarySearch(list,1);
        System.out.println(index);
        //获取最大值
        Integer max = Collections.max(list);
        System.out.println(max);
        //获取最小值
        Integer min = Collections.min(list);
        System.out.println(min);
        //将集合中的某一元素全部替换
        Collections.replaceAll(list,3,91078);
        System.out.println(list);
        //反转集合
        Collections.reverse(list);
        System.out.println(list);
        //打乱集合,每次打乱的结果都是不一样的
        Collections.shuffle(list);
        System.out.println(list);
        //copy复制集合元素,并非地址
        List<Integer> list1 = new ArrayList<>();
        Collections.copy(list, list1);
        System.out.println(list1);
        //copy的只是集合里面的内容,并非对象地址
        System.out.println(list.equals(list1));
    }
posted @ 2025-07-22 21:57  柠檬糖沉没  阅读(28)  评论(0)    收藏  举报