一、继承思想
1 引出继承关系
如下,Teacher、Student和Employee类中存在共同的代码;
class Teacher{
String name;
int age;
String level;
void sleep(){ }
}
class Student{
String name;
int age;
String number;
void sleep(){ }
}
class Employee{
String name;
int age;
String hireDate;
void sleep(){ }
}
class ExtendsDemo {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
解决代码重复的问题,就需要引入继承extends;
2 继承关系
(1)概念
基于某个父类对对象的定义加以拓展,从而产生新的子类的定义,子类可继承父类原来的某些定义,也可增加原来父类没有的定义,或覆盖父类中的某些特性;
从面向对象的角度,继承是一种从一般到特殊的关系,是一种“is a”的关系,即子类是对父类的拓展,是一种特殊的父类;
如dog是animal的一种特殊情况,dog属于animal;
(2)语法格式
在Java语言中,存在多个类时,使用extends关键字表示子类和父类之间的关系;
在定义子类时,需要表明自身拓展哪一个父类;
class <SubClassName> extends <SuperClassName>
{
// 编写自身特有的状态和行为(字段和方法)
}
注:
在Java中,类与类之间的继承关系只允许单继承,不允许多继承,即A类只能有一个直接的父类,不能出现类A同时继承于多个类;
但在Java中,允许多重继承;
在Java中,除Object类,每个类都有一个直接的父类;
Object类是根类,任何类都是Object类的子类;
Object类要么是一个类的直接父类,要么是一个类的间接父类;
子类父类编写的顺序,一般在开发过程中先编写多个自定义类,写完后发现多个类之间存在共同代码,此时可抽取一个父类;
做开发都是基于框架/组件,即在别人的基础上继续做开发,只需定义新的类,继承于框架/组件提供的父类;
(3)作用
a 解决代码重复问题;
b 表明一个体系;(继承关系的真正作用)
(4)子类继承父类的哪些成员
根据访问修饰符判断;
a 父类中成员使用public修饰,子类继承;
b 父类中成员使用protected修饰,子类继承,即使父类和子类不在同一包中;
c 父类和子类在同一包中,子类可继承父类中使用缺省修饰的成员;
d 父类中成员使用private修饰,子类不能继承,因为private只能在本类中访问;
e 父类中构造器,子类不能继承,因为构造器必须和当前类名相同;
3 方法覆盖
(1)原则 —— 一同两小一大
a 一同
实例方法签名必须相同(方法签名 = 方法名 + 方法参数列表);
建议直接拷贝父类方法的定义,再重写方法体;
b 两小
子类方法的返回值类型与父类方法的返回值类型相同或是其子类;
子类方法声明抛出的异常类型与父类方法抛出的异常类型相同或是其子类;
子类方法中声明抛出的异常小雨或等于父类方法声明抛出的异常;
子类方法可同时抛出多个属于父类方法声明抛出的异常的子类(除RuntimeException类型除外);
c 一大
子类方法的访问权限比父类方法的访问权限相同或更大;
(2)作用
当父类的方法不符合子类具体特征时,子类重新定义父类方法,并重写方法体;
注:
只有方法有覆盖的概念,字段没有覆盖的概念;
private修饰的方法不能被子类继承,也就不存在覆盖的概念;
覆盖方法必杀技 —— 在方法前加上@Override,用来判断子类方法是否覆盖父类方法,若覆盖则编译通过,否则编译报错;
class Bird{
public void fly(){
System.out.println("我在飞翔!");
}
}
class Penguin extends Bird{
@Override
public void fly(){
System.out.println("我不能飞!");
}
}
class OverrideDemo {
public static void main(String[] args){
Penguin p = new Penguin();
p.fly();
}
}
(3)方法重载和方法覆盖的区别
a 方法重载overload
作用是解决同一类中相同功能的方法名不同的问题;
规则是两同一不同(同类,方法名相同,参数列表不同);
b 方法覆盖override
作用是解决子类继承父类后,父类中某方法不满足子类的具体特性,需要在子类中定义该方法并重写方法体;
规则是一同两小一大(方法签名相同,子类方法返回类型小于等于父类方法返回类型,子类方法声明抛出的异常小于等于父类方法声明抛出的异常,子类方法访问修饰符大于等于父类方法访问修饰符);
4 super关键字
(1)概念
在子类方法中,调用父类被覆盖的方法,使用super关键字;
this关键字表示当前对象,即调用this所在的方法的对象;
super关键字表示当前对象的父类对象;
注:
同一类中方,实例方法之间的互调使用this关键字,也可省略;
如下,使用内存分析对super和this关键字进行讲解
(2)子类初始化过程(即创建子类对象过程)
在创建子类对象前,先创建父类对象(先有父类对象,再有子类对象);
在调用子类构造器前,在子类构造器中先调用父类构造器,默认调用父类无参数构造器;
注:
若父类不存在可被子类访问的构造器,则不能存在子类;
若父类没有提供无参数构造器,子类必须显式通过super语句调用父类带参数的构造器;
在子类构造器中,显式调用super语句时,必须将super语句位于子类构造器的第一条语句;
如下,注意父类字段使用private修饰符时,对于子类使用有参数构造器的代码
class Animal{
private String name;
private int age;
Animal(String name, int age){
this.name = name;
this.age = age;
System.out.println("Animal类构造器");
}
String getName(){
return this.name;
}
int getAge(){
return this.age;
}
}
class Fish extends Animal{
private String color;
Fish(String name, int age, String color){
super(name, age);
this.color = color;
System.out.println("Fish类构造器\t" + super.getName() + "\t" + super.getAge() + "\t" + color);
}
}
class SubclassInitDemo{
public static void main(String[] args){
// System.out.println("Hello World!");
Fish f = new Fish("Nimo", 5, "orange");
}
}
(3)super应用场景
a 子类隐藏父类的字段
一般应用开发中不允许,因为会破坏封装;
b 子类方法中调用父类被覆盖的方法
c 子类构造器中调用父类的构造器
5 “隐藏”问题
(1)概念
隐藏,即遮蔽,而非覆盖;
(2)出现隐藏的场景
a 满足继承的访问权限下,隐藏父类静态方法
若子类定义的静态方法的签名和父类中的静态方法签名相同,则隐藏父类方法;
注:仅仅静态方法,子类存在和父类一样的静态方法!
b 满足继承的访问权限下,隐藏父类字段
若子类中定义的字段和父类中的字段相同,则隐藏父类字段,只能通过super关键字访问隐藏的字段;
c 隐藏本类字段
若同类中某个局部变量名和字段名相同,则隐藏本类字段,只能通过this关键字访问隐藏的字段;
注:
static不能与super及this共存;
6 Object类
(1)概念
Object类是Java语言中的根类,要么是一个类的直接父类,要么是一个类的间接父类;
如,class ABC(){} 等价于 class ABC extends Object(){}
注:
Object本身指对象,所有的对象都具有某一些共同的行为,于是抽象出Object类,用以表示对象类,其他类都继承于Object类,也拥有Object类中的方法;
引用数据类型包括类、接口、数组,引用类型又称对象类,数组变量名指数组对象;
(2)方法
a protected void finalize()
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法;
垃圾回收器回收一个对象前,先调用该方法;
一般,不要调用;
b Class getClass()
返回当前对象的真实类型;
c int hashCode()
返回该对象的哈希码值;
hashCode决定对象在哈希表的存储位置(哈希表是一种数据结构),不同对象拥有不同的hashCode;
d boolean equals(Object obj)
当前对象与参数obj作比较;
Object类中的equals方法和==相同,比较对象的内存地址;
String类中的equals方法覆盖了Object类中的equals方法,比较字符串内容;
官方建议,每个类都应覆盖equals方法,不要比较内存地址,而比较关心的数据;
e String toString()
返回该对象的字符串表示,即把一个对象转换成字符串;
打印对象时,其实打印内容是对象的toString方法;
默认情况下打印对象,内容是对象的十六进制hashCode值,但有时更关心对象中存储的数据;
官方建议,每个都类应覆盖toString方法,返回关心的数据;
二、多态思想
1 理解多态
子类是一种特殊的父类,则子类对象也是父类的对象;
(1)对象具有两种类型
a 编译类型,指声明对象变量的类型,表示把对象看成什么类型
b 运行类型,指对象的真实类型
Animal a = new Dog();
注:
编译类型必须是运行类型的父类或相同;
当编译类型和运行类型不同,多态出现;
字段不存在多态特性,通过对象调用字段,在编译时期已决定调用哪一块内存空间的数据;
字段不存在覆盖的概念,只有方法存在覆盖的概念;
当子类和父类存在相同字段时,无论修饰符是什么,都会在各自的内存空间中存储数据;
class SuperClass{
public String name = "Super.name";
public void doWork(){
System.out.println("Super.doWork");
}
}
class SubClass extends SuperClass{
public String name = "Sub.name";
public void doWork(){
System.out.println("Sub.doWork");
}
}
class FieldDemo{
public static void main(String[] args){
SuperClass s = new SubClass();
s.doWork(); // Sub.doWork
System.out.println(s.name); // Super.name
}
}
(2)多态概念
多态,指对象具有多种形态,对象可以存在不同形式;
Animal a = new Dog(); // a表示Dog类型的形态
a = new Cat(); // a表示Cat类型的形态
多态前提 —— 可以是继承关系(类与类之间),也可以是实现关系(接口与实现的类之间);
在开发中,多态一般指实现关系;
(3)多态特点
把子类对象赋值给父类变量,在运行期间才表现出具体的子类特征(即调用子类方法);
如下,帮助理解多态
class Animal{
public void eat(){
System.out.println("吃一般食物");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("吃骨头");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
}
}
class AnimalDemo{
public static void main(String[] args){
Animal a = new Animal();
a.eat(); // 吃一般食物
a = new Dog();
a.eat(); // 吃骨头
a = new Cat();
a.eat(); // 吃鱼
}
}
2 多态的好处
当把不同的子类对象都当作父类类型看待,可屏蔽不同子类对象之间的实现差异,从而写出通用代码达到通用编程,以适应需求的不断变化;
class Animal{
public void eat(){
System.out.println("吃一般食物");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("吃骨头");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
}
}
class Person{
public void feed(Dog d){
System.out.println("feeding...");
d.eat();
}
public void feed(Cat c){
System.out.println("feeding...");
c.eat();
}
}
class AnimalDemo{
public static void main(String[] args){
Person p = new Person();
Dog d = new Dog();
p.feed(d);
Cat c = new Cat();
p.feed(c);
}
}
代码分析如下,Person对象调用feed方法时,传入的实参相当于Dog d = new Dog();;
针对不同类型的动物,需要提供不同feed方法;
使用多态,代码优化如下
class Animal{
public void eat(){
System.out.println("吃一般食物");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("吃骨头");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
}
}
class Person{
public void feed(Animal a){
System.out.println("feeding...");
a.eat();
}
}
class AnimalDemo{
public static void main(String[] args){
Person p = new Person();
Dog d = new Dog();
p.feed(d);
Cat c = new Cat();
p.feed(c);
}
}
代码分析如下,Person对象调用feed方法时,传入的实参相当于Animal a = new Dog();,因此在运行期间调用Dog对象的方法;
3 多态时方法调用问题
前提 —— 存在多态;
(1)情况1:doWork方法存在于SuperClass类中,不存在于SubClass类中
编译通过,执行SuperClass类中的doWork方法;
过程是先从SubClass类中寻找doWork方法,找不到再从SuperClass类中寻找doWork方法;
class SuperClass{
public void doWork(){
System.out.println("Super.doWork");
}
}
class SubClass extends SuperClass{
}
class MethodCallDemo{
public static void main(String[] args){
SuperClass s = new SubClass();
s.doWork(); // Super.doWork
}
}
(2)情况2:doWork方法存在于SubClass类中,不存在于SuperClass类中
编译错误;
编译时,从编译类型(SuperClass)中寻找doWork方法,若有则编译通过,否则编译报错;
class SuperClass{
}
class SubClass extends SuperClass{
public void doWork(){
System.out.println("Sub.doWork");
}
}
class MethodCallDemo{
public static void main(String[] args){
SuperClass s = new SubClass();
s.doWork();
}
}
(3)情况3:doWork方法存在于SuperClass类和SubClass类中
编译通过;
运行时期,调用运行类型(SubClass)中doWork方法;
class SuperClass{
public void doWork(){
System.out.println("Super.doWork");
}
}
class SubClass extends SuperClass{
public void doWork(){
System.out.println("Sub.doWork");
}
}
class MethodCallDemo{
public static void main(String[] args){
SuperClass s = new SubClass();
s.doWork(); // Sub.doWork
}
}
(4)情况4:doWork方法存在于SuperClass类和SubClass类中,但doWork是静态方法(属于隐藏,而非覆盖)
编译通过;
静态方法调用只需类即可,若使用对象进行调用,实际上,使用对象的编译类型调用静态方法,和对象没有关系;
class SuperClass{
public static void doWork(){
System.out.println("Super.doWork");
}
}
class SubClass extends SuperClass{
public static void doWork(){
System.out.println("Sub.doWork");
}
}
class MethodCallDemo{
public static void main(String[] args){
SuperClass s = new SubClass();
s.doWork(); // Super.doWork
}
}
注:
多态是对象级别的,静态是类级别的;
4 引用类型转换
(1)基本数据类型转换
a 自动类型转换:把小类型的数据赋值给大类型的变量
byte b =5;
int i = b;
b 强制类型转换:把大类型的数据赋值给小类型的变量
short s = i;
(2)引用数据类型转换
a 自动类型转换:把子类对象赋值给父类变量(多态)
Animal a = new Dog();
b 强制类型转换:把父类对象赋值给子类变量(父类类型变量真实类型应该是子类类型)
Animal a = new Dog();
Dog d = (Dog)a;
注:
instanceof运算符用来判断该对象是否是某一类的实例;
语法格式:
boolean b = <对象> instanceof <类>;
若一个对象是子类的实例,也一定是子类的父类的实例;
在开发中,若只想判断是否是真实类型的实例,而非编译类型的实例,则调用对象的getClass方法,并与预判断类的class字段比较是否相等;
boolean b = obj.getClass() == String.class;
如下,举例引用数据类型的强制类型转换和instanceof的使用
class Animal{
public void eat(){
System.out.println("吃一般食物");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("吃骨头");
}
public void watch(){
System.out.println("watching...");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
}
public void catchMouse(){
System.out.println("catching...");
}
}
class Person{
public void feed(Animal a){
System.out.println("feeding...");
a.eat();
if(a instanceof Dog){
Dog d = (Dog)a;
d.watch();
}else if(a instanceof Cat){
Cat d = (Cat)a;
d.catchMouse();
}
}
}
class AnimalDemo{
public static void main(String[] args){
Person p = new Person();
Dog d = new Dog();
p.feed(d);
Cat c = new Cat();
p.feed(c);
}
}
三、组合关系
继承关系:子类继承父类中部分成员,则子类可修改父类的信息,从而继承关系破环封装,使子类具有不该有的功能;
引入继承的目的是解决代码复用问题,另一种方法是使用包含关系;
若A类得到B类的功能行为,且A类是B类的特殊情况,则采用继承实现,否则使用组合实现;
如下,使用组合关系实现