第5章 面向对象(上) 5.1 面向对象编程 5.1.2 类和对象 1、什么是类 类 是一类具有相同特性的事物的抽象描述,是一组相关属性 和行为 的集合。
属性 :就是该事物的状态信息。
行为 :就是在你这个程序中,该状态信息要做什么操作,或者基于事物的状态能做什么。
2、什么是对象 对象 是一类事物的一个具体个体(对象并不是找个女朋友)。即对象是类的一个实例 ,必然具备该类事物的属性和行为。
例如:做一个养宠物的小游戏
类:人、猫、狗等
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Dog { String type; String nickname; int energy; final int MAX_ENERGY = 10000 ; void eat () { if (energy < MAX_ENERGY){ energy += 10 ; } } }
1 2 3 4 5 6 7 8 9 10 public class Person { String name; char gender; Dog dog; void feed () { dog.eat(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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); } }
3、类与对象的关系
类是对一类事物的描述,是抽象的 。
对象是一类事物的实例,是具体的 。
类是对象的模板,对象是类的实体 。
5.1.3 如何定义类 1、类的定义格式 关键字:class(小写)
类的定义格式举例:
1 2 3 public class Student { }
2、对象的创建 关键字:new
1 2 3 4 5 new 类名()类名 对象名 = new 类名();
那么,对象名中存储的是什么呢?答:对象地址
1 2 3 4 5 6 7 8 9 10 11 public class TestStudent { public static void main (String[] args) { System.out.println(new Student ()); Student stu = new Student (); System.out.println(stu); int [] arr = new int [5 ]; System.out.println(arr); } }
发现学生对象和数组对象类似,直接打印对象名和数组名都是显示“类型@对象的hashCode值”,所以说类、数组都是引用数据类型,引用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。
那么像“Student@4e25154f”是对象的地址吗?不是,因为Java是对程序员隐藏内存地址的,不暴露内存地址信息,所以打印对象时不直接显示内存地址,而是JVM帮你调用了对象的toString方法,将对象的基本信息转换为字符串并返回,默认toString方法返回的是“对象的运行时类型@对象的hashCode值的十六进制值”,程序员可以自己改写toString方法的代码(后面会讲如何改写)。
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
注意:
(1)必须在源文件的代码首行
(2)一个源文件只能有一个声明包的package语句
包的命名规范和习惯: (1)所有单词都小写,每一个单词之间使用.分割 (2)习惯用公司的域名倒置开头
例如:com.atguigu.xxx;
建议大家取包名时不要使用“java.xx”包
5.2.3 如何跨包使用类 ==注意:==只有public的类才能被跨包使用
(1)使用类型的全名称
例如:java.util.Scanner input = new java.util.Scanner(System.in);
(2)使用import 语句之后,代码中使用简名称
import语句告诉编译器到哪里去寻找类。
import语句的语法格式:
1 2 import 包.类名;import 包.*;
注意:
使用java.lang包下的类,不需要import语句,就直接可以使用简名称
import语句必须在package下面,class的上面
当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。一个使用全名称,一个使用简名称
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.atguigu.test02.pkg;import com.atguigu.test01.oop.Student;import java.util.Date;import java.util.Scanner;public class TestPackage { public static void main (String[] args) { Scanner input = new Scanner (System.in); Student student = new Student (); Date d1 = new Date (); java.sql.Date d2 = new java .sql.Date(0 ); } }
5.3 成员变量 5.3.1 如何声明成员变量 1 2 3 【修饰符】 class 类名{ 【修饰符】 数据类型 成员变量名; }
示例:
1 2 3 4 5 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、实例变量的访问
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.atguigu.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 (); 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的运行时内存区域分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器几大块。
区域名称
作用
程序计数器
程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址
本地方法栈
当程序中调用了native的本地方法时,本地方法执行期间的内存区域
方法区
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
堆内存
存储对象(包括数组对象),new来创建的,都存储在堆内存。
虚拟机栈
用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。
Java对象保存在内存中时,由以下三部分组成:
对象头
Mark Word:记录了和当前对象有关的GC、锁等信息。(后面再讲)
指向类的指针:每一个对象需要记录它是由哪个类创建出来的,而Java对象的类数据保存在方法区,指向类的指针就是记录创建该对象的类数据在方法区的首地址。该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
数组长度(只有数组对象才有)
实例数据
对齐填充
因为JVM要求Java对象占的内存大小应该是8bit的倍数,如果不满足该大小,则需要补齐至8bit的倍数,没有特别的功能。
5.4 方法(Method) 5.4.1 方法的概念 方法也叫函数,是一组代码语句的封装,从而实现代码重用,从而减少冗余代码,通常它是一个独立功能的定义,方法是一个类中最基本的功能单元。
1 2 3 4 5 6 Math.random()的random()方法 Math.sqrt(x)的sqrt(x)方法 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 3 4 5 6 7 8 类{ 方法1 (){ } 方法2 (){ } }
错误示例:
1 2 3 4 5 6 7 类{ 方法1 (){ 方法2 (){ } } }
2、声明方法的语法格式 1 2 3 【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】{ 方法体的功能代码 }
(1)一个完整的方法 = 方法头 + 方法体。
方法头就是 【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】,也称为方法签名,通常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。
方法体就是方法被调用后要指定的代码,也是完成方法功能的具体实现代码,对于调用者来说,不了解方法体如何实现的,并影响方法的使用。
(2)方法头可能包含5个部分,但是有些部分是可能缺省的
修饰符:可选的。方法的修饰符也有很多,例如:public、protected、private、static、abstract、native、final、synchronized等,后面会一一学习。其中根据是否有static,可以将方法分为静态方法和非静态方法。其中静态方法又称为类方法,非静态方法又称为实例方法。==接下来咱们先学习实例方法==。
返回值类型: 表示方法运行的结果的数据类型,方法执行后将结果返回到调用者
基本数据类型
引用数据类型
无返回值类型:void
方法名:给方法起一个名字,见名知意,能准确代表该方法功能的名字
参数列表:表示完成方法体功能时需要外部提供的数据列表
(3)方法体:方法体必须有{}括起来,在{}中编写完成方法功能的代码
关于方法体中return语句的说明:
return语句的作用是结束方法的执行,并将方法的结果返回去
如果返回值类型不是void,方法体中必须保证一定有 return 返回值; 语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。
如果返回值类型为void时,方法体中可以没有return语句,如果要用return语句提前结束方法的执行,那么return后面不能跟返回值,直接写return ; 就可以。
return语句后面就不能再写其他代码了,否则会报错:Unreachable code
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.atguigu.test04.method;public class MethodDefineDemo { void sayHello () { System.out.println("hello" ); } void printRectangle (int length, int width, char sign) { for (int i = 1 ; i <= length ; i++) { for (int j=1 ; j <= width; j++){ System.out.print(sign); } System.out.println(); } } int getIntBetweenOneToHundred () { return (int )(Math.random()*100 +1 ); } int max (int a, int b) { return a > b ? a : b; } }
5.4.4 如何调用实例方法 1、方法调用语法格式
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.atguigu.test04.method;public class MethodInvokeDemo { public static void main (String[] args) { MethodDefineDemo md = new MethodDefineDemo (); System.out.println("-----------------------方法调用演示-------------------------" ); md.sayHello(); md.sayHello(); md.sayHello(); System.out.println("------------------------------------------------" ); md.printRectangle(5 ,10 ,'@' ); System.out.println("------------------------------------------------" ); md.getIntBetweenOneToHundred(); int num = md.getIntBetweenOneToHundred(); System.out.println("num = " + num); System.out.println(md.getIntBetweenOneToHundred()); System.out.println("------------------------------------------------" ); md.max(3 ,6 ); int bigger = md.max(5 ,6 ); System.out.println("bigger = " + bigger); System.out.println("8,3中较大者是:" + md.max(8 ,9 )); } }
回忆之前的代码:
1 2 3 4 5 6 7 8 Scanner input = new Scanner (System.in);System.out.print("请输入一个整数:" ); int num = input.nextInt();
2、形参和实参
形参(formal parameter):在定义方法时方法名后面括号中声明的变量称为形式参数(简称形参)即形参出现在方法定义时。
实参(actual parameter):调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数(简称实参)即实参出现在方法调用时。
调用时,实参的个数、类型、顺序顺序要与形参列表一一对应。如果方法没有形参,就不需要也不能传实参。
无论是否有参数,声明方法和调用方法是==()都不能丢失==
3、返回值问题 方法调用表达式是一个特殊的表达式:
如果被调用方法的返回值类型是void,调用时不需要也不能接收和处理(打印或参与计算)返回值结果,即方法调用表达式==只能==直接加;成为一个独立语句。
如果被调用方法有返回值,即返回值类型不是void,
方法调用表达式的结果可以作为赋值表达式的值,
方法调用表达式的结果可以作为计算表达式的一个操作数,
方法调用表达式的结果可以作为另一次方法调用的实参,
方法调用表达式的结果可以不接收和处理,方法调用表达式直接加;成为一个独立的语句,这种情况,返回值丢失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.atguigu.test04.method;public class MethodReturnValue { public static void main (String[] args) { MethodDefineDemo md = new MethodDefineDemo (); md.sayHello(); md.printRectangle(5 ,10 ,'@' ); int bigger = md.max(7 ,3 ); System.out.println("bigger = " + bigger); int sum = md.getIntBetweenOneToHundred() + md.getIntBetweenOneToHundred(); System.out.println("sum = " + sum); int x = 4 ; int y = 5 ; int z = 2 ; int biggest = md.max(md.max(x,y),z); System.out.println("biggest = " + biggest); md.getIntBetweenOneToHundred(); } }
5.4.5 实例方法使用当前对象的成员 在实例方法中还可以使用当前对象的其他成员。在Java中当前对象用this表示。
this:在实例方法中,表示调用该方法的对象
如果没有歧义,完全可以省略this。
1、使用this. 案例:矩形类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.atguigu.test04.method;public class Rectangle { int length; int width; int area () { return this .length * this .width; } int perimeter () { return 2 * (this .length + this .width); } void print (char sign) { for (int i = 1 ; i <= this .width; i++) { for (int j = 1 ; j <= this .length; j++) { System.out.print(sign); } System.out.println(); } } String getInfo () { return "长:" + this .length + ",宽:" + this .width +",面积:" + this .area() +",周长:" + this .perimeter(); } }
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.atguigu.test04.method;public class TestRectangle { public static void main (String[] args) { Rectangle r1 = new Rectangle (); Rectangle r2 = new Rectangle (); System.out.println("r1对象:" + r1.getInfo()); System.out.println("r2对象:" + r2.getInfo()); r1.length = 10 ; r1.width = 2 ; System.out.println("r1对象:" + r1.getInfo()); System.out.println("r2对象:" + r2.getInfo()); r1.print('#' ); System.out.println("---------------------" ); r1.print('&' ); System.out.println("---------------------" ); r2.print('#' ); System.out.println("---------------------" ); r2.print('%' ); } }
2、省略this. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.atguigu.test04.method;public class Rectangle { int length; int width; int area () { return length * width; } int perimeter () { return 2 * (length + width); } void print (char sign) { for (int i = 1 ; i <= width; i++) { for (int j = 1 ; j <= length; j++) { System.out.print(sign); } System.out.println(); } } String getInfo () { return "长:" + length + ",宽:" + width +",面积:" + area() +",周长:" + perimeter(); } }
5.4.6 方法调用内存分析 方法不调用不执行,调用一次执行一次,每次调用会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值,当方法执行结束后,会释放该内存,称为出栈,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。
栈结构:先进后出,后进先出。
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.atguigu.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.4.7 实例变量与局部变量的区别 1、声明位置和方式 (1)实例变量:在类中方法外 (2)局部变量:在方法体{}中或方法的形参列表、代码块中
2、在内存中存储的位置不同 (1)实例变量:堆 (2)局部变量:栈
3、生命周期 (1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡, 而且每一个对象的实例变量是独立的。 (2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡, 而且每一次方法调用都是独立。
4、作用域 (1)实例变量:通过对象就可以使用,本类中“this.,没有歧义还可以省略this.”,其他类中“对象.” (2)局部变量:出了作用域就不能使用
5、修饰符(后面来讲) (1)实例变量:public,protected,private,final,volatile,transient等 (2)局部变量:final
6、默认值 (1)实例变量:有默认值 (2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。
5.5 参数问题 5.5.1 特殊参数之一:可变参数 在JDK1.5 之后,当定义一个方法时,形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变参数。可变参数的格式:
1 【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){ }
可变参数的特点和要求:
(1)一个方法最多只能有一个可变参数
(2)如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个
(3)在声明它的方法中,可变参数当成数组使用
(4)其实这个书写“≈”
1 【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型[] 形参名){ }
只是后面这种定义,在调用时必须传递数组,而前者更灵活,既可以传递数组,又可以直接传递数组的元素,这样更灵活了。
1、方法只有可变参数 案例:求n个整数的和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.atguigu.test05.param;public class NumberTools { int total (int [] nums) { int he = 0 ; for (int i = 0 ; i < nums.length; i++) { he += nums[i]; } return he; } int sum (int ... nums) { int he = 0 ; for (int i = 0 ; i < nums.length; i++) { he += nums[i]; } return he; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.atguigu.test05.param;public class TestVarParam { public static void main (String[] args) { NumberTools tools = new NumberTools (); System.out.println(tools.sum()); System.out.println(tools.sum(5 )); System.out.println(tools.sum(5 ,6 ,2 ,4 )); System.out.println(tools.sum(new int []{5 ,6 ,2 ,4 })); System.out.println("------------------------------------" ); System.out.println(tools.total(new int []{})); System.out.println(tools.total(new int []{5 })); System.out.println(tools.total(new int []{5 ,6 ,2 ,4 })); } }
2、方法包含非可变参数和可变参数
非可变参数部分必须传入对应类型和个数的实参;
可变参数部分按照可变参数的规则传入0~n个对应类型的实参或传入1个对应类型的数组实参;
案例:
n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回空字符串””
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.atguigu.test05.param;public class StringTools { String concat (char seperator, String... args) { String str = "" ; for (int i = 0 ; i < args.length; i++) { if (i==0 ){ str += args[i]; }else { str += seperator + args[i]; } } return str; } }
1 2 3 4 5 6 7 8 9 10 11 12 package com.atguigu.test05.param;public class StringToolsTest { public static void main (String[] args) { StringTools tools = new StringTools (); System.out.println(tools.concat('-' )); System.out.println(tools.concat('-' ,"hello" )); System.out.println(tools.concat('-' ,"hello" ,"world" )); System.out.println(tools.concat('-' ,"hello" ,"world" ,"java" )); } }
5.5.2 特殊参数之二:命令行参数(了解) 通过命令行给main方法的形参传递的实参称为命令行参数
1 2 3 4 5 6 7 8 9 10 11 public class TestCommandParam { 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]); } } }
命令行:
1 java TestCommandParam 1 2 3
1 java TestCommandParam hello atguigu
IDEA工具:
(1)配置运行参数
(2)运行程序
5.5.3 方法的参数传递机制 方法的参数传递机制:实参给形参赋值,那么反过来形参会影响实参吗?
方法的形参是基本数据类型时,形参值的改变不会影响实参;
方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。
注意:String、Integer等特殊类型容易错
1、形参是基本数据类型 案例:编写方法,交换两个整型变量的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.atguigu.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); tools.swap(x,y); System.out.println("交换之后:x = " + x +",y = " + y); } }
2、形参是引用数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.atguigu.test05.param;public class ReferenceTypeParam { void swap (MyData my) { 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); tools.swap(data); System.out.println("交换之后:x = " + data.x +",y = " + data.y); } }
1 2 3 4 public class MyData { int x; int y; }
3、形参是数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.atguigu.test05.param;public class ArrayTypeParam { void sort (int [] arr) { for (int i = 1 ; i < arr.length; i++) { for (int j = 0 ; j < arr.length - i; 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); tools.sort(nums); System.out.println("排序之后:" ); tools.iterate(nums); } }
4、形参指向新对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.atguigu.test05.param;public class AssignNewObjectToFormalParam { void swap (MyData my) { my = new MyData (); int temp = my.x; my.x = my.y; my.y = temp; } public static void main (String[] args) { AssignNewObjectToFormalParam tools = new AssignNewObjectToFormalParam (); MyData data = new MyData (); data.x = 1 ; data.y = 2 ; System.out.println("交换之前:x = " + data.x +",y = " + data.y); tools.swap(data); System.out.println("交换之后:x = " + data.x +",y = " + data.y); } }
5.6 方法的重载
方法重载 :指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
参数列表:数据类型个数不同,数据类型不同(按理来说数据类型顺序不同也可以,但是很少见,也不推荐,逻辑上容易有歧义)。
重载方法调用:JVM通过方法的参数列表,调用匹配的方法。
先找个数、类型最匹配的
再找个数和类型可以兼容的,如果同时多个方法可以兼容将会报错
案例,用重载实现:
(1)定义方法求两个整数的最大值
(2)定义方法求三个整数的最大值
(3)定义方法求两个小数的最大值
(4)定义方法求n个整数最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.atguigu.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); } public int max (int ... nums) { int max = nums[0 ]; for (int i = 1 ; i < nums.length; i++) { if (nums[i] > max){ max = nums[i]; } } return max; } }
1、找最匹配的 1 2 3 4 5 6 7 8 9 10 11 package com.atguigu.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、找唯一可以兼容的 1 2 3 4 5 6 7 8 9 10 11 package com.atguigu.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 )); } }
3、多个方法可以匹配或兼容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package com.atguigu.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); } public int max (int ... nums) { int max = nums[0 ]; for (int i = 1 ; i < nums.length; i++) { if (nums[i] > max){ max = nums[i]; } } return max; } }
5.7 方法的递归调用 递归调用 :方法自己调用自己的现象就称为递归。
递归的分类:
递归分为两种,直接递归和间接递归。
直接递归称为方法自身调用自己。
间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
注意事项 :
案例:计算斐波那契数列(Fibonacci)的第n个值,斐波那契数列满足如下规律,
即从第三个数开始,一个数等于前两个数之和。假设f(n)代表斐波那契数列的第n个值,那么f(n)满足:
f(n) = f(n-2) + f(n-1);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.atguigu.test07.recursion;public class FibonacciTest { public static void main (String[] args) { FibonacciTest t = new FibonacciTest (); for (int i=1 ; i<=10 ; i++){ System.out.println("斐波那契数列第" +i +"个数:" + t.f(i)); } System.out.println(t.f(20 )); } int f (int n) { if (n<1 ){ return 1 ; } if (n==1 || n==2 ){ return 1 ; } return f(n-2 ) + f(n-1 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.atguigu.exer.recursion;public class FibonacciTest { public static void main (String[] args) { FibonacciTest t = new FibonacciTest (); for (int i=1 ; i<=10 ; i++){ System.out.println("斐波那契数列第" +i +"个数:" + t.fValue(i)); } System.out.println(t.fValue(20 )); } int fValue (int n) { if (n<1 ){ return 1 ; } if (n==1 || n==2 ){ return 1 ; } int beforeBefore = 1 ; int before = 1 ; int current = beforeBefore + before; for (int i=4 ; i<=n; i++){ beforeBefore = before; before = current; current = beforeBefore + before; } return current; } }
5.8 对象数组 数组是用来存储一组数据的容器,一组基本数据类型的数据可以用数组装,那么一组对象也可以使用数组来装。
即数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。
注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。
5.8.1 对象数组的声明和使用 案例:
(1)定义矩形类,包含长、宽属性,area()求面积方法,perimeter()求周长方法,String getInfo()返回圆对象的详细信息的方法
(2)在测试类中创建长度为5的Rectangle[]数组,用来装3个矩形对象,并给3个矩形对象的长分别赋值为10,20,30,宽分别赋值为5,15,25,遍历输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atguigu.test08.array;public class Rectangle { double length; double width; double area () { return length * width; } double perimeter () { return 2 * (length + width); } String getInfo () { return "长:" + length + ",宽:" + width + ",面积:" + area() + ",周长:" + perimeter(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.atguigu.test08.array;public class ObjectArrayTest { public static void main (String[] args) { Rectangle[] array = new Rectangle [3 ]; for (int i = 0 ; i < array.length; i++) { array[i] = new Rectangle (); array[i].length = (i+1 ) * 10 ; array[i].width = (2 *i+1 ) * 5 ; System.out.println(array[i].getInfo()); } } }
5.8.2 对象数组的内存图分析 对象数组中数组元素存储的是元素对象的首地址。
5.8.3 二维数组 1.什么是二维数组? 一个一维数组只能存储一组同类型的数据,如果需要同时存储多组同类型的数据,就需要使用二维数组。例如,使用一维数组存储一个小组的学员成绩,使用二维数组可以存储多个小组的学员成绩。
二维数组:本质上就是元素为一维数组的一个数组。
二维数组的标记:[][]
==二维数组也可以看成一个二维表,行*列组成的二维表==,只不过这个二维表,每一行的列数还可能不同。但是每一个单元格中的元素的数据类型是一致的,例如:都是int,都是String等。
==二维数组也可以看成一个一维数组,只是此时元素是一维数组对象==。
2.二维数组的声明 二维数组声明的语法格式:
1 2 3 4 5 6 7 元素的数据类型[][] 二维数组的名称; 元素的数据类型 二维数组名[][]; 元素的数据类型[] 二维数组名[];
例如:
1 2 3 4 5 6 7 8 9 public class Test20TwoDimensionalArrayDefine { public static void main (String[] args) { int [][] grades; String[][] names; } }
面试:
1 2 int[] x, y[]; //x是一维数组,y是二维数组
3.二维数组的静态初始化 静态初始化就是用静态数据(编译时已知)为数组初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 元素的数据类型[][] 二维数组的名称 = { {元素1 ,元素2 ,元素3 。。。}, {第二行的值列表}, ... {第n行的值列表} }; 元素的数据类型[][] 二维数组名 = new 元素的数据类型[][]{ {元素1 ,元素2 ,元素3 。。。}, {第二行的值列表}, ... {第n行的值列表} }; 元素的数据类型[][] 二维数组名; 二维数组名 = new 元素的数据类型[][]{ {元素1 ,元素2 ,元素3 。。。}, {第二行的值列表}, ... {第n行的值列表} };
如果是静态初始化,右边new 数据类型[][]中不能写数字,因为行数和列数,由{}的元素个数决定
举例:
1 2 3 4 5 6 7 8 int [][] arr = { {1 ,2 ,3 },{4 ,5 ,6 },{7 ,8 ,9 ,10 }};int [][] arr = new int [][]{ {1 ,2 ,3 },{4 ,5 ,6 },{7 ,8 ,9 ,10 }};int [][] arr;arr = new int [][]{ {1 ,2 ,3 },{4 ,5 ,6 },{7 ,8 ,9 ,10 }}; arr = new int [3 ][3 ]{ {1 ,2 ,3 },{4 ,5 ,6 },{7 ,8 ,9 ,10 }};
二维数组静态初始化演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Test21TwoDimensionalArrayInitialize { public static void main (String[] args) { int [][] grades = { {89 ,75 ,99 ,100 }, {88 ,96 ,78 ,63 ,100 ,86 }, {56 ,63 ,58 }, {99 ,66 ,77 ,88 } }; String[][] names = { {"张三" ,"李四" , "王五" , "赵六" }, {"刘备" ,"关羽" ,"张飞" ,"诸葛亮" ,"赵云" ,"马超" }, {"曹丕" ,"曹植" ,"曹冲" }, {"孙权" ,"周瑜" ,"鲁肃" ,"黄盖" } }; } }
4.二维数组的使用 因为二维数组是用来存储多组数据的,因此要比一维数组麻烦一些,需要我们搞清楚如下几个概念:
二维数组的长度/行数:二维数组名.length
二维数组的某一行:二维数组名[行下标],此时相当于获取其中一组数据。它本质上是一个一维数组。行下标的范围:[0, 二维数组名.length-1]。此时把二维数组看成一维数组的话,元素是行对象。
某一行的列数:二维数组名[行下标].length,因为二维数组的每一行是一个一维数组。
某一个元素:二维数组名[行下标][列下标],即先确定行/组,再确定列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Test22TwoDimensionalArrayUse { public static void main (String[] args) { int [][] scores = { {85 ,96 ,85 ,75 }, {99 ,96 ,74 ,72 ,75 }, {52 ,42 ,56 ,75 } }; System.out.println(scores); System.out.println("一共有" + scores.length +"组成绩." ); System.out.println(scores[0 ]); System.out.println(scores[1 ]); System.out.println(scores[2 ]); System.out.println("第1组有" + scores[0 ].length +"个学员." ); System.out.println("第2组有" + scores[1 ].length +"个学员." ); System.out.println("第3组有" + scores[2 ].length +"个学员." ); System.out.println("第1组的每一个学员成绩如下:" ); System.out.println(scores[0 ][0 ]); System.out.println(scores[0 ][1 ]); System.out.println(scores[0 ][2 ]); System.out.println(scores[0 ][3 ]); } }
5.二维数组的遍历 1 2 3 4 5 6 for (int i=0 ; i<二维数组名.length; i++){ for (int j=0 ; j<二维数组名[i].length; j++){ System.out.print(二维数组名[i][j]); } System.out.println(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Test23TwoDimensionalArrayIterate { public static void main (String[] args) { int [][] scores = { {85 ,96 ,85 ,75 }, {99 ,96 ,74 ,72 ,75 }, {52 ,42 ,56 ,75 } }; System.out.println("一共有" + scores.length +"组成绩." ); for (int i = 0 ; i < scores.length; i++) { System.out.print("第" + (i+1 ) +"组有" + scores[i].length + "个学员,成绩如下:" ); for (int j = 0 ; j < scores[i].length; j++) { System.out.print(scores[i][j]+"\t" ); } System.out.println(); } } }
6.二维数组动态初始化 如果二维数组的每一个数据,甚至是每一行的列数,需要后期单独确定,那么就只能使用动态初始化方式了。动态初始化方式分为两种格式:
(1)规则二维表:每一行的列数是相同的 1 2 3 4 5 6 7 8 9 元素的数据类型[][] 二维数组名 = new 元素的数据类型[m][n]; m:表示这个二维数组有多少个一维数组。或者说一共二维表有几行 n:表示每一个一维数组的元素有多少个。或者说每一行共有一个单元格 二维数组名[行下标][列下标] = 值;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Test24SameElementCount { public static void main (String[] args) { int [][] arr = new int [4 ][5 ]; for (int i = 0 ; i < arr.length; i++) { for (int j = 0 ; j < arr.length; j++) { arr[i][j] = i + 1 ; } } 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(); } } }
(2)不规则:每一行的列数不一样 1 2 3 4 5 6 7 8 9 10 11 12 元素的数据类型[][] 二维数组名 = new 元素的数据类型[总行数][]; 二维数组名[行下标] = new 元素的数据类型[该行的总列数]; 二维数组名[行下标][列下标] = 值;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class Test25DifferentElementCount { public static void main (String[] args) { int [][] arr = new int [5 ][]; for (int i=0 ; i<arr.length; i++){ arr[i] = new int [i+1 ]; } for (int i=0 ; i<arr.length; i++){ for (int j=0 ; j<arr[i].length; j++){ arr[i][j] = i+1 ; } } 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(); } } }
7.空指针异常 观察一下代码,运行后会出现什么结果。
1 2 3 4 5 6 7 8 public class Test26NullPointerException { public static void main (String[] args) { int [][] arr = new int [3 ][]; System.out.println(arr[0 ][0 ]); } }
因为此时数组的每一行还未分配具体存储元素的空间,此时arr[0]是null,此时访问arr[0][0]会抛出 NullPointerException
空指针异常。
空指针异常在内存图中的表现
8.二维数组的内存图分析 二维数组本质上是元素类型是一维数组的一维数组。
1 2 3 4 5 6 7 int [][] arr = { {1 }, {2 ,2 }, {3 ,3 ,3 }, {4 ,4 ,4 ,4 }, {5 ,5 ,5 ,5 ,5 } };
1 2 3 4 5 6 7 8 9 int [][] arr = new int [4 ][5 ];for (int i = 0 ; i < arr.length; i++) { for (int j = 0 ; j < arr.length; j++) { arr[i][j] = i + 1 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int [][] arr = new int [5 ][];for (int i=0 ; i<arr.length; i++){ arr[i] = new int [i+1 ]; } for (int i=0 ; i<arr.length; i++){ for (int j=0 ; j<arr[i].length; j++){ arr[i][j] = i+1 ; } }