目录
案例3:演示如何追源码,看看java设计者是怎么实现的。(提高编程思想)
Object类详解
equals方法
== 和 equals 的对比
1、==:既可以判断基本类型,又可以判断引用类型
2、==:如果判断基本类型,判断的是值是否相等。示例:int i=10; double d=10.0;
3、==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象【案例说明】
4、equals:是Object类中的方法,只能判断引用类型,如何看Jdk源码,看演示
5、默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。
比如:Integer,String【看String 和 Integer 的 equals 源代码】
package com.object;
public class Equals {
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 bObj = a;
System.out.println(bObj == c);//true
int num1 = 10;
double num2 = 10.0;
System.out.println(num1 == num2);//基本数据类型,判断值是否相等
//equals方法,源码怎么查看
//把光标放在equals方法,直接输入ctrl+b
// 看Jdk的源码 Object类的 equals方法
// public boolean equals(Object anObject) {
// if (this == anObject) {//如果是同一个对象
// return true;//返回true
// }
// if (anObject instanceof String) {//判断类型
// String anotherString = (String)anObject;//向下转型
// int n = value.length;
// if (n == anotherString.value.length) {//如果长度相同
// char v1[] = value;
// char v2[] = anotherString.value;
// int i = 0;
// while (n-- != 0) {//然后一个一个的比较字符
// if (v1[i] != v2[i])
// return false;
// i++;
// }
// return true;//如果两个字符串的所有字符都相等,则返回true
// }
// }
// return false;//如果比较的不是字符串,则直接返回false
// }
"hello".equals("abc");
//看Object类的 equals
//即Object 的equals 方法默认就是比较对象地址是否相同
//也就是判断两个对象是不是同一个对象
// public boolean equals(Object obj) {
// return (this == obj);
// }
//从源码可以看到 Integer 也重写了Object类的equals方法
//变成了判断两个值是否相同
// public boolean equals(Object obj) {
// if (obj instanceof Integer) {
// return value == ((Integer)obj).intValue();
// }
// return false;
// }
Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
System.out.println(integer1 == integer2);//false
System.out.println(integer1.equals(integer2));//true
String str1 = new String("甲柒");
String str2 = new String("甲柒");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
}
}
class B {
}
class A extends B {
}
如何重写equals方法
应用实例:
判断两个Person对象的内容是否相等,如果两个Person对象的各个属性值都一样,则返回true,反之false。
package com.object;
public class EqualsExercise {
public static void main(String[] args) {
Person person1 = new Person("jack", 10, '男');
Person person2 = new Person("jack", 10, '男');
Person person3 = new Person("tom", 20, '男');
Person person4 = new Person("tom", 29, '男');
System.out.println(person1.equals(person2));//true
System.out.println(person3.equals(person4));//false
}
}
//判断两个Person对象的内容是否相等,
//如果两个Person对象的各个属性值都一样,则返回true,反之false。
class Person {
private String name;
private int age;
private char gender;
//重写Object 的 equals方法
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则直接返回true
if (this == obj) {
return true;
}
//类型判断
if (obj instanceof Person) {//是Person,才比较
//进行 向下转型,因为需要得到obj的 各个属性
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == gender;
}
//如果不是Person,则直接返回false
return false;
}
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 char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
}
练习题
1、
package com.object;
public class EqualsExercise {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "甲柒";
Person p2 = new Person();
p2.name = "甲柒";
System.out.println(p1 == p2);//false
System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.equals(p2));//false
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.equals(s2));//true
System.out.println(s1 == s2);//false
}
}
class Person {
public String name;
}
运行结果
2、
package com.object;
public class EqualsExercise {
public static void main(String[] args) {
int it = 65;
float fl = 65.0f;
System.out.println("65和65.0f是否相等?" + (it == fl));//true
char ch1 = 'A';
char ch2 = 12;
System.out.println("65和'A'是否相等?" + (it == ch1));//true
System.out.println("12和ch2是否相等?" + (12 == ch2));//true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?" + (str1 == str2));//false
System.out.println("str1是否equals str2?" + (str1.equals(str2)));//true
// System.out.println("hello" == new java.sql.Date());//编译错误
}
}
运行结果
hashCode方法
小结:
1、提高具有哈希结构的容器的效率!
2、两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
3、两个引用,如果指向的是不同对象,则哈希值是不一样的
4、哈希值主要根据地址号来的!,不能完全将哈希值等价于地址。
5、案例演示
[测试:A obj1 = new A();A obj2 = new AO);Aobj3 = obj1]
6、后面在集合,中hashCode如果需要的话,也会重写
package com.object;
public class HashCode {
public static void main(String[] args) {
AA aa1 = new AA();
AA aa2 = new AA();
AA aa3 = aa1;
System.out.println("aa1.hashCode()=" + aa1.hashCode());
System.out.println("aa2.hashCode()=" + aa2.hashCode());
System.out.println("aa3.hashCode()=" + aa3.hashCode());
}
}
class AA {
}
运行结果
toString方法
基本介绍
默认返回:全类名+@+哈希值的十六进制,【查看Object 的toString方法】
子类往往重写toString方法,用于返回对象的属性信息
重写toString方法
打印对象或拼接对象时,都会自动调用该对象的toString形式
案例演示:Monster [name, job, sal] 案例:ToString.java
当直接输出一个对象时,toString方法会被默认的调用
比如:System.out.println(monster);就会默认调用monster.toString(0)
package com.object;
public class ToString {
public static void main(String[] args) {
//Object的toString() 源码
//(1)getClass().getName()类的全类名(包名+类名)
//(2)Integer.toHexString(hashCode())将对象的hashCode值转成16进制字符串
// public String toString() {
// return getClass().getName() + "@" + Integer.toHexString(hashCode());
// }
Monster monster = new Monster("小妖怪", "巡山的", 1000);
System.out.println(monster.toString() + " hashcode=" + monster.hashCode());
System.out.println("==当直接输出一个对象时,toString方法会被默认的调用==");
System.out.println(monster);//等价monster.toString()
}
}
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方法
1、当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作【演示】
2、什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法。
3、垃圾回收机制的调用,是由系统来决定(即有自己的GC算法),也可以通过System.gc() 主动触发垃圾回收机制,测试:Car [name]
提示:在实际开发中,几乎不会运用finalize,所以更多就是为了应付面试
package com.object;
//演示Finalize
public class Finalize {
public static void main(String[] args) {
Car car = new Car("兰博基尼");
car = null;
//这时 car对象就是一个垃圾,垃圾回收器就会回收(销毁)对象,
//在销毁对象前,会调用该对象的finalize方法
//,程序员就可以在finalize中,写自己的业务逻辑代码(比如释放资源:数据库连接,或者打开文件...)
//,如果程序员不重写 finalize,那么就会调用0bject类的 finalize,即默认处理
//,如果程序员重写了finalize,就可以实现自己的逻辑
System.gc();//主动调用垃圾回收器
System.out.println("程序退出了...");
}
}
class Car {
private String name;
public Car(String name) {
this.name = name;
}
//重写finalize
@Override
protected void finalize() throws Throwable {
System.out.println("销毁 汽车" + name);
System.out.println("释放了某些资源");
}
}
运行结果
断点调试(debug)
一个实际需求
1、在开发中:新手程序员在查找错误时,这时老程序员就会温馨提示,可以用断点调试, 一步一步的看源码执行的过程,从而发现错误所在。
2、重要提示:在断点调试过程中,是运行状态,是以对象的运行类型来执行的
A extends B; Bb = new A0); b.xx();
断点调试介绍
1、断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。进行分析从而找到这个Bug
2、断点调试是程序员必须掌握的技能。
3、断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平。
断点调试的快捷键
F7:(跳入)
F8:(跳过)
shift+F8:(跳出)
F9:(resume,执行到下一个断点)
F7:跳入方法内
F8:逐行执行代码
shift+F8:跳出方
断点调试应用案例
案例1:看变量的变化情况
package com.debug;
public class Debug {
public static void main(String[] args) {
//演示逐行执行代码
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += i;
System.out.println("i=" + i);
System.out.println("sum=" + sum);
}
System.out.println("退出for...");
}
}
案例2:看一下数组越界的异常
package com.debug;
public class Debug {
public static void main(String[] args) {
int[] arr = {1, 10, -1};
for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println("退出for...");
}
}
案例3:演示如何追源码,看看java设计者是怎么实现的。(提高编程思想)
小技巧:将光标放在某个变量上,可以看到最新的数据。
package com.debug;
import java.util.Arrays;
public class Debug {
public static void main(String[] args) {
int[] arr = {1, -1, 10, -20, 100};
//看Arrays.sort方法底层实现 ->Debug
Arrays.sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
案例4:演示如何直接执行到下一个断点F9 resume
小技巧:断点可以在debug过程中,动态的下断点
package com.debug;
import java.util.Arrays;
//演示执行到下一个断点,同时支持动态的下断点.
public class Debug {
public static void main(String[] args) {
int[] arr = {1, -1, 10, -20, 100};
//看Arrays.sort方法底层实现 ->Debug
Arrays.sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println("hello world~~~100");
System.out.println("hello world~~~200");
System.out.println("hello world~~~300");
System.out.println("hello world~~~400");
System.out.println("hello world~~~500");
System.out.println("hello world~~~600");
System.out.println("hello world~~~700");
System.out.println("hello world~~~800");
}
}
断点调试练习
1、使用断点调试的方法,追踪下一个对象创建的过程。
Person [name, age, 构造器..]
package com.debug;
//debug对象创建的过程,加深对调试的理解
public class DebugExercise {
public static void main(String[] args) {
Person jack = new Person("jack", 20);
System.out.println(jack);
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2、我们使用断点调试,查看动态绑定机制的如何工作
package com.dynamic;
public class DynamicBinding {
public static void main(String[] args) {
//a 的编译类型 A,运行类型 B
A a = new B();//向上转型
System.out.println(a.sum());//40 -> 30
System.out.println(a.sum1());//30 -> 20
}
}
class A {//父类
public int i = 10;
//动态绑定机制:
public int sum() {//父类sum()
return getI() + 10;//20 + 10
}
public int sum1() {//父类sum1()
return i + 10;//10 + 10
}
public int getI() {//父类getI()
return i;
}
}
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;
// }
}
项目-零钱通
项目开发流程说明
项目需求说明
使用Java开发零钱通项目,可以完成收益入账,消费,查看明细,退出系统等功能.
项目的界面
化繁为简
1、先完成显示菜单,并可以选择
2、完成零钱通明细
3、完成收益入账
4、消费
5、退出
项目代码实现
编写文件 SmallChangeSys.java 完成基本功能(过程编程)
提示:先使用过程编程,后面改成OOP版本,体会OOP编程带来的好处
package com.smallchange;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class SmallChangeSys {
//化繁为简
//1.先完成显示菜单,并可以选择菜单,给出对应提示
//2.完成零钱通明细
//3.完成收益入账
//4.消费
//5.退出
public static void main(String[] args) {
//定义相关变量
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
//2.完成零钱通明细
//思路
//(1)可以把收益入账和消费,保存到数组
//(2)可以使用对象
//(3)简单的话可以使用String拼接
String details = "=============零钱通明细=============";
//3.完成收益入账 完成功能驱动程序员增加新的变化和代码
//思路,定义新的变量
double money = 0;
double balance = 0;
Date date = null;// date是java.util.Date 类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");//可以用于日期格式化
//4.消费
//定义新变量,保存消费原因
String note = "";
do {
System.out.println();
System.out.println("=============零钱通菜单=============");
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分支结构
switch (key) {
case "1":
System.out.print(details);
break;
case "2":
System.out.print("收益入账金额:");
money = scanner.nextDouble();
//money 的值范围应该校验
balance += money;
//拼接收益入账信息到 details
date = new Date();//获取当前时间
// System.out.println(sdf.format(date));
details += "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
break;
case "3":
System.out.print("消费金额:");
money = scanner.nextDouble();
//money 的值范围应该校验
System.out.print("消费说明:");
note = scanner.next();
balance -= money;
//拼接消费信息信息到 details
date = new Date();//获取当前时间
// System.out.println(sdf.format(date));
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
break;
case "4":
System.out.println("4 退 出");
loop = false;
break;
default:
System.out.println("选择有误,请重新选择");
}
} while (loop);
System.out.println("============退出零钱通项目============");
}
}
运行结果
项目代码实现改进
1、用户输入4退出时,给出提示"你确定要退出吗? y/n",必须输入正确的y/n,否则
循环输入指令,直到输入y或者n。
2、在收益入账和消费时,判断金额是否合理,并给出相应的提示。
package com.smallchange;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class SmallChangeSys {
//化繁为简
//1.先完成显示菜单,并可以选择菜单,给出对应提示
//2.完成零钱通明细
//3.完成收益入账
//4.消费
//5.退出
//6.用户输入4退出时,给出提示"你确定要退出吗? y/n",必须输入正确的y/n,否则
//循环输入指令,直到输入y或者n。
//7.在收益入账和消费时,判断金额是否合理,并给出相应的提示。
public static void main(String[] args) {
//定义相关变量
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
//2.完成零钱通明细
//思路
//(1)可以把收益入账和消费,保存到数组
//(2)可以使用对象
//(3)简单的话可以使用String拼接
String details = "=============零钱通明细=============";
//3.完成收益入账 完成功能驱动程序员增加新的变化和代码
//思路,定义新的变量
double money = 0;
double balance = 0;
Date date = null;// date是java.util.Date 类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");//可以用于日期格式化
//4.消费
//定义新变量,保存消费原因
String note = "";
do {
System.out.println();
System.out.println("=============零钱通菜单=============");
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分支结构
switch (key) {
case "1":
System.out.print(details);
break;
case "2":
System.out.print("收益入账金额:");
money = scanner.nextDouble();
//money 的值范围应该校验
//思路
//找出不正确的金额条件,然后给出提示,就直接break
if (money <= 0) {
System.out.println("收益入账金额 需要 大于 0");
break;
}
balance += money;
//拼接收益入账信息到 details
date = new Date();//获取当前时间
// System.out.println(sdf.format(date));
details += "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
break;
case "3":
System.out.print("消费金额:");
money = scanner.nextDouble();
//money 的值范围应该校验
//找出金额不正确的情况
if (money <= 0 || money > balance) {
System.out.println("您的消费金额 应该在0-" + balance);
break;
}
System.out.print("消费说明:");
note = scanner.next();
balance -= money;
//拼接消费信息信息到 details
date = new Date();//获取当前时间
// System.out.println(sdf.format(date));
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
break;
case "4":
//6.用户输入4退出时,给出提示"你确定要退出吗? y/n",必须输入正确的y/n,否则
//循环输入指令,直到输入y或者n。
//思路
//(1)定义一个变量choice,接收用户的输入
//(2)使用while + break,来处理接收到的输入是 y 或者 n
//(3)退出while后,再判断choice是y还是n,就可以决定是香退出
//(4)建议一段代码,完成一个小功能,尽量不要混在一起
String choice = "";
while (true) {
System.out.println("你确定要退出吗? y/n :");
choice = scanner.next();
if ("y".equals(choice) || "n".equals(choice)) {
break;
}
//第二个方案
// if ("y".equals(choice)) {
// loop = false;
// break;
// } else if ("n".equals(choice)) {
// break;
// }
}
//当用户退出while后,进行判断
if (choice.equals("y")) {
loop = false;
}
break;
default:
System.out.println("选择有误,请重新选择");
}
} while (loop);
System.out.println("============退出零钱通项目============");
}
}
3、将面向过程的代码修改成面向对象的方法,编写SmallChangeSysOOP.java类,
并使用 SmallChangeSysApp.java完成测试。
package com.smallchange.oop;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
//该类是完成零钱通的各个功能的类
//使用OOP(面向对象编程)
//将各个功能对应一个方法
public class SmallChangeSysOOP {
//属性..
//定义相关变量
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
//2.完成零钱通明细
//思路
//(1)可以把收益入账和消费,保存到数组
//(2)可以使用对象
//(3)简单的话可以使用String拼接
String details = "=============零钱通明细=============";
//3.完成收益入账 完成功能驱动程序员增加新的变化和代码
//思路,定义新的变量
double money = 0;
double balance = 0;
Date date = null;// date是java.util.Date 类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");//可以用于日期格式化
//4.消费
//定义新变量,保存消费原因
String note = "";
//先完成显示菜单,并可以选择
public void mainMenu() {
do {
System.out.println();
System.out.println("===========零钱通菜单(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分支结构
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("============退出零钱通项目============");
}
//完成零钱通明细
public void detail() {
System.out.println(details);
}
//完成收益入账
public void income() {
System.out.print("收益入账金额:");
money = scanner.nextDouble();
//money 的值范围应该校验
//思路
//找出不正确的金额条件,然后给出提示,就直接return
if (money <= 0) {
System.out.println("收益入账金额 需要 大于 0");
return;//退出方法,不在执行后面的代码
}
balance += money;
//拼接收益入账信息到 details
date = new Date();//获取当前时间
// System.out.println(sdf.format(date));
details += "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
}
//消费
public void pay() {
System.out.print("消费金额:");
money = scanner.nextDouble();
//money 的值范围应该校验
//找出金额不正确的情况
if (money <= 0 || money > balance) {
System.out.println("您的消费金额 应该在0-" + balance);
return;
}
System.out.print("消费说明:");
note = scanner.next();
balance -= money;
//拼接消费信息信息到 details
date = new Date();//获取当前时间
// System.out.println(sdf.format(date));
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
}
//退出
public void exit() {
//6.用户输入4退出时,给出提示"你确定要退出吗? y/n",必须输入正确的y/n,否则
//循环输入指令,直到输入y或者n。
//思路
//(1)定义一个变量choice,接收用户的输入
//(2)使用while + break,来处理接收到的输入是 y 或者 n
//(3)退出while后,再判断choice是y还是n,就可以决定是香退出
//(4)建议一段代码,完成一个小功能,尽量不要混在一起
String choice = "";
while (true) {
System.out.println("你确定要退出吗? y/n :");
choice = scanner.next();
if ("y".equals(choice) || "n".equals(choice)) {
break;
}
//第二个方案
// if ("y".equals(choice)) {
// loop = false;
// break;
// } else if ("n".equals(choice)) {
// break;
// }
}
//当用户退出while后,进行判断
if (choice.equals("y")) {
loop = false;
}
}
}
package com.smallchange.oop;
//这里我们直接调用SmallChangeSysOOP对象,显示主菜单即可
public class SmallChangeSysApp {
public static void main(String[] args) {
System.out.println("++++++++++++++++甲柒++++++++++++++++");
new SmallChangeSysOOP().mainMenu();
}
}
运行结果