08、Java学习-面向对象中级:
Java学习第十二天——面向对象中级:
IDEA:
创建完新项目后,再src里面创建.java文件进行编写。
- src——存放源码文件(.java文件);
- out——存放编译后的字节码文件(.class文件)
在IDEA中,我们run一个文件时,会先编译成一个class文件,再运行。
常用快捷键:
- 删除当前行:ctrl + y;
- 复制当前行:ctrl + d;
- 补全代码:alt + /;
- 添加注释和取消注释:ctrl + /;(第一次是注释,后一次是取消注释);
- 导入改行需要的类:先配置 auto import,然后使用alt + enter即可;
- 快速格式化代码:ctrl + alt + L;
- 快速运行程序:alt + R;
- 快速生成构造器:(自定义为Alt + I了)Alt + Insert,选择Constructor,要选择属性的话要按住ctrl;
- 查看类的层级关系(学完类后很有用):把光标放在要查找的类上,然后ctrl + H;
- 定位方法(学完继承后很有用):将光标放到一个方法上,输入 ctrl + B,可以定位到方法;
- 自动分配变量名:通过再后面跟.var ,举例——
new.Mytools().var
; - 还有其余很多的快捷键,暂时不介绍。
模板(template)/自定义模板:
file -> settings -> editor -> Live templates -> 查看有哪些模板快捷键/可以自己增加模板。
模板可以高效地完成开发。
不过,建议新手暂时不用哦,还是再熟悉熟悉代码。
包:
为什么要有包?
因为我们在一个文件夹下不能有两个同名文件。
三大作用:
- 区分相同名字的类;
- 当类很多时,可以很好地管理类;
- 控制访问范围
包的基本语法:
package 包名;
class 类名{}
- package 为关键字,表示打包;
- 下面的内容表示在这个包内的内容。
包的本质:
其实就是创建不同的文件夹/目录来保存类文件。
在IDEA中,我们找到src,右键,找到package,点击,输入包名便创建好了。
包名里的"."表示第几级目录,如:com.xiaoming,com为xiaoming的上一级目录,其内含有子目录xiaoming
包的命名:
命名规则:
只能包含数字、字母、下划线、小圆点.,但不能用数字开头,不能是关键字或保留字。
命名规范:
一般是小写字母+小圆点,一般是:
com.公司名.项目名.业务模块名。
常用的包:
一个包下,包含很多的类,java中常用的包有:
- java.lang.*——lang是基本包,默认引用,不需要再引入;
- java.util.*——util包,系统提供的工具包,工具类,如Scanner;
- java.net.*——网络包,网络开发;
- java.awt.*——是做java的界面开发,GUI。
如何引用包?
有两种格式:
- import java.util.Scanner;这种格式就是只引入包下的一个特定的类;(import 包名.类名;)
- import java.util.*; 这种格式表示将java.util包所有都引入。(import 包名. *;)
package com.jiangxian.pkg;
import java.util.Arrays;
/*
我们需要使用哪个类就引入哪个类就好了;
尽量不要使用*号导入。
*/
public class Import01 {
public static void main(String[] args) {
// 使用系统提供的Arrays完成数组排序;
int[] arr = {-1, 20, 2, 13, 3};
// 比如对其进行排序;
// 传统方法是,自己编写排序函数(冒泡)
// 系统是提供了相关的类,可以方便完成Arrays
Arrays.sort(arr);
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i] + "\t");
}
}
}
注意事项和使用细节:
- package的作用是声明当前类所在的包,需要放再类(class)的最上面,一个类中最多有一句package;
- import指令 位置放置再package的下面,在类定义的前面,可以有多句且没有顺序要求。
访问修饰符:
基本介绍:
java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用public修饰,对外公开;
- 受保护级别:用protected修饰,对子类和同一个包中的类公开;
- 默认级别:没有修饰符号,向同一个包中的类公开;
- 私有级别:用private修饰,==只有类本身可以访问,不对外公开。==6
访问级别 | 访问修饰控制符 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | 没有修饰符 | √ | √ | × | × |
私有 | private | √ | × | × | × |
注意事项:
- 修饰符可以用来修饰属性,成员方法,和类(只有public和默认可以修饰类);
- 只有默认和public可以修饰类,且遵循上表的访问权限特点;
- 暂时没有学习继承,关于子类的访问权限,在下文展开;
- 成员方法的访问规则和属性完全一致。
面向对象编程三大特征:
基本介绍:
- 封装;
- 继承;
- 多态。
封装(Encapsulation)——第一大特征:
封装介绍:
封装(encapsulation)就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(方法),才能对数据进行操作。
封装的理解和好处:
- 隐藏实现的细节;
- 可以对数据进行验证,保证安全合理性。
- 比如我们使用电脑,但是我们可以不用去知道电脑是怎么运行的。
封装的实现步骤:
- 属性私有化private;
- 提供一个public的set用于修改属性(Alt + I调出,选择Setter);
- 提供一个public的get方法,用于获取某个属性的值(Alt + I调出,选择Getter)
封装的快速入门:
package com.jiangxian.encap;
public class Encapsulational01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("江弦");
person.setAge(21);
person.setSalary(30000);
System.out.println(person.info());
System.out.println(person.getSalary());
// 使用构造器:
Person person2 = new Person(80,"凤九",500000);
System.out.println("=======凤九的信息=======");
System.out.println(person2.info());
}
}
class Person{
public String name;
private int age;
private double salary;
public void say(int n, String name){
}
public Person(int age, String name, double salary) {
this.age = age;
this.name = name;
this.salary = salary;
// 将set写入构造器内,这样仍然可以验证。
setAge(age);
setSalary(salary);
setName(name);
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
if(name.length() >= 2 && name.length() <= 6){
this.name = name;
}else{
this.name = "无名人";
System.out.println("名字的长度不对,请输入(2-6)个字符的名字。");
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age >=0 && age <= 150){
this.age = age;
}else{
this.age = 0;
System.out.println("你设置的年龄不正确。");
}
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String info(){
return "信息为 name=" + name + ", age=" + age + ", salary=" + salary;
}
}
封装与构造器:
24.11.6
可以将封装与构造器结合,为什么要这样做呢,因为我们想要在构造器中也引入防控机制,如上文中:
public Person(int age, String name, double salary) {
//this.age = age;
//this.name = name;
//this.salary = salary;
// 将set写入构造器内,这样仍然可以验证。
setAge(age);
setSalary(salary);
setName(name);
}
练习:
package com.jiangxian.encap;
public class Account {
private String name;
private double balance;
private String password;
public Account(String name, double balance, String password) {
setName(name);
setBalance(balance);
setPassword(password);
}
public Account() {}
public String getName() {
return name;
}
public void setName(String name) {
if(name.length() >= 2 && name.length() <=4) {
this.name = name;
}else{
this.name = "无名人";
System.out.println("请输入2~4个字符的名字。");
}
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
if(balance > 20){
this.balance = balance;
}else{
this.balance = 0;
System.out.println("余额必须大于20。");
}
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
if(password.length() == 6){
this.password = password;
}else{
this.password = "000000";
// sout
System.out.println("密码长度有误,你的默认密码是000000");
}
}
public String info(){
return "name=" + name + ", balance=" + balance + ", password=" + password;
}
}
package com.jiangxian.encap;
public class AccountTest {
public static void main(String[] args) {
Account account = new Account("jiangxian", 100, "123456");
System.out.println(account.info());
System.out.println("======江弦的账户======");
Account jiangxian = new Account("江弦", 3080, "123456");
System.out.println(jiangxian.info());
}
}
继承(Extends)——第二大特征:
为什么需要继承?
从一个例子引入:
package com.jiangxian.extend_;
// 小学生 -》 模拟小学生考试的情况
public class Pupil {
public String name;
public int age;
private double score;
public void setName(String name) {
this.name = name;
}
public void testing(){
System.out.println("小学生 " + name + "正在考小学数学。");
}
public void info(){
System.out.println("小学生名 " + name + " 年龄 " + age + " 成绩 " + score);
}
}
package com.jiangxian.extend_;
// 大学生 -》 模拟大学生的考试情况
public class Graduate {
public String name;
public int age;
private double score;
public void setName(String name) {
this.name = name;
}
public void testing(){
System.out.println("大学生 " + name + "正在考大学数学。");
}
public void info(){ // 和小学生不一样的地方
System.out.println("大学生名 " + name + " 年龄 " + age + " 成绩 " + score);
}
}
package com.jiangxian.extend_;
public class Extends01 {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "XiaoMing";
pupil.age = 10;
pupil.setScore(60);
pupil.testing();
pupil.info();
System.out.println("======");
Graduate graduate = new Graduate();
graduate.name = "DaMing";
graduate.age = 22;
graduate.testing();
graduate.setScore(100);
graduate.info();
}
}
我们发现,两个类的属性与方法有很多都是相同的,我们可以使用继承去解决我们的代码复用问题。
基本介绍:
继承可以解决代码复用,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。
基本语法:
class 子类 extends 父类{
}
- 子类就会自动拥有父类定义的属性和方法;
- 父类叫做超类,基类(共有属性和共有方法);
- 子类又叫派生类(特有属性和特有方法)。
快速入门:
package com.jiangxian.extend_.improve_;
// 父类,是Pupil和Graduate的父类
public class Student {
// 共有的属性与方法如下所示:
public String name;
public int age;
private double score;
public void setScore(double score) {
this.score = score;
}
public void info(){
System.out.println("学生名 " + name + " 年龄 " + age + " 成绩 " + score);
}
}
package com.jiangxian.extend_.improve_;
public class Pupil extends Student{
public void testing(){
System.out.println("小学生正在考小学数学..");
}
}
package com.jiangxian.extend_.improve_;
public class Graduate extends Student {
public void testing(){
System.out.println("大学生正在考大学数学..");
}
}
package com.jiangxian.extend_.improve_;
public class Test {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "XiaoMing";
pupil.age = 10;
pupil.setScore(60);
pupil.testing();
pupil.info();
System.out.println("======");
Graduate graduate = new Graduate();
graduate.name = "DaMing";
graduate.age = 22;
graduate.testing();
graduate.setScore(100);
graduate.info();
}
}
继承带来的便利:
- 代码的复用性提高了;
- 代码的扩展性和维护性提高了。
继承的细节:
-
子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问(可以间接访问),要通过父类提供的公共的方法;
package com.jiangxian.extend_; public class Base { // 父类 // 四个不同修饰类型的四个属性 public int n1 = 100; protected int n2 = 200; int n3 = 300; private int n4 = 400; // 无参构造器 public Base(){ System.out.println("base()...."); } // 四个不同修饰符的方法 public void test100(){ System.out.println("test100()...."); } protected void test200(){ System.out.println("test200()...."); } void test300(){ System.out.println("test300()...."); } private void test400(){ System.out.println("test400()...."); } // 父类提供一个public的方法: public int getN4() { return n4; } public void callTest400(){ test400(); } }
package com.jiangxian.extend_; public class Sub extends Base{ public Sub(){ System.out.println("sub()..."); } public void sayOk(){ // 非私有的属性可以在子类直接访问; System.out.println(n1 + " " + n2 + " " + n3 + " " + getN4()); // System.out.println(n4); test100(); test200(); test300(); // test400 callTest400(); } }
package com.jiangxian.extend_; public class ExtendsDetail { public static void main(String[] args) { Sub sub = new Sub(); sub.sayOk(); } }
-
子类必须调用父类的构造器,完成父类的初始化。
-
当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译会不通过。(第一个代码演示有默认构造器的情况,剩下两个代码演示没有默认构造器的情况)
public class Sub extends Base{ public Sub(){ // 这里隐藏了一句super();这样就会默认调用系统的无参构造器 System.out.println("sub()..."); } public Sub(String name){ System.out.println("sub(String name)..."); } public void sayOk(){ // 非私有的属性可以在子类直接访问; System.out.println(n1 + " " + n2 + " " + n3 + " " + getN4()); // System.out.println(n4); test100(); test200(); test300(); // test400 callTest400(); } }
// 父类没有提供无参构造器 package com.jiangxian.extend_; public class Base { // 父类 // 四个不同修饰类型的四个属性 public int n1 = 100; protected int n2 = 200; int n3 = 300; private int n4 = 400; // 无参构造器 // public Base(){ // System.out.println("base()...."); // } public Base(String name, int age){ System.out.println("base(String, int age)...."); } // 四个不同修饰符的方法 public void test100(){ System.out.println("test100()...."); } protected void test200(){ System.out.println("test200()...."); } void test300(){ System.out.println("test300()...."); } private void test400(){ System.out.println("test400()...."); } // 父类提供一个public的方法: public int getN4() { return n4; } public void callTest400(){ test400(); } }
package com.jiangxian.extend_; public class Sub extends Base{ public Sub(){ // 这里隐藏了一句super();这样就会默认调用系统的无参构造器 super("Smith", 10); System.out.println("sub()..."); } public Sub(String name){ super("Tom", 100); System.out.println("sub(String name)..."); } public void sayOk(){ // 非私有的属性可以在子类直接访问; System.out.println(n1 + " " + n2 + " " + n3 + " " + getN4()); // System.out.println(n4); test100(); test200(); test300(); // test400 callTest400(); } }
-
若希望指定去调用父类的某个构造器,则显示的调用一下:super(参数列表);
package com.jiangxian.extend_; public class Sub extends Base{ public Sub(String name, int age){ // 1. 调用父类的无参构造器,如下,或者什么都不写就会默认调用super(): // super();// 父类的无参构造器 // 2.调用父类的Base(String name) // super("江弦"); // 3.调用父类的Base(String name, int age) super(name, age); System.out.println("Sub(String name, int age)..."); } public Sub(){ // 这里隐藏了一句super();这样就会默认调用系统的无参构造器 super("Smith", 10); System.out.println("sub()..."); } public Sub(String name){ super("Tom", 100); System.out.println("sub(String name)..."); } public void sayOk(){ // 非私有的属性可以在子类直接访问; System.out.println(n1 + " " + n2 + " " + n3 + " " + getN4()); // System.out.println(n4); test100(); test200(); test300(); // test400 callTest400(); } }
-
super在使用时,需要放在子类构造器的第一行,super关键字只能在构造器中使用;(先有爸爸,才能有儿子)
-
super()和this()(this()是调用同类中的其它构造器的意思)都只能放在构造器的第一行,且这两个方法不能共存一个构造器;
-
java所有类都是Object类的子类;
-
父类构造器的调用不限于直接父类!将一直往上追溯到Object类(顶级父类);
-
子类最多只能继承一个父类(指直接继承),即java中是单继承机制(想要A继承B和C只能是B继承C,然后A再继承B);
-
不能滥用继承,子类和父类之间必须满足is-a的逻辑关系(Person is a Music)。
继承的本质:
(294/910)
当子类对象创建好后,建立查找关系。
super关键字:
super代表父类的引用,用于访问父类的属性、方法、构造器。
基本语法:
- 访问父类的属性,但不能访问父类的private属性:super.属性名;
- 访问父类的方法,不能访问父类的private方法:super.方法(参数列表);
- 访问父类的构造器:super(参数列表);只能放在构造器的第一句,只能出现一句。
super给编程带来的便利/细节:
- 调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子类初始化);
- 当子类中有和父类的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。若没有重名,使用super、this、直接访问时一样的效果;
- super访问不限于直接父类,若爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;若多个基类中都有重名的成员,使用super访问就近原则。
super和this的比较:
No. | 区别点 | super | this |
---|---|---|---|
1 | 访问属性 | 从父类开始查找属性 | 访问本类的属性,如果本类没有此属性则从父类中继续查找 |
2 | 调用方法 | 从父类开始查找方法 | 访问本类中的方法,如果本类没有此方法则从父类继续查找 |
3 | 调用构造器 | 调用父类构造器,必须放在子类构造器的首行 | 调用本类构造器,必须放在构造器的首行 |
4 | 特殊 | 子类访问父类的对象 | 表示当前对象 |
例子:
package com.jiangxian.super_;
public class Base {
public int n1 = 999;
public int age = 111;
public void cal(){
System.out.println("Base类的cal()...");
}
public void eat(){
System.out.println("Base类的eat()...");
}
}
package com.jiangxian.super_;
public class A extends Base{
// 4个属性:
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public A(){}
public A(String name){}
public A(String name, int age){}
// public void cal(){
// System.out.println("A类的cal()...");
// }
public void test100(){}
protected void test200(){}
void test300(){}
private void test400(){}
}
package com.jiangxian.super_;
public class B extends A{
public int n1 = 888;
// 编写测试方法:
public void test(){
// super的访问不限于直接父类,若爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;
// 若多个基类(上级类)中都有同名的成员,使用super访问就近原则。
System.out.println("super.n1=" + super.n1);
super.cal();
}
public void hi(){
System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
}
public void cal(){
System.out.println("B类的cal()...");
}
public void sum(){
System.out.println("B类的sum()...");
// 希望调用父类A的cal;
// 这时,因为子类B没有cal方法,因此我可以使用下面三种方式
// 找cal方法时(cal()和this.cal()),顺序:
// 1.先找本类,若有,则调用;
// 2.若没有,则找父类(若有,且可以调用,则调用)
// 3.若父类没有,则继续找父类的父类,整个规则都是一样的,一直找到Object类
// 提示:若查找方法的过程中,找到了,但不能访问(private),则报错,cannot access
// 若查找方法的过程中,没有找到,则提示方法不存在。
// cal();
this.cal();
// 找cal方法(super.cal())的顺序是直接查找父类,其他的规则一样
// super.cal()
// 演示访问属性的规则:
// 和方法一样。
System.out.println(n1);
System.out.println(this.n1);
// 查找父类的:
System.out.println(super.n1);
}
public void ok(){
super.test100();
super.test200();
super.test300();
// super.test400(); 不能直接访问父类的private方法
}
public B(){
super("jack");
}
}
package com.jiangxian.super_;
public class Super01 {
public static void main(String[] args) {
B b = new B();
b.test();
}
}
方法重写/覆盖(override):
基本介绍:
方法重写就是子类有一个方法,和父类的某个方法的名称、返回类型、参数都一样,那么,我们就说子类的这个方法覆盖了父类的方法。
快速入门:
package com.jiangxian.override_;
public class Animal {
public void cry(){
System.out.println("动物叫唤...");
}
}
package com.jiangxian.override_;
public class Dog extends Animal{
// 1.Dog是Animal子类;
// 2.Dog的cry方法和Animal类的cry定义形式一样(名称、返回类型、参数)
// 3.这时我们就说Dog的cry方法,重写了Animal的cry方法
public void cry(){
System.out.println("小狗在叫...");
}
}
package com.jiangxian.override_;
public class Override01 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.cry();
}
}
注意事项和使用细节:
需要满足以下条件:
- 子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样;
- 子类方法的返回类型和父类方法返回类型一样,或者是父类发返回类型的子类,例如:父类 返回类型是Object,子类方法返回类型是String
- public Object getInfo();父类
- public String getInfo();子类
- 子类方法不能缩小父类方法的访问权限。
- void sayOk();
- public void sayOk();
package com.jiangxian.override_;
public class Animal {
public void cry(){
System.out.println("动物叫唤...");
}
public Object m1(){
return null;
}
public String m2(){
return null;
}
public AAA m3(){
return null;
}
protected void eat(){}
}
package com.jiangxian.override_;
public class Dog extends Animal{
// 1.Dog是Animal子类;
// 2.Dog的cry方法和Animal类的cry定义形式一样(名称、返回类型、参数)
// 3.这时我们就说Dog的cry方法,重写了Animal的cry方法
public void cry(){
System.out.println("小狗在叫...");
}
// 细节:子类方法的返回类型和父类方法返回类型一样,
// 或者是父类返回类型的子类。
public String m1(){ //构成重写,因为String是Object的子类
return null;
}
// 报错,提示一个不兼容的返回类型
// public Object m2(){
// return null;
// }
public BBB m3(){
return null;
}
public void eat(){}
// protected void eat(){}
}
class AAA{
}
class BBB extends AAA {
}
package com.jiangxian.override_;
public class Override01 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.cry();
}
}
和重载的区别:
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载(overload) | 本类 | 必须一样 | 至少有一个不同 | 无要求 | 无要求 |
重写(override) | 父子类 | 必须一样 | 全部相同 | 子类重写的方法,返回类型和父类一致或者是父类返回类型的子类。 | 子类方法不能缩小父类方法的访问范围。 |
多态(polymorphic)——第三大特征(难点):
考虑一个喂食问题,假设有一个动物园,里面每只动物都需要喂食且喂食的东西种类都不一样,虽然都是喂食,但实际喂食的东西不一样。
我们可以使用重载函数的方式去完成,但是不利于我们维护和管理(当动物很多的时候)。
package com.jiangxian.poly_;
public 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;
}
}
package com.jiangxian.poly_;
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(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());
}
}
package com.jiangxian.poly_;
public 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;
}
}
package com.jiangxian.poly_;
public class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
package com.jiangxian.poly_;
public class Dog extends Animal{
public Dog(String name) {
super(name);
}
}
package com.jiangxian.poly_;
public class Fish extends Food{
public Fish(String name) {
super(name);
}
}
package com.jiangxian.poly_;
public class Bone extends Food{
public Bone(String name) {
super(name);
}
}
为了解决代码的复用性不高,且不利于代码维护,我们引入了多态的概念。
多态的基本介绍:
方法或对象具有多种形态,是面向对象的第三大特征,多态是建立再封装和继承的基础上的。
多态的具体体现:
-
方法的多态(重写和重载体现多态);
-
对象的多态(核心,困难,重点):
- 一个对象的编译类型和运行类型可以不一致;
- 编译类型在定义对象时,就确定了,不能改变;
- 运行类型是可以变化的;
- 编译类型看定义时 = 号左边,运行类型看 = 右边;
Animal animal = new Dog();
animal的编译类型时Animal,运行类型是Doganimal = new Cat();
animal的运行类型现在变为了Cat,编译类型任然是Animal
package com.jiangxian.poly_.objpoly_; public class Animal { public void cry(){ System.out.println("Animal is crying..."); } }
package com.jiangxian.poly_.objpoly_; public class Dog extends Animal{ @Override // 注解 public void cry() { System.out.println("Dog is crying..."); } }
package com.jiangxian.poly_.objpoly_; public class Cat extends Animal{ @Override public void cry() { System.out.println("cat is crying..."); } }
package com.jiangxian.poly_.objpoly_; public class PolyObject { public static void main(String[] args) { // animal 的编译类型是 Animal,运行类型 Animal Animal animal = new Animal(); animal.cry(); // 执行到这行时,animal的运行类型是Animal,运行Animal的cry // animal1 的编译类型是 Animal,运行类型是Dog Animal animal1 = new Dog(); animal1.cry();// 执行到这行时,animal1的运行类型是Dog,运行Dog的cry Animal animal2 = new Cat(); animal2.cry(); // 编译类型仍然是 Animal,但现在的运行类型为 Dog; animal = new Dog(); animal.cry();// 执行Dog的cry } }
父类的对象引用可以指向子类对象,运行时以运行类型为主。
披着羊皮的狼(羊——编译类型,狼——运行类型)
快速入门:
现在我们来更改之前的代码:
package com.jiangxian.poly_;
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 使用多态机制:
// animal 的编译类型是Animal,可以接收(指向)Animal和Animal子类的对象
// food 的编译类型是 Food,可以接收(指向)Food 和 Food子类的对象
public void feed(Animal animal, Food food){
System.out.println("主人 " + name + " 给 " + animal.getName() + " 吃 " + food.getName());
}
// // 主任给小狗喂食:
// 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());
// }
}
多态的注意事项和细节讨论:
多态的前提:两个对象存在继承关系(建立在继承和封装的基础上);
多态的向上转型。
- 本质:父类的引用指向了子类的对象;
- 语法:父类类型 引用名 = new 子类类型();
- 特点,编译类型看左边,运行类型看右边。
- 可以调用父类中的所有成员(需遵守访问权限,无法使用private);
- 不能调用子类中特有成员(因为在编译阶段,能调用哪些成员由编译类型决定);
- 最终运行结果看子类的具体实现(若子类没有,就找父类,和前面super的调用是一样的)。
多态的向下转型:
-
语法:子类类型 引用名 = (子类类型) 父类引用;
-
只能强转父类的引用,不能强转父类的对象;
Animal animal = new Cat(); // animal是父类的引用,指向的确实是Cat类型的对象。 Cat cat = (Cat) animal;
-
要求父类的引用必须是指向当前目标类型的对象;(披着羊皮的狼是狼,不会是老虎。)
-
当向下转型后,可以调用子类类型中所有的成员。
属性的重写问题:
属性没有重写之说,属性的值看编译类型。
instanceof 比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类型(判断的是运行类型),若是,返回true,否则为false。
动态绑定机制(非常非常重要):
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定;
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
package com.jiangxian.poly_.dynamic;
public class A {
public int i = 10;
public int sum(){
return getI() + 10;
}
public int sum1(){
return i + 10;
}
public int getI(){ // 父类有getI
return i;
}
}
package com.jiangxian.poly_.dynamic;
public class B extends A{
public int i = 20;
// public int sum(){
// return i + 20;
// }
public int getI(){ // 子类也有getI
return i;
}
public int sum1(){
return i + 10;
}
}
package com.jiangxian.poly_.dynamic;
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum()); // 发生动态绑定
// 1.看a的运行类型是什么,发现时B类型的,所以调用子类的getI;
// 2.getI是返回属性,没有动态绑定,所以返回的是子类的i
// 3.由于子类没有sum函数,所以使用父类的,答案为30
System.out.println(a.sum1());
}
}
看到函数时要注意有没有动态绑定机制。
多态的应用:
1)多态数组:
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。
package com.jiangxian.poly_.polyarr_;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
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 say(){
return name + "\t" + age;
}
}
package com.jiangxian.poly_.polyarr_;
public class Student extends Person {
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String say() {
return "学生" + super.say() + "分数:" + score;
}
// 特有方法:
public void study(){
System.out.println("学生 " + getName() + " 正在学Java。");
}
}
package com.jiangxian.poly_.polyarr_;
public class Teacher extends Person{
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String say() {
return "教师 " + super.say() + " 薪水:" + salary;
}
// 特有方法:
public void teach(){
System.out.println("老师 " + getName() + " 正在学java课程。");
}
}
package com.jiangxian.poly_.polyarr_;
public class PolyArray {
public static void main(String[] args) {
Person[] persons = new Person[5];
persons[0] = new Person("Jack", 20);
persons[1] = new Student("Marry", 18, 100);
persons[2] = new Student("Smith", 19, 31);
persons[3] = new Teacher("Scott", 30, 20000);
persons[4] = new Teacher("King", 50, 25000);
for(int i = 0; i < persons.length; i++){
System.out.println(persons[i].say());
if(persons[i] instanceof Student){
((Student)persons[i]).study();
}else if(persons[i] instanceof Teacher){
((Teacher)persons[i]).teach();
}else if(persons[i] instanceof Person){
}else{
System.out.println("你输入的类型有误。");
}
}
}
}
2)多态参数:
方法定义的形参类型为父类类型,实参类型运行为子类类型。
例如:前面说的喂动物。
下面给出一个另外的例子:
package com.jiangxian.poly_.polyparameter_;
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public void setName(String name) {
this.name = name;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getAnnual(){
return salary*12;
}
}
package com.jiangxian.poly_.polyparameter_;
public class Manage extends Employee {
private double bonus;
public Manage(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public double getAnnual() {
return super.getAnnual() + bonus;
}
public void manage(){
// 因为name是私有类型,所以只能通过getName来访问
System.out.println("经理 " + getName() + " is managing.");
}
}
package com.jiangxian.poly_.polyparameter_;
public class Worker extends Employee{
public Worker(String name, double salary){
super(name, salary);
}
public void work(){
System.out.println("工人 " + getName() + " is working.");
}
}
package com.jiangxian.poly_.polyparameter_;
public class PolyParameter {
public static void main(String[] args) {
Worker tom = new Worker("Tom", 2500);
Manage milan = new Manage("Milan", 5000, 200000);
PolyParameter plogParameter = new PolyParameter();
plogParameter.showEmpAnnual(tom);
plogParameter.showEmpAnnual(milan);
plogParameter.testWork(tom);
plogParameter.testWork(milan);
}
public void showEmpAnnual(Employee e){
System.out.println(e.getAnnual());
}
public void testWork(Employee e){
if(e instanceof Worker){
((Worker)e).work();
}else if(e instanceof Manage){
((Manage)e).manage();
}else{
System.out.println("类型错误,请自检。");
}
}
}
Object类详解:
Object是所有类的父类,所以需要知道其内部有什么。
equals方法:
== 和 equals的对比:
== 是一个比较运算符:
- 既可以判断基本类型,也可以判断引用类型是否相等;
- 判断基本类型时,判断的是值是否相等;
- 判断引用类型时,判断地址是否相等,即判断是不是同一个对象(只关注运行类型,不管编译类型)。
package com.jiangxian.object_;
public class Equals01 {
public static void main(String[] args) {
A a = new A();
A b = a;
A c = new B();
A d = c;
System.out.println(c == d);
System.out.println(b == a);
}
}
class A{
}
class B extends A{
}
equals方法时Object类的一个方法:
-
只能用于判断引用类型;
-
默认判断的是地址是否相同,子类中往往重写该方法,用于判断内容是否相同。
public boolean equals(Object obj) { // Object中的equals原码 return (this == obj); // 使用== 判断,而用==判断引用类型就是判断地址是否相同,即是否是同一个对象 }
public boolean equals(Object obj) { // Integer中的equals原码 if (obj instanceof Integer) { // 先判断obj是不是Integer的类型和子类型 return value == ((Integer)obj).intValue(); // 若是,比较值的大小即可 } return false; // 若不是Integer的类型或子类,或者值不相等直接返回False }
如何重写equals方法?
package com.jiangxian.object_;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
setName(name);
setAge(age);
}
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 = "无名";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age >=0 && age <= 120){
this.age = age;
}else{
System.out.println("你的年龄不符合生物学,默认为0岁");
this.age = 0;
}
}
public boolean equals(Person obj){
if(obj == this){
return true;
}
if(obj instanceof Person){
Person person = (Person)obj;
return this.name.equals(person.getName()) && this.age == person.getAge();
}
return false;
}
}
package com.jiangxian.object_;
public class Equals02 {
public static void main(String[] args) {
Person p1 = new Person("江弦", 21);
Person p2 = new Person("江弦", 21);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
}
}
hashcode方法(在第二阶段深入,现在留个印象):
返回对象的哈希码值。支持此方法是为了提高哈希表(例如java.util.HashTable 提供的哈希表)的性能。
- 提高具有哈希结构的容器的效率;
- 两个引用,若指向同一个对象,则哈希值肯定是一样的;
- 两个引用,若指向的是不同的对象,则哈希值肯定是不一样的;
- 哈希值主要是根据地址号来的!不能完全将哈希值等价为地址;
- 以后在集合中,若hashCode需要的话,也可以进行重写。
package com.jiangxian.object_;
public class HashCode_ {
public static void main(String[] args) {
AA aa = new AA();
AA aa1 = new AA();
AA aa2 = aa;
System.out.println("aa.hashCode(): " + aa.hashCode());
System.out.println("aa1.hashCode(): " + aa1.hashCode());
System.out.println("aa2.hashCode(): " + aa2.hashCode());
}
}
class AA{
}
toString方法:
基本介绍:
默认返回:全类名(包名与类名) + @ + 哈希值的十六进制,子类往往重写toString方法,用于返回对象的属性信息。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
package com.jiangxian.object_;
public class ToString_ {
/*
原码:
(1)getClass().getName() 类的全类名(包名 + 类名)
(2)Integer.toHexString(hashCode()) 将对象的hashCode值转成16进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
public static void main(String[] args) {
Monster monster = new Monster("小妖怪", "巡山的", 1000);
System.out.println(monster.toString() + "hashcode: " + monster.hashCode());
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
}
重写:
由于经常重写,其也被集成到了快捷键Alt + Insert(我更改为了Alt + I)。
package com.jiangxian.object_;
public class ToString_ {
/*
原码:
(1)getClass().getName() 类的全类名(包名 + 类名)
(2)Integer.toHexString(hashCode()) 将对象的hashCode值转成16进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
public static void main(String[] args) {
Monster monster = new Monster("小妖怪", "巡山的", 1000);
System.out.println(monster.toString() + "hashcode: " + monster.hashCode());
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
// 重写toString方法,输出对象的属性
// 快捷键Alt + Insert,选择toString,默认是返回对象的属性值。
@Override
public String toString() {
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
}
直接输出一个对象时,toString方法会被默认的调用:
package com.jiangxian.object_;
public class ToString_ {
/*
原码:
(1)getClass().getName() 类的全类名(包名 + 类名)
(2)Integer.toHexString(hashCode()) 将对象的hashCode值转成16进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
public static void main(String[] args) {
Monster monster = new Monster("小妖怪", "巡山的", 1000);
System.out.println(monster.toString() + "hashcode: " + monster.hashCode());
System.out.println(monster);
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
// 重写toString方法,输出对象的属性
// 快捷键Alt + Insert,选择toString,默认是返回对象的属性值。
@Override
public String toString() {
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
}
finalize方法:
- 当对象被回收时,系统自动调用该对象的finalize方法,子类可以重写该方法,做一些释放资源的操作(比如,对象打开了一个文件,这就是占用了一个资源,关闭文件就是释放资源);
- 什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁对象,正在销毁对象前,会先调用finalize方法;
- 垃圾回收机制的调用,是由系统来决定,也可以通过System.gc()主动出发垃圾回收机制。
- 在新版中,finalize已经被弃用了且在实际开发中不会使用,所以就不细看了。
package com.jiangxian.object_;
public class Finalize_ {
public static void main(String[] args){
Car bmw = new Car("BMW"); // 现在有一个对象引用bmw指向Car对象
bmw = null;
// 现在将bmw指向 null, 那么之前创建的Car对象就没有人使用了,变为一个垃圾,
// 垃圾回收器就会进行销毁,即把堆中Car的那个空间给释放出来了
// 在销毁对象前,会调用该对象的finalize方法,我们可以在这个方法中,写一些自己的业务逻辑代码,比如释放资源(数据库连接,或者是打开的文件)
// 若程序员不重写finalize方法,那么,就会调用Object的finalize方法
// ,若程序员重写了finalize方法,就能实现自己的业务逻辑了。
System.gc();
}
}
class Car{
private String name;
public Car(String name){
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("销毁汽车" + this.name);
System.out.println("释放了某些资源。");
}
}
如何查看原码:
在IDEA中,将光标放在想要查看原码的方法上,点击ctrl + b即可访问。
或者右键方法,点击 Go To -> Declaration or Usages 访问。
断点调试:
为什么要有断点调试?
断点调试能让我们一步步的看原码的执行过程,从而发现错误所在;
在断点调试的过程中,是运行状态,是以对象的运行类型来执行的。
断点调试介绍:
- 断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住(该行此时没有执行),然后,你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下,进而分析从而找到bug所在。
- 是程序员必须掌握的技能;
- 断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平。
断点调试的快捷键:
- F7:跳入方法内;
- F8:逐行执行代码;
- shift + F8:跳出方法;
- F9:执行到下一个断点。
断点调试应用:
01:
想在哪行加断点,就将鼠标挪动到行号处,单击鼠标即可。然后debug运行
package com.jiangxian.debug_;
public class test01 {
public static void main(String[] args) {
int sum = 0;
for(int i = 0; i < 10; i++){
sum += i;
System.out.println("i=" + i);
System.out.println("sum=" + sum);
}
System.out.println("end..");
}
}
02:
数组越界:
package com.jiangxian.debug_;
public class test02 {
public static void main(String[] args) {
int arr[] = new int[5];
for (int i = 0; i <= arr.length; i++) {
System.out.println("arr[" + i + "] = " + arr[i]);
}
}
}
03:
进入JDK的方法源码,需要先配置一下:(要是不想看就自己勾选回来吧~)
- 点击Setting --> Build,Execution,Deployment --> Debugger --> Stepping;
- 把Do not step into the classes中的java.*,javax.*取消勾选,其他的随意。
但是进入源码可以会看晕,那么怎么出来呢?
可以使用shift + F8 跳出一层,重复,直到跳回我们进入的位置。
package com.jiangxian.debug_;
import java.util.Arrays;
public class test03 {
public static void main(String[] args) {
int[] arr = {1, -1, 10, -20, 100};
// 这个例子是为了看源码Arrays.sort的实现;
Arrays.sort(arr);
for(int i = 0; i< arr.length; i++){
System.out.println(arr[i]);
}
System.out.println("end..");
}
}
04:
演示如何执行到下一个断点:
package com.jiangxian.debug_;
import java.util.Arrays;
public class test04 {
public static void main(String[] args) {
int[] arr = {8, -1 , 199, 70, 10};
Arrays.sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i] + '\t');
}
System.out.println("hello100");// 断点
System.out.println("hello200");
System.out.println("hello300");
System.out.println("hello400");
System.out.println("hello500");
System.out.println("hello600"); // 断点
System.out.println("end...");
}
}
05:
查看对象的创建过程,以及动态继承的实现:
package com.jiangxian.debug_;
public class DebugExercise {
public static void main(String[] args) {
// 创建对象的过程:
// 1.加载Person类
// 2.1默认初始化;2.显示初始化;3.构造器初始化
// 3.返回对象的地址
Person person = new Person("江弦",21);
System.out.println(person);
}
}
class Person{
private String name;
private int age;
public Person(){}
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
零钱通:
模仿微信的零钱通:
项目的开发流程:
- 项目需求说明:
- 使用Java开发 零钱通项目,可以完成收益入账,消费,查看明细,退出系统等功能。
- 先完成菜单,并可以选择:
- 完成零钱通明细(即内部的功能)
- 完成收益入账;
- 完成消费;
- 完成退出功能;
项目开发说明:
- 项目代码实现:
- 先完成基本功能(过程编程);
- 后改进为OOP。
面向过程的代码:
package com.jiangxian.smallchange;
import java.text.SimpleDateFormat;
import java.util.Scanner;
import java.util.Date;
public class SmallChangeSys {
// 1.先完成显示菜单,并可以选择菜单,给出对应提示:
public static void main(String[] args) {
// 定义一个循环条件变脸loop
boolean loop = true;
// 为什么实现功能的选择,我们需要定义一个Scanner,与用户形成交互
Scanner scanner = new Scanner(System.in);
String key = "";
// 2.实现明细:1、使用数组;2、适用对象;3、使用String拼接;
// 为什么暂时这样定义就可以了呢,因为我们还没有收益入账,消费等,后续只要把这两个的内容拼接到details的后面就可以了
String details = "---------------零钱通明细---------------";
// 3.实现收益入账:要完成这个程序,驱动程序员增加新的变量和代码
double money = 0.0;
double balance = 0.0;
Date date = null; // date是java.util.Date 类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 可以用于日期格式化
// 4.完成消费:完成这个功能,我们也需要增加一些变量:
String note = "";
// 让用户对是否退出做出判断:
char choice;
do{// 为什么用do while循环,因为菜单至少要显示一次
System.out.println("\n===============零钱通菜单===============");
System.out.println("\t\t\t1 零钱通明细");
System.out.println("\t\t\t2 收益入账");
System.out.println("\t\t\t3 消 费");
System.out.println("\t\t\t4 退 出");
System.out.println("请选择功能(1-4):");
key = scanner.next();
// 使用分支控制:
switch(key){
case "1":
System.out.println(details);
break;
case "2":
System.out.println("收益入账金额:");
money = scanner.nextDouble();
if (money < 0){
System.out.println("你输入的金额有误,收益不应该为负数,请仔细确认!");
break;
}
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();
// money 的值需要校验,不能比余额大
if (money > balance){
System.out.println("你输入的金额有误,超过了你的剩余财产,请检验!");
break;
}
System.out.println("输入消费说明:");
note = scanner.next();
date = new Date();
balance -= money;
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t" + balance;
break;
case "4":
// 一段代码,一般只完成一个小功能,尽量不要混在一起,这样阅读起来比较轻松。
System.out.println("你确认要退出吗?(y/n)");
choice = scanner.next().charAt(0);
while(choice != 'y' && choice != 'n'){
System.out.println("你的输入有误,请输入yes或no,或者y或n!");
choice = scanner.next().charAt(0);
}
if(choice == 'y'){
loop = false;
}
break;
default:
System.out.println("你输入了零钱通没有的功能。");
}
}while(loop);
System.out.println("你退出了零钱通。");
}
}
OOP:
package com.jiangxian.smallchange;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
/**
* 该类是完成零钱通的各个功能的类;
* 使用OOP(面向对象编程);
* 将各个功能对应一个方法。
*/
public class SmallChangeOOP {
// 属性:怎么来的,就是我们面向过程里面设定的变量
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
String details = "---------------零钱通明细---------------";
double money = 0.0;
double balance = 0.0;
Date date = null; // date是java.util.Date 类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 可以用于日期格式化
String note = "";
char choice;
// 方法是怎么来的,就是我们分析时候拆解的实现步骤
// 先完成显示菜单,并可以选择:
public void menu(){
do{// 为什么用do while循环,因为菜单至少要显示一次
System.out.println("\n===============选择零钱通菜单(OOP)===============");
System.out.println("\t\t\t1 零钱通明细");
System.out.println("\t\t\t2 收益入账");
System.out.println("\t\t\t3 消 费");
System.out.println("\t\t\t4 退 出");
System.out.println("请选择功能(1-4):");
key = scanner.next();
// 使用分支控制:
switch(key){
case "1":
this.detail();
break;
case "2":
this.income();
break;
case "3":
this.consume();
break;
case "4":
// 一段代码,一般只完成一个小功能,尽量不要混在一起,这样阅读起来比较轻松。
this.exit();
break;
default:
System.out.println("你输入了零钱通没有的功能。");
}
}while(loop);
System.out.println("你退出了零钱通。");
}
// 再完成零钱通明细:
public void detail(){
System.out.println(details);
}
// 完成收入:
public void income(){
System.out.println("收益入账金额:");
money = scanner.nextDouble();
if (money < 0){
System.out.println("你输入的金额有误,收益不应该为负数,请仔细确认!");
// break; 在方法里应该是return,即退出方法,不再执行
return;
}
balance += money;
date = new Date(); // 获取当前的日期,格式很奇怪,在循环前,应该要设置下格式
details = details + "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t" + balance;
}
// 完成消费:
public void consume(){
System.out.println("消费金额为:");
money = scanner.nextDouble();
// money 的值需要校验,不能比余额大
if (money > balance){
System.out.println("你输入的金额有误,超过了你的剩余财产,请检验!");
return;
}
System.out.println("输入消费说明:");
note = scanner.next();
date = new Date();
balance -= money;
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t" + balance;
}
// 完成退出:
public void exit(){
System.out.println("你确认要退出吗?(y/n)");
choice = scanner.next().charAt(0);
while(choice != 'y' && choice != 'n'){
System.out.println("你的输入有误,请输入yes或no,或者y或n!");
choice = scanner.next().charAt(0);
}
if(choice == 'y'){
loop = false;
}
}
}
package com.jiangxian.smallchange;
public class SmallChangeSysApp {
public static void main(String[] args) {
new SmallChangeOOP().menu();
}
}
24.11.9——(343/910),这周状态有点小差,另外这章真的好多,学的比较慢。。。明天把作业写了~