目录
一、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里面自己配置的)
- 编码用UTF-8就行(只有在DOS上采用GBK)。
- IDEA中run一个程序的时候,就会自动编译生成字节码文件class放在out目录中。
2、快捷键
快捷键要熟悉:
- 快捷键:ctrl+Y删除行;ctrl+D复制行;alt+/ 补全;ctrl+/ 注释
- 自动导入该行需要的类:alt+enter就可以,不用自己import了
- 快速格式化代码:ctrl+alt+L; 快速运行程序:alt+R
- 生成构造器等:ctrl+insert
- 查看一个类的继承关系:ctrl+H
- ctrl+B:可以定位该方法的定位位置。
- 自动分配变量名:在方法后面加.var就自动定义变量了,比如new Person().var
模板快捷键(setting-editor-live templates可以查看模板,也能自己添加模板)
- 比如输入sout,就会自动生成System.out.println()了;main就可以自动生成主方法
上面这些快捷键都可以大大提高开发速度。
二、包
场景引出:想在一个项目下面定义两个相同名字的类。
包的三大作用:
- 区分相同名字的类
- 当类很多时,可以很好地管理类
- 控制访问范围
包的基本语法: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了。
包的规则:
- 包的命名规则:只能包含数字、字母、下划线、小圆点。不能以数字开头,不能是关键字或保留字 (比如demo.class.exec1 就不行 demo.12a也不行 demo.ab12.oa就可以)
- 常用的包:java.lang.*(默认是有的比如String、Math,不需要再引用)、java.util.*(系统提供的工具包)、java.net.*(网络包,网络开发)、java.awt.*(做java界面开发的,GUI)
- 如何导入包:import 包名; 比如import java.util.Scanner , 如果要导入该包下面的所有类,就用import java.util.* (一般还是需要哪个类就导入哪个)
- 比如用Arrays.sort(arr)就可以很快对数组arr进行排序。
- 包package作用是申明当前类所在包,需要放在最上面。一个类只能有一个package。import要放在package下面。
三、访问修饰符
java一共提供了4种访问修饰符号,用于控制方法和属性的访问权限:
- public:公开级别,对外公开。
- protected:受保护级别,对子类和同一个包中的类公开。
- 默认:向同一个包的类公开。
- private:私有级别,只有本类中可以访问。
注意:只有默认的和public才能修饰类,遵循上面的权限规则。
四、核心1:封装
面向对象编程的三大特征:封装Encap、继承Extends和多态。
封装:把抽象出的属性和方法封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对数据进行操作。有很多好处:
- 隐藏实现的细节(使用方法的时候,只需要调用,不用管怎么实现的)
- 可以对数据进行验证,保证安全合理。(比如传入数据不合规的时候可以报错)
封装的实现步骤——分为3个步骤:
- 将属性进行私有化private(让外部无法直接修改)
- 提供一个public的set方法,对属性的判断并赋值。public void setXxx(参数名){属性=参数名}
- 提供一个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、继承的本质(内存)
- new了之后,首先查找son的父类信息,所以首先加载Object、然后Grandpa、Father、son(见方法区右下角)
- 然后在堆里面,首先给grandpa类分配属性,然后给Father类分配属性,最后才是son。分别都会开空间。(遇到字符串就会再指向常量池)
- 在栈里面,名字为son的对象,指向地址0X11,所以这个空间里面是有3个小空间的。
- 当用son对象来访问name的时候,要看查找关系了。按照下面的规则:
- 先看子类是否有属性,如果子类有,并且可以访问,则访问信息(如果不能访问就直接报错了,虽然内存中是有的,就不会再向上级找了)
- 如果子类没有这个属性,就看父类是否有这个属性(有且可以访问,就返回信息)
- 如果父类也没有就继续找上级父类,直到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、基础版本
化繁为简:
- 先打印出菜单,让用户可以选择。菜单直接print打印就好。用dowhile进入不断提示用户输入选项、接收选项,并且用一个switch来进行选项的判断。
- 完成零钱通明细:如何存放零钱的这些信息,有三种方法:
- 可以把收益入账和消费数据,保存到数组中
- 可以使用对象
- 可以用String进行拼接(比较简单笨拙),先用这种
- 完成收益入账:需要接收当前入账金额、时间、余额,这3种信息。
- 完成消费:需要额外创建一个变量接收消费说明。然后在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(面向对象)的方法更有助于在后期进行扩展和管理方法。