JAVA基础——面向对象编程(中级)(韩顺平P264-P361)

目录

一、Intellij IDEA

1、IDEA介绍及安装

2、快捷键

二、包

三、访问修饰符

四、核心1:封装

五、核心2:继承

1、为什么要用继承:代码复用

2、继承的原理

3、继承的细节

4、继承的本质(内存)

5、练习题

六、Super关键字

七、overwrite方法重写(覆盖)

八、核心3:多态(难度最大的)

1、为什么要引入多态?

2、多态的定义、实现

3、向上、向下转型

4、一些细节

5、java的动态绑定机制(重要)

6、多态的应用:数组、参数

九、Object类详解

1、==和equals的区别

2、hashCode方法

3、toString方法

4、finalize方法

十、断点调试

十一、小项目:零钱通

1、基础版本

2、OOP版本


一、Intellij IDEA

1、IDEA介绍及安装

IDEA是一个比较好的开发工具(IDE指集成开发环境)。是JetBrains公司的产品(位于捷克)

支持java开发,还支持HTML、CSS、PHP、MySQL、Python等

后面会再讲Eclipse(也是集成开发工具),由IBM公司开发的,是免费的。

IDEA如果是Ultimate版本就只有30天试用期,要不就下Community版本

IDEA是以项目project的概念来管理java源码的,所以要创建java项目

步骤:file-新建项目,选择JAVA类别的。然后创建好了之后—源文件都放在src中,src右键新建java类即可。然后就能编写代码了。运行之后,直接在控制台就能看到结果

注意(下面这些都可以在setting里面自己配置的)

  1. 编码用UTF-8就行(只有在DOS上采用GBK)。
  2. IDEA中run一个程序的时候,就会自动编译生成字节码文件class放在out目录中

2、快捷键

快捷键要熟悉:

  1. 快捷键:ctrl+Y删除行;ctrl+D复制行;alt+/ 补全;ctrl+/ 注释
  2. 自动导入该行需要的类:alt+enter就可以,不用自己import了
  3. 快速格式化代码:ctrl+alt+L; 快速运行程序:alt+R
  4. 生成构造器等:ctrl+insert
  5. 查看一个类的继承关系:ctrl+H
  6. ctrl+B:可以定位该方法的定位位置。
  7. 自动分配变量名:在方法后面加.var就自动定义变量了,比如new Person().var

模板快捷键(setting-editor-live templates可以查看模板,也能自己添加模板)

  • 比如输入sout,就会自动生成System.out.println()了;main就可以自动生成主方法

上面这些快捷键都可以大大提高开发速度。

二、包

场景引出:想在一个项目下面定义两个相同名字的类。

包的三大作用

  1. 区分相同名字的类
  2. 当类很多时,可以很好地管理类
  3. 控制访问范围

包的基本语法:package com.use  (package关键字表示打包,com.use是包名)。实际上就是创建不同的文件夹来保存类文件比如下面com.use / com.xiaoming 就都是包名,其实就是创建了子目录(src右键new package,输入com.xiaoming,这样在文件夹中就是在com下面又创建了子目录小明)。(下面不同的包下面创建了Dog类,是不一样的

  • 看下面的图,可以看到new dog()的时候,会选择是用的哪个包的,然后前面就会import
  • 如果两个dog都要用,就要把包名加上 new com.xiaoming.Dog();才行,这样就不用import了。
  • import是时候,只能引入一个类,比如下面只能import com.xiaoming.Dog了。

包的规则:

  1. 包的命名规则:只能包含数字、字母、下划线、小圆点。不能以数字开头,不能是关键字或保留字  (比如demo.class.exec1 就不行   demo.12a也不行  demo.ab12.oa就可以)
  2. 常用的包:java.lang.*(默认是有的比如String、Math,不需要再引用)、java.util.*(系统提供的工具包)、java.net.*(网络包,网络开发)、java.awt.*(做java界面开发的,GUI)
  3. 如何导入包:import 包名;  比如import java.util.Scanner  , 如果要导入该包下面的所有类,就用import java.util.*  (一般还是需要哪个类就导入哪个)
    1. 比如用Arrays.sort(arr)就可以很快对数组arr进行排序。
  4. package作用是申明当前类所在包,需要放在最上面。一个类只能有一个package。import要放在package下面。

三、访问修饰符

java一共提供了4种访问修饰符号,用于控制方法和属性的访问权限:

  1. public:公开级别,对外公开。
  2. protected:受保护级别,对子类和同一个包中的类公开。
  3. 默认:向同一个包的类公开。
  4. private:私有级别,只有本类中可以访问。

注意:只有默认的和public才能修饰类,遵循上面的权限规则。

四、核心1:封装

面向对象编程的三大特征:封装Encap、继承Extends和多态。

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

  1. 隐藏实现的细节(使用方法的时候,只需要调用,不用管怎么实现的)
  2. 可以对数据进行验证,保证安全合理。(比如传入数据不合规的时候可以报错)

封装的实现步骤——分为3个步骤:

  1. 将属性进行私有化private(让外部无法直接修改)
  2. 提供一个public的set方法,对属性的判断并赋值。public void setXxx(参数名){属性=参数名}
  3. 提供一个public的get方法,用于获取属性的值。public xx getXxx(){return xx}

注意(主要看下面的这个例子):

  • 如果对于private的属性,只有通过set和get方法才能进行修改和访问。(alt+insert)
  • 如果想要同时写一个构造方法。按照普通的写法,会使得set方法失效。所以解决的方案是在构造器中调用this方法来进行赋值。这样就可以在new的时候,同时进行set中的范围判断。
    • 注意构造器的知识点别忘记了,要自己额外添加一个无参构造器才能new()
package com.hspedu.encap;

// 要求实现一个Person类,不能随便查看个人的年龄、工资等隐私
// 如果要设置年龄,必须在1-120之间,name的长度必须在2-6个字符
public class Encapsulation01 {
    public static void main(String[] args) {
        Person person = new Person();  // 这是无参构造器
        person.setName("jack");
        person.setAge(150);  // 因为150不在规定范围内,所以不能成功赋值
        person.setSalary(30000);
        System.out.println(person.info());
        System.out.println(person.getSalary());  // 对薪水必须用get方法才能访问
        // 采用构造器
        Person person2 = new Person("smith",200,50000);
        System.out.println(person2.info());
    }
}

class Person{
    public String name;  // 年龄是公开的
    private int age;  // 年龄和薪资是私有的
    private double salary;

    public Person() {  // 添加一个无参构造器
    }

    // 再添加了包含三个属性的构造器  如果直接这么写的话,在main函数中可以直接赋值,相当于把set方法跳过去了
//    public Person(String name, int age, double salary) {
//        this.name = name;
//        this.age = age;
//        this.salary = salary;
//    }

    // 解决方法就是把set方法写在构造器中
    public Person(String name, int age, double salary) {
        this.setName(name);   // 也可以不写this. 默认是本对象的
        this.setAge(age);
        this.setSalary(salary);
    }

    // 可以直接用alt+inset直接生成get和set方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        if(name.length()>=2 && name.length()<=6){   // 同样添加条件判断
            this.name = name;
        }else{
            System.out.println("名字长度不对");
            this.name = "zql";   //不符合规范就给一个默认值
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(age>=1 && age<=120){   // 控制年龄设置的范围
            this.age = age;
        }else{
            System.out.println("注意:年龄需要在1-120岁之间");
            this.age = 18;  // 否则就给一个默认值
        }
    }

    public double getSalary() {
        // 可以增加对向前对象的权限判断,比如让传个密码之类的
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    // 再写一个方法把信息数出来
    public String info(){
        return "信息为: 姓名=" + name + "年龄=" + age + "薪水=" + salary;
    }
}

五、核心2:继承

1、为什么要用继承:代码复用

应用场景:比如要编写两个类,pupil小学生和graduate大学生,两个类有很多属性方法都是相同的

这两个类的属性都相同,就方法有一点点不一样,但是要是这么写,代码复用性很差。

可以用继承,来提高代码的复用性

2、继承的原理

当多个类存在相同的属性和方法时,可以从中抽象出父类,在父类中统一定义,子类直接extends继承就行

  • 子类下面还能再有子类,比如3级子类,那么他就拥有其父类和父类的父类的属性方法。
  • 语法:class 子类 extends 父类 { }。子类就会自动拥有父类的属性方法。父类还能叫基类、超类,子类还能叫派生类。(父子只是相对关系)

package com.hspedu.extends_.improve_;
// 把这个Student作为Pupil和Graduate的父类
public class Student {
    // 下面这3个属性和1个方法是共有的
    public String name;
    public int age;
    private double score;
    public void setScore(double score) {
        this.score = score;
    }
}

//——————————————————————————————————————————
//下面是同包下的另一个类(也可以放在一个文件中)

package com.hspedu.extends_.improve_;
// 让Graduate继承父类Student
public class Graduate extends Student {
    public void testing(){
        System.out.println("大学生  正在考试");
    }
}

3、继承的细节

注意下面这些细节:

  • 1、子类继承了所有的属性和方法,但是private的属性和方法不能直接在子类访问要通过父类的公共方法才能访问
    • 即private的不能在子类中直接用,比如Sudent子类的score就是私有的,所以在下面的class Graduate中,不能直接print(score),要在Student中添加一个public的get方法,再print(getScore()))
    • 父类中private的方法也是不能直接调用的,也要在父类中新增一个public方法,在其中调用这个方法才行。即要这样转折调用一下。
  • 2、子类必须调用父类的构造器,完成父类的初始化。
    • 也就是在new一个子类的时候,父类的无参构造器也会被调用。(顺序是先调用父类的构造器、再调用子类的构造器),即父类总会先默认被初始化
    • 这是因为子类的构造器中,默认有一个super();
    • new子类的时候,不管使用子类的哪个构造器,默认总会去调用父类的无参构造器如果父类没有提供无参构造器,则必须在子类中用super去指定使用哪个父类构造器。在子类的构造器中写super(xx,xx)相当于确定了调用哪个父类构造器。不然会报错!!!
      • 如果想确定调用哪个父类构造器,则在子类构造器中写一下super(xx,xx)指定下。
      • super()必须放在构造器的第一行,且只能在构造器中使用。(由于this也必须在第一行,所以super和this只能用一个
  • 3、java所有类都是object的子类。(ctrl+H可以查看类的继承关系)

  • 4、对父类构造器的调用不仅限于直接父类,会一直往上追溯,直到object类。(所以一次会调用很多构造器)
  • 5、java的单继承机制:子类最多只能直接继承一个父类。
  • 6、不能滥用继承,子类和父类必须满足is-a的逻辑关系。(Cat is a Animal)

4、继承的本质(内存)

  1. new了之后,首先查找son的父类信息,所以首先加载Object、然后Grandpa、Father、son(见方法区右下角)
  2. 然后在堆里面,首先给grandpa类分配属性,然后给Father类分配属性,最后才是son。分别都会开空间。(遇到字符串就会再指向常量池)
  3. 在栈里面,名字为son的对象,指向地址0X11,所以这个空间里面是有3个小空间的
  4. 当用son对象来访问name的时候,要看查找关系了。按照下面的规则
    1. 先看子类是否有属性,如果子类有,并且可以访问,则访问信息(如果不能访问就直接报错了,虽然内存中是有的,就不会再向上级找了
    2. 如果子类没有这个属性,就看父类是否有这个属性(有且可以访问,就返回信息)
    3. 如果父类也没有就继续找上级父类,直到object

5、练习题

1)首先调用B类的无参构造器,然后里面有个this(因为this和super不能同时存在所以没有super),就调用有参构造器(在里面有一个默认的super),里面是先调用父类的无参构造器,再执行。——输出 a,b name,b

六、Super关键字

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

  • super可以访问父类的属性方法,但是private的属性和方法不能访问
  • 访问属性:super.属性名;访问方法: super.方法名(参数列表);构造器:super(参数列表)
  • 当子类有和父类中重名的属性方法时,想用父类的,可以用super,否则默认先用子类的。(如果是一般的属性方法,其实直接继承了,就可以直接用)
  • 在子类中如果写 method(); 就是先在本类找,找不到去父类找。this.method();也是一样的。但是要是写super.method();就是跳过本类,从父类开始找,找不到再往上级找(就近原则)

super和this的比较

七、overwrite方法重写(覆盖)

定义:就是子类有一个方法,和父类的某个方法的名称返回类型形参列表一样,那么我们就说子类这个方法覆盖了父类的方法。(注意这里不一定是直属父类,可以是爷类)

  • 注意:返回类型可以是父类的子类,比如子类中有public String Method(){},父类是public Object Method(){},因为String是Object的子类,所以也算是重写了。
  • 子类方法不能缩小父类的访问权限。 比如父类是public,子类是protected,就会报错。但是如果子类扩大了父类的权限,是可以的

注意要和方法重载区分开。重载是指在一个类中,方法名一样,形参列表不一样的情况

可以看下面这个例子:在子类student中重写父类的say方法,然后同时输出所有属性。(注意java的规定是在同一个java文件中只能写一个public的class否则会报错,所以这里把两个小的class写成了默认的)

package com.hspedu.override_;

public class exam {
    public static void main(String[] args) {
        Person jack = new Person("jack",10);
        System.out.println(jack.say());

        Student smith = new Student("Smith",20,1234,99);
        System.out.println(smith.say());

    }
}

class Person{
    private String name;
    private int age;
    public Person(String name, int age) {  // 一个有参构造函数
        this.name = name;
        this.age = age;
    }
    public String say(){
        return "name=" + name + "age=" + age;
    }
}

class Student extends Person{
    private int id;
    private double score;
    // 然后再写一个有参构造函数
    public Student(String name, int age, int id, double score) {
        super(name, age);  // 这里调用父类构造器
        this.id = id;
        this.score = score;
    }
    public String say(){  // 这是重写了父类的say方法
        return super.say() + "id=" + id + "score=" + score;
        //这里调用父类的say方法
    }
}

其实这里有疑问?为什么要重写,比如上面这个例子里面,在student类命名方法为别的,不命名say()也是同样的,因为main中调用对象的方法时候,也是先看子类有没有方法,没有再去父类找

八、核心3:多态(难度最大的)

1、为什么要引入多态?

场景引入:比如下面这个图,想设置一个master主人类,里面有一个feed方法,传入食物和动物的参数,实现主人给动物喂食的信息打印。

按照传统方法来写,就要向下面这个样子,写超级多个类,而且feed方法中,传入的参入又不一样,所以要重载很多很多feed方法。非常不利于管理和维护

package com.hspedu.poly_;

public class Poly01 {
    public static void main(String[] args) {
        Master tom = new Master("Tom");
        Dog dog = new Dog("大黄");
        Bone bone = new Bone("大棒骨");
        tom.feed(dog, bone);

        Cat cat = new Cat("小花");
        Fish fish = new Fish("黄花鱼");
        tom.feed(cat, fish);
    }
}

// 生成food这个大类以及3个小类
class Food{
    private String name;
    public Food(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class Fish extends Food{
    public Fish(String name) {
        super(name);
    }
}
class Bone extends Food{
    public Bone(String name) {
        super(name);
    }
}
// 生成animal这个大类以及2个小类
class Animal{
    private String name;
    public Animal(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class Cat extends Animal{
    public Cat(String name) {
        super(name);
    }
}
class Dog extends Animal{
    public Dog(String name) {
        super(name);
    }
}

// 写一个主人类,里面有feed这个方法
class Master{
    private String name;
    public Master(String name) {
        this.name = name;
    }
    // 主人给小狗喂骨头
    public void feed(Dog dog, Bone bone){
        System.out.println("主人"+name+"给"+dog.getName()+"吃"+bone.getName());
    }
    // 主人给小猫喂鱼
    public void feed(Cat cat, Fish fish){
        System.out.println("主人"+name+"给"+cat.getName()+"吃"+fish.getName());
    }
}

所以引入多态polymorphic(面向对象的第三大特征),可以提高代码的复用性。

2、多态的定义、实现

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

  • 方法的多态:方法的重载和重写都会体现多态。(比如说main中写a.say(xx,xx)和a.say(xx)这样调用了两个重载的方法,其实就是呈现了两种状态)
  • 对象的多态
    • 一个对象的编译类型运行类型可以不一致。(比如说可以Animal aa = new Dog();其中Dog是Animal的子类,可以把子类对象赋给父类对象的引用)这时候aa的编译类型的Animal、运行类型是Dog 
    • 编译类型在定义对象的时候就确定了,不能改变,运行类型可以改变。(所以再aa=new Cat(),那这样运行类型就是Cat了,但是编译类型还是Animal)运行类型的多态也是体现了多态。(编译类型看new等号左边,运行类型看=的右边)
package com.hspedu.poly_.objpoly_;

public class PolyObject {
    public static void main(String[] args) {
        Animal aa = new Dog();  // 编译类型的Animal,执行类型是Dog
        aa.cry();  // 这里aa要看运行类型是什么
        // 运行类型是Dog,所以执行Dog的cry,输出”狗在叫“
    }
}
class Animal{
    public void cry(){
        System.out.println("动物在叫");
    }
}
class Cat extends Animal{
    public void cry(){
        System.out.println("猫在叫");
    }
}
class Dog extends Animal{
    public void cry(){
        System.out.println("狗在叫");
    }
}

这就可以解决前面的主人喂食物的问题了,实现统一管理。把feed方法改成如下,然后main中还是一样tom.feed(dog,bone):

本质是因为可以把子类对象dog/cat、bone/fish传递给父类的引用aa、food,然后在实际执行中就是用的子类对象dog、bone,实现同样的效果。

// 在Master中写feed (直接传入animal和food两个父类类型)
    public void feed(Animal aa, Food food){
        System.out.println("主人"+name+"给"+aa.getName()+"吃"+food.getName());
    }

3、向上、向下转型

多态的前提是:两个对象(类)存在继承关系(所以说多态的基于封装继承的)

  • 向上转型:子类的对象指向父类的引用。父类类型 引用名= new 子类类型();
    • 这里的父类都是广义的,可以是爷类……
    • 可以调用父类的所有成员(遵守访问权限);但不能调用子类的特有成员。(因为在编译阶段能调用哪些成员是由编译类型决定的),即写了就会报错
    • 具体的运行效果还是要看子类的具体实现。(xx.method()是时候先看子类有没有方法,然后再看其父类,是遵守前面继承的规则的)
  • 向下转型:如果就想调用子类的特殊成员。 子类类型 引用名=(子类类型)父类引用;  在前面Animal animal = new Dog();  相当于强制转换  Dog gg = (Dog) animal;  相当于这时候gg的编译类型和运行类型都是Dog了
    • 只能强转父类的引用,不能强转父类的对象。
    • 要求父类的引用必须指向的是当前目标类型的对象。即animal原先就是指向Dog类型的,才能这样强制转换回来,要是想强制转换成Cat就会报错。
    • 可以调用子类类型中的所有成员。

4、一些细节

  • 属性没有重写之说,属性的值看编译类型!!!(只有方法可以overwrite,所以如果子类和父类都有int num属性。其实空间中是有两个num。看编译类型的值)
  • instanceOf比较符,用于判断对象的类型是否为xxx类型或其子类型。(比如BB继承AA,分别new了对象bb和aa。。那么bb instanceOf BB 或者bb instanceOf AA 都是true
    • 比较的是运行类型!

下面这个练习题,其实不难,就是有点绕

练习题2:注意到Base b=s;的时候是把子类对象s赋给父类引用b,相当于b和s指向同一个实际对象(地址空间)。所以b==s输出的是True。然后b.count是看编译类型的属性值,输出10,b.display();执行的是运行类型Sub的方法,输出20。

5、java的动态绑定机制(重要)

这个题输出很简单就是40、30。但是要是把class B中的sum()方法注释掉呢?

a.sum();首先看运行类型B,发现没有找到该方法,根据继承机制就会去找它的父类A,有sum方法。这时候方法内涉及到调用getI()方法,会对应到子类中再去找,发现有getI就执行,所以a.sum()输出30。   

如果把B中的sum1()方法注释掉呢?a.sum1()发现在B中找不到,就去父类A中,i+10,i就是这个类A的i,所以直接返回20.

6、多态的应用:数组、参数

1)多态数组数组的定义类型为父类类型,里面保存的实际元素是子类类型。如下例子,父类是Person,子类是Student和Teacher。创建一个Person类的数组,但是里面实际存放的是子类对象

即每个person[i]的编译类型都是Person,运行类型则是Student/Teacher,在调方法的时候注意动态绑定机制

public class PloyArray {
    public static void main(String[] args) {
        Person[] persons = new Person[3];
        // 因为父类的引用可以存放子类的对象
        persons[0] = new Person("jack",20);
        persons[1] = new Student("tom",18,100);
        persons[2] = new Teacher("Smith",45,4500);
        // 然后for循环调用所有对象的say方法
        for(int i = 0; i< persons.length; i++){
            persons[i].say();
            // 注意persons[i]的编译类型是Person,运行类型是之际的子类型Student、Teacher
            if(persons[i] instanceof Student){  // 如果运行类型是Student
                Student stu = (Student) persons[i];  // 强制转换
                stu.study();
            }
            if(persons[i] instanceof Teacher){
                Teacher teac = (Teacher) persons[i];  // 强制转换
                teac.teach();
            }
        }
    }
}

如果Student有自己特有的方法study,想要调用。因为编译类型是Person类,没办法解析子类方法(之前的做法是要强制向下转换)这是类型判断+向下转型

2)多态参数:方法定义的形参类型为父类类型,实参类型允许为子类类型。

对应到前面那个feed问题里面,传入的是对象,所以可以在形参列表填入父类类型,然后实际传入的是子类类型。

九、Object类详解

因为Object是所有类的超类,所以它的方法,其他所有class都会共同拥有,所有对象都可以用Object类中的方法。Object类在java.lang这个包,所以是自动引入的,默认都会有。

1、==和equals的区别

  • ==是一个比较运算符,既可以判断基本类型,也可以判断引用类型
    • 如果是判断基本类型,判断的是值是否相等。
    • 如果判断的是引用类型,判断的是地址是否相等。
public class dengdneg {
    // 先看等号
    public static void main(String[] args) {
        A a = new A();
        A b = a;
        A c = b;
        System.out.println(a == c);  // true,同一个地址
        System.out.println(b == c);  // true
        B bb = a;  // 把子类对象赋给父类引用
        System.out.println(b == c);  // true,赋给父类后,对项目bb也是指向相同的地址
        System.out.println(1 == 2);  //false,也能判断基本数据类型
    }
}
class B{}
class A extends B{}
  • equals方法:是Object类中的方法,只能判断引用类型。​​​​​​​
    • 默认判断的是地址是否相等,但是在子类中往往会重写该方法用于判断内容是否相等。
    • 在Object的源码里面,传入object对象,默认代码是this==object,判断地址是否相同。然后比如传入的是Integer或者String,会重写方法换成比较值是否相等
public class equals {
    public static void main(String[] args) {
        System.out.println("tom".equals("tom"));  // true,值相同
        // 因为Integer和String这两个类中是重写了equals方法的,所以equals变成了比较值
        Integer num1 = new Integer(100);
        Integer num2 = new Integer(100);
        System.out.println(num1 == num2);  // false,==判断地址
        System.out.println(num1.equals(num2));  // true,值相同
    }
}

同样可以对其他所有的类,重写equals方法,改变功能

注意:为什么要向下转型:这里相当于是把子类的对象赋给父类的引用,那么子类特有的成员,都不能通过这个变量名来直接调用了,只有向下转型才能用。

public class equals {
    public static void main(String[] args) {
        Person p1 = new Person("Tom");
        Person p2 = new Person("Tom");
        System.out.println(p1.equals(p2));  // true,值相同
        // 如果不在Person中重写equals方法,那么是比较地址,返回false
    }
}

class Person{
    private String name;
    public Person(String name) {
        this.name = name;
    }
    // 重写equals方法,改成比较值
    public boolean equals(Object obj){
        if (this == obj){  // 如果直接就指向一个对象,那么值肯定相等
            return true;
        }else{
            if (obj instanceof Person){  // 都是Person才比较
                Person pp = (Person) obj;  // 要向下转型,才能获得子类的值
                return this.name.equals(pp.name);   // 相当于是比较两个String,比较的是值,也可以用==
            }else {
                return false;
            }
        }
    }
}

2、hashCode方法

hashCode()可以返回哈希编码值(为了提高哈希表的效率)。xxx.hashCode();

  • 可以提高具有哈希结构的容器的效率。
  • 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
  • 两个引用,如果指向的是不同的对象,则哈希值是不一样的。(很小概率会发生碰撞)
  • 哈希值主要是根据地址号来的,但不能等价。(因为java是跑在虚拟机上的,所以地址也不是真实地址)

3、toString方法

默认返回:全类名+@+哈希值的十六进制。

p1.toString() 输出com.hspedu.Object_.Person@14ae5a5

不过一般会把toString重写,因为我们通常想要输出对象的属性之类的信息(直接insert快捷键就能直接在重写的toString方法里面return属性信息)。

当我们直接输出一个对象的时候print(p1); 就会默认调用toStirng方法

4、finalize方法

当确定一个对象不存在被别的对象名引用的时候(也不是立马就回收),jvm就会认为这是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在此之前系统就会自动调用finalize方法来回收

子类可以重写该方法,做一些释放资源的操作(同样在重写的时候,可以用insert快捷键)。

默认回收的时机是有自己的一套GC算法的。但是也可以用System.gc()来自己调用进行垃圾回收

十、断点调试

就是在开发中,可以用断点来一步步的看源码的执行过程,从而发现错误所在。

注意:整个debug过程中,都是看的运行状态,是以对象的运行类型来执行的。

一些快捷键:F7跳入、F8跳过、shift+F8 跳出、F9执行到下一个断点resume

如果想进入这一行中一个方法的内部,就用F7跳入方法,追踪。看完了可以用shift+F8跳出

十一、小项目:零钱通

需求:这个系统可以选择1234这四个选项,进行操作入账或者消费。还能退出系统

1、基础版本

化繁为简:

  1. 先打印出菜单,让用户可以选择。菜单直接print打印就好。用dowhile进入不断提示用户输入选项、接收选项,并且用一个switch来进行选项的判断。
  2. 完成零钱通明细:如何存放零钱的这些信息,有三种方法:
    1. 可以把收益入账和消费数据,保存到数组中
    2. 可以使用对象
    3. 可以用String进行拼接(比较简单笨拙),先用这种
  3. 完成收益入账:需要接收当前入账金额、时间、余额,这3种信息。
  4. 完成消费:需要额外创建一个变量接收消费说明。然后在case里面同样也要接收money和date,计算balance。然后把内容拼接到detail中进行记录。
package com.hspedu.smallchange;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class SmallChangeSys {
    // 1、完成显示菜单、并且可以选择菜单,给出对应提示信息
    public static void main(String[] args) {
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);
        String key = "";
        // 2、完成零钱通明细
        String details = "-----------零钱通明细------------";
        // 3、完成收益入账
        double money = 0;
        double balance = 0;  // 余额
        Date date = null;  // date这个类型,表示日期
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");  // 可以用于日期格式化的
        // 4、完成消费功能
        String note = "";

        do{  // 用dowhile是因为一开始就直接进入系统
            System.out.println("========零钱通菜单========");
            System.out.println("\t\t\t 1 零钱通明细");
            System.out.println("\t\t\t 2 收益入账");
            System.out.println("\t\t\t 3 消费");
            System.out.println("\t\t\t 4 退出");
            System.out.print("请选择操作(1-4):");
            key = scanner.next(); // 接收输入1
            switch (key){
                case "1":
                    System.out.println(details);
                    break;
                case "2":
                    System.out.println("您这次收益入账金额多少钱?");
                    money = scanner.nextDouble();
                    balance += money;  // 余额增加
                    date = new Date();  // 获取当前的日期,调成年月日格式
                    // 下面拼接字符串,是收益入账信息(details会在选择明细的时候输出来)
                    details += "\n收益入账\t" + money + "\t" + sdf.format(date) + "\t" + balance;
                    break;
                case "3":
                    System.out.println("您这次消费了多少钱?");
                    money = scanner.nextDouble();
                    System.out.println("消费说明");
                    note = scanner.next();  // 接收消费说明
                    balance -= money;
                    date = new Date();  // 接收当前日期
                    // 还是要拼接当前信息到detail里面去
                    details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t" + balance;
                    break;
                case "4":
                    loop = false;
                    break;
                default:
                    System.out.println("选择有误,重新选择");
            }
        }while(loop);
        System.out.println("退出了零钱通系统");
    }
}

2、OOP版本

修改成面向对象的封装:左边这个java类用来实现所有的方法,包括属性,全部封装起来,就有4个小方法,然后再额外写一个大方法dodo,把原本的do-while和switch写进去。

然后另外一个java类。new左边这个类,实例化对象后调用dodo这个方法,就直接可以了。

OOP(面向对象)的方法更有助于在后期进行扩展和管理方法。

  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值