【零基础学Java知识梳理超详细版】第七章-面向对象编程(中级)

【零基础学Java知识梳理超详细版】第七章-面向对象编程(中级)

IDE:集成开发环境

  • IDEA

  • Eeclipse:一个开源的、基于 Java 的可扩展开发平台。是由 IBM 公司开发,在 2001 年 11 月贡献给开源社区的,目前最优秀的 Java 开发 IDE 之一。

7.1 IDEA 的使用

IDEA:全程 IntelliJ IDEA。在业界被公认为最好的 Java 开发工具。是捷克 JetBrains 公司的产品。除了 Java 开发,还支持 HTML,CSS,PHP,MySQL,Python 等。下载地址

7.1.1 常用快捷键

  • 删除当前行:ctrl + Y
  • 复制当前行:ctrl + D
  • 补全代码:alt + /
  • 添加 / 取消注释:ctrl + /
  • 导入该行需要的类:alt + enter
  • 快速格式化代码:ctrl + alt + L
  • 快速运行程序:shift + F10(我改成了alt + R
  • 生成构造器:alt + insert
  • 查看一个类的层级关系:ctrl + H
  • 定位一个方法:把光标放在一个方法上,按 ctrl + B
  • 自动分配变量名:在后面加上 .var
  • 查看模板快捷键:ctrl + J
  • 快速环绕代码:ctrl + alt + T

7.1.2 模板快捷键

  • mainpublic static void main(String[] args) {}
  • soutSystem.out.println();
  • forifor (int i = 0; i < ; i++) {}
  • xxx.forfor(int i = 0; i < xxx; i++) {}

更多的请在 File - Settings - Editor - Live template 中查看或添加

或者,通过下列快捷键查看

  • ctrl + J:查看模板快捷键

7.2 包

包的作用:1. 区分相同名字的类 2. 当类很多时,便于管理 3. 控制访问范围

语法:package com.name 其中 com name 分别是 一级 和 二级目录,用 . 分隔

包的本质:就是创建不同 文件夹/目录 来保存 类 文件

如何使用包中的对象:

  1. 先引入包,之后创建对象

import com.name.T;
...
T tools = new T();
  • 不引入包,而在创建对象时写全路径

  • com.name.T tools = new com.name.T();
    

    命名规则:

    • 只能包含 数字 1 2 3、字母 a b A b、下划线 _、小圆点 .
    • 不能用 数字 开头。每级目录都不能。

    命名规范:

    • 全小写字母 + 小圆点
    • com.公司名.项目名.业务模块名

    常用的包:

    java.lang:基本包,默认引入,不需要再引入

    java.util:系统提供的工具包。工具类。

    java.net:网络包,网络开发。

    java.awt:Java 的界面开发,GUI。

    引入包:

    • 只引入该包下的一个类:import java.util.Scanner
    • 引入该包的所有内容(不建议):import java.util.*

    使用细节:

    1. package 的作用是声明当前类所在的包,要放在 类 的 最上面。一个 类 中最多有一句 package

    2. import 放在 package 下面,类定义 前面。可以有多条语句,且没有顺序要求

    3. 编译器编译时 不会 检查目录结构。

      即使一个包处于错误的目录下(只要其不依赖其他包)也可能通过编译。

      但是,虚拟机会找不到该包,最终程序无法运行。

    4. 从 1.2 版本开始,用户不能再把包放在 java. 开头的目录下了。若如此做,这些包会被禁止加载。

    7.4.1 静态导入

    有一种 import 语句允许导入静态方法和字段,而不只是类

    比如:

    import static java.lang.Math.*;
    

    这个场合,使用 Math 包内的静态方法、字段时,不需要再添加类名前缀。

    double n = pow(10, 5);					// <———— 本来是 double n = Math.pow(10, 5);
    double pi = PI;							// <———— 本来是 double pi = Math.PI;
    

    7.3 访问修饰符

    7.3.1 访问权限特点

    Java 提供 4 种 访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围)

    • 公开级别:public,对外公开。

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

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

    • 私有级别:private,只有 同类 可以访问,不对外公开。

    默认(无修饰符)privateprotectedpublic
    本类
    同包中的子类不可以
    同包的非子类不可以
    其他包的子类不可以不可以
    其他包的非子类不可以不可以不可以

    7.3.2 使用说明

    1. 修饰符可以修饰类中的 属性、成员方法 及 类
    2. 只有 默认 和 public 才能修饰 类,并遵循上述访问权限特点
    3. 成员方法 的访问规则和 属性 相同
    4. private 修饰的变量可以被 任意本对象同类的对象访问

    7.4 封装

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

    封装的好处:

    • 隐藏实现细节
    • 可以对数据进行验证,保证安全合理

    实现步骤:

    1. 将属性私有化 private
    2. 提供一个公共的 set 方法,用于对属性判断并赋值
    3. 提供一个公共的 get 方法,用于获取属性的值

    编译多个源文件:

    ```cmd javac MyClass.java ```

    该文件中使用了其他类时,Java 编译器会查找对应名称的 .class 文件。没有找到的场合,转而寻找 .java 文件,并对其编译。倘若 .java 文件相较原有 .class 文件更新,编译器也会自动重新编译该文件。

    7.4.1 静态导入

    有一种 import 语句允许导入静态方法和字段,而不只是类

    比如:

    import static java.lang.Math.*;
    

    这个场合,使用 Math 包内的静态方法、字段时,不需要再添加类名前缀。

    double n = pow(10, 5);					// <———— 本来是 double n = Math.pow(10, 5);
    double pi = PI;							// <———— 本来是 double pi = Math.PI;
    

    7.4.2 JAR 文件

    为了避免向用户提供包含大量类文件的复杂目录结构,可以将 Java 程序打包成 JAR (Java 归档)文件。

    一个 JAR 文件既可以包含类文件,也可以包含诸如图像和声音等其他类型的文件。

    JAR 文件是压缩的。其使用了 ZIP压缩格式。

    创建 JAR:

    使用 jar 工具以制作 JAR 文件。该工具在 jdk/bin 目录下

    jar cvf 包名 文件名1 文件名2 ...

    关于 jar 工具的各种指令,还是自己去百度一下吧

    7.5 继承

    继承:能解决代码复用,让我们的编程更接近人类思维。当多个类存在相同的 属性(变量)和 方法 时,可以从这些类中抽象出 父类(基类/超类)。在 父类 中定义这些属性·方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。

    通过继承的方法,代码的复用性提高了,代码的维护性和拓展性也提高了。

    public class Son extends Father {};				// Son 类继承了 Father 类
    

    定义类时可以指明其父类,也能不指明。不指明的场合,默认继承 Object 类。

    所有类有且只有一个父类。Object 是所有类的直接或间接父类。只有 Object 本身没有父类。

    7.5.1 使用细节

    1. 子类 继承了所有属性和方法,但私有(private)的 属性·方法 不能在 子类 直接访问。要调用父类提供的 公共(public)等方法 访问。

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

    3. 当创建 子类对象 时,不管使用 子类的哪个构造器,默认情况下总会调用 父类的无参构造器。如果 父类 没有提供 无参构造器,则必须在 子类的构造器 中用 super 去指定使用 父类的哪个构造器 完成 对父类的初始化。否则编译不能通过。

    4. 如果希望指定调用 父类的某构造器,则显式地调用一下:super(形参列表);

    5. super 在使用时,必须放在构造器第一行。super 只能在构造器中使用。

    6. 由于 superthis 都要求放在第一行,所以此两个方法不能同时存在于同一构造器。

    7. Java 所有的类都是 Object 的子类。换言之,Object 是所有类的父类。

    8. 父类构造器的调用不限于直接父类,将持续向上直至追溯到顶级父类 Object

    9. 子类 最多只能直接继承 一个 父类。即,Java 中是 单继承机制。

    10. 不能滥用继承。子类 和 父类 之间必须满足 is - a 的逻辑关系。

    7.5.2 继承的本质

    • 内存布局:

      1. 在 方法区,自顶级父类起,依次加载 类信息。
      2. 在 堆 中开辟一个空间,自顶级父类起,依次创建并初始化各个类包含的所有属性信息。
      3. 在 栈 中存放该空间的 地址。
    • 如何查找信息?

      1. 查看该子类是否有该属性。如果该子类有这个属性且可以访问,则返回信息。
      2. 子类没有该属性的场合,查看父类是否有该属性。如有且可访问,则返回信息。如不可访问,则报错。
      3. 父类也没有该属性的场合,继续查找上级父类,直到顶级父类(Object)。
      4. 如需调用某个特定类包含的特定信息,可以调用该类提供的方法。

    7.5.3 super 关键字

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

    super 的使用:

    • super.属性名:访问父类的属性。不能访问父类的私有(private)属性。
    • super.方法名(形参列表):访问父类的方法。不能访问父类的私有(private)方法。
    • super(参数列表);:访问父类的构造器。此时,super 语句必须放在第一句。

    使用细节:

    1. 调用父类构造器,好处是分工明确。父类属性由父类初始化,子类由子类初始化。
    2. 子类中由和父类中成员(属性和方法)重名时,要调用父类成员必须用 super。没有重名的场合,superthis 及直接调用的效果相同。
    3. super 的访问不限于直接父类。如果爷爷类和本类中都有同名成员也能使用。如果多个基类中都有同名成员,则遵循就近原则。

    7.5.4 方法重写 / 覆盖

    方法重写/覆盖(Override):如若子类有一个方法,和父类的某方法的 名称、返回类型、参数 一样,那么我们就说该子类方法 覆盖 了那个父类方法。

    使用细节:

    1. 子类方法的参数,方法名称,要和父类方法完全一致。
    2. 子类方法的返回类型需和父类方法 一致,或者是父类返回类型的子类。
    3. 子类方法 不能缩小 父类方法的访问范围(访问修饰符)。

    7.6 多态

    多态:方法 或 对象 有多种形态。多态 是面向对象的第三大特征,是建立在 封装 和 继承 的基础之上的

    7.6.1 多态的体现

    1. 方法的多态:重写 和 重载 体现了 方法的多态。

    2. 对象的多态:

      • 一个对象的 编译类型 和 运行类型 可以不一致。

        Animal animal = new Dog();

        上例,编译类型是 Animal,运行类型是子类 Dog。要理解这句话,请回想6.1.4 类与对象的内存访问机制: animal 是对象的引用

      • 编译类型在定义对象时就确定了,不能改变。

      • 运行类型是可以变化的。

        上例中,再让 animal = new Cat();,这样,运行类型变为了 Cat

      • 编译类型看定义时 = 的左边,运行类型看 = 的右边。

    7.6.2 使用细节

    1. 多态的前提:两个对象 / 类存在继承关系。

    2. 多态的向上转型:

      • 本质:父类的引用指向了子类的对象。(如 [ 7.6.1.2 ])
      • 语法:父类类型 引用名 = new 子类类型(参数列表);
      • 编译类型看左边,运行类型看右边。
      • 可以调用父类中的所有成员,但不能调用子类特有的成员,而且需要遵守访问权限。因为在编译阶段,能调用哪些成员是由编译类型决定的。
      • 最终的运行结果要看子类的具体实现。即从子类起向上查找方法调用(与 [ 7.5.2 ] 规则相同)。
    3. 多态的向下转型:

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

        [7.6.2.2] 的例子里,向下转型。这个语法其实和 [2.8.2 强制类型转换] 很像。

        Dog dog = (Dog)animal;

      • 只能强转父类的引用,不能强转父类的对象。

      • 要求父类的引用必须指向的是当前目标类型的对象。即上例中的 animal 运行类型需是 Dog

      • 向下转型后,可以调用子类类型中的所有成员。

    4. 属性没有重写一说。和 方法 不同,属性的值 看编译类型。

    5. instanceof 比较操作符。用于判断对象类型是否是某类型或其子类型。此时判断的是 运行类型

    7.6.3 理解方法调用

    在对象上调用方法的过程如下:

    1. 编译器查看对象的声明类型和方法名。该类和其父类中,所有同名方法(包括参数不同的方法)都被列举。

      至此,编译器已经知道所有可能被调用的方法。

    2. 编译器确认方法调用中提供的参数类型。

      那些列举方法中存在参数类型完全匹配的方法时,即调用该方法。

      没有发现匹配方法,抑或是发现经过类型转换产生了多个匹配方法时,就会报错

      至此,编译器已经知道要调用方法的名字和参数类型

    3. 如若是 private 方法、static 方法、final 方法、构造器,那么编译器将能准确知道要调用哪个方法。这称为 静态绑定

      与之相对的,如果调用方法依赖于隐式参数类型,那么必须在运行时 动态绑定

    4. 程序运行并采取动态绑定方法时,JVM 将调用那个 实际类型 对应的方法。

    倘若每次调用方法都进行以上搜索,会造成庞大的时间开销。为此,JVM 预先为每个类计算了 方法表

    方法表中列举了所有方法的签名与实际调用的方法。如此,每次调用方法时,只需查找该表即可。

    特别地,使用 super 关键字时,JVM 会查找其父类的方法表。

    动态绑定机制:

    • 当调用对象方法的时候,该方法和该对象(隐式参数)的内存地址/运行类型绑定。
    • 当调用对象属性时,没有动态绑定机制。于是哪里声明,哪里调用。

    7.7 Object 类

    Object 类是所有类的超类。Java 中所有类默认继承该类。

    equals 方法

    boolean equals(Object obj)

    用于检测一个对象是否等于另一对象。

    在 Object 中,该方法的实现是比较 形参 与 隐式参数 的对象引用是否一致。

    == 的区别:

    • ==:既可以判断基本类型,也可以判断引用类型。如果判断基本类型,判断的是值是否相等。如果判断引用类型,判断的是地址是否相等。

    • equals 方法:是 Object 中的方法,只能判断引用类型。默认判断地址是否相等,但子类中往往重写该代码,以判断内容是否相等。

      在子类中定义 equals 方法时,首先调用超类的 equals 方法。那个一致时,再比较子类中的字段。

    Java 语言规范要求 equals 方法具有如下特性:

    • 自反性:对于任何非空引用 x,x.equals(x) 应返回 true

    • 对称性:对于任何引用 x 和 y,当且仅当 x.equals(y) 为 true 时,y.equals(x) 为 true

      如果所有的子类具有相同的相等性语义,可以使用 instanceof 检测其类型。否则,最好使用 getClass 方法比较类型。

    • 传递性:对于任何引用 x、y、z,如果 x.equals(y) 为 true ,y.equals(z) 为 true,那么 x.equals(z) 也应该为 true

    • 一致性:如果 x 和 y 的引用没有发生变化,反复调用 x.equals(y) 应该返回相同的结果

    • 对于任何非空引用 x,x.equals(null) 应该返回 false

    hashCode 方法

    int hashCode()

    返回对象的 散列码值。

    散列码值是由对象导出的一个整型值。散列码是无规律的。如果 x 与 y 是不同对象,两者的散列码基本上不会相同。

    字符串的散列码是由其内容导出的,而其他引用对象的散列码是根据存储地址得出的。

    散列码的作用:

    1. 提高哈希结构的容器的效率。
    2. 两个引用,若是指向同一对象,则哈希值一般不同。
    3. 哈希值是根据地址生成的,因而,哈希值不能等同于地址

    相关方法:

    • Objects.hashCode(Object obj)

      这是一个 null 安全的返回散列值的方法。传入 null 时会返回 0

    • Objects.hash(Object... values)

      组合所有传入参数的散列值

    • Integer.hashCode(int value)

      返回给定基本数据类型的散列值。所有包装类都有该静态方法

    • Arrays.hashCode(xxx[] a)

      计算数组的散列码。数组类型可以是 Object 或基本数据类型

    空对象调用 hashCode 方法会抛出异常。

    hashCode 与 equals 的定义必须相符。如果 x.equals(y) 返回 true,那么 x.hashCode()y.hashCode() 应该返回相同的值。

    toString 方法

    String toString()

    返回表示对象的一个字符串。Object 的默认实现如下

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    
    • Class getClass()

      返回包含对象信息的 Class 对象。

    • String getName()

      由 Class 类实例调用。返回这个类的全类名

      全类名:即包名 + 类名。比如 com.prictice.codes.Person

    • Class getSuperClass()

      由 Class 类实例调用。以 Class 形式返回其父类

      Object 使用时返回 null

    • Integer.toHexString(int val)

      返回一个数字的十六进制表示的字符串

    toString 方法非常实用。Java 标准类库中的很多类重写了该方法,以便用户能获得一些有关对象状态的信息。

    打印对象 或 使用 + 操作符拼接对象 时,都会自动调用该对象的 toString 方法。

    当直接调用对象时,也会默认调用该方法。

    finalize 方法

    1. 当对象被回收时,系统会自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作。
    2. 何时被回收:当某对象没有任何引用时,JVM 就认为该对象是一个垃圾对象,就会(在算法决定的某个时刻)使用垃圾回收机制来销毁该对象。在销毁该对象前,会调用 finalize 方法。
    3. 垃圾回收机制的调用,是由系统决定。也可以通过 System.gc(); 主动触发垃圾回收机制。这个方法一经调用就会继续执行余下代码,而不会等待回收完毕。
    4. 实际开发中,几乎不会运用该方法。

    7.8 断点调试(Debug)

    断点调试:在程序某一行设置一个断点,调试时,代码运行至此就会停住,然后可以一步一步往下调试。调试过程中可以看各个变量当前的值。如若出错,则测试到该出错代码行即显示错误并停下。进行分析从而找到这个 Bug。

    调试过程中是运行状态,所以,是以对象的 运行类型 执行。

    断点调试是程序员必须掌握的技能,能帮助我们查看 Java 底层源代码的执行过程,提高程序员 Java 水平。

    快捷键如下

    • 跳入:F7
    • 跳过:F8
    • 跳出:shift + F8
    • resume,执行到下一个断点:F9

    附录

    零钱通程序

    • Wallet.java

    package com.the_wallet;
    
    public class Wallet {
        public static void main(String[] args) {
            Data p1 = new Data("Melody");
            p1.menu();
            System.out.println("再见~");
        }
    }
    
  • Data.java

  • package com.the_wallet;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Scanner;
    
    public class Data {
        private String name = "user";
        private double balance = 0;
        private String[][] detail = new String[1][5];
    
        private Data() {
            detail[0][0] = "项目\t";
            detail[0][1] = "\t\t";
            detail[0][2] = "时间";
            detail[0][3] = " ";
            detail[0][4] = " ";
        }
    
        public Data(String name) {
            this();
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void menu() {
            char inp = 'a';
            double inpD;
            Scanner scanner = new Scanner(System.in);
            while (inp != 'y' && inp != 'Y') {
                System.out.print("\n===============零钱通菜单==============="
                        + "\n\t\t\t1.零钱通明细"
                        + "\n\t\t\t2.收益入帐"
                        + "\n\t\t\t3.消费入账"
                        + "\n\t\t\t4.退   出"
                        + "\n请选择(1-4):");
                inp = scanner.next().charAt(0);
                System.out.println("======================================");
                switch (inp) {
                    case '4':
                        System.out.println("确定要退出吗?(y/n):");
                        inp = scanner.next().charAt(0);
                        while (inp != 'y' && inp != 'n' && inp != 'Y' && inp != 'N') {
                            System.out.println("请输入“y”或者“n”!听话!");
                            inp = scanner.next().charAt(0);
                        }
                        break;
                    case '1':
                        showDetail();
                        break;
                    case '2':
                        System.out.println("请输入收益数额:");
                        inpD = scanner.nextDouble();
                        if (inpD <= 0) {
                            System.out.print("收益需要为正,记录消费请选择“消费入账”");
                            break;
                        }
                        earning(inpD);
                        break;
                    case '3':
                        System.out.println("请输入支出数额:");
                        inpD = scanner.nextDouble();
                        if (inpD < 0) {
                            inpD = -inpD;
                        }
                        if (balance < inpD) {
                            System.out.println("您的余额不足!");
                            break;
                        }
                        System.out.println("请输入支出项目:");
                        spending(inpD, scanner.next());
                        break;
                    case 'g':
                        break;
                    default:
                        System.out.print("错误。请输入数字(1-4)");
                }
            }
        }
    
        private void earning(double earn) {
            String[][] temp = new String[this.detail.length + 1][5];
            record(detail, temp);
            this.balance += earn;
            tidy("收益入账", earn, true, temp);
            showDetail();
            System.out.println("\n收益记录完成");
        }
    
    
        private void spending(double spend, String title) {
            String[][] temp = new String[this.detail.length + 1][5];
            record(detail, temp);
            this.balance -= spend;
            tidy(title, spend, false, temp);
            showDetail();
            System.out.println("\n消费记录完成");
    
        }
    
        private void record(String[][] detail, String[][] temp) {
            for (int i = 0; i < detail.length; i++) {
                for (int j = 0; j < 5; j++) {
                    temp[i][j] = detail[i][j];
                }
            }
        }
    
        private void tidy(String title, double num, boolean isPos, String[][] temp) {
            Date date = new Date();
            SimpleDateFormat sDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            if (title.length() <= 2) {
                temp[temp.length - 1][0] = title + "\t\t";
            } else {
                temp[temp.length - 1][0] = title + "\t";
            }
            String sign = isPos ? "+" : "-";
            temp[temp.length - 1][1] = sign + num + "";
            temp[temp.length - 1][2] = sDate.format(date);
            temp[temp.length - 1][3] = "余额:";
            temp[temp.length - 1][4] = balance + "";
            detail = temp;
        }
    
        private void showDetail() {
            System.out.println("--------------------------------------");
            for (int i = 0; i < detail.length; i++) {
                System.out.println(detail[i][0] + detail[i][1] + "\t" + detail[i][2] + "\t\t" + detail[i][3] + detail[i][4]);
            }
            System.out.println("--------------------------------------");
        }
    }
    
  • 11
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Eric天哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值