5.1 面向对象编程
5.1.1 面向过程和面对对象的区别
面向过程(Procedure Oriented)是一种以过程为中心的编程思想,它关注的是将问题分解成一个个详细的步骤,并通过函数实现每一个步骤,然后依次调用这些函数来解决问题。常见的面向过程的语言有C语言。
面向对象编程(Object-Oriented Programming,简称OOP)是一种计算机编程模型,它围绕数据或对象来组织软件设计,而不是围绕功能和逻辑。在面向对象的编程中,更强调对象与对象之间的交互,对象的属性和方法都被封装在对象内部。面向对象编程具有封装性、继承性和多态性三大特性。常见的面向对象的语言有Java,Python,PHP等。
举例:到超市购物,超市有500种商品,买了5样商品
面向过程思考:到超市购物,买了5样商品,回家放入冰箱
面向对象思考:到超市购物,超市所有商品创建实体,买了5样商品,回家放入冰箱
需求变更:
面向过程:又买了猪肉,修改代码,才能放入冰箱
面向对象:猪肉实体已经买了,无需修改代码,就把猪肉放入冰箱即可
5.1.2 类和对象
1、什么是类
类是一类具有相同特性的事物的抽象描述,是一组相关属性和行为的集合。
属性:就是该事物的状态信息。
行为:就是在你这个程序中,该状态信息要做什么操作,或者基于事物的状态能做什么。
2、什么是对象
对象是一类事物的一个具体个体(对象并不是找个女朋友)。即对象是类的一个实例,必然具备该类事物的属性和行为。
3、类与对象的关系
-
类是对一类事物的描述,是抽象的。是相同属性和方法的一组对象的集合。
-
对象是一类事物的实例,是具体的。
-
类是对象的模板,对象是类的实体。是能够看的到,摸得到的具体实体。
-
世间万物皆对象
5.1.3 如何定义类
1、类的定义格式
关键字:class(小写)
【修饰符】 class 类名{
}
类的定义格式举例:
public class Student{
}
2、对象的创建
关键字:new
new 类名()//也称为匿名对象
//给创建的对象命名
//或者说,把创建的对象用一个引用数据类型的变量保存起来,这样就可以反复使用这个对象了
类名 对象名 = new 类名();
那么,对象名中存储的是什么呢?答:对象地址
public class TestStudent{
public static void main(String[] args){
System.out.println(new Student());//Student@7852e922
Student stu = new Student();
System.out.println(stu);//Student@4e25154f
int[] arr = new int[5];
System.out.println(arr);//[I@70dea4e
}
}
发现学生对象和数组对象类似,直接打印对象名和数组名都是显示“类型@对象的hashCode值",所以说类、数组都是引用数据类型,引用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。
那么像“Student@4e25154f”是对象的地址吗?不是,因为Java是对程序员隐藏内存地址的,不暴露内存地址信息,所以打印对象时不直接显示内存地址,而是JVM帮你调用了对象的toString方法,将对象的基本信息转换为字符串并返回,默认toString方法返回的是“对象的运行时类型@对象的hashCode值的十六进制值”,程序员可以自己改写toString方法的代码(后面会讲如何改写)。
例如:做一个养宠物的小游戏
类:人、猫、狗等
public class Dog{
String type; //种类
String nickname; //昵称
int energy; //能量
final int MAX_ENERGY = 10000;
//吃东西
void eat(){
if(energy < MAX_ENERGY){
energy += 10;
}
}
}
public class Person{
String name;
char gender;
Dog dog;
//喂宠物
void feed(){
dog.eat();
}
}
public class Game{
public static void main(String[] args){
Person p = new Person();
p.name = "张三";
p.gender = '男';
p.dog = new Dog();
p.dog.type = "哈巴狗";
p.dog.nickname = "小白";
for(int i=1; i<=5; i++){
p.feed();
}
System.out.println(p.dog.energy);
}
}
5.2 包(package)
5.2.1 包的作用
(1)可以避免类重名:有了包之后,类的全名称就变为:包.类名
(2)可以控制某些类型或成员的可见范围
如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用。
(3)分类组织管理众多的类
例如:
-
java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread等,提供常用功能
-
java.net----包含执行与网络相关的操作的类和接口。
-
java.io ----包含能提供多种输入/输出功能的类。
-
java.util----包含一些实用工具类,如集合框架类、日期时间、数组工具类Arrays,文本扫描仪Scanner,随机值产生工具Random。
-
java.text----包含了一些java格式化相关的类
-
java.sql和javax.sql----包含了java进行JDBC数据库编程的相关类/接口
-
java.awt和java.swing----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
5.2.2 如何声明包
关键字:package
package 包名;
注意:
(1)必须在源文件的代码首行
(2)一个源文件只能有一个声明包的package语句
包的命名规范和习惯:
(1)所有单词都小写,每一个单词之间使用.分割
(2)习惯用公司的域名倒置开头
例如:com.haogu.xxx;
建议大家取包名时不要使用“java.xx"包
5.2.3 如何跨包使用类
注意:只有public的类才能被跨包使用
(1)使用类型的全名称
例如:java.util.Scanner input = new java.util.Scanner(System.in);
(2)使用import 语句之后,代码中使用简名称
import语句告诉编译器到哪里去寻找类。
import语句的语法格式:
import 包.类名;
import 包.*;
注意:
使用java.lang包下的类,不需要import语句,就直接可以使用简名称
import语句必须在package下面,class的上面
当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。一个使用全名称,一个使用简名称
package com.haogu.test02.pkg;
import com.haogu.test01.oop.Student;
import java.util.Date;
import java.util.Scanner;
public class TestPackage {
public static void main(String[] args) {
/* java.util.Scanner input = new java.util.Scanner(System.in);
com.haogu.test01.oop.Student stu = new com.haogu.test01.oop.Student();*/
Scanner input = new Scanner(System.in);
Student student = new Student();
}
}
5.3 成员变量
5.3.1 如何声明成员变量
【修饰符】 class 类名{
【修饰符】 数据类型 成员变量名;
}
public class Person{
String name;
char gender;
int age;
}
位置要求:必须在类中,方法外
类型要求:可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等)
修饰符:成员变量的修饰符有很多,例如:public、protected、private、static、volatile、transient、final等,后面会一一学习。
其中static可以将成员变量分为两大类,静态变量和非静态变量。其中静态变量又称为类变量,非静态变量又称为实例变量或者属性。接下来先学习实例变量。
5.3.2 对象的实例变量
1、实例变量的特点
(1)实例变量的值是属于某个对象的
- 必须通过对象才能访问实例变量
- 每个对象的实例变量的值是独立的
(2)实例变量有默认值
分类 | 数据类型 | 默认值 |
---|---|---|
基本类型 | 整数(byte,short,int,long) | 0 |
浮点数(float,double) | 0.0 | |
字符(char) | '\u0000' | |
布尔(boolean) | false | |
数据类型 | 默认值 | |
引用类型 | 数组,类,接口 | null |
2、实例变量的访问
实例对象.实例变量
package com.haogu.test03.field;
public class TestPerson {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "张三";
p1.age = 23;
p1.gender = '男';
Person p2 = new Person();
/*
(1)实例变量的值是属于某个对象的
- 必须通过对象才能访问实例变量
- 每个对象的实例变量的值是独立的
(2)实例变量有默认值
*/
System.out.println("p1对象的实例变量:");
System.out.println("p1.name = " + p1.name);
System.out.println("p1.age = " + p1.age);
System.out.println("p1.gender = " + p1.gender);
System.out.println("p2对象的实例变量:");
System.out.println("p2.name = " + p2.name);
System.out.println("p2.age = " + p2.age);
System.out.println("p2.gender = " + p2.gender);
}
}
3、实例变量的内存分析
内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。Java虚拟机要运行程序,必须要对内存进行空间的分配和管理,每一片区域都有特定的处理数据方式和内存管理方式。
JVM的运行时内存区域分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器几大块。
Java对象保存在内存中时,由以下三部分组成:
-
对象头
-
Mark Word:记录了和当前对象有关的GC、锁等信息。(后面再讲)
-
指向类的指针:每一个对象需要记录它是由哪个类创建出来的,而Java对象的类数据保存在方法区,指向类的指针就是记录创建该对象的类数据在方法区的首地址。该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
-
数组长度(只有数组对象才有)
-
-
实例数据
-
即实例变量的值
-
-
对齐填充
-
因为JVM要求Java对象占的内存大小应该是8bit的倍数,如果不满足该大小,则需要补齐至8bit的倍数,没有特别的功能。
-
5.3.3 静态成员
静态成员:
静态成员变量(由static修饰,内存加载一次):常表示在线人数需要被共享的信息,可以被共享访问。
同一类中静态成员变量的访问可以省略类名。同一个类中,访问静态方法,类名可以省略不写【这点就可以解释在主函数main中,可以不用写类名,直接写方法名就可以用这个方法。
使用场景区别
实例方法:表示对象自己的行为,且方法中需要访问实例成员。
静态方法:如果该方法是以执行一个共用功能为目的。
内存原理
当类被加载到方法区的时候,其静态方法也已经被加载了出来,但成员方法还暂时没有加载。 Main方法加载到栈内存,然后创建对象(在堆内存中创建对象,在栈内存中的main方法中存放着对象在堆内存的地址。)对象中包含着实例成员变量和实例成员方法(方法在方法引用中。)当需要用到方法时,实例方法会加载到方法区中。
5.4 方法(Method)
5.4.1 方法的概念
方法也叫函数,是一组代码语句的封装,从而实现代码重用,从而减少冗余代码,通常它是一个独立功能的定义,方法是一个类中最基本的功能单元
//比如之前接触到的
Math.random()的random()方法
System.out.println(x)的println(x)方法
Scanner input = new Scanner(System.in);
input.nextInt()的nextInt()方法
5.4.2 方法的特点
(1)必须先声明后使用。
(2)不调用不执行,调用一次执行一次。
5.4.3 如何声明方法
1、声明方法的位置
声明方法的位置必须在类中-方法外,即不能在一个方法中直接定义另一个方法。
正确位置示例
类{
方法1(){
}
方法2(){
}
}
错误示例
类{
方法1(){
方法2(){ //位置错误
}
}
}
2、声明方法的语法格式
【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】{
方法体的功能代码
}
一个完整的方法 = 方法头 + 方法体。
-
方法头就是 【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】,也称为方法签名,
通常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。
-
方法体就是方法被调用后要指定的代码,也是完成方法功能的具体实现代码。
3、方法头
包含5个部分,但是有些部分是可能缺省的
①修饰符:可选的。
方法的修饰符也有很多,例如:final、static、public、protected、private等,后面会一一学习。
②返回值类型: 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者
-
基本数据类型
-
引用数据类型
-
无返回值类型:void
③方法名:遵循标识符的规则且见名知意即可
④参数列表:表示完成方法体功能时需要外部提供的数据列表
-
无论是否有参数,()不能省略
-
如果有参数,每一个参数都要指定数据类型和参数名,多个参数之间使用逗号分隔,例如:
-
一个参数: (数据类型 参数名)
-
二个参数: (数据类型1 参数1, 数据类型2 参数2)
-
⑤throws 异常列表:可选,在异常章节再讲
4、方法体
方法体必须有{ }括起来,在{ }中编写完成方法功能的代码。
package com.huohua.day0412.Model;
public class Shop {
/*
* 知道如何进行定义方法
* 参数和返回值使用
* 了解this使用和不使用的区别
*
* */
String shopName;
String proName;
// 无参 无返回值
public void shopping() {
System.out.println("我现在可以去购物了");
}
// 有参 无返回值
public void shopped1(String productName) {
proName = productName;
}
// 将使用this来区别参数还是对象中的属性
public void shopped2(String productName) {
this.proName = proName;
}
// 无参 有返回值
public String getShopName() {
return "大型超市购物";
}
// 有参 有返回值
public String shopAll(String shopName) {
this.shopName = shopName;
return "我今天去了" + shopName + "进行了一次完美的购物";
}
}
5.4.4 如何调用实例方法
1、方法调用语法格式
对象.非静态方法(【实参列表】)
回忆之前代码
//1、创建Scanner的对象
Scanner input = new Scanner(System.in);//System.in默认代表键盘输入//2、提示输入xx
System.out.print("请输入一个整数:"); //对象.非静态方法(实参列表)//3、接收输入内容
int num = input.nextInt(); //对象.非静态方法()
2、形参和实参
-
形参(formal parameter):在定义方法时方法名后面括号中声明的变量称为形式参数(简称形参)即形参出现在方法定义时。
-
实参(actual parameter):调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数(简称实参)即实参出现在方法调用时。
-
调用时,实参的个数、类型、顺序顺序要与形参列表一一对应。如果方法没有形参,就不需要也不能传实参。
-
无论是否有参数,声明方法和调用方法是()都不能丢失
3、返回值问题
-
return语句的作用是结束方法的执行,并将方法的结果作为返回值返回。
-
如果返回值类型不是void,方法体中必须保证一定有 return 返回值。
-
如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执行,那么return后面不能跟返回值,直接写return 就可以。
-
return语句后面就不能再写其他代码了,否则会报错:Unreachable code。
-
5.4.5 实例方法使用当前对象的成员(this的使用)
在实例方法中还可以使用当前对象的其他成员。用this表示当前对象。
-
this:在实例方法中,表示调用该方法的对象
-
如果没有歧义,完全可以省略this。
// 将使用this来区别参数还是对象中的属性
public void shopped2(String productName) {
this.proName = proName;
}
5.4.6 方法调用内存分析
-
方法不调用不执行,调用一次执行一次;
-
每次调用会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值,当方法执行结束后,会释放该内存,称为出栈;
-
如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。
栈结构:先进后出,后进先出。
package com.haogu.test04.method;
public class MethodMemory {
public static void main(String[] args) {
Rectangle r1 = new Rectangle();
Rectangle r2 = new Rectangle();
r1.length = 10;
r1.width = 2;
r1.print('#');
System.out.println("r1对象:" + r1.getInfo());
System.out.println("r2对象:" + r2.getInfo());
}
}
5.5 参数问题
5.5.1 特殊参数之一:可变参数
在JDK1.5之后,当定义一个方法时,形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变参数。可变参数的格式:
【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){ }
可变参数的特点和要求:
(1)一个方法最多只能有一个可变参数
(2)如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个
(3)在声明它的方法中,可变参数当成数组使用
(4)其实可以书写...
【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型[] 形参名){ }
只是后面这种定义,在调用时必须传递数组,而前者更灵活,既可以传递数组,又可以直接传递数组的元素,这样更灵活了。
1、方法只有可变参数
案例:求n个整数的和
package com.huohua.day0412.Model;
public class SumToolss {
public int sum1(int[] arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
//可变参数当作数组来使用
public int sum2(int... numArr) {
int sum = 0;
for (int i = 0; i < numArr.length; i++) {
sum += numArr[i];
}
return sum;
}
public int sum3(int multiple, int... numArr) {
int sum = 0;
for (int i = 0; i < numArr.length; i++) {
sum += numArr[i];
}
return sum * multiple;
}
}
package com.huohua.day0412;
import com.huohua.day0412.Model.SumToolss;
public class TestSumToolss {
public static void main(String[] args) {
SumToolss sumTools = new SumToolss();
int[] arr = {12, 3, 5, 6, 7};
int rel1 = sumTools.sum1(arr);
int rel2 = sumTools.sum2(arr);
int rel3 = sumTools.sum2(12, 3, 5, 6, 7);
int rel4 = sumTools.sum3(2,12, 3, 5, 6, 7);
int rel5 = sumTools.sum3(2,arr);
System.out.println(rel1);
System.out.println(rel2);
System.out.println(rel3);
System.out.println(rel4);
System.out.println(rel5);
}
}
2、方法包含非可变参数和可变参数
-
非可变参数部分必须传入对应类型和个数的实参;
-
可变参数部分按照可变参数的规则传入0~n个对应类型的实参或传入1个对应类型的数组实参;
案例:
n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回空字符串""
package com.huohua.day0412;
public class TestConcat2 {
static String concat1(String str, String... arr) {
String strEnd = "";
for (int i = 0; i < arr.length; i++) {
if (i == 0) {
strEnd += arr[i];
} else {
strEnd += str + arr[i];
}
}
return strEnd;
}
public static void main(String[] args) {
String rel1 = concat1("-", "a", "b", "c");
System.out.println(rel1);
String rel2 = concat1("-", "");
System.out.println(rel2);
}
}
5.5.2 特殊参数之二:命令行参数(了解)
通过命令行给main方法的形参传递的实参称为命令行参数
public class TestCommandParam{
//形参:String[] args
public static void main(String[] args){
System.out.println(args);
System.out.println(args.length);
for(int i=0; i<args.length; i++){
System.out.println("第" + (i+1) + "个参数的值是:" + args[i]);
}
}
}
命令行:
java TestCommandParam
java TestCommandParam 1 2 3
java TestCommandParam hello haogu
5.5.3 方法的参数传递机制
方法的参数传递机制:实参给形参赋值,那么反过来形参会影响实参吗?
-
方法的形参是基本数据类型时,形参值的改变不会影响实参;
-
方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。
-
注意:String、Integer等特殊类型容易错
-
1、形参是基本数据类型
package com.haogu.test05.param;
public class PrimitiveTypeParam {
void swap(int a, int b){//交换两个形参的值
int temp = a;
a = b;
b = temp;
}
public static void main(String[] args) {
PrimitiveTypeParam tools = new PrimitiveTypeParam();
int x = 1;
int y = 2;
System.out.println("交换之前:x = " + x +",y = " + y);//1,2
tools.swap(x,y);//实参x,y是基本数据类型,给形参的是数据的“副本”,调用完之后,x与y的值不变
System.out.println("交换之后:x = " + x +",y = " + y);//1,2
}
}
2、形参是数组
package com.haogu.test05.param;
public class ArrayTypeParam {
void sort(int[] arr){//给数组排序,修改了数组元素的顺序,这里对arr数组进行排序,就相当于对nums数组进行排序
for (int i = 1; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
void iterate(int[] arr){//输出数组的元素,元素之间使用空格分隔,元素打印完之后换行
//这个方法没有修改元素的值
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
ArrayTypeParam tools = new ArrayTypeParam();
int[] nums = {4,3,1,6,7};
System.out.println("排序之前:");
tools.iterate(nums);//实参nums把数组的首地址给形参arr,这个调用相当于输出nums数组的元素
//对数组的元素值没有影响
tools.sort(nums);//对nums数组进行排序
System.out.println("排序之后:");
tools.iterate(nums);//输出nums数组的元素
//上面的代码,从头到尾,堆中只有一个数组,没有产生新数组,无论是排序还是遍历输出都是同一个数组
}
}
3、形参是引用数据类型
package com.haogu.test05.param;
public class ReferenceTypeParam {
void swap(MyData my){//形参my是引用数据类型,接收的是对象的地址值,形参my和实参data指向同一个对象
//里面交换了对象的两个实例变量的值
int temp = my.x;
my.x = my.y;
my.y = temp;
}
public static void main(String[] args) {
ReferenceTypeParam tools = new ReferenceTypeParam();
MyData data = new MyData();
data.x = 1;
data.y = 2;
System.out.println("交换之前:x = " + data.x +",y = " + data.y);//1,2
tools.swap(data);//实参是data,给形参my的是对象的地址值,调用完之后,x与y的值交换
System.out.println("交换之后:x = " + data.x +",y = " + data.y);//2,1
}
}
public class MyData{
int x;
int y;
}
4、形参指向新对象
package com.haogu.test05.param;
public class AssignNewObjectToFormalParam {
void swap(MyData my){
my = new MyData(); //这里让my形参指向了新对象,此时堆中有两个MyData对象,和main中的data对象无关
int temp = my.x;
my.x = my.y;
my.y = temp;
}
public static void main(String[] args) {
//创建这个对象的目的是为了调用swap方法
AssignNewObjectToFormalParam tools = new AssignNewObjectToFormalParam();
MyData data = new MyData();
data.x = 1;
data.y = 2;
System.out.println("交换之前:x = " + data.x +",y = " + data.y);//1,2
tools.swap(data);//调用完之后,x与y的值交换?
System.out.println("交换之后:x = " + data.x +",y = " + data.y);//1,2
}
}
5.6 方法的重载
-
方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
-
参数列表:数据类型个数不同,数据类型不同(按理来说数据类型顺序不同也可以,但是很少见,也不推荐,逻辑上容易有歧义)。
-
重载方法调用:JVM通过方法的参数列表,调用匹配的方法。
-
先找个数、类型最匹配的
-
再找个数和类型可以兼容的,如果同时多个方法可以兼容将会报错
-
案例,用重载实现:
(1)定义方法求两个整数的最大值
(2)定义方法求三个整数的最大值
(3)定义方法求两个小数的最大值
(4)定义方法求n个整数最大值
package com.haogu.test06.overload;
public class MathTools {
//求两个整数的最大值
public int max(int a,int b){
return a>b?a:b;
}
//求两个小数的最大值
public double max(double a, double b){
return a>b?a:b;
}
//求三个整数的最大值
public int max(int a, int b, int c){
return max(max(a,b),c);
}
//求n整数的最大值
public int max(int... nums){
int max = nums[0];//如果没有传入整数,或者传入null,这句代码会报异常
for (int i = 1; i < nums.length; i++) {
if(nums[i] > max){
max = nums[i];
}
}
return max;
}
}
1、找最匹配的
package com.haogu.test06.overload;
public class MethodOverloadMosthMatch {
public static void main(String[] args) {
MathTools tools = new MathTools();
System.out.println(tools.max(5,3));
System.out.println(tools.max(5,3,8));
System.out.println(tools.max(5.7,2.5));
}
}
2、找唯一可以兼容的
package com.haogu.test06.overload;
public class MethodOverloadMostCompatible {
public static void main(String[] args) {
MathTools tools = new MathTools();
System.out.println(tools.max(5.7,9));
System.out.println(tools.max(5,6,8,3));
// System.out.println(tools.max(5.7,9.2,6.9)); //没有兼容的
}
}
5.7 方法的递归调用
递归调用:方法自己调用自己的现象就称为递归。
递归的分类:
-
递归分为两种,直接递归和间接递归。
-
直接递归称为方法自身调用自己。
-
间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
注意事项:
-
递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
-
在递归中虽然有限定条件,但是递归深度不能太深,否则效率低下,或者也会发生栈内存溢出。
-
能够使用循环代替的,尽量使用循环代替递归
-
案例:计算斐波那契数列(Fibonacci)的第n个值,斐波那契数列满足如下规律,
1,1,2,3,5,8,13,21,34 ....
即从第三个数开始,一个数等于前两个数之和。假设f(n)代表斐波那契数列的第n个值,那么f(n)满足:
f(n) = f(n-2) + f(n-1);
package com.huohua.day0412;
import java.util.Scanner;
public class Feibo {
int fei(int n) {
if (n < 1) {
return 1;
}
if (n == 1 || n == 2) {
return 1;
}
return fei(n - 2) + fei(n - 1);
// n = 8
// 第一次返回 fei(6)+fei(7)
// 第二次返回 fei(4)+fei(5) + fei(5)+fei(6)
// 第三次返回 fei(3)+fei(2) + fei(3)+fei(4) + fei(3)+fei(4) + fei(4)+fei(5)
// ...
// 1+1+1+1+1+...
}
public static void main(String[] args) {
// 计算斐波那契数列的第 n个值
Feibo feibo = new Feibo();
Scanner scan = new Scanner(System.in);
System.out.println("请输入你要求第几个值");
int n = scan.nextInt();
int num = feibo.fei(n);
System.out.println("斐波那契数列的第" + n + "个值是" + num);
}
}
5.8 对象数组
数组是用来存储一组数据的容器,一组基本数据类型的数据可以用数组装,那么一组对象也可以使用数组来装。
即数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。
注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。
对象数组的声明和使用
package com.huohua.day0412;
import com.huohua.day0412.Model.Dog;
public class TestDog2 {
public static void main(String[] args) {
Dog[] dog = new Dog[5];
// 第一个
Dog dog1 = new Dog();
dog1.name = "小黑";
dog1.age = 2;
dog[0] = dog1;
// 数组对象中得先设置值,然后再使用对象中的属性和方法
// 排序时是比较对象的属性值
// dog[0].name = "小黑";
// dog[0].age = 2;
// 第二个
Dog dog2 = new Dog();
dog2.name = "老黄";
dog2.age = 10;
dog[1] = dog2;
// 第三个
Dog dog3 = new Dog();
dog3.name = "小白";
dog3.age = 5;
dog[2] = dog3;
// 第四个
Dog dog4 = new Dog();
dog4.name = "薯条";
dog4.age = 6;
dog[3] = dog4;
// 第五个
Dog dog5 = new Dog();
dog5.name = "可乐";
dog5.age = 4;
dog[4] = dog5;
// 根据狗的年龄,进行狗的对象数据的排序
for (int i = 0; i < dog.length - 1; i++) {
for (int j = 0; j < dog.length - i - 1; j++) {
if (dog[j].age > dog[j + 1].age) {
Dog temp = new Dog();
temp = dog[j];
dog[j] = dog[j + 1];
dog[j + 1] = temp;
}
}
}
String dogMsg = "";
for (int i = 0; i < dog.length; i++) {
dogMsg += dog[i].name + dog[i].age + "岁" + " ";
}
System.out.println(dogMsg);
}
}
package com.huohua.day0412.Model;
public class Dog {
// 狗的名字
public String name;
// 狗的年龄
public int age;
}