面向对象(多态、抽象类、接口)

继承是希望得到父类的特性,所以子类可以访问父类的静态属性和方法,实现只是实现一种规则,不需访问特性,所以接口的静态成员不能被子类访问 https://github.com/niliv/practice/tree/master/java/bz/InterfaceProject/src

继承--->多态内存图解

多态

定义:事物在运行过程中存在不同的状态 猫既是猫也是动物 猫 cat = new 猫 动物 cat = new 猫 一个对象对象这不同类型

指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法.

Java实现多态有三个必要条件:继承、重写、向上转型

  • 要有继承关系
  • 子类要重写父类的方法
  • 父类引用指向子类对象
  • 多态是方法的多态,不是属性的多态(多态与属性无关)

在Java中有两种形式可以实现多态。继承和接口

基于继承实现的多态

public class Animal {

    int num = 10;

    public void eat(){
        System.out.println("动物吃饭");
    }
}
public class Cat extends Animal {
    int num = 80;
    int age = 10;
    public void eat(){
        System.out.println("猫吃饭");
    }
    public void catchMouse(){
        System.out.println("猫在抓老鼠");
    }
}
public static void main(String[] args) {

	Animal am = new Animal();
	System.out.println(am.num);
	am.eat();

	Animal amCat = new Cat();
	System.out.println(amCat.num);
	//System.out.println(amCat.age);  报错
	amCat.eat(); 
    //amCat.catchMouse();  报错
}

基于继承实现的多态可以总结如下:

  • new谁就调用谁的方法,引用是谁,就用谁的成员和静态
  • 不能使用子类有,但父类没有的方法
  • 对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。
  • 多态后不能使用子类特有的属性和方法,如果要使用,只有向下转型

自动类型提升:Animal a = new Cat() 向上转型,子类特有方法无法访问 向下转型:Cat c = (Cat)a 为了使用子类特有方法 转型都是子类在变化,一会猫一会动物,不能是一会猫一会狗,也不能是父类一会是动物一会是猫

a可以使用Animal的所有方法,也可以使用cat继承的所有方法,不能使用cat特有方法

基于接口实现的多态

在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。

继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

花木兰替父从军

抽象类

如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来支撑它,那么这样的类我们称它为抽象类。

我们都知道这个是产生一个动物Animal对象,但是这个Animal具体长成什么样子我们并不知道,它没有一个具体动物的概念,所以他就是一个抽象类,需要一个具体的动物,如狗、猫来对它进行特定的描述,我们才知道它长成啥样。

同时,抽象类体现了数据抽象的思想,是实现多态的一种机制。它定义了一组抽象的方法,至于这组抽象方法的具体表现形式有派生类来实现。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。所以说定义的抽象类一定是用来继承的。
在使用抽象类时需要注意几点:

  • 抽象类不能被实例化,实例化的工作应该交由它的子类来完成

  • 抽象类有构造函数,用于给子类对象初始化

  • 抽象方法必须由子类来进行重写。

  • 只要包含一个抽象方法的抽象类,该方法必须要定义成抽象类,不管是否还包含有其他方法。

  • 抽象类中可以包含具体的方法,当然也可以不包含抽象方法。

  • 子类中的抽象方法不能与父类的抽象方法同名。

  • abstract 不能与final并列修饰同一个类。

  • abstract 不能与private、static、final或native并列修饰同一个方法。

public abstract class Animal {

    int num = 10;

    public void eat(){
        System.out.println("动物吃饭");
    }

    public abstract void cry();
}
public class Cat extends Animal {
    int num = 80;
    int age = 10;
    public void eat(){
        System.out.println("猫吃饭");
    }
    public void catchMouse(){
        System.out.println("猫在抓老鼠");
    }
    public void cry(){
        System.out.println("喵喵喵!");
    }
}
public static void main(String[] args) {
	Animal amCat = new Cat();
	System.out.println(amCat.num);
	//System.out.println(amCat.age);  报错
	amCat.eat();
	//amCat.catchMouse();  报错
	amCat.cry();
}

接口

接口是抽象类的延伸,java了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,但是推荐继承和接口共同使用,因为这样既可以保证数据安全性又可以实现多重继承。

在使用接口过程中需要注意如下几个问题:

  • Interface的所有方法的访问权限,自动被声明为public。确切的说只能为public,当然你可以显示的声明为protected、private,但是编译会出错!

  • 接口中可以定义“成员变量”,或者说是不可变的常量,因为接口中的“成员变量”会自动变为public static final。可以通过类命名直接访问:ImplementClass.name

  • 在jdk1.8中可以用default来修饰实现的非抽象方法,接口对象可以直接调用,也可以声明静态方法,但要注意静态方法只能用类名调用,默认方法只能用实例调用

  • 实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不用实现。

  • 不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用一个实现该接口的类的对象。可以使用 instanceof 检查一个对象是否实现了某个特定的接口。例如:if(anObject instanceof Comparable){}。

  • 在实现多接口的时候一定要避免方法名的重复。

  • 接口与接口之间可以继承 extends,而且可以多继承

https://blog.csdn.net/aitangyong/article/details/54134385 https://blog.csdn.net/sun_promise/article/details/51220518

public interface Play {

	String name="Play"; //默认被public static final修饰 必须初始化
	
	public abstract void show();
	public void cry(String name); //可以省略public abstract
	void eat();
	
	//1.8新特性  定义非抽象方法  默认方法可以被子类覆盖
	public default void method() {
		System.out.println("Play-method");
	}
	
	//默认方法
	public default void defaultMethod() {
		System.out.println("Play-default");
	}
	//静态方法
	public static void staticMehtod() {
		System.out.println("Play-static");
	}
}
public abstract class Person {
	
	public static String name="Person";
	
	public static void study() {
		System.out.println("abstract-static");
	}
	
	public abstract void show();
	public abstract void cry();
}
public class Student extends Person implements Play {

	@Override
	public void eat() {
		// TODO Auto-generated method stub
		
	}

	//实现的是Person的show方法  血缘关系
	@Override
	public void show() {
		// TODO Auto-generated method stub
		
	}
	
	//接口中的cry
	@Override
	public void cry(String name) {
		// TODO Auto-generated method stub
		
	}
	//Person中的cry 必须构成重载,否则报错,无法同时实现
	@Override
	public void cry() {
		// TODO Auto-generated method stub
		
	}
	//覆盖接口默认方法
	@Override
	public void method() {
		// TODO Auto-generated method stub
		System.out.println("student-method");
	}
	
}
public class TestInterface {

	public static void main(String[] args) {
		
		//接口实例化
		Play play = new Student();
		play.method();  //student-method
		System.out.println(play.name); //静态变量可以用实例调用  Play
		//为什么默认方法只能通过对象调用?
		//为什么接口的静态方法只能用类名调用?
		play.defaultMethod(); //Play-default
		Play.staticMehtod(); //Play-static
		
		//抽象类实例化
		//为什么抽象类的静态方法和属性,用实例都可以调用?
		Person person = new Student();
		person.study(); //abstract-static
		Person.study();
		System.out.println(person.name); //Person
		
	}

}

接口的传递性

public interface InterfaceA {
	public void show();
}
interface InterfaceB extends InterfaceA {

}
interface InterfaceC extends InterfaceB {

}
class MyClass implements InterfaceC{

	//实现的是A的show
	@Override
	public void show() {
		// TODO Auto-generated method stub
		
	}
}

接口的多继承

public interface InterfaceA {
	public void show();
}
interface InterfaceB extends InterfaceA {
	
	//实现A的show
	public void show();
}
class MyClass implements InterfaceA,InterfaceB{
	//实现A的show
	@Override
	public void show() {
		// TODO Auto-generated method stub
		
	}
}

接口和抽象类的区别

  • 抽象层次不同 抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象
  • 跨域不同 抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系,共同之处。
  • 设计层次不同 我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。抽象类是自底向上抽象而来的,接口是自顶向下设计出来的
  • 抽象类只能单继承,接口可以多实现
  • 接口只能定义抽象方法,子类必须实现;子类可以继承抽象类的非抽象方法,也可以实现抽象方法
  • 抽象类定义基本共性,接口提供额外功能

为了更好的阐述他们之间的区别,下面将使用一个例子来说明。该例子引自: http://blog.csdn.net/ttgjz/article/details/2960451

我们有一个Door的抽象概念,它具备两个行为open()和close(),此时我们可以定义通过抽象类和接口来定义这个抽象概念:

//抽象类
abstract class Door{
    abstract void open();
    abstract void close();
}
//接口
interface Door{
    void open();
    void close();
}

现在如果我们需要门具有报警的功能,那么该如何实现呢?
解决方案一:给Door增加一个报警方法:clarm();

//抽象类
abstract class Door{
    abstract void open();
    abstract void close();
    abstract void alarm();
}
//接口
interface Door{
    void open();
    void close();
    void alarm();
}

这种方法违反了面向对象设计中的一个核心原则 ISP (Interface Segregation Principle)—见批注,在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方 法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变而改变,反之依然。

解决方案二

既然open()、close()和alarm()属于两个不同的概念,那么我们依据ISP原则将它们分开定义在两个代表两个不同概念的抽象类里面,定义的方式有三种:

1、两个都使用抽象类来定义。

2、两个都使用接口来定义。

3、一个使用抽象类定义,一个使用接口定义。

由于java不支持多继承所以第一种是不可行的。后面两种都是可行的,但是选择何种就反映了你对问题域本质的理解。

如果选择第二种都是接口来定义,那么就反映了两个问题:1、我们可能没有理解清楚问题域,AlarmDoor在概念本质上到底是门还报警器。2、如果我们对问题域的理解没有问题,比如我们在分析时确定了AlarmDoor在本质上概念是一致的,那么我们在设计时就没有正确的反映出我们的设计意图。因为你使用了两个接口来进行定义,他们概念的定义并不能够反映上述含义。

第三种,如果我们对问题域的理解是这样的:AlarmDoor本质上是Door,但同时它也拥有报警的行为功能,这个时候我们使用第三种方案恰好可以阐述我们的设计意图。AlarmDoor本质是们,所以对于这个概念我们使用抽象类来定义,同时AlarmDoor具备报警功能,说明它能够完成报警概念中定义的行为功能,所以alarm可以使用接口来进行定义。如下:

abstract class Door{
    abstract void open();
    abstract void close();
}

interface Alarm{
    void alarm();
}

class AlarmDoor extends Door implements Alarm{
    void open(){}
    void close(){}
    void alarm(){}
}

类型判断 instanceof

用于判断对象的具体类型,只能用于引用类型判断 a instanceof Cat 通常在向下转型前进行健壮性判断

多态成员的特点

成员变量 编译时:参照引用变量所属类,没有就报错 编译和运行都看左边 成员函数 编译时:参考引用类中是否有,没有就报错 编译看左边,运行看右边 静态函数 多态是对象的多态,静态函数不涉及对象,跟到类走 编译和运行都看左边


书籍推荐