8.1 包
8.1.1 看一个应用场景
现在有两个程序员共同开发一个java项目,程序员xiaoming希望定义一个类取名Dog,程序员xiaoqiang也想定义一个类也叫Dog。两个程序员为此还吵架了,该怎么办呢?
8.1.2 包的三大作用
- 区别相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
8.1.3 包基本语法
eg:package com.aimoyudett;
说明:
- package 关键字,表示打包
- com.aimoyudett:表示包名
8.1.4 包的本质分析(原理)
包的本质 实际上就是创建不同的文件夹/目录来保存类文件。
8.1.5 快速入门
针对开头所说的场景,我们将用包解决他们的矛盾
运行结果:
8.1.6 包的命名
8.1.6.1 命名规则
只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
eg:
demo.class.exec1 //错误 class是关键字
ermo.12a // 错误 12a是数字开头
demo.ab12.oa // 对
8.1.6.2 命名规范
一般是小写字母 + 小圆点 ** com.公司名.项目名.业务模块名**
比如:com.aimoyudett.oa.model;
举例:
com.sina.crm.user; //用户模块
com.sina.crm.order; //订单模块
com.sina.crm.utils; //工具类
8.1.7 常用的包
一个包下,包含很多的类.java中常用的包有:
- java.lang.* //lang包是基本包,默认引入,不需要再引入
- java.util.* //util包,系统提供的工具包,工具类,使用Scanner
- java.net.* //网络包,网络开发
- java.awt.* //是做java的界面开发,GUI
8.1.8 如何引入包
语法: import 包名;
我们引入一个包的主要目的是要使用该包下的类,比如import java.util.Scanner;就只是引入一个类Scanner;import java.util.*;表示将java.util包下的所有都引入。
package com.aimoyudett;
import java.util.Arrays;
//注意:
//爱摸鱼的TT~建议:我们需要使用到哪个类,就导入哪个类即可,不建议使用 *导入
// import java.util.Scanner;
// 表示只会引入 java.util 包下的 Scanner
// import java.util.*;
// 表示将 java.util 包下的所有类都引入(导入)
public class Import01 {
public static void main(String[] args) {
//使用系统提供 Arrays 完成 数组排序
int[] arr = {-1,2,0,12,1,6};
//比如对其进行排序
//传统方法是,自己编写排序(冒泡)
//系统是提供了相关的类,可以方便完成 Arrays
Arrays.sort(arr);
//输出排序结果
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
8.1.9 注意事项和使用细节
- package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package;
- import指令 位置放在package的下面,在类的定义前面,可以有多句且没有顺序要求。
8.2 访问修饰符
8.2.1 基本介绍
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
-
- 公开级别:用 public 修饰,对外公开
-
- 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
-
- 默认级别:没有修饰符号,向同一个包的类公开.
-
- 私有级别:用 private 修饰,只有类本身可以访问,不对外公开。
- 私有级别:用 private 修饰,只有类本身可以访问,不对外公开。
8.2.2 4种访问修饰符的访问范围
8.2.3 使用的注意事项
- 修饰符可以用来修饰类中的属性,成员方法以及类;
- 只有默认的和public才能修饰类!!!并且遵循上述访问权限的特点;
- 因为没有学习继承,因此,关于在子类中的访问权限,我们讲完子类后,再回头讲解;
- 成员方法的访问规则和属性完全一样。
8.3 面向对象编程—封装
8.3.1 基本介绍
面向对象编程有三大特征:封装、继承、多态。
8.3.2 封装介绍
封装(encapsulate)就是把抽象出的数据【属性】和对数据的操作【方法】封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作【方法】,才能对数据进行操作。
8.3.3 封装的理解和好处
- 隐藏实现细节:方法(连接数据库)<— 调用(传入参数…)
- 可以对数据进行验证,保证安全合理。
8.3.4 封装的实现步骤(三步)
- 将属性进行私有化private【不能直接修改属性】
- 提供一个公共的**(public)set方法**,用于对属性判断并赋值
public void setXXX(类型 参数名){ //XXX表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名;
}
- 提供一个公共的**(public)get方法**,用于获取属性的值
public 数据类型 getXXX(){ // 权限判断,XXX某个属性
return xx;
8.3.5 快速入门案例
请大家看一个小程序,不能随便查看人的年龄、工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认年龄,必须在1-120,,年龄和工资不能直接查看,name的长度在2-8字符之间。
package com.aimoyudett.encap;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-07 23:04
* @Modified By:
*/
public class Encapsulate {
public static void main(String[] args) {
//如果要使用快捷键 alt+r, 需要先配置主类
//第一次,我们使用鼠标点击形式运算程序,后面就可以用
Person person = new Person();
person.setName("爱摸鱼的TT~");
person.setAge(123);
person.setSalary(18000);
System.out.println(person.info());
}
}
class Person{
/*请大家看一个小程序,不能随便查看人的年龄、工资等隐私,并对设置的年龄进行合理的验证。
年龄合理就设置,否则给默认年龄,必须在1-120,年龄和工资不能直接查看,name的长度在2-8字符之间。
*/
String name;
private int age;
private double salary;
//自己写 setXxx 和 getXxx 太慢,我们使用快捷键
//然后根据要求来完善我们的代码.
public void setName(String name){
//加入对数据的校验,相当于增加了业务逻辑
if(name.length() >= 2 && name.length() <= 8) {
this.name = name;
}else{
System.out.println("名字的长度不对,需要(2-8)个字符,默认名字");
this.name = "无名氏";
}
}
public String getName(){
return name;
}
public void setAge(int age){
//加入对数据的校验,相当于增加了业务逻辑
if(age >= 1 && age <= 120) {
this.age = age;
}else{
System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄 18 ");
this.age = 18;
}
}
public int getAge(){
return age;
}
public void setSalary(double salary){
this.salary = salary;
}
public double getSalary(){
return salary;
}
//写一个方法,返回属性信息
public String info(){
return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
}
}
运行结果:
8.3.6 将构造器和 setXxx 结合
我们将上面代码增加构造器,
package com.aimoyudett.encap;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-07 23:04
* @Modified By:
*/
public class Encapsulate {
public static void main(String[] args) {
Person person1 = new Person("爱摸鱼的TT~", 123, 18000);
System.out.println(person1.info());
}
}
class Person{
/*请大家看一个小程序,不能随便查看人的年龄、工资等隐私,并对设置的年龄进行合理的验证。
年龄合理就设置,否则给默认年龄,必须在1-120,年龄和工资不能直接查看,name的长度在2-8字符之间。
*/
String name;
private int age;
private double salary;
//构造方法
public Person(){
}
public Person(String name, int age, double salary){
this.name = name;
this.age = age;
this.salary = salary;
}
//自己写 setXxx 和 getXxx 太慢,我们使用快捷键
//然后根据要求来完善我们的代码.
public void setName(String name){
//加入对数据的校验,相当于增加了业务逻辑
if(name.length() >= 2 && name.length() <= 8) {
this.name = name;
}else{
System.out.println("名字的长度不对,需要(2-8)个字符,默认名字");
this.name = "无名氏";
}
}
public String getName(){
return name;
}
public void setAge(int age){
//加入对数据的校验,相当于增加了业务逻辑
if(age >= 1 && age <= 120) {
this.age = age;
}else{
System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄 18 ");
this.age = 18;
}
}
public int getAge(){
return age;
}
public void setSalary(double salary){
this.salary = salary;
}
public double getSalary(){
return salary;
}
//写一个方法,返回属性信息
public String info(){
return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
}
}
运行结果:
从上面运行结果看出,age超出了规定范围,但是setAge方法不起作用,不会提示信息。那这种问题要咋整呢?
需要在有参构造方法中修改代码,将setXXX(xx) 写入!
public Person(String name, int age, double salary){
// this.name = name;
// this.age = age;
// this.salary = salary;
//我们可以将 set 方法写在构造器中,这样仍然可以验证
this.setName(name);
this.setAge(age);
this.setSalary(salary);
}
修改后运行的结果:
综合代码:
package com.aimoyudett.encap;
public class Encapsulate {
public static void main(String[] args) {
//如果要使用快捷键 alt+r, 需要先配置主类
//第一次,我们使用鼠标点击形式运算程序,后面就可以用
/*Person person = new Person();
person.setName("爱摸鱼的TT~");
person.setAge(123);
person.setSalary(18000);
System.out.println(person.info());*/
//如果我们自己使用构造器指定属性
Person person1 = new Person("爱摸鱼的TT~", 123, 18000);
System.out.println(person1.info());
}
}
class Person{
/*请大家看一个小程序,不能随便查看人的年龄、工资等隐私,并对设置的年龄进行合理的验证。
年龄合理就设置,否则给默认年龄,必须在1-120,年龄和工资不能直接查看,name的长度在2-8字符之间。
*/
String name;
private int age;
private double salary;
//构造方法
public Person(){
}
public Person(String name, int age, double salary){
// this.name = name;
// this.age = age;
// this.salary = salary;
//我们可以将 set 方法写在构造器中,这样仍然可以验证
this.setName(name);
this.setAge(age);
this.setSalary(salary);
}
//自己写 setXxx 和 getXxx 太慢,我们使用快捷键
//然后根据要求来完善我们的代码.
public void setName(String name){
//加入对数据的校验,相当于增加了业务逻辑
if(name.length() >= 2 && name.length() <= 8) {
this.name = name;
}else{
System.out.println("名字的长度不对,需要(2-8)个字符,默认名字");
this.name = "无名氏";
}
}
public String getName(){
return name;
}
public void setAge(int age){
//加入对数据的校验,相当于增加了业务逻辑
if(age >= 1 && age <= 120) {
this.age = age;
}else{
System.out.println("你设置年龄不对,需要在 (1-120), 给默认年龄 18 ");
this.age = 18;
}
}
public int getAge(){
return age;
}
public void setSalary(double salary){
this.salary = salary;
}
public double getSalary(){
return salary;
}
//写一个方法,返回属性信息
public String info(){
return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
}
}
8.3.7 巩固练习
创建程序,在其中定义两个类:Account 和 AccountTest类体会Java的封装性。
- Account类要求具有属性:姓名(长度为2位3位或4位)、余额(必须大于20)、密码(必须是六位),如果不满足,则给出提示信息,并给默认值(程序员自己定)
- 通过setXXX的方法给Account的属性赋值
- 在AccountTest中测试
package com.aimoyudett.encap;
public class Account {
/*1. Account类要求具有属性:姓名(长度为2位3位或4位)、余额(必须大于20)、密码(必须是六位),
如果不满足,则给出提示信息,并给默认值(程序员自己定)
2. 通过setXXX的方法给Account的属性赋值
3. 在AccountTest中测试
*/
private String name;
private double money;
private String passWord;
//构造方法
public Account(){}
public Account(String name, double money, String passWord){
this.setName(name);
this.setMoney(money);
this.setPassWord(passWord);
}
public String getName() {
return name;
}
public void setName(String name) {
if(name.length() >= 2 && name.length() <= 4) {
this.name = name;
}else{
System.out.println("名字的长度不对,需要(2-4)位字符,默认名字");
this.name = "爱摸鱼的TT~";
}
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
if(money >= 20.0) {
this.money = money;
}else{
System.out.println("金额不满足大于20元,默认金额");
this.money = 20;
}
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
if(passWord.length() == 6) {
this.passWord = passWord;
}else{
System.out.println("密码输入有误,不是六位数,默认密码");
this.passWord = "000000";
}
}
public void showInfo(){
System.out.println("账号信息 name=" + name + " 余额=" + money + " 密码" + passWord);
}
}
public class AccountTest {
public static void main(String[] args) {
Account account = new Account();
account.setName("Tom");
account.setMoney(12008);
account.setPassWord("123456");
//调用构方
//Account account = new Account("Tom", 12008, "123456");
//输出信息
account.showInfo();
}
}
运行结果:
8.4 面向对象编程—继承
8.4.1 为什么需要继承
还是用问题引入主题,我们编写两个类,一个是Pupil类(小学生),一个是Graduate(大学生毕业生),问题:两个类的属性和方法有很多是相同的,怎么办?
看上面代码,很明显很多代码是重复性了。那怎么解决代码复用性呢? ===> 继承
8.4.2 继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过** extends **来声明继承父类即可。画出继承的示意图。
8.4.3 继承的基本语法
class 子类 extends 父类{
}
- 子类就会自动拥有父类定义的属性和方法
- 父类又叫 超类、基类
- 子类又叫 派生类。
8.4.4 继承给编程带来的便利
- 代码的复用性提高了
2) 代码的扩展性和维护性提高了
8.4.5 继承的深入讨论/细节问题
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问 。
运行结果:
- 子类必须调用父类的构造器, 完成父类的初始化
运行结果:
- a、当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,b、如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过(怎么理解。)
a、
b、
- 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
- super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
- super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器【super() 是调用父类、this() 是调用本类】
- java 所有类都是 Object 类的子类, Object 是所有类的基类
- 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
- 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。
- 思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
8.4.6 继承的本质分析(重要!!!)
我们看一个案例分析当子类继承父类,创建子类对象时,内存中到底发生了什么?
爱摸鱼的TT~提示:当子类对象创建好后,其实就是建立(子父类)查找的关系。
以上就是继承的内存布局分析图,相信大家都能理解掌握,接下来就有个问题:如果要访问里面属性、方法,该怎么访问?
这时请大家注意,要按照查找关系来返回信息
- (1) 首先看子类是否有该属性
- (2) 如果子类有这个属性,并且可以访问,则返回信息
- (3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息…)
- (4) 如果父类没有就按照(3)的规则,继续找上级父类,直到Object…
- (5) 如果有该属性,但是不能直接访问,修饰符时private,则需要通过父类提供公共的方法去访问 。
package com.aimoyudett.extend_.import_;
/**
* 讲解继承的本质
*/
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();//内存的布局
System.out.println(son.name);//返回就是大头儿子
//System.out.println(son.age);//返回的就是39
//System.out.println(son.getAge());//返回的就是39
System.out.println(son.hobby);//返回的就是旅游
}
}
class GrandPa { //爷类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa {//父类
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
class Son extends Father { //子类
String name = "大头儿子";
}
咦,有同学会问 如果父类的age属性是被private修饰的,但是爷类的age属性不是私有的,那在内存 堆中不存在私有属性?答案是照样存在私有属性。还有访问时是直接跳过父类,来访问爷类的公有age属性?答案肯定是错的,它一直会直接访问父类age,如果没有公有的方法去访问,编译就会报错!!!
8.4.7 巩固练习
- 案例1
- 案例2
- 案例3
编写 Computer 类,包含 CPU、内存、硬盘等属性,getDetails 方法用于返回 Computer 的详细信息
编写 PC 子类,继承 Computer 类,添加特有属性【品牌 brand】
编写 NotePad 子类,继承 Computer 类,添加特有属性【color】
编写 Test 类,在 main 方法中创建 PC 和 NotePad 对象,分别给对象中特有的属性赋值,以及从 Computer 类继承的属性赋值,并使用方法并打印输出信息
- Computer类
//编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
public class Computer {
private String cpu;
private int memory;
private int disk;
public Computer(String cpu, int memory, int disk) {
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
}
//返回Computer信息
public String getDetails() {
return "cpu=" + cpu + " memory=" + memory + " disk=" + disk;
}
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;
}
}
- PC类
//编写PC子类,继承Computer类,添加特有属性【品牌brand】
public class PC extends Computer{
private String brand;
//这里IDEA 根据继承的规则,自动把构造器的调用写好
//这里也体现: 继承设计的基本思想,父类的构造器完成父类属性初始化
//子类的构造器完成子类属性初始化
public PC(String cpu, int memory, int disk, String brand) {
super(cpu, memory, disk);
this.brand = brand;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void printInfo() {
System.out.print("PC信息:");
// System.out.println(getCpu() + getMemory() + getDisk());
//调用父类的getDetails方法,得到相关属性信息..
System.out.println(getDetails() + " brand=" + brand);
}
}
- 测试方法
public class ExtendsExercise03 {
public static void main(String[] args) {
PC pc = new PC("intel", 16, 500, "IBM");
pc.printInfo();
}
}
/*
编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
编写PC子类,继承Computer类,添加特有属性【品牌brand】
编写NotePad子类,继承Computer类,添加特有属性【color】 //同学们自己写。
编写Test类,在main方法中创建PC和NotePad对象,分别给对象中特有的属性赋值,
以及从Computer类继承的属性赋值,并使用方法并打印输出信息
*/
运行结果:
8.5 super 关键字
8.5.1 基本介绍
super 代表父类的引用,用于访问父类的属性、方法、构造器
8.5.2 基本语法
- 访问父类的属性/方法,但不能访问父类的private属性/方法【super.属性名/方法】
- 访问父类的构造器(这点前面用过)【super(参数列表);只能放在构造器的第一句,只能出现一句!】
8.5.3 super关键字的注意事项和细节
-
调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类属性由子类初始化)
-
当子类中有何父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果!【好好理解,难度不大】
分以下情况:
- 访问方法
- 找xxx方法时(xxx() 和 this.xxx()),顺序是:
- (1)先找本类,如果有,则调用
- (2)如果没有,则找父类(如果有,并可以调用,则调用)
- (3)如果父类没有,则继续找父类的父类,整个规则都是一样的,直到 Object类
- 提示:如果查找方法的过程中,找到了,但是不能访问, 则报错, cannot access
如果查找方法的过程中,没有找到,则提示方法不存在
- 找xxx方法(super.xxx()) 的顺序是直接查找父类,其他的规则一样
- 找xxx方法时(xxx() 和 this.xxx()),顺序是:
- 访问属性
- xx属性 和 this.xx 查找的规则是
- (1) 先找本类,如果有,则调用
- (2) 如果没有,则找父类(如果有,并可以调用,则调用)
- (3) 如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object类
- 提示:如果查找属性的过程中,找到了,但是不能访问, 则报错, cannot access
如果查找属性的过程中,没有找到,则提示属性不存在
- 找xx属性 (super.xx) 的顺序是直接查找父类属性,其他的规则一样
- xx属性 和 this.xx 查找的规则是
- super的访问不限于直接父类,如果爷爷类和本类中又同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。A->B->C,当然也需要遵守访问权限的相关规则。
8.5.4 super 和 this 的比较
8.6 方法重写/覆盖(override)
8.6.1 基本介绍
简单来说:方法覆盖(重写)就是子类有一个方法,和父类(可以不是直接父类,爷爷类等都行)的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法。
8.6.2 注意事项和使用细节
方法重写也叫方法覆盖,需要满足下面的条件:
- 子类的方法的形参列表、方法名称 要和父类方法的形参列表、方法名称完全一致。
- 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类。比如:父类 返回类型是Objet,子类的返回类型是String。
父类:
public Object getInfo(){
}
子类:
public String getInfo(){
}
- 子类方法不能缩小父类方法的访问权限【public > protected > 默认 > private】
父类:
protected void getInfo(){
}
子类:
public void getInfo(){
}
8.6.3 方法重写和重载比较
8.6.4 巩固练习
-
- 编写一个 Person 类,包括属性/private(name、age),构造器、方法 say(返回自我介绍的字符串)。
- 编写一个 Student 类,继承 Person 类,增加 id、score 属性/private,以及构造器,定义 say 方法(返回自我介绍的信息)。
3) 在 main 中,分别创建 Person 和 Student 对象,调用 say 方法输出自我介绍
- Person类
//编写一个Person类,包括属性/private(name、age),构造器、方法say(返回自我介绍的字符串)
public 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;
}
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;
}
}
- Student类
//编写一个Student类,继承Person类,增加id、score属性/private,以及构造器,定义say方法(返回自我介绍的信息)。
public 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;
}
//say
public String say() { //这里体现super的一个好处,代码复用.
return super.say() + " id=" + id + " score=" + score;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
- Test类
public class Test {
public static void main(String[] args) {
//在main中,分别创建Person和Student对象,调用say方法输出自我介绍
Person person = new Person("爱摸鱼的TT~", 18);
System.out.println(person.say());
Student student = new Student("Jack", 20, 123456, 99.8);
System.out.println(student.say());
}
}
运行结果:
8.7 面向对象编程—多态
8.7.1 先看一个问题
请编写一个程序,Master类中有一个feed(喂食)方法,可以完成主人给动物喂食物的信息。
- 使用传统的方法来解决(private属性)
代码编写:
//动物类
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;
}
}
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
//食物类
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;
}
}
public class Fish extends Food {
public Fish(String name) {
super(name);
}
}
public class Bone extends Food {
public Bone(String name) {
super(name);
}
}
//主人
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());
}
}
//测试类
public class Poly01 {
public static void main(String[] args) {
Master tom = new Master("汤姆");
Dog dog = new Dog("大黄~");
Bone bone = new Bone("大棒骨~");
tom.feed(dog, bone);
Cat cat = new Cat("小花猫~");
Fish fish = new Fish("黄花鱼~");
System.out.println("===========-------");
tom.feed(cat, fish);
}
}
运行结果:
从上面看,传统的方法带来的问题是什么呢?代码的复用性不高,而且不利于代码维护。
那如何解决呢?引出我们要讲解的多态。
8.7.2 多态基本介绍
什么是多态呢?从字面意思理解就是多种状态。
方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
8.7.3 多态的具体体现
多态的具体体现主要就两种:第一种是方法的多态,第二种是对象的多态。下面就用代码举例说明。
- 方法的多态
重写和重载就体现多态
- 对象的多态 **(核心,困难,重点) **
爱摸鱼的TT~重要的几句话(记住):
(1)一个对象的编译类型和运行类型可以不一致;
//解读:父类的引用指向子类的对象【animal引用类型 指向 dog对象】
// animal编译类型是Animal,运行类型是Dog
Animal animal = new Dog();
(2)编译类型在定义对象时,就确定了,不能改变;
(3)运行类型是可以变化的;
// animal的运行类型变成了Cat,编译类型仍然是Animal
animal = new Cat();
(4)编译类型看定义时 =号的左边,运行类型看 =号的右边。
看看代码理解下:
public class Animal {
public void cry() {
System.out.println("Animal cry() 动物在叫....");
}
}
public class Cat extends Animal {
public void cry() {
System.out.println("Cat cry() 小猫喵喵叫...");
}
}
public class Dog extends Animal {
public void cry() {
System.out.println("Dog cry() 小狗汪汪叫...");
}
}
//测试类
public class PolyObject {
public static void main(String[] args) {
//体验对象多态特点
//animal 编译类型就是 Animal , 运行类型 Dog
Animal animal = new Dog();
//因为运行时 , 执行到该行时,animal运行类型是Dog,所以cry就是Dog的cry
animal.cry(); //小狗汪汪叫
//animal 编译类型 Animal,运行类型就是 Cat
animal = new Cat();
animal.cry(); //小猫喵喵叫
}
}
运行结果:
总结:父类的引用(编译类型)指向子类的对象(运行类型),在运行时,它是以运行类型为主。
因此,使用多态的机制来解决主人喂食物的问题,走代码。
将Master类的上面代码进行修改为父类的引用指向子类的对象方式进行编码
//使用多态机制,可以统一的管理主人喂食的问题
//animal 编译类型是Animal,可以指向(接收) Animal子类的对象
//food 编译类型是Food ,可以指向(接收) Food子类的对象
public void feed(Animal animal, Food food){
System.out.println("主人 " + name + " 给 " + animal.getName() + " 吃 " + food.getName());
}
8.7.4 多态注意事项和细节讨论
-
多态的前提:两个对象(类)存在继承关系;
-
多态的向上转型:
- 本质:父类的引用指向子类的对象;
- 语法:父类类型 引用名 = new 子类类型( );
- 特点:编译类型看左边,运行类型看右边
- 可以调用父类中的所有成员(需遵守访问权限)
- 不能调用子类中特有成员(属性和方法);【因为在编译阶段,能调用哪些成员(属性和方法),是由编译类型来决定的】
- 最终运行效果看子类(运行类型)的具体实现。即调用方法时,按照从子类(运行类型)开始查找方法
,然后调用,规则我前面讲的方法调用规则一致【super知识那块】。
public class Animal {
String name = "动物";
int age = 10;
public void sleep(){
System.out.println("睡");
}
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hello,你好");
}
}
public class Cat extends Animal {
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//Cat特有方法
System.out.println("猫抓老鼠");
}
}
//测试类
public class PolyDetail {
public static void main(String[] args) {
//向上转型: 父类的引用指向了子类的对象
//语法:父类类型引用名 = new 子类类型();
Animal animal = new Cat();
//Object obj = new Cat();//可以吗? 可以 Object 也是 Cat的父类
//向上转型调用方法的规则如下:
//(1)可以调用父类中的所有成员(需遵守访问权限)
//(2)但是不能调用子类的特有的成员
//(#)因为在编译阶段,能调用哪些成员(属性和方法),是由编译类型来决定的
//animal.catchMouse();错误
//(3)最终运行效果看子类(运行类型)的具体实现, 即调用方法时,按照从子类(运行类型)开始查找方法
//,然后调用,规则我前面我们讲的方法调用规则一致【super知识那块】。
animal.eat();//猫吃鱼..
animal.run();//跑
animal.show();//hello,你好
animal.sleep();//睡
System.out.println("ok~~");
}
}
- 多态的向下转型:
- 语法:子类类型 引用名 = (子类类型)父类引用;
- 只能强转父类的引用,不能强转父类的对象;
- 要求父类的引用必须指向的是当前目标类型的对象;
- 当向下转型后,可以调用子类类型中所有的成员。
public class PolyDetail {
public static void main(String[] args) {
//向上转型: 父类的引用指向了子类的对象
//语法:父类类型引用名 = new 子类类型();
Animal animal = new Cat();
//Object obj = new Cat();//可以吗? 可以 Object 也是 Cat的父类
//爱摸鱼的TT~希望,可以调用Cat的 catchMouse方法
//多态的向下转型
//(1)语法:子类类型 引用名 =(子类类型)父类引用;
//问一个问题? cat 的编译类型 Cat,运行类型也是 Cat
Cat cat = (Cat) animal;
//(3)当向下转型后,可以调用子类类型中所有的成员。
cat.catchMouse();//猫抓老鼠
//(2)要求父类的引用必须指向的是当前目标类型的对象
Dog dog = (Dog) animal; //可以吗? 不可以,不能将指向猫的动物变成狗的引用!!!
System.out.println("ok~~");
}
}
- 属性没有重写之说!属性的值看编译类型
public class PolyDetail02 {
public static void main(String[] args) {
//属性没有重写之说!属性的值看编译类型
Base base = new Sub();//向上转型
System.out.println(base.count);// ? 看编译类型 10
Sub sub = new Sub();
System.out.println(sub.count);//? 20
}
}
class Base { //父类
int count = 10;//属性
}
class Sub extends Base {//子类
int count = 20;//属性
}
- instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
public class PolyDetail03 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(bb instanceof AA);// true
//aa 编译类型 AA, 运行类型是BB
//BB是AA子类
AA aa = new BB();
System.out.println(aa instanceof AA);//true
System.out.println(aa instanceof BB);//true
Object obj = new Object();
System.out.println(obj instanceof AA);//false
String str = "hello";
//System.out.println(str instanceof AA);
System.out.println(str instanceof Object);//true
}
}
class AA {} //父类
class BB extends AA {}//子类
8.7.5 巩固练习
请说出下面的每条语句,哪些是正确的,哪些是错误的,为什么?
- 案例1
public class PolyExercise01{
public static void main(String[] args){
double d = 13.4; //ok
long l = (long)d; //ok
System.out,println(l); //13
int i = 5; //ok
boolean b = (boolean)i; //不对,boolean -> int
Object obj = "Hello"; //可以,向上转型
String objStr = (String)obj; //可以,向下转型
Object objPri = new Integer(5); //可以,向上转型
String str = (String)objPri; //错误ClassCastException,指向Integer的父类引用,转成String
Integer str1= (Integer_objPri; //可以,向下转型
- 案例2
8.7.6 Java 的动态绑定机制(非常非常重要!)
Java 重要特性: 动态绑定机制
首先,我们看看下面这个代码,输出什么结果:
0.png
不难得出结果,结合我们上所讲的知识:
最终运行效果看子类(运行类型)的具体实现。即调用方法时,按照从子类(运行类型)开始查找方法
,然后调用,规则我前面讲的方法调用规则一致【super知识那块】。
所以,运行结果是 40 和 30。
那我将子类的sum( ) 方法给注释了,就直接调用父类的sum( )/sum1( )方法,但getI( )方法父子类都有,那该调用谁的呢?这时输出结果又有啥变化呢?这里就涉及到动态绑定机制。
Java的动态绑定机制
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定;
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
public class DynamicBinding {
public static void main(String[] args) {
//a 的编译类型 A, 运行类型 B
A a = new B();//向上转型
System.out.println(a.sum());//? 30
System.out.println(a.sum1());//? 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;
// }
}
8.7.7 多态的应用
1. 多态数组
定义:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法。
Person类:
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;
}
}
Student类:
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;
}
//重写父类say()
public String say(){
return "学生 " + super.say() + " score = " + score;
}
}
Teacher类:
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;
}
//重写父类的say()
public String say(){
return "老师 " + super.say() + " salary = " + salary;
}
}
测试类:
public class PolyArray {
public static void main(String[] args) {
// 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象,
// 统一放在数组中,并调用每个对象 say 方法。
//向上转型
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
//循环遍历多态数组,调用say
for (int i = 0; i < persons.length; i++) {
//爱摸鱼的TT~提示: person[i] 编译类型是 Person ,运行类型是根据实际情况有JVM来判断
System.out.println(persons[i].say());//动态绑定机制
}
}
}
运行结果:
应用实例升级:如何调用子类特有的方法,比如Teacher 有一个 teach , Student 有一个 study怎么调用?
Student类多添加一个方法:
Teacher类多添加一个方法:
测试类:
public class PolyArray {
public static void main(String[] args) {
// 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象,
// 统一放在数组中,并调用每个对象 say 方法。
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
//循环遍历多态数组,调用say
for (int i = 0; i < persons.length; i++) {
//爱摸鱼的TT~提示: person[i] 编译类型是 Person ,运行类型是根据实际情况有JVM来判断
System.out.println(persons[i].say());//动态绑定机制
//这里大家聪明. 使用 类型判断 + 向下转型.
if(persons[i] instanceof Student) {//判断person[i] 的运行类型是不是Student
Student student = (Student)persons[i];//向下转型
student.study();
//小伙伴也可以使用一条语句 ((Student)persons[i]).study();
} else if(persons[i] instanceof Teacher) {
Teacher teacher = (Teacher)persons[i];
teacher.teach();
} else if(persons[i] instanceof Person){
//System.out.println("你的类型有误, 请自己检查...");
} else {
System.out.println("你的类型有误, 请自己检查...");
}
}
}
}
运行结果:
2. 多态参数
定义:方法定义的形参类型为父类类型,实参类型允许为子类类型
应用实例1:前面的主任喂动物【自己回头看看】
应用实例2:定义员工类Employee,包含姓名和月工资【private】,以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法。
测试类中添加一个showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法【e.getAnnual( )】
测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
Employee类:
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
//得到年工资的方法
public double getAnnual() {
return 12 * salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
Manger类:
public class Manager extends Employee{
private double bonus;
public Manager(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;
}
public void manage() {
System.out.println("经理 " + getName() + " is managing");
}
//重写获取年薪方法
@Override
public double getAnnual() {
return super.getAnnual() + bonus;
}
}
Worker类:
public class Worker extends Employee {
public Worker(String name, double salary) {
super(name, salary);
}
public void work() {
System.out.println("普通员工 " + getName() + " is working");
}
@Override
public double getAnnual() { //因为普通员工没有其它收入,则直接调用父类方法
return super.getAnnual();
}
}
测试类:
public class PloyParameter {
public static void main(String[] args) {
Worker tom = new Worker("tom", 2500);
Manager milan = new Manager("milan", 5000, 200000);
PloyParameter ployParameter = new PloyParameter();
ployParameter.showEmpAnnual(tom);
ployParameter.showEmpAnnual(milan);
ployParameter.testWork(tom);
ployParameter.testWork(milan);
}
//showEmpAnnual(Employee e)
//实现获取任何员工对象的年工资,并在main方法中调用该方法 [e.getAnnual()]
public void showEmpAnnual(Employee e) {
System.out.println(e.getAnnual());//动态绑定机制.
}
//添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
public void testWork(Employee e) {
if(e instanceof Worker) {
((Worker) e).work();//有向下转型操作
} else if(e instanceof Manager) {
((Manager) e).manage();//有向下转型操作
} else {
System.out.println("不做处理...");
}
}
}
运行结果:
8.7.8 小结
什么是多态、多态的具体体现有哪些?(可举例说明)
多态:方法或对象具有多种形态,是OOP的第三大特征,是建立在封装和继承基础之上多态具体体现。
- 方法多态
- 重载体现多态
- 重写体现多态
- 对象多态
- 对象的编译类型和运行类型可以不一致,编译类型在定义时,就确定,不能变化
- 对象的运行类型是可以变化的,可以通过getClass()方法来查看运行类型
- 编译类型看定义时=号的左边,运行类型看定义时=号的右边
- 举例说明:
Java的动态绑定机制是什么?
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定;
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
8.8 Object 类详解
我们可以查看下jdk的帮助文档,看看Object类有哪些内容,要掌握什么
8.8.1 equals 方法
我们在前面也有用到过equals方法判断相等的情况,当时也没有大致说它的内涵,现在这里就详解下,并且需要掌握equals 和 == 二者的区别【面试题】
== 是一个比较运算符
- ==:既可以判断基本类型,又可以判断引用类型;
- ==:如果判断基本类型,判断的是值是否相等。比如int i = 10; double d = 10.0;//二者值是相等的
- ==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
equals方法
- equals:是Object类中对的方法,只能判断引用类型【要学会看jdk源码】
- 默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如:Integer、String【看看String 和 Integer的equals源代码】
接下来带大家一起分析源码【怎么查看源码?在配置好的前提,把光标放在XXX方法,直接输入ctrl+b】
- Object类的equals方法:比较对象地址是否相同
//即Object 的equals 方法默认就是比较对象地址是否相同
//也就是判断两个对象是不是同一个对象.
public boolean equals(Object obj) {
return (this == obj);
}
- String类的 equals方法:比较两个字符串值是否相同
//把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
}
- Integer类的equals方法:判断两个值是否相同
//从源码可以看到 Integer 也重写了Object的equals方法,
//变成了判断两个值是否相同
public boolean equals(Object obj) {
if (obj instanceof Integer) {
//(Integer)obj 就是向下转型,转换成Integer对象
return value == ((Integer)obj).intValue();
}
return false;
}
因此,从上面源码分析,容易得出:Object类是基类,它的equals方法是直接比较两个对像(对象地址)是否相等,而String、Integer等类是重写了Object类的equals方法,故它们equals方法是判断两个值是否相等。但是呢,我们自定义person类等自定义类,是不会自动重写Object类中equals方法的,所以在没有重写的前提还是比较两个对象地址是否相等,重写之后则比较两个值是否相等。
下面就做几道题巩固知识点
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("爱摸鱼的TT~");
String str2 = new String("爱摸鱼的TT~");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
Person person1 = new Person("爱摸鱼的TT~");
Person person2 = new Person("爱摸鱼的TT~");
System.out.println(person1 == person2);//false
System.out.println(person1.equals(person2));//true
8.8.2 如何重写 equals 方法
应用实例: 判断两个 Person 对象的内容是否相等,如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false。 【这题目很明显,就是要重写equals方法来比较两个对象内容是否相等】
步骤:
- 判断比较的两个对象是否为同一个对象
- 两个对象的类型判断
- 对象进行向下转型【因为需要得到子类的 各个属性】
public class EqualsExercise01 {
public static void main(String[] args) {
Person person1 = new Person("爱摸鱼的TT~", 10, '男');
Person person2 = new Person("爱摸鱼的TT~", 20, '男');
System.out.println(person1.equals(person2));//重写之后就是真
}
}
//判断两个Person对象的内容是否相等,
//如果两个Person对象的各个属性值都一样,则返回true,反之false
class Person{ //extends Object
private String name;
private int age;
private char gender;
//重写Object 的 equals方法
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 == p.gender;
}
//如果不是Person ,则直接返回false
return false;
}
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
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;
}
}
8.8.3 == 和 equals 的区别
名称 | 概念 | 是否可以用于基本数据类型 | 是否可以用于引用类型 |
---|---|---|---|
== | 比较运算符 | 可以,判断值是否相等 | 可以,判断两个对象是否相等 |
equals | Object类的方法,Java类都可以使用equals | 不可以 | 可以,默认是判断两个对象是否相等,但是子类往往重写该方法,比较对象的属性是否相等,比如String、Integer |
8.8.4 巩固练习:equals 和 ==
- 案例1:判断分析输出语句的结果
public class EqualsExercise02 {
public static void main(String[] args) {
Person_ p1 = new Person_();
p1.name = "爱摸鱼的TT~";
Person_ p2 = new Person_();
p2.name = "爱摸鱼的TT~";
System.out.println(p1==p2); //False
System.out.println(p1.name .equals( p2.name));//T 因为p1.name是字符串,字符串是重写了equals方法
System.out.println(p1.equals(p2));//False 而p1是person类,是没有自动重写equals方法
String s1 = new String("爱摸鱼的TT~");
String s2 = new String("爱摸鱼的TT~");
System.out.println(s1.equals(s2));//T
System.out.println(s1==s2); //F
}
}
class Person_{//类
public String name;
}
- 案例2:判断分析输出语句的结果
int it = 65;
float fl = 65.0f;
System.out.println(“65 和 65.0f 是否相等?” + (it == fl));//T
char ch1 = ‘A’;
char ch2 = 12;
System.out.println(“65 和‘A’是否相等?” + (it == ch1));//T
System.out.println(“12 和 ch2 是否相等?” + (12 == ch2));//T
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1 和 str2 是否相等?"+ (str1 == str2)); //F
System.out.println(“str1 是否 equals str2?”+(str1.equals(str2)));//T
System.out.println(“hello” == new java.sql.Date()); //编译错误
8.8.5 hashCode 方法
爱摸鱼的TT~的 6 个小结: 【记住!】
1) 提高具有哈希结构的容器的效率!
2) 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
3) 两个引用,如果指向的是不同对象,则哈希值是不一样的。【不是绝对的,如果在很大很大范围内,那就可能发生碰撞】
4) 哈希值主要根据地址号来的!不能完全将哈希值等价于地址。【注意这里的哈希值不是真正的内部地址,在Java中是无法得到真正的内部地址,因为它是运行在JVM中】
5) 案例演示[测试:A obj1 = new A(); A obj2 = new A(); A obj3 = obj1]
6) 后面在集合中 hashCode 如果需要的话,也会重写,在讲解集合时,爱摸鱼的TT~在说如何重写 hashCode()
public class HashCode_ {
public static void main(String[] args) {
AA aa = new AA();
AA aa2 = new AA();
AA aa3 = aa;
System.out.println("aa.hashCode()=" + aa.hashCode());//aa.hashCode()=460141958
System.out.println("aa2.hashCode()=" + aa2.hashCode());//aa2.hashCode()=1163157884
System.out.println("aa3.hashCode()=" + aa3.hashCode());//aa3.hashCode()=460141958
}
}
class AA {}
8.8.6 toString 方法
- 基本介绍
默认返回:全类名【指包名+类名】+@+哈希值的十六进制,【查看 Object 的 toString 方法】
子类往往重写 toString 方法,用于返回对象的属性信息
//Object的toString() 源码
//(1)getClass().getName() 类的全类名(包名+类名 )
//(2)Integer.toHexString(hashCode()) 将对象的hashCode值转成16进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
举例:没有重写toString方法的运行结果:
public class ToString_ {
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 方法,目的是打印对象或拼接对象时,都会自动调用该对象的 toString 形式;
public class ToString_ {
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+ c -> toString
@Override
public String toString() { //重写后,一般是把对象的属性值输出,当然程序员也可以自己定制
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
}
运行结果:
- 当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用monster.toString()
//补充代码
System.out.println("==当直接输出一个对象时,toString 方法会被默认的调用==");
System.out.println(monster); //等价 monster.toString()
运行结果:
8.8.7 finalize 方法
-
- 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作【什么是资源?比如数据库的连接就是资源、又如对象在调用时打开文件,也是一种资源】
-
- 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
-
- 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法,后面会讲解), 也可以通过** System.gc() **主动触发垃圾回收机制,测试:Car [name]
爱摸鱼的TT~提示: 我们在实际开发中,几乎不会运用 finalize , 所以更多就是为了应付面试。
public class Finalize_ {
public static void main(String[] args) {
Car bmw = new Car("宝马");
//这时 car对象就是一个垃圾,垃圾回收器就会回收(销毁)对象, 在销毁对象前,会调用该对象的finalize方法
//,程序员就可以在 finalize中,写自己的业务逻辑代码(比如释放资源:数据库连接,或者打开文件..)
//,如果程序员不重写 finalize,那么就会调用 Object类的 finalize, 即默认处理
//,如果程序员重写了finalize, 就可以实现自己的逻辑
bmw = null;
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("释放了某些资源...");//模拟而已
}
}
运行结果:
8.9 IDEA断点调试(debug)【较为重要】
1. 一个实际需求
- 在开发中,新手程序员在查找错误时,这时老程序员就会温馨提示,可以用断定调试,一步一步的看源码执行的过程,从而发现错误所在。
- 重要提醒:在断点调试过程中,是运行状态,是以对象的运行类型来执行的。
A extends B; B b = new A(); b.xx();
2. 断点调试介绍
- 断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。进行分析从而找到这个bug。
- 断点调试时程序员必须掌握的技能。
- 断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平。
3. 断点调试的快捷键
- F7(跳入):跳入方法内
- F8(跳过) :逐行执行代码
- shift+F8(跳出): 跳出方法
- F9(resume,执行到下一个断点)
4. 断点调试应用案例
1)案例1:看一下变量的变化情况等
public class Debug01 {
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=" + i);
}
System.out.println("退出for....");
}
}
运行debug F8快捷键操作
2)看一下数组越界的异常
public class Debug02 {
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 设计者是怎么实现的。(提高编程思想)。
小技巧:将光标放在某个变量上,可以看到最新的数据。【F7跳入】
//现在我们分析sort方法的源码
public class Debug03 {
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");
}
}
}
采用快捷键F7 和 shif + F8
好了,大致就这样查看方法的源码,但是有些同学按了快捷键不起作用,那该怎么办呢?需要自己设置好相关配置,具体如下:
点击Setting --> Build,Execution,Deployment --> Debugger --> Stepping
把Do not step into the classes中的java.*,javax.*取消勾选,其他的随意
4)演示如何直接执行到下一个断点 F9 resume。
爱摸鱼的TT~小技巧: 断点可以在 debug 过程中,动态的下断点
//演示执行到下一个断点,同时支持动态的下断点.
public class Debug04 {
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("hello100");
System.out.println("hello200");
System.out.println("hello300");
System.out.println("hello400");
System.out.println("hello500");
System.out.println("hello600");
System.out.println("hello700");
}
}
巩固习题
断点调试需要多加练习,慢慢熟悉它,之后会经常要用到的,像研究算法,底层东西时就会采用断点调试来理解代码。
8.10 项目-零钱通
8.10.1 项目开发流程说明
8.10.2 项目需求说明
使用 Java 开发 零钱通项目 , 可以完成收益入账,消费,查看明细,退出系统等功能。
8.10.3 项目的界面
化繁为简:
-
- 先完成显示菜单,并可以选择
-
- 完成零钱通明细.
-
- 完成收益入账
-
- 消费
-
- 退出
- 退出
8.10.4 项目代码实现
编写文件 SmallChangeSys.java 完成基本功能 (过程编程)
爱摸鱼的TT~提示:先使用过程编程,后面改成 OOP 版本,请小伙伴体会 OOP 编程带来的好处
package smallchange;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-09 16:22
* @Modified By:
*/
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 myscanner = new Scanner(System.in);
String key = "";
//2. 完成零钱通明细
//爱摸鱼的TT~思路:
//(1)可以把收益入账和消费,保存到数组
//(2)可以使用对象
//(3)简单的话可以使用String拼接
String details = "--------------零钱通明细--------------";
//3. 完成收益入账
//完成功能驱动程序员增加新的变量和代码
//爱摸鱼的TT~思路,定义新的变量
double money = 0;
double balance = 0; // 余额
Date date = null; // date 是java.util.Date类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); // 可以用于日期格式化的对象
//4. 消费
//定义新变量,记录保存消费的原因
String note = "";
//1. 先定义显示菜单,并可以选择菜单,给出相应信息
do{
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 = myscanner.next();
switch(key){
case "1":
System.out.println(details);
break;
case "2":
System.out.println("收益入账金额:");
money = myscanner.nextDouble();
//money的值范围应该校验
//爱摸鱼的TT~思路
//编程思想:就是在一系列判断时,找不符合条件即可【过关斩将 校验方式】
//找出不正确的金额条件,然后给出提示,就直接break
if(money <= 0){
System.out.println("收益入账金额 需要 大于 0");
break;
}
date = new Date();
balance += money;
//拼接收益入账信息到“details”
details += "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
break;
case "3":
System.out.println("消费金额:");
money = myscanner.nextDouble();
//money的值范围应该校验
//找出金额不正确的情况
//过关斩将 校验方式
if(money <= 0 || money > balance){
System.out.println("你的消费金额 应该在 0-" + balance);
break;
}
System.out.println("消费说明:");
note = myscanner.next();
balance -= money;
//拼接消费信息到“details”
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
break;
case "4":
//用户输入4退出时,给出提示“你确定要退出吗?y/n”,必须输入正确的y/n否则循环输入指令,知道输入y或者n。
//爱摸鱼的TT~思路分析:
//(1)定义一个变量 choice,接收用户的收入
//(2)使用while循环 + break,来处理接收到的输入时 y 或者 n
//(3)退出while后,在判断choice是y还是n,就可以决定是否退出
//(4)建议一段代码,完成一个小功能,尽量不要混在一起
String choice = "";
while(true){//要求用户输入y/n,否则就继续循环
System.out.println("你确定要退出吗?y/n");
choice = myscanner.next();
if("y".equals(choice) || "n".equals(choice)){
break;
}
//第二种方案:一个while循环含有两个功能
// if("y".equals(choice)){
// loop = false;
// break;
// }else if("n".equals(choice)){
// break;
// }
}
//当用户退出while,进行判断是y 还是n
if(choice.equals("y")){
loop = false;
}
break;
default:
System.out.println("选择有误,请重新选择");
}
}while(loop);
System.out.println("\n--------退出了零钱通项目--------");
}
}
8.10.5 项目代码实现改进
- 用户输入4退出时,给出提示“你确定要退出吗?y/n”,必须输入正确的y/n否则循环输入指令,知道输入y或者n。
- 在收益入账和消费时,判断金额是否合理,并给出相应的提示。
以上两个代码改进在8.10.4中实现了,大家需要认真分析解读里面的注释!!!
- 将面向过程的代码修改成面向对象的,编写SmallChangeSysOOP.java类,并使用SmallChangeSysApp.java完成测试。
- SmallChangeSysOOP类
package smallchange.oop;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
/**
* 该类是完成零钱通的各个功能的类
* 使用OOP(面向对象编程)
* 将各个功能对应一个方法.
*/
public class SmallChangeSysOOP {
//属性..
//定义相关变量
boolean loop = true;
Scanner myscanner = new Scanner(System.in);
String key = "";
//2. 完成零钱通明细
//爱摸鱼的TT~思路:
//(1)可以把收益入账和消费,保存到数组
//(2)可以使用对象
//(3)简单的话可以使用String拼接
String details = "--------------零钱通明细--------------";
//3. 完成收益入账
//完成功能驱动程序员增加新的变量和代码
//爱摸鱼的TT~思路,定义新的变量
double money = 0;
double balance = 0; // 余额
Date date = null; // date 是java.util.Date类型,表示日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); // 可以用于日期格式化的对象
//4. 消费
//定义新变量,记录保存消费的原因
String note = "";
//先完成显示菜单,并可以选择
public void mainMenu() {
//1. 先定义显示菜单,并可以选择菜单,给出相应信息
do{
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 = myscanner.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("\n--------退出了零钱通项目--------");
}
//完成零钱通明细
public void detail() {
System.out.println(details);
}
//完成收益入账
public void income() {
System.out.println("收益入账金额:");
money = myscanner.nextDouble();
//money的值范围应该校验
//爱摸鱼的TT~思路
//编程思想:就是在一系列判断时,找不符合条件即可【过关斩将 校验方式】
//找出不正确的金额条件,然后给出提示,就直接break
if(money <= 0){
System.out.println("收益入账金额 需要 大于 0");
return;//
}
date = new Date();
balance += money;
//拼接收益入账信息到“details”
details += "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
}
//消费
public void pay() {
System.out.println("消费金额:");
money = myscanner.nextDouble();
//money的值范围应该校验
//找出金额不正确的情况
//过关斩将 校验方式
if(money <= 0 || money > balance){
System.out.println("你的消费金额 应该在 0-" + balance);
return;
}
System.out.println("消费说明:");
note = myscanner.next();
balance -= money;
//拼接消费信息到“details”
details += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t余额:" + balance;
}
//退出
public void exit() {
//用户输入4退出时,给出提示“你确定要退出吗?y/n”,必须输入正确的y/n否则循环输入指令,知道输入y或者n。
//爱摸鱼的TT~思路分析:
//(1)定义一个变量 choice,接收用户的收入
//(2)使用while循环 + break,来处理接收到的输入时 y 或者 n
//(3)退出while后,在判断choice是y还是n,就可以决定是否退出
//(4)建议一段代码,完成一个小功能,尽量不要混在一起
String choice = "";
while(true){//要求用户输入y/n,否则就继续循环
System.out.println("你确定要退出吗?y/n");
choice = myscanner.next();
if("y".equals(choice) || "n".equals(choice)){
break;
}
//第二种方案:一个while循环含有两个功能
// if("y".equals(choice)){
// loop = false;
// break;
// }else if("n".equals(choice)){
// break;
// }
}
//当用户退出while,进行判断是y 还是n
if(choice.equals("y")){
loop = false;
}
}
}
- SmallChangeSysApp类
package smallchange.oop;
/**
* 这里我们直接调用SmallChangeSysOOP 对象,显示主菜单即可
*/
public class SmallChangeSysApp {
public static void main(String[] args) {
new SmallChangeSysOOP().mainMenu();
}
}
到此…
8.11 本章巩固练习
- 定义一个Person类(name,age,job),初始化Person对象数组,有3个Person对象,并按照age从大到小进行排序,提示:使用冒泡排序。
package com.homework;
/**
* @Author: 爱摸鱼的TT~
* @Description: 1. 定义一个Person类(name,age,job),初始化Person对象数组,有3个Person对象,并按照age从大到小进行排序,
* 提示:使用冒泡排序。
* @Date Created in 2021-12-13 19:57
* @Modified By:
*/
public class Homework01 {
public static void main(String[] args) {
//初始化Person 对象数组,有3个person对象
Person[] persons = new Person[3];
persons[0] = new Person("爱摸鱼的TT", 18, "在校生");
persons[1] = new Person("芳芳", 21, "实习生");
persons[2] = new Person("小明", 16, "电子厂");
//输出当前对象数组
for(int i = 0; i < persons.length; i++){
System.out.println(persons[i]);
}
//使用冒泡排序
Person temp = null;//临时变量,用于交换
for(int i = 0; i < persons.length - 1; i++){//外层循环
for(int j = 0; j < persons.length - 1 -i; j++){//内层循环
//并按照 age 从 大到 小进行排序, 如果前面的人的age < 后面人的年龄,就交换
if(persons[j].getAge() < persons[j + 1].getAge()){
temp = persons[j];
persons[j] = persons[j + 1];
persons[j + 1] = temp;
}
//要求按照名字的长度从小到大
//if(persons[j].getName().length() > persons[j+1].getName().length()){}
}
}
System.out.println("排序后的效果");
for(int i = 0; i < persons.length; i++){
System.out.println(persons[i]);
}
}
}
class Person{
private String name;
private int age;
private String job;
public Person(String name, int age, String job){
this.name = name;
this.age = age;
this.job = job;
}
public Person(){
}
//setter 和 getter
public void setName(String name){
this.name = name;
}
public String getName(){
return 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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", job='" + job + '\'' +
'}';
}
}
- 编写老师类
- 要求有属性“姓名name”,“年龄age”,“职称post”,“基本工资salary”
- 编写业务方法,introduce( ),实现输出一个教师信息。
- 编写教师类的三个子类:教授类(Professor)、副教授类、讲师类。工资级别分别为教授1.3、副教授为1.2、讲师为1.1,在三个子类里面都重写父类的introduce()方法。
- 定义并初始化一个老师对象,调用业务方法,实现对象基本信息的后台打印。
package com.homework;
/**
* @Author: 爱摸鱼的TT~
* @Description: a. 要求有属性“姓名name”,“年龄age”,“职称post”,“基本工资salary”
* b. 编写业务方法,introduce( ),实现输出一个教师信息。
* @Date Created in 2021-12-13 20:26
* @Modified By:
*/
public class Teacher {
private String name;
private int age;
private String post;
private double salary;
//这里我们在增加一个工资级别
private double 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 Teacher(){}
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;
}
public double getGrade() {
return grade;
}
public void setGrade(double grade) {
this.grade = grade;
}
public void introduce(){
System.out.println("name: " + name + ", age: " + age + ", post: " + post + ", salary: " + salary + ", grade: " + grade);
}
}
class Professor extends Teacher{
//特有属性...
public Professor(String name, int age, String post, double salary, double grade) {
super(name, age, post, salary, grade);
}
@Override
public void introduce() {
System.out.println(" 这是教授的信息 ");
super.introduce();
}
}
测试类:
package com.homework;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-13 20:25
* @Modified By:
*/
public class Homework02 {
public static void main(String[] args) {
Professor professor = new Professor("爱摸鱼的TT~", 30, "高级职称", 30000, 1.3);
professor.introduce();
}
}
- 通过继承实现员工工资核算打印功能
- 父类:员工类(Employee)
- 子类:部门经理类(Manager)、普通员工(Worker)
- 部门经理工资 = 1000 + 单日工资天数等级(1.2) => 奖金 + 基本工资
- 普通员工工资 = 单日工资天数等级(1.0) => 基本工资
- 员工属性:姓名,单日工资,工作天数
- 员工方法(打印工资)
- 普通员工及部门经理都是员工子类,需要重写打印工资方法
- 定义并初始化普通员工对象,调用打印工资方法输出工资,定义并初始化部门经理对象,调用工资方法输出工资
package com.homework;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-13 20:40
* @Modified By:
*/
public class Employee {
//属性
//员工属性:姓名,单日工资,工作天数
private String name;
private double daySal;
private int dayWork;
//分析出还有一个属性等级
private double grade;
public Employee(String name, double daySal, int dayWork, double grade) {
this.name = name;
this.daySal = daySal;
this.dayWork = dayWork;
this.grade = grade;
}
public Employee() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getDaySal() {
return daySal;
}
public void setDaySal(double daySal) {
this.daySal = daySal;
}
public int getDayWork() {
return dayWork;
}
public void setDayWork(int dayWork) {
this.dayWork = dayWork;
}
public double getGrade() {
return grade;
}
public void setGrade(double grade) {
this.grade = grade;
}
//打印工资方法
public void printSalary(){
System.out.println(name + " 工资是 " + daySal * dayWork * grade);
}
}
class Manager extends Employee{
//特有属性
private double bonus;
//创建Manager对象时,奖金是多少并不是确定的,因为老师在构造器中,不给bonus
//可以通过setBonus
public Manager(String name, double daySal, int dayWork, double grade) {
super(name, daySal, dayWork, grade);
}
//方法:重写父类的 printSal
@Override
public void printSalary() {
//因为经理的工资计算方式和Employee不一样,所以我们重写
System.out.println("经理 " + getName() + " 工资是 "
+ (bonus + getDaySal() * getDayWork() * getGrade()));
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
class Worker extends Employee{
//分析普通员工没有特有的属性
public Worker(String name, double daySal, int dayWork, double grade) {
super(name, daySal, dayWork, grade);
}
//重写printSal
//因为普通员工和Employee输出工资情况一样,所以直接调用父类的printSalary()
@Override
public void printSalary() {
System.out.print("普通员工 ");//自己的输出信息
super.printSalary();//调用父类的方法,复用代码
}
}
测试类:
package com.homework;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-13 20:36
* @Modified By:
*/
public class Homework03 {
public static void main(String[] args) {
Manager manage = new Manager("刘备", 100, 20, 1.2);
//设置奖金
manage.setBonus(3000);
//打印经理的工资情况
manage.printSalary();
Worker worker = new Worker("关羽",50, 10, 1.0);
worker.printSalary();
}
}
- 设计父类一员工类,子类:工人类(Worker)、农民类(Peasant)、教师类(Teacher)、科学家类(Scientist)、服务生类(Waiter)
- 其中工人、农民、服务生只有基本工资sal
- 教师除基工资外,还有课酬(元/天)classDay,classSal
- 科学家除基本工资外,还有年终奖bonus
- 编写一个测试类,将各种类型的员工的全年工资打印出来
//父类
public class Employee {
//属性
private String name;
private double sal;
//分析有一个带薪的月份,不固定
private int salMonth = 12;
public Employee(String name, double sal) {
this.name = name;
this.sal = sal;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public int getSalMonth() {
return salMonth;
}
public void setSalMonth(int salMonth) {
this.salMonth = salMonth;
}
//打印全年工资
public void printSal(){
System.out.println(name + "年工资是" + (sal * salMonth));
}
}
class Worker extends Employee{
public Worker(String name, double sal) {
super(name, sal);
}
@Override
public void printSal() {
System.out.print("工人 ");
super.printSal(); //使用父类的printSal()
}
}
class Peasant extends Employee{
public Peasant(String name, double sal) {
super(name, sal);
}
@Override
public void printSal() {
System.out.print("农民 ");
super.printSal();
}
}
...
class Teacher extends Employee{
//特有属性
private int classDays;//一年上课的次数
private double classSal;//课时费
public Teacher(String name, double sal) {
super(name, sal);
}
public int getClassDays() {
return classDays;
}
public void setClassDays(int classDays) {
this.classDays = classDays;
}
public double getClassSal() {
return classSal;
}
public void setClassSal(double classSal) {
this.classSal = classSal;
}
@Override
public void printSal() {
System.out.print("老师 ");
System.out.println(getName() + "年工资是 "
+ (getSal() * getSalMonth() + classDays * classSal));
}
}
class Scientist extends Employee{
//特有属性
private double bonus;
public Scientist(String name, double sal) {
super(name, sal);
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public void printSal() {
System.out.print("科学家 ");
System.out.println(getName() + "年工资是 "
+ (getSal() * getSalMonth() + bonus));
}
}
测试类:
package com.homework.homework04;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-13 22:13
* @Modified By:
*/
public class Homework04 {
public static void main(String[] args) {
Worker worker = new Worker("小明", 10000);
worker.setSalMonth(10);
worker.printSal();
Peasant peasant = new Peasant("smith", 20000);
peasant.setSalMonth(8);
peasant.printSal();
Waiter waiter = new Waiter("John", 8000);
waiter.setSalMonth(12);
waiter.printSal();
//老师测试
Teacher teacher = new Teacher("爱摸鱼的TT~", 2000);
//老师有课时费
teacher.setClassDays(360);
teacher.setClassSal(1000);
teacher.printSal();
//科学家
Scientist scientist = new Scientist("钟南山", 20000);
scientist.setBonus(2000000);
scientist.printSal();
}
}
- 假定Frand、Father和Son在同一个包,问:父类和子类中通过this和super都可以调用哪些属性和方法?
class Grand{//超类
String name = "AA";
private int age = 100;
public void g1(){}
}
class Father extends Grand{//父类
String id = "001";
private double score;
//super可以访问哪些成员(属性和方法)?
super.name;super.g1();
//this可以访问哪些成员?
this.id;this.score;this.f1();this.name;this.g1();
}
class Son extends Father{//子类
String name = "BB";
public void g1(){}
private void show(){
//super可以访问哪些成员?
super.id;super.f1();super.name;super.g1();
//this可以访问哪些成员?
this.name;this.g1();this.show();this.id;this.f1();
}
- 现有Person类,里面又方法run、eat,Student类继承了Person类,并重写了run方法,自定义了study方法,试写出对象向上转型和向下转型的代码,并写出各自都可以调用哪些方法,并写出方法输出什么?
class Person{//父类
public void run(){
System.out.println("person run");
}
public void eat(){
System.out.println("person eat");
}
}
calss Student extends Person{//子类
public void run(){
System,out.println("studnet run");
}
public void study(){
System.out.println("studnet study...");
}
}
答案:
//向上转型:父类的引用指向子类对象
Person p = new Studnet();
p.run();//student run
p.eat();//person eat
//向下转型:把指向子类对象的父类引用,转成指向子类对象的子类引用
Student s = (Student)p;
- 编写Doctor类(name、age、job、gender、sal),相应的getter和setter方法,5个参数的构造方法,重写父类(Object)的equals()方法,并判断测试类中的创建的两个对象相等。相等就是判断属性是否相同。
public class Doctor {
//属性
//{name, age, job, gender, sal}
private String name;
private int age;
private String job;
private char gender;
private double sal;
//5个参数的构造器
public Doctor(String name, int age, String job, char gender, double sal) {
this.name = name;
this.age = age;
this.job = job;
this.gender = gender;
this.sal = sal;
}
//方法
//相应的getter()和setter()方法
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 char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
//重写父类(Object)的equals()方法:public boolean equals(Object obj),并判断测试类中创建的两个对象是否相等。相等就是判断属性是否相同
public boolean equals(Object obj) {
//判断两个比较对象是否相同
if (this == obj) {
return true;
}
//判断obj 是否是 Doctor类型或其子类
//过关斩将 校验方式
if (!(obj instanceof Doctor)) { //不是的话
return false;
}
//向下转型, 因为obj的运行类型是Doctor或者其子类型
Doctor doctor = (Doctor)obj;
return this.name.equals(doctor.name) && this.age == doctor.age &&
this.gender == doctor.gender && this.job.equals(doctor.job) && this.sal == doctor.sal;
}
}
测试类:
public class Homework10 {
public static void main(String[] args) {
//测试
Doctor doctor1 = new Doctor("jack", 20, "牙科医生", '男', 20000);
Doctor doctor2 = new Doctor("jack", 20, "牙科医生", '男', 20000);
System.out.println(doctor1.equals(doctor2));//T
}
}
- (综合题)案例题目描述
(1)做一个Student类,Student类有名称(name),性别(sex),年龄(age)学号(stu_id),做合理封装,通过构造器在创建对象时将4个属性赋值。
(2)写一个Teacher类,Teacher类有名称(name),性别(sex),年龄(age),工龄(work_age),做合理的封装,通过构造器在创建时将4个属性赋值。
(3)抽取一个父类Person类,将共同和方法放到Person类。
(4)学生需要又学习的方法(study),在方法里写上“我承诺,我会好好学习。”
(5)教师需要有教学的方法(teach),在方法里写上“我承诺,我会认真教学。”
(6)学生和教师都有玩的方法(Play),学生玩的是足球,老师玩的是象棋,此方法是返回字符串的,分别返回“xx爱玩足球”和“xx爱玩象棋”(其中xx分别代表学生和老师的姓名)。因为玩的方法名称都一样,所以要求此方法定义在父类中,子类实现重写。
应当分析出,我们打印信息的方法,printlnfo()【先完成到第6题】
【Person类】
package com.homework.Homework08;
import javax.lang.model.element.NestingKind;
/**
* @Author: 爱摸鱼的TT~
* @Description: 抽取一个父类Person类,将共同属性和方法放到Person类
* @Date Created in 2021-12-14 8:26
* @Modified By:
*/
public class Person {
private String name;
private char sex;
private int age;
public Person(String name, int age, char sex) {
this.name = name;
this.sex = sex;
this.age = age;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//编写一个play 方法, 把共有的输出内容写到父类
public String play(){
return name + "爱玩";
}
public String printInfo(){
return "姓名:" + name + "\n" + "年龄:" + age + "\n" + "性别:" + sex + "\n";
}
@Override
public String toString() {
return printInfo();
}
}
【Student类】
package com.homework.Homework08;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-14 8:34
* @Modified By:
*/
public class Student extends Person {
private String stu_id;
public Student(String name, int age, char sex, String stu_id) {
super(name, age, sex);
this.stu_id = stu_id;
}
public String getStu_id() {
return stu_id;
}
public void setStu_id(String stu_id) {
this.stu_id = stu_id;
}
public String study(){
return "我承诺,我会好好学习。";
}
@Override
public String play() {
return super.play() + "足球。";
}
@Override
public String printInfo() {
System.out.println("学生的信息:");
return super.printInfo() + "学号:" + stu_id + "\n" + study() + "\n" + play();
}
@Override
public String toString() {
return printInfo();
}
}
【Teacher类】
package com.homework.Homework08;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-14 8:41
* @Modified By:
*/
public class Teacher extends Person{
private int work_age;
public Teacher(String name, int age, char sex, int work_age) {
super(name, age, sex);
this.work_age = work_age;
}
public int getWork_age() {
return work_age;
}
public void setWork_age(int work_age) {
this.work_age = work_age;
}
public String teach(){
return "我承诺,我会认真教学。";
}
@Override
public String play() {
return super.play() + "象棋。";
}
@Override
public String printInfo() {
System.out.println("老师的信息");
return super.printInfo() + "工龄:" + work_age + "\n" + teach() + "\n" + play();
}
@Override
public String toString() {
return printInfo();
}
}
【测试类】
package com.homework.Homework08;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-14 8:44
* @Modified By:
*/
public class Homework08 {
public static void main(String[] args) {
Student stu = new Student("小张",18,'男',"1912406030236");
System.out.println(stu.printInfo());
System.out.println("--------------------------------");
Teacher teacher = new Teacher("爱摸鱼的TT~", 24, '男', 1);
System.out.println(teacher.printInfo());
}
}
(7)定义多态数组,里面保存2个学生和2个教师,要求按照年龄从高到底排序。
(8)定义方法,形参为Person类型,功能:调用学生的study或教师的teach。
【最后两题好好分析题意,难度不大】
package com.homework.Homework08;
/**
* @Author: 爱摸鱼的TT~
* @Description:
* @Date Created in 2021-12-14 8:44
* @Modified By:
*/
public class Homework08 {
public static void main(String[] args) {
//定义多态数组,里面保存2个学生和2个教师,要求按年龄从高到低排序
Person[] persons = new Person[4];
persons[0] = new Student("jack", 10, '男', "0001");
persons[1] = new Student("mary", 20,'女', "0002");
persons[2] = new Teacher("smith", 36,'男', 5);
persons[3] = new Teacher("scott", 26,'男', 1);
Homework08 homework08 = new Homework08();
homework08.bubbleSort(persons);
//输出排序后的数组
System.out.println("---排序后的数组-----");
for(int i = 0; i < persons.length; i++) {
System.out.println(persons[i]);//persons[i].toString()
}
//遍历数组,调用test方法
System.out.println("=======================");
for (int i = 0; i < persons.length; i++) {//遍历多态数组
homework08.test(persons[i]);
}
}
//(7)方法,完成年龄从高到底排序
public void bubbleSort(Person[] persons){
Person temp = null;
//冒泡排序法
for(int i = 0; i < persons.length - 1; i++){
for(int j = 0; j < persons.length - 1 - i; j++){
//判断条件, 注意这里的条件可以根据需要,变化
if(persons[j].getAge() < persons[j+1].getAge()){
temp = persons[j];
persons[j] = persons[j+1];
persons[j+1] = temp;
}
}
}
}
//(8)定义方法,形参为Person类型,功能:调用学生的study或教师的teach方法
//分析这里会使用到向下转型和类型判断
public void test(Person p) {
if(p instanceof Student) {//p 的运行类型如果是Student
System.out.println(((Student) p).study());
} else if(p instanceof Teacher) {
System.out.println(((Teacher) p).teach());
} else {
System.out.println("do nothing...");
}
}
}