Java继承与多态
子类与父类、子类的继承性
成员变量的隐藏与方法重写
super与final关键字
对象类型转换
继承与多态
抽象方法与抽象类
接口
枚举
1、类的继承
在面向对象程序设计中,可以从已有的类派生出新类,称为继承。
- Java程序中的每一个类均显式或隐式的派生自一个已存在的类。
- java.lang.Object是类层次结构的根类.。如果一个类定义时没有指定继承, 则其父类是Object。
- 子类(child class) 又称 次类(subclass)、扩展类(extend class)或派生类(derived class)。
- 父类(parent class) 又称 超类(super class)或基类(base class)。
1.1、子类的创建
// ava只支持单继承, 即一个子类只能继承一个父类
// 不写时, 默认父类是:java.lang.Object
修饰符 class 子类名 extends 父类名 {
……
}
如何理解继承
子类继承父类的成员变量和方法是指子类可以将它们作为自己定义的成员, 访问权限保持不变。
子类继承父类有2个方面的含义:
- 继承父类可访问的数据域和方法
子类与父类在同一包中, 继承:public, protected, default
子类与父类在不同包中, 继承:public, protected - 扩展添加新的数据域和新方法
说明: - 子类不继承父类的构造方法
- 子类不继承父类的不可访问成员
1.2、子类的构造方法
- 子类不继承父类的构造方法,但子类构造方法中必须调用父类的构造方法
super();
super(参数);
- 调用父类构造方法的语句必须在子类构造方法的第一句
public A(double d) {
super();
//语句
}
- 子类构造方法没有显式调用父类构造方法,则默认调用super()
public A(double d) {
// 默认调用了super();
//语句
}
- 子类构造方法也可以调用本类中重载的其它构造方法(this),此时构造方法间接调用super()或super(参数)
public A(){
this(100);
}
public A(int data) {
super(); // 间接被调用
this.data = data;
}
- 构造方法调用链
先调用父类方法,再调用子类方法 - 习惯上,父类的无参构造即使什么都不做也要写上,供给子类调用。
1.3、方法覆盖(overriding)
- 子类定义一个与继承实例方法同名的方法
- 子类方法的返回类型要求:
①基本类型或void:与父类方法返回类型相同。
②与父类方法返回类型相同或是返回该类型的子类。 - 子类方法的形参的个数、类型必须与父类方法完全相同。参数名称可以不同。
- 访问权限:子类方法的访问权限必须大于等于父类方法的访问权限。
1.4、父类静态方法的隐藏
Java中静态方法能否被重写
https://www.cnblogs.com/vcgo/p/10459352.html
如果子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是将父类中的该同名方法进行了隐藏,而非重写。
1.5、使用super访问父类的实例成员
关键字super在子类中使用,2种用途
- 调用父类的构造方法
- 访问已经继承的成员变量和实例方法
super.方法名(参数)
super.成员变量
- 父类和子类同名成员变量,首先找子类的,再找父类的
package test1;
public class ParentChildDemo {
public static void main(String[] args) {
Child child = new Child();
child.printData();
}
}
class Parent {
int data = 1;
public void printData() {
System.out.println(data);
}
}
class Child extends Parent {
//private int data = 2;
public void printData() {
System.out.println(data);
System.out.println(this.data);
System.out.println(super.data);
}
}
1.6、关键字总结
- final类不能被继承
- final方法不能再子类中被重写
- final变量的值不能改变,即常量
1.7、Object类
1.7.1、Object类的常见方法
public native int hashCode()
public boolean equals(Object obj)
public String toString()
1.7.2、toString方法的重写
System.out.println(类创建出的对象);
// 等价于
System.out.println(类创建出的对象.toString());
即默认调用toString方法。为了显示方便,我们通常在自己创建的类中重写toString方法
@Override
public String toString() {
return "Circle [x=" + x + ", y=" + y +
", radius=" + radius + "]";
}
1.7.3、equals方法重写
对象.equals(对象);
// 等价于
对象 == 对象;
即equals比较的是两者的引用(地址)是否相同。若要实现比较对象的默写值是否相同,我们通常在自己创建的类中重写equals方法
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Circle other = (Circle) obj; // 父类强转为子类
if (Double.doubleToLongBits(radius) !=
Double.doubleToLongBits(other.radius)) return false;
if (x != other.x) return false;
if (y != other.y) return false;
return true;
}
1.7.4、hashcode方法重写
子类重写equals方法时, 应尽量使用同样的参数重写hashCode。
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(radius);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + x;
result = prime * result + y;
return result;
}
1.8、对象类型转换和instanceof运算符
1.8.1、向上类型转换
- 父类类型的对象引用可以引用一个父类对象或者任何的子类对象. 称为隐式类型转换(向上类型转换)。
Object obj = new Student();
Student stud = new Student();
Object obj = stud;
- 这是实现多态的一种方式
// 同一类型引用指向不同类型的对象
Animal cat = new Cat();
Animal dog = new Dog();
Animal pig = new Pig();
1.8.2、向下强制类型转换
- 把父类类型强制转换为子类类型称为向下强制类型转换。
- 引用子类对象(new Cat)的父类类型引用(Animal),在创建之初既创建了父类的属性和方法,又创建了子类其它的属性和方法,故子类对象和父类对象可以相互转换,但是要满足is-a关系(即等号右边的对象的类型 is 左边对象的类型的子类或同类)。
Object obj = new Date(); // 正确。Date(右) is an Dbject(左),满足is-a关系
Date date = obj; // 编译出错。Object(右) is not a Date(左),不满足is-a关系
Date date = (Date)obj; // 正确。Date(右) is a Date(左),满足is-a关系。
- 引用父类对象(new Animal)的父类类型引用(Animal),在创建之初只创建了父类的属性和方法,而没有创建子类其它的属性和方法,故没有办法强转为子类对象。
// 下面语句无语法错误, 但是导致运行错误
Date d = (Date) new Object();
1.8.3、类型平等
- 如果两个类类型是平等的,即任何一个都不是另一个的祖先或后代,则这两个类类型不能进行类型转换。
Date d = new String(); // 语法错误
Date d = (Date) new String(); // 语法错误
String str = (String )new Date(); // 语法错误
1.8.4、instanceof运算符
对象 instanceof 类
// 如果对象的类是后面的类或其子类,返回true;否则返回false。
只要是子类就会被instanceof判断为true,如果要精确判断一个对象的类,可用下述方法
对象.getClass() == 类名.class
2、多态及应用
程序中如何实现多态:
- 多态在类的继承层次结构中实现
- 表现多态的形式是子类覆盖父类的实例方法
- 通过父类的引用去访问继承层次中不同类的对象。调用覆盖的方法时产生多态
注:如果子类重写父类的静态方法,子类对象的向上转型对象不能调用子类静态方法, 只能调用父类静态方法。即:多态性只针对实例方法
3、抽象类
- 抽象方法: 只有方法头没有方法体.用abstract修饰。
- 抽象类: 用abstract修饰的类, 不能用new创建对象。
public abstract class TheAbstarctClass{
……
public abstract void abstractMethod();
……
}
3.1、抽象类与抽象方法的相关说明:
- 包含抽象方法的类必须是抽象的。但抽象类可以没有抽象方法。
- 抽象类不能用new运算符实例化, 但包含构造方法。
- 具体的父类也可以派生抽象的子类。
- 子类可以将父类的具体方法覆盖为抽象方法。(少用)
- 抽象类不能创建对象, 但可以声明对象引用变量.GeometricObject geo;
3.2、抽象类使用场景
例如:各个员工都有“计算工资”的方法,但是不同种类的员工“计算工资”的方法是不一样的。
所以可以讲员工类中的“计算工资”的方法设置为抽象方法。
各个员工的类的父类是员工类,必须重写“计算工资”的抽象方法来计算不同工种的工资。
4、接口
接口(interface)是一种与类相似的结构, 其结构中包括:
- 静态常量
- 抽象方法
- 静态方法(Java 8 或更高版本)
- 默认方法(Java 8 或更高版本)
4.1、接口的定义
[public] interface 接口名称 [extends 父接口1, ..., 父接口n] {
[public] [static] [final] 数据类型 常量名 = 值;
[public] [abstract] 返回类型 方法名(形参表);
[public] static 返回类型 方法名(形参表) { ... }
[public] default 返回类型 方法名(形参表) { ... }
}
接口头部分:
- interface前的public省略时,接口的访问范围为package.
- interface的extends部分省略时,没有默认的父接口.
接口体部分:
- 接口成员前面的public无论是否省略,均为public.
- 接口中成员变量默认由public static final修饰
- 接口中成员方法默认由abstract修饰。
- 接口中成员方法由static或default修饰时
另外:
- 接口源程序文件命名规则与类相同, 接口名.java
- 接口在编译时生成一个.class, 接口不能用new创建实例
4.2、接口的实现与引用
4.2.1、接口的实现
一个类可以extends一个父类, 同时implements多个接口.
class 子类 extends 父类 implements 接口1, …, 接口N {
……
}
- 子类需要Override其实现接口的抽象方法,如果没有全部覆盖(实现),则子类需要使用abstract修饰。
- 接口中的抽象方法均为public,因此子类在覆盖接口的抽象方法时,形式上要求方法头完全相同。
4.2.1、接口的引用
- 接口可以声明变量,称为接口变量。
- 接口变量可以存放一个对象的引用(地址), 要求该对象的类实现对应接口。
- 接口回调:
- 实现了某个接口的类的对象的引用可以赋值给该接口声明的接口变量。
- 通过接口变量去调用被类实现的接口的方法。
- 通过接口变量无法调用类中非实现接口中声明的方法。
上面三句话非常绕,我们通过一个案例理解
Moveable.java
接口文件
package test1;
public interface Moveable {
int MAX_SPEED = 100;
void run();
void fly();
}
SuperMan.java
用于实现接口的文件
package test1;
public class SuperMan implements Moveable{
private String name; //私有成员变量
public SuperMan() {// 构造方法
super();
}
@Override
public void run() {// 接口方法1
System.out.println("running...");
}
@Override
public void fly() {// 接口方法2
System.out.println("flying...");
}
public String getName() {// 这个方法接口没有
return name;
}
public void setName(String name) {// 这个方法接口没有
this.name = name;
}
}
SuperManTest.java
主函数
package test1;
public class SuperManTest {
public static void main(String[] args) {
// 证明第一句话。实现了某个接口的类的对象的引用可以赋值给该接口声明的接口变量
Moveable man = new SuperMan();
man.run(); // 证明第二句话。通过接口变量去调用被类实现的接口的方法
man.fly(); // 证明第二句话
// 证明第三句话。通过接口变量无法调用类中非实现接口中声明的方法。
// 下面两个方法虽然在SuperMan里有,但接口没有
// 因为是使用了接口引用,接口中没有的方法无法被调用
// man.setName(); // 报错
// man.getName(); // 报错
SuperMan superman = (SuperMan)man; //若转换引用类型,则可以使用
superman.setName("David");
System.out.println("The superman's name is " + superman.getName());
}
}
可以通过“打印机模型”加深对接口的理解
https://www.jianshu.com/p/fde8ba4a8529
4.3、接口的继承
- 一个子接口可以extends一个或多个父接口
- 由于接口中的成员都是public的, 因此子接口可以继承父接口中的所有成员。
public interface 子接口 extends 父接口1, …, 父接口N {
……
}
4.4、使用接口实现对象的比较
- java.lang.Comparable<T> 为类实现默认比较
package java.lang;
public interface Comparable <T> {
public int compareTo(T o); //省略了abstract
}
通过继承接口,重写Comparable的方法实现个性化比较
public class Product implements Comparable<Product> {
@Override
public int compareTo(Product other) { ... }
}
- java.util.Comparator<T> 实现灵活比较
package java.util;
@FunctionalInterface // 声明函数式接口, 该接口中只能有一个抽象方法。
public interface Comparator<T> {
int compare(T o1, T o2); //需要override的方法
}
package demo;
import java.util.Comparator;
public class ProductPriceComparator implements Comparator<Product> {
@Override
public int compare(Product p1, Product p2) {
return Double.compare(p1.getPrice(), p2.getPrice());
}
}
4.5、使用java.lang.Cloneable实现对象复制
浅克隆:即两个对象共用同一个地址,两个对象的修改会互相关联
public class House implements Cloneable{
private int id;
private double area;
private Date whenBuilt;
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
House h1 = new House(1, 137.00);
House h2 = (House)h1.clone(); //注意强制类型转换