Java-Day-9(IDE工具 + 包 + 访问修饰符 + 面向对象编程的三大特征)—— Java 中级

发布时间 2023-04-16 00:23:04作者: 朱呀朱~

Java-Day-9

IDE ( 集成开发环境 ) 工具

Intellij IDEA

Eclipse

( 以上两种工具的安装会另行编写随笔 )

  • IDEA代码常用快捷键
    • 配置:File — settings — Keymap — 搜索、自查 — 右键 Reset Shortcuts 删除已有 — 右键 Add Keyboard Shortcut 添加新的快捷键
    • 复制当前行默认:ctrl + D
    • 补全代码:alt + /
    • 注释增删:ctrl + /
    • 删除当前行 ( Delete Line )
    • 快速运行程序 ( Run )
    • 快速生成构造器:alt + insert
      • 右键 Generate — Constructor
    • 查看一个类的层级关系:ctrl + H ( 继承常用 )
    • 直接定位到方法:将光标放在一个方法上 + ctrl + B
    • 自动分配变量名:编写 new 对象后面加 .var + 回车
    • 快速格式化代码 ( Reformat Code ):ctrl + alt + L
    • 快速导入所需的类:
      • File — se ttings — Editor — General — Auto Import — 勾选 Add unambiguous imports on the fly 和 Optimize imports on the fly — 点击 OK,勾选如图:image-20230412233320533
      • 然后就可以使用 alt + enter 快捷,上下键选中后回车确定选择
  • 模板快捷键
    • 查看:File — settings — editor — Live templates
    • 增加自己的模板:+ 号键右键 — Live Template — 依次编写模板名、模板 — Define 选择应用范围 ( java ... )
      • image-20230412235947871
    • sout、fori

  • 作用
    • 区分相同名字的类 ( 同一文件夹不能有两个重名包 )
    • 当类很多的时候能够更好的管理
    • 控制访问范围
  • 基本语法
    • package com.qut
      • package:关键字,表示打包
      • com.qut:表示包名
  • 本质分析
    • 实际就是创建不同的文件夹/目录来保存类文件
  • 包的命名
    • 只能是数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
    • 一般都是小写字母 + 小圆点
      • com.公司名.项目名.业务模块名
  • 常用的包
    • java.lang.* :lang 包是基本包,默认引入
    • java.util.* :util 包,系统提供的工具包,
    • java.net.* :网络包,网络开发
    • java.awt.* :是做 java 的界面开发,GUI
  • 包的导入
    • 语法
      • import 包;
      • 加 * :将此包下的所有类都导入
    • 建议使用哪个就导入哪个包,少用 *
  • 注意细节
    • package 的作用是声明当前类所在的包,要放在 class 的最上面,一个类中最多只有一句 package
    • import 指令,放在 package 的下面,在类定义前面,可以有多句且没有顺序要求

访问修饰符

  • 作用

    • java 提供四种访问修饰符号,用于控制方法和属性的访问权限 ( 范围 )
  • 四种访问修饰符

    • 公开级别:用 public 修饰,对外公开

    • 受保护级别:用 protected 修饰,对子类和同一个包中的类公开

    • 默认级别:没有修饰符号,向同一个类公开

    • 私有级别:用 private 修饰,只有类本身可以访问,对外不公开

    • ( 同包无 private,不同包只有 public )

      访问级别 访问控制修饰符 同类 同包 子类 不同包
      公开 public
      受保护 protected
      默认 没有修饰符号
      私有 private
  • 注意细节

    • 修饰符可以用来修饰类中的属性、成员方法以及类
    • 注意:只有默认和 public 才能修饰类
    • 成员方法的访问规则和属性完全一样

面向对象编程的三大特征

封装

( encapsulation )

  • 把抽象出的数据 ( 属性 ) 和对数据的操作 ( 方法 ) 封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作 ( 方法 ) 才能对数据进行操作

  • 封装的好处

    • 可以实现隐藏细节
      • 把方法写好,使用时传入参数调用方法即可,调用者无需知道内部如何
    • 可以对数据进行验证,保证安全合理
  • 封装的实现步骤

    • 对属性进行私有化 private,不能直接修改属性

    • ( 以下 set 和 get 方法可以在开发软件里右键Generate 自动加入已有属性的方法 )

    • 提供一个公共的 set 方法,用于对属性判断并赋值

      public void setXxx(类型 参数名){
          // 可以编写数据验证的业务逻辑语句,例if(参数名.length() < 2)...
          属性 = 参数名;
      }
      
    • 提供一个公共的 get 方法,用于获取属性的值

      public 数据类型 getXxx(){ 
          // 权限判断
          return xx;
      }
      
    • 还有是将构造器和 set 方法结合

      // 提供两个构造器,一个无参构造器
      // 无参构造器就正常使用提供的 get 和 set 方法来赋值
      public Test() {}
      
      // 一个有参构造器,创建即赋值(属性私有且有set方法时常用)
      public Test(String name, double pwd, int age) {
          this.setName(name);
          this.setPwd(pwd);
          this.setAge(age);
      }
      

继承

( extends )

  • 作用

    • 解决代码复用的问题,当多个类存在相同的属性和方法时,可以从这些类中抽出父类,在父类中定义这些相同的属性和方法,所有的子类就不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可
    • 此时父类 ( 基类 ) 内的是共有属性和共有方法,各子类 ( 派生类 ) 内是各自的特有属性和特有方法,子类下还可以再有一个子类,继承的就是父类的和第一层子类的所有属性和方法
  • 基本语法

    • class 子类 extends 父类
  • 注意细节

    • 子类继承了父类的所有属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供的公共的方法去访问 ( 私有的属性和方法放进提供了的公共的方法里 — 同类直接用,相当于转了个弯实现了对私有的调用 )

    • 子类必须调用父类的构造器,完成父类的初始化

      所以父类只有有参构造器时,刚创建好子类会报错 ( 默认的 super 找不到父无参构造器了 )

      public class B extends A{
          public B() {
      // 默认调用父类的无参构造器,不写出来就是自带的一行代码
              super();
      // 运行后子类的构造器和父类的无参构造器都被执行了
          }
      

    }

    
    - 当创建子类对象时,不管使用子类的哪个有参或无参的构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。
    
    ```java
    // 若是父类仅一个有参构造器如下
    public A(String name, int age){
        System.out.println("不声明无参构造器的话,无参就被覆盖,只有父类有参构造器被调用")
    }
    
    // 子类不写super就会报错,需要加上以下代码
    public class B extends A{
        private int id;
    //   静态定死了父类构造器的赋值    
        public B() {
            super("zhu", 20);
            System.out.println("子类的构造器和父类的有参构造器都被调用");
        }
        
    //	活用动态型,在main里调用时再赋值
        public B(String name, int age, int id){
            super(name,age);
            this.id = id;
        }
    }
    
    • 如果希望指定去调用父类的某个构造器,则显式的调用一下,即:super ( 所需的参数列表 )

    • super 只能在构造器中使用,在使用时,必须放在构造器第一行,代码先走父,再到子

    • super() 和 this() 都只能放在构造器的第一行,所以这两个语句不能同时出现在同一个构造器里面

    • java 所有类都是 Object 类的子类,Object 是所有类的基类,可 ctrl + H 查看 类的层级关系

    • 父类构造器的调用不限于直接父类,将一直向上追溯到 Object 类 ( 顶级父类 — 只不过隐藏起来了 )

    • java 是单继承机制,即子类最多只能继承一个父类 ( 指直接继承 ),间接的父类继承不限制 ( 即让父类再继承所需的类 )

    • 不能滥用继承,如员工类继承动物类等等,所以子类和父类之间必须满足符合常理的逻辑关系

  • 继承的本质分析

    • jvm 内继承的内存布局,例 son 继承 father,father 继承 grandpa
      • Son son = new Son() 时,最先在方法区加载 Object 类,再加载Grandpa 类,再加载 Father 类,最后再加载 Son 类,而且相互之间两两有着子指向父的关联关系
      • 然后因 new 在堆里开辟了一块拥有地址的空间,其中又划分了三个空间,分别存放 Grandpa 的所有属性、 father 的所有属性和 Son 的所有属性,三个空间相互之间各自独立,所以各类可有重复的属性名,重复属性名若是 String 类型,则各自指向常量池不同的地址来存值
      • son 对象名在栈中存有地址,一个指向堆内真正对象的地址
      • 若是 son、father、grandpa 类含同类型 name 属性,那么在 main 里是先访问子类的这个属性,子类若是没有就访问父的这个属性,借此逻辑没有就找上一级,直到 Object,都没有则报错
      • 若是某属性在 son 里没有,father 里面是私有 private 修饰符,则就直接访问不到,直接报错,不会跳到 grandpa 里面再找有没有。
  • 小练习

    class A{
        A(){
            System.out.println("a");
        }
        A(String name){
            System.out.println("a name");
        }
    }
    class B extends A{
        B(){
            this("abc");
            System.out.println("b");
        }
        B(String name){
            System.out.println("b name");
        }
    }
    // main 中
    B b = new B();
    // 输出:a
    //       b name
    //	   b
    

super关键字

  • super 代表父类的引用,用于访问父类的属性、方法、构造器

  • 基本语法

    • 访问父类的属性,但不能访问私有属性

      // 在子类的普通方法里
      System.out.println(super.n1 + " " + super.n2);
      
    • 访问父类的方法,但不能访问私有方法

      // 在子类的普通方法里
      public void test(){
          super.test1();
          super.test2();
      }
      
    • 访问父类的构造器,只能放在构造器的第一句

      // 详见继承所讲
      public B(){
          super();
      }
      
  • 便利细节

    • 分工明确,父属性由父类初始化,子类的属性由子类初始化
    • 当子类中有和父类中成员 ( 属性和方法 ) 的重名时,为了访问父类的成员,必须通过 super,但若是无重名,super、this、直接访问效果相同
      • 例:在子类中 this.sum() 等价于 sum(),查找 sum() 方法逻辑如下
        1. 调用方法时,如果本类有就直接调用
        2. 没有的话就去父类、父类的父类找... 直到 Object
        3. 继承中但凡能调用的话 ( 非私有 ) 都是直接调用就可
      • this:当前对象
      • super:子类中访问的父类对象
    • 在子类中 super.sum() 就跨过了子类直接从父类开始找 sum() 方法,注意 super 的访问不限于直接父类,遵循就近原则,逻辑同上的 this

方法重写 / 覆盖

( override )

  • 简单来说就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类 ( 甚至父类的父类,并不单一指一级父类 ) 的方法

  • 注意细节

    • 参数列表、方法名要完全一样
    • 除了参数列表和方法名外,返回类型也要一样,或者是父类返回类型的子类
      • 父类返回是 Object 类型,子类返回类型是 String 也可以重写 ( string 是 object 的子类 )
      • 已知 class BBB extends AAA 情况下 ( 不管是在父类还是子类声明的 ),父类方法返回类型可以是 AAA,其子类就可以返回 AAA / BBB 的类型来完成重写
    • 子类方法不能缩小父类方法的访问权限
      • 父类默认访问权限时,子类的访问权限可以是 public、protected,不能是 private
  • 小练习

    • 定义了父一方法,返回父类的私有属性值,在子类中重写父类的这一方法,返回的值是在父类基础上再加上子类的一些私有属性值

      // 父类
      private int id;
      public String say(){
          return "id=" + id;
      }
      
      // 子类,活用super
      private String name;
      public String say(){
          return super.say() + "name=" + name;
      }
      
      // main里new子类和父类,调用同一个方法但是返回值:子比父多一个name
      
  • 重载和重写

    范围 方法名 形参列表 返回类型 修饰符
    overload 本类 必须一样 类型、个数、顺序至少一个不同 无要求 无要求
    override 父子类 必须一样 相同 父子同,或子类返回类型是父返回类型的子类 子类不能缩小访问权限

多态

( polymorphic )

  • 传统方法:( 代码复用性不高,而且不利于代码维护 )

    • Food 作为 Fish 和 Bone 的父类,Food 构造器仅含食物种类名这一私有属性的 this.name = name 语句

    • Animal 作为 Dog 和 Cat 的父类,也同样父类只含动物名这一私有属性的 this.name = name 语句

    • Master 作为主人,构造器也只含人名这一私有属性的 this.name = name 语句,新编写喂食方法给狗喂骨头,给猫喂鱼 ( 方法的重载 )

    • main 里 new 人、狗、骨、猫和鱼,调用喂食方法,将对象写进参数列表

      public void feed(Dog dog, Bone bone){
          System....
      }
      public void feed(Cat cat, Fish fish){
          System...
      }
      ......
      
    • 如此下来,若是再添加动物和食物,会有更多喂食方法要再编写,所以要使用多态

  • 多态,就是多种状态,方法或对象具有多种状态,是建立在封装和继承之上的

  • 方法重载体现多态

    • 传入不同的参数,就会调用不同的 sum,这就是种多态的体现

      // class里
      public int sum(int n1, int n2){
          return n1 + n2;
      }
      public int sum(int n1, int n2, int n3){
          return n1 + n2 + n3;
      }
      
  • 方法重写体现多态

    • School s = new School();
      Student st = new Student();
      s.say();
      st.say();
      
  • 对象的多态

    • 一个对象的编译类型和运行类型可以不一致
      • Animal animal = new Dog();
      • Dog 是 Animal 的子类,能用父类的引用指向子类的一个对象
      • 编译类型是 Animal,运行类型是 Dog
    • 编译类型在定义对象时就确定了,不能改变
      • animal 编译类型是 Animal 不可改变
    • 运行类型是可以变化的
      • animal = new Cat();
      • 编译类型仍然是 Animal,运行类型却变成了 Cat,指向的堆里的真正对象做了更改
    • 编译类型看定义时等号的左边,运行类型在等号的右边
  • 对象的多态的体现:披着羊皮的狼,狼是运行对象,但从栈里表面上看是羊,狼脱下来给狈穿,就又变成披着羊皮的狈了,羊皮 ( animal ) 重复利用

  • 传统方法进行多态改善:

    // 所有feed都简化为了这么一个方法
    public void feed(Animal animal, Food food){
        System...
    }
    
    // main里
    Dog dog = new Dog("博美犬");
    Bone bone = new Bone("小骨头");
    master.feed(dog, bone);
    
    • animal 编译类型是 Animal,可以指向所以的 ( 接收 ) Animal 子类的对象
    • food 编译类型是 Food,可以指向所有的 ( 接收 ) Food 子类的对象
  • 注意细节

    • 多态前提为两个对象 ( 类 ) 存在继承关系

    • 多态是向上转型

      • 本质:父类的引用指向了子类的对象 ( 栈里是 Animal ( Object ),堆里是 Dog )
      • 语法:父类类型 引用名 = new 子类类型();
    • 可以调用父类的所有成员 ( 遵守访问权限 )

      Animal animal = new Dog(...); //1
      animal.所有的权限允许的父中的方法
      // 编译类型是Animal,运行类型是Dog
      
    • 不能调用子类中特有成员( 除非向下转型 )

      // animal.Dog中特有的方法 ——> 报错
      
      • 注意:因为在编译阶段,能调用哪些成员是由编译类型 ( 左边的 Animal ) 来决定的
    • 最终运行结果看子类的具体实现

      • 但运行结果是看运行类型 ( 右边的 ),方法是从运行类型开始按照就近子到父逻辑来找的
    • 编译看左,能用的成员看左,实际运行看右,从右的子的方法开始找具体实现代码,动态的

    • 多态的向下转型

      • 语法: 子类类型 引用名 = ( 子类类型 ) 父类引用

        Dog dog = (Dog) animal; //2
        dog.Dog中特有的方法 //就不会报错了
        // 此时编译类型是Dog,运行类型也是Dog了
        
      • 只能强转父类的引用,不能强转父类的对象,即堆部分的真正的对象不能强转

      • 要求父类的引用必须指向的是当前目标类型的对象

        // 原来animal在创建是就是指向Dog的,即父类的引用指向着堆里的Dog
        // 现在强转了,变成Dog引用指向堆里的Dog,变成栈里两个都指向着这个堆
        // 此时再
        Cat cat = (Cat) animal; //3
        // 报错,因为new时堆里的是Dog,指向Dog的父类引用animal,怎么能转成Cat宁
        
      • 当向下转型后,就可以调用子类类型中所有成员了

    • 属性没有重写之说

      // Animal里name = 动物
      // Dog里name = 狗
      Animal animal = new Dog();
      System.out.println(animal.name); // 输出为 动物
      // 属性方面不能与方法同逻辑,属性的话直接看左边编译类型,属性就是编译类型的属性
      
    • instanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或者XX类型的子类型

      Dog dog = new Dog();
      System.out.println(dog instanceof Dog);  // true
      System.out.println(dog instanceof Animal);  //true
      
      Animal animal = new Dog();
      System.out.println(animal instanceof Dog);  // true
      System.out.println(animal instanceof Animal);  //true
      // 判断的是对象的运行类型,右边的,instanceof语句:左边(对象名)是否小于等于右边(对象)
      
      Object obj = new Object();
      System.out.println(obj instanceof Animal);  // false
      String str = "狗";
      System.out.println(str instanceof object);  // true
      
  • 小练习

    Object obj = "Hello"; // 可以,向上转型
    String str = (String)obj; // 可以,向下转型
    System.out.println(str);  // Hello
    
    Dog dog = new Dog();
    Animal animal = dog; // 向上转型
    System.out.println(animal == dog); // true
    System.out.println(animal.name); // 动物
    animal.say(); // 我是狗
    // 属性看左边编译类型,方法看右运行类型
    

java 的动态绑定机制

( dynamic binding ) !!

  • 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
    • 例:即便因继承用了父类的方法,再遇到某方法时还是第一时间到运行类型 ( 子类 ) 里找
  • 当调用对象属性时,注意属性没有动态绑定机制,哪里声明,就在哪里使用
    • 例:除非在声明的类 ( 要调用属性的方法所在的类 ) 中没有此属性,才再依继承去父类找
class A{
    public int i = 10;
    public int sum(){
        return geti() + 10;
    }    
    public int geti(){
        return i;
    }
    public int sum1(){
        return i + 10;
    }
}

class B extends A{
    public int i = 20;  
    public int geti(){
        return i;
    }
}
// main里
A a = new B();
System.out.println(a.sum()); // 因动态绑定机制先去B找,B没有sum()就依继承机制到A找,用A的sum方法,但其方法内的geti是指B类的geti(方法的动态绑定机制又体现),返回的i属性是geti所在类里的i(属性无动态绑定),即20,所以输出结果是30

System.out.println(a.sum1()); // B没有sum1()就到A找,用A的sum1方法,因为属性没有动态绑定机制,所以就在sum1所在的类中找属性i,即10,所以输出结果是20

//若B里没有声明属性i,则在a.sum()时就是A的sum方法、B的geti方法、A的i属性 —— (动态绑定+继承机制),输出结果为20
  • 小练习

    // 在employee里构造器this.salary = salary,写方法A算年基本工资12*salary,在员工Worker和管理员Manager的类中重写A方法,加上各自的奖金bonus,员工Worker类中是work方法,管理员Manager类中是manage方法
    
    
    // 主类public class Person里
    public static void main(String[] args){
        Worker zhang = new Worker(...);
        Manager zhu = new Manager(...);
        Person p = new Person();
        p.showAnnual(zhang);
        p.showAnnual(zhu);
    }
    // 获取对应员工的年工资
    public void showAnnual(Employee e){
        System.out.println(e.A);
    }
    // 获取各员工在干什么
    public void testWork(Employee e){
        if(e instanceof Worker){
            ((Worker) e).work();
        } else if(... Manager){
            ((Manager) e).manage();
        } else......
    }
    

多态数组

  • 数组定义类型为父类类型,里面保存的实际元素为子类型

    • idea 打开 structure 查看,点击设置可 move 到右边直观看各类方法
    // 父类Person有一个say方法:输出Person中所有的属性:name
    // 子类Student extends Person,特有属性score,类中重写say方法
    public String say(){
        return super.say() + "成绩" + score;
    }
    // 子类Teacher extends Person,特有属性salary,重写say似上
    
    // main中
    Person[] persons = new Person[];
    person[0] = new Person("zhu");
    person[1] = new Student("zhu",20);
    person[2] = new Teacher("zhu",7000);
    
    for(int i = 0; i < persons.length; i++){
        // 编译类型Person,运行类型根据实际情况来判断
        System.out.println(persons[i].say());
        // 动态绑定机制
    }
    
    // 若子类Student、Teacher都再加一个特有方法,想要加上特有的方法就:
    for(int i = 0; i < persons.length; i++){
        System.out.println(persons[i].say());
        if(persons[i] instanceof Student){
            Student student = (Student)person[i]; //向下转型
            student.特有方法;
        } else if(...... Teacher){
            ......
        } else if(...... Person){
            // 不操作
        } else {
            System.out.println("类型有误");
        }
    }