一 - 九、基础语法
一、JAVA特点
- java是面向对象的
- java语言健壮,java的强类型机制,异常处理,垃圾自动回收机制等是java程序健壮的重要保证
- java是跨平台性的,一次编译(.class)多处(系统)运行(靠JVM)
- 是解释型语言,如javascript,php;编译性语言如c,C++\
- 解释性语言不能被机器直接执行,需要解释器;编译性语言,编译后的代码可被机器直接执行
二、java概述
2.1 jdk
- java核心机制-JVM(java virtual machine)
- jdk8和11用的广泛
- JDK全称(Java Development Kit Java开发工具包)
- JDK=JRE+JAVA开发工具集
- JRE=JVM+JAVA SE标准类库
2.2 安装与配置
- 去 JAVA官网 下载对应系统版本,直接下一步下一步即可。JRE也推荐安装。
- 注意:路径不要有英文,空格
- 配环境变量path
- JAVA_HOEM:安装路径
- path:增加
%JAVA_HOME%\bin
2.3 java快速入门
第一个程序
//public 类
public class Hello {
// main 程序入口
public static void main(String[] args) {
//打印语句
System.out.println("Hello,world!");
}
}
注意:
- java命令运行的是类,不是字节码文件
2.4 学习方法
- 产生需求
- 用现有技术能否解决
- 引出新技术和知识点
- 学习新技术的基本原理和基本语法(先不考虑细节)
- 使用(crud)
- 开始研究技术的注意事项,使用细节,规范,如何优化(无止境的)
2.5 转义
//转义字符
// \n 换行
// \t 制表符
// \" 引号
// \'
// \r 回车 后面的字替换最前面的几个字
一句话输入如下结果
System.out.println("书名\t作者\t价格\t销量\n三国\t罗贯中\t120\t1000");
2.6 常见错误
- 语法错误 未加{} ;等
- 拼写错误 main,void
- 业务错误,环境错误等
2.7 注释
方便自己阅读,也方便别人。
// 单行注释
/* */ 多行注释
注意:不可多行注释嵌套多行注释
文档注释:
/**
* @author Carl
* @version 1.0
*/
2.8 DOS
- 相对路径:从本目录出发的路径,
- ..\上级目录,....\上上级目录
- 绝对路径:从根目录出发,如
C:\Program Files\Java\jdk1.8.0_60
2.9 代码规范
- 类,方法的注释,要以javadoc的方式来写。
- 非Java Doc的注释,常用以给维护者看,表明代码的原因,如何修改,注意什么问题等
- 选中+tab,整体右移;选中+shift+tab,整体左移
- = 号两边应空格
- 源文件用utf-8编码格式
- 行宽不应超过80个字符
- 代码编写的行尾风格和次行风格
三、变量
3.1 细节与注意事项
- 变量表示内存中的一个存储区域(不同的变量,类型不同,占空间大小不同)
- 该区域有自己的名字(变量名)和类型(数据类型)
- 该区域内的值可在一定范围内不断变化
- 变量一定要先声明后使用
- 变量三要素:变量名,数据类型,变量值。
- 在同一作用域内不能同名
3.1.1 遇到的问题
- 找不到或无法加载类 XXX
原因:环境变量配置了classpath,path变量配置了jre下的bin。
解决方案:删除即可。
3.1.2 程序中+号的使用
- 两边是数值类型,做加法运算
- 两边其中一边是字符类型,做拼接运算
//加法运算
System.out.println(999 + 1);
//字符拼接
System.out.println("999" + 1);//9991
System.out.println("999" + 1 + 2);//99912
System.out.println(1 + 2 + "999");//3999
3.2 数据类型
3.2.1 分类
- 8大基本数据类型:
- 整数型:byte(1字节),short(2),int(4),long(8)
- 浮点数:float(4),double(8)
- 字符:char(2)
- 布尔类型:boolean(4)
- 3大引用类型:
- class类
- interface接口
- []数组
3.3 整型细节
- 整数类型在内存中占指定区域,不受系统变化影响;
- 整数默认是int类型,声明long类型需要在整数后面加l或L;
- 整数一般都用int,除非表示范围不足才用long;
- bit是计算机中最小单位,byte是计算机中基本单位。1byte = 8bit;
3.3.1 API 文档
即 API 规范,通过 API在线中文手册查看
组成:
- 包 package
- 类 class
- 字段 field
- 方法 method
- 构造器 struct
- 接口 interfaces
- 异常 exceptions
- 类 class
3.4 浮点数细节
- 在内存中占固定区域,不受系统变化影响
- 默认浮点数是 double 类型,若要用float,需要在值后面加小写f
- 两种表现形式
- 十进制 5.5,5.5f,.5
- 科学计数法 5.5e2(5.5*102),5.5E-2(5.5/102)
- 通常情况下用double,它比float更精准
- 浮点数表示近似的值,所以2.7 != 8.1/3;所以判断两个小数是否相等应该用差值是否小于某个范围来判断
3.5 字符细节
- char 表示一个字符;
- 在内存里用码值来存放,根据 Unicode 码表,每一个字母对应一个整数
- 将整数赋值给char类型变量,输出时,先根据 Unicode 码表找到对应字母再输出(97 -》a)
- char 类型变量还可以用来计算,计算时 char 值代表 Unicode 表中对应整数
- 还可以用来存放转义字符,
char a = '\n'
3.5.1 字符编码
- ASCII 码 一个字节表示一个字符,一共规定了 128 个字符。一个字节最多可以包含 256 个字符。 在线ASCII码表,不能表示中文
- Unicode 码,两个字节表示一个字符,中英文都是两个字节。Unicode 码兼容ASCII码,编码 0-127 字符的码值和 ASCII 码是一致的
- UTF-8,一个字节表示一个英文字符,三个字节表示一个中文。
3.6 布尔值细节
- boolean只有true和false两个值。
- 占一个字节,一般用于流程控制语句
3.7 数据类型转换
3.7.1 自动类型转换
char -> int -> long -> float -> double
byte - > short ->int -> long -> float -> double
细节
- 不同类型计算时,数据的数据类型会全部转换成精度(容量)最大的数据类型
- char byte short 不能自动转换
- char byte short 计算时先转换成int
- 给byte赋值时,先判断值是否在[-128,127]区间内,否就是int类型
- 数量类型提升的原则:计算结果转换成操作数中精度最大的数据类型
- boolean不参与自动类型转换
3.7.2 强制类型转换
int num1 = (int) 3.3;//double -> int
- 强制转换只针对就近的值生效,可用()来提升优先级
- 强制类型转换用来将精度大的转换成精度小的,但是可能会发生精度损失
- char类型可以保存int型整数,但是不能保存int型变量,需要强制转换
3.7.3 基本数据类型和String类型的转换
基本数据类型转 String
:在变量或数值后面加 +"";
String
转基本数据类型:用包装类的 parseXXX
方法
细节:
String
转换成char
通过方法charAt(index)
截取字符串的第index个字母String
转换成基本数据类型要有意义,否则会抛异常:java.lang.NumberFormatException
//String转基本数据类型
int num1 = Byte.parseByte(str1);
int num2 = Short.parseShort(str2);
int num3 = Integer.parseInt(str3);
int num4 = Long.parseLong(str4);
int num5 = Float.parseFloat(str5);
int num6 = Double.parseDouble(str6);
int num7 = Boolean.parseBoolean(str7);
//String转char
char char1 = str1.charAt(0);
四、运算符
4.1 算术运算符 AirthmeticOperator
- /号,运算时应该考虑数据类型,java里两个int值相除结果也是取整
double num1 = 6/4.0;//为保留小数应该加 .0
- %取模,公式是 a % b = a - a / b * b,当a是int,公式为 a % b = a - (int)a / b * b;
- ++自增、--自减:
- ++a 和 a++:独立使用时结果一样,都表示 a = a + 1;
- 运算时++a表示先自增后赋值,a++ 表示先赋值再自增;
//注意:
int a = 1;
a = a++;//实际运算时用临时变量,temp = a; a= a + 1; a = temp;
a = ++a;// ++在前先计算,从右到左: a = a + 1; temp = a; a = temp;
4.2 关系运算符 RelationalOperator
- ==,!=,>,<,>=,<=,instenceof(判断对象是否是类的实例化,待面向对象学习)
- 关系表达式返回的结果是boolean类型值
4.3 逻辑运算符 LogicOperator
- && 短路与,|| 短路或,!取反;& 逻辑与,| 逻辑或,^逻辑异或;
- 逻辑运算符操作数是boolean类型,结果为boolean值,常用在if,while,for等做条件
- && 与 &的区别:
- 两者都表示两边有一个为false则结果为false
- &&左边为false就不执行右边,&左边为false也会执行右边,右边为false则结果为false
- 一般用&& 提高效率
- || 与 | 的区别:
- 两者都表示两边有一个为true则结果为true
- ||左边为true则不执行右边,|左边为true也会执行右边,右边为true则结果为true
- 一般用||提高效率
- !与^:
- !也叫做非
- ^ 或,表示两边值不一样则结果为true,两边值一样则结果为false
- 注意:
int x = 1;
if(x++ == 1) x++;//x = 3,++在后,先比较,再自增
4.4 赋值运算符 AssignOperator
- 赋值运算符 = ,复合赋值运算符 +=,-=,*=,/=,%=
a += b
等价于a = a + b
- 赋值运算符从右往左运算
- 左边只能是变量,右边可以是变量,表达式,常量值
- 复合赋值运算符计算时会类型转换
4.5 三元运算符 TernaryOperator
- 表达式 ? 值1 : 值2; 等价于
if (表达式) 值1; else 值2;
- 三元运算符接收的变量类型要兼容运算结果的数据类型,或者强制转换
- 一般不使用三元运算符进行嵌套
4.6 运算符优先级
- 不要背 不要背 不要背
- 只有单目运算符,赋值运算符是从右往左运算的
4.7 标识符的命名规则和规范
4.7.1 规则
- 由英文,数字,_和$组成,区分大小写,不能含空格
- 数字不能开头
- 不可含关键字,保留字
4.7.2 规范
- 包名:每个单词都小写
- 类名,接口名:多个单词每个单词首字母大写(大驼峰)
- 变量名,方法名:多个单词第一个单词首字母小写,其余每个单词首字母都大写(小驼峰)
- 常量名:单词全部大写
4.8 关键字
- java里有专门用途的单词,IDE里会变色
4.9 保留字
- 以后可能 java 会有专门用途的关键字,byValue,cast,future,inner,operator,outer,rest,var,goto,const
4.10 键盘输入
调用 Java.util里的Scanner类。
//1.导包
import java.util.Scanner;
public class Input {
public static void main(String[] args) {
//2.创建对象
Scanner sca = new Scanner(System.in);
//3.创建变量接受用户输入内容
int imputNum = sca.nextInt();
double imputDouNum = sca.nextDouble();
String imputStr = sca.next();
}
}
4.11 进制转换(基本功)
4.11.1 进制
- 二进制 binary :0b 开头,逢2进1
- 八进制 octonary :0开头,逢8进1
- 十进制 decimal :逢10进1
- 十六进制 hexadecimal :0x开头,逢16进1,a = 10,b = 11,c = 12,d = 13,e = 14,f = 15
4.11.2 进制转换
- 十进制转二进制,八进制,十六进制:十进制数除以对应进制,直到商为0,将每次得到的余数倒过来就是对应进制数
- 二进制,八进制,十六进制转十进制:从最右边起,每个数乘以对应进制的位数-1次方,结果累加就是对应十进制数
- 二进制转八进制:从右边起,每三位一组,转换成十进制数就是一个对应八进制数
- 二进制转十六进制:从右边起,每四位一组,转化成十进制数就是一个对应十六进制数
- 八进制转十六进制:从右边起,每一位就是三位二进制数
- 十六进制转二进制:从右边起,每一位就是四位二进制数
4.12 原码反码补码
对于有符号的数而言:
- (基本概念)二进制的数都是有符号的,0表正,1表负(0横过来还是0,1横过来就是-)
- (正负数0的区别)
- 正数的原码反码补码都一样
- 负数的反码:原码的基础上符号位不变,其他位取反(0变1,1变0)
- 负数的补码:反码的基础上+1,负数的反码,补码的基础上-1
- 0的补码和反码都是0
- (计算机中的使用)
- java里的所有数都是有符合的
- 计算机计算时以补码的方式运算,补码能解决数字的符合问题
- 看运算结果时看原码
4.13 位运算符
4.13.1 & | ^ ~
- & : 按位与,两个都是1则结果是1,否则为0
- |:按位或,两个有一个是1则结果是1,都没有则为0
- ^ : 按位异或,两个相同则为1,相异则为0
- ~:按位取反,0-->1,1-->0
-
: 算术右移,符号位不变,低位溢出,高位用符号位代替
1 >> 2 --> 00000001 -> 00000000 相当于1 / 2 / 2 = 0
- <<:算术左移,符号位不变,高位溢出,低位用0代替
1 << 2 --> 00000001 -> 00000100 相当于1 * 2 * 2 = 4
-
:逻辑右移,无符号右移。低位溢出,高位用0代替
五、程序控制结构
5.1 顺序结构
java里的代码按从上到下的顺序执行
5.2 条件语句
5.2.1 if-else 语句
基本语法:
if (条件)
\\
else
\\
要点:
- if和else后面只有一条语句的时候可以不加{}
5.2.2 if-else-if 语句
基本语法:
if (条件1) {
……
} else if (条件2) {
……
} …… {
} else {
……
}
要点:
- else非必填项
- else if后面只有一条语句可不写{}
5.2.3 if 嵌套
基本语法:
if (条件1) {
if (条件2) {
//if-else
} else {
//if-else
}
}else {
……
}
要点:
- 不建议超过三层,可读性不强
5.3 switch 分支
基本语法:
switch (表达式) {
case 常量1:
//语句
break;
case 常量2:
//语句
break;
...
default :
//语句
break;
}
要点:
- 表达式结果的数据类型要和 case 后面的常量类型一致或能被常量的数据类型兼容
- 表达式结果仅支持 byte,short,int,char,enum(枚举),String
- case 里必须是常量,不能是变量
- default 是可选项,表示所有case都不符合,进入default分支
- break 表示结束 case 分支跳出 switch 代码块,不写则往下继续执行 case,直到遇到 break(case 穿透)
switch 和 if 比较
- switch 表达式里根的是有准确的值,if 表达式里跟boolean类型的值
- 当判断的值准确,数量不多,数据类型符合的时候用 switch 表示,范围性的或范围较大的用 if ,表示的范围更广。
5.4 for 循环
基本语法:
for (初始化循环变量; 条件; 迭代循环变量) {
//
}
要点:
- 初始化循环变量和迭代循环变量可以写在其他地方,但是两边的
;
不能去掉 - 初始化循环变量和迭代循环变量可以一次多个,中间用
,
隔开 - 如果只有一条循环语句,可以省略{},但不建议
编程思想:化繁为简(拆解需求),先死后活(通用)
5.5 while 循环
基本语法:
//初始化循环变量
while (循环条件) {
//语句块
//循环变量迭代
}
5.6 do-while 循环
基本语法:
//初始化循环变量
do {
//代码块
//循环变量迭代
} while (循环条件);
do-while 和 while 区别:
- 判断循环条件的位置不一样,do-while 是先执行后判断,所以至少执行一次;
5.7 面试题
重点理解:化繁为简,先死后活
5.7.1 打印空心金字塔
import java.util.Scanner;
public class Star02 {
public static void main(String[] args) {
//打印空心金字塔
// *
// * *
// * *
// *******
//1.一半 : i层,每行j个,j <= i
//2.完整的金字塔:i层,每层j个,j <= 2 * i - 1
//3.前面的空格:每层打印 总层数 - i个
//4.空心:除了两边和最后一行是*,其他都是空格
//先死后活:层数变为控制台可输入
Scanner sc = new Scanner(System.in);
System.out.println("要打印几层?");
int totalLevel = sc.nextInt();
for (int i = 1; i <= totalLevel; i++) {
for (int j = 1; j <= totalLevel - i; j++) //空格
System.out.print(" ");
for (int j = 1; j <= 2 * i - 1; j++) {
if (j == 1 || j == 2 * i - 1 || i == totalLevel) //行头和行尾打印实心
System.out.print("*");
else
System.out.print(" ");//其他打印空格
}
System.out.println();
}
}
}
5.7.2 打印空心菱形
import java.util.Scanner;
public class Rhombus {
public static void main(String[] args) {
//打印菱形
// *
// * *
// * *
// * *
// *
//1.打印一半倒三角
//2.打印完整倒三角 amount 行, i从amount开始递减, 每行 2 * i - 1
//2.1 每行前面 amount - i个空格
//3.打印空心倒三角 每行行头,行尾,和第totalLevel行打印*,其他打印空格
//4.打印上部分三角 totalLevel行,i从1递增 每行2 * i - 1个,中间用空心代替
//5.拼一起 totalLevel行,中间行就是(totalLevel + 1) / 2,
//5.1
//先打印上部分:小于等于中间行打印正三角(totalLevel + 1) / 2行,中间用空心代替
//再打印下部分:大于打印倒三角totalLevel - (totalLevel + 1) / 2
Scanner sc = new Scanner(System.in);
System.out.print("要打印几层?");
int totalLevel = sc.nextInt();
int helfLevel = (totalLevel + 1) / 2;
//先打印上部分
for (int i = 1; i <= helfLevel; i++) { //从第一行打印到中间行
for (int j = 1; j <= helfLevel - i; j++)
System.out.print(" ");
for (int j = 1; j <= 2 * i - 1; j++) {
if (j == 1 || j == 2 * i - 1) {
System.out.print("*");
}else{
System.out.print(" ");
}
}
System.out.println("");
}
//再打印下部分
for (int i = helfLevel - 1; i >= 1; i--) { //第中间行 - 1开始,打印最多,往下递减,i--
for (int j = 1; j <= helfLevel - i; j++)
System.out.print(" ");
for (int j = 1; j <= 2 * i - 1; j++) {
if (j == 1 || j == 2 * i - 1) {
System.out.print("*");
}else{
System.out.print(" ");
}
}
System.out.println("");
}
}
}
5.8 跳转控制语句-break的使用
基本语法:
//随机生成 1-100 的一个数,直到生成了 97 这个数,看看你一共用了几次?
//提示使用 (int)(Math.random() * 100) + 1
//思路:死循环,当num = 97则break
int num = 0;
for (int i = 1; 1 == 1 ; i++) {
num = (int)(Math.random() * 100 + 1);//用变量保存随机数
System.out.println("i = " + i + ", num = " + num);
if (num == 97) {
break;
}
}
要点:
- 可以配合标签跳出指定语句块
- lable的名字是自定义的
- lable:后面跟的哪个语句体,就代表哪个语句块
- 实际开发过程中不建议使用标签
- 无标签则跳出最近的循环体
**补充:比较字符串的方法 **** str1.equals(str2)**
5.9 跳转控制语句-continue
基本语法:
public class ContinueDetail {
public static void main(String[] args) {
label1:
for(int j = 0; j < 4; j++){
label2:
for(int i = 0; i < 10; i++){
if(i == 2){
//看各个结果
//continue ; //0,1,3,....9,0,1,3...9...
continue label2; //0,1,3,....9,0,1,3...9...
//continue label1; //0,1...0,1....
}
System.out.println("j = " + j + "i = " + i);
}
}
}
}
要点:
- continue指跳过当前层循环,继续执行下一层
- 配合标签使用跳过指定层循环
5.10 跳转控制语句-return
要点:
- 配合方法使用,跳出方法
- return放在main方法里,直接退出程序
六、数组,排序和查找
6.1数组的引入
快速入门:
//一个养鸡场有 6 只鸡,它们的体重分别是 3kg,5kg,1kg,3.4kg,
//2kg,50kg 。请问这六只鸡的总体重是多少?平 均体重是多少?
//for循环遍历通过索引拿到每个元素,再进行累加,求平均值
double checkens [] = {3,5,1,3.4,2,50};
toltalWeight = 0;
for (int i = 0; i < checkens.length; i++) {
toltalWeight += checkens[i];
}
avgWeight = toltalWeight / 6;
System.out.println("6只鸡总体重 = " + toltalWeight + "kg, 平均体重 = " + avgWeight);
6.2 数组的使用
6.2.1 动态初始化1
语法:数据类型 数组名[] = 数据类型[大小];
6.2.2 动态初始化2
语法:
先声明:数据类型 数组名[];
或 数据类型[] 数组名;
再创建:数组名 = 数据类型[大小];
6.2.3 静态初始化1
语法:数据类型 数组名[] = {element1, element2.....};
6.2.4 静态初始化2
语法:数据类型[] 数组名 = new 数据类型[]{element1,element2......}
6.3 注意事项和要点
- 数组表示相同数据类型的一组数据,实现对这一组数据的统一管理
- 数组的数据类型可以是基本数据类型和引用数据类型的一种,但是不能混用
- 数组声明了没有使用有默认值
- byte 0, short 0,int 0, long 0, float 0.0, double 0.0, char \u0000, boolean false, String null;
- 数组使用的一般步骤:声明并开辟空间;填入元素;使用数组
- 数组的下标从0开始
- 下标只能在 [0,数组.length) 范围内,否则报数组越界异常
- 数组是引用数据类型,数组型数据是对象(Object)
6.4 数组赋值机制
- 基本数据类型的是直接赋值,变量代表具体的值,互不影响。
- 数组默认是引用传递,赋值的是地址。(内存图:重点难点)
6.5 数组扩容
//原始数组使用静态分配 int[] arr = {1,2,3}
//增加的元素 4,直接放在数组的最后 arr = {1,2,3,4}
//用户可以通过如下方法来决定是否继续添加,添加成功,是否继续?y/n
//思路:
// 循环do-while控制用户循环输入,
// 创建一个新数组arr2,大小arr.length + 1
// 循环遍历将arr的数存入arr2中,再将用户输入的数存到arr2的最后一个下标
// 再将arr2的地址拷贝给arr
// char key 接收用户输入的字符 while key == n 退出
import java.util.Scanner;
public class ArrayAdd {
public static void main(String[] args) {
//用户输入
Scanner sc = new Scanner(System.in);
int[] arr = {1,2,3};
//接收用户输入的字符
char key = '0';
do {
//创建一个新数组arr2,大小arr.length + 1
int[] arr2 = new int[arr.length + 1];
//循环遍历将arr的数存入arr2中
for (int i = 0; i < arr.length; i++) {
arr2[i] = arr[i];
}
//再将用户输入的数存到arr2的最后一个下标
System.out.println("请输入要添加的数 ");
arr2[arr2.length - 1] = sc.nextInt();
//将arr2的地址拷贝给arr
arr = arr2;
//接收用户输入的字符
System.out.println("是否继续添加? y/n");
key = sc.next().charAt(0);
}while(key == 'y');
System.out.println("已结束添加!");
//打印添加后的结果
System.out.println("========arr添加后的结果=========");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
6.6 排序的介绍
6.6.1 内部排序
将所有要排列的数据加载到内存中进行排序(交换式排序法、选择式排序法和插入式排序法)
6.6.2 外部排序
数据量大的时候,无法全部存入内存中,借助外部储存进行排序的方法(合并排序法和直接合并排序法)
6.7 冒泡排序
- 概述:
指将数据排序比作冒泡,从元素下标最大的数开始,依次比较相邻的值,逆序则交换位置,使大的数排在最后面
例:
//我们将五个无序:24,69,80,57,13 使用冒泡排序法将其排成一个从小到大的有序数列。
//
//思路:1.声明数组保存数据{24,69,80,57,13},声明临时变量temp用于交换
// 2.每层确定一个最小的数,总共arr.length-1层 外层循环 i 0-->arr.length-1
// 3.从后往前比较,倒序遍历数组,内层每次比较的次数减少一次,排出的最小号元素不用再排 内层循环:j arr.length-->i
// 4.相邻的数逆序则交换位置,用临时变量temp
// 5.排序完后打印数组
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {24,69,80,57,13};
int temp = 0;
//2.每层确定一个最小的数,总共arr.length-1层 , i 0-->arr.length-1
for (int i = 0; i < arr.length - 1; i++) {
//3.从后往前比较,倒序遍历数组,内层每次比较的次数减少一次,排出的最小号元素不用再排
//j arr.length-->i
for (int j = arr.length - 1; j > i; j--) {
//相邻的数逆序则交换位置
if (arr[j] < arr[j - 1]) {
temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
}
}
//5.排序完后打印数组
System.out.println("===========冒泡排序后的arr为===========");
for (int i = 0;i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
6.8 查找
6.8.1 顺序查找
//有一个数列:白眉鹰王、金毛狮王、紫衫龙王、青翼蝠王猜数游戏:从键盘中任意输入一个名称,判断数列中是否
//包含此名称【顺序查找】 要求: 如果找到了,就提示找到,并给出下标值
//思路:1. 声明String数组{"白眉鹰王","金毛狮王","紫衫龙王","青翼蝠王"}
// 2.定义变量保存用户输入的字符,定义变量保存查询结果
// 3.遍历数组,将字符与数组的值对比,
// 4.相同则提示,打印下标,break
import java.util.Scanner;
public class SeqSearch {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String[] strs = {"白眉鹰王","金毛狮王","紫衫龙王","青翼蝠王"};
System.out.println("请输入要查找的字符串:");
String str = sc.next();
//定义标识保存查询结果
boolean flag = true;
//3.遍历数组,将字符与数组的值对比,
for (int i = 0; i < strs.length; i++) {
if (strs[i].equals(str)) {
System.out.println("找到了!,在数组里的下标为 " + i);
flag = false;
break;
}
}
if (flag) {
System.out.println("暂无数据!");
}
}
}
6.8.2 二分法(在算法内容讲解)
6.9 多维数组 - 二维数组
定义:二维数组可以理解为把一维数组的每个元素看成一个数组;
6.9.1 使用方式1-静态初始化
//声明
int[][] twoDemArra = {{0,0,0,0,0,0},{0,0,1,0,0,0},{0,2,0,3},{0,0,0}};
//打印
for (int i = 0; i < twoDemArra.length; i++) {
for (int j = 0; j < twoDemArra[i].length; j++) {
System.out.print(twoDemArra[i][j] + "\t");
}
//3.每循环一次外层换行一次
System.out.println();
}
6.9.2 使用方式2-动态初始化1
int[][] twoDemArra = new int[3][2]; //类似与一维数组的动态初始化
6.9.3 使用方式3-动态初始化2
//方式一、先声明,再开辟空间
int[][] twoDemArra ;
twoDemArra = new int[3][2];
6.9.4 使用方式4-动态初始化3
//1.创建列数不确定的二维数组
int[][] twoDemArra2 = new int[3][];
//int[][] twoDemArra2 = new int[3][];
//twoDemArra2 = {{1},{2,2},{3,3,3}};//错误写法
for (int i = 0; i < twoDemArra2.length; i++) {
//2.分别给每个一维数组动态开辟不同空间
twoDemArra2[i] = new int[i + 1];
//3.分别填值
for (int j = 0; j < twoDemArra2[i].length; j++) {
twoDemArra2[i][j] = i + 1;
}
}
6.9.5 杨辉三角
// 使用二维数组打印一个10行杨辉三角
//
// 【提示】
// 1.第一行有1个元素,第n行有n个元素
// 2.每一行的第一个元素和最后一个元素都是1
// 3.从第三行开始,对于非第一个元素和最后一个元素的元素的值.arr[i][j]
// arr[i][j]=arr[i-1][j]+arr[i-1][j-1];
//
// 思路:1.每行列数不一样,共10行-->定义二维数组 int[][] arr = new arr[10][];
// 2.遍历二维数组,给一维数组开辟空间,每层列数为i + 1
// 3.往数组里填值,j < arr[i].length
// 4.判断 当 j == 0 || j == i 则 arr[i][j] = 1
// 5.else : arr[i][j]=arr[i-1][j]+arr[i-1][j-1];
// 6.打印数组
public class YangHui {
public static void main(String[] args) {
// 1.每行列数不一样,共10行-->定义二维数组 int[][] arr = new arr[10][];
int[][] arr = new int[10][];
// 2.遍历二维数组,给一维数组开辟空间,每个大小为i + 1
for (int i = 0; i < arr.length; i++) {
arr[i] = new int[i + 1];
// for (int j = 0; j < i; j++) { //j 0 1 0 1 0 1
// //i 0 0 1 1 2
// arr[i] = new int[i + 1]; //arr[0] = new int[1]; arr[1] = new int[2]; arr[1] = new int[2]; arr2 = new int[3]
// }
// 这样会让32行报空指针异常,等用iDEA 打断电再找出原因
//3.往每个一维数组里填值
for (int j = 0; j < arr[i].length; j++) {
// 4.判断 当 j == 0 || j == i 则 arr[i][j] = 1
if (j == 0 || j == arr[i].length - 1 )
arr[i][j] = 1;
else
arr[i][j]=arr[i-1][j]+arr[i-1][j-1];
}
}
// 6.打印数组
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
}
}
6.9.6 二维数组注意事项
- 声明方式
- 一维数组的声明方式有:
int[] nums1
或int nums2[]
- 二维数组的声明方式为:
int[][] nums1
或int nums2[][]
或int[] nums3[]
- 一维数组的声明方式有:
- 大小:二维数组的每个一维数组的大小无影响,可以相同也可以不同(列数不等的二维数组)
int[][] nums = {{1,2,3},{1,3},{111},{11,25}};
6.10本章练习
已知有个升序的数组,要求插入一个元素,该数组顺序依然是升序,比如:[10,12,45,90],添加23后,数组为[10,12,23,45,90]
//方式一:
//思路:
// 方式1.不使用冒泡排序,只要从后往前遇到大数往后挪就行
// 2.1 声明一个新数组arr2,遍历原数组将元素拷贝给新数组
// 2.2 倒序遍历原数组,如果遇到比要替换的数大就将新数组的对应位置往后挪,比要替换的数小就插到新数组后一个位置
//先死后活 插入的数用变量表示
public class Homework04 {
public static void main(String[] args) {
// 2.1 声明一个新数组,大小arr.length + 1
int[] arr = {9,10,12,45,90};
int[] arr2 = new int[arr.length + 1];
//先死后活 插入的数用变量表示
int num = 8;
//先拷贝
for (int i = 0; i < arr.length; i++) {
arr2[i] = arr[i];
}
//再排序,如果遇到比要插入的数大,就把新数组的数往后挪,如果遇到比他小的,直接替换后一个数,结束。
//arr[i] > num;则 arr2[i + 1] = arr [i]; arr2[i] = num; 否则 arr2[i + 1] = num;break
for (int i = arr.length - 1; i >= 0; i--) {
if (arr[i] > num) {
arr2[i + 1] = arr [i];
arr2[i] = num;
}else{
arr2[i + 1] = num;
break;//
}
}
//将arr2的地址拷贝给arr
arr = arr2;
//打印数组
System.out.println("=======排列后的arr为=======");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
方式二:
//4.已知有个升序的数组,要求插入一个元素,该数组顺序依然是升序,比如:
//[10,12,45,90],添加23后,数组为[10,12,23,45,90]
//
//思路:
//1.先找到插入位置的索引 定位
// 遍历数组,找到arr[i] > 23 的数,i就是要插入的索引,如果遍历结束仍未找到,则插入到数组最后面
// 声明变量index保存索引,遍历结束index仍未改变,index = arr.length
//
//方式2.创建新数组 扩容
// 遍历新数组,如果i = index,那就将23插入到这个位置,否则就拷贝arr[i]
// 声明一个新数组,int[] arrNew = int [arr.length + 1];
// 遍历arrNew,如果i = index, arrNew[i] = 23,否则就拷贝arr[]
// 当23插入数组中,arr[]和arrNew[]的相同元素的下标就不一一对应,用j表示arr[]的下标,每次arrNew[i] = arr[j],j++
public class Homework0402 {
public static void main(String[] args) {
// 1.先找到插入位置的索引 定位
// 遍历数组,找到arr[i] > 23 的数,i就是要插入的索引,如果遍历结束仍未找到,则插入到数组最后面
// 声明变量index保存索引,遍历结束index仍未改变,index = arr.length
int[] arr = {10,12,45,90};
int index = -1;
for (int i = 0; i < arr.length; i++) {
if(arr[i] > 23) {
index = i;
break;
}
}
//遍历结束index仍未改变,index = arr.length
if (index == -1) {
index = arr.length;
}
//2.创建新数组 扩容
// 当23插入数组中,arr[]和arrNew[]的相同元素的下标就不一一对应,用j表示arr[]的下标,每次arrNew[i] = arr[j],j++
int[] arrNew = new int[arr.length + 1];
// 遍历新数组,如果i == index,那就将23插入到这个位置,否则就拷贝arr[]的数
// 用j表示arr[]的下标,每次arrNew[i] = arr[j],j++
for (int i = 0,j = 0; i < arrNew.length; i++) {
if (i == index){
arrNew[i] = 23;
}else{
// arrNew[i] = arr[i];//如果插入了新的数,两个数组的下标就不一一对应了
//引入变量j表示arr的下标,每次拷贝一次,j自增
arrNew[i] = arr[j];
j++;
}
}
//拷贝
arr = arrNew;
//打印
System.out.println("==========排序后的arr为=========");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
七、面向对象_基础
7.1 类与对象
7.1.1对象的引入
养猫猫问题:张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。还有一只叫小花,今年 100 岁,花色。
- 使用现有方法-数组解决
String[][] cat = {{"小白","3岁","白色"},{"小花","100岁","花色"}};
int index = -1;
System.out.println("请输入要找的小猫名字:");
String catName = sc.next();
// 拿cat[i][0] 跟用户输入的名字比较,相同则拿到这个下标
for (int i = 0; i < cat.length; i++) {
if (cat[i][0].equals(catName)) {
index = i;
}
}
现有技术的缺点:
- 数据类型无法体现
- 只能用下标,内容和变量名无法联系起来
- 不能描述行为
- 使用面向对象的方法:
//用面向对象的方法
//创建一个cat类
class Cat {
//有name,age,color三个属性
String name;
int age;
String color;
}
// 再实例化两个对象,分别给对象的属性赋值
Cat cat1 = new Cat();
Cat cat2 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
cat2.name = "小花";
cat2.age = 100;
cat2.color = "花色";
//拿到用户输入的名称进行比较
System.out.println("请输入要找的小猫名字:");
String catName = sc.next();
if (cat1.name.equals(catName)) {
System.out.println("猫的信息:" + cat1.name + ", " + cat1.age + ", " + cat1.color);
}else if (cat2.name.equals(catName)) {
System.out.println("猫的信息:" + cat2.name + ", " + cat2.age + ", " + cat2.color);
}else{
System.out.println("查无此猫!");
}
7.1.2 类与对象的关系
- 类是具有某种相同属性和行为的一类事物的抽象表达,是数据类型。
- 对象是类的实例化的表现形式,是一个个体。
7.1.3 类与对象在内存中的存在形式(画图)
7.1.4 属性/成员变量/字段filed
概念:
- 属性是类的组成部分,属性 = 成员变量 = 字段(filed)。
- 属性的数据类型一般是基本数据类型,也可以是引用数据类型。
要点
- 属性的定义方法和变量类似:
访问修饰符 数据类型 变量名;
- 访问修饰符控制属性的访问范围。由大到小:public, protected, 默认, private
- 属性的数据类型可以是基本数据类型,也可以是引用数据类型;
- 和变量一样,未赋值的属性有初始值;
- byte 0, short 0, int 0, long 0, float 0.0, double 0.0,char \u0000, boolean false, String null;
7.1.5 对象的创建
语法:
- 先声明
类 对象名;
再创建对象名 = new 类();
- 直接创建
类 对象名 = new 类();
- 注意:对象名 只是对象的名字,存的是地址,new 类() 才是真正的对象,指内存中对应的空间
7.1.6 如何访问属性
语法:对象名.属性
7.1.7 类和对象的内存分配机制
内存的组成:
- 栈:保存基本数据类型 (局部变量);
- 堆:保存对象(new Person(),数组等);
- 方法区:常量池(常量,字符串等), 类加载信息(属性, 方法);
java创建对象的流程:
- 先加载Person类信息(属性,方法)到方法区
- 先声明对象
Person p;
- 在堆中分配对象空间,初始化默认值
new Person();
- 将内存空间对应的地址传递给对象名
p = new Person()
- 进行制定初始化
p.name = "赵四"
7.2 成员方法
7.2.1 方法的引入
问题:请遍历一个数组 , 输出数组的各个元素值。
-
传统方法:使用单个for循环,遍历数组循环打印各个元素值
- 缺陷:
- 无法复用。重复使用又要写相同的代码
- 无法在其他类中使用
- 缺陷:
-
引入成员方法:定义一个MyTools类,定义一个printArra方法,传入数组,打印数组的元素
- 优点:
- 提高复用性能
- 可以在其他类使用
- 优点:
public class Method02 {
public static void main(String[] args) {
int[] nums = {1,2,3,4,11,2,};
//1.传统方法
// for (int i = 0; i < nums.length; i++) {
// System.out.println("nums的第 " + i + " 个元素是 " + nums[i]);
// }
//2.定义一个printArray方法,接收数组,打印数组的参数
//好处:
//1.提高代码复用性;
//2.将实现细节封装,方便其他类使用。
MyTools m = new MyTools();
m.printArray(nums);
System.out.println("===========");
m.printArray(nums);
System.out.println("===========");
m.printArray(nums);
System.out.println("===========");
}
}
class MyTools {
public void printArray(int[] array) {
for (int i = 0; i < array.length; i++) {
System.out.println("第 " + i + " 个元素是 " + array[i]);
}
}
}
7.2.2 方法在内存中的调用机制 示意图
7.2.3 方法的定义
语法:
访问修饰符 返回值类型 方法名(形参列表) {
方法体;
return 返回值;
}
- 形参列表:传给方法的值;
- 返回值类型:返回给调用处的值的类型。void表示无返回值
- 访问修饰符:控制方法的访问范围,和对象一致:public, protected, 默认,private
- return :非必填;
7.2.4 注意事项和使用细节
注意事项:
-
形参列表:
- 一个方法可以有0个或多个参数,中间用,号隔开
- 方法定义时的参数为形式参数,调用时传入的值为实际参数。
- 形参和实参的数据类型要一致或能兼容,顺序,长度要一致。
-
返回值类型:
- 一个方法只能有一个返回值,多个结果可以用数组
- 可以是任意数据类型,(如对象,数组等);
- 如果是void,不用写返回值或只写return。
-
方法名:驼峰命名法,做到见名知义
-
方法体:方法不能嵌套方法;
使用细节:
- 同一个类中不同方法间的调用,直接调用即可。
方法名();
- 不同类中的方法间的调用,需要声明对象,通过对象来调用。
7.3 成员方法传参机制
- 传的形参是基本数据类型,传递数值,不会对实参有影响。(可通过画内存图理解)
- 传的形参是引用数据类型,传递的值是地址,会对实参有影响
7.4 方法递归(recursion)调用
7.4.1 基本介绍
介绍:递归就是方法自己调用自己,能解决很多复杂性问题,使代码简洁。
7.4.2 能解决哪些问题
- 数学问题:8皇后,阶乘,迷宫等等。
- 算法中:快排,归并排序,二分查找,分治算法等。
- 用栈解决的问题,用递归方法可以使代码简洁。
7.4.3 举例
//阶乘 n! = 1 * 2 * 3 ... * (n - 1) * n;
// n! = (n - 1)! * n;
// 0! = 1;
//
public class Recursion01 {
public static void main(String[] args) {
Caculator c = new Caculator();
int n = 3;
int num = c.factorial(n);
if (num != -1) //不等于-1则说明有结果
System.out.println(n + "的阶乘 = " + num);
}
}
class Caculator {
//计算n的阶乘方法
public int factorial(int n) {
if (n < 0) { //不能为复数
System.out.println("n要大于0!");
return -1;
}else if(n == 0)
return 1;
else
return factorial(n - 1) * n; //n的阶乘 = (n-1)的阶乘 * n
}
}
7.4.5 递归重要规则
- 执行一个方法时,就创建一个新的独立受保护的栈空间。
- 每个空间的基本数据类型变量是独立互不影响的。
- 调用引用数据类型变量时,传递的是地址。各个栈空间该变量的值会同步。
- 递归必须逐渐返回到跳出递归的语句。否则就是无限递归,会报 StackOverflowError 堆栈溢出异常。
- 方法执行完毕或遇到return,就会将返回值传递给调用方法的位置,该方法执行完毕。
7.4.6 练习
- 斐波那契数列问题: 请使用递归的方式求出斐波那契数1,1,2,3,5,8,13.…给你一个整数n,求第n个数是多少
- 猴子吃桃:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时,想再吃时(即还没吃)发现只有1个桃子了。问题:最初共多少个桃子?
public class RecursionExercise01 {
public static void main(String[] args) {
Tools t = new Tools();
int n = 7;
int res = t.fibonacci(n);
if (res != -1)
System.out.println("斐波那契数列第 " + n + "个数 = " + res);
int num = t.peachNumber(1); //第n天的桃子数量
if (num != -1)
System.out.println("第1天的桃子数量是 " + num); //513
}
}
class Tools {
//返回值类型 int
//方法名 fibonacci
//形参 int n
//方法体
//
//斐波那契数列:从第三个数开始,每个数 = 前两个数之和
//如果n <= 2,返回1,否则调用fibonacci(n - 2) + fibonacci(n - 1)
//return n;
public int fibonacci(int n) {
//只能输入 n > 0的数
if (n <= 0) {
System.out.println("要求输入 n > 0 的数");
return -1;
}else if (n <= 2)
return 1;
else
return fibonacci(n - 2) + fibonacci(n - 1);
}
//猴子吃桃
//返回值类型: int
//方法名 :peachNumber
//形参:int day
//方法体:
//
//
//第10天 --- 1
//第9天 --- (第10天的桃 + 1) * 2
//第8天 ---- (第9天的桃 + 1) * 2
//.....
//天数不为0
public int peachNumber(int day) {
//天数只能是大于0的数
if (day <= 0) {
System.out.println("day在1-10之间");
return -1;
}else if (day == 10)
return 1;
else
return (peachNumber(day + 1) + 1) * 2; //前一天是后一天的个数 + 1 再 * 2
}
}
7.4.7 迷宫问题
用递归的方法走出迷宫
public class MiGong {
public static void main(String[] args) {
//1.先用数组将迷宫表示出来,1为障碍物,0为无障碍
// 一个8行7列的二维数组
// 第1行,8行,1列,7列,4行2列,4行3列为1,其余为0
int[][] map = new int[8][7];
//设置障碍物
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
if (i == 0 || j == 0 || i == map.length - 1 || j == map[i].length - 1)
map[i][j] = 1;
else
map[i][j] = 0;
}
}
map[3][1] = 1;
map[3][2] = 1;
//打印地图
System.out.println("=========地图如下=======");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
//调用对象走迷宫
Game g = new Game();
boolean res = g.findWay(map, 1, 1);
//走得通就打印地图,走不通就给出错误提示并打印地图
if (res) {
System.out.println("找到出口了!");
}else{
System.out.println("没有出口!");
}
//打印地图
System.out.println("======路线如下==========");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
}
class Game {
//思路:1.方法就是找路fingWay,形参:int[][] map, int i, int j(坐标)
// 2.找到正确的路返回true,否则返回false
// 2.map--要走的迷宫, i, j 走的坐标 初始化是(1, 1)
// 3.要用递归, 所以制定规则:0--无障碍 1--有障碍 2--走过,通路 3--走过,死路
// 4.定退出递归条件:当map[6][5] == 2则找到出口 return true;
// 5.策略:下--右--上--左
//
public boolean findWay(int[][] map, int i, int j) {
if (map[6][5] == 2) { //当找到map[6][5]则为true;
return true;
}else if (map[i][j] == 0){
//继续找
//假设走得通
map[i][j] = 2;
//验证
if (findWay(map, i + 1, j)) { //往下走走得通,就不变,返回true
return true;
}else if (findWay(map, i, j + 1)) { //往右走走得通,就不变,返回true
return true;
}else if (findWay(map, i - 1, j)) { //往上走走得通,就不变,返回true
return true;
}else if (findWay(map, i, j - 1)) { //往左走走得通,就不变,返回true
return true;
}else { //如果上下左右都走不通,就是死路
map[i][j] = 3;
return false;
}
}else {
//当该点有障碍--1, 走过的--2, 死路--3, 也返回
return false;
}
}
//2.用不同策略看路径的变化 上--下--左--右走
public boolean findWay02(int[][] map, int i, int j) {
if (map[6][5] == 2) {
return true;
}else if (map[i][j] == 0) {
//开始走
//先假定走得通
map[i][j] = 2;
//验证下一步的结果
//策略 上--下--左--右
if (findWay02(map, i + 1, j)) {
return true;
}else if (findWay02(map, i - 1, j)) {
return true;
}else if (findWay02(map, i, j - 1)) {
return true;
}else if (findWay02(map, i, j + 1)) {
return true;
}else {
//都走不通 就是死路
map[i][j] = 3;
return false;
}
}else { //碰到 1, 2, 3则返回
return false;
}
}
}
7.4.8 汉诺塔问题
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子, 在一根柱子上从下往上按照大小顺序摞着 64 片圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一 根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
假如每秒钟移动一次,共需多长时间呢?移完这些金片需要 5845.54 亿年以上,太阳系的预期寿命据说也就是数百亿年。 真的过了 5845.54 亿年,地球上的一切生命,连同梵塔、庙宇等,都早已经灰飞烟灭。
public class HanoiTower {
public static void main(String[] args) {
//调用对象移动汉诺塔
HanoiTowerTool ht = new HanoiTowerTool();
ht.move(3, 'A', 'B', 'C');
}
}
class HanoiTowerTool {
// 思路:
// 1.化繁为简 假如是两个盘 a,b,c三根柱子,则先a->b,a->c,b->c;
// 2.多个盘 也可以看成两个盘 先移动最上面的,再移动最下面的 一共有n个,最上面的就是n-1个
// 3.退出递归条件:当n = 1时,直接移动 a -> c
// 4.定义方法:
// 返回值 void
// 形参 int n(移动的个数) , char a, char b, char c (三个柱子 a,b,c)
public void move(int n, char a, char b, char c) {
// 3.退出递归条件:当n = 1时,直接移动 a -> c
if (n == 1 ) {
System.out.println(a + " -> " + c);
}else {
// 2.多个盘 也可以看成两个盘
// 先移动最上面的,将a - > b, 一共有n个,最上面的就是n-1个
move(n - 1, a, c, b);
// 再移动最下面的 a - c,最下面只有一个,所以直接打印即可
System.out.println(a + " -> " + c);
// move(1, a, b, c);
//再将刚才移动到b的盘移动到c
move(n - 1, b, a, c);
}
}
}
7.4.8 八皇后问题--待解决
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于 1848 年 提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同 一斜线上,问有多少种摆法
思路分析:
1)第一个皇后先放第一行第一列
2)第二个皇后放在第二行第一列、然后判断是否OK,如果不OK,继、续放在第二列、第三列、依次把所有列都放完,找到一个合适
3)继续第三个皇后,还是第一列、第二列.…….直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解
4)当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,全部得到.
5)然后回头继续第一个皇后放第二列,后面继续循环执行1,2,3,4的骤。
说明:理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题.arr={0,4,7,5,2,6,1,3}//对应arr下标表示第几行,即第几个皇后,arr[i]=val,val表示第i+1个皇后,放在第i+1行的第val+1列
7.5 方法重载Overload
7.5.1 介绍
方法重载即同一个类的两个方法同名,但形参列表不同 (长度,数据类型或数据类型的顺序);
class Person{
String name;
public void speak(Person p) {
System.out.println(p.name + "一个人在大声说话");
}
//1.一个类可以有多个同名方法,但方法的形参列表不同 长度或数据类型或数据类型的顺序
public void speak(Person p, String name) {
System.out.println(p.name + "和" + name + "在说悄悄话!");
}
public void speak(String name) {
System.out.println(name + "一个人在大声说话");
}
public void speak(String name, Person p) {
System.out.println(name + "和" + p.name + "在说悄悄话!");
}
}
7.5.2 注意事项和要点:
- 减少起名的麻烦
- 减少记名的麻烦
要点:
- 不受返回值类型影响
- 见介绍。
7.6 可变参数Variable Parameter
7.6.1 基本介绍
介绍:多个方法的功能,返回值,名称,参数类型都一致,除了参数长度不一致。可以通过可变参数来优化成一个方法。
格式:方法(int... nums);
- int... :多个int类型的参数
- nums :可以看成存放多个参数的数组
class HspMethod {
//求多个数的和
//int... :多个int类型参数
//nums 可看成数组
public int sum(int... nums) {
int sum = 0;
//遍历nums的元素,求和
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
}
7.6.2 注意事项和使用细节
- 实参可以传入同类型的数组
- 可变参数的本质就是数组
- 可变参数可以和普通参数一起放在形参列表,但必须放在最后面
- 一个形参列表最多有一个可变参数
7.7 变量的作用域 Varible Scope
7.7.1 基本介绍
- 变量分类:
- 全局变量:类的属性,在类中创建
- 局部变量:创建在方法或代码块中
- 作用域:
- 全局变量可以在类中使用
- 局部变量只能在创建的代码块中使用
- 初始值:
- 全局变量有默认值,可以不赋值直接使用,详见7.1.4
- 局部变量没有默认值,必须先赋值再使用
7.7.2 注意事项和区别
- 变量名:
- 全局变量可以和局部变量重名,使用时遵循就近原则
- 同一个作用域内的局部变量不能重名
- 访问修饰符:
- 全局变量有访问修饰符,局部变量没有
- 生命周期:
- 全局变量生命周期较长。全局变量保存在对象中,在堆里,随着对象的创建而创建,随着对象的销毁而销毁。
- 局部变量生命周期较短。局部变量保存在栈里,随着代码块执行而被创建,随着代码块结束而销毁。
public class VarScopeDetail {
public static void main(String[] args) {
Car c = new Car();
c.brand = "路虎";
c.price = 100;
c.driver = "王小虎";
c.run("兰博基尼", 200, c.driver);
// public int price = 100; //局部变量没有修饰符
// int price = 200; //局部变量不能重名
}
}
class Car {
public String brand;
double price;
String driver;
public void run(String brand, double price,String driver) { //brand是局部变量,可以和全局变量重名
System.out.println(driver + "开着一辆" + price + "万的" + brand + "在马路上跑!");//就近原则
}
}
7.8 构造器 Constructor
7.8.1 基本介绍
语法:访问修饰符 方法名(形参列表){ 方法体; }
介绍:
- 构造器是一类特殊的方法,是为了完成对新对象属性的初始化;
- 访问修饰符可以是默认,也可以是其他,和成员方法一致 public, protected, private
- 无返回值类型,void也不能写
- 方法名必须和类名一致
- 形参列表和成员方法的规则一致
- 构造器在创建新对象的时候系统自动调用,只是完成对象信息初始化,不创建对象
7.8.2 注意事项和使用细节
要点:
- 一个类可以有多个构造器,方法重载
- 不重写构造器,系统会自动生成一个默认的无参构造器 ,如
Dog(){}
- 重写了构造器,会覆盖默认构造器,无法使用,除非重写无参构造器;
public class ConstructorDetail {
public static void main(String[] args) {
Dog d1 = new Dog("小黄", "白色");
Dog d2 = new Dog();
System.out.println("姓名:" +d1.name + ",毛色:" +d1.color);
System.out.println("姓名:" +d2.name + ",毛色:" +d2.color);
}
}
class Dog {
String name;
int age;
String color;
char gender;
//1.可以有多个构造器,方法重载
public Dog(String dName, String dColor) { name = dName; color = dColor; }
public Dog(int dAge) { age = dAge; }
// public Dog(String dColor) { color = dColor; } //错误: 已在类 Dog中定义了构造器 Dog(String)
public Dog(char dGender) { gender = dGender; }
//2.不重写构造器,系统会定义一个无参构造器 比如 Dog(){}
//3.当重写构造器后,系统定义的无参构造器会被覆盖,无法使用,除非重写无参构造器
Dog() {}
}
7.9 对象创建的流程分析
Class Person { person
int age = 90;
String name;
Person(String n, int a){ //构造器初始化
name = n;
age = a;
}
}
Person p = new Person("小倩", 20);
流程分析:
- main方法执行到
new Person("小倩", 20);
时先加载Person类信息到方法区。 - 在堆中分配一个空间(地址)
- 开始对象初始化:
- 默认初始化:
age = 0; name = null
,null在常量池中,name只保存null的地址值。 - 显示初始化:
age = 90; name = null
- 构造器初始化:
name = "小倩"; age = 90
"小倩" 依旧存到常量池
- 默认初始化:
- 初始化完毕,将堆中对象的地址值传递给p。
7.10 this关键字
7.10.1 this关键字的引入
class Dog {
String name;
String color;
public Dog(String dName, String dColor) { //构造器的变量名得和属性不同,不够友好
name = dName;
color = dColor;
}
}
为了解决属性与局部变量重名问题 ---> this
7.10.2 this基本介绍
介绍:JVM
会给每个对象创建一个this
,代表该对象本身
7.10.3 this的要点和注意事项
要点:
this
关键字可以用来访问本类的属性、方法、构造器this
用于区分当前类的属性和局部变量- 访问成员方法的语法:
this.方法名(参数列表);
- 访问构造器语法:
this(参数列表);
,语句必须写在构造器中,且写在第一行 this
只能在定义类的内部使用,不可在类定义的外部使用
八、面向对象-中级
8.1 IDEA
8.1.1 安装
勾选64位,下一步下一步即可
破解路线见破解教程
8.1.2 设置
- 字体
菜单--> Settings
- 字符编码
IDEA的文件目录
8.1.3 常用快捷键
- 删除当前行, 默认是 ctrl + Y 自己配置
ctrl + shift + d
- 复制当前行, 自己配置
ctrl + d
- 补全代码
alt + /
- 添加注释和取消注释
ctrl + /
[第一次是添加注释,第二次是取消注释] - 导入该行需要的类 先配置
auto import
, 然后使用alt+enter
即可 - 快速格式化代码
ctrl + alt + L
- 快速运行程序 自己定义
alt + R
- 生成构造器等
alt + insert
[提高开发效率] - 查看一个类的层级关系
ctrl + H
**[学习继承后,非常有用] ** - 将光标放在一个方法上,输入
ctrl + B
, 可以定位到方法, 也可以光标 + 鼠标左键
**[学继承后,非常有用] ** - 自动的分配变量名 , 通过 在后面加
.var
[老师最喜欢的] - 还有很多其它的快捷键..
8.1.4 模板
在 Settings --> Editor --> Live Templates
查看模板/自定义模板,提高效率
8.2 包
8.2.1 包的引入
作用:
- 解决类名重复问题
- 便于类的管理
- 控制访问范围
8.2.2 基本语法
语法:package 包名
8.2.3 本质
包的本质就是通过文件夹来管理区分不同的类
8.2.4 包的命名
- 字母,数字,下划线和 . 组成,数字不能开头,不能是关键字和保留字
- 一般格式为小写字母和 .
com.公司名.项目名.模块名
如com.carl.crm.model
8.2.5 导包
语法: import 具体的包
import java.util.Scanner;
导入util包下的Scanner类import java.util.*
导入util包下所有类,一般用哪个类就导哪个类即可。
8.2.6 注意事项
- package的作用是声明当前类所在的包,需要放在第一行,一个类最多只有一条package语句
- import语句放在package下面,可以有多条语句,无顺序要求
8.3 访问修饰符
8.3.1 介绍
介绍:
public
公开的,对外公开protected
被保护的,只能在子类和同个包下的类访问默认
,不填,只能在同个包下访问private
私有的, 只能在同一个类访问
8.3.2 注意事项和细节
- 访问修饰符可以修饰类的属性,方法,类
- 类只能被
public
和默认修饰符
修饰 - 属性和方法的访问规则一致
- 关于子类的访问规则,见 · 继承
8.4 三大特性 - 封装encapsulation
8.4.1 基本介绍
- 面向对象的三大特性:封装、继承、多态
- 封装:把类的属性和方法通过访问修饰符保护起来,使得外部只能通过特定的方法操作类中的数据。
- 好处:
- 隐藏类的实现细节
- 在内部能对数据进行校验
8.4.2 一般步骤
//1.private(私有化) 属性;
//2.定义公开的(public) set方法,对数据进行设置和校验
public void setName(String name) {
//对数据进行校验
this.name = name;
}
//3.定义公开的(public) get方法,让外部获得对应数据
public void getName() {
//权限判断
return name;
}
8.4.3 构造器结合set方法
Person(String name, int age) {
//调用set方法对属性进行初始化和校验
set(this.name);
set(this.age);
}
8.5 面向对象 - 继承Extend
8.5.1 继承的介绍
两个类A, B有相同的属性和语法,可以创建一个父类C,提供通用属性和通用方法,让两个类继承C,提高代码的复用性
8.5.2 继承的语法
public class Father{...}
public class Son extends Father{} //子类继承父类 获得父类的所有属性和方法
- 父类又叫基类,超类
- 子类又叫派生类,
8.5.3 继承的好处
- 提取通用方法和属性,可以提高代码的复用性
- 子类可以写特有方法和属性,要修改共同属性只要修改父类即可。提高代码扩展性和可维护性
示意图
8.5.4 注意事项和细节
- 子类继承父类,获得父类的所有属性和方法。但是范围修饰符不同,子类的范围权限不同。
- 子类构造器必须借用父类构造器完成子类对象初始化 (先有父再有子)
- 不管调用的子类的哪种构造器,系统默认都会调用父类的无参构造器,如果父类无参构造器无法调用,就会报错。必须重写父类无参构造器或子类构造器中指定调用父类的构造器。
- 子类构造器指定调用父类构造器语法
super(参数列表);
super()
必须在构造器第一行,类似this()
,且必须写在构造器中super()
和this()
只能有一个存在- java中所有的类都继承
Object
类,定义类的时候JVM默认会补上extends Object
(一般不手动写) - 父类构造器初始化时候会先向上追溯,直到
Obect
类完成初始化,再往下执行 - 一个类只能最多继承一个父类,A要继承B和C,可以A继承B,B再继承C
- 定义继承关系的时候要满足
is a
的关系,不能滥用
8.5.5 继承的本质
内存图
本质
- 继承的本质即定义查找关系
package com.hspedu.extends_;
/**
* @author: Carl Zhang
* @create: 2021-11-10 16:26
*/
public class Extends01 {
public static void main(String[] args) {
Son son = new Son();
System.out.println("son.name = " + son.name); //大头儿子
System.out.println("son.hobby = " + son.hobby); //旅游
System.out.println("son.getAge() = " + son.getAge()); //40
// System.out.println("son.age = " + son.age); 找到父类的age被私有,报错,不再继续线上追溯
//1.继承的本质即定义查找关系
//2.首先看子类有没有该属性,子类有,就返回子类属性信息
//3.子类没有就找父类,父类该属性存在且可被访问,就返回
//4.父类没有就再继续向上查找,重复【3.】的规则,直到找到Object类为止
}
}
class GrandPa {
public String name = "大头爷爷";
public String hobby = "旅游";
public int age;
}
class Father extends GrandPa {
public String name = "小头爸爸";
private int age = 40;
public int getAge() {
return age;
}
}
class Son extends Father {
String name = "大头儿子";
// int age = 20;
}
8.5.6 练习
- 思考代码输出结果:
package com.hspedu.extends_;
/**
* @author: Carl Zhang
* @create: 2021-11-10 16:42
*/
public class ExtendsExercise01 {
public static void main(String[] args) {
B b = new B(); // a b name b
}
}
class A {
A() {
System.out.println("a"); //2
}
A(String name) {
System.out.println("a name");
}
}
class B extends A {
B() {
this("abc");//1 //1.有this() 默认的super() 不执行
System.out.println("b"); //4
}
B(String name) { //2.默认点调用父类无参构造 super()
System.out.println("b name"); //3
}
}
- 输出结果?(考点:super())
package com.hspedu.extends_.exercise02;
/**
* @author: Carl Zhang
* @create: 2021-11-10 16:57
*/
public class ExtendsExercise02 {
public static void main(String[] args) {
// main 方法中: C c =new C(); 输出什么内容? 3min
C c = new C(); //a类.. , hahaha b类有参, c类有参, c类无参
}
}
class A {//A 类
public A() { //step 5
System.out.println("我是 A 类");
}
}
class B extends A { //B 类,继承 A 类
public B() {
System.out.println("我是 B 类的无参构造");
}
public B(String name) { //step 4 隐藏了super()
System.out.println(name + "我是 B 类的有参构造"); //step 6
}
}
class C extends B { //C 类,继承 B 类
public C() {
this("hello"); //step 1
System.out.println("我是 c 类的无参构造"); //step 8
}
public C(String name) { //step 2
super("hahaha"); //step 3
System.out.println("我是 c 类的有参构造"); //step 7
}
}
8.6 super关键字
8.6.1 super关键字介绍
super
表示父类的引用,用于访问父类的属性,方法,构造器。
8.6.2 super基本语法
- 访问父类的属性
super.父类属性
,但不能访问父类私有的(private
)属性 - 访问父类的方法
super.父类方法
,但不能访问父类私有的(private
)方法 - 访问父类的构造器
super(参数列表)
,必须放在构造器中且在第一行
8.6.3 super好处和细节
super
调用父类构造器的好处:分工明确,父类属性由父类构造器完成初始化,子类属性由子类构造器完成初始化super
访问父类属性或方法时,如果子类和父类有重名属性或方法,则必须用super
访问父类属性或方法;如果没有重名的,super
和this
还有直接访问
父类属性或方法效果都一样。super
访问对象不仅限于直接父类,如果父类没有该元素,会一直向上追溯直到Object
类;- 如果爷类和子类有同名元素,也可以用
super
访问爷类的元素;如果子类,父类,爷类都有同名元素,则遵循就近原则查找:子类 --> 父类 --> 爷类。在有权限的情况下。
8.6.4 super 和 this比较
8.7 方法重写Overload
8.7.1 方法重写的介绍
子类方法和父类方法的名称,返回值类型,参数列表都一致。
8.7.2 注意事项和细节
- 子类重写方法的返回值类型要和父类一致或者是父类返回值类型的子类:父类方法返回值类型Object,子类方法返回值类型String
- 子类重写的方法访问范围不能小于父类 public > protected > 默认 > private
8.7.3 重写和重载的区别
8.8 面向对象-多态Polymorphic
8.8.1 多态的介绍
多态:继承中方法和对象的多种形态就是多态,多态的前提是封装和继承
好处:提高代码复用性
8.8.2 多态的体现
- 方法的多态:重写和重载
- 对象的多态:
- 对象的编译类型和运行类型可以不一致
- 编译类型在定义对象的时候就确定了,不能变,运行对象可以改变
- 编译类型看 = 号左边,运行类型看右边
8.8.3 注意事项和细节
- 多态的前提两个对象(对象)是封装和继承
- 向上转型
- 语法:
父类类型 引用名 = new 子类类型()
- 向上转型即父类引用指向子类对象,可以访问父类的所有成员
- 不能访问子类的特有成员 -- **因为能调用什么成员已经在编译阶段由编译类型决定了 **
- 最终运行阶段看的是子类(运行类型)的实现,即调用方法是,是从子类开始,调用规则与继承时将讲的规则一致 -- 见继承本质图
- 语法:
- 向下转型
- 语法:
子类类型 引用名 = (子类类型) 父类引用
- 向下转型后子类引用可以访问子类所有的成员
- 只能转换父类引用,不能转换父类对象
- 父类引用所指向的对象,必须是目标子类类型的
- 语法:
package com.hspedu.poly_.detail;
/**
* @author Carl Zhang
* @description
* @date 2021/11/11 20:13
*/
public class PolyDetail {
public static void main(String[] args) {
Animal animal = new Cat();
Object obj = new Cat();
//向上转型调用方法的规则如下:
// (1)可以调用父类中的所有成员(需遵守访问权限)
// (2)但是不能调用子类的特有的成员
// (#)因为在编译阶段,能调用哪些成员,是由编译类型来决定的 -- javac
// animal.catchMouse();错误
// (4)最终运行效果看子类(运行类型)的具体实现, 即调用方法时,按照从子类(运行类型)开始查找方法
// ,然后调用,规则我前面我们讲的方法调用规则一致--见继承的本质图
animal.eat(); //猫吃鱼
//animal.catchMouse(); //找不到 无法调用子类特有的方法
//System.out.println("animal.color = " + animal.color); //找不到
System.out.println("animal.name = " + animal.name); //动物 访问属性时看的编译类型
Cat cat = (Cat) animal; //向下转型
cat.cathMouse(); //1.可以调用子类的所有成员
System.out.println("cat.color = " + cat.color);
Cat cat2 = (Cat) (new Animal());//ClassCastException 2.不能转换父类对象
Animal animal1 = new Dog();
Cat cat1 = (Cat) animal1; //ClassCastException 3.待转换的父类引用指向的必须是当前目标类型的对象
}
}
class Animal {
String name = "动物";
int age = 10;
public void eat() {
System.out.println("动物吃");
}
public void sleep() {
System.out.println("动物睡");
}
public void run() {
System.out.println("动物跑");
}
public void show() {
System.out.println("动物展示");
}
}
class Cat extends Animal {
String name = "猫";
String color = "白色";
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void cathMouse() { //特有方法
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal {
}
8.8.4 多态中属性的调用
属性没有重写一说!属性的值看的是编译类型。见上述代码25
8.8.5 instanceof比较操作符
语法:对象 instanceof 类型
,返回值 boolean
介绍: 比较对象是否是目标类型的或是目标类型的子类类型
8.8.6 动态绑定机制DynamicBinding
- 调用对象方法时,有动态绑定机制:即方法会和对象的内存地址(运行类型)绑定
- 调用对象属性时,没有动态绑定机制,属性在哪里声明的,就使用哪里的。
package com.hspedu.poly_.dynamic_;
/**
* @author: Carl Zhang
* @create: 2021-11-12 11:39
*/
public class DynamicBinding {
public static void main(String[] args) {
Base base = new A();
System.out.println("base.getI() = " + base.getI()); // 20 调用方法--动态绑定机制,看运行类型
System.out.println("base.i = " + base.i);; // 10 属性的值看的是编译类型
System.out.println("base.sum() = " + base.sum()); //40 //30
System.out.println("base.sum2() = " + base.sum2()); //40 //20
}
}
class Base {
int i = 10;
public int getI() {
return i;
}
public int sum() {
return getI() + 10; //此处根据动态绑定机制 调用的子类的getI() 所以是20 + 10
}
public int sum2() {
return i + 10; //此处 i 在Base类声明的 所以是10 + 10
}
}
class A extends Base {
int i = 20;
@Override
public int getI() {
//此处 i 是 A类声明的 所以返回20
return i;
}
//@Override
//public int sum() {
// //此处又动态绑定 getI() 是 A类的, 返回20
// return getI() + 20;
//}
//@Override
//public int sum2() {
// return i + 20; //i 是 A类声明的 所以是20 + 20
//}
}
8.8.7 数组的多态
数组的定义类型是父类,里面存入的元素是子类对象。
8.9 Object 类详解
8.9.1 equals 方法
==
和 equals
比较
==
是比较运算符,比较基本数据类型的值,比较引用数据类型的地址。equals
只能比较引用数据类型,Object
类的equals
是比较对象的地址值。- 一般重写
equals方法
来比较内容是否相等, 如Integer
类型和String
类型的 equalse 查看源码
8.9.2 重写 equals 方法
package com.hspedu.object;
/**
* @author: Carl Zhang
* @create: 2021-11-12 16:51
*/
//重写equals方法比较两个Person对象各属性值是否相等, 是则返回true, 否则false
public class Equals02 {
public static void main(String[] args) {
Person p = new Person("赵本山", 33, '男', "9527");
Person p2 = new Person("赵本山", 33, '男', "9527");
System.out.println(p.equals(p2));
}
}
class Person {
private String name;
private int age;
private char gender;
private String idCard;
public Person(String name, int age, char gender, String idCard) {
this.name = name;
this.age = age;
this.gender = gender;
this.idCard = idCard;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
//@Override
//public boolean equals(Object o) {
// if (this == o)
// return true; //对象一样直接返回true
// if (o instanceof Person){ // 先要是同一个类型才能比较
// Person o1 = (Person) o;
// //返回属性的对比结果
// return o1.age == age && o1.name.equals(name) &&
// o1.gender == gender && o1.idCard.equals(idCard);
// }
// return false;
//}
//IDEA 自动生成
@Override
public boolean equals(Object o) {
if (this == o) return true; //如果同个对象直接返回true
if (!(o instanceof Person)) return false; // 如果不是同类型返回false
Person person = (Person) o; //再比较内容
return getAge() == person.getAge() &&
getGender() == person.getGender() &&
getName().equals(person.getName()) &&
getIdCard().equals(person.getIdCard());
}
}
8.9.3 hashcode 方法
老韩的5句话:
- 提高具有哈希结构的容器的效率!
- 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
- 两个引用,如果指向的是不同对象,则哈希值是不一样的
- 哈希值主要根据地址号来的!, 不能完全将哈希值等价于地址。
- 后面在集合,中 hashCode 如果需要的话,也会重写, 在讲解集合时,老韩在说如何重写 hashCode()
package com.hspedu.object_.hashcode_;
/**
* @author Carl Zhang
* @description
* @date 2021/11/13 9:31
*/
public class HashCode01 {
public static void main(String[] args) {
//1) 提高具有哈希结构的容器的效率!
//2) 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
String str1 = new String("1234");
String str2 = str1;
System.out.println(str1.hashCode()); //1509442
System.out.println(str2.hashCode()); //1509442
//3) 两个引用,如果指向的是不同对象,则哈希值是不一样的
String str3 = new String("1234");
System.out.println(str1.hashCode()); //1509442
System.out.println(str3.hashCode()); //1633
System.out.println("str1 == str3 " + str1 == str3);//String重写了hashcode方法
Person person = new Person("张三");
Person person2 = new Person("张三");
System.out.println(person.hashCode() == person2.hashCode());
//4) 哈希值主要根据地址号来的!, 不能完全将哈希值等价于地址。
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
8.9.4 toString 方法
基本介绍:
- 默认返回:全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】 子类往往重写 toString 方法,用于返回对象的属性信息
- 重写 toString 方法,打印对象或拼接对象时,都会默认自动调用该对象的 toString 形式. 快捷键
alt + insert
8.9.5 finalize 垃圾回收--应付面试,开发不常用
- 当对象被回收时,系统自动调用该对象的
finalize
方法。子类可以重写该方法,做一些释放资源的操作 - 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
- 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过
System.gc()
主动触发垃圾回收机制
package com.hspedu.object_;
/**
* @author Carl Zhang
* @description
* @date 2021/11/13 10:08
*/
public class Finalize01 {
public static void main(String[] args) {
Person person = new Person("张三");
person = null;
System.gc(); //主动调用垃圾回收器
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println(this.name + "没有被引用了, 系统自动回收....");
super.finalize();
}
}
8.9.6 断点调试
介绍:查错的时候,通过断点调试,看代码执行过程的变化。调试的时候是执行状态,看运行类型
快捷键:
F7
跳入方法内F8
逐行执行代码.shift+F8
跳出方法F9
resume,执行到下一个断点)
8.10 零钱通项目
8.10.1 项目需求
使用 Java 开发 零钱通项目 , 可以完成收益入账,消费,查看明细,退出系统等功能
8.10.2 项目界面
化繁为简:
- 先完成显示菜单,并可以选择 -- 将零钱明细用字符串拼接
- 完成零钱通明细.
- 完成收益入账
- 消费
- 退出
8.9.7 编程思想
- 一段代码完成一个小功能 便于扩展
//编程思想:一段代码完成一个小功能 便于扩展
while (true) { //判断用户输入的字符,如果不是y/n就一直循环
System.out.println("你确定要退出吗?y/n");
choice = scanner.next();
if (choice.equals("y") || choice.equals("n"))
break;
}
//如果输入的是y就退出
if (choice.equals("y"))
flag = false;
- 先判断错误的条件,不符合就return 再执行正确的条件 便于扩展
public void income() {
System.out.print("2. 收益入账: ");
//4. 收益入账 需要用到新的变量 money date balance
money = scanner.nextDouble();
//编程思想:先判断错误的条件,不符合就return 再执行正确的条件 便于扩展
//如果输入的值不合范围就break
if (money < 0) {
System.out.println("入账金额必须大于0");
return;
}
balance += money;
date = new Date(); //获取当前日期
//将收益信息拼接到明细里
detail += "\n收益入账\t+" + money + "\t" + sdf.format(date) +
"\t" + balance;
}
每个人都可以获得更精彩,不要让惰性毁了你 — 韩顺平说
九、房屋出租系统
9.1 房屋出租系统-需求
- 实现基于文本界面的《房屋出租软件》。
- 能够实现对房屋信息的添加、修改和删除(用数组实现),并能够打印房屋明细表
9.2 房屋出租系统-界面
9.2.1 项目界面 - 主菜单
9.2.2 项目界面 - 新增房源
9.2.3 项目界面 - 查找房源
9.2.4 项目界面 - 删除房源
9.2.5 项目界面 - 修改房源
9.2.6 项目界面 - 房屋列表
9.2.7 项目界面 - 退出系统
9.3 房屋出租系统设计 (!!)
项目设计-程序框架图 (分层模式=>当软件比较复杂,需要模式管理)
9.4 房屋出租系统 - 代码实现
9.4.1 Utility工具类 - 提高效率
- 不重复造轮子,提高效率
- static修饰的方法,直接
类名.方法名()
调用
package com.hspedu.house.utils;
/**
工具类的作用:
处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。
*/
import java.util.*;
/**
*/
public class Utility {
//静态属性。。。
private static Scanner scanner = new Scanner(System.in);
/**
* 功能:读取键盘输入的一个菜单选项,值:1——6的范围
* @return 1——6
*/
public static char readMenuSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1, false);//包含一个字符的字符串
c = str.charAt(0);//将字符串转换成字符char类型
if (c != '1' && c != '2' &&
c != '3' && c != '4' && c != '5' && c != '6') {
System.out.print("选择错误,请重新输入:");
} else break;
}
return c;
}
/**
* 功能:读取键盘输入的一个字符
* @return 一个字符
*/
public static char readChar() {
String str = readKeyBoard(1, false);//就是一个字符
return str.charAt(0);
}
/**
* 功能:读取键盘输入的一个字符,如果直接按回车,则返回指定的默认值;否则返回输入的那个字符
* @param defaultValue 指定的默认值
* @return 默认值或输入的字符
*/
public static char readChar(char defaultValue) {
String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符
return (str.length() == 0) ? defaultValue : str.charAt(0);
}
/**
* 功能:读取键盘输入的整型,长度小于10位
* @return 整数
*/
public static int readInt() {
int n;
for (; ; ) {
String str = readKeyBoard(10, false);//一个整数,长度<=10位
try {
n = Integer.parseInt(str);//将字符串转换成整数
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
/**
* 功能:读取键盘输入的 整数或默认值,如果直接回车,则返回默认值,否则返回输入的整数
* @param defaultValue 指定的默认值
* @return 整数或默认值
*/
public static int readInt(int defaultValue) {
int n;
for (; ; ) {
String str = readKeyBoard(10, true);
if (str.equals("")) {
return defaultValue;
}
//异常处理...
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
/**
* 功能:读取键盘输入的指定长度的字符串
* @param limit 限制的长度
* @return 指定长度的字符串
*/
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
/**
* 功能:读取键盘输入的指定长度的字符串或默认值,如果直接回车,返回默认值,否则返回字符串
* @param limit 限制的长度
* @param defaultValue 指定的默认值
* @return 指定长度的字符串
*/
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("")? defaultValue : str;
}
/**
* 功能:读取键盘输入的确认选项,Y或N
* 将小的功能,封装到一个方法中.
* @return Y或N
*/
public static char readConfirmSelection() {
System.out.println("请输入你的选择(Y/N)");
char c;
for (; ; ) {//无限循环
//在这里,将接受到字符,转成了大写字母
//y => Y n=>N
String str = readKeyBoard(1, false).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}
/**
* 功能: 读取一个字符串
* @param limit 读取的长度
* @param blankReturn 如果为true ,表示 可以读空字符串。
* 如果为false表示 不能读空字符串。
*
* 如果输入为空,或者输入大于limit的长度,就会提示重新输入。
* @return
*/
private static String readKeyBoard(int limit, boolean blankReturn) {
//定义了字符串
String line = "";
//scanner.hasNextLine() 判断有没有下一行
while (scanner.hasNextLine()) {
line = scanner.nextLine();//读取这一行
//如果line.length=0, 即用户没有输入任何内容,直接回车
if (line.length() == 0) {
if (blankReturn) return line;//如果blankReturn=true,可以返回空串
else continue; //如果blankReturn=false,不接受空串,必须输入内容
}
//如果用户输入的内容大于了 limit,就提示重写输入
//如果用户如的内容 >0 <= limit ,我就接受
if (line.length() < 1 || line.length() > limit) {
System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:");
continue;
}
break;
}
return line;
}
}
9.4.2 项目功能实现-House类
属性:编号 房主 电话 地址 月租 状态(未出租/已出租
9.4.3 项目功能实现-显示主菜单和完成退出软件功能
编程思想:
- 化繁为简,一个一个功能来实现,从用户角度考虑先后顺序
- 实现功能的三部曲 [明确完成功能->思路分析->代码实现]
功能:用户打开软件, 可以看到主菜单,可以退出软件
思路分析:在 HouseView.java 中,编写一个方法 mainMenu,显示菜单 见9.2.1
9.4.5 项目功能实现-添加房屋信息的功能
package com.hspedu.house.service;
class...
//private int id = 0;
private int idCounter = 1;
private int houseNum = 1;
//添加房屋
public boolean addHouse(String name, String telephone, String address, int rent, String state) {
////房间满了情况 返回false
//if (houses[houses.length - 1] != null) return false;
////id自增 -- 将空元素的index+1 就是id
//for (int i = 0; i < houses.length; i++) {
// if (houses[i] == null) {
// id = i + 1;
// break;
// }
//}
//houses[id - 1] = new House(id, name, telephone, address, rent, state);
//return true;
//方式二 id和数组长度无关联
//创建两个变量分别保存id和长度
//数组满了就不能再添加了
if (houseNum == houses.length) return false;
//id自增
houses[houseNum++] = new House(++idCounter, name, telephone, address, rent, state);
return true;
}
9.4.6 项目功能实现 - 删除房屋功能
思路分析:
- HouseView: 新增deleteHouse方法,功能:展示删除界面,调用HouseService的方法完成删除
- HouseService: 根据传入的编号,删除房屋数组对应元素,编号不能存在则返回boolean
//传入编号 删除房屋数组对应元素
public boolean deleteHouse(int id) {
//删除失败情况:id不存在 返回false
//定义boolean变量检测遍历结果
//定义变量保存对应id房屋的下标
int index = searchIndex(id);
//如果 index没有变化,就是id不存在 直接退出
if (index == -1) return false;
//如果是最后一个,直接设置为null
if (index == --houseNum) {
houses[index] = null;
return true;
}
//将对应下标元素设置为null,再和后面的元素交换
//因为删除后总有一个位置是空的,所以直接把最后一个位置置空就行
//再houseNum--, 上面已经--了就不用再写
//i = id - 1 ,i < houseNum - 1
houses[index] = null;
for (int i = index; i < houseNum; i++) { //上面已经--houseNum了,所以这里houseNum不用再-1
houses[i] = houses[i + 1];
}
houses[houseNum] = null;
return true;
}
//根据房屋编号返回对应下标, 没有则返回-1
public int searchIndex(int id) {
//判断id是否存在, 存在则获取对应下标
int index = -1;
for (int i = 0; i < houseNum; i++) {
if (id == houses[i].getId()){
index = i;
break;
}
}
return index;
}
9.4.7 项目功能实现 - 查询房屋信息功能
功能说明:
思路分析:
- HouseView:查找房屋界面,调用Service的查询方法,传入ID进行查询,没有则提示
- HouseService:根据ID找到对应房屋下标,返回房屋对象,如果ID不存在则返回null
代码实现:
9.4.8 项目功能实现 - 修改房屋信息功能
功能说明:
思路分析:
- HouseView:根据ID调用查找房屋方法,获取房屋信息并展示,调用工具类的含默认值方法,将用户输入的信息用变量保存,调用修改房屋信息传入房屋对象修改
- HouseService:获取对应ID的房屋下标,调用set方法修改房屋信息
代码实现:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南