学习目的
- 掌握java基本语句的执行流程与使用
- 了解java方法的概念
- 掌握java方法的定义与使用
- 掌握java方法的分类(方法重写、方法重载)
- 了解方法递归的概念与使用
一.选择语句
- 定义
选择语句又称为分支语句,通过对给定的条件进行判断,从而决定执行两个或多个分支中的哪一支。 - 分类:if选择语句、switch选择语句
- 效率
选择语句的判断条件是布尔表达式<逻辑判断式>时,使用if语句;判断条件是几个常量的数据时,使用switch语句。根据不同的判断条件选择控制语句,性能和效率各不同。
1.1 if()语句
- 原理
影响事件执行的条件总时两方面的,if语句可以有0个或以上个分支执行,即可能一个分支都不执行。 -
判断条件:布尔表达式
1.2 if-else语句
- 原理
影响事件运行的条件不止一方面,可能有多个条件连带性触发,if-else语句肯定会有一个分支执行。 -
判断条件:布尔表达式
1.3 if-else if- else if
- 原理
有0个或以上个分支执行,即可能一个分支都不执行。 -
判断条件:布尔表达式
1.4 if-else if-else if-else
- 原理
肯定会有一个分支执行。 -
判断条件:布尔表达式
1.5 switch-case
- 原理
根据switch条件的数值匹配case的数值来执行case。 - 判断条件
- JDK1.6 之前:只允许int类型作为判断条件值,也可以将 byte、short、char 类型作为判断条件<小容量向大容量自动转换int 类型>
-
JDK1.7 之后: 在基础之上,允许采用String类型作为判断条件值
- 原理阐述
如上原理图,先拿c和表达式1比较,相等则执行语句 1;不相等,继续拿c和表达式 2 进行,相等则执行语句2;如果不相等,继续...;如果 c 和所有的分支表达式都不相等,则执行 default 分支“语句 n+1”。
- 注意点:default 语句可以写在任何位置,但它的执行时机是不变的,永远都是在所有分支都没有匹配成功的时候才会执行。
1.5.1 case 合并
- 合并解释
case合并指的是匹配switch条件的多条case可能执行结果相同,因此将多个case写到一起,节省代码量。
switch(c){
case c1 : case c2 : case c3: case c4 :
语句1;
break;
case c5:
语句2;
break;
default :
语句3;
}
1.5.2 case 穿透
- 原理
switch 语句中某个分支匹配成功后,开始执行此分支,遇到当前分支中的break语句时则结束该分支;若当前分支中没有break语句,则发生 case 穿透--即不需要分支匹配而直接进入下一个分支执行,直到遇到break为止。
二.循环语句
- 定义
循环语句就是在程序中需要重复执行的某些语句。循环语句是由循环体<反复执行的程序>及循环的终止条件两部分组成的。 - 分类
for 循环、while 循环、do..while 循环,后期扩展for-each增强循环、迭代器循环。
2.1 for()循环
- 原理
- 先执行初始化表达式<只执行一次>
- 到布尔表达式,结果为true,循环继续; 结果为false,循环结束
- 布尔表达式为true后,开始执行循环体,循环体结束
- 回到更新表达式,更新操作后
- 重新回到布尔表达式<以此为一次循环>
-
循环条件
初始化表达式、布尔表达式、更新表达式都不是必须的,当布尔表达式缺失时,没有条件控制前提下会形成死循环。
-
循环执行原理
注意点
for 循环内部声明的变量是局部变量,因此只在 for 循环中可见/有效,该变量随着循环体的结束而释放其在栈内存中的空间。-
循环嵌套
在一个循环A中加入另外一个循环B,即B作为A的循环体。
执行顺序:A初始化--A布尔表达式--A循环体<--B初始化--B布尔表达式--B循环体--B更新表达式--B布尔表达式>--A更新表达式
2.2 while()循环
- 原理
- 先判断布尔表达式, true则执行循环体;
- 循环体结束,再次判断布尔表达式,还是 true再执行循环体;
- 直到布尔表达式为 false 结束循环。
-
判断条件
while()循环判断条件为布尔表达式,因此循环的执行结果为0次或以上。
-
执行原理
2.3 do-while循环
- 原理
do-while循环,先执行do里面循环体,再执行while判断布尔表达式,判断后再回来决定是否执行循环体。do-while循环是执行在前,判断在后,因此循环体执行次数为1次或以上。 -
条件
布尔表达式,但保证了循环体肯定至少执行一次。
-
循环执行原理
- 注意点
do..while循环最后的一个半角的分号 ; 不能丢。 - 常见业务背景案例
循环判断一个系统用户的用户名和密码是否正确?
java.util.Scanner scanner = new java.util.Scanner(System.in);
String username;
String password;
//业务要求:先输入用户名和密码,再判断是否正确
do{
System.out.print("用户名:");
username = scanner.next();
System.out.print("密码:");
password = scanner.next();
}while(!username.equals("admin") || !password.equals("123"));
System.out.println("登录成功,欢迎" + username + "回来!");
三. 转向语句
- 定义
转向语句是用于实现 对执行循环体过程中 进行程序跳转的语句,即跳出循环的语句。 - 分类
break语句、continue语句、return语句
3.1 break语句
- 原理
break语句用于循环嵌套时,可以跳出离其最近的一层循环,遵循就近原则。break后面接标识时,还可以跳出指定的循环。 - 使用位置
switch 语句,while循环,for循环 - 使用方法
在循环体后面 break; 或 break 循环标识; - 示例
//在外层遍历5次的循环里,嵌套一个内层遍历5次的循环,当内层遍历满足条件=3时,break;则输出结果是5次0、1、2
for (int i=0;i<5;i++){
for (int j=0;j<5;j++){
if (j == 3){
break;
}
System.out.println(j);
}
}
3.2 continue语句
- 原理
continue语句用于循环体中表示终止当前本次循环,直接进入下一次循环继续执行。 - 使用方法
在循环体后面 continue; 表示跳出该次循环 -
示例
在外层遍历5次的循环里,嵌套一个内层遍历5次的循环,当内层遍历满足条件=3时,continue;则输出结果是5次1、2、4、5
3.3 return语句
- 原理
return语句是最大的转向,结束的是整个方法程序。return语句一旦执行,整个方法就结束。 - 区别
break 和 continue都是结束循环并未结束方法,而return出现则结束整个方法,return后面的语句也不再执行<因此return后面不能再写语句>
四.方法
- 方法定义
方法就是现实中事物的行为,是对某一具体动作实现的描述。而java中将所有具有共性的行为 抽取出来用代码描述,就形成了java的方法。在面向对象的java中,方法是不可或缺的一部分,方法就是实现了对象与对象之间的联系。 - 方法的作用
方法是对某一个共性行为的描述,因此可以不断调用,减少代码量,提高代码复用率和可观性,提高可利用率,增强可读性。 - 定位
java中描述功能性的代码叫方法,C语言中叫函数。 - 执行原理
遵循自上而下的顺序依次逐行执行。 - 方法分类
- static静态方法:存在于类体中,和类加载时一起加载到方法区内存中
- 实例方法:实例方法又叫成员方法,需要创建对象后才能调用
- 构造方法:构造方法又分为无参构造和有参构造,构造方法用于在堆内存中new出一个对象
- 方法结构
[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
}
public static void main(String[] args) {
System.out.println(j);//方法体
}
4.1 方法调用
- 实质
方法调用的本质是参数的传递,将变量中保存的“值”复制一份再去赋值。从而完成对象间的消息传递。 - 值传递
基本数据类型的赋值,将字面量的值复制一份传递给另一个可以存储该值的变量 -
引用传递
引用数据类型的内存地址赋值,将对象的内存地址传递给另一个变量,由另一个变量指向该内存地址
4.2 方法与栈数据结构
数据结构
数据结构是指存储数据的一种结构形式,是数据相互之间存在一种或多种特定关系的集合。-
栈数据结构
栈是一种 一端封闭一端打开的数据结构,特点是先进后出,最开始进入栈的数据最后出来,最后一个进来的最先出栈。栈使用 压栈和出栈caozuo 完成先进后出。
疑问
为什么方法执行过程的内存要采用栈这种数据结构呢?为什么不选择其它数据结构呢?
- 答疑:因为方法体当中的代码是顺序执行的,必须遵循自上而下的顺序依次逐行执行。前一行代码必须执行结束之后,下一行代码才能执行,不能跳行执行。而栈数据结构的特点就是当后进的压栈未出栈时,最先开始压栈的不能进行出栈。
4.3 方法重载(Overload)
- 重载定义
方法重载指的是 在同一个类中,实现的功能类似、方法名相同、参数列表不同(参数个数不同,参数类型不同,参数顺序不同)的多个方法。 - 使用原理
Java 编译器通过检查调用的方法的 参数类型和个数 选择其中一个恰当的方法<先看方法名,再看形式参数列表>。 - 决定条件
java线程具体执行哪个方法,起决定性作用的是参数的数据类型,因此方法重载时参数名字随意。 - 特殊点
在平常开发中,使用的最多的重载方法是 System类的println()方法<查看java的原码>。
4.4 方法覆盖(方法重写Override)
- 方法覆盖定义
方法重写指的是在具有继承关系的父类与子类中,子类重写/覆盖父类的方法以满足子类特有的需求。重写后的方法,方法名相同,实现的功能可以不同,因为子类需要对父类的方法进行重写添加子类自己的属性。 - 条件
- 相同的方法名,相同的返回值类型,相同的参数列表(参数列表若不同则为方法重载)
- 访问权限不比父类更低,抛出异常不能比父类更多
- 原则
- 私有的方法不能被覆盖:编译能通过不会报错
- 静态方法不能被覆盖:静态方法覆盖无意义,静态方法属于类级别,不涉及对象级别和多态
- 构造方法不能被继承,因此也不能被覆盖
- 方法覆盖只和方法有关,和属性无关;
4.5 注意点(Object类--重点)
- 必须重写的方法
- String toString()方法:打印对象时默认调用的方法。不重写时,输出对象的类名@十六进制的哈希地址,重写后将对象的输出转换成字符串形式。在开发中,定义每一个类时都需要重写toString方法。
- boolean equals(Object obj) 方法:判断两个对象是否相等。不重写时,默认采用==号比较对象内存地址是否相等;重写后,比较对象的每一个属性是否都相等,从而判断两个对象是否相同。
- int hashCode():获取对象在内存的哈希码值。重写后,获得该对象的哈希码值经过哈希算法处理后的下标。
-
需要注意的方法
Java的子类不是必须重写父类所有方法的,两种情况:
- 父类方法为抽象方法时,子类必须重写(实现)所有父类的抽象方法;
- 父类方法为普通方法时,子类可以重写父类方法,也可以不重写。
4.6 方法递归
- 递归定义
方法递归指的是方法在执行过程中又调用了本身,不断循环。 - 递归存在问题
递归调用容易产生栈内存溢出<即使栈内存足够大但也是有限的>。不断的递归调用,没有结束条件时会一直方法压栈,导致栈内存不足,从而会发生StackOverflowError栈内存溢出错误
- 使用原则
能不用递归尽量不用,能用循环代替尽可能使用循环。 -
避免条件
检查递归的终止条件是否合法,调整堆栈空间大小
常见面试题
- 实现九九乘法表,并说明其实现原理?
答:
有几行,每一行有几个式子。第 1 行 1 个式子,第 2 行 2 个式子,第 3 行 3 个式子...,第 9 行 9 个式子。
for (int i = 1; i <= 9; i++) { //输出每一行:1、2、3、4...、9
for (int j = 1; j <= i; j++) { //输出当前行有几个式子
System.out.print(i + "*" + j + "=" + i * j + " ");
}
System.out.println();//输出每一行后换行
}
- Java方法的 Override(覆盖/重写) 和 Overload(重载)区别?
答:
- 重载:同一个类中,实现功能相同或者类似,因此方法名相同,但参数类型、参数个数、参数顺序不同的多个方法。
- 重写:对于产生了继承关系的父类和子类之间的方法的关系,子类继承过来的父类方法,但具体实现不同叫做重写。
谈谈==和equals()方法的深度区别?
答:
首先在JVM中,内存分为堆内存跟栈内存。当我们new 创建一个对象时,会调用对象的构造函数来开辟两块空间--一是将对象的数据存储到堆内存中,二是在栈内存中生成对应的引用(该引用指向堆内存创建对象的空间地址)。当调用时用的都是栈内存中的引用。(基本数据类型变量和引用类型都是是存储在栈内存中)
而==是判断两个栈内存中的变量或实例是否指向同一个堆内存空间(或方法区内存空间<常量时>),equals是判断两个变量或实例所指向的内存空间的值是否相同(一般需要重写该方法<同时重写hashcode()方法>)。==是指对内存地址进行比较 , equals()是对字符串的内容进行比较(原未重写的Object中的equals方法比较的是两个对象的内存地址)。==指引用是否相同, equals()指的是-值是否相同。打印 2 到 10000 的所有素数,每行显示 8 个素数
答:
素数即质数,除了1和它本身以外不再有其他因数的自然数。
int count = 0;//判断每行8个输出一次
for(int i=2;i<=10000;i++){ //求素数的数
boolean isZhiShu = true;
for(int j=2;j<i;j++){ //从2开始按模取余,直到取余到 i 本身,能除尽则不是素数
if(i % j == 0){
isZhiShu = false;//按模2取余,能除尽则不是素数
break;
} }
if(isZhiShu){ //是素数则输出,且限定每行输出8个
System.out.print(i + " ");//2 3 5 7 11 13 17 19
count++;
if(count == 8){
System.out.println();
count = 0;
}
}
}
编写程序,计算 5 的阶乘
答:
定义一个存储阶乘结果的变量result,开启一个循环,循环变量i的初始值为1,且最大值小于等于5,每一次循环体执行result *= i;循环体结束后再打印输出结果result。编写程序输出菱形或圣诞树
答:篮球从 5 米高的地方掉下来,每次弹起的高度是原来的 30%,经过几次弹起,篮球的高度是 0.1 米。
答:
第一次弹起530%=1.5,第二次弹起1.530%=0.45,第三次弹起0.4530%=0.135依次类推。初始值为5,循环初始变量i=1,判断式小于100,每次变化后高度为5=30%,当变化后高度<=0.1时,输出i。
double height = 5.0;
int count = 0;//弹起的次数
while(height > 0.1){
height *= 0.3;
count++;
}
System.out.println("弹起次数 = " + count);
for (int i = 1;i<100;i++){
height *=0.3;
count++;
if (height<0.1){
break;
}
}
- 使用递归完成 1~N的求和
答:
public static void main(String[] args) {
int n = N;
int result = accumulate(n);
System.out.println("1 到" + n + "的和是:" + result);
}
public static int accumulate(int n){
if(n == 1){
return 1;
}
return n + accumulate(n - 1);
}
- 定义一个方法选出2 个 int 类型较大的数据,并返回较大的数据。再定义一个方法可以选出 3 个 int 类型中较大的数据,并返回较大的数据。
答:先获取比较的数据,再比较数据的大小后输出。
public static int getBiger(int a , int b){
return a > b ? a : b;
}
public static int getBiger(int a , int b , int c){
return getBiger(a , b) > c ? getBiger(a , b) : c;
}