第八章:面向对象编程(中级部分)
总体内容
IDEA
IDEA介绍
ECLIPSE介绍
IDEA使用
破解IDEA2020.2.2
我用的是这个破解IDEA2020.2.2
IDEA工作界面介绍
IDEA调整代码,导航栏的字体大小,主题颜色,字符编码
File --> Settings
- 调正代码字体大小
- 调正导航栏字体大小和主题颜色
- 字符编码
IDEA使用
- src目录是存放代码源文件的
- 目录结构:
- 在file中新建项目,在src下新建类等
- 运行Java程序两种方式
- 双击标签关闭或打开左边的导航
- 练习中的两个快捷方式(后面的idea模板专门讲这个)
6.1 快捷1. 输入main回车
public static void main(String[] args) {}
6.2 快捷2. 输入sout回车
System.out.println();
IDEA快捷键
汇总
- 删除当前行 ctrl + d
- 复制当前行到下一行 ctrl + shift + 向下光标
- 补全代码 alt + /
- 导入该行需要的类 alt + enter
- 格式化代码 ctrl + shift + l
- 运行程序 alt + r
- 生成构造器等 alt + insert
- 查看一个类的层级结构 ctrl + h
- 从调用定位到方法所在位置 ctrl +b
- 自动分配对象名 等号右边一串后面 + .var
- 修改删除当前行的快捷键为ctrl+d
- 修改向下复制整行
- 自动导入包alt+enter
- 代码格式化
- 运行
自动分配变量名:
IDEA模板(template)
- file --> settings --> editor --> Live templates
可以查看有哪些模板快捷键/可以自己增加模板 - 常用的模板
2.1 main生成main方法
2.2 fori 生成for循环
2.3 sout 生成输出语句
2.4 还有很多自己在live template里面看 - 自增模板的四步
包
包的作用和语法
- 包的三大作用
- 包的基本语法
包的原理
包的快速入门案例(讲解)
- 创建包
1.1 在src下新建包就:
1.2 右键src --> new --> package - 输入包的名字
2.1 com.xiaoming --> com是一级目录,xiaoming是二级目录 - 在包下创建DOG类
3.1 右键包,new–>Java class - 在com包下新建java程序test,在程序中创建Dog类
4.1 会提示选择哪个包下的dog类
4.2 使用不同包下相同名称的类
- 目录结构:
包命名
命名规则
命名规范
常用包
包的使用细节
- 如何导入包
1.1 导入包的目的是使用该包下面的类
1.2 两种方式(推荐1)
1.2 案例:使用Arrays类下的sort()方法进行排序
前面是使用冒泡排序的,这里使用Arrays中sort()自动排序
- 细节
类的完善
访问修饰符(modifier)
基本使用
访问范围图(背):
- 本类中:都可以
- 同包中:私有的不可以
- 子类中:public和protected可以
- 不同包:只有public可以
使用细节
OOP三大特征(重点)
封装(encapsulation)
封装的介绍和好处
- 介绍
- 好处
封装实现的步骤
封装快速入门案例
- 解读为什么需要写set和get方法
不能直接操作的(需要校验逻辑的)属性,需要使用private修饰符
- 代码
package com.wpangzi.encapsulation;
public class encapsulation01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("王胖子啦啦啦啦");
person.setAge(100000);
person.setSalary(0);
System.out.println(person.info());
}
}
/*
需求:
1. 不能随便查看人的年龄,工资等隐私
2. 名字长度在2 - 6字符之间(校验)
3. 对设置的年龄进行合理的验证,(校验)
年龄合理就设置,否则给默认年龄
必须在1 - 120
4. 如果需要可以在get方法中添加权限验证
*/
class Person {
public String name;
private int age;
private double salary;
//alt + insert快捷键写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("名字需为2-6字符,默认名字:无名人");
this.name = "无名人";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
//增加校验逻辑
if (age >= 1 && age <= 120) {
this.age = age;
}else{
System.out.println("年龄范围(1 - 120),默认年龄:18");
this.age = 18;
}
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//输出三个属性
public String info() {
return "name = " + this.name + "\t" + "age = " +
this.age + "\t" + "salary = " + this.salary;
}
}
- 出现一个问题:
如果写一个构造器来初始化属性的话就会绕过我们写的set方法
解决方法:将构造器和set方法结合
Person wpangzi = new Person("wpangzi", 100000, 0);
System.out.println(wpangzi.info());
public Person(String name, int age, double salary) {
setName(name);
setAge(age);
setSalary(salary);
}
引申:idea取消参数提示
继承
继承基本介绍和使用
- 引出为什么需要继承
因为有些属性和方法是相同的,为了减少重复
- 介绍
文字介绍
示意图
注意:可以说D是B的子类,也可以说D是A的子类
- 基本语法
- 继承带来的便利
※继承本质详解(重要)
- 使用的源码
- 原理内存图
① 加载类信息:先从父类的最顶层开始加载,也就是从object开始加载到son类
② 创建son对象:在堆中开辟一块地址,然后先从父类最顶层开始初始化(grandpa类)一直到son类
③ 把地址返回给对象引用
④ 根据对象调用方法:服从调用规则,先看子类有没有,如果没有就去找父类…如果父类也没有,则报错(子类不能访问私有的属性或方法)
3. 方法或者属性查找规则
继承快速入门案例
- 写4个类
1.1 一个父类(子类相同的属性和方法)
1.2 两个子类(特有的属性和方法)
1.3 一个测试类(测试类要有主程序main())
2. 子类一定要写extends来继承父类
3. 代码省略
继承使用的细节
一共有10点
- 第一点如下
① 子类会继承父类所有属性和方法,但不是所有属性和方法都能直接访问
② 非私有属性和方法可以直接访问
③ 私有属性和方法需要父类提供公共方法去访问
- 访问私有属性的公有方法
//在子类中...
System.out.println("访问父类的n4属性"+ getN4());
- 访问私有方法的公有方法
//在子类中...
callinfo();
- 第2,3点如下
① 不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
②子类会先调用父类的构造器,再调用自己的构造器,因为子类构造器中会默认写一个super()去调用父类构造器
③ 如果父类没有提供无参构造器,则必须在子类构造器中用super去指定使用父类的哪个构造器
注意1. 如果父类自定义了有参构造器,则默认的无参构造器消失
注意2. 子类中每一个构造器都需写super(参数)
- 第4,5,6点如下
① 子类构造器中如果不是父类的无参构造器则需要显示调用
- 第7,8点如下
第8点也就是:在调用子类构造器时,会先调用最顶层父类的构造器,再调用下一层父类,再下一层…直到把父类构造器全部调用完毕,再调用子类构造器(从子类向上回溯一直到Object,然后从Object向下调用)
- 第9,10点如下
继承三个练习
- 练习1
解读注意:
在子类B中:
- 第一个构造器中没有super(),因为this()和super()在一个构造器中不能同时出现
- 第二个构造器中会默认调用父类的无参构造器
- 子类构造器中一定有一个super(),或许无参(默认的可不写)或许有参
- 练习2:有多个父类时调用构造器的情况
- 练习3
test.java
package com.wpangzi.extends_.excise.Excise03;
public class Test {
public static void main(String[] args) {
PC pc = new PC("intel",16,500,"IBM");
System.out.println(pc.getPCDetails());
}
}
父类:Computer.java
package com.wpangzi.extends_.excise.Excise03;
public class Computer {
private String cpu;
private int memory;
private int disk;
//因为是私有的,所以要写get,set方法来访问--封装
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public int getDisk() {
return disk;
}
public void setDisk(int disk) {
this.disk = disk;
}
//写有参构造器来对父类初始化
public Computer(String cpu, int memory, int disk) {
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
}
//写一个public方法来返回computer信息
public String getDetails(){
return "cpu = "+cpu+" 内存 = "+memory+" 硬盘 = "+disk;
}
}
注意:如果父类没有无参构造器时,子类继承会报错,直到子类构造器调用了父类有参构造器时,报错消失
子类:PC.java
package com.wpangzi.extends_.excise.Excise03;
public class PC extends Computer{
//对属性进行封装
private String brand;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
//先调用父类构造器对父类属性进行初始化
//再子类构造器对子类属性初始化
//IDEA自动把父类属性加上了
public PC(String cpu, int memory, int disk, String brand) {
super(cpu, memory, disk);
this.brand = brand;
}
//输出继承的属性信息以及特有的属性信息
//可以用get方法对computer类的信息进行访问,
// 也可以用public方法访问(推荐)
public String getPCDetails(){
return getDetails() + this.brand;
}
}
)
多态(polymorphic)
多态引出与介绍
- 引出多态
想解决的问题
用传统方法解决很繁琐
- 基本介绍
多态具体体现
方法的多态
- 方法重载体现多态
- 方法重写体现多态
对象的多态(核心)
- 重要的几句话
- 代码演示说明
初步理解:
- 编译类型指的是对象引用,运行类型是存储在堆中实质性的对象
- 在运行时,jvm会找到运行类型并调用其方法
2.1 比如:编译类型是Animal,运行类型是Dog–>Animal animal = new Dog();
2.2 运行animal.cry()时,jvm会在堆中找到animal指向的对象Dog,并调用Dog的cry方法- 编译类型在定义对象时就确定了(在栈中有一个Animal类的对象引用),不能修改,但是运行类型是可以修改的,因为对象引用可以指向堆中不同的对象,animal先指向dog对象,再指向cat对象
- Animal是一个引用数据类型,它声明了一个变量animal,再把堆中某个地址(对象)赋给animal(子类对象赋给父类引用)
多态快速入门
就用引出多态的哪个案例
多态注意事项和细节
- 前提
- 向上转型
- 向下转型
- 属性不能重写
- instanceOf
- 前提
- 向上转型
代码讲解
- 向下转型
图解
代码讲解
- 属性重写问题
代码讲解
- instanceOf
代码讲解
两个练习
- 练习一:判对错
- 练习二:输出
java动态绑定机制(非常重要)
多态数组
数组 + 向上转型 + 向下转型
- 概念
- 应用实例
2.1 注:如果没有说属性的访问修饰符,那默认写成私有属性+构造器+get和set方法+重写父类的say()方法
2.2 重写父类的say()方法–>先调用了父类的say方法,然后加上自己多出的内容
2.3 多态数组
用到向上转型
- 应用升级
3.1 书写子类特有方法
注:子类中没有name属性,需要使用父类的name属性,但是父类name属性是私有的,所以要用公有方法getName()来访问name
3.2 用到类型判断+向下转型
多态参数
- 概念
- 应用实例
2.1 问题2的解决:
这样就可以使这两个子类都能传入该方法中
2.2 问题3的解决:
Object类详解
方法摘要
==运算符
引申:idea查看jdk源码
- 快捷键:ctrl+b
- = =和equals区别
2.1 引用类型用 == 判断地址是否相同
2.2 引用类型(integer和string)用 equlas() 判断值是否相等
2.3 equals默认是判断地址是否相同,而integer和string重写了该方法,变成了判断值是否相等 - integer重写了object的equals方法
equals()方法
- 概念
- 练习一 - 自己重写equals()方法
- 练习二 - 判断输出真假
- 练习三 - 判断输出真假
- 概念
- 练习一 - 自己重写equals()方法
2.1 因为person类继承了object类,所以equals()默认是判断地址是否相等,所以这里需要在person类中重写equals()方法来满足题意
2.2 重写equals()方法–>两个判断(地址是否相等,是否为person类)
- 练习二 - 判断输出真假
- 练习三 - 判断输出真假
hashCode()方法
toString()方法
- Object 的toString()源码
- 概念
- 重写toString()方法
3.1 重写是默认输出类的属性
3.2 快捷键alt + insert
3.3 在子类中重写方法后,子类就近原则先调用自己的方法,
finalize()方法
-
概念
-
解读
-
子类中重写finalize()方法
-
告诉系统现在就释放(系统有时间就会释放)
断点调试
断点调试介绍
断点调试快捷键
蓝色高亮行是执行到该行(还没有执行)
fn + f8 逐行执行代码
案例 - 数组越界异常
案例 - 追方法的源码
- 用解决方法2后按快捷键 fn + f7(跳入该条语句的方法–>追这个方法的源码)
- shift + f8(跳出该方法的源码–>逐层跳出)
- 如果fn + f7还是进不去,可以alt + shift +f7强制进入
- 跳入跳出方法源码的图解
案例 - 执行下一个断点
fn + f9 就是执行下一个断点
断点练习 - 看对象创建过程和动态绑定机制如何工作
- 练习一
强制进入会进到底层(读取类信息),一般进入就是表层的方法(本类构造器)f7进入,shift+f8跳出,f8逐行执行
进入方法后逐行执行,再想看方法源码就再次进入即可
1.1 加载类信息
1.2 调用本类toString方法
super关键字
基本介绍
基本语法
super访问构造器时只能写在构造器中,且只能时第一句
super的好处和细节
- 好处和细节
- super,this,直接访问的规则
super和this的比较
类的完善
方法重写/覆盖
基本介绍
方法重写细节
public > protected > 默认 > private
方法重载和重写的区别
方法重写练习
- 类的属性格式为:修饰符 + 数据类型 + 属性名
- 在子类中返回父类和子类信息
- 方法重写:
3.1 子类和父类都写say()方法
3.2 方法重写时前两个(修饰符和返回类型)有要求 - 要求细节里有,这里不写了
3.3 后两个(方法名和形参列表)需要完全一致
3.4 方法体内写重写的内容就好了
3.5 父类中say方法输出两个属性信息,子类中say方法多输出两个属性信息(使用super调用父类的say方法,进行代码复用)
项目 :零钱通
零钱通介绍smallChangeSys
- 功能介绍
- 代码实现介绍
- 代码实现改进
在实现基础功能的基础上再想着改进–万丈高楼平地起,化繁为简
1. 零钱通菜单
菜单功能实现 :
先完成显示菜单,并可以选择菜单,给出对应提示
- do - while 来重复打印菜单 + 接收输入选择
- switch - case 判断执行哪个功能
- 定义一个布尔变量,当选择退出系统时,将它置false,结束打印菜单
package com.wpangzi.smallChange;
import java.util.Scanner;
public class SmallChangeSys {
//化繁为简
//1. 先完成显示菜单,并可以选择菜单,给出对应提示
public static void main(String[] args) {
//定义变量
// - 判断是否继续循环
boolean loop = true;
// - 接收用户输入
String key = "";
Scanner scanner = new Scanner(System.in);
do{
System.out.println("========零钱通菜单=========");
System.out.println("\t\t1 零钱通明细");
System.out.println("\t\t2 收益 入账");
System.out.println("\t\t3 消 费");
System.out.println("\t\t4 退 出");
System.out.print("请选择(1 - 4):");
key = scanner.next();
//使用switch - case判断选择的功能
switch (key) {
case "1" :
System.out.println("1 零钱通明细");
break;
case "2" :
System.out.println("2 收益 入账");
break;
case "3" :
System.out.println("3 消 费");
break;
case "4" :
System.out.println("4 退 出");
loop = false;
break;
default:
System.out.println("选择有误,请重新选择");
}
}while(loop);
System.out.println("--------退出了零钱通系统--------");
}
}
2. 零钱通明细
明细
完成零钱通明细的三个思路(这里使用了第三种)
- 把收益和消费放到数组中
- 使用对象
- 简单的话可以使用字符串拼接
package com.wpangzi.smallChange;
import java.util.Scanner;
public class SmallChangeSys {
//化繁为简
//1. 先完成显示菜单,并可以选择菜单,给出对应提示
//2. 完成零钱通明细
public static void main(String[] args) {
//定义变量
//1. 先完成显示菜单,并可以选择菜单,给出对应提示
// - 判断是否继续循环
boolean loop = true;
Scanner scanner = new Scanner(System.in);
// - 接收用户输入
String key = "";
//2.完成零钱通明细
// - 零钱通明细的字符串
String details = "--------零钱通明细--------";
do {
System.out.println("========零钱通菜单=========");
System.out.println("\t\t1 零钱通明细");
System.out.println("\t\t2 收益 入账");
System.out.println("\t\t3 消 费");
System.out.println("\t\t4 退 出");
System.out.print("请选择(1 - 4):");
key = scanner.next();
//使用switch - case判断选择的功能
switch (key) {
case "1":
System.out.println(details + "\n");
break;
case "2":
System.out.println("2 收益 入账");
break;
case "3":
System.out.println("3 消 费");
break;
case "4":
System.out.println("4 退 出");
loop = false;
break;
default:
System.out.println("选择有误,请重新选择");
}
} while (loop);
System.out.println("--------退出了零钱通系统--------");
}
}
3. 收益入账
完成收益入账 完成功能 驱动程序员增加新的变化和代码
- 日期
- Date date = null;//date是java.util.Date类型,表示日期
- 这里date是指向空,当调用功能3的时候再给它传一个地址,此时date就是调用时的时间了
- 日期默认是美式的,要改变date的格式
- 修改时:SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm”);
- 调用时:sdf.format(date)
- 字符串拼接的思想
- 定义一个字符串String details = “--------零钱通明细--------”;
- 当没有零钱明细的时候就只输出"--------零钱通明细--------"
- 当添加了收益或者消费就把money和balance拼接到details字符串上
details += "\n收益入账" + "\t" + money + "\t" + sdf.format(date) + "\t" + balance;
- 再调用功能一输出details时就会显示详细的明细了
System.out.println(details + "\n");
部分代码如下
//3. 完成收益入账 完成功能驱动程序员增加新的变化和代码
double money = 0;//收益
double balance = 0;//余额
/*
- 这里date是指向空,当调用功能3的时候再给它传一个地址,此时date就是调用时的时间了
- 日期默认是美式的,要改变date的格式
- - 修改时:SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
- - 调用时:sdf.format(date)
*/
Date date = null;//date是java.util.Date类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
do {
System.out.println("========零钱通菜单=========");
System.out.println("\t\t1 零钱通明细");
System.out.println("\t\t2 收益 入账");
System.out.println("\t\t3 消 费");
System.out.println("\t\t4 退 出");
System.out.print("请选择(1 - 4):");
key = scanner.next();
//使用switch - case判断选择的功能
switch (key) {
case "1":
System.out.println(details + "\n");
break;
case "2":
System.out.printf("收益入账金额:");
//money需要校验范围
money = scanner.nextDouble();
balance += money;
date = new Date();
//字符串拼接:把收益详细信息拼接到details字符串上
details += "\n收益入账" + "\t" + money + "\t" + sdf.format(date) + "\t" + balance;
break;
4. 消费
和收益相似
部分代码
//4. 消费
String note = "";//消费说明
do {
System.out.println("========零钱通菜单=========");
System.out.println("\t\t1 零钱通明细");
System.out.println("\t\t2 收益 入账");
System.out.println("\t\t3 消 费");
System.out.println("\t\t4 退 出");
System.out.print("请选择(1 - 4):");
key = scanner.next();
//使用switch - case判断选择的功能
switch (key) {
case "3":
System.out.printf("消费金额:");
//money需要校验范围
money = scanner.nextDouble();
System.out.printf("消费说明:");
note = scanner.next();
balance -= money;
date = new Date();
//字符串拼接:把消费信息拼接到details字符串上
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
break;
5. 代码改进 - 确认退出(有代码思想在里面)
代码思想
- 建议一段代码,完成一个小功能,尽量不要混在一起
- 使用while + break,来处理接收到的数据,必须是y 或者 n (判断条件是true,当满足条件时break)
- 退出while后,再判断choice是 y 还是n,就可以决定是否退出
case "4":
String choice = "";
//一段代码完成一个小功能,尽量不混在一起
//while和break结合
// - 来判断输入内容是否是y/n
while(true){
System.out.println("你确定要退出吗?y/n");
choice = scanner.next();
//字符串判断相等需要用到equals()方法
if("y".equals(choice) || "n".equals(choice)){
break;
}
}
//当用户退出while,进行判断
if("y".equals(choice)){
loop = false;
}
break;
6. 代码改进 - 判断金额是否合理
- 先完成基本功能,想到改进可以先写在代码旁,后期改进
- 找出不正确金额的条件,然后给出提示,就直接break(这样容易维护和审查,找出不正确的然后再有不正确的条件直接添加就好了,如果找正确的还要把正确时做的代码放进去,if中嵌套会很多)
case "2":
System.out.printf("收益入账金额:");
//money需要校验范围
money = scanner.nextDouble();
if (money <= 0) {
System.out.println("收益入账金额 需要 大于0");
break;
}
balance += money;
date = new Date();
//字符串拼接:把收益详细信息拼接到details字符串上
details += "\n收益入账" + "\t+" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
break;
case "3":
System.out.printf("消费金额:");
//money需要校验范围
money = scanner.nextDouble();
if(money<=0||money>balance){
System.out.println("您的消费金额应该在 0 - "+balance);
break;
}
System.out.printf("消费说明:");
note = scanner.next();
balance -= money;
date = new Date();
//字符串拼接:把消费信息拼接到details字符串上
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
break;
※将面向过程修改成面向对象(总代码)
- 结构
- 代码
面向对象的好处:
- 把各个功能封装在一起,然后在测试类中调用
- 每一个功能对应一个方法
- 易于后期扩展,维护
功能类书写
- 该类完成零钱通各个功能
- 该类包括所有功能的属性以及各功能对应的方法
- 使用oop(面向对象编程)
- 将各个功能对应一个方法
package com.wpangzi.smallChange.oop;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
/*
1. 该类完成零钱通各个功能
2. 该类包括所有功能的属性以及各功能对应的方法
3. 使用oop(面向对象编程)
4. 将各个功能对应一个方法
*/
public class SmallChangeSysOOP {
//(属性)
//1. 先完成显示菜单,并可以选择菜单,给出对应提示
// - 判断是否继续循环
boolean loop = true;
Scanner scanner = new Scanner(System.in);
// - 接收用户菜单输入
String key = "";
//2.完成零钱通明细
// - 零钱通明细的字符串
String details = "--------零钱通明细--------";
//3. 完成收益入账 完成功能驱动程序员增加新的变化和代码
double money = 0;//收益
double balance = 0;//余额
/*
- 这里date是指向空,当调用功能3的时候再给它传一个地址,此时date就是调用时的时间了
- 日期默认是美式的,要改变date的格式
- - 修改时:SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
- - 调用时:sdf.format(date)
*/
Date date = null;//date是java.util.Date类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
//4. 消费
String note = "";//消费说明
//(方法)
//1. 先完成显示菜单,并可以选择菜单,给出对应提示
public void mainMenu() {
do {
System.out.println("========零钱通菜单(OOP)=========");
System.out.println("\t\t1 零钱通明细");
System.out.println("\t\t2 收益 入账");
System.out.println("\t\t3 消 费");
System.out.println("\t\t4 退 出");
System.out.print("请选择(1 - 4):");
key = scanner.next();
//使用switch - case判断选择的功能
switch (key) {
case "1":
this.detail();
break;
case "2":
this.income();
break;
case "3":
this.pay();
break;
case "4":
this.exit();
break;
default:
System.out.println("选择有误,请重新选择");
}
} while (loop);
System.out.println("--------退出了零钱通系统--------");
}
//2. 完成零钱通明细
public void detail() {
System.out.println(details + "\n");
}
//3. 完成收益入账
public void income() {
System.out.printf("收益入账金额:");
//money需要校验范围
money = scanner.nextDouble();
//金额不合理就return
if (money <= 0) {
System.out.println("收益入账金额 需要 大于0");
return;
}
balance += money;
date = new Date();
//字符串拼接:把收益详细信息拼接到details字符串上
details += "\n收益入账" + "\t+" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
}
//4. 消费
public void pay() {
System.out.printf("消费金额:");
//money需要校验范围
money = scanner.nextDouble();
//找出不合理的,然后return退出方法
if (money <= 0 || money > balance) {
System.out.println("您的消费金额应该在 0 - " + balance);
return;
}
System.out.printf("消费说明:");
note = scanner.next();
balance -= money;
date = new Date();
//字符串拼接:把消费信息拼接到details字符串上
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
}
//5. 退出程序的代码改进
public void exit() {
String choice = "";
//一段代码完成一个小功能,尽量不混在一起
//while和break结合
// - 来判断输入内容是否是y/n
while (true) {
System.out.println("你确定要退出吗?y/n");
choice = scanner.next();
//字符串判断相等需要用到equals()方法
if ("y".equals(choice) || "n".equals(choice)) {
break;
}
}
//当用户退出while,进行判断
if ("y".equals(choice)) {
loop = false;
}
}
}
package com.wpangzi.smallChange.oop;
//调用oop类中的方法
public class SmallChangeSysAPP {
public static void main(String[] args) {
new SmallChangeSysOOP().mainMenu();
}
}
效果:
本章作业
1. 类数组,元素顺序根据属性排序
使用冒泡排序,用年龄或者名字比较,然后交换数组中的对象
package com.wpangzi.homeWork;
public class HomeWork1 {
public static void main(String[] args) {
//数组是用方括号的
Person[] persons = new Person[3];
persons[0] = new Person("pang", 20, "学生");
persons[1] = new Person("shou", 27, "学生");
persons[2] = new Person("men", 22, "教师");
//排序
Person person1 = new Person();
System.out.println("====排序前====");
for (int i = 0; i < persons.length; i++) {
System.out.println(persons[i]);
}
System.out.println("====排序后====");
person1.sort(persons);
}
}
class Person {
private String name;
private int age;
private String job;
public Person() {
}
public Person(String name, int age, String job) {
this.name = name;
this.age = age;
this.job = job;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
//对年龄进行冒泡排序
public void sort(Person[] persons) {
Person temp = null;
for (int i = 0; i < persons.length - 1; i++) {
//内层循环一直是从0开始,
for (int j = 0; j < persons.length - 1 - i; j++) {
//年龄从大到小排序
//名字的话修改成name.length()就好了
if (persons[j].age < persons[j + 1].age) {
temp = persons[j];
persons[j] = persons[j + 1];
persons[j + 1] = temp;
}
}
}
for (int i = 0; i < persons.length; i++) {
System.out.println(persons[i]);
}
}
//重写toString()
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", job='" + job + '\'' +
'}';
}
}
效果:
2. 访问修饰符
3. 类继承以及方法重写
package com.wpangzi.homeWork;
public class HomeWork2 {
public static void main(String[] args) {
Professor professor = new Professor("jack", 30, "高级", 30000, 1.3);
professor.introduce();
}
}
class Teacher{
private String name;
private int age;
private String post;
private double salary;
//这里我们分析出来有工资级别
private double grade;
//输出教师信息
public void introduce(){
System.out.println("name = "+name+" age = "+age+" post = "+post+" salary = "+salary+" grade = "+grade);
}
public Teacher(String name, int age, String post, double salary, double grade) {
this.name = name;
this.age = age;
this.post = post;
this.salary = salary;
this.grade = grade;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPost() {
return post;
}
public void setPost(String post) {
this.post = post;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
class Professor extends Teacher{
public Professor(String name, int age, String post, double salary, double grade) {
super(name, age, post, salary, grade);
}
//重写introduce方法
public void introduce(){
System.out.println("教授信息...");
super.introduce();
}
}
4. 类继承,重写方法
- 写子类时,先分析是否有其特有属性以及父类中没有明确提到但是需要的属性
- 基类:属性 + 构造器 + get和set方法 + 自己的方法
- 子类:构造器(调用父类)+ 特有属性 + 重写方法/特有方法
- 写类时最重要的就是先分析出类的属性 和 方法
注意set和get方法(很灵活):
① 若属性不确定时,不用构造器初始化,用set方法来设置,这样可以动态设置属性值–>可以先给属性一个默认值,当需要改变值时,调用set方法来修改
② 写子类时,若用到父类中private属性,需要用到get方法
重写代码–>复用父类代码
5. this和super
注意图片中标识的内容,this:如果本类有该内容就不会找父类的
6. 子类和父类构造器
创建子类Demo对象,会先调用对应子类Demo的构造器,子类构造器中会默认调用父类的无参构造器
① 子类构造器一定会调用父类的构造器(隐式默认调用无参或者显式调用)
② 若父类中没有无参构造器,就需要在子类中显式调用父类的有参构造器
③ 子类的构造器可以显式的选择要调用的父类构造器
④ 构造器中this和super只存在一个
7. 方法重写(好好体会)
这里巧妙使用了父类的deposit方法(父类是核心,扩展时创建一个子类,在父类的基础上进行扩展)
- 开闭原则:只扩展,不修改
新增业务是存款手续费需要1元,那么就创建一个它的子类,
在子类中对父类的内容进行调整super.deposit(amount - 1);
,而不修改父类内容(高内聚,低耦合)
main方法测试
public static void main(String[] args) {
//测试CheckingAccount
CheckingAccount checkingAccount = new CheckingAccount(1000);
checkingAccount.deposit(10);//1010 - 1 = 1009
System.out.println(checkingAccount.getBalance());
//测试SavingsAccount
SavingsAccount savingsAccount = new SavingsAccount(1000);
savingsAccount.deposit(100);
savingsAccount.deposit(100);
savingsAccount.deposit(100);
System.out.println(savingsAccount.getBalance());//1300
savingsAccount.deposit(100);
System.out.println(savingsAccount.getBalance());//1399(减了手续费)
//月初,计时器调用earnMonthlyInterest方法
System.out.println(savingsAccount.getBalance());//余额+利息
savingsAccount.deposit(100);}//因为重置了次数,所以免手续费
}
父类(核心类,只扩展不修改)
class BankAccount {
//属性-余额
private double balance;
//构造器
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
//get和set方法
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//存款 - amount就是变动的钱
public void deposit(double amount) {
balance += amount;
}
//取款
public void withdraw(double amount) {
balance -= amount;
}
}
子类(扩展:收取1元手续费)
class CheckingAccount extends BankAccount {
//属性
//方法
//构造器(把变动的钱传给父类的构造器)
public CheckingAccount(double initialBalance) {
super(initialBalance);
}
//重写父类存款方法
@Override
public void deposit(double amount) {
/*
这里巧妙使用了父类的deposit方法(可能父类的deposit方法很复杂,但是我们只需给amount-1)
开闭原则:只扩展,不修改
新增业务是存款手续费需要1元,那么就创建一个它的子类,
在子类中对父类的内容进行调整(对变动的金额-1),而不修改父类内容(高内聚,低耦合)
*/
super.deposit(amount - 1);
}
//重写父类取款方法
@Override
public void withdraw(double amount) {
super.withdraw(amount + 1);
}
}
子类(扩展:收取1元手续费)
/*
每个月都有利息产生(earnMonthlyInterest方法被调用)
并且每个月三次免手续费的存款或取款。
在earnMonthlyInterest方法中重置交易次数
*/
class SavingsAccount extends BankAccount{
//属性(自己初始化没用构造器初始化)
private int cont = 3;//交易次数
private double rate = 0.01;//利率
public SavingsAccount(double initialBalance) {
super(initialBalance);
}
public int getCont() {
return cont;
}
public void setCont(int cont) {
this.cont = cont;
}
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
//重写 - 免手续费的逻辑
@Override
public void deposit(double amount) {
if (cont>0){
super.deposit(amount);//免手续费
}else{
super.deposit(amount-1);
}
cont--;
}
@Override
public void withdraw(double amount) {
if (cont>0){
super.withdraw(amount);
}else{
super.withdraw(amount+1);
}
cont--;
}
//每个月初,统计上个月利息,同时重置免单次数
public void earnMonthlyInterest(){
cont=3;
//把利息加到余额里
super.deposit(getBalance()*rate);
}
}
8. 类中重写equals方法(过关斩将的思想)
- 判断两个对象是否相同(地址)
- 找出不正确的(过关斩将)–>因为找正确的需要写很多嵌套,而找不正确的,找到即可return false终止比较。
2.1 判断obj是否是SavingsAccount类型或者其子类
2.2 向下转型,判断各个属性值是否相等即可- 调用equals方法:
person1.equals(person2)
public boolean equals(Object obj) {
//判断两个对象是否相同(地址)
if (this == obj) {
return true;
}
/*
找出不正确的(过关斩将)
判断obj是否是SavingsAccount类型或者其子类
*/
if (!(obj instanceof SavingsAccount)){//不是的话
return false;
}
//向下转型,因为现在obj的运行类型肯定是savingsAccount或其子类
//然后判断各个属性值是否相等即可
SavingsAccount that = (SavingsAccount) obj;
return cont == that.cont &&
Double.compare(that.rate, rate) == 0;
}
9. 向上/下转型,编译/运行类型
10. == 和 equals的区别
11. 综合(封装、继承、多态)
① 方法重写部分
父类共有部分
子类特有部分
② 应当分析出还有一个打印信息的方法
- 提取出信息共有的部分写到父类中
- 在子类中根据自己的需要对父类的字符串进行拼接或者是调用
子类中重写该方法(信息的组合)
测试调用
③ 第7个问题,对toString方法进行重写,来输出排列后数组元素的信息
④ 第8个问题 形参是person类型,调用子类特殊方法
12. 构造器的程序阅读
13. 什么是多态,多态体现在哪
14. Java动态绑定机制
比如讲这个知识点时的例子,
- 子类要调用方法A,但是子类中没有,所以去父类中找方法A,找到了
- 方法A中又调用了方法B,此时因为运行类型是子类,所以B方法会先去子类中寻找是否存在,若没有才回去父类中找
- 所以要切记方法和运行类型是时刻绑定的,方法总是会先去运行类型中查找