JAVA基础
入门篇
注释
单行注释// 快捷键为ctrl+/
多行注释/* */ 快捷键为ctrl+shift+/
注释的颜色字体可在setting中修改
不要注释嵌套
关键字
特点:字母全部小写、单词高亮显示
public class+类名{
类的范围
}
- C 语言视角:C 语言是面向过程的,只有函数;而 Java 是面向对象的,一切皆类。
- 强制规则:
- 一个
.java文件里只能有一个public类。 - 文件名必须和这个
public类名一模一样(大小写敏感)。 - 这就像 C 语言里,头文件通常要和里面的主要结构体或函数对应一样,但 Java 管得更严
- 一个
package com.zlj.test;
// package: 表示当前的类定义在哪个包下
// 这段代码不需要我们自己写,是IDEA自动生成的
//表示定义一个类
//class:表示定义一个类,类是java项目中最基本的组成单元
//HelloWorld:类名,类名必须与文件名一致
public class HelloWorld {
static void main() {
System.out.println("Hello World");//输出语句
}
}
主方法
主方法 (public static void main(String[] args))
public static void main()
快捷缩写psvm
这是你最需要注意的地方,也是 Java 最死板的地方。
在 Java 中,public 的意思是“公开的”。 如果一个类被声明为 public,就意味着它是给外界看的,是给其他程序调用的。
public:公开的。因为 JVM(Java 虚拟机)是从外面调用这个方法的,所以它必须公开。static:静态的。这和 C 语言里的static有点像,意味着它属于类本身,不需要new一个对象就能运行。JVM 启动时还没创建任何对象,所以入口必须是static的。void:没有返回值。main:方法名,JVM 只认准这个名字。String[] args。这是命令行参数。- 在 C 语言里,
main函数通常写成int main(int argc, char *argv[])。 - 在 Java 里,它把参数封装成了一个字符串数组
String[] args。哪怕你不用参数,这个格式也必须写上,否则 JVM 会认为“找不到入口”,直接报错。
}- 在 C 语言里,

语法篇
字面量

练习 输出恐龙的信息:年龄 体重 性别

变量
变量是存储数据的小空间,而不是数据
定义格式如下

变量定义及数据类型与C语言一样的
练习 输出余额

变量的注意事项
- 只能存一个值
- 变量名不允许重复定义
- 变量在使用之前一定要进行赋值(变量a没有存数据时不能输出a,无意义)
- 一条语句可以定义多个变量,也可以连续赋值(a=b=c=d=10)
计算机存储规则
在计算机中,任意数据都是以二进制的形式来存储的
在计算机中,不同类型的数据有不同的存储单元
字节是最小的存储单元,1字节=8bit
在Java中,int类型占4个字节/32个bit位
数据类型
| 整数 | 取值范围 | 内存(字节) |
|---|---|---|
| byte | -128~127 | 1 |
| short | -32768~32767 | 2 |
| int | -2147483648~2147483647(10位数) | 4 |
| long | 19位数 | 8 |
在计算机表示56
若为byte类型,则是00111000
若为short类型,则是00000000 00111000
//1.定义byte类型的变量
byte b=127;
System.out.println(b);
//2.定义short类型的变量
short s=32767;
System.out.println(s);
//3.定义int类型的变量
int i=200;
System.out.println(i);
//4.定义long类型的变量
//long类型数据必须以L或l结尾:`long l=10000000000L;`
long l=10000000000L;
System.out.println(l);
| 浮点数 | 取值范围 | 内存(字节) |
|---|---|---|
| float | -3.402 *10^38~3.402 *10^38 | 4 |
| double | -1.797 *10^308~1.797 *10^308 | 8 |
//定义float类型的变量
//浮点数类型的变量,必须以f或F结尾,也可以不写
float f=1.1f;
System.out.println(f);
//定义double类型的变量
double d=1.1;
System.out.println(d);
字符型:char 二个字节 ‘好’ ‘a’
char c='中';
System.out.println(c);
注意:C语言中的char不能存储中文,但是Java里可以
布尔型:boolean 一个字节 true/false
boolean bb=ture;
System.out.println(bb);//打印出的就是ture,但在C语言中就是1/0
string、数组都是引用数据类型,其他是基本数据类型
标识符
代码中所有我们自己起的名字。比如类名、变量名、方法名
其命名有以下硬性要求
- 由数字、字母、下划线__、美元符¥组成
- 不能以数字开头
- 不能是关键字
- 区分大小写
驼峰命名法
- 小驼峰命名法 (lowerCamelCase)
- 规则:第一个单词的首字母小写,后续每个单词的首字母大写。
- 用途:在 Java、JavaScript 等语言中,通常用于变量名和方法名。
- 示例:
userName,calculateTotalPrice,getStudentInfo
- 大驼峰命名法 (UpperCamelCase / PascalCase)
- 规则:每个单词的首字母都大写。
- 用途:在 Java 等语言中,通常用于类名、接口名。
- 示例:
StudentInfo,UserService,DataProcessor
键盘录入
相当于c语言里的scanf,但Java需要先创建一个Scanner对象
package com.zlj.test;
import java.util.Scanner;
public class demo09 {
static void main() {
/*
键盘录入:
1. 创建Scanner对象(创建了一个Scanner类型的变量)
2. 录入数据
3. 输出数据
4. 关闭Scanner对象
就是要找到Scanner这个打工人,然后让它干活
*/
//Scanner为类名(固定) sc为变量名(自定义)
//右边的赋值表示“从键盘获取输入”
Scanner sc = new Scanner(System.in);
//接收键盘录入的整数
//.nextInt()为方法名,读取一个整数,是规定好的功能
int a = sc.nextInt();
System.out.println(a);
//接收键盘录入的double
double b = sc.nextDouble();
System.out.println(b);
//接收键盘录入的字符串
String c = sc.next(); //以空格为结束标志 换行再输入吃的是缓冲区的换行 显示空格 应该用String c=sc.nextline() 以回车为结束标志
System.out.println(c);
}
}
println输出
//字符串输出
System.out.println("你好,世界!");
//值输出
int Num=100;
System.out.prinln(Num);
//混合输出
int num1 = 10;
int num2 = 20;
System.out.println("第一个数是:" + num1 + ",第二个数是:" + num2);
// 也可以在里面直接做数学运算(记得加括号)
System.out.println("它们的和是:" + (num1 + num2));
文字与数字间的 + 号不叫加法,叫“拼接”。 它的作用是把前后的内容粘成一条长长的字符串。快捷缩写sout
类型转换
隐式转换
不同类型的数据进行计算,默认采取隐式转换,java自动转换,无需我们写代码
如有byte short类型的数据,先提升为int类型
把取值范围小的提升为取值范围大的,再进行运算
| 变量 | 类型 |
|---|---|
| a | byte |
| b | byte |
| c=a+b | int |
| 变量 | 类型 |
|---|---|
| a | double |
| b | byte |
| c=a+b | double |
强制转换
强制转换不会自动触发,需要手动书写代码
书写格式:目标数据类型 变量名=(目标数据类型)被强转的数据;
int a=10;
byte b=(byte)a;
不能把大类型变量赋值给小类型
例如
short a=10;
short b=10;
byte c=a+b;
a+b会隐形转换为int类型,上面代码试图把int类型变量赋值给byte类型变量,会出现报错,虽然byte足以储存20这个数,但是编译器为了安全起见,认为 int 变量有可能超出 byte 的范围,所以它强制要求你进行强制类型转换。必须写成byte c=(byte)a+b;
数据类型的范围大小顺序是:double > float > long > int > char > short > byte。
- 从小给大(比如
int给long):安全,直接给(自动类型转换)。 - 从大给小(比如
int给char):危险,可能会丢数据,必须加(char)强转(强制类型转换)。
但是强制转换有弊端:有可能会改变原有数据。例如:
int类型的300:二进制00000000 00000000 00000001 00101100
把它强制转换为byte,就是去掉前面三个字节 变为00101100 其十进制是44而不是300
所以最好写成int c=a+b;
字符运算

大写字母=小写字母-32;
package com.zlj.test;
public class demo16 {
static void main() {
//大写字母转换为小写
char ch = 'A';
System.out.println((char)(ch + 32));
//小写字母转换为大写
char ch1 = 'a';
System.out.println((char)(ch1 - 32));
}
}
疑惑:char既能表示数字,又能表示ASC码对应的字符,那它到底是数字还是字符
public class CharSummary {
public static void main(String[] args) {
// --- 1. 存储本质:只存数字 ---
// 虽然代码写的是 'A',但计算机内存里实际存储的是整数 65 (ASCII码)。
// char 类型只占 2 个字节,用来存这个整数。
char c = 'A';
// --- 2. 显示规则:看类型翻译 ---
// 当变量类型是 char 时,println 会自动去查表(ASCII/Unicode),
// 把数字 65 翻译成字符 'A' 打印出来。
System.out.println(c); // 输出: A
// --- 3. 运算规则:还原数字身份 ---
// 一旦 char 参与数学运算(+ - * /),Java 会立即把它当作整数处理。
// 这里的 c 会被视为 65,而不是 'A'。
// 且根据规则,char 运算时会自动升级为 int 类型。
int result = c + 1; // 相当于 65 + 1 = 66
// 因为 result 是 int 类型,println 直接打印数字,不查表。
System.out.println(result); // 输出: 66
// --- 4. 赋值规则:大给小需强转 ---
// c + 1 的结果默认是 int 类型(数字 66)。
// 试图把 int(大范围)赋值给 char(小范围),编译器会报错。
// 必须使用 (char) 强制转换,告诉编译器“我知道我在做什么”。
char nextChar = (char) (c + 1);
// 此时 nextChar 存储的是数字 66。
// 但因为它的类型是 char,打印时又会查表,显示为 'B'。
System.out.println(nextChar); // 输出: B
}
}
char 是最特殊的类型,它既是字符,也是数字。
- 本质:它底层存的就是一个数字(0 到 65535),对应着ASCII 编码表。
- 什么时候是字符?
- 当你用
System.out.println(c)打印它时,或者赋值给另一个char时,它显示为字符(比如'A')。也就是打印char类型,无论你写的是char a=65还是char a=‘A’,只要它是char类型都会显示为字符
- 当你用
- 什么时候是数字?
- 它本质就是数字,当你把它参与运算(
c + 1)时,或者强制转为int时,它显示为数字(比如65)。关于第一种当char c=65时如果你写的是char a=c+1会报错。如果是char a=(char)(c+1)时,a打印出来就是字符b。如果是int a=c+1时,它就变成了数字,不再是字符
- 它本质就是数字,当你把它参与运算(
字符串拼接运算
这张图讲清楚了 Java 中字符串(String)与 + 号的特殊规则,这也是 Java 和 C 语言最大的区别之一。
核心原则
- 只有加法:字符串只支持
+操作,不支持-、*、/。 - 拼接本质:只要
+号两边任意一方是字符串,它就变成了“连接器”,而不是数学加法。 - 自动转换:非字符串数据(如数字)会被自动转成文本形式连接。
运算顺序(从左到右)
Java 是严格按照从左往右的顺序执行的,这点非常重要:
- 数字在前,字符串在后:先算加法,再拼接。
10 + 8 + "岁"→ 先算10+8得18→ 再拼"岁"→ 结果"18岁"
- 字符串在中间:前面的拼完,后面的接着拼。
10 + 8 + "岁" + 1 + 2- 第一步:
18 + "岁"→"18岁" - 第二步:
"18岁" + 1→"18岁1" - 第三步:
"18岁1" + 2→"18岁12"
运算符
算数运算符
+ – * / %
与C语言一样
整数:整数相除结果还是整数,其他运算跟数学中是一模一样的
小数:小数直接参与计算,结果有可能不精确的
自增自减运算符
与c语言一样的,不想说,略
赋值运算符
其实也不想说,但我表格瘾犯了
| 符号 | 说明 | 举例 |
|---|---|---|
| = | 直接赋值 | int a=10,将10赋值给a |
| += | 加后赋值 | a+=b,将a+b的值给a |
| -= | 减后赋值 | a-=b,将a-b的值给a |
| *= | 乘后赋值 | a*=b,将a×b的值给a |
| /= | 除后赋值 | a/=b,将a÷b的商给a |
| %= | 取余赋值 | a%=b,将a÷b的余数给a |
关系/比较运算符

此处与c语言唯一的不同是结果类型是 boolean(布尔值)true或false,但c语言结果类型是int(整数)1或0
逻辑运算符

&&(短路与):如果左边是假(C 是 0,Java 是 false),右边根本不会执行。||(短路或):如果左边是真(C 是 1,Java 是 true),右边根本不会执行。&(单与):不管左边是真还是假,右边一定会执行。|(单或):不管左边是真是假,右边一定会执行
基本逻辑和c语言一样,但是操作数的类型要求更严格,java要求必须是boolean类型
C 语言遵循“非 0 即真”的原则。在逻辑运算中,整数可以和逻辑值混用。 1 && 1 → 真 (1)
5 && 0 → 假 (0) (因为 5 非 0,被视为真)
!10 → 假 (0)
Java 的逻辑运算符只能操作 boolean 类型(即 true 或 false)。绝对不允许使用数字(int)代替。 true && true → true
true && false → false
a>=60&&a<=80,当a为70时→ture,当a为55时→false
5 && 0 → 编译报错! (Type mismatch: 不能将 int 转换为 boolean)
三元运算符
基本语法
三元运算符由三个操作数组成,格式如下: 条件表达式 ? 值1 : 值2
执行逻辑
- 先计算 条件表达式 的值(必须是
boolean类型)。 - 如果为
true,则取 值1。 - 如果为
false,则取 值2。 - 最终结果就是选中的那个值。
场景:求二个数的最大值
int a = 10;
int b = 20;
// 语法:(条件) ? 真值 : 假值
int max = (a > b) ? a : b;
System.out.println(max); // 输出 20
运算符优先级

记住小括号优先于所有即可
分支/选择结构
判断语句(if)
分为简单if语句、if-else语句、if-else if-else嵌套语句
1.简单if语句
这是最基本的形式,当条件为 true 时,执行大括号内的代码;如果为 false,则直接跳过。
if (条件表达式) {
// 条件为真时执行的代码
}
小细节:(个人觉得K&R风更舒服点)

package com.itheima.ifdemo;
public class IfDemo4 {
public static void main(String[] args) {
/* 4. 判断布尔类型的变量
判断布尔类型的变量,直接把变量写在小括号中即可*/
boolean b = false;
if(b){
System.out.println("为真");
}
}
}
2.if-else语句
这是“二选一”的场景。如果条件为 true,执行 if 块的代码;否则,执行 else 块的代码。
if (条件表达式) {
// 条件为真时执行的代码
} else {
// 条件为假时执行的代码
}
3.if-else if-else语句
当需要处理多个互斥的条件时,可以使用这种“多选一”的结构。程序会从上到下依次判断,一旦某个 if 或 else if 的条件为 true,就执行其对应的代码块,然后整个判断结构结束。
if (条件1) {
// 条件1为真时执行
} else if (条件2) {
// 条件1为假,且条件2为真时执行
} else if (条件3) {
// 条件1、2都为假,且条件3为真时执行
} else {
// 以上所有条件都为假时执行
}
选择语句(switch)
switch 语句适用于需要根据单个变量或表达式的值,从多个固定的选项中进行选择的场景。相比一连串的 if-else if,代码结构通常更清晰。
switch (表达式) {
case 值1:
// 当表达式的值等于 值1 时执行的代码
break; // 跳出 switch 结构
case 值2:
// 当表达式的值等于 值2 时执行的代码
break;
// ... 可以有任意数量的 case
default:
// 当表达式的值与所有 case 都不匹配时执行的代码
}
核心规则
- 表达式类型:
switch后面的表达式结果必须是特定类型,例如byte、short、int、char、String(Java 7 及以上)或枚举类型。 break关键字:每个case语句块末尾的break非常重要,它的作用是跳出整个switch结构。case穿透:如果缺少break,程序会继续执行下一个case的代码,直到遇到break或switch结束,这种现象称为“穿透”。default分支:default是可写可不写的,类似于if-else结构中的else,用于处理所有未明确列出的情况。它的位置可以写在switch代码块里的任意位置(开头、中间、结尾)。为了代码的可读性,建议写在最后
switch新特性(JDK14+)
- 箭头语法 (
->)用->取代case后的冒号: 好处:代码更简洁,防止 case 穿透(不需要写 break)。//旧特性
int score = 85;
String result = “”; // 必须先定义空变量
switch (score) {
case 100:
result = “满分”;
break; // 必须写 break,否则会继续执行下面的 case
case 80, 90: // JDK 12+ 支持逗号合并
result = “优秀”;
break;
case 60:
result = “及格”;
break;
default:
result = “未知等级”;
}
System.out.println(result);
//新特性
int score = 85;
// 直接赋值,不需要预先定义 result
String result = switch (score) {
case 100 -> “满分”; // 无需break
case 80, 90 -> “优秀”; // 逗号分隔多个值
case 60 -> “及格”;
default -> “未知等级”;
};
System.out.println(result);哦买噶,字符串也能直接赋值了吗,c不可以的,插个眼 - 多值合并case 1, 2, 3 -> …好处:用逗号分隔多个值,替代了旧式的“穿透写法”。
- 返回值 (表达式)int res = switch(…) { … }好处:Switch 可以直接算出一个结果赋值给变量。
- Yield 关键字:用于在代码块中返回结果。


import java.util.Scanner; // 1. 导入 Scanner 类
public class SwitchDemo5 {
public static void main(String[] args) {
// 2. 创建 Scanner 对象
Scanner scanner = new Scanner(System.in);
System.out.println("=== 简易计算器 ===");
// 3. 获取用户输入的第一个数字
System.out.print("请输入第一个数字 (a): ");
int a = scanner.nextInt();
// 4. 获取用户输入的运算符号
System.out.print("请输入运算符 (+, -, *, /): ");
// next() 读取一个字符串,因为我们 switch 用的是 String
String operator = scanner.next();
// 5. 获取用户输入的第二个数字
System.out.print("请输入第二个数字 (b): ");
int b = scanner.nextInt();
System.out.println("----------------");
// 6. Switch 表达式计算
// 注意:除法这里为了演示保持整数除法,如果需要小数,需将 int 强转为 double
int result = switch (operator) {
case "+" -> a + b;
case "-" -> a - b;
case "*" -> a * b;
case "/" -> {
if (b == 0) {
System.out.println("错误:除数不能为0!");
yield 0;
}
yield a / b;
}
default -> {
System.out.println("错误:不支持的运算符!");
yield 0;
}
};
// 7. 输出结果
System.out.println("计算结果: " + a + " " + operator + " " + b + " = " + result);
// 8. 关闭 scanner,释放资源
scanner.close();
}
}
循环结构
for循环
格式:
for(初始化语句;条件判断语句;条件控制语句){
循环体语句
}
执行流程: ① 执行初始化语句 ② 执行条件判断语句,看其结果是否成立 成立:执行循环体语句,然后执行条件控制语句,然后回到② 不成立:结束循环
// 打印 1 到 5
for (int i = 1; i <= 5; i++) {
System.out.println("这是第 " + i + " 次");
}
//int i也可以写在for循环外面
int i=1;
for(;i<=5;i++){
System.out.println("这是第"+i+"次");
}
初始化语句、条件判断语句、条件控制语句可以不写,但是之间的分号不能省略
条件判断语句被省略,则为无限循环/死循环,注意在无限循环的下面,不能写任何其他代码,否则会报错
while循环
格式:
初始化语句
while(条件判断语句){
循环体语句
条件控制语句
}
int guess = 0;
int target = 7;
// 只要没猜对,就一直问
while (guess != target) {
System.out.println("请输入数字:");
// 这里需要配合 Scanner 获取输入
// guess = scanner.nextInt();
break; // 暂时用 break 防止死循环
}
for和while的区别:
for循环中:知道循环次数或循环的范围
while循环:不知道循环的次数和范围,只知道循环的结束条件
do-while循环
格式:
初始化语句;
do{
循环体语句;
条件控制语句;
}while(条件判断语句);
先执行后判断,循环体至少执行一次,而while是先判断再执行
int choice;
do {
System.out.println("=== 主菜单 ===");
System.out.println("1. 登录");
System.out.println("0. 退出");
// choice = scanner.nextInt();
choice = 0; // 模拟退出
} while (choice != 0); // 至少显示一次菜单
break和continue
break:直接结束
- 含义:打断、终止。
- 作用:一旦遇到 break,整个循环立刻停止,不再进行下一次判断,直接跳出循环体,执行循环后面的代码。
- 生活案例:你在吃一筐苹果,吃到第 3 个发现是烂的(满足条件),你直接把整筐苹果扔了,剩下的都不吃了。
continue:跳过本次(单次要赖)
- 含义:继续(下一次)
- 作用:一旦遇到
continue,本次循环剩下的代码不跑了,直接进入下一次循环(比如去执行i++,然后判断条件)。 - 生活案例:你在吃一筐苹果,吃到第 3 个发现是烂的(满足条件),你把这个烂苹果扔掉,然后继续吃第 4 个。
猜数字游戏
import java.util.Random;
import java.util.Scanner;
public class Test2 {
public static void main(String[] args) {
/*
生成一个1~100之间的随机数,利用键盘录入模拟猜的动作,一直猜,直到猜中为止
*/
// 1. 生成一个1~100之间的随机数
Random r = new Random();
int number = r.nextInt(100) + 1;
System.out.println(number); // 这一行是为了方便测试,实际游戏中通常会去掉
while (true) {
// 2. 键盘录入模拟猜的动作
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要猜的数字:");
int guessNumber = sc.nextInt();
// 3. 比较
if (guessNumber > number) {
System.out.println("你猜的数字太大了");
} else if (guessNumber < number) {
System.out.println("你猜的数字太小了");
} else {
System.out.println("恭喜你猜对了");
// 猜中了循环结束
break;
}
}
}
}
随机数生成代码
// 生成 一个[min, max] 之间的随机数
Random r = new Random();
int num = r.nextInt(max - min + 1) + min;
打印复杂图形
方法一:找规律
方法二:把复杂图形拆分为长方形和三角形,只要打印长方形和三角形就好


红色所围区域为要打印的平行四边形,第二种方法就是把这个四边形变成了多个三角形和长方形相加
打印九九乘法表
package com.itheima.looploop;
public class Test6 {
public static void main(String[] args) {
/*
打印九九乘法表
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
*/
// 1. 直角三角形 (9 * 9)
// 外循环:行数
for (int i = 1; i <= 9; i++) {
// 内循环:列数
for (int j = 1; j <= i; j++) {
// 打印算式,\t 是制表符,用于对齐
System.out.print(j + " * " + i + " = " + i * j + "\t");
}
// 每一行打印完后换行
System.out.println();
}
}
}
制表符\t
- 简单理解:
长度可变的大空格,打印表格数据的时候,可以使上下对齐
可以把它直接写在内容后面代替空格,如果一个\t没对齐可以多写几个
- 真正的含义:
在前面的字符后面补1~4个空格,让这个整体(\t前面的内容及它本身)的长度凑成4的整数倍(IDEA)
部分编译器是补1~8个空格,凑成8的整数倍。
数组
静态初始化
完整格式
数据类型 数组名[] = new 数据类型[]{数据值,数据值…};
int arr[]= new int []{1,2,3};
int []arr = new int[3];
简写格式
数据类型 数组名[]={数据值,数据值…};
int arr[]={1,2,3};
[]可以写在数组名前也可以写在数组名后
数组特点:是连续的空间,一旦定义,长度不可变
元素访问
元素的访问都是通过索引进行的
含义:数组的一个编号,也叫作:角标、下标、编号
特点:从0开始的,连续+1,不间断
获取元素
变量=数组名[索引]
int num=arr[5];
修改元素
数组名[索引]=数据值;
arr[5]=10;
数组遍历
获取数组长度:int L=arr.length
for(int i=0;i<arr.length;i++){
System.out.printlin(arr[i]);
}
方法
就等同于 C 语言里的“函数”。它们都是将一段可重复使用的代码封装起来,完成特定任务。不过,由于 Java 是一门纯粹的面向对象语言,而 C 是面向过程的,所以“方法”在 Java 中有一些独特的规则和特性。
注意:方法不能嵌套,但c语言的函数可以
Java 允许用局部变量给成员变量赋值

格式:
public static 返回值类型 方法名(参数1,参数2…){
方法体;
return 返回值;
}
Java21+版本可以不写public
注意点:方法跟方法之间是平级关系,不能相互嵌套,故要写在main外面
package com.zlj.test;
import java.util.Scanner;
public class demo5_1 {
//方法的定义
public static int getsum(int a,int b){
return a+b;
}
static void main() {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int b = sc.nextInt();
int sum = getsum(a, b);//方法的调用
System.out.println(sum);
}
}
/*
注意点:
方法跟方法之间是平级关系,不能相互嵌套
方法不会主动运行,需要被调用才可以
小括号中的参数需要一一对应
方法必须放在类(class)里面
*/
一个包下的类是互通的,当你把Scanner写成一个类,方法再写成一个类,在方法类中用Scanner对象不需要再写import…..除非那个成员是private 只能在当前类使用
方法重载
同一个类中,定义了多个同名的方法,这些方法具有类似的功能
每个方法具有不同的参数类型和参数个数,这些同名的方法,就构成了重载关系
简单理解:同一个类,方法名相同,形参【类型、个数、顺序之一或以上】不同的方法构成重载关系,无需看返回值(方法返回值类型)

但一般不会写成顺序不同的方式

a、b是int型,int会被提升为double,但第三个和第四个顺序不同,二个都可以,Java不知道调用哪个,所以报错了,如果把第二个注释掉,那么又能运行了
集合
在 Java 中,普通的数组一旦创建,长度就固定了。而集合就像是一个“可变长度的容器”,它可以随时存放任意数量的对象,并且提供了丰富的方法来操作这些数据。
格式:ArrayList<类型> list = new ArrayList<>();
类型不能是基本数据类型,只能用引用数据类型或者是它们的包装类
例如:ArrayList< String > list = new ArrayList<>();
ArrayList:表示这是一个基于数组实现的列表,支持随机访问(通过下标查找很快)。<String>:这是泛型。它规定了这个集合里只能装String(字符串)类型的对象。如果你尝试放入Integer或其他类型,编译器会直接报错。这保证了数据的安全性。new ArrayList<>():右边的<>是 Java 7 引入的“菱形语法”(Diamond Operator),可以省略具体的类型,编译器会自动推断。
常用方法:
- 增加 (Create)
boolean add(E e):将元素添加到集合末尾。返回true表示添加成功。- 补充:
void add(int index, E element):可以在指定位置插入元素。
- 删除 (Delete)
boolean remove(Object o):根据内容删除元素。如果集合里有 “aaa”,它会移除第一个匹配的 “aaa”。E remove(int index):根据索引(下标)删除元素。比如list.remove(0)会删除第一个元素,并返回被删除的那个元素。
- 修改 (Update)
E set(int index, E element):替换指定位置的元素。例如list.set(0, "bbb")会把第 0 个位置的值改成 “bbb”,并返回旧值。
- 查询 (Read)
E get(int index):获取指定位置的元素。例如list.get(0)获取第一个元素。int size():获取集合中当前有多少个元素(注意不是length,数组才用 length,集合用 size)。
/*
boolean add(E e) 添加
boolean remove(E e) 删除
E remove(int index)
E set(int index, E e) 修改
E get(int index) 查询
int size() 获取长度
*/
public class ArrayListDemo2 {
public static void main(String[] args) {
//1.创建一个集合
ArrayList<String> list = new ArrayList<>();
//2.添加元素
boolean result = list.add("aaa");
//也可以直接list.add("aaa");这里只是把返回结果复制给了result
System.out.println(result); //输出ture
System.out.println(list);//输出[aaa]
list.add("bbb");
list.add("ccc");
System.out.println(list);//输出[aaa,bbb,ccc]
//3.删除元素
//第一种方法,删除aaa成功,删除不存在的ddd失败
boolean result = list.remove("aaa")
System.out.println(result);//输出ture
System.out.println(list);//输出[bbb,ccc]
boolean result = list.remove("ddd")
System.out.println(result);//输出false
//第二种方法,删除bbb成功
String str = list.remove(0)
System.out.println(str)//输出bbb 即返回值为被删除的元素
System.out.println(list);//输出[ccc]
//4.修改元素
String result = list.set(0, "ddd");
System.out.println(result);//输出ccc 即返回值为被覆盖的元素
System.out.println(list)//输出[ddd] 表示索引为0的元素已被替换为ddd(原来是ccc)
//5.查询(获取)元素(根据索引)
String s = list.get(0);
System.out.println(s);//输出ddd
System.out.println(list);//输出[ddd]
//6.获取集合长度
int size = list.size();
System.out.println(size);//输出1
//7.遍历集合
for(int i = 0;i < list.size();i++){
String str = list.get(i);
System.out.println(str);
}
}
}
原理篇
Java运行机制
Java程序运行的过程
HelloWorld.java文件(人类能看懂的源码)通过JDK中的javac编译器编译变为HelloWorld.class字节码文件,然后class文件运行在Java虚拟机(JVM)中而不是在操作系统里。 编译器的作用是把人类能看懂的 .java 源码,翻译成JVM 能看懂的“字节码”(也就是 .class 文件),然后由 JVM 内部的“翻译官”(执行引擎)把它进一步翻译成当前操作系统能听懂的 0 和 1 机器指令。也就是说编译后得到字节码,运行后才变成机器指令
虚拟机( VM)
本质上就是通过软件模拟出来的“假电脑”。它是一台运行在你真实电脑(物理机)里的“电脑中的电脑”。这台虚拟出来的电脑拥有自己独立的 CPU、内存、硬盘和网卡,甚至可以安装和运行完全独立的操作系统(比如在你的 Windows 电脑里运行一个完整的 Linux 或 macOS 系统)。
分为系统虚拟机和进程虚拟机。之前用的雷电模拟器模拟手机操作系统,就是系统虚拟机,下载不同版本的虚拟机就能在任意操作系统里运行该软件,这种特性叫跨平台。也就是说手机游戏本身不能跨平台,只能在手机中运行,但它借助于不同版本的虚拟机实现了跨平台
跨平台好处:只要一个安装包就能在所有操作系统里运行
JDK里包含我们需要的虚拟机JVM,JVM是进程虚拟机。针对不同操作系统,我们安装不同版本的JDK(Java Development Kit开发者工具包)就可以了,
打开idea你可以看到src文件里是源代码,out里是编译后的代码
内存和内存地址
内存:
软件在运行时,用来临时存储数据的
把数据保存到内存中,从内存的对应位置(内存地址)把数据取出
内存地址:
内存以字节为单位划分,每个字节都有编号,叫做内存地址,用于快速管理内存空间
内存地址编号规则:
32位操作系统:内存地址以32位的二进制表示。共2^32个内存地址,最大支持内存:8*2 ^32次方(字节),也就是4096MB约等于4GB
64位的操作系统:内存地址以64位的二进制表示。最大内存地址:2^64 最大支持内存:2^64字节 17592186GB 17179TB
内存地址表示形式:二进制太长了,平常都是每四个二进制为一组转换为16进制来表示,方便阅读
底层存储的是二进制,显示形式是16进制
虽然叫“64位操作系统”,但在目前的实际硬件和系统设计中(如x86-64架构),为了降低硬件复杂度和成本,真正被用到的地址线通常只有48位(256TB)
变量和方法的内存分配
java虚拟机把内存分为了栈、堆、方法区、本地方法栈、程序计数器
栈跟方法有关系,方法被调用就进栈执行,方法执行完毕就从栈里出去
通过new开辟的空间一定在堆里面,如数组
class文件会加载在方法区内临时存储


方法被调用就进栈运行
数组的内存分配

int是基本数据类型,数组是引用数据类型,基本数据类型里存储的是真实的数据,传递的也是真实的数据,引用数据类型存储的数据的地址,传递的也是地址值
面向对象
类和对象
对象:把相关的数据和方法组织为一个整体来看待
面向对象:利用对象进行软件开发
面向对象的三大特性:封装、继承、多态
类就是一种封装
定义一个类描述一类事物
public class Dog(){
string name;
int age;
string color;
double weight;
}
创建对象,记录单独个体的所有信息
Dog d=new Dog();
d.name="小白";
d.color="白色";
d.weight=5.5;
d.age=2;
小细节:
描述一类事物的类叫Javabean类
带有main方法的类叫测试类
Javabean类可以写属性和行为
注意:写在Javabean类里的方法是不加static的
内部类
数学类
1. 概述与特性
- 不可实例化:
Math类的构造方法是私有的,不能创建对象(例如new Math()是错误的)。 - 调用方式:所有方法均为
static(静态)方法,直接通过类名调用,格式为Math.方法名(参数)。
2. 取整相关方法
(1) abs – 获取绝对值
- 作用:返回参数的绝对值。
- 示例:int num1 = Math.abs(-10); // 结果: 10
(2) ceil – 向上取整
- 作用:返回大于或等于参数的最小整数(进一法)。
- 返回值类型:
double - 示例:System.out.println(Math.ceil(10.11)); // 结果: 11.0
System.out.println(Math.ceil(10.55)); // 结果: 11.0
(3) floor – 向下取整
- 作用:返回小于或等于参数的最大整数(去尾法)。
- 返回值类型:
double - 示例:System.out.println(Math.floor(10.11)); // 结果: 10.0
System.out.println(Math.floor(10.55)); // 结果: 10.0
(4) round – 四舍五入
- 作用:返回与参数最接近的整数(四舍五入)。
- 特殊规则:
- 如果参数的小数部分刚好是
.5,且参数为负数时,采取“舍去”操作(即向正无穷方向取整,例如 -10.5 会变成 -10)。 - 正数的 .5 则正常进位。
- 如果参数的小数部分刚好是
- 示例:System.out.println(Math.round(10.11)); // 结果: 10
System.out.println(Math.round(10.55)); // 结果: 11
System.out.println(Math.round(-10.11)); // 结果: -10
System.out.println(Math.round(-10.55)); // 结果: -11
System.out.println(Math.round(-10.5)); // 结果: -10 (特殊情况)
3. 数值比较与运算
(1) max / min – 最大值与最小值
- 作用:获取两个数值中的较大值或较小值。
- 示例:System.out.println(Math.max(10, 20)); // 结果: 20
System.out.println(Math.min(10, 20)); // 结果: 10
(2) pow – 幂运算
- 作用:返回第一个参数的第二个参数次幂的值 (a^b)。
- 隐藏用法:可以通过小数指数实现开根号功能。
- 示例:System.out.println(Math.pow(2, 3)); // 结果: 8.0 (2的3次方)
System.out.println(Math.pow(3, 0.5)); // 结果: 1.732… (3的平方根)
System.out.println(Math.pow(4, -0.5)); // 结果: 0.5 (4的-0.5次方)
4. 开根号专用方法
(1) sqrt – 平方根
- 作用:返回参数的正确舍入的正平方根。
- 示例:System.out.println(Math.sqrt(4)); // 结果: 2.0
(2) cbrt – 立方根
- 作用:返回参数的立方根。
- 示例:System.out.println(Math.cbrt(27)); // 结果: 3.0
5.随机值
- random():返回值为
double的随机值,范围在 [0.0, 1.0) 之间。
包装类
基本数据类型所对应的对象
//3.这两种方式获取对象的区别(掌握)|
//因为在实际开发中,-128~127之间的数据,用的比较多。
//如果每次使用都是new对象,那么太浪费内存了
//所以,提前把这个范围之内的每一个数据都创建好对象
//如果要用到了不会创建新的,而是返回已经创建好的对象。
Integer i6 = Integer.valueOf(127);
Integer i7 = Integer.valueOf(127);
System.out.println(i6 == i7);//true
Integer i8 = Integer.valueOf(128);
Integer i9 = Integer.valueOf(128);
System.out.println(i8 == i9);//false
//因为看到了new关键字,在Java中,每一次new都是创建了一个新的对象
//所以下面的两个对象都是new出来,地址值不一样。
Integer i10 = new Integer( value: 127);
Integer i11 = new Integer( value: 127);
//System.out.println(i10 == i11);//false
package com.itheima.a03integerdemo;
public class A03_IntegerDemo3 {
public static void main(String[] args) {
//在JDK5的时候提出了一个机制:自动装箱和自动拆箱
//自动装箱:把基本数据类型会自动的变成其对应的包装类
//自动拆箱:把包装类自动的变成其对象的基本数据类型
//在底层,此时还会去自动调用静态方法valueOf得到一个Integer对象,只不过这个动作不需要我们自己去操作了
//自动装箱的动作
Integer i1 = 10;
Integer i2 = new Integer( value: 10);
//自动拆箱的动作
int i = i2;
//在JDK5以后,int和Integer可以看做是同一个东西,因为在内部可以自动转化。
}
import java.util.Scanner;
public class A05_IntegerDemo5 {
public static void main(String[] args) {
//键盘录入
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串");
/* String str = sc.next();
System.out.println(str);*/
//弊端:
//当我们在使用next, nextInt, nextDouble在接收数据的时候,遇到空格,回车,制表符的时候就停止了
//键盘录入的是123 123 那么此时只能接收到空格前面的数据
//我想要的是接收一整行数据
//约定:
//以后我们如果想要键盘录入,不管什么类型,统一使用nextLine
//特点遇到回车才停止
String line = sc.nextLine();
System.out.println(line);
int i = Integer.parseInt(line);
}
}
//1.把整数转成二进制,十六进制
String str1 = Integer.toBinaryString( i: 100);
System.out.println(str1);//1100100
//2.把整数转成八进制
String str2 = Integer.toOctalString( i: 100);
System.out.println(str2);//144
//3.把整数转成十六进制
String str3 = Integer.toHexString( i: 100);
System.out.println(str3);//64
//4.将字符串类型的整数转成int类型的整数
//强类型语言:每种数据在java中都有各自的数据类型
//在计算的时候,如果不是同一种数据类型,是无法直接计算的。
int i = Integer.parseInt( s: "123");
System.out.println(i);
System.out.println(i + 1);//124
//细节1:
//在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错
System类和Object类
封装
数据安全问题(私有化+get/set)
防止外界输入非法数据
private关键字:
是一个权限修饰符,可以修饰成员变量和成员方法
特点:一旦被private修饰,只能在本类中才能访问,外界无法访问
虽然防止了输入错误数据,但是用了它我们也无法输入正确数据
可以调用set/get方法解决这个问题
步骤:
1、私有化成员变量
2、set/get方法(快捷方法alt+insert 或者直接鼠标右键空白处→生成→getter and setter→ctrl+A全选→一个回车)
详见源代码文件com.itheima.ooptest5
this关键字
成员变量:定义在方法外面、类里面的变量
局部变量:定义在方法里面或方法形参
就近原则
在方法中使用变量有查找顺序:先找局部变量,如果没有再用所在类的成员变量
pubilc class Dog(){
private int age; //成员变量
//set方法(赋值),不写Static
public void setAge(int age){ //局部变量
age=age;
}
//get方法(获取)
public int getAge(){
return age;
}
}
this作用:当变量名相同时,可用于区别成员变量和局部变量
如果想直接使用成员变量,就加this前缀,this.age就是成员变量
pubilc class Dog(){
private int age;
//set方法(赋值),不写Static
public void setAge(int age){
age=age;
System.out.println(this.age);
}
//get方法(获取)
public int getAge(){
return age;
}
}
构造方法/构造器
构造方法也叫构造器、构造函数
作用:创建对象的时候,由虚拟机JVM自动调用,给成员变量进行初始化
特点:
方法名与类名相同,大小写也要一致
没有返回值类型,连void也没有
没有具体的返回值(不能有return带回结果的数据)
执行时机:
创建对象的时候由虚拟机调用,不能手动调用构造方法
每创建一次对象就会调用一次构造方法
格式:
修饰符 类名(){
方法体;
}
public class Student{
private String name;
private int age;
public Student(){
空参构造方法
}
public Student(String name,int age){
带全部参数构造方法
}
}



如果没有定义构造方法,系统将给出一个默认的无参数构造方法,如上,不会报错
如果定义了构造方法,系统将不再提供默认的构造方法,如下,没有无参构造,却又调用了,故报错


带参构造方法和无参数构造方法,二者方法名相同,但是参数不同,这叫做构造方法的重载
建议无论是否使用,都手动书写无参数构造方法,和带全部参数的构造方法
构造方法快捷键:alt+insert 或者直接鼠标右键空白处→生成→构造函数
封装设计要求
合理隐藏,合理暴露
如何合理隐藏?
使用private关键字修饰成员变量,就只能在本类中直接访问,其他任何地方都不能直接访问
如何暴露?
使用public修饰的get(取值)和set(赋值)方法合理暴露

实体类(JavaBean)
必须满足以下条件:
1、类中成员变量全部私有,并提供public修饰的getter/setter方法
2、类中需要提供一个无参数构造器,有参数构造器可选
基本作用:创建它的对象,存取数据(封装数据)
实体类的对象只负责数据存取,而数据的业务处理交给其他类完成,实现数据和数据业务处理相分离,也就是一个包建多个类

原理篇
Java中对象的内存分配


对象在方法中进行传递
把一个对象传递给方法,实际传递的是对象的内存地址
当多个变量指向同一个对象的时候,只要有一个变量修改了对象中的属性,其他变量再次访问就是修改之后的结果了
this关键字的本质
代表所在方法调用者的内存地址
static
static修饰成员变量
static:表示静态,是Java的修饰符,用来修饰成员变量/成员方法。
1.静态变量:被当前类所有的对象共享
共享:赋值只要赋值一次。只要有一个对象修改了静态变量,其他对象再次访问的时候就是修改之后的结果了
2.调用方式:
方式一:类名调用(推荐)
方法二:对象名调用
public class Student {
String name;//姓名
int age;//年龄
//学生共享一个老师
static String teacherName;//老师的名字
}
Student.teacherName="小雯"; // 这是类名调用,之后创建的所有对象
Student s2 = new Student();
s2.teacherName="小雯"; //这是对象名调用
当前的属性如果是对象独有的则不加static,如果是对象共享的就要加static
详见代码com.itheima.staticvariabletest1
静态变量的内存图
静态变量随着类的加载而加载的
第一行加载类,memory.class进到方法区
第二行加载main方法,main方法进栈
第三行用到Student类,故此时该类的字节码文件Student.class加载到方法区,并创造静态变量teacherName(静态区),静态变量随着类的加载而加载的,会优先于对象出现。JDK8前静态区在方法区里,之后就挪到了堆里,以后静态变量一律在堆内存。然后对该变量赋值
第四行有new关键字,此时对象才会在内存出现,等号左边定义变量s1存放在栈内存,等号右边在堆内存开辟小空间,小空间里就是我们创建的对象,存着所有非静态的成员变量(name、age),默认初始化值null、0 然后把小空间的内存地址赋值给栈内存里的s1,与此同时记录静态区的内存地址

第五行第六行给name、age赋值
第七行用s1调用show方法,show方法加载进栈,记录调用者s1的地址。由就近原则,先找show方法里有没有name、age(局部变量),没找到,再去s1地址找name、age(成员变量),打印出小诗诗,23,阿伟老师
方法执行完毕后直接出栈
第八行创建第二个对象,把操作又重复了一遍,第九行show方法再次进栈

到此时show出栈,表示main方法全部执行完,然后main方法出栈,main方法里的变量和对象也会从内存中随之消失,但是静态区变量和方法区字节码信息会永远存在,除非关闭整个虚拟机

由此可见static静态变量只有一份,放在静态区中,在该类中被所有对象共享的

static修饰成员方法
特点:
static修饰的方法叫做静态方法
该方法多用在测试类和工具类中
Javabean类中很少会用
调用方式:
方式一:类名调用
方式二:对象名调用
工具类:不是用来描述一类事物的,也没有main方法,而是帮我们做一些事情的类。
- 类名见名知意
- 私有化构造方法(不让外界创造对象)
- 方法定义为静态 (类名调用方法)
public class ArrayUtil{
private ArrUtil(){}
public static int getMax(...){...}
public static int getMin(...){...}
public static int getSum(...){...}
}
例如:
package com.itheima.toolclasstest;
public class ArrayUtil {
//私有化构造方法,目的:不让外界创建对象
private ArrayUtil(){}
//定义方法(工具类必须是静态)
public static void printArray(int[] arr){
System.out.println("[");
for (int i = 0; i < arr.length; i++) {
if(i == arr.length-1){
System.out.println(arr[i]+"]");
}else{
System.out.println(arr[i]+",");
}
}
}
public static double getAverage(int[] arr){
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
double avg = (double)sum / arr.length;
return avg;
}
}
package com.itheima.toolclasstest;
public class Test {
/*
需求:
在实际开发中,经常会遇到一些数组使用的工具类。
请按照如下要求编写一个数组的工具类完成一下需求:
1.提供一个方法printArr,用于遍历数组
格式如下:[10,20,50,34,100]
2.提供一个方法getAverage,用于返回平均值
*/
static void main() {
//创建一个数组
int[] arr = {10,20,50,34,100};
//遍历
ArrayUtil.printArray(arr);
System.out.println("平均值:"+ArrayUtil.getAverage(arr));
}
}
静态的注意事项:
静态方法只能访问静态变量和其他的静态方法
非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法
静态方法中是没有this关键字,所以调用进栈时不会记录调用者的内存地址

总结:静态只能调用静态,非静态可以调用所有,静态方法无this


静态方法属于类,非静态方法属于对象,前者可以类名调用,后者必须先创造一个对象,只能用对象调用
final
表示最终,不可变。可以修饰变量、类、方法
特点:
只能赋值一次,数据不可变
名字大写,多个单词下划线隔开
package com.itheima.finaltest;
public class Student { //Java bean类
private String name;
private int age;
public Student(String name, int age) { //带参构造器
this.name = name;
this.age = age;
}
public Student() {} //空参构造器
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
package com.itheima.finaltest;
public class finalTest {
public static void main(String[] args) {
/*
final修饰的变量,此时叫做常量
1.只能被赋值一次,一旦赋值,就不能在修改
2.final修饰的变量,必须赋值
3.常量名大写,多个单词用下划线隔开
细节:
final修饰的变量,如果被static修饰,那么常量名可以不大写
基本数据类型:
final修饰的变量,不能修改的是数据类型和数据
引用数据类型:
stu里面记录对象的内存地址,不可改变的是stu记录的内存地址
而对象里的属性值(数据),是可以发生改变的
*/
//定义一个常量
final int NUM=10;
//使用常量
System.out.println(NUM);
// NUM=200;//再次赋值会直接报错
//定义一个引用数据类型的变量
final Student STU=new Student("zhangsan",23);
// STU=new Student(); //创建第二个对象的地址值赋给stu,会报错
STU.setName("lisi");//修改对象属性值(地址里的数据)不会报错
STU.setAge(24);
}
}
枚举
枚举是一个特殊的javabean,这个类的对象是有限个
使用场景:订单的状态、月份、星期、游戏角色的状态、会议室的预约状态、设备状态…
定义格式
public enum 类名{
//枚举项1,枚举项2,枚举项3;
// 属性
// 行为
}
使用枚举对象,不要自己创建,直接调用就可以了
在JavaBean基础上修改:
1、枚举类在第一行已经把所有对象罗列了出来,全是常量
2、空参构造方法:创建对象时采取的都是示例那样的有参构造的话,直接去掉
3、有参构造方法:枚举不让外界创造对象,需要私有化构造方法,像工具类,public→private
4、去掉set方法,因为对象都罗列了出来,是常量,根本不需要修改属性值
注意事项:
1、所有枚举项在底层其实就是常量,都默认使用public static final修饰的。不需要我们自己写,虚拟机默认加上的。对象都是静态变量,因此可以直接类名调用。如下对class反编译,可以看到都有public static final修饰

2、每一个枚举项,都是该枚举类的对象,每一个对象都是通过构造方法创建出来的。如下,每创一个枚举项,都会执行一次构造方法,就打印出“看看我执行了吗…….”

3、枚举类的第一行上必须是枚举项,枚举项用逗号隔开,以分号结尾
4、枚举类的构造方法必须是private修饰,不让外界创建本类的对象。就算你没写private,系统也会自动默认补上
5、编译器会给枚举类新增二个默认存在的方法:values().valueof()
前者:表示获取本类所有的枚举项
后者:表示获取一个指定的枚举项


调用方法
枚举类名.枚举项;


继承
什么是继承
是类与类之间的一种父子关系,Java中提供关键字extends,用于建立类与类之间的关系
public class Student extends Person{
子类 父类
派生类 基类/超类
}
二个类里有部分相同属性和行为时,可以把相同部分单独放在另一个类里,称之为父类,子类可以用父类里的变量和方法
继承的好处
可以把多个子类中重复的代码抽取到父类中,提高代码的复用性
子类可以在父类的基础上,增加其他的功能,使子类更强大
例子
学生类:属性有姓名、年龄、年级;行为有吃饭、学习
老师类:属性有姓名、年龄、学科;行为有吃饭、教书
将他们共有部分:姓名、年龄、吃饭提取出来,单独写在父类
剩下的放在子类,子类对象可以调用父类变量和方法
package com.itheima.oopextendstest;
//父类
public class Person {
String name;
int age;
public void eat(){
System.out.println("吃饭");
}
}
package com.itheima.oopextendstest;
//子类1
public class Student extends Person {
//特有的内容
//属性:年级
String grade;
//行为:学习
public void study() {
System.out.println("学习");
}
}
package com.itheima.oopextendstest;
//子类2
public class Teacher extends Person{
//子类特有的内容
//属性:学科
String subject;
//行为: 教书
public void teacher(){
System.out.println("老师教书育人");
}
}
package com.itheima.oopextendstest;
//测试类
public class Test {
public static void main(String[] args) {
//创建学生的对象
Student s = new Student();
s.name = "张三";
s.age=18;
s.grade="大一";
System.out.println(s.grade+","+s.name+","+s.age);
s.eat();
s.study();
//创建老师的对象
Teacher t = new Teacher();
t.name = "王老师";
t.age = 30;
t.subject = "语文";
System.out.println(t.subject+","+t.name+","+t.age);
}
}
如何设计继承结构
当类与类之间,存在相同的内容,并满足子类是父类中的一种(比如上面例子,学生和老师都是人),就可以考虑使用继承,来优化代码
步骤:
1、利用画图法解决
2、分类
3、抽取共性的内容不断往上抽取(从下往上)
4、写代码(从上往下写)

上面这个不太完美,因为安卓手机和苹果手机里还有共同行为。可以设计成下面这张图这样

代码详见com.itheima.oopextendstest2
继承的特点
1、Java只支持单继承,不支持多继承,但支持多层继承
即一个子类只能继承一个父类,不能同时继承多个父类,但可以a继承b,b继承c这种多层继承,b、c都是a的父类,其中b是直接父类,c是间接父类
2、顶级父类:Object,每个类都直接或间接的继承与Object
继承中的成员特点
成员变量
把多个子类中相同的属性抽取到父类中
就近原则:当前方法的局部变量->当前方法所在类的成员变量->当前子类的父类的成员变量,按照这个顺序找变量,找不到时报错
关键字:
本类成员变量this.前缀
父类成员变量super.前缀最多只能向上访问一层(父类),不能访问多层(爷爷类)
成员方法
把多个子类中共性的成员方法抽取到父类当中
遵守就近原则
方法重写:如果父类的方法不能满足子类的要求了。子类中可以把父类的方法再写一遍,方法申明保持一致
@Override//方法重写的校验注解(标志),要求重写的方法与父类方法签名一致,否则编译报错,可读性好
方法重写要求:
1.重写方法的名称、形参列表必须与父类中的一致,方法体按照实际需求
2、private私有方法,static静态方法,final最终方法不能被重写
3、子类重写父类方法时,访问权限子类必须大于等于父类,返回值类型必须小于等于父类
构造方法
父类中的构造方法不会被子类继承,只能通过super关键字调用
如果子类的构造方法不写super,JVM也会加一个默认的super(),先访问父类的无参构造。
如果想要访问父类的带参构造,super(参数)必须手动写上,不能省略
子类里的构造方法:无参构造与平常一样,带参构造参数为父类+子类里的成员变量,方法体内父类属性调用super,子类属性与平常一样this.xxx=xxx
在创建对象的时候,先执行父类的构造方法,再执行子类的构造方法
根据下面定义继承结构
学生:属性有姓名、年龄、年级 子类
老师:属性有姓名、年龄、学科 父类
public class Person{
String name;
int age;
public Person(){}
public Person(String name,int age){
this.name=name;
this.age=age;
}
}
public class Student extends Person{
String grade;
public Student(){}
public Student(String name,int age,String grade){
super(name,age);
this.grade=grade;
}
public class Teacher extends Person{
String subject;
public Teacher(){}
public Teacher(String name,int age,String subject){
super(name,age);
this subject=subject;
}
}
}


看到this后调用带参构造方法,运行结果依旧是同学18
如果子类中有多个构造方法时,不能用this()互动调用,一定要预留一个调用父类的构造方法
如果构造方法当中写上了this(),就不能再写super()
带继承结构的标准javabean类练习

public class Person{
//属性
private String name;
private int age;
//行为
public void eat(){
System.out.println("正在吃饭");
}
public void sleep(){
System.out.println("正在睡觉");
}
//构造方法
public Person(){} //空参构造(千万别加void)
public Person(String name,int age){ //带参构造
this.name=name;
this.age=age;
}
//get和set方法
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void setName(String name){
this.name=name;
}
public void setAge(int age){
this.age=age;
}
}
public class Student extends Person{
//属性
private String grade;
//行为
public void Study(){
System.out.println("正在学习");
}
//构造方法
public Student(){} //空参构造
public Student(String name,int age,String grade){//带参构造(父类+自己)
this.grade=grade;
super(name,age);
}
//get和set方法(只需要写子类独有的,不用写父类,因为已经写过了)
public String getGrade(){
return grade;
}
public void setGrade(String grade){
this.grade=grade;
}
}
public class Teacher extends Persosn{
//属性
private String subject;
//行为
public void teach(){
System.out.println("正在教书");
}
//构造方法
public Teacher(){} //空参构造
publci Teacher(String name,int age,String subject){ //带参构造(父类+自己)
super(name,age);
this.subject=subject;
}
//get和set方法
public void setSubject(String subject){
this.subjuet=subject;
}
public String getSubject(){
return subject;
}
}
public class BachelorStudent extends Student{
//私有化成员变量 不写 没有独有的
//构造方法
public BachelorStudent(){} //空参构造
public BachelorStudent(String name,int age,String grade){ //带参构造(间接父类+直接父类+自己)
super(name,age,grade);
}
//set和get 不写 没有独有的 父类已经有了
//重写学习方法
public void Study(){
System.out.prinln("本科的同学正在攻读本科内容");
}
}
public class MatserStudent extends Student{
//私有化成员变量 不写 没有独有的
//构造方法
public MatserStudent(){}
public MatserStudent(String name,int age,String grade){
super(name,age,grade);
}
//get和set 不写 子类没有独有的属性
//重写方法
public void Study(){
System.out.println("硕士研究生的同学正在攻读硕士内容");
}
}
public class MajorTeacher extends Teacher{
//构造方法
public MajorTeacher(){}
public MajorTeacher(String name,int age,String subject){
super(name,age,subject);
}
//重写方法
public void teach(){
System.out.println("专业课老师正在教授专业课知识");
}
}
public class GeneraTeacher{
//构造方法
public GeneraTeacher(){}
public GeneraTeacher(String name,int age,String subject){
super(name,age,subject);
}
//重写方法
public void teach(){
System.out.println("通识课老师正在教授通识课知识");
}
}
权限修饰符

多态
什么是多态
概念:事物的多种形态
表现形式:父类类型 对象名称=子类对象;Fu f=new Zi();
多态前提:有继承/实现关系;(必须)
有父类引用指向子类对象;(必须)
有方法重写(可选)
好处:方法形参写父类类型(Person per)则可以接收所有子类类型
如果子类重写了父类方法则可以根据传递的对象调用不同子类中重写的方法per.show()
package com.itheima.test1;
public class Test {
@Contract(pure = true)
public static void main(String[] args) {
/*
学生类:
属性: 姓名, 账号, 密码
行为: work: 学生的工作是学习
老师类:
属性: 姓名, 账号, 密码
行为: work: 老师的工作是教书
管理员:
属性: 姓名, 账号, 密码
行为: work: 管理员的工作是管理网站
学生管理系统:
行为: 注册register
要求: 能注册所有的角色
输出: 姓名为张三的同学注册成功, 账号zhangsan, 密码123456
*/
Student stu=new Student("张三","zhangsan","123456");
StudentManger sm=new StudentManger();
sm.register(stu);
Teacher t=new Teacher("李四","lisi","12345678");
sm.register(t);
}
}
public class Person{
//属性
private String name;
private String username;
private String password;
//行为
public void word(){
System.out.println("工作");
}
//构造器
public Person(){}
public Person(String name,String username,String password){
this.name=name;
this.username=username;
this.password=password;
}
//get和set
public void setName(String name){
this.name=name
}
public String getName(){
return name;
}
public void setUsername(String username){
this.username=username
}
public String getUsername(){
return username;
}
public void setpassword(String password){
this.password=password
}
public String getpassword(){
return password;
}
}
public class Student extends Person{
public Student() {
}
public Student(String name, String username, String password) {
super(name, username, password);
}
//重写方法
@Override
public void work() {
System.out.println("学生的工作是学习~");
}
}
package com.itheima.test1;
public class Teacher extends Person{
public Teacher(){}
public Teacher(String name, String username, String password){
super(name, username, password);
}
//重写方法
@Override
public void work(){
System.out.println("老师的工作是教书~");
}
}
package com.itheima.test1;
public class Admin extends Person{
public Admin() {}
public Admin(String name, String username, String password) {
super(name, username, password);
}
//重写方法
@Override
public void work() {
System.out.println("管理员的工作是管理网站");
}
}
public class StudentManger{
public void register(Person person){
System.out.println("姓名为"+person.getName()+"的账户注册成功,账号"+person.getUsername()+",密码"+person.getPassword())
}
//传递不同的对象有不同的方法
person.work();
}
多态调用成员的特点
变量调用:编译看左边,运行也看左边
Fu f = new Zi();
System.out.println(f.name);
编译此行看上一行左边,也就是父类里有没有name变量,没有就报错。运行此行时看上一行左边,使用父类中的该变量
方法调用:编译看左边,运行看右边
Fu f = new Zi();
f.ziShow() ;
编译此行时看上一行左边,也就是看父类里有没有ziShow方法,没有就报错。运行此行时看上一行的右边,也就是看子类有没有ziShow方法,有就调用,没有就用父类里的ziShow
弊端:多态下不能使用子类的独有功能,因为

解决多态弊端:类型转化(把对象再转回子类类型,从而实现调用子类独有的功能)
自动类型转换(从子到父):子类对象赋值给父类类型的变量
强制类型转换(从父到子):子类 对象变量=(子类)父类类型的变量
//向上转:创建一个子类的对象,但是把它当作父类来看待。
Person p = new Student();
//向下转:把父类转为子类
Student s=(Student)p;
// 注意点:
// 爷爷 爸爸 儿子
// 创建了爸爸的对象,赋值给了爷爷类型
// 在进行强制类型转换的时候,只能转到爸爸类型,不能转成儿子类型
//但是转换类型与真实对象类型不一致会报错例如:
Ye y = new Fu(); //真实对象类型为Fu
Zi ff = (Zi)y;
//此时会报错
Ye y = new Fu(); //真实对象类型为Fu
// 判断一下,y是不是父类类型的
if(y instanceof Fu){
Fu ff = (Fu)y;
}else{
System.out.println("请确定好类型,再进行转换");
}
//此时不会报错
抽象类和抽象方法
在学习继承的时候,我们知道子类的方法需要根据需求将父类方法重写,因此实际上父类的方法没有任何意义。可以使用抽象类方法,强制子类重写
抽象方法:将共性的行为(方法)抽取到父类之后。因为每一个子类的方法体是不一样的。所以,在父类中不能确定具体的方法体。该方法可以定义为抽象方法
public abstract 返回值类型 方法名(参数列表);
//不含方法体
抽象类:如果一个类中存在抽象方法,那么该类必须声明为抽象类
public abstract class 类名{}
alt+回车:自动修复
好处:让父类不再纠结方法体到底写什么
强制让子类必须要重写抽象方法,否则代码就会报错
注意事项:
- 抽象类不能示例化(不能创建该类对象)
- 抽象类里的抽象方法没有方法体,创建对象去调用没有方法体的抽象方法无意义
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 作用:不让外界创建本类的对象
- 抽象类中可以有构造方法
- 构造方法的作用:给成员变量赋值,而不是创建对象
- 抽象类的子类:要么重写抽象类的抽象方法,要么子类也是抽象类
接口
接口就是一个规则,而且是独立于继承体系以外的规则(可以理解为干爹,父类是亲爹)
格式:public interface 接口名(){}
使用:接口和类之间的是实现关系,通过implements关键字表示
public class 类名 implements 接口{}
public class 类名 extends 父类{}
注意点:
- 接口不能实例化
- 接口的子类(实现类),要么重写接口中所有的抽象方法,要么实现类是一个抽象类
- 一个类可以实现多个接口,也可以在继承一个类的同时实现多个接口
public class 类名 implements 接口1,接口2{}
public class 类名 extends 父类 implements 接口1,接口2{}
在 Java 的接口(interface)中,编译器有一条默认规则: 接口里的普通方法,默认就是 public abstract 的。
这意味着,下面这两种写法是完全等价的:
public interface English {
public abstract void speakEnglish();
}
public interface English {
void speakEnglish(); // 编译器会自动把它当成 public abstract
}
接口中成员特点
- 成员变量
- 只能是常量
- 默认修饰符:public static final
- 构造方法
- 没有
- 成员方法
接口新特性
JDK7及以前:
接口只能定义抽象方法。其接的类必须重写抽象方法
JDK8:
为了接口升级时向下兼容,接口中可以定义有方法体的方法(默认、静态)
- 允许接口中定义默认方法,需要使用关键字default修饰
- 作用:为了接口升级时,代码不报错(因为增加了接口内方法,其所接的类也必须重写这个方法,否则报错)
- 定义格式:public default 返回值类型 方法名(参数列表){}
- 注意事项:
- 默认方法不是抽象方法,不强制被重写。但是如果被重写,重写的时候去掉default关键字
- public可省略,但default不能省略
- 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
- 允许在接口中定义静态方法,需要用static修饰
- 定义格式:public static 返回值类型 方法名(参数列表){}
- 注意事项:
- 静态方法只能通过接口名调用,不能通过实现类名或对象名调用
- public可以省略,static不能省略
JDK9:
为了抽取重复代码,接口中可以定义私有方法(私有普通方法、私有静态方法)
- 普通私有方法
- 格式1:
private返回值类型 方法名(参数列表) { } - 范例1:
private void show() { }
- 格式1:
- 静态私有方法
- 格式2:
private static返回值类型 方法名(参数列表) { } - 范例2:
private static void method() { }
- 格式2:
疑惑
关于构造器
Java里的构造器用来给参数初始化,可是set/get方法也可以,那构造器是否有点多余?
- 构造器:负责“生”。保证对象一出生就是合法的、有初始数据的。
- Set 方法:负责“变”。用于对象创建之后,根据业务需求修改数据(比如小狗长大了,年龄从 1 岁变成 2 岁)。
所以,它们不是谁替代谁,而是分工合作。通常我们写代码是“构造器初始化 + Set 方法修改”搭配使用的。
c与Java的不同
C 语言是面向过程的,只有函数;而 Java 是面向对象的,一切皆类。
Java字符串用String类型,而不是c语言的char类型
c语言的bool类型在java里是boolean,且bool返回值为0或1,boolean返回值为ture或false
Java方法里的局部变量可以直接赋值给成员变量,但c语言函数是单独的模块,只能通过指针传地址来改该地址的值,不能直接赋值给函数外的变量
Java里字符串可以通过+号直接拼接,数字与字符串混合输出也是通过+号拼接,但c不行,必须格式化(“%d %s”,参数,参数)
其他
一般方法是public static +返回值类型+方法名(){},但写在Javabean类里的方法是不加static的
1、枚举类不能在外界创建对象,这里第十三行难度就不是在创建对象了吗



进阶篇
集合进阶
Collection单列集合
一次添加一个数据
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的
List系列
添加的元素是有序、可重复、有索引的
ArrayList、LinkedList、Vector
Set系列
添加的元素是无序、不重复、无索引
HashSet、TreeSet
Map双列集合
一次添加一对数据(如商品+价格)
异常
体系介绍


语法错误——编译异常
数组越界、参数错误——运行异常
异常的作用
- 用来查询bug的关键参考信息
- 异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况
异常的处理
1、JVM默认处理
- 把报错信息打印在控制台并停止运行
2、自己处理(捕获异常)
- 格式
try{
可能出现异常的代码
}catch(异常类名 变量名){
异常的处理代码
}
- 目的:当代码出现异常时,可以让程序继续往下执行
- 好处:可以让程序继续下去,不会停止
int[] arr = {1, 2, 3, 4, 5, 6};
try{
//可能出现异常的代码;
System.out.println(arr[10]);//此处出现了异常,程序就会在这里创建一个ArrayIndexOutOfBoundsException对象
//new ArrayIndexOutOfBoundsException();
//拿着这个对象到catch的小括号中对比,看括号中的变量是否可以接收这个对象
//如果能被接收,就表示该异常就被捕获(抓住),执行catch里面对应的代码
//当catch里面所有的代码执行完毕,继续执行try...catch体系下面的其他代码
}catch(ArrayIndexOutOfBoundsException e){
//如果出现了ArrayIndexOutOfBoundsException异常,我该如何处理
System.out.println("索引越界了");
}
System.out.println("看看我执行了吗?");
捕获异常注意事项


异常的常见方法
抛出异常
throws
写在方法定义处,表示声明一个异常,告诉调用者本方法可能会出现哪些异常
public void 方法名()throws 异常类名1,异常类名2...{
.......
}
tips:编译时异常必须要写,运行时异常可以不写
throw
写在方法内,结束方法。手动抛出异常对象,交给调用者,方法中下面的代码不再执行
public void 方法名(){
throw new NullpointerException();
}
抛出和捕获
public class ExceptionDemo {
// 1. 【底层方法】:负责检查密码
// 注意:这里没有 try-catch,因为底层不负责“收拾残局”,只负责“发现问题并上报”
public static void checkPassword(String pwd) {
if (pwd == null || pwd.length() < 6) {
// 核心动作:new 出一个异常对象,并且 throw 扔出去!
// 括号里的 "密码太短..." 就是给 catch 准备的错误提示信息
throw new RuntimeException("密码太短,必须大于6位!");
}
System.out.println("密码校验通过!");
}
// 2. 【上层调用者】:负责调用底层方法,并准备好接住异常
public static void main(String[] args) {
System.out.println("=== 程序开始 ===");
try {
// 尝试调用可能出错的方法
checkPassword("123");
// 如果上面没报错,才会执行下面这句。
// 但因为我们传了 "123",上面一定会 throw,所以这行代码会被直接跳过!
System.out.println("准备保存密码到数据库...");
} catch (RuntimeException e) {
// 【捕获动作】:接到了上面扔过来的异常对象,并把它赋值给变量 'e'
// 此时,e 里面就包含了刚才括号里的信息:"密码太短,必须大于6位!"
System.out.println("哎呀,捕获到错误了!");
System.out.println("具体的错误信息是:" + e.getMessage());
}
System.out.println("=== 程序结束,没有崩溃 ===");
}
}
e它是通过“赋值”来的。
当你在上面写下 throw new RuntimeException("密码太短") 时,Java 在内存里创建了一个包含“密码太短”信息的异常对象,并把它扔了出去。
当程序执行到 catch (RuntimeException e) 时,Java 在底层其实偷偷帮你做了一个赋值操作:
// 伪代码:Java 底层的真实动作
e = 刚才那个被扔出来的 RuntimeException 对象;
用生活中的例子来理解:
throw new RuntimeException("密码太短"):你写了一张写着“密码太短”的纸条,并把它揉成团扔了出去。catch (RuntimeException e):相当于你拿了一个叫e的篮子,精准地接住了这个纸团。e.getMessage():打开这个叫e的篮子,拿出纸条,读出上面的字。
所以,e 不是天上掉下来的,它是 catch 这个“捕网”在接住异常对象时,顺手给它贴上的一个“临时标签”。
必须先抛出异常对象,才能作为参数传入到catch里
反面:只捕获,不抛出异常,此时没有throw异常对象,出错就不会执行catch,依旧往下执行,输出奖金发放成功
// 底层发钱方法
public void sendMoney(int amount) {
try {
if (amount < 0) {
System.out.println("金额不能为负数!"); // 自己消化了
}
// 因为没有 throw,程序继续往下走,把负数金额发出去了!
System.out.println("奖金发放成功!");
} catch (Exception e) {
// ...
}
}
正确方法
// 底层发钱方法
public void sendMoney(int amount) {
if (amount < 0) {
// 强行制造异常,打断程序,把问题扔给上层!
throw new RuntimeException("金额不能为负数!");
}
// 如果上面抛出了异常,这行代码绝对不会被执行!
System.out.println("奖金发放成功!");
}
// 上层调用者
public static void main(String[] args) {
try {
sendMoney(-100);
} catch (RuntimeException e) {
// 上层终于知道了出错了,可以做出正确的业务反应
System.out.println("发钱失败,原因:" + e.getMessage());
}
}
那为什么之前学捕获的时候,都没有throw异常对象还能进入到catch?
答:看下面第二点。系统级错误,JVM会自动帮我们抛出
抛出(Throw)与捕获(Catch)的关系
- Throw(抛出):是“制造并扔出”异常对象的动作。它是异常处理的源头(因)。
- Catch(捕获):是“接收并处理”异常对象的机制。它是异常处理的终点(果)。
- 核心逻辑:Catch 只能接住已经飞在空中的异常对象。如果没有 Throw 抛出对象,Catch 就无事可做。
2. 为什么必须手动抛出(Throw)?
- JVM 的局限性:JVM 只能识别“系统级错误”(如除以0、数组越界),并自动帮你抛出。
- 业务逻辑错误:对于“业务级错误”(如密码太短、余额不足),JVM 无法识别。必须由程序员手动
throw new Exception("提示信息"),强行制造异常对象,才能触发 Catch 机制。
3. 异常信息的传递机制
- 制造信息:
throw new Exception("错误提示")括号里的字符串,就是异常对象携带的错误信息。 - 接收信息:
catch (Exception e)中的e是接收到的异常对象变量。 - 读取信息:调用
e.getMessage()即可提取出括号里的错误提示字符串。
4. 运行时异常 vs 编译时异常
- 运行时异常(RuntimeException):由逻辑错误引起,JVM 会自动抛出。编译器不强制要求处理,代码能正常编译。
- 编译时异常(Checked Exception):由外部环境引起(如读文件、连网络),JVM 不会自动抛出。编译器强制要求处理,必须在代码中显式编写
try-catch或throws,否则无法通过编译。
