Java-Day-8(方法重载 + 可变参数 + 作用域 + 构造方法 + this 关键字 )

发布时间 2023-04-11 22:00:48作者: 朱呀朱~

Java-Day-8

方法重载

( Overload )

  • java 中允许同一个类中,多个同名方法的存在,但要求形参列表不一致

    • 在调用方法时,通过所给的参数来选择执行的是哪个方法
  • 重载好处

    • 减轻了起名的麻烦
    • 减轻了记名的麻烦
  • 注意细节

    • 方法名必须相同
    • 参数列表必须不同
      • 形参类型或个数或顺序,至少有一样不同,参数名无要求
      • 若只是多个方法的形参名更改,其余不变就只是多个方法的重复定义
    • 方法名前面的返回类型无要求
  • 使用时要考虑类型的自动转换

    • 如果在调用方法时,输入参数 ( 10.0, 2.0, 3 ),但类中的方法只有 ( double x, double y, double z ),就将类型进行自动转换 ( int —> double ) 使用此方法。

      // main 中,调用以下类中的方法:max(10.0, 2.0, 30)
      public double max(double n1, double n2, int n3){
          double max = n1 > n2 ? n1 : n2;
          return n3 > max ? n3 : max;
      } // 优先级第一
      public double max(double n1, double n2, double n3){
         ...
      } // 优先级第二
      // 输出的最大值都是 30.0
      

可变参数

( Variable parameters )

  • java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法 ——> 通过可变参数来实现

  • 基本语法

    • 访问修饰符 返回类型 方法名 ( 数据类型... 形参名 )
  • 注意细节

    • 可变参数的实参可以为零个或任意多个

      // main 中使用此 sum 方法时,参数可以随便输入
          
      public int sum(int... nums){
          int res = 0;
          for (int i = 0; i < nums.length; i++){
              res += nums[i];
          }
          return res;
      }
      
    • 可变参数的实参可以为数组

      // main 中定义任意数组,只论长度,可不赋初值,将数组名作为方法的形参
      
      public void sum(int... nums){
          System.out.println("长为" + nums.length);
      }
      
    • 可变参数的本质就是数组

      • 例如在求所有的可变参数之和的时,就用 for 循环 .length 遍历
    • 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数放在最后

    • 一个形参列表中只能出现一个可变参数

作用域

( Scope )

  • 在 java 编程中,主要的变量就是属性 ( 成员变量 ) 和局部变量
  • java 中作用域的分类
    • 全局变量:也就是属性,作用域为整个类体 ( 于类中,方法外 )
    • 局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中 ( 一般就是指在成员方法中定义的变量,不绝对 )
  • 全局变量可以不赋值直接使用,因为有默认值 ( 属性是有默认值的 ),但局部变量不可以,因为局部无默认值必须初始化
  • 注意细节
    • 属性和局部变量可以重名,访问时遵循的是就近原则
    • 在同一个作用域中不可重名,但属性和局部之间没关系
    • 属性的生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁 ( ... = new 类(); 而产生的对象 ) 。局部变量的生命周期较短,伴随着其代码块的执行而创建,伴随着代码块的结束而销毁 ( 即类中方法调用完便销毁 )。
    • 作用域范围不同
      • 全局变量 ( 属性 ):可以被本类使用,也可以在其他类方法中通过创建对象调用而被使用,或者是在 main 里传一个创建的对象到另一个类方法里进行调用 ( 即把一个对象放进形参列表中 )
      • 局部变量:只能被本类中对应的方法中使用
    • 修饰符不同
      • 全局变量可以加修饰符
      • 局部变量不可以加修饰符

构造方法/构造器

( Constructor )

  • 构造方法又叫构造器,石磊的一种特殊的方法,它的主要作用是完成对新对象的初始化 ( 并非创建 )

    • 方法名和类名必须一致
    • 没有返回值
    • 在创建对象时,系统会自动调用该类的构造器完成对对象的初始化
  • 基本语法

    • [修饰符] 方法名 (形参列表)

    • 构造器的修饰符可以默认

    • 形参列和成员方法一样的规则

      // main 里
      Person p1 = new Person("zhuyazhu",1);
      System.out.println("名为" + p1.name + " " + p1.age);
      
      // 编写构造器,使 p1 创建后紧接着对其进行初始化赋值
      class Person{
          String name;
          int age;
      // 构造器
          public Person(String pName, int pAge){
              System.out.println("已调用构造器,完成属性的初始化");
              name = pName;
              age = pAge;
          }
      
      // 利用无参构造器将所有人的年龄定为18
      // main 中创建就无需传值于形参列表中:Person p1 = new Person();
          public Person(){
              age = 18;
          }
      
      }
      
  • 使用细节

    • 一个类可以有多个构造器,即构造器重载

    • 构造器名都要与类名相同且无返回值

    • 对象是在 new 时创建,在使用构造器时,其对象已经存在,所以是对其初始化

    • 构造器是不能自行调用的,只能是系统自动执行使用

    • 若程序员没有定义构造器,系统就会自动给类生成一个默认无参构造器

      • 即默认构造器,可在 Dos 里 javap Person.class 进行反编译看源文件

        Person() {
        }  
        
    • 拓展 javap

      • javap 是 JDK 提供的一个命令行工具,可以对指定的 class 文件提供的字节代码进行反编译
      • 从而对照源代码和字节码,能了解更深内容
      • -v ( -verbose ):输出附加信息 -c:对代码进行反汇编 -p ( -private ):显示所有类和成员
    • 一旦定义了自己的构造器,就是覆盖了默认的无参构造器,除非自行定义无参构造器

      • 是非无参构造器的话,在创建类的时候 Person p1 = new Person(); 就会报错
  • 对象创建流程分析

    • 例:

      // main 内:
      Person p = new Person("zhuyazhu",20);
      
      class Person{
          String name;
          int age = 90;
          Person(String n, int a){
              name = n;
              age = a;
          }
      }
      
    • 流程分析 ( 加载 Person 类信息,只加载此一次;堆内分配空间;完成对象初始化;堆中地址返回给对象名 )

      • 创建时,先加载 person 类于方法区内
      • new 了,就在堆内开辟了一个地址空间,有存放 name 与 age 两个空间大小,其内值都是先默认初始化
      • 然后显式的初始化把 age 改为 90
      • 执行构造器初始化,把 main 内形参列表的值传给构造方法
      • 常量池一个带地址的空间存放 " zhuyazhu ",把地址放进堆内 name 的空间里,形成指向
      • 再把 20 替换掉原 90
      • 此时才把堆内的地址给栈内的自定义的对象名,使此对象的引用 ( 对象名 ) 指向着堆内的地址空间 ( 真正的对象 )

this 关键字

  • java 虚拟机会给每个对象分配 this,代表当前对象

    • 简单来说就是,哪个对象调用,this 就代表哪个对象
    • 活用 get 和 set 来获取和更改属性
    class Person{
        String name;
        int age;
    //    Person(String n, int a){
    //        name = n;
    //        age = a;
    //    }
        
        public Person(String name, int age){
            this.name = name;
            this.age = age;
    // 不加this的话,根据就近原则,name 就都是局部变量,和属性一点关系都没有了
    // this 的话就是当前对象的属性
        }
    }
    
  • jvm 中,于堆中真正的对象都有个隐藏的 this 地址指向堆内真正的对象自己

    • 可以借用 this.hashCode() 简单的看作对象地址,但注意并不是对象的实际地址 ( 而是把真正地址转成了一个整数,借由此来观察地址所在 )
  • 注意细节

    • this 可以访问本类的属性、方法、构造器

      • 方法内若是直接调用某变量名是按就近原则,但 this 就一定是指向属性
      • 若方法里更改了属性的值,则同对象的其他方法再调用此属性就是更改后的属性值,但是再 new 的对象调用的属性值就是没有改变的值
    • this 用于区分当前类的属性和局部变量

    • 访问成员方法的语法:this.方法名 ( 参数列表 )

      class Person{
          public void f1(){
              System.out.println("f1()");
          }
          public void f2(){
              System.out.println("f2()");
      //        F1:同一类中可以直接用
              f1();
      //        F2:
              this.f1();
          }
      }
      
    • 访问构造器语法,this (参数列表);注意只能在构造器内使用 ( 即只能在构造器中访问另一个构造器 )

      • this 调用必须是构造器中的第一个语句
      class Person{
          public Person(){
              this(10);
              System.out.println("构造器1");
      //        this(10);  非第一句就会报错
          }
          public Person(int a){
              System.out.println("构造器2," + "值为" +a);
          }
      }
      // 走构造器1时,输出先"构造器2,值为10",再输出"构造器1"
      
    • this 不能在类定义的外部使用,只能在类定义的方法中使用

  • 本章小练习

    • 查询 double 类型数组的最大值

      // 常用写法:(缺少健壮性,若是传来的是空数组就会报错)
      class Test{
          public double max(double[] arr){
              double max = arr[0];
      //        for循环判断最大值并return
          }
      }
      
      // 优化,改double为包装类Double,可以返回空null
      class Test{
          public Double max(double[] arr){
              if (arr != null && arr.length > 0){
                  double max = arr[0];
      //            for循环判断最大值并return           
              } else {
                  return null;
      //            在main调用时用Double接收判断是否为空
              }
          }
      }
      
    • 一个对象 new 创建后,再赋给新的对象,则此两个对象指向同一个堆内地址,在任意一个对象的方法里修改属性,则两个对象的属性值都改变 ( 勿忘,记不清了就画 jvm 内的存储图 )

    • 复用构造器( 只能在一个方法里使用一个构造器,由于 this 只能放在第一条语句,所以无法同时调用两个 this 构造器 )

    • 人与电脑剪刀石头布

      package com.qut.test;
      
      import java.util.Random;
      import java.util.Scanner;
      
      public class java_test {
          public static void main(String[] args) {
              finger_guessing fg = new finger_guessing();
      //        二维接收各自出拳状况
              int[][] arr1 = new int[3][3];
              int j = 0;
      
      //        输赢情况
              String[] arr2 = new String[3];
      
              Scanner scanner = new Scanner(System.in);
              for (int i = 0; i < 3; i++){
                  System.out.println("请输入(0:石头  1:剪刀  2:布)");
      //            获取玩家出拳
                  int num = scanner.nextInt();
                  fg.setPerGuess(num);
                  arr1[i][j + 1] = num;
      
      //            获取电脑出拳
                  int comnum = fg.computerGuess();
                  fg.setComGuess(comnum);
                  arr1[i][j + 2] = comnum;
      
      //            比较出拳大小
                  String isWin = fg.vsComputer();
                  arr2[i] = isWin;
                  arr1[i][j] = i;
      
      //            战况输出
                  System.out.println("————————————");
                  System.out.println("局数\t玩家出拳\t电脑出拳\t输赢情况");
                  System.out.println(i + "\t" + num + "\t\t" + comnum + "\t\t" + isWin);
                  System.out.println("————————————");
              }
          }
      }
      
      class finger_guessing{
          int perGuess;  // 人猜拳
          int comGuess;  // 人猜拳
          int perWinGuess;  // 人猜拳赢的次数
          int count = 1;  // 总次数
      
          public void showInfo(){
      
          }
          public int computerGuess(){
              Random r = new Random();
              comGuess = r.nextInt(3);  // 返回0~2的随机数
              return comGuess;
          }
      
          public void setPerGuess(int perGuess) {
              if (perGuess > 2 || perGuess < 0){
                  throw new IllegalArgumentException("数字输入不在范围内");
              }
              this.perGuess = perGuess;
          }
      
          public void setComGuess(int comGuess) {
              this.comGuess = comGuess;
          }
      
          public String vsComputer(){
              if (perGuess == 0 && comGuess == 1){
                  return "玩家获胜";
              } else if (perGuess == 1 && comGuess == 2) {
                  return "玩家获胜";
              } else if (perGuess == 2 && comGuess == 0){
                  return "玩家获胜";
              } else if (perGuess == comGuess) {
                  return "平局";
              } else {
                  return "电脑获胜,玩家输咧";
              }
          }
      }