Java核心技术05:继承、接口和抽象类

1.继承

1.1 继承

  • 面向对象编程语言和过的最突出特点就是变量类型的继承
  • 更符合大自然规律:父亲有的,儿子就有
  • 首先看个例子
// Father
public class Father
{
	public void f1() 
	{
		System.out.println("hi");
	}
}

// Son 
public class Son extends Father
{
	public static void main(String[] a)
	{
	    Son s = new Son();
	    s.f1();  //Son没有定义f1,而是通过父类继承的
	}
}

1.2 继承

  • 面向过程编语言没有继承,导致出现很多类型重复定义
  • 物以类聚,世间万皆对象,对象也可分成若干类别。
  • 类别内的对象属性和方法都具有一定共同点。
  • 将共同点提取出来,即形成了 父类 /基类 /超类
    • Parent class / Base class / Super class
  • 而其他类 则自动成为 子类/派生类
    • Child class / Derived class

1.3 继承

// Man
public class Man {
	int height;    
    int weight;
	public void eat(){};
	public void plough(){}; //耕田
}
// Woman
public class Woman {
	int height;    
    int weight;
	public void eat(){};
	public void weave(){}; //织布
}

提取共同点:

// Human 
public class Human {
	int height;    
    int weight;
    public void eat(){};
}

继承:

public class Man extends Human{
	public void plough(){}; //耕田
}

public class Woman extends Human {
	public void weave(){}; //织布
}

从多个类别(对象)中提前共性,形成了符类。其他类继承符类,成为子类,也拥有这些共性

1.4 继承

  • Man extends Human表示Man继承于human
  • Human是父类,Man是子类
  • 子类继承父类所有的属性和方法(但不能直接访问private成员)
  • 根据信息隐藏原则:子类会继承符类所有的方法。可以直接使用
  • 子类也会继承父类的符类的所有的属性和方法(但不能直接访问private成员)

1.5 继承

Base :

public class Base {
	private int num = 10;
	
	public int getNum()
	{
		return this.num;
	}
}

Derived : 1

public class Derived extends Base{
	public static void main(String[] args) {
		Derived foo = new Derived();
		System.out.println(foo.getNum());
	}
}
// 输出结果:10

子类可以通过调用父类的方法来访问父类的私有的成员属性

Derived : 2

public class Derived extends Base{
    private int num = 20;	
	
	public int getNum()	{
		return this.num;
	}
	
	public static void main(String[] args) {
		Derived foo = new Derived();
		System.out.println(foo.getNum());
	}
}
// 输出结果:20

在同样方法名和参数情况下,本类的方法会比父类的方法优先级高

1.6 继承

  • 单根继承原则:每个类都只能继承一个类(Java继承和C++一个最大的区别)

    • Java:若一个方法在当前类中没有定义,那此方法就来自它的父类
    • C++支持一个类继承多个类(需要去寻找某方法对应的中间父类)
    • 在Java语言的设计中,针对C++中方法指代不清的问题进行改进,特意强调了单根继承的原则
  • 如果不写extends,Java类都默认继承java.lang.Object类

    • class Human{…}
    • 等价于 class Human extends Object{…}
    • 等价于 class Human extends java.lang.Object{…}
  • class Human extends java.lang.Object

  • Java所有类从java.lang.Object开始,构建出一个类型继承树

  • Object类里面默认就有clone、equals、finalize、getClass、hashCode、toString等方法

    • class A{}也是继承于Object类,所以类A也有clone(),equals()等方法

1.7 继承

  • 每个Java类都必须有构造函数
  • 如果没有显示定义构造函数,Java编译器自动为该类产生一个空的无线参构造函数。如果以及有了显示的有参构造函数,编译器就不会越俎代庖了
  • 每个子类的构造函数的第一句话,都默认调用父类的无参数构造函数super(),除非子类的构造函数第一句话是super,而且super语句必须放在第一条,不会出现连续两条super语句。
  • 查看A.java和B.java程序
// A 
public class A {
	public A(){
		System.out.println("111111");
	}
	public A(int a){
		System.out.println("222222");
	}
}

// B 
public class B extends A{
	public B(){
		//super();编译器自动增加super()
		System.out.println("333333");
	}
	public B(int a){
		super(a);  //编译器不会自动增加super();
		System.out.println("444444");
	}
	public static void main(String[] a){
		B obj1 = new B();
		System.out.println("==============");
		B obj2 = new B(10);		
	}
}

输出结果

111111
333333
==============
222222
444444

如果构造函数的第一句话不是super,编译器会自动增加一句super();如果构造函数第一句是程序员自己写的super语句,编译器就不会自动增加了

1.8 总结

  • 子类继承父们所有的东西 但不能直接访问 private 成员 )
  • Java所有类都继承自java.lang.Object类
  • JavaJavaJava 所有的 类都是单根继承
  • 子类构造函数 默认第一句话都会去调用父的子类构造函数

2.抽象类和接口

2.1 抽象类

  • 类:属性 (0 或多个)+ 方法 (0 或多个 )
    最简单的类:class A{}
  • 一个完整 (健康 )的类:所有方法都实现有实现 (方法体 )
    完整的方法:方法名(参数){ 方法体 }
  • 类可以没有方法,但是有方法就肯定要有实现,这才是一 个完整的类
  • 一个完整的类才可以被实例化,被new 出来
  • 如果一个类 暂时有方法未实现,需要被定义为抽象类
    若方法只有方法名字,形参列表,没有方法体,那所在的类就被定义为抽象类

2.2 抽象类

  • 抽象类关键字abstract声明
  • 抽象类的组成
    • (optional)成员变量,个数不限
    • (optional)具体方法,方法有实现,个数不限
    • (optional)抽象方法,加abstract关键字,个数不限
public abstract class Shape {
	//
	int area;
	// 抽象帆帆
    public abstract void calArea(); 
}

当图形未知时,无法给出calArea的具体实现,因此此方法被定义为abstract。
calArea()是一个不完整/不健康的方法,因为它没有方法体。
类A被定义为抽象的:abstract class A{};因此 A a=
new A();是不成立的。
如果有一个abstract的方法,那么类也必须是 abstract的

2.3 抽象类

  • 抽象类也是类。一个类继承于抽象类,就不能继承于其他的(抽象)类
  • 子类可以继承于抽象类,但是一定要实现父类们所有abstract的方法。如果不能完全实现,那么子类也必须被定义为抽象类
  • 只有实现父类(们)的所有抽象方法,才变成完整类

public abstract class Shape {
	//面积
	int area;
	//计算面积方法
    public abstract void calArea(); 
}


//继承自Shape抽象类
public class Rectangle extends Shape{
	int width;  //宽
	int length; //长
	
	public Rectangle(int length, int width) {
		this.length = length;
		this.width = width;
	}
	
	public void calArea() {
		System.out.println(this.length * this.width);
	}
	
	public static void main(String[] args) {
		Rectangle rect = new Rectangle(10,5);
		rect.calArea();	
	}
}

Rectangle继承于Shape,那么Rectangle就必须实现calArea方法

2.4 接口

  • 如果类的所有方法都没实现,那么这个就算是接口interface
public interface Animal {
	public void eat();
	public void move();
}

  • 接口不算类,或者说是“特殊”的类
    interface是一种特殊的类
  • 类只可以继承 (extends) 一个类,但是可以实现 (implements) 多 个接口,继承和实现可以同时。

1.被继承的类可以时抽象类,也可以是普通类。
2.继承抽象类,必须实现abstract的方法;实现多个接口,必须实现接口中所定义的所有方法
3.接口设计时为了弥补单根继承的不足:单根继承下,一个类只能遵循另一个类的标准,不是很灵活
4.接口里所有方法都是空的
5.一个类的方法,只会在当前类或者父类中定义,肯定不会再所实现的父类接口中定义
例如:类C继承于类A,同事也实现接口B。C中的f1()方法只会在A或者C中实现

2.5 接口

  • 接口可以继承 (多个 )接口,没有实现的方法将会叠加
  • 类实现接口,就必须实现所有未实现的方法。如果没有全部实现,那么只能成为一个抽象类。
    抽象类的方法可以为空(没有方法体),正常类的方法不能为空(必须有方法体)。
  • 接口里可以定义变量,但是一般是常量,详细乐意参考final节
  • 例子Animal/Cat

Animal 接口:

// 
public interface Animal {
	public void eat();
	public void move();
}

接口Animal存储上是.java文件,编译后也是.class文件。 、

Cat类:

// Cat 实现了 Animal接口
public class Cat implements Animal{
	public void eat() {
		System.out.println("Cat: I can eat");
	}
	
	public void move(){
		System.out.println("Cat: I can move");
	}
}
  • 例子Animal/ClimbTree/LandAnimal/Rabbit

ClimbTree 接口:

public interface ClimbTree {
	public void climb();
}

LandAnimal 抽象类:

// LandAnimal 实现了 Animal 的move方法
public abstract class LandAnimal implements Animal {

	public abstract void eat() ;

	public void move() {
		System.out.println("I can walk by feet");
	}
}

Rabbit 类:继承LandAnimal抽象类、实现ClimbTree接口
extends必须写在implements 前面

public class Rabbit extends LandAnimal implements ClimbTree {

	public void climb() {
		System.out.println("Rabbit: I can climb");		
	}

	public void eat() {
		System.out.println("Rabbit: I can eat");		
	}
}
  • 例子Animal/ClimbTree/CatFamily/Tiger

CatFamily 接口:继承Animal接口以及ClimbTree接口

public interface CatFamily extends Animal, ClimbTree{
	// CatFamily包含以下3个方法
	//eat()
	//move()
	//climb()
}

Tiger 类:实现CatFamily 接口
不全部实现所有方法,需改成抽象类

public class Tiger implements CatFamily {
	//必须实现CatFamily中的三个方法
	public void eat() {
		System.out.println("Tiger: I can eat");
	}

	public void move() {
		System.out.println("Tiger: I can move");
	}

	public void climb() {
		System.out.println("Tiger: I can climb");
	}
}

继承多个接口,相当于将多个接口为实现的方法都“承载”过来。

2.6 总结

  • 抽象类和接口相同点:两者都不能 被实例化,不能new操作
  • 抽象类和接口不同点:
    • 抽象类abstract,接口interface
    • 抽象类可以有部分方法实现,接口所有方法不能有实现
    • 一个类只能继承(extends)一个(抽象)类,实现(implements)多个接口
    • 接口可以继承(extends)多个接口
    • 抽象类有构造函数,接口没有构造函数
    • 抽象类可以有main,可以独立运行;接口没有main函数,不能被运行
    • 抽象类方法可以有private/protected,接口方法都是public

3.转型、多态和锲约设计

3.1 类转型

  • 变量支持相互转化,比如int a=(int)3.5;
  • 类型可以相互转型,但是只限制于有继承关系的类
    • 子类可以转换成父类,而父类不可以转为子类
    • 子类继承父类所有的财产,子类可以变成父类(从大变小,即向上转型);从父类直接变成子类(从小到大,即向下转型)则不允许
Human obj1 = new Man();//OK,Man extends Human
Man obj2 = new Human ();//illegal,Man is a derived class Human

3.2 类转型

  • 父类转为子类有一种情况例外:
    就是这个父类本身就是从子类转化过来的
Human obj1 = new Man();//OK,Man extends Human
Man obj2 =  (Man)obj1 ;//Ok,because obj1 is born form Man class

obj1本身就是Human类型。obj1本身起源就是来自Man。所以可以强制转换成Man类型

3.3 多态

  • 类型转换,带来的作用就是多态
  • 子类继承父类的所有方法,但子类可以重写定义一个名字、参数和父类一样的方法,这种行为就是重写(覆写、覆盖、overwrite/override、not overload(重载))
    • 重载:函数名一样,形参不一样
    • 重写:子类的方法,替换掉父类的方法(同名同参数)
  • 子类的方法的优先级高于父类的
    Man 类:重写了Human的eat和plough方法
public class Man extends Human {
	public void eat() {
		System.out.println("I can eat more");
	}
	
	public void plough() { }

	public static void main(String[] a)	{
		Man obj1 = new Man();
		obj1.eat();   // call Man.eat()
		Human obj2 =  (Human) obj1;
		obj2.eat();   // call Man.eat()
		Man obj3 = (Man) obj2;
		obj3.eat();	  // call Man.eat()
	}
}

输出结果:

I can eat more
I can eat more
I can eat more

obj2转型之前是obj1,而obj1是Man类型,所以obj2本质上是Man类型
obj2本身也是脱胎于obj1
5.3.3.1

3.4 多态

  • 多态的作用
    • 以统一的接口来操纵某一类中不同的对象的动态行为
    • 对象之间的解耦
  • 参看AnimalTest.java
// Animal 接口
public interface Animal {
   public void eat();
   public void move();
}

// Cat 
public class Cat implements Animal{
   public void eat() {
   	System.out.println("Cat: I can eat");
   }
   public void move(){
   	System.out.println("Cat: I can move");
   }
}
// Dog 
public class Dog implements Animal{
   public void eat() {
   	System.out.println("Dog: I can eat");
   }
   public void move() {
   	System.out.println("Dog: I can move");
   }
}

// AnimalTest 
public class AnimalTest {
   public static void haveLunch(Animal a)	{
   	a.eat();
   }
   
   public static void main(String[] args) {
   	Animal[] as = new Animal[4];
   	as[0] = new Cat();
   	as[1] = new Dog();
   	as[2] = new Cat();
   	as[3] = new Dog();
   	
   	for(int i=0;i<as.length;i++) {
   		as[i].move();  //调用每个元素的自身的move方法
   	}
   	for(int i=0;i<as.length;i++) {
   		haveLunch(as[i]);
   	}
   	
   	haveLunch(new Cat());  //Animal  a = new Cat();  haveLunch(a);
   	haveLunch(new Dog());
   	haveLunch(
   			new Animal()
   			{
   				public void eat() {
   					System.out.println("I can eat from an anonymous class");						
   				}
   				public void move() {
   					System.out.println("I can move from an anonymous class");
   				}
   				
   			});
   }
}

3.5 契约设计

  • Java编程设计遵循契约精神
  • 契约:规定规范了对象应该包含的行为方法
  • 接口定义了方法的名称、参数和返回值,规范了派生类的行为
  • 基于接口,利用转型和多态,不影响真正方法的调用,成功地将调用类和被调用类解耦(decoupling)

3.6 契约设计

  • 被调用类(haveLunch只和Animal有联系)
	public static void haveLunch(Animal a)	{
		a.eat();
	}

Animal是一个接口,里面所有的方法都是空的,没有方法体

  • 调用类
	haveLunch(new Cat());  //隐形转型:Animal  a = new Cat();  haveLunch(a);
	haveLunch(new Dog());
	haveLunch(
	// 接口是不能new的,实际上在这里重新new了下接口
	// new接口的同时,必须将接口中所有的方法实现补足补全
	// 这段话中间就隐藏地定义了一个匿名类
	// 匿名类只在这句话里面用一次就结束了
			new Animal(){
				public void eat() {
					System.out.println("I can eat from an anonymous class");						
				}
				public void move() {
					System.out.println("I can move from an anonymous class");
				}
					
			});

只需要传进来一个实现Animal接口地对象,就可以运行haveLunch方法。
这2个类就彻底地完成解耦

3.7总结

  • 类转型:子类可以转父类,父类不可以转子类(除非父类对象本身就是子类)
  • 多态:子类转型为父类后,调用普通方法,依旧是子类本身的方法
  • 契约设计:类不会直接使用另外一个类,而实采用接口形式,外部可以“空投”这个接口下地任意子类对象
    在类相互调用中,Java普遍采用接口地契约设计,使对象之间或者说,程序之间的耦合度减轻
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值