11【抽象类、多态、根父类】
今日内容
- 抽象类
- 多态
- 向上转型
- 向下转型
- native
- 根父类
学习目标
- 能够声明抽象类
- 能够说出抽象类的特点
- 能够继承抽象类
- 能够应用多态解决问题
- 理解向上转型与向下转型
- 能够使用instanceof关键字判断对象类型
- 了解native关键字
- 了解Object类的常用方法特点
- 会重写Object的常用方法
第七章 面向对象基础–下
7.1 抽象类
7.1.1 由来
抽象:即不具体、或无法具体
例如:当我们声明一个几个图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长、获取图形详细信息。那么这些共同特征应该抽取到一个公共父类中。但是这些方法在父类中又无法给出具体的实现,而是应该交给子类各自具体实现。那么父类在声明这些方法时,就只有方法签名,没有方法体,我们把没有方法体的方法称为抽象方法。Java语法规定,包含抽象方法的类必须是抽象类。
- 一般通过继承关系抽取的父类是多个子类共有的属性和行为,理论上不应该被实例化;(理想化:不能实例化)
- 一般通过继承关系抽取的父类的行为方法,需要被子类重写时,写完父类后很容易忘记;(理想化:子类没有重写父类的行为方法时,编译报错)
- 通过继承关系抽取的父类的行为方法,因为需要被子类重写时,所有方法实体比较多余;(理想化:不要写方法实体,交给子类完成,提高开发效率)
7.1.2 定义
- 抽象方法 : 没有方法体的方法。
- 抽象类:被abstract所修饰的抽的类。
抽象类的语法格式
【权限修饰符】 abstract class 类名{
}
【权限修饰符】 abstract class 类名 extends 父类{
}
抽象方法的语法格式
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
注意:抽象方法没有方法体
7.1.3 抽象的使用
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
代码举例:
public abstract class Animal {
public abstract void run();
}
public class Cat extends Animal {
public void run (){
System.out.println("小猫在墙头走~~~");
}
}
public class CatTest {
public static void main(String[] args) {
// 创建子类对象
Cat c = new Cat();
// 调用run方法
c.run();
}
}
输出结果:
小猫在墙头走~~~
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
7.1.4 注意事项
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
-
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
-
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
-
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
7.1.5 练习
1、练习1
定义一个几何图形父类Graphic。所有几何图形都应该具备一个计算面积的方法。但是不同的几何图形计算面积的方式完全不同。
abstract class Graphic{
public abstract double getArea();
}
class Circle extends Graphic{
private double radius;
public Circle(double radius) {
super();
this.radius = radius;
}
public Circle() {
super();
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Graphic{
private double length;
private double width;
public Rectangle(double length, double width) {
super();
this.length = length;
this.width = width;
}
public Rectangle() {
super();
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double getArea() {
return length * width;
}
}
2、练习2
1、声明抽象父类:Person,包含抽象方法:
public abstract void walk();
public abstract void eat();
2、声明子类Man,继承Person
重写walk():大步流星走路
重写eat():狼吞虎咽吃饭
新增方法:public void smoke()实现为吞云吐雾
3、声明子类Woman,继承Person
重写walk():婀娜多姿走路
重写eat():细嚼慢咽吃饭
新增方法:public void buy()实现为买买买…
4、在测试类中创建子类对象,调用方法测试
public abstract class Person {
public abstract void walk();
public abstract void eat();
}
public class Man extends Person {
@Override
public void walk() {
System.out.println("大步流星走路");
}
@Override
public void eat() {
System.out.println("狼吞虎咽吃饭");
}
public void smoke(){
System.out.println("吞云吐雾");
}
}
public class Woman extends Person {
@Override
public void walk() {
System.out.println("婀娜多姿走路");
}
@Override
public void eat() {
System.out.println("细嚼慢咽吃饭");
}
public void buy(){
System.out.println("买买买...");
}
}
public class TestExer1 {
public static void main(String[] args) {
Man m = new Man();
m.eat();
m.walk();
m.smoke();
System.out.println("-------------------------");
Woman w = new Woman();
w.eat();
w.walk();
w.buy();
}
}
7.1.6 模板设计模式
1、当解决某个问题,或者完成某个功能时,主体的算法结构(步骤)是确定的,只是其中的一个或者几个小的步骤不确定,要有使用者(子类)来确定时,就可以使用模板设计模式
2、示例代码:计算任意一段代码的运行时间
//模板类
public abstract class CalTime{
public long getTime(){
//1、获取开始时间
long start = System.currentTimeMills();
//2、运行xx代码:这个是不确定的
doWork();
//3、获取结束时间
long end = System.currentTimeMills();
//4、计算时间差
return end - start;
}
protected abstract void doWork();
}
使用模板类:
public class MyCalTime extends CalTime{
protected void doWork(){
//....需要计算运行时间的代码
}
}
测试类
public class Test{
public static void main(String[] args){
MyCalTime my = new MyCalTime();
System.out.println("运行时间:" + my.getTime());
}
}
7.2 多态
7.2.1 引入
多态是继封装、继承之后,面向对象的第三大特性。
生活中,比如求面积的功能,圆、矩形、三角形实现起来是不一样的。跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
7.2.2 定义
- 多态: 是指同一行为,具有多个不同表现形式。
7.2.3 前提【重点】
- 继承父类或者实现接口【二选一】
- 方法的重写【意义体现:不重写,无意义】
- 父类引用指向子类对象【格式体现】
7.2.4 多态的体现
多态体现的格式:
父类类型 变量名 = new 子类对象;
变量名.方法名();//这个方法是父类中声明,子类中重写的方法
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
多态体现出来的现象:
编译时,看“父类”,只能调用父类声明的方法,不能调用子类扩展的方法;
运行时,看“子类”,一定是执行子类重写的方法体;
代码如下:
定义父类:
public abstract class Animal {
public abstract void eat();
}
定义子类:
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Animal a1 = new Cat();
// 调用的是 Cat 的 eat
a1.eat();
//a1.catchMouse();//错误,catchMouse()是子类扩展的方法,父类中没有
/*
多态引用,编译时,看“父类”,只能调用父类声明的方法;
运行时,看“子类”,一定是执行子类重写的方法体;
*/
// 多态形式,创建对象
Animal a2 = new Dog();
// 调用的是 Dog 的 eat
a2.eat();
}
}
7.2.5 多态的好处
1、多态参数
实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。代码如下:
定义父类:
public abstract class Animal {
public abstract void eat();
}
定义子类:
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Cat c = new Cat();
Dog d = new Dog();
// 调用showCatEat
showCatEat(c);
// 调用showDogEat
showDogEat(d);
/*
以上两个方法, 均可以被showAnimalEat(Animal a)方法所替代
而执行效果一致
*/
showAnimalEat(c);
showAnimalEat(d);
}
public static void showCatEat (Cat c){
c.eat();
}
public static void showDogEat (Dog d){
d.eat();
}
public static void showAnimalEat (Animal a){
a.eat();
}
}
由于多态特性的支持,showAnimalEat方法的Animal类型,是Cat和Dog的父类类型,父类类型接收子类对象,当然可以把Cat对象和Dog对象,传递给方法。
当eat方法执行时,多态规定,执行的是子类重写的方法,那么效果自然与showCatEat、showDogEat方法一致,所以showAnimalEat完全可以替代以上两方法。
不仅仅是替代,在扩展性方面,无论之后再多的子类出现,我们都不需要编写showXxxEat方法了,直接使用showAnimalEat都可以完成。
所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展。
2、多态数组
例如:家里养了两只猫,两条狗,想要统一管理他们的对象,可以使用多态数组
public class TestAnimal {
public static void main(String[] args) {
Animal[] all = new Animal[4];//可以存储各种Animal子类的对象
all[0] = new Cat();
all[1] = new Cat();
all[2] = new Dog();
all[3] = new Dog();
for (int i = 0; i < all.length; i++) {
all[i].eat();//all[i]编译时是Animal类型,运行时看存储的是什么对象
}
}
}
7.2.6 多态练习
练习1:
(1)声明抽象父类Traffic,包含抽象方法public abstract void drive()
(2)声明子类Car,Bicycle等,并重写drive方法
(3)在测试类的main中创建一个数组,有各种交通工具,遍历调用drive()方法
模拟马路上跑的各种交通工具
public abstract class Traffic {
public abstract void drive();
}
public class Car extends Traffic {
@Override
public void drive() {
System.out.println("滴滴滴...");
}
}
public class Bicycle extends Traffic {
@Override
public void drive() {
System.out.println("蹬蹬蹬。。。");
}
}
public class TestExer1 {
public static void main(String[] args) {
//右边这些是用匿名对象,初始化数组
Traffic[] arr = {
new Car(),new Bicycle(),new Car(),new Bicycle()};
for (int i = 0; i < arr.length; i++) {
arr[i].drive();
}
}
}
练习2:
(1)声明一个抽象父类Person类,public abstract void toilet();
(2)声明一个子类Woman类,重写方法
(3)声明一个子类Man类,重写方法
(4)在测试类中声明一个方法,
public static void goToToilet(Person p){
p.toilet();
}
在main中,创建不同子类对象,调用goToToilet方法进行测试
public abstract class Person {
public abstract void toilet();
}
public class Man extends Person {
@Override
public void toilet() {
System.out.println("站着..");
}
}
public class Woman extends Person {
@Override
public void toilet() {
System.out.println("坐着..");
}
}
public class TestPerson {
public static void main(String[] args) {
goToToilet(new Woman());//隐含了Person p = new Woman();
goToToilet(new Man());//隐含了Person p = new Man();
}
public static void goToToilet(Person p){
p.toilet();
}
}
练习3:
1、声明一个父类Employee员工类型,有属性,姓名(String)
有方法,public abstract double earning() 用于返回实发工资
public String getInfo():显示姓名和实发工资
2、声明一个子类SalaryEmployee正式工,继承父类Employee,增加属性,薪资,工作日天数,请假天数
重写方法,public double earning()返回实发工资,实发工资 = 薪资 - 薪资/工作日天数 * 请假天数,
3、声明一个子类HourEmployee小时工,继承父类Employee
有属性,工作小时数,每小时多少钱
重写方法,public double earning()返回实发工资, 实发工资 = 每小时多少钱 * 小时数
4、声明一个子类Manager经理,继承SalaryEmployee,增加属性:奖金比例
重写方法,public double earning()返回实发工资,实发工资 = (薪资 - 薪资/工作日天数 * 请假天数)*(1+奖金比例)
5、你现在是财务,需要查看每个人的实发工资,并查看工资总额。
声明一个员工数组,存储各种员工,并遍历显示他们的姓名和实发工资,并计算工资总额
public abstract class Employee {
private String name;
public Employee(String name) {
super();
this.name = name;
}
public Employee() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract double earning();
public String getInfo() {
return "姓名:" + name + ",实发工资:" + earning();
}
}
public class SalaryEmployee extends Employee {
private double salary;
private int workingDays;//工作日天数,
private double offDays;//请假天数
public SalaryEmployee() {
super();
}
public SalaryEmployee(String name, double salary, int workingDays, double offDays) {
super(name);
this.salary = salary;
this.workingDays = workingDays;
this.offDays = offDays;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getWorkingDays() {
return workingDays;
}
public void setWorkingDays(int workingDays) {
this.workingDays = workingDays;
}
public double getOffDays() {
return offDays;
}
public void setOffDays(double offDays) {
this.offDays = offDays;
}
/*
* 重写方法,public double earning()返回实发工资,
实发工资 = 薪资 - 薪资/工作日天数 * 请假天数
*/
@Override
public double earning() {
return salary - salary/workingDays * offDays;
}
}
public class HourEmployee extends Employee {
private double moneyPerHour;
private double hours;
public HourEmployee() {
super();
}
public HourEmployee(String name, double moneyPerHour, double hours) {
super(name);
this.moneyPerHour = moneyPerHour;
this.hours = hours;
}
public double getMoneyPerHour() {
return moneyPerHour;
}
public void setMoneyPerHour(double moneyPerHour) {
this.moneyPerHour = moneyPerHour;
}
public double getHours() {
return hours;
}
public void setHours(double hours) {
this.hours = hours;
}
/*
* 重写方法,public double earning()返回实发工资,
实发工资 = 每小时多少钱 * 小时数
*/
@Override
public double earning() {
return moneyPerHour * hours;
}
}
public class Manager extends SalaryEmployee {
private double commisionPer;
public Manager() {
super();
}
public Manager(String name, double salary, int workingDays, double offDays, double commisionPer) {
super(name, salary, workingDays, offDays);
this.commisionPer = commisionPer;
}
public double getCommisionPer() {
return commisionPer;
}
public void setCommisionPer(double commisionPer) {
this.commisionPer = commisionPer;
}
@Override
public double earning() {
return super.earning() * (1+commisionPer);
}
}
public class TestEmployee {
public static void main(String[] args) {
Employee[] all = new Employee[3];
all[0] = new HourEmployee("张三", 50, 50);
all[1] = new SalaryEmployee("李四", 10000, 22, 1);
all[2] = new Manager("老王", 20000, 22, 0, 0.3);
double sum = 0;
for (int i = 0; i < all.length; i++) {
System.out.println(all[i].getInfo());
sum += all[i].earning();
}
System.out.println("总额:" + sum);
}
}
7.2.7 父子类之间的类型转换
多态的转型分为向上转型与向下转型两种:
向上转型
- 向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。
使用格式:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
向下转型
- 向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:Cat c =(Cat) a;
为什么要转型
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
转型演示,代码如下:
定义类:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
}
}
转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
这段代码可以通过编译,但是运行时,却报出了 ClassCastException
,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。
instanceof运算符
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
格式如下:
变量名/对象 instanceof 数据类型
如果变量/对象属于该数据类型,返回true。
如果变量/对象不属于该数据类型,返回false。
所以,转换前,我们最好先做一个判断,代码如下:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
哪些情况下instanceof判断返回true
示例代码:
class Person{
//方法代码省略...
}
class Woman extends Person{
//方法代码省略...
}
class ChineseWoman extends Woman{
//方法代码省略...
}
public class Test{
public static void main(String[] args){
Person p1 = new Person();
Person p2 = new Woman();
Person p3 = new ChineseWoman();
if(p1 instanceof Woman){
//false
}
if(p2 instanceof Woman){
//true
//p2转为Woman类型安全
}
if(p3 instanceof Woman){
//true
//p3转为Woman类型安全
}
}
}
练习
1、声明一个父类Employee员工类型,
有属性,姓名(String),出生日期(MyDate类型,也是自定义的含年,月,日属性日期类型)
有方法,public abstract double earning()
public String getInfo():显示姓名和实发工资
2、声明一个子类SalaryEmployee正式工,继承父类Employee
增加属性,薪资,工作日天数,请假天数
重写方法,public double earning()返回实发工资, 实发工资 = 薪资 - 薪资/工作日天数 * 请假天数,
重写方法,public String getInfo():显示姓名和实发工资,月薪,工作日天数,请假天数
3、声明一个子类HourEmployee小时工,继承父类Employee
有属性,工作小时数,每小时多少钱
重写方法,public double earning()返回实发工资, 实发工资 = 每小时多少钱 * 小时数
重写方法,public String getInfo():显示姓名和实发工资,时薪,工作小时数
增加方法,public void leave():打印查看使用工具是否损坏,需要赔偿
4、声明一个子类Manager经理,继承SalaryEmployee
增加属性:奖金,奖金比例
重写方法,public double earning()返回实发工资, 实发工资 = (薪资 - 薪资/工作日天数 * 请假天数)*(1+奖金比例)
重写方法,public String getInfo():显示姓名和实发工资,月薪,工作日天数,请假天数,奖金比例
5、声明一个员工数组,存储各种员工,
你现在是人事,从键盘输入当前的月份,需要查看每个人的详细信息。
如果他是正式工(包括SalaryEmployee和Manager),并且是本月生日的,祝福生日快乐,通知领取生日礼物。如果是HourEmployee显示小时工,就进行完工检查,即调用leave方法
public abstract class Employee {
private String name;
private MyDate birthday;
public Employee(String name, MyDate birthday) {
super();
this.name = name;
this.birthday = birthday;
}
public Employee(String name, int year, int month, int day) {
super();
this.name = name;
this.birthday = new MyDate(year, month, day);
}
public Employee() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
public abstract double earning();
public String getInfo(){
return "姓名:" + name + ",生日:" + birthday.getInfo() +",实发工资:" + earning();
}
}
public class SalaryEmployee extends Employee {
private double salary;
private int workingDays;//工作日天数,
private double offDays;//请假天数
public SalaryEmployee() {
super();
}
public SalaryEmployee(String name, int year, int month, int day, double salary, int workingDays, double offDays) {
super(name, year, month, day);
this.salary = salary;
this.workingDays = workingDays;
this.offDays = offDays;
}
public SalaryEmployee(String name, MyDate birthday, double salary, int workingDays, double offDays) {
super(name, birthday);
this.salary = salary;
this.workingDays = workingDays;
this.offDays = offDays;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getWorkingDays() {
return workingDays;
}
public void setWorkingDays(int workingDays) {
this.workingDays = workingDays;
}
public double getOffDays() {
return offDays;
}
public void setOffDays(double offDays) {
this.offDays = offDays;
}
/*
* 重写方法,public double earning()返回实发工资,
实发工资 = 薪资 - 薪资/工作日天数 * 请假天数
*/
@Override
public double earning() {
return salary - salary/workingDays * offDays;
}
@Override
public String getInfo() {
return super.getInfo() + ",月薪:" + salary + ",工作日:" + workingDays +",请假天数:" + offDays;
}
}
public class HourEmployee extends Employee {
private double moneyPerHour;
private double hours;
public HourEmployee() {
super();
}
public HourEmployee(String name, int year, int month, int day, double moneyPerHour, double hours) {
super(name, year, month, day);
this.moneyPerHour = moneyPerHour;
this.hours = hours;
}
public HourEmployee(String name, MyDate birthday, double moneyPerHour, double hours) {
super(name, birthday);
this.moneyPerHour = moneyPerHour;
this.hours = hours;
}
public double getMoneyPerHour() {
return moneyPerHour;
}
public void setMoneyPerHour(double moneyPerHour) {
this.moneyPerHour = moneyPerHour;
}
public double getHours() {
return hours;
}
public void setHours(double hours) {
this.hours = hours;
}
/*
* 重写方法,public double earning()返回实发工资,
实发工资 = 每小时多少钱 * 小时数
*/
@Override
public double earning() {
return moneyPerHour * hours;
}
@Override
public String getInfo() {
return super.getInfo() + ",时薪:" + moneyPerHour + ",小时数:" + hours;
}
public void leave(){
System.out.println("小时工,查看使用工具是否损坏,需要赔偿,然后拿钱走人");
}
}
public class Manager extends SalaryEmployee {
private double commisionPer;
public Manager() {
super();
}
public Manager(String name, int year, int month, int day, double salary, int workingDays, double offDays,
double commisionPer) {
super(name, year, month, day, salary, workingDays, offDays);
this.commisionPer = commisionPer;
}
public Manager(String name, MyDate birthday, double salary, int workingDays, double offDays, double commisionPer) {
super(name, birthday, salary, workingDays, offDays);
this.commisionPer = commisionPer;
}
public double getCommisionPer() {
return commisionPer;
}
public void setCommisionPer(double commisionPer) {
this.commisionPer = commisionPer;
}
@Override
public double earning() {
return super.earning() * (1+commisionPer);
}
@Override
public String getInfo() {
return super.getInfo() + ",奖金比例:" + commisionPer;
}
}
public class TestEmployee {
public static void main(String[] args) {
Employee[] all = new Employee[3];
/*all[0] = new HourEmployee("张三", new MyDate(1990, 5, 1), 50, 50);
all[1] = new SalaryEmployee("李四", new MyDate(1991, 1, 1), 10000, 22, 1);
all[2] = new Manager("老王", new MyDate(1987, 12, 8), 20000, 22, 0, 0.3);*/
all[0] = new HourEmployee("张三", 1990, 5, 1, 50, 50);
all[1] = new SalaryEmployee("李四", 1991, 1, 1, 10000, 22, 1);
all[2] = new Manager("老王", 1987, 12, 8, 20000, 22, 0, 0.3);
//从键盘输入当前的月份
Scanner input = new Scanner(System.in);
System.out.print("请输入当前月份:");
int month;
while(true){
month = input.nextInt();
if(month>=1 && month<=12){
break;
}
}
input.close();
for (int i = 0; i < all.length; i++) {
System.out.println(all[i].getInfo());
if(all[i] instanceof SalaryEmployee){
if(month == all[i].getBirthday().getMonth()){
System.out.println(all[i].getName() +"生日快乐,领取生日补助购物卡");
}
}else{
HourEmployee he = (HourEmployee) all[i];
he.leave();
}
}
}
}
7.2.8 属性、静态方法没有多态性
1、属性没有多态性
如果直接访问成员变量,那么只看编译时类型
public class TestField {
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.x);//只看编译时类型
}
}
class Father{
int x = 1;
}
class Son extends Father{
int x = 2;
}
2、静态方法没有多态性
public class TestField {
public static void main(String[] args) {
Father f = new Son();
f.test();//只看编译时类型
}
}
class Father{
public static void test(){
System.out.println("father");
}
}
class Son extends Father{
public static void test(){
System.out.println("son");
}
}
小贴士:
静态方法不能被重写
调用静态方法最好使用“类名.”
7.3 native关键字
native:本地的,原生的
用法:
只能修饰方法
表示这个方法的方法体代码不是用Java语言实现的。
但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它。
JVM内存的管理:
方法区:类的信息、常量、静态变量、动态编译生成的字节码信息
虚拟机栈:Java语言实现的方法的局部变量
本地方法栈:非Java语言实现的方法的局部变量,即native方法执行时的内存区域
堆:new出来的对象
程序计数器:记录每一个线程目前执行到哪一句指令
修饰符一起使用问题?
外部类 | 成员变量 | 代码块 | 构造器 | 方法 | 局部变量 | |
---|---|---|---|---|---|---|
public | √ | √ | × | √ | √ | × |
protected | × | √ | × | √ | √ | × |
private | × | √ | × | √ | √ | × |
static | × | √ | √ | × | √ | × |
final | √ | √ | × | × | √ | √ |
abstract | √ | × | × | × | √ | × |
native | × | × | × | × | √ | × |
不能和abstract一起使用的修饰符?
(1)final:和final不能一起修饰方法和类
(2)static:和static不能一起修饰方法
(3)native:和native不能一起修饰方法
(4)private:和private不能一起修饰方法
static和final一起使用:
(1)修饰方法:可以,因为都不能被重写
(2)修饰成员变量:可以,表示静态常量
(3)修饰局部变量:不可以,static不能修饰局部变量
(4)修饰代码块:不可以,final不能修改代码块
(5)修饰内部类:可以一起修饰成员内部类,不能一起修饰局部内部类(后面讲)
7.4 Object根父类
类 java.lang.Object
是类层次结构的根类,即所有类的父类。每个类都使用 Object
作为超类。所有对象(包括数组)都实现这个类的方法。
如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
public class MyClass /*extends Object*/ {
// ...
}
根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的5个:
(1)public String toString():
①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②通常是建议重写,如果在eclipse中,可以用Alt +Shift + S–>Generate toString()
③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
例如自定义的Person类:
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
// 省略构造器与Getter Setter
}
(2)public final Class<?> getClass():获取对象的运行时类型
public static void main(String[] args) {
Object obj = new String();
System.out.println(obj.getClass());//运行时类型
}
(3)protected void finalize():当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法。而且这个方法只会被调用一次。子类可以选择重写。
public class TestFinalize {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyData my = new MyData();
}
System.gc();//通知垃圾回收器来回收垃圾
try {
Thread.sleep(2000);//等待2秒再结束main,为了看效果
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyData{
@Override
protected void finalize() throws Throwable {
System.out.println("轻轻的我走了...");
}
}
(4)public int hashCode():返回每个对象的hash值。
规定:①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
②如果两个对象的hash值是相同的,那么这两个对象不一定相等。
主要用于后面当对象存储到哈希表等容中时,为了提高性能用的。
public static void main(String[] args) {
System.out.println("Aa".hashCode());//2112
System.out.println("BB".hashCode());//2112
}
(5)public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写,重写有些要求:
A:如果重写equals,那么一定要一起重写hashCode()方法,因为规定:
a:如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
b:如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
c:如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false
B:如果重写equals,那么一定要遵循如下几个原则:
a:自反性:x.equals(x)返回true
b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true
c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
d:对称性:x.equals(y)与y.equals(x)结果应该一样
e:非空对象与null的equals一定是false
class User{
private String host;
private String username;
private String password;
public User(String host, String username, String password) {
super();
this.host = host;
this.username = username;
this.password = password;
}
public User() {
super();
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [host=" + host + ", username=" + username + ", password=" + password + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + ((password == null) ? 0 : password.hashCode());
result = prime * result + ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
}