Java目前已经是Java17了,但是我们学习的和用的仍然是Java8。
Java是95(96)正式对外发布。
Java诞生于SUN公司,现在归Oracle(甲骨文)。
Java之父:詹姆斯.高斯林。
2、Java的特点面向对象、跨平台(JVM:Java虚拟机)、健壮、安全、支持分布式等。
3、搭建环境JDK = JRE + 开发工具
JRE = JVM + 核心类库
JVM:Java虚拟机,只识别字节码格式数据,负责把字节码解释成CPU能识别的指令等。JVM有内存管理机制,有自己的垃圾回收(GC)机制。
JRE:Java运行环境。
JDK:Java开发工具包。
开发人员:安装JDK。
如果希望在任意目录下都可以使用javac,java等开发工具,最好配置环境变量。
JAVA_HOME=JDK的安装根目录
path新建一个变量值%JAVA_HOME%bin
4、HelloWorld程序//class是关键字,表示定义一个类
//HelloWorld是类名,自己命名的
class HelloWorld{
//main方法,主方法,是Java程序的入口,固定写法
public static void main(String[] args){
//语句,语句以;结尾,所有标点符号是英文半角输入
System.out.println("java");
}
}
第二章 Java的基础语法
2.1 注释
单行注释:
//
多行注释:
文档注释:
2.2 关键字
一共50个单词,全部是小写的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BIQEcz9w-1647162051490)(复习笔记.assets/image-20220301093011253.png)]
昨天已经见过的:
class,public,static,void,byte,short,int,long,float,double,char,boolean
以下这些不是关键字:
main,String,System,out,println,java都不是关键字
两个保留字(在关键字表中):
const,goto
3个特殊值(不在关键字表中):
true,false,null2.3 标识符
1、标识符的命名规则和规范(各5条) 命名规则: (1)标识符可以由26个英文字母大小写、数字0-9、下划线_、美元符号$ (2)数字不能开头 (3)不能直接使用关键字、保留字、特殊值 class不能直接做标识符,但是myClass是可以。 (4)标识符中间不能包含空格 (5)严格区分大小写 命名规范: (1)见名知意 (2)变量名等,从第二个单词开始首字母大写,例如:xxxYyyZzz (3)类名等,每一个单词首字母大写,例如:XxxYyyZzz (4)常量名,每一个单词都大写,下划线分割多个单词,例如:XXX_YYY_ZZZ (5)包名,每一个单词都小写,.分割多个单词,例如:xxx.yyy.zzz 不能使用java开头取包名。2.4 Java的数据类型
分两大类: (1)基本数据类型: byte,short,int,long,float,double,char,boolean (2)引用数据类型: 类、数组、接口、枚举等2.5 字面常量
long类型,当整数值比较大,需要在数字后面加L或l(小写) float类型,需要在小数后面加F或f double类型,可以在数字后面加D或d,也可以省略D或d char类型,需要使用单引号 String类型,需要双引号2.6 变量
变量的三要素:数据类型、变量名、变量值 使用要求: (1)先声明后使用 (2)声明后还需要初始化 (3)变量有作用域 (4)同一个作用域中不允许重复声明,但是可以反复赋值 (5)变量的值的类型必须和变量声明的类型一致或兼容
变量的声明:
数据类型 变量名;
变量的赋值:
变量名 = 值;2.7 最终变量
final 数据类型 变量名 = 值;
这种变量的值是不允许修改的。
这种变量称为常量,常量名一般是大写的。
2.8 计算机如何存储数据 2.8.1 进制分类和表示进制分类:二进制、八进制、十进制、十六进制
二进制:以0B开头,可以是0B或0b,习惯用0B。数字范围是0和1
八进制:以0开头。数字范围是0和7。
十进制:正常表示。数字范围是0-9。
十六进制:0X,可以是0X或0x,习惯用0X。数字范围是0-9,A-F或a-f。
2.8.2 数据存储单位和方式最小单位:bit比特,1位。
最基本单位:byte字节,8位。
符号位:看二进制的最高位(最左边),1表示负数,0表示正数。
原码、反码、补码:
正数:原码、反码、补码三码合一负数:原码、反码、补码三码不同
-25 + 30
-25原码:10000000 00000000 00000000 00011001
-25反码:11111111 11111111 11111111 11100110 在原码基础上,符号位不变,其余1变0,0变1
-25补码:11111111 11111111 11111111 11100111 在反码基础上,+1
+30补码:00000000 00000000 00000000 00011110 原码、反码、补码一致
-25补码:11111111 11111111 11111111 11100111
+30补码:00000000 00000000 00000000 00011110
-25+30:00000000 00000000 00000000 00000101 ==>5
2.8.3 各种数据类型宽度和存储方式
byte:1个字节,范围-128~127 其中-128比较特殊,理解为特殊规定 1000 0000,其他的数字都可以通过原码、反码、补码的概念推算。 short:2个字节,范围-32768~32767 int:4个字节 long:8个字节 float:4个字节 double:8个字节 char:2个字节(在Java程序的内存中)Unicode字符集的编码值(它也兼容ASCII码表) boolean:1位
注意:
(1)float和double
其中float和double类型的存储是分为(1)符号位(2)指数位(3)尾数位的三个部分存储。
就因为它存储指数,所以float类型的4个字节反而比long类型的8个字节的数据范围还大。
因为double类型的指数位和尾数位都比float类型多,所以double类型的数据范围和精度都比float要大。
float类型大概是小数点后7-8位(基于科学计数法)
double类型大概是小数点后15-16位(基于科学计数法)
因为float和double的二进制和十进制转换之间会有约等于的情况,不精确的。
(2)char
char类型底层二进制是通过char类型对应的编码值计算出来的。
每一个字符都有自己对应的唯一的编码值。
例如:
'a'的编码值97
'b'的编码值98
...
'A'的编码值是65
'B'的编码值是66
...
'0'的编码值是48
'1'的编码值是49
...
char类型在程序中可以用以下几种方式表示:
(1)'单个字符'
(2)直接用十进制的编码值
char c = 97; 等价于 char c = 'a';
(3)使用十六进制编码值
char c = 'u0061'; 十六进制,1位十六进制=4位二进制,4位十六进制,等价于16位二进制,2个字节
(4)有几个特殊字符需要转义
\ 如果要表示斜杆,无论在单引号还是双引号里面都要转义
' 如果要表示单引号,在单引号里面需要转换,在双引号中不用转义 "'"
" 如果要表示双引号,在双引号里面需要转换,在单引号中不用转义'"'
t 制表符
r 回车结束本行,光标到行首
n 换行,结束本行,光标到下一行
b 回退一位
2.9 基本数据类型转换
1、基本数据类型之间的转换(boolean类型不参与)
(1)自动类型转换
A:多种类型混合运算,换算为它们中最大大
B:byte和byte,short和short,char和char,或是它们三个之间计算都是转为int处理
C:当把存储范围小的数据类型的数据 赋值给 存储范围大的类型的变量时,自动类型提升
byte->short->int->long->float->double
char->
(2)强制类型转换
A:当把存储范围大的数据类型的数据 赋值给 存储范围小的类型的变量时,强制类型转换,这种有风险,可能会溢出或损失精度
B:当需要提升某个变量或表达式结果的类型,没有风险。
(转换后的类型)变量 转换某一个变量
(转换后的类型)(表达式) 转换的是表达式计算的结果
int x = 1;
int y = 2;
System.out.println((double)x/y); //转x的类型为double
System.out.println((double)(x/y));//转x/y的结果的类型为double
2、基本数据类型和字符串之间的转换
无论什么类型只要和字符串进行“+”拼接,结果都变成了字符串。
2.10 运算符 2.10.1 运算符的分类一共38个运算符。
按照操作数的个数分:
一元运算符(单目运算符):++,--,逻辑非,按位取反,正号(+),负号(-) 二元运算符(双目运算符):其余都是二元的 三元运算符(三目运算符): ? :
按照功能分:
算术运算符、比较运算符、逻辑运算符、条件运算符、位运算符、赋值运算符、Lambda操作符。
2.10.2 算术运算符加:+
(1)当+左右两边都是数值类型或char,表示求和
(2)当+左右两边只要出现字符串,表示拼接
(3)当+作为一元运算符使用时,表示正号,而且+要在变量或数字的前面
减:-
(1)当-左右两边都是数值类型或char,表示求差
(2)当-作为一元运算符使用时,表示负号,而且-要在变量或数字的前面
乘:*
除:/
(1)如果两个整数相除,结果只保留整数部分
(2)如果两个整数相除,除数为0,编译通过,运行报错 ArithmeticException算术异常
(3)如果两个小数相除,除数为0,编译通过,运行结果是Infinity
模:%
(1)无论是整数还是小数都可以求模
(2)模运算结果的正负号看被模数
自增:++
自减:--
(1)当自增/自减表达式单独加;成一个语句时,++/--在前在后没影响
a++;
++a;
a--;
--a;
(2)当自增/自减表达式和其他的运算一起构成一个语句时,++/--在前在后是不同。
++/--在前:先自增/自减,再取自增变量的值
++/--在后:先取自增/自减变量原来的值,然后自增/自减变量+/-1,然后用已经取出来的值做其他运算
2.10.3 比较运算符
大于:>
小于:<
大于等于:>=
小于等于:<=
等于:== 特别容易和=混淆
不等于:!=
特点:
所有的比较运算符都是二元运算符,计算的结果都是true/false
2.10.4 逻辑运算符
逻辑与:&
短路与:&&(开发中一般用它)
无论是&还是&&,都是两边为true的时候,最终结果才为true。
其中&&的左边为false时,右边会不看。
逻辑或:|
短路或:||
无论是|还是||,都是两边只要有一边是true,结果就为true。
其中||的左边为true时,右边会不看。
逻辑异或:^
只有两边不同时,一边为true,另一边为false,结果才为true。
逻辑非:!
!true为false
!false为true
特点:
逻辑运算符的操作数一定是boolean值,结果也是boolean值
2.10.5 位运算符
左移:<<
快速口诀:左移几位,就是乘以2的几次方
右移:>>
快速口诀:右移几位,就是除以2的几次方,向下取整
无论是左移还是右移,当移动位数超过当前类型的总宽度时,会先减去总宽度。
int a = 5;
a << 40 等价于 a << (40-32)
无符号右移:>>>
正数和普通右移一样。
负数不同,最高位补0,会使得负数变为正数。
只有负数的普通右移,会在最左边补1,其他移位都是补0。
按位与:&
对应二进制位都是1时,这个二进制位才为1。
按位或:|
对应二进制位只要有1,这个二进制位就为1。
按位异或:^
对应二进制位只有一个是1,一个是0, 这个二进制位才为1。
按位取反:~
原来0的变为1,1的变为0,包括符号位。
2.10.6 条件运算符
条件表达式 ? 结果表达式1 : 结果表达式2
注意:
三元运算符的表达式不能直接加;构成语句,需要把结果赋值给变量,或者直接输出结果等,才可以。
即
条件表达式 ? 结果表达式1 : 结果表达式2; 错误的
变量 = 条件表达式 ? 结果表达式1 : 结果表达式2;
System.out.println(条件表达式 ? 结果表达式1 : 结果表达式2);
2.10.7 赋值运算符
(1)基本的赋值运算符 =
(2)扩展的(复合的)赋值运算符: +=,-=,*=,/=,%=,<<=,>>=,>>>=,&=,|=,^=
注意:
赋值运算符优先级一定是最低的,一定是最后算赋值操作。
赋值运算符的左边一定是变量,不能是表达式、常量等。
如果是=,要求=右边的值(常量值、变量值、表达式的值)的类型 小于等于 =左边变量的类型。如果大于了左边变量的类型,一定要做强制类型转换。
如果是扩展的赋值运算符,当=右边的值(常量值、变量值、表达式的值)的类型大于左边变量的类型时,会自动发生强制类型转换。
2.10.8 优先级
(1)单目:++,–,~,!
(2)算术运算符:先乘除模,再加减
(3)移位:<<,>>,>>>
(4)比较运算符:先大小比较>,<,>=,<=,再等或不等比较==,!=
(5)逻辑运算符:依次是&,^,|,&&,||
(6)条件运算符:? :
(7)赋值运算符:所有赋值运算符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6L9CW5m8-1647162051492)(复习笔记.assets/1553858424335.png)]
第三章 流程控制语句结构 3.1 表达式和语句表达式分三种:
(1)计算表达式:常量值、变量值 加上 运算符
(2)new表达式
new Scanner(System.in)
(3)方法调用表达式
System.out.println() input.nextInt() input.next()
语句分为两大类:
(1)单语句
; 空语句
表达式直接加;构成的语句
(1)自增、自减、赋值表达式可以直接;构成语句
(2)new表达式可以直接;构成语句
(3)方法调用表达式可以直接;构成语句
如果是(2)和(3),没有把表达式结果赋值给变量,结果就会丢失。
new Scanner(System.in); 这个语句创建的对象就丢失了
正确的写法 Scanner input = new Scanner(System.in);
input.nextInt();//从键盘接收的整数就丢失了;
正确的写法:int num = input.nextInt();
(2)复合语句
条件判断,选择结构,循环结构等等3.2 流程控制语句结构
1、流程控制语句结构有三种
(1)顺序结构:Java代码方法体中的语句整体都是从上往下执行的
(2)分支结构:
条件判断:if…else选择结构:switch…case
(3)循环结构
3.3 条件判断(必须掌握) 3.3.1 单分支条件判断if(条件表达式){
语句块;
}
执行特点:
当if()中结果是true时,就执行对应的{}中的语句块,否则就不执行。
注意:
(1)if()中的值(变量、常量、表达式)一定要是boolean类型
(2){}中如果只有一个语句,{}可以省略。
千万不要在if()后面直接写;
3.3.2 双分支条件判断
if(条件表达式){
语句块1;
}else{
语句块2;
}
执行特点:
当if()中结果是true时,就执行对应的{}中的语句块1,否则就执行语句块2。
注意:
(1)if()中的值(变量、常量、表达式)一定要是boolean类型
(2){}中如果只有一个语句,{}可以省略。
千万不要在if()后面直接写;
(3)else不能单独使用,必须配合if使用
3.3.3 多分支条件判断
if(条件表达式1){
语句块1;
}else if(条件表达式2){
语句块2;
}else if(条件表达式2){
语句块2;
}...
【else{
语句块n+1;
}】
说明:笔记中【】表示这个部分可选,有可能有,有可能没有
执行特点:
(1)先判断if(条件表达式1),如果它成立,就直接执行语句块1,后面的所有条件和分支都不看了; (2)如果if(条件表达式1)不成立,接着看if(条件表达式2),如果它成立,就直接执行语句块2,后面的所有条件和分支都不看了; 以此类推 总之,下面的条件要被看到,说明上面的条件不成立。 如果所有的if都不成立,那么就找最下来的else分支执行。
注意:
(1)if()中的值(变量、常量、表达式)一定要是boolean类型
(2){}中如果只有一个语句,{}可以省略。
千万不要在if()后面直接写;
(3)else不能单独使用,必须配合if使用
(4)多个if()和else if()的条件范围是有重叠部分(包含关系),必须小的在上,大的在下面
多个if()和else if()的条件范围是没有重叠部分(互斥关系),顺序随意
3.3.4 嵌套
嵌套的原则是:外层的满足后,再看里面的。外层的不满足,里面的是不看的。3.4 选择结构
switch(表达式){
case 常量值1:
语句块1;
【break;】
case 常量值2:
语句块2;
【break;】
...
【default:
语句块n+1;
【break;】
}
执行特点:
(1)入口
A:switch()中表达式的值和case后面的常量值匹配了,就从这个case进入执行
B:switch()中表达式的值和所有case后面的常量值都不匹配,就从default进入。
无论defaut编写的位置在哪里,都是先看A,如果A没找到,再看B;
(2)贯穿/穿透
一旦找到入口,就会一直贯穿执行,除非遇到出口。
(2)出口
A:自然出口:switch的结束}
B:中断出口:break,return等
注意:
(1)case后面必须写常量值或常量表达式,不能是变量
(2)case后面的常量值不能重复,即不能有两个case后面的常量值相同
(3)switch()中值(变量、常量、表达式)的类型只能是
byte,short,int,char四种基本数据类型
和
枚举和String两种引用数据类型
3.5 循环结构
3.5.1 for循环
1、语法结构
for(;;){
循环体语句块;
}
for(【循环变量的初始化】; 【循环条件表达式】; 【迭代表达式】){
【循环体语句块;】
}
2、执行特点
(1)先执行【循环变量的初始化】
(2)判断【循环条件表达式】结果:
如果为true,那么执行(3)
如果为false,直接结束当前for
(3)执行【循环体语句块;】
(4)执行【迭代表达式】
(5)回到(2)
3、注意
(1)for()中的两个;不能多也不能少
(2)如果循环条件第一次就不成立,那么循环体语句就一次也不执行。
(3)for(;{}结构是死循环,除非在{}中有break、return等语句,否则无法结束循环。
3.5.2 while循环1、语法结构
while(循环条件表达式){
【循环体语句块;】
}
while(true){
【循环体语句块;】
}
2、执行特点
(1)先判断“循环条件表达式”
如果为true,那么执行(2)
如果为false,直接结束当前while
(2)执行【循环体语句块;】
(3)回到(1)
3、注意
(1)while()中的条件不允许省略
(2)while循环也可能第一次判断条件就不成立,循环体语句块一次也不执行。
(3)如果while(true)就是死循环,除非{}中有break,return等语句,否则就无法结束循环。
3.5.3 do…while循环1、语法结构
do{
【循环体语句块;】
}while(循环条件表达式);
do{
【循环体语句块;】
}while(true);
2、执行特点
(1)先执行一次【循环体语句块;】
(2)先判断“循环条件表达式”。循环条件成立,继续循环,循环条件不成立,结束循环。
如果为true,那么执行(3)
如果为false,直接结束当前do…while
(3)执行【循环体语句块;】
(4)回到(2)
3、注意
(1)do…while()中的条件不允许省略
(2)do…while循环至少执行一次循环体语句。
(3)如果do…while(true)就是死循环,除非{}中有break,return等语句,否则就无法结束循环。
3.5.4 三种循环的对比注意三种循环都可以实现重复执行xx代码的功能,完全可以互换。
习惯上:for更多的用于的循环次数或循环的起点和终点比较明显的场景。
while习惯用于循环次数不明显,循环条件比较明显的场景。
do…while习惯用于循环体至少执行一次的场景。
3.5.5 跳转语句 1、breakbreak必须出现在:
(1)switch
(2)循环中(for,while,do…while)
如果同时出现在switch和循环的嵌套代码中,跳出最近的。
package review;
public class TestBreak {
public static void main(String[] args) {
// int age = 18;
for(int i=1; i<=3; i++){
switch (i){
case 1:
System.out.println("monday");
break;//这里跳出switch,for继续
case 2:
System.out.println("tuesday");
break;
case 3:
System.out.println("wednesday");
break;
}
}
}
}
2、continue
continue是只能用于循环结构。
表示提前结束本次循环,==本次==循环continue下面的循环体语句不执行,提前进入下一次循环的准备:
对于while和do…while来说,下一次循环的准备是指循环条件的判断;对于for来说,下一次循环的准备是指迭代表达式的执行和循环条件的判断;
如果有嵌套的情况,也是作用于最近的循环。
3.5.6 循环的嵌套一句话:
外层循环循环一次,内层循环循环一轮。内层循环是作为外层循环的循环体语句。
第四章 数组 4.1 数组的概念数组:是一个容器,也是一种数据的集合。容器是用来装东西的,数组是用来装一组数据,而且这组数据它们的类型是一样的。
数组这种容器的特点:
(1)元素是连续存储。
数组根据[下标]的方式访问元素的方式,效率是非常高,可以根据数组名中记录的首地址 和 下标直接 计算出元素的存储位置。
(2)数组的长度一旦确定,就不能更改,除非创建新的数组。
数组中每一个数据称为元素,数组中元素的总个数称为数组的长度,每一个元素是通过[下标]来进行区分的,下标的范围是[0, 数组的长度-1],如果下标指定超过这个范围了,会报一个==ArrayIndexOutOfBoundsException==数组下标越界的异常。
数组的好处:可以用一个数组名,统一管理一组数据。
数组的分类:
按照维度来分:一维数组和二维数组
按照元素的类型划分:元素是基本数据类型 和元素是引用数据类型
4.2 数组的声明和使用 1、数组的声明元素的类型[] 数组名;
public static void main(String[] args) {
//Java中推荐的数组写法,也是阿里规范中推荐的写法
//元素的类型[] 数组名
//例如:
int[] arr1;
//为什么Java推荐这种写法?
//因为在Java中 int[]是一种数据类型,称为数组类型
System.out.println(int[].class);
//但是Java为了照顾C语言的程序员习惯,也支持如下写法
//元素的类型 数组名[];
//例如:
int arr2[];
}
2、数组的初始化
初始化的目的:确定数组的长度和元素。
(1)静态初始化
//写法一:声明和初始化是一句完成的
元素的类型[] 数组名 = {元素的值列表};
//写法二:声明和初始化不是一句完成的
元素的类型[] 数组名;
数组名 = new 元素的类型[] {元素的值列表}; //此处注意右边的[]中不要写长度,因为此时的长度由{}中元素的个数决定
(2)动态初始化
元素的类型[] 数组名 = new 元素的类型[长度]; 元素的类型[] 数组名; 数组名 = new 元素的类型[长度];3、获取数组的元素和长度
数组的元素表示方式:
数组名[下标]
数组的长度表示方式:
数组名.length
4、数组的遍历
for(int i=0; i<数组名.length; i++){
数组名[i]表示元素
}
4.3 数组的存储
数组的元素是存在==“堆”==中,当我们使用new关键字时,表示在堆中申请一块“连续”的存储空间用来存储数组的元素等信息。
数组名中记录的是这块连续存储空间的==首地址==。
4.4 数组的基础算法 4.4.1 对元素统计分析1、累加和
所有元素都累加,所以累加的语句 sum += 元素的语句,不加条件控制的。
int[] arr = {.....};//假设arr.length=5
//定义一个变量用来存储总和
int sum = 0;
for(int i=0; i
2、累加偶数和
不是所有元素都累加,所以累加的语句 sum += 元素的语句,必须加条件控制。
int[] arr = {.....};//假设arr.length=5
//定义一个变量用来存储总和
int sum = 0;
for(int i=0; i
3、统计偶数个数
int[] arr = {.....};//假设arr.length=5
//定义一个变量用来存储个数
int count = 0;
for(int i=0; i
4、统计素数个数
int[] arr = {.....};//假设arr.length=5
//定义一个变量用来存储个数
int count = 0;
for(int i=0; i1){//满足素数的条件再累加个数
count++;
}
}
for(int i=0; i1){//满足素数的条件再累加个数
count++;
}
}
…
4.4.2 找最大值/最小值
只是找最大最小值,是不需要考虑元素重复。
int[] arr = {....};
//对于元素已经已知的数组
int max = arr[0];//先假设第一个元素的值最大
int min = arr[0];//先假设第一个元素的值最小
for(int i=1; i max){
max = arr[i];
}
if(arr[i] < min){
min = arr[i];
}
}
for(int i=0; i max){
max = arr[i];
}
if(arr[i] < min){
min = arr[i];
}
}
4.4.3 找最大值/最小值下标
如果要找下标,那么就要分两种情况,一种是元素不会重复,一种是元素有重复。
(1)一种是元素不会重复
以最大值为例
int[] arr = {....};
//对于元素已经已知的数组
int max = arr[0];//先假设第一个元素的值最大
int index = 0;
for(int i=1; i max){
max = arr[i];
index = i;
}
}
int maxIndex = 0;//先假设第一个元素的值最大
for(int i=1; i arr[maxIndex]){
maxIndex = i;
}
}
(2)另一种是元素有重复
以最大值为例:
必须分两步走,先找出最大值,然后再找下标
int[] arr = {....};
//对于元素已经已知的数组
//第一轮遍历,目的是找最大值
int max = arr[0];//先假设第一个元素的值最大
for(int i=1; i max){
max = arr[i];
}
}
//第二轮遍历,目的是找最大值的下标
System.out.println("最大值的下标有:");
for(int i=0; i
4.4.4 查找元素下标
1、顺序查找:如果数组无序,只能选择顺序查找。
2、二分查找:如果数组有序,优先选择二分查找更快。
数组有序的话,既可以二分查找,又可以顺序查找。
1、顺序查找
int[] arr = {....};//数组不要求有序
int target = ?;
//每一个元素都比较一遍
for(int i=0; i
2、二分查找
int[] arr = {....}; //数组必须有序 ,假设是从小到大
int target = ?;
//先不考虑重复
int index = -1;
//boolean flag = false;//假设没找到
for(int left=0, right=arr.length-1; left<=right;){
int mid = left + (rigth-left)/2;
if(target == arr[mid]){
index = mid;//只要修改了index,index一定不会是-1,因为正常的下标是从0开始的正数
//flag = true;//找到了
break;
}else if(target > arr[mid]){
//说明目标应该在[mid]右边,移动左边界
left = mid + 1;
}else{
//说明目标应该在[mid]左边,移动右边界
right = mid - 1;
}
}
if(index==-1){
System.out.println("不存在");
}else{
System.out.println("存在,下标是" + index);
}
考虑重复元素(不要求都掌握,能掌握更好)
int[] arr = {....}; //数组必须有序 ,假设是从小到大
int target = ?;
//先不考虑重复
int index = -1;
//boolean flag = false;//假设没找到
for(int left=0, right=arr.length-1; left<=right;){
int mid = left + (rigth-left)/2;
if(target == arr[mid]){
index = mid;//只要修改了index,index一定不会是-1,因为正常的下标是从0开始的正数
//flag = true;//找到了
System.out.print("找到了,下标有" + mid);
//找到了,说明arr[mid]是目标元素
//那么要判断[mid]的左右位置是否也相同
for(int j=mid-1; j>=0; j--){//看[mid]的左边
if(arr[j] == target){
System.out.print("," + j);
}else{
break;
}
}
for(int j=mid+1; j<=arr.length-1; j++){//看[mid]的右边
if(arr[j] == target){
System.out.print("," + j);
}else{
break;
}
}
break;
}else if(target > arr[mid]){
//说明目标应该在[mid]右边,移动左边界
left = mid + 1;
while(arr[left] == arr[mid]){
left++;
}
//当arr[left] != arr[mid]就结束while,这里按照假设 left=9, arr[left]的值是8
}else{
//说明目标应该在[mid]左边,移动右边界
right = mid - 1;
while(arr[right] == arr[mid]){
right--;
}
//当arr[right] != arr[mid]就结束while,这里按照假设 right=3, arr[right]的值是5
}
}
if(index==-1){
System.out.println("不存在");
}else{
System.out.println("存在,下标是" + index);
}
4.4.5 排序
1、直接选择排序
思路:每一趟找出本轮的最小值/最大值的位置,如果它不在应该在的位置,就与应该在的位置元素交换。
以从小到大为例,每一趟找出本轮的最小值为例
int[] arr = {....};
for(int i=0; i
2、冒泡排序
思路:每一轮都从头开始,相邻元素比较,如果相邻元素不满足最终的顺序,交换。
例如:要实现从小到大,那么如果前面的元素比后面的元素大,就交换。
int[] arr = {....};
for(int i=1; i arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
如果考虑优化:
int[] arr = {....};
for(int i=1; i arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = false;//只要交换,那么说明数组还不确定是完全有序的
}
}
if(flag){
break;
}
}
第五章 面向对象(上)
5.1 概念
类:一类具有相同特性的事物的“抽象”描述。
对象:是某个类的具体的一个个体,实例。
抽象与具体的关系。
5.2 类的定义和对象的创建
1、类的定义(class)
【修饰符】 class 类名{
//成员
}
注意:类名,尽量见名知意,每一个单词首字母大写
2、对象的创建(new)
(1)匿名对象
new 类名()
new 类名(实参列表)
(2)把对象赋值给一个变量,相当于给对象命名
类名 变量名/对象名 = new 类名(【实参列表】);
5.3 包
1、包的作用
(1)避免类的重名
(2)组织管理不同功能的类
(3)控制某些类等的可见性范围
只有public的类才能跨包使用。
2、包的声明
package 包名;
注意:
(1)包名要小写,每一个单词用.分割
(2)包名不能以java开头,包名也不能直接使用关键字
(3)包名习惯用公司域名倒置 + 业务模块名
(4)package语句必须在.java文件的首行,一个.java文件只能有一个,而且对应的.java文件必须在对应包目录结构中。
3、如何跨包使用类
方式1:使用全名称:包.类名
方式2:在类上面写import 语句; 代码中使用简单的类名
import java.util.Scanner;
public class Test{
public static void main(String[] args){
Scanner input = new Scanner(System.in);
}
}
5.4 成员变量
1、成员变量声明的位置
类中方法外
2、成员变量声明的格式
【修饰符】 class 类名{
【修饰符】 数据类型 变量名;
}
3、成员变量的特点
目前讨论的都是没有static修饰的。
(1)有默认值
(2)所有对象的成员变量的值都是“独立”的
4、成员变量的使用
目前讨论的都是没有static修饰的。
(1)在本类中使用
如果没有形参等局部变量和成员变量重名,直接使用。
如果有形参等局部变量和成员变量重名,加this.成员变量
(2)在别的类中使用
先创建对象
再通过对象.成员变量的方式使用
public class Rectangle {
double length;
double width;
void setLengthAndWidth(double length, double width){
//这里的this表示当前对象,这里是哪个对象呢?
//哪个对象调用这个方法,this就是哪个对象
//这里加this的作用是用于区别“成员变量”和“形参”
this.length = length;
this.width = width;
}
double area(){
return length * width;
}
}
public class RectangleTest {
public static void main(String[] args){
Rectangle r = new Rectangle();
r.length = 5;
r.width = 3;
}
}
5、成员变量的存储
目前讨论的都是没有static修饰的。
成员变量是存在”堆“中,每new一次,创建一个对象,给这个对象开辟对应的内存空间,用来存储它的成员变量的值。
5.5 成员方法
1、方法的概念和好处
方法是一个“独立的" "可复用的" "功能"。
2、方法的定义格式
【修饰符】 class 类名{
//成员变量
【修饰符】 数据类型 变量名;
//成员方法
【修饰符】 返回值类型 方法名(【形参列表】){
【方法体语句块;】
}
}
(1)方法名
尽量见名知意,而且从第二个单词开始首字母大写。
(2)返回值类型
void:表示方法没有结果返回
方法体中可以有 return ; 语句,不能有 return 结果; 语句 非void:表示方法有结果返回
可以是int等的基本数据类型也可以是String、int[]、Circle等各种引用数据类型方法体中必须有 return 结果; 语句,而且要保证方法体执行的最后一条语句是 return语句。
(3)(【形参列表】):表示方法中有未知的变量,它的初始化需要调用者来传参数进行赋值
():表示方法没有形参,但是()也不能省略,而且这个方法调用时,不需要传“实参”(数据类型 形参名, 数据类型 形参名):表示方法有形参,()也不能省略,而且这个方法调用时,需要传“实参”
3、方法的调用格式
无论在哪里使用方法,()都不能省略。
(1)在本类中
直接使用
(2)在其他类中
先创建对象
再通过对象.成员方法的方式使用
package com.atguigu.method;
//Rectangle矩形
public class Rectangle {
double length;
double width;
void setLengthAndWidth(double length, double width){
this.length = length;
this.width = width;
}
double area(){
return length * width;
}
double perimeter(){
return 2 * (length + width);
}
String getInfo(){
return "长:" + length + ",宽:" + width +",面积:" + area() +",周长:" + perimeter();
}
}
public class TestRectangle {
public static void main(String[] args) {
Rectangle r = new Rectangle();
r.setLengthAndWidth(5,3);
System.out.println(r.getInfo());
System.out.println("单独看面积:" + r.area());
}
}
4、形参和实参
形参是指方法==“声明”==时()中定义的变量。
【修饰符】 class 类名{
//成员变量
【修饰符】 数据类型 变量名;
//成员方法
【修饰符】 返回值类型 方法名(【形参列表】){
【方法体语句块;】
}
}
class Demo{
//方法的定义,方法的声明
void printInt(int a){//(int a)就是形参
System.out.println(a);
}
}
实参是指方法==“调用”==时()中传入的值(常量值、变量值、表达式的值)。
class Test{
public static void main(String[] args){
Demo d = new Demo();
//这里是调用/使用printInt(int a)方法
//调用就是让printInt(int a)方法执行
d.printInt(5);//(5)就是实参,而且是常量值的实参
int num = 6;
d.printInt(num);//(num)就是实参,而且是变量值的实参
d.printInt((int)(Math.random()*100));//((int)(Math.random()*100))就是实参,而且是表达式的值作为实参,这个表达式是 (int)(Math.random()*100),调用Math类的random()方法,产生随机值,这里是产生一个[0,100)的整数的表达式
}
}
5、return关键字
两种使用的形式:
(1)return ;
(2)return 结果;
return ;
这种形式只能出现在方法的返回值类型是void的方法体中。
作用表示提前结束方法体的运行。表示结束方法但不返回结果。
那么调用这种方式的表达式只能直接加;单独成一个语句。
public class TestReview {
public static void main(String[] args){
Demo d = new Demo();
//这里是调用/使用printInt(int a)方法
//调用就是让printInt(int a)方法执行
d.printInt(5);//d.printInt(5)这个方法调用表达式,只能直接加;构成语句。
// int num = d.printInt(5);//错误
// System.out.println(d.printInt(5));//错误
}
}
class Demo{
//方法的定义,方法的声明
void printInt(int a){//void表示不返回结果
System.out.println(a);
}
}
return 结果;
这种形式只能出现在方法的返回值类型“不是”void的方法体中。
作用表示提前结束方法体的运行。并且返回结果。
那么调用这种方式的表达式可以
A:直接加;单独成一个语句。(返回值会丢失)
B:可以用变量接收返回值
C:可以直接输出返回值
D:可以把返回值作为另一个方法调用的实参
E:可以把返回值作为另一个表达式的操作数
...
package com.atguigu.review;
public class TestReview2 {
public static void main(String[] args) {
Example e = new Example();
e.max(4,6);//直接加;构成语句,max方法执行了,并且有结果返回,但是没有接收,就丢了
int bigger = e.max(7,9);//用变量接收返回值
System.out.println("bigger = " + bigger);
System.out.println("最大值是:" + e.max(8,2));//直接输出返回值
int biggest = e.max(e.max(4,6),9);//e.max(4,6)的返回值作为 第二次e.max方法调用的实参
System.out.println("biggest = " + biggest);
int x = 1;
int y = 3;
int result = e.max(x,y) + 5;//用x,y中的最大值 + 5,这里把e.max(x,y)返回值作为表达式的操作数
System.out.println("result = " + result);
}
}
class Example {
int max(int a, int b) {
System.out.println("Example.max");
return a > b ? a : b;
}
}
6、方法的调用过程
入栈:一旦方法被调用执行就会在“栈”中开辟一块独立的内存空间,用于存储方法的“局部变量”等信息。
出栈:一旦方法运行结束,就会自动释放对应的内存空间。Java中只要这个方法体没有完全执行结束,那么就不会彻底释放对应的空间。
栈:先进后出。
7、局部变量和实例变量的区别
(1)声明的位置不同
实例变量:类中方法外局部变量:属于方法里面
【修饰符】 class 类名{
//成员变量
【修饰符】 数据类型 实例变量;//没有static修饰的成员变量
//成员方法
【修饰符】 返回值类型 方法名(【形参列表】){ //(【形参列表】)也是方法的局部变量
【方法体语句块;】 //这里面声明的变量都是局部变量
}
}
(2)在内存中存储值的位置不同
实例变量:堆局部变量:栈
(3)作用域
实例变量:整个类中(非静态static的方法中)局部变量:从声明处开始,到它所属结构的{}结束
(4)生命周期
实例变量:和所属的对象生命周期一样局部变量:随着方法调用而分配,方法调用结束就自动释放
(5)初始化方式不同
实例变量:有默认值局部变量:必须手动初始化
(6)修饰符
实例变量:有很多局部变量:只能有一个final
8、可变参数
如果形参声明时,在数据类型后面加...就是可变参数。
要求:
(1)一个方法只能有一个可变参数
(2)可变参数的声明必须是形参列表的最后一个
调用时:
(1)可变参数部分,可以传入对应类型的数组
(2)可变参数部分,可以传入对应类型的0~n个元素
在声明它的方法中,当数组使用即可。
class Demo{
//String s和 double d不属于可变参数
//int...nums才是可变参数
void method(String s, double d, int... nums){
//在这里要使用nums,就把nums当成数组使用即可
}
}
class Test{
public static void main(String[] args){
Demo d = new Demo();
d.method("hello", 5.2);//"hello"这个实参是给 String s这个形参赋值的
//5.2这个实参是给double d这个形参赋值的
//这里可变参数部分没有传入实参
d.method("hello", 5.2, 1,2,3,4,5);//"hello"这个实参是给 String s这个形参赋值的
//5.2这个实参是给double d这个形参赋值的
//这里可变参数部分传入5个实参,1,2,3,4,5
d.method("hello", 5.2, new int[]{1,2,3,4,5});//"hello"这个实参是给 String s这个形参赋值的
//5.2这个实参是给double d这个形参赋值的
//这里可变参数部分传入1个数组
}
}
9、命令行参数(了解)
给main方法传的参数称为命令行参数
class Test{
public static void main(String[] args){
//args就是一个数组
}
}
运行时,可以通过java命令传入实参
java Test 参数1 参数2
10、方法的参数传递机制(重要,容易出面试题)
方法的形参是基本数据类型(8种),那么实参给形参的是“数据值”的“副本”,传完值之后,实参和形参就无关了。即无论形参怎么修改,和实参都无关。方法的形参是引用数据类型(可以是类、可以是数组等),那么实参给形参的是“地址值”的“副本”,相当于此时形参和实参“指向”同一个“对象”。即此时形参修改对象的属性(属性就是成员变量)的值,和实参自己修改是一样的。
但是这里要警惕一种情况,如果形参又指向了一个“新”的对象(形参名 = new 新对象),那么就和实参无关了,除非返回这个新对象,并让实参重新接收。
11、方法的重载
在同一个类中,方法名相同,形参列表不同(可以是个数不同,可以是类型不同)的几个方法称为方法的重载。和返回值类型是否一致无关。
当有多个方法重载时,调用时,会遵循如下的原则:
(1)先找最匹配的
实参的个数和类型与形参的个数和类型完全一致。
(2)再找唯一兼容的
实参的类型 < 形参的类型如果形参是可变参数,实参的个数比较灵活(0~n个元素,或者是数组都兼容)。
(3)如果找不到匹配的或兼容的,或者找到多个兼容的 都会报错。
12、方法的递归调用
当方法出现自己调用自己,就是递归调用。
必须有条件的递归调用,否则就会发生 StackOverflowError(栈内存溢出的错误)
5.6 对象数组
情况分为两种:
(1)元素类型是非数组以外的引用数据类型的一维数组。
Student[] students; //元素是Student
Circle[] circles; //元素是Circle
Rectangle[] rectangles; //元素是Rectangle
(2)元素类型是一维数组的一维数组。其实就是二维数组。
int[][] arr; //元素类型看成int[]类型,当一维数组看成的
//元素类型是int类型,当二维数组看
char[][] letters; //元素类型看成char[]类型
//元素类型是char,当二维数组看
String[][] strings; //元素类型看成String[]类型
//元素类型是String,当二维数组看
Student[][] students;//元素类型看成Student[]类型
//元素类型是Student,当二维数组看
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rh9KJ34u-1647162051493)(复习笔记.assets/image-20220312104048273.png)]
第6章 面向对象基础(中)
6.1 封装
1、封装的好处
隐藏内部细节,既可以安全,又可以方便/简化使用者的负担。使得可控。成员变量赋值的可控(通过方法可以使得非法值不会赋值进去)、成员的可见性范围可控。
2、封装的实现
封装就是靠权限修饰符来控制的。
本类 本包 其他包的子类 其他包的非子类 private √ 缺省(不写) √ √ protected √ √ √ public √ √ √ √
这些权限修饰符可以修饰的角色:
class(外部类) 成员变量 成员方法 。。。 private √ √ 缺省(不写) √ √ √ protected √ √ public √ √ √
3、成员变量私有化和get/set
package com.atguigu.review;
public class Employee {
private String name;
private int age;
private char gender;
private double salary;
private boolean marry;
//快捷键:Alt + Insert
public String getName() {//调用get方法的目的是为了获取某个属性的值
return name;
}
public void setName(String name) { //调用set方法的目的是为了修改某个属性的值,或者说给某个属性赋值
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 double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean isMarry() {//get换成了is
return marry;
}
public void setMarry(boolean marry) {
this.marry = marry;
}
}
6.2 继承
1、继承的好处/目的
(1)代码的复用:在子类中复用父类的所有的成员变量和成员方法。(如果从逻辑意义上来说,就是要在原有的某个事物中再细分出更加具体的某个子类别)
(2)代码的扩展:在子类中扩展父类中没有的成员变量或成员方法。(如果从逻辑意义上来说,就是要表现的更加具体,更加具体就是通过更多的成员变量来进行描述,更多的可用功能来表示)。
(3)表示两个事物之间的is-a的关系。
2、问题?子类和父类谁大?
这里大家要明确这里,比较大小的关系是从事物的范围角度来说。
父类 > 子类
父类能表示的范围更广。子类更具体,范围更窄。
父类是Person,子类是Man,Person的范围更大,Man的范围更小。
错误的理解?子类的成员更多,应该是子类>父类才对。错误的原因是,成员更多只是表明它更具体而已,,不是更大。
3、继承的语法
【修饰符】 class 子类 extends 父类{
}
4、继承的要求和特点
(1)Java的类只支持单继承==>只有一个亲生父亲
(2)Java支持多层继承,即表示父类仍然可以有父类,父类的父类的成员也一并会继承到子类中。==>代代相传
(3)同一个父类可以同时存在多个子类。 ==>子孙满堂
(4)父类中所有的成员变量和成员方法都会继承到子类中。(如果从逻辑意义上来说,子类是父类的某个子类别,而类的定义是一类具体相同特性的事物的抽象描述,那么父类中应该是所有子类共同的特征。那么子类在继承父类时,就应该会继承父类所有的特点)。但是要注意的是,在父类中声明为private,在子类中不能直接使用。跨包的话,在父类中声明为private和缺省的,在子类不能直接使用。
但是这个特征子类要有,意味着在创建子类对象时,就要检查所有分类都声明了什么成员变量(哪怕是私有的),在子类对象中就要为这些成员变量开辟内存空间。
注意:父类对象中的值,不会继承到子类中。
(5)如果父类中某个方法的实现不适合子类,那么子类可以对这个方法进行重写。但是要注意,重写时要遵循重写的要求。
5、方法重写(Override)
要求:
(1)方法名必须相同
(2)形参列表也必须相同
(3)返回值类型
基本数据类型和void:必须相同
引用数据类型:<= (子类重写父类的方法时,子类重写的方法的返回值类型 可以是父类被重写方法的返回值类型的子类)
class Father{
public A method(){
//.....
}
}
class Son extends Father{
public B method(){
//.....
}
}
class Sub extends Father{
public A method(){
//.....
}
}
//B是A的子类
(4)权限修饰符:>=
如果父类被重写方法的权限修饰符是private的,那么子类是无法进行重写。如果父类被重写方法的权限修饰符是缺省的,那么本包的子类可以重写,跨包的子类就不能重写。如果父类被重写方法的权限修饰符是protected,那么无论是本包还是跨包的子类,都可以对它进行重写,而且重写后的方法的权限修饰符可以是protected,也可以是public。如果父类被重写方法的权限修饰符是public,那么无论是本包还是跨包的子类,都可以对它进行重写,而且重写后的方法的权限修饰符只能是public。
(5)其他要求(待补充)
注意:
方法重写(Override)和方法重载(Overload)容易混淆?
方法重载的要求:方法名相同,形参列表必须不同,不看返回值类型,也不看权限修饰符。严格来说,方法重载是在一个类中的两个或多个方法。如果宽泛一点,如果子类定义了和父类某个方法名相同,形参列表不同的方法,也可以归到重载中。
6.3 多态
1、多态的好处
(1)可以让代码编写更灵活
即某个变量是A类型,那么给这个变量赋值可以是A类的对象,也可以是A类的子类对象。
(2)让Java也支持动态绑定技术
即某个变量编译时是A类型,那么通过这个变量调用某个方法时,它不一定是执行A类中的方法体,可能是调用子类“重写的”方法体。
class A{
public void method(){
//....(1)
}
}
class B extends A{
public void method(){
//....(2)
}
}
class Demo{
private A a;
public void test(){
a.method();//看起来它应该执行 (1),但是实际上不一定执行的是(1),可能是(2)
//什么情况下执行(2),当a变量被赋值为B类的对象。
//多种形态,a对象可以是很多情况。可以是A类自己的对象,也可以是A类的子类的对象
}
}
2、多态的现象发生的前提条件
(1)继承:父子类关系
(2)虚方法的重写
什么是虚方法?可以被重写的方法。
3、多态的现象
编译时看父类,运行时看子类
某个变量它的类型有两种情况:
A a = new B();
a.虚方法()
//从编译的角度来说,a变量是A类型,称为编译时类型
//从运行的角度来说,a变量是B类型,称为运行时类型
4、多态的应用场景
(1)某个变量(局部变量、成员变量、形参)声明为父类类型,而实际赋值的是子类对象
(2)某个方法的返回值类型是父类类型,而实际返回的是子类的对象
(3)某个数组声明时,元素类型声明为父类类型,而实际存储的元素对象是子类对象
要特别注意,此时这些情况拿到的变量或元素在调用虚方法时,警惕它可能执行的是子类重写的方法。



