面向对象基础
今日内容
- 可变参数
- 方法的重载
- 方法的参数传递
- 递归
- 对象数组
教学目标
- 掌握方法的可变参数的使用
- 掌握方法重载的概念
- 能够判断出方法的重载
- 理解方法的参数传递机制
- 了解命令行参数
- 了解递归方法
- 理解对象数组
第五章 面向对象基础–上(续)
5.5.7 可变参数
在JDK1.5之后,如果我们定义一个方法时,此时某个形参的类型可以确定,但是形参的个数不确定,那么我们可以使用可变参数。
格式:
修饰符 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){ }
要求:
(1)一个方法只能有一个可变参数
(2)可变参数必须是形参列表的最后一个
(3)其实这个书写“等价于”
修饰符 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型[] 形参名){ }
只是后面这种定义,在调用时必须传递数组,而前者更灵活,既可以传递数组,又可以直接传递数组的元素,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。
好处:
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。
代码演示:
public class ChangeArgs {
public static void main(String[] args) {
// 创建对象
Count c = new Count();
int[] arr = {
1, 4, 62, 431, 2 };
int sum1 = c.getSum1(arr);
System.out.println(sum1);
int sum2 = c.getSum2(arr);
System.out.println(sum2);
int sum3 = c.getSum2(1, 4, 62, 431, 2);
System.out.println(sum3);
}
}
class Count {
// 完成数组 所有元素的求和
// 原始写法
public int getSum1(int[] arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// 可变参数写法
public int getSum2(int... arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
}
练习一:找最大值
定义求1-n个整数中的最大值
public class ChangeArgs_Exer1 {
public static void main(String[] args) {
Count c = new Count();
System.out.println(c.max(1));
System.out.println(c.max(5,3,2,6));
}
}
class Count{
public int max(int num, int... others){
int max = num;
for (int i = 0; i < others.length; i++) {
if(max < others[i]){
max = num;
}
}
return max;
}
}
练习二:字符串拼接
定义将n个字符串进行拼接,如果没有传入字符串,那么返回空字符串
public class ChangeArgs_Exer2 {
public static void main(String[] args) {
StringUtil su = new StringUtil();
System.out.println(su.concat());
System.out.println(su.concat("hello","world"));
}
}
class StringUtil{
public String concat(String... args){
String str = "";
for (int i = 0; i < args.length; i++) {
str += args[i];
}
return str;
}
}
练习三:字符串拼接2
定义将n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回空字符串
public class ChangeArgs_Exer4 {
public static void main(String[] args) {
StringUtil su = new StringUtil();
System.out.println(su.concat('+'));
System.out.println(su.concat('+',"hello","world"));
}
}
class StringUtil{
public 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;
}
}
5.5.8 方法重载
- 方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
- 参数列表:数据类型个数不同,数据类型不同,数据类型顺序不同。
- 重载方法调用:JVM通过方法的参数列表,调用不同的方法。
方法重载练习一:比较两个数据是否相等
比较两个数据是否相等。参数类型分别为两个byte
类型,两个short
类型,两个int
类型,两个long
类型,并在main
方法中进行测试。
public class Method_Demo6 {
public static void main(String[] args) {
//创建
Count c = new Count();
//定义不同数据类型的变量
byte a = 10;
byte b = 20;
short c = 10;
short d = 20;
int e = 10;
int f = 10;
long g = 10;
long h = 20;
// 调用
System.out.println(c.compare(a, b));
System.out.println(c.compare(c, d));
System.out.println(c.compare(e, f));
System.out.println(c.compare(g, h));
}
}
class Count {
// 两个byte类型的
public boolean compare(byte a, byte b) {
System.out.println("byte");
return a == b;
}
// 两个short类型的
public boolean compare(short a, short b) {
System.out.println("short");
return a == b;
}
// 两个int类型的
public boolean compare(int a, int b) {
System.out.println("int");
return a == b;
}
// 两个long类型的
public boolean compare(long a, long b) {
System.out.println("long");
return a == b;
}
}
方法重载练习二:求各种最大值
用重载实现:
定义方法求两个整数的最大值
定义方法求三个整数的最大值
定义方法求两个小数的最大值
//求两个整数的最大值
public int max(int a,int b){
return a>b?a:b;
}
//求三个整数的最大值
public int max(int a, int b, int c){
return max(max(a,b),c);
}
//求两个小数的最大值
public double max(double a, double b){
return a>b?a:b;
}
方法重载练习三:判断两个方法是否是合理的重载方法
//判断如下两个方法是否构成重载:是
class StringUtil{
public 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;
}
public String concat(String[] args){
String str = "";
for (int i = 0; i < args.length; i++) {
str += args[i];
}
return str;
}
}
//判断如下两个方法是否构成重载:不是
class Count{
public int getSum(int... nums){
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
public int getSum(int[] nums){
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
return sum;
}
}
class Test06_Overload_Problem2{
public static void main(String[] args){
Count c = new Count();
System.out.println(c.sum(1,2));//(int a, int b)
System.out.println(c.sum(1,2,3));//(int... args)和(int a, int... args)都兼容,就有问题了
}
}
//Count类编译没问题,但是调用者有问题
class Count{
public int sum(int a, int b){
return a+b;
}
public int sum(int... args){
int sum = 0;
for(int i=0; i<args.length; i++){
sum += args[i];
}
return sum;
}
public int sum(int a, int... args){
int sum = a;
for(int i=0; i<args.length; i++){
sum += args[i];
}
return sum;
}
}
5.5.9 方法的参数传递机制
- 形参:在定义方法时方法名后面括号中的变量名称称为形式参数(简称形参),即形参出现在方法定义中。
- 实参:调用者方法中调用另一个方法时,方法名后面括号中的参数称为实际参数(简称实参),即实参出现在调用者方法中。
- 方法的参数传递机制:实参给形参赋值
- 方法的形参是基本数据类型时,形参值的改变不会影响实参;
- 方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。
- 注意:String、Integer等特殊类型除外
示例代码1:
class Test{
public static void swap(int a, int b){
int temp = a;
a = b;
b = temp;
}
public static void main(String[] args){
int x = 1;
int y = 2;
swap(x,y);//调用完之后,x与y的值不变
}
}
示例代码2:
class Test{
public static void change(MyData my){
my.num *= 2;
}
public static void main(String[] args){
MyData m = new MyData();
m.num = 1;
change(m);//调用完之后,m对象的num属性值就变为2
}
}
class MyData{
int num;
}
示例代码3:
public class Test {
public static void main(String[] args) {
int[] arr = {
2,4,1,5,3};
ArrayUtil util = new ArrayUtil();
util.sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
class ArrayUtil{
public 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;
}
}
}
}
}
陷阱1:
/*
陷阱1:在方法中,形参 = 新new对象,那么就和实参无关了
*/
class Test{
public static void change(MyData my){
my = new MyData();//形参指向了新对象
my.num *= 2;
}
public static void main(String[] args){
MyData m = new MyData();
m.num = 1;
change(m);//调用完之后,m对象的num属性值仍然为1
}
}
class MyData{
int num;
}
陷阱2:见字符串和包装类部分
public class Test {
public static void main(String[] args) {
StringUtil util = new StringUtil();
String str = "尚硅谷";
util.change(str);
System.out.println(str);
}
}
class StringUtil{
public void change(String str){
str += "你好";//String对象不可变,一旦修改就会产生新对象
}
}
5.5.10 命令行参数(了解)
通过命令行给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 atguigu
5.5.11 递归方法(recursion)(难点)
- 递归:指在当前方法内调用自己的这种现象。
- 递归的分类:
- 递归分为两种,直接递归和间接递归。
- 直接递归称为方法自身调用自己。
- 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
- 注意事项:
- 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
- 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
语法案例演示一:计算1-100之间所有自然数的和
public class RecursionMethod1{
public static void main(String[] args) {
Count c = new Count();
int sum = c.sum(100);
System.out.println("1-100的和:" + sum);
}
}
class Count{
public int sum(int n){
if(n == 1){
return 1;
}else{
return n + sum(n-1);
}
}
}
练习1:求n!
public class RecursionMethod2{
public static void main(String[] args) {
Count c = new Count();
int jieCheng = c.jieCheng(10);
System.out.println("10的阶乘是:" + jieCheng);
}
}
class Count{
public int jieCheng(int n){
if(n <= 1){
return 1;
}else{
return n * jieCheng(n-1);
}
}
}
练习2:计算斐波那契数列(Fibonacci)的第n个值
规律:一个数等于前两个数之和,
f(0) =1,
f(1) = 1,
f(2) = f(0) + f(1) =2,
f(3) = f(1) + f(2) = 3,
f(4) = f(2) + f(3) = 5
…
f(n) = f(n-2) + f(n-1);
public class RecursionMethod3{
public static void main(String[] args) {
Count c = new Count();
System.out.println("f(10):" + c.f(10));
System.out.println("f方法被调用的总次数:" + c.total);
}
}
class Count{
int total = 0;
public int f(int n){
total++;
if(n <= 1){
return 1;
}else{
return f(n-2) + f(n-1);
}
}
}
5.6 对象数组
数组是用来存储一组数据的容器,一组基本数据类型的数据可以用数组装,那么一组对象也可以使用数组来装。
即数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。
注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。
代码演示
class MyDate{
int year;
int month;
int day;
}
class Test{
public static void main(String[] args){
MyDate[] arr = new MyDate[3];//创建数组对象本身,指定数组的长度
for(int i=0; i<arr.length; i++){
arr[i] = new MyDate();//每一个元素要创建对象
arr[i].year = 1990 + i;
arr[i].month = 1 + i;
arr[i].day = 1 + i;
}
}
}
对象数组的内存图分析
练习1
(1)定义学生类Student
声明姓名和成绩实例变量,
getInfo()方法:用于返回学生对象的信息
(2)测试类ObjectArrayTest的main中创建一个可以装3个学生对象的数组,并且按照学生成绩排序,显示学生信息
public class ObjectArrayTest {
public static void main(String[] args) {
Student[] arr = new Student[3];
arr[0] = new Student();
arr[0].name = "张三";
arr[0].score = 89;
arr[1] = new Student();
arr[1].name = "李四";
arr[1].score = 84;
arr[2] = new Student();
arr[2].name = "王五";
arr[2].score = 85;
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr.length-1; j++) {
if(arr[j].score > arr[j+1].score){
Student temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i].getInfo());
}
}
}
class Student{
String name;
int score;
public String getInfo(){
return "姓名:" + name + ",成绩:" + score;
}
}
class Test18_ObjectArrayExer2_2{
public static void main(String[] args){
//创建一个可以装3个学生对象的数组
Student[] arr = new Student[3];//只是申明这个数组,可以用来装3个学生,此时里面没有学生对象
//从键盘输入
java.util.Scanner input = new java.util.Scanner(System.in);
for(int i=0;i<arr.length; i++){
System.out.println("请输入第" + (i+1) + "个学生信息:");
arr[i] = new Student();
System.out.print("姓名:");
arr[i].name = input.next();
System.out.print("成绩:");
arr[i].score = input.nextInt();
}
//先显示一下目前的顺序
for(int i=0; i<arr.length; i++){
System.out.println(arr[i].getInfo());
}
System.out.println("------------------------------------------");
//冒泡排序
for(int i=1; i<arr.length; i++){
for(int j=0; j<arr.length-i; j++){
//arr[j] > arr[j+1]//错误的
if(arr[j].score > arr[j+1].score){
//交换两个元素,这里是两个学生对象,所以temp也得是Student类型
Student temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
//再显示一下目前的顺序
for(int i=0; i<arr.length; i++){
System.out.println(arr[i].getInfo());
}
}
}
class Student{
String name;
int score;//使用int或double都可以
public String getInfo(){
return "姓名:" + name +",成绩:" + score;
}
}
练习2
(1)定义圆Circle类,包含radius半径属性,getArea()求面积方法,getPerimeter()求周长方法,String getInfo()返回圆对象的详细信息的方法
(2)在测试类中创建长度为5的Circle[]数组,用来装5个圆对象,并给5个圆对象的半径赋值为[1,10)的随机值
class Test16_ObjectArray{
public static void main(String[] args){
//要在数组中存储5个圆对象
//声明一个可以用来存储圆对象的数组
Circle[] arr = new Circle[5];
//for(int i=0; i<arr.length; i++){
// System.out.println(arr[i]);
//}
//System.out.println(arr[0].radius);//NullPointerException
//给元素赋值
//元素的类型是:Circle,应该给它一个Circle的对象
//arr[0] = 1.2;//错误的
//arr[0]相当于它是一个Circle类型的变量,也是对象名
/*
arr[0] = new Circle();
arr[0].radius = 1.2;
System.out.println(arr[0].radius);
*/
//创建5个对象,半径随机赋值为[1,10)的随机值
//Math.random()==>[0,1)
//Math.random()*9==>[0,9)
//Math.random()*9+1==>[1,10)
for(int i=0; i<arr.length; i++){
arr[i] = new Circle();//有对象才有半径
arr[i].radius = Math.random()*9+1;
}
//遍历显示圆对象的信息
for(int i=0; i<arr.length; i++){
//arr[i]是一个Circle的对象,就可以调用Circle类中的属性和方法
System.out.println(arr[i].getInfo());
}
}
}
class Circle{
double radius;
public double getArea(){
return 3.14 * radius * radius;
}
public double getPerimeter(){
return 3.14 * 2 * radius;
}
public String getInfo(){
return "半径:" + radius +",面积:" + getArea() + ",周长:" + getPerimeter();
}
}