Java07------面向对象中的继承与多态、抽象类与接口

继承概述

  • 类中为什么需要继承?
    多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。子类就可以访问到父类的实例变量和实例方法。

  • 继承格式
    使用关键字extends描述子类与父类之间的关系

  • 继承的利弊
    优点
    继承提高了代码的复用性;
    提高了代码的维护性;如果想要更改整个动物的习性,只需要修改一个Animal类,如果没有继承,需要修改每一个动物类;
    让类与类之间产生了关系,是多态的前提
    缺点:
    软件开发的迪米特原则就是高内聚,低耦合,内聚指的是自己完成某件事的能力,耦合指的是类与类的关系,这样程序才能达到最大的复用。但是继承的出现使得类与类之间产生关系耦合性增强

  • Java中类的继承特点

    • Java只支持单继承,一个子类只能继承一个父类;
    • 支持多层继承(子继承父,父继承爷,子拥有父与爷的成员);
    • 子类只能继承父类所有非私有成员(成员变量和成员方法),子类无法访问被关键字private修饰的成员注意父类里面不能存在私有构造(因为子类的构造方法第一行会默认的调用父类的构造方法);
    • 子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法;
    • 不要为了部分功能去继承(继承的缺点导致)因为继承会增加耦合性;继承就是抽取多个子类的共性部分组成父类;
    • java继承体系中的顶层父类:object,所有类都会直接或间接继承它,不写继承的类默认继承object
public class MyTest {
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println(son.num);//200
        System.out.println(son.f);//20000

        //2.Java中子类只能继承父类非私有的成员,私有成员子类无法继承。
        //System.out.println(son.a);//报错
        
        //3.构造方法不参与继承。
        //son.Fu();//报错
    }
}

 //1.Java中只支持单继承,一个子类只能有一个父类,但是支持多层继承。
class Fu {
    public Fu() {
    }
    int num=200;
    private int a=500;
}

class Father extends Fu{
    int f=20000;
}

class Son extends Father{

}
  • 那么什么时候使用继承呢?
    如果有两个类A,B。只有他们符合A是B的一种,或者B是A的一种,就可以考虑使用继承。

this和super的区别和应用

为什么需要super?---------子类局部范围访问父类成员

this——代表的是本类对象的引用;
super——代表的是父类存储空间的标识(可以理解成父类的引用,可以操作父类的成员);

this和super的使用

a:调用成员变量
this.成员变量 调用本类的成员变量
super.成员变量 调用父类的成员变量
b:调用构造方法
this(…) 调用本类的构造方法
super(…) 调用父类的构造方法
c:调用成员方法
this.成员方法 调用本类的成员方法
super.成员方法 调用父类的成员方法

小练习1 理解this与super:
子类调用方法:
编译器会先去——>查找子类有没有这个方法,如果有就使用,如果没有就去——>父类里面查找,最终没有的话就会——>报错;

public class MyTest {
    public static void main(String[] args) {
        Son son = new Son();
        son.show();
        son.test();
    }
}

class Father{
    int num = 20;
   
    public void hehe(){
        System.out.println("父类的方法");
    }
}

class Son extends Father{
    int a=2000;
    int num=2;
    public void show(){
        System.out.println(a);//2000
        System.out.println(this.num); //2
        System.out.println(super.num); //20
        System.out.println(new Father().num);//20
    }

    public void test(){
    //子类调用方法: 编译器会先去——>查找子类有没有这个方法,如果有就使用,如果没有就去——>父类里面查找,最终没有的话就会——>报错;
    //用子类对象调用的子类继承的父类的方法
        this.hehe();
        //用的是父类空间标识,来调用的父类方法。
        super.hehe();
    }
   
    public void heihei(){
        this.show(); //调用本类的方法
    }
}

小练习2:求程序运行结果

class Fu { 
    static {
        System.out.println("静态代码块Fu"); //1
    }

    {
        System.out.println("构造代码块Fu"); //3
    }

    public Fu() {
        System.out.println("构造方法Fu"); //4
    }
}

class Zi extends Fu {
    static {
        System.out.println("静态代码块Zi");//2
    }

    {
        System.out.println("构造代码块Zi"); //5
    }

    public Zi() {
		//super(); 执行默认的父类空参构造方法
        System.out.println("构造方法Zi"); //6
    }
}

class Test {
    public static void main(String[] args) {
    Zi z = new Zi(); //请执行结果。
}
}

执行结果:
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi

上面程序分析:

1、类字节码文件进入方法区(没有初始化数据),先找到父类,加载父类字节码文件;
2、执行父类的时候会先执行父类的静态代码块,它随类一起进入方法区执行;
3、方法区内然后加载子类,这时子类的静态代码块也执行了;
4、接着方法区内找到main方法,进入栈区读取代码,因为要new对象,因此会执行构造方法在堆区分配内存空间;
5、但是在执行构造方法之前必须先要执行构造代码块;
6、子类的构造方法执行之前先执行父类的构造方法;
7、因此父类的构造代码块先执行,然后执行父类的构造方法;
8、再依次执行子类的构造代码块、构造方法。

继承中构造方法的关系

我们在创建子类对象时,为什么会先去调用父类的构造方法呢?
解释:

  1. 因为子类要继承父类的数据,甚至要使用父类的数据,所以,在初始化子类的时候,想要调用父类的构造方法,来完成父类数据的初始化,这样子类才能够继承父类的数据和使用父类的数据。
  2. 在每个类的构造方法中的第一行,有一行默认语句是super() 会去调用父类的空参构造,来完成父类数据的初始化,然后才执行子类构造方法的内容。

父类没有空参构造,怎么办?

方法一: 在父类中添加一个无参的构造方法

方法二: 子类通过super去显示调用父类有参的构造方法:
如果父类定义了有参构造方法,子类在完成父类数据初始化时要调用父类有参构造方法:子类构造方法中第一行写super(参数)

方法三: 子类通过this去调用本类的其他构造方法
本类其他构造方法也必须首先访问了父类构造,再执行当前构造方法的其他语句

注意事项:
super(…)用来调用父类构造方法、this()通过调用本类构造来间接调用父类构造方法,super(…) 或者 this(….)都必须出现在构造方法第一条语句上(为了保证父类的构造函数先被执行),因此this和super不能同时存在构造方法中

public class MyTest extends Object {
    public static void main(String[] args) {
      
        // 方法二: 子类通过super去显示调用父类有参的构造方法:
        //先找到子类对应的有参构造方法,先执行父类的构造方法不指定父类构造方法就默认执行父类空参构造,再执行子类构造方法的内容。
        Zi zi1 = new Zi("aaa", 100);

        //方法三: 子类通过this去调用本类的其他构造方法
        Zi zi = new Zi("aaa");
  }
}

//Java继承体系中的顶层父类是Object类,所有类都是直接或间接继承自他
class Fu extends Object {
    int num = 100;
    public Fu() {
        super();
        System.out.println("父类的空参构造调用了");
    }

    public Fu(String name) {
        super();
        System.out.println("父类的有参构造调用了");
    }

    public Fu(String name, int age) {
        super();
        System.out.println("父类的有参构造调用了" + name + "==" + age);
    }
}

class Zi extends Fu {
    public Zi() {
        super("abc"); //调用父类有参构造
        System.out.println("子类的空参构造调用了");

    }

    public Zi(String str) {
        //this();和super(); 不能同时存在在构造方法里面
        this();//调用本类空参构造
        //this("abc",23); //调用本类有参参构造
        System.out.println("子类的空参构造调用了");
    }

    public Zi(String str, int age) {
        // this();和super(); 不能同时存在在构造方法里面
       super(str, age);
        System.out.println("子类的空参构造调用了");
    }

}

执行结果:
父类的有参构造调用了aaa==100
子类的空参构造调用了
父类的有参构造调用了
子类的空参构造调用了
子类的空参构造调用了 
  • 继承中的成员变量及成员方法之间的关系

对于成员变量:

    • 子类中的成员变量和父类中的成员变量名称一样,在子类中访问一个变量的查找顺序(“就近原则”),首先在子类的方法的局部范围中查找,有就使用,没有的话会去——>子类的成员变量中找,如果还找不到,——>就去父类的成员范围内找,如果找不到就——>报错;
    • 子类没有父类中的成员变量名称时,子类继承父类的成员变量值
public class Person{
    public static void main(String[] args) {
        Teacher teacher1 = new Teacher();
        //子类teacher没有父类中的成员变量age时,调用父类的成员变量值
        System.out.println(teacher1.age);//200
       
        Student student1 = new Student();
        //自己调用自己的成员变量,与父类无关
        System.out.println(student1.age);//100
        
        //子类中的成员变量和父类中的成员变量名称一样时,
        //      a: 在子类的方法的局部范围找,有就使用
        //		b: 在子类的成员范围找,有就使用
        //		c: 在父类的成员范围找,有就使用
        //		d:如果还找不到,就报错
        student1.show(20);
        //子类继承父类的sleep方法
        teacher1.sleep();
    }
}


class Personn {
   int age=200;
   String sex;
   public static void sleep(){
       System.out.println("sleep");
   }
}

class Teacher extends Personn {
    int age=10;
    public static void job(){
        System.out.println("teaching");
    }
}
class Student extends Personn{
    int age=100;
    public static void task(){
        System.out.println("studying");
    }
    public void show(int age){
        //成员变量的就近原则:当成员变量和局部变量重名时,变量访问原则遵循就近原则,先在方法内和形参找,找不到就去该类的成员范围找,如果找不到就去父类的成员范围找
        System.out.println(age);//20
        System.out.println(this.age);//100
        System.out.println(super.age);//200
    }
}

对于成员方法:
通过子类调用方法:
编译器会先去——>查找子类有没有这个方法,如果有就使用,如果没有就去——>父类里面查找,最终没有的话就会——>报错;

方法重写概述及其应用

方法重写概念:
子类中出现了和父类中一模一样的方法声明(方法名,参数列表,返回值类型),也被称为方法覆盖,方法复写。
方法重写的应用:
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法。
这样,即沿袭了父类的功能,又定义了子类特有的内容。

方法重写的注意事项:

  • 只有父类实例方法参与重写

  • 父类中私有方法不能被重写;
    因为父类私有方法子类无法访问,就无法继承,更不能重写

  • 父类构造方法不参与重写;
    因为子类不继承父类构造方法,只是在创建子类的时候访问执行父类构造方法

  • 父类静态方法不参与重写;
    静态方法属于类,不参与重写,子类对于父类的静态方法重写就是自定义一个子类的静态方法;

  • 父类final修饰的方法子类不能重写;

  • 子类重写父类方法时,访问权限不能更低
    最好就一致

  • 子类重写父类方法的时候,最好声明一模一样。

小练习:定义手机的父类与子类

public class Phone {
    public void call(){
        System.out.println("打电话");
    }
    
    protected void sendMsg(){
        System.out.println("发短信");
    }

    public static void fuShow(){
        System.out.println("父类静态方法");
    }
}
public class IPhone extends Phone {
    @Override
    public void call() {
        //super沿袭父类的功能
        super.call();
        //扩展你自己的功能
        System.out.println("自己扩展了视频通话");
    }

    //public> protected >缺省>private
    @Override
    public void sendMsg() {
        System.out.println("发彩信");
    }

     // @Override可以检测该方法是不是重写父类,报错,说明此方法不是重写父类
    public static void fuShow() {
        System.out.println("子类自定义静态方法,不是对父类静态方法的重写");
    }
}

测试:

public class MyTest {
    public static void main(String[] args) {
        IPhone iPhone = new IPhone();
        iPhone.call();
        //重写时注意的事项。@Override 他可以检测该方法是不是重写父类 Ctrl+O 重写父类方法。
        iPhone.fuShow();//对象调用静态方法
        IPhone.fuShow();//子类调用自己的静态方法
        Phone.fuShow();//父类调用自己的静态方法

    }
}


执行结果:
打电话
自己扩展了视频通话
子类静态方法
子类静态方法
父类静态方法

final关键字

为什么会有final
由于继承中有一个方法重写的现象,而有时候我们不想让子类去重写父类的方法.这对这种情况java就给我们提供了一个关键字: final
final概述
final关键字是最终的意思,可以修饰类,变量,成员方法

final关键字修饰类,方法以及变量的特点
final修饰特点

  • 修饰类: 被修饰类不能被继承
  • 修饰方法: 被修饰的方法不能被重写,可以继承。因此final与public常搭配
  • 修饰变量: 被修饰的变量不能被重新赋值,因为这个量其实是一个常量
    对于基本类型,是值不能被改变
    对于引用类型(对象),是地址值不能被改变
public class MyTest {
    //公共的静态常量
    public static final int B = 2000;

    public static void main(String[] args) {
        
        //此静态常量不可以被重新赋值
        //报错: MyTest.B=100;
        
        System.out.println(MyTest.B);//2000
        
        //final修饰基本类型,指的是,这个值不能被再次改变。
        //final修饰引用类型,指的是,这个地址值不能再次被改变。
         final Fu fu = new Fu();
        System.out.println(fu);//org.westos.demo94.Fu@4554617c
    }
}

//定义一个final类,此类不能被继承
final class Fu{
    public final void show(){
        System.out.println("fu show");
    }
}

//Fu不能被继承,以下代码报错
/* class Zi extends Fu{

}*/

多态概述

  • 多态的概述
    指的是一种事物,在不同时刻,所表现出的不同状态。
    比如说,动物这个类可以表现为猫、狗、鸟。。等等

  • 多态的前提

1、要有继承关系;
2、要有方法重写,其实没有也是可以的,但是如果没有多态就没有意义;
3、要有父类引用接收各种子类对象:
Animal one =new Cat();Animal two =new Dog();
注意:多态one、two是父类的引用,它调用静态方法仍然是父类的静态方法。

多态的成员访问特点

1、成员变量
编译看左边(父类有此成员变量才不报错),运行看左边(多态形式访问成员变量访问的是父类的成员变量);
2、构造方法
创建子类对象的时候,会访问父类的构造方法,首先对父类的数据进行初始化;
3、成员方法
编译看左边(看父类是否有此方法(有,语法不报错)),运行看右边(以子类重写过后的方法为准,如果子类没有重写就运行父类的方法);
4、静态方法
编译看左边,运行看左边;(静态属于类,只能继承不参与重写,所以只看当前引用变量所属的类中的静态方法。尽管子类有和父类相同的静态方法,也只是子类自定义一个与父类相同的静态方法,与父类无关,所以多态中对于静态方法的访问,依旧是多态所属的父类的静态方法的调用)

public class MyTest {
    public static void main(String[] args) {
        //多态中,子类创建对象先执行父类构造方法,再执行子类构造方法
        Fu fu = new Zi();
       
        //多态的形式来访问成员变量,编译看左边,运行也看左边。多态的形式访问成员变量,访问的都是父类的变量。
        int n=fu.num;
        System.out.println(n); //100
       
        //编译看左边,运行看右边,编译期,看父类中有没有这个方法,如果有语法就报错,实际调用时,会以子类重写过后的为准。
        //当然,子类确实没有重写,那就以父类方法为准。
        fu.show();
       
        //多态访问静态方法,是对多态所属的父类的静态方法的调用。
        fu.jing();
        Fu.jing();//父类调用自己的静态方法
        Zi.jing();//子类继承父类的静态方法,对父类静态方法的调用
    }
}

class Fu{
    public Fu() {
        System.out.println("父类 构造调用了");
    }

    int num=100;
    public void show(){
        System.out.println("fu show");
    }
    public static void jing(){
        System.out.println("fu jing");
    }
}
class Zi extends Fu{
    int num=10;

    public Zi() {
        System.out.println("子类构造执行了");
    }

    @Override
    public void show() {
        System.out.println("zi show");
    }
}


执行结果:
父类 构造调用了
子类构造执行了
100
zi show
fu jing
fu jing
fu jing

成员方法重写的深度理解:

class A {
		public void show() {
			show2();
		}
		public void show2() {
			System.out.println("我");
		}
	}
	class B extends A {
		public void show2() {
			System.out.println("爱");
		}
	}
	class C extends B {
		public void show() {
			super.show();
		}
		public void show2() {
			System.out.println("你");
		}
	}
	public class DuoTaiTest4 {
		public static void main(String[] args) {
			A a = new B();
			a.show(); // 爱 
			
			B b = new C();
			b.show(); // 你 
		}
	}

【分析】:

1、上面的程序,发现首先创建了一个B类的对象,但是使用A类的引用来接收,父类的引用指向子类的对象,这就是多态的形式;
2、因为使用该引用去调用方法,因此这里应该遵循编译看左,执行看右;也就是编译器回首先查看A类里面有没有show(),然后再去执行B类里面的show();
3、显然,A里面存在show(),但是B类里面没有重写A里面的show(),因此a继承A的show()方法,由于A的show()方法调用了show2()方法,因此a.show()就是在运行a.show2(),这里依旧遵循编译看左,执行看右;也就是编译器首先查看A类里面有没有show2(),然后再去执行B类里面的show2();因此a.show()执行结果为“爱”;
4、b.show();中,这次是使得B类的引用指向C类的对象,因为B本身继承于A,虽然B里面没有定义方法show(),但是他继承了A里面的方法,因此编译看左不会报错;执行看右边,由3可知b.show();就是在执行b.show2(),这里依旧遵循编译看左,执行看右;也就是编译器首先查看B类里面有没有show2(),然后再去执行C类里面的show2();因此b.show()执行结果为“你”;

多态的好处和弊端

好处:
1、提高了代码的维护性(继承保证);维护只是维护父类即可。
2、提高了代码的扩展性(由多态保证) ;设计一个类,方法形参类型使用父类引用,传入时为各种子类对象,对重写的子类方法调用。这个方法可以接收它所有的子类。

对于多态好处的理解:首先定义多个子类继承父类

public class Animal {
    public void eat(){
        System.out.println("吃饭");
    }
}

class Dog extends Animal {
    public void eat(){
        System.out.println("狗吃骨头");
    }
    public void lockdoor(){
        System.out.println("狗看门");
    }
}

class Rabbit extends Animal{
    public void eat(){
        System.out.println("兔子吃草");
    }
    public void beauty(){
        System.out.println("兔子可爱");
    }
}

其次设计一个TestUtils类,它的方法形参类型使用父引用,指向各种子类对象,对重写的子类方法调用。这个方法就可以接收它各种各样的子类

public class TestUtils {
    //让此类构造方法私有化,不想让这个类被实例化
    private TestUtils(){}

    //设计一个类的方法形参类型使用父引用,指向各种子类对象,对重写的子类方法调用。这个方法就可以接收它各种各样的子类
    public static void testEat(Animal animal){
 //形参类型为父类,给形参传入子类,父类引用指向子类对象,eat()方法的调用是子类对父类重写的eat方法,使得多种子类实现各自的自定义方法
        //Rabbit rabbit=new Rabbit();
        // Animal animal=rabbit;
        animal.eat();
    }

}

测试:使得多种子类(狗、兔子)实现各自的自定义方法

public class Test {
    public static void main(String[] args) {
    Dog dog=new Dog();
    TestUtils.testEat(dog);
    Rabbit rabbit=new Rabbit();
    TestUtils.testEat(rabbit);
    }  


执行结果:
 狗吃骨头
 兔子吃草 

弊端:
不能直接调用子类自定义(非重写父类)的方法,也就是不能使用子类特有的功能。
解决方法:可以采用向下转型来调用子类特有方法,而多态就是向上转型

向下转型:把父类的引用强制转换为子类的引用。

小练习:理解向下转型使得多态可以调用子类自定义功能

public class Test {
    public static void main(String[] args) {

        Fu fu=new Zi();
        //可以采用向下转型来调用子类特有方法;
        Zi one= (Zi) fu;
        one.hehe();//调用子类特有方法
        System.out.println(one.num);//100

class Fu{
    int num=10;
    public void show(){
        System.out.println("父类show方法");
    }
        }

class Zi extends Fu{
    int num=100;

    @Override
    public void show() {
        System.out.println("子类重写父类show方法 ");
    }
    public void hehe(){
        System.out.println("子类自定义的非父类重写的方法");
    }    
}

向下转型的注意事项:父类引用指向第一种子类向(第一种子类)下转型,不能继续向下转型为第二种子类,要先将父类引用指向第二种子类,再向下转型。

小练习:理解向下转型的注意事项,首先定义父类与子类

public class Animal {
    public void eat(){
        System.out.println("吃饭");
    }
}


public class Dog extends Animal {
    public void eat(){
        System.out.println("狗吃骨头");
    }
   
    //子类特有功能,多态无法调用
    public void lockdoor(){
        System.out.println("狗看门");
    }
}


public class Rabbit extends Animal{
    public void eat(){
        System.out.println("兔子吃草");
    }
    
     //子类特有功能,多态无法调用
    public void beauty(){
        System.out.println("兔子可爱");
    }
}

测试:

public class Test {
    public static void main(String[] args) {

        //父类与一种子类转型可以互相转换
        Animal an=new Dog();//父类引用指向子类狗 狗这个子类向上转型为父类an
        an.eat();//使用子类狗的重写方法
        //父类an向下转型为狗
        Dog dog= (Dog) an;
        dog.lockdoor();//可以调用子类狗的自定义方法
        
        //父类引用想要向下转型为第二中子类兔子,需要先将引用指向第二种子类兔子,再向下转型
        an=new Rabbit();//将父类引用指向第二种子类兔子
        an.eat();//父类an执行子类的重写eat方法
        Rabbit rabbit= (Rabbit) an;//父类引用向下转型为第二中子类兔子
        rabbit.beauty();
    }

}

执行结果:
狗吃骨头
狗看门
兔子吃草
兔子可爱

多态的内存图解

子类会在创建出对象的空间为父类开辟一段内存空间用来存放父类的变量;

在这里插入图片描述

抽象类

抽象类:抽取所有子类的共性功能,但是不给出共性功能的具体实现,而是交由子类根据自身的特性做以具体的实现。

抽象类特点

a:抽象类和抽象方法必须用abstract关键字修饰
抽象类格式: abstract class 类名 {} 抽象方法格式: public abstract void eat();

b:抽象类不一定有抽象方法,有抽象方法的类一定是抽象类

c:抽象类中可以有构造方法,抽象类不能进行实例化(无法创建具体事物),那么要构造方法有什么作用呢?
用于子类访问父类数据时的初始化

d:抽象类不能直接实例化那么,抽象类如何实例化呢?
按照多态的方式,由具体的子类实例化。其实这也是多态的一种,抽象类多态。

举例如下:

public class MyTest2 {
    public static void main(String[] args) {
        //抽象类,不能直接实例化,我们可以通过多态的形式,间接实例化父类
        Fu fu= new Zi();

    }
}

abstract class Fu{
    int num=20;
    final int A=200;
    //抽象类有构造方法,作用:用于子类访问父类数据时的初始化
    public Fu(){
        System.out.println("父类的构造执行了");
    }
}

class Zi extends Fu{
    public Zi() {
        System.out.println("子类的构造执行了");
    }
}

e:抽象类的子类
要么是抽象类
要么重写抽象类中的所有抽象方法

抽象类的成员特点

成员变量:既可以是变量,也可以是常量。
构造方法:有,用于子类访问父类数据的初始化。
成员方法:既可以是抽象的,也可以是非抽象的。
抽象方法:强制要求子类做的事情。,抽象类中所有抽象方法强制子类必须重写;
非抽象方法:子类继承的事情,提高代码复用性。

小练习:理解抽象类
假如我们在开发一个系统时需要对员工(Employee) 类进行设计,员工包含3个属性:姓名、工号以及工资(salary)。 经理(Manager) 也是员工,除了含有员工的属性外,另为还有一个奖金(bonus) 属性。然后定义工作的方法. 请使用继承的思想设计出员工类和经理类
首先定义一个抽象员工类与经理类,经理类继承员工类

public abstract class Employee {
    String name;
    int number;
    double salary;

    public Employee(String name, int number, double salary) {
        this.name = name;
        this.number = number;
        this.salary = salary;
    }
    public Employee() {
    }
    public abstract void working();
}


class Manager extends Employee{
    double bonus;
    public Manager(String name, int number, double salary) {
        //创建对象首先执行父类的有参构造方法,把参数传递过去
        super(name, number, salary);
    }
    //写出子类空参构造方法,子类有参构造方法的存在会让字类空参构造方法不存在
    //如果想要使用空参构造实例化对象,父类也要有空参构造,因为子类空参构造默认第一行是调用父类的空参构造
    public Manager() {
    }
    @Override
    public void working() {
        System.out.println(
                "little work");
    }
    public void businesstrip(){
        System.out.println("经理出差");
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        //抽象类有构造方法可以采用多态的形式间接实例化
        //找到子类相对应的构造方法,并先执行父类构造方法
        Employee employee1=new Manager("scq",1,1000);
        Employee employee2=new Manager();//父类与字类定义了空参构造才可以创建对象时执行空参构造
        System.out.println(employee1.name);
        employee1.working();
        //如果想要执行经理特殊功能,选择向下转型
        Manager manager= (Manager) employee1;
        manager.businesstrip();
    }
}

思考:一个类如果没有抽象方法,可不可以定义为抽象类 ? 如果可以,有什么意义 ?

可以,这样定义后目的是这个类就不可以让外界创建对象,只能让别的类去继承。定义格式为abstract class A{}

abstract不能共存的关键字

private 矛盾 abstract 强制子类必须重写 private 私有的不能继承,重写不了
final 矛盾 final方法子类不能重写 abstract 又强制重写
static 方法不参与重写 abstract 他又要重写

接口

为了体现事物功能的扩展性,Java中就提供了接口来定义这些额外功能,并不给出具体实现。从设计理念来理解是为了定义额外功能,哪些类想要拥有这些额外功能就使用接口实现;
接口主要是用于定义一些规则和规范来实现扩展性,事物类自己实现接口定义的规范,接口只需要维护规范即可。

  • 定义接口格式: interface 接口名 {}

  • 类实现接口用implements表示,格式为:class 类名 implements 接口名 {}

  • 接口成员特点:

  1. 成员变量:只能是常量,并且是静态的。
    默认修饰符:public static final ,静态变量使用接口名.成员变量名来调用
    建议:自己手动给出。
public class MyTest {
    public static void main(String[] args) {
        System.out.println(A.NUM); //接口名直接调用成员变量
    }
}

interface A{
    //接口中的成员变量,全部是公共的静态常量 前面存在默认修饰符  public static final
    public static final int NUM=100;
    int N=20;//前面有默认的public static final

    //JDK1.7 之前接口中的方法,全部是抽象方法,不存在非抽象方法。方法的前面存在 默认修饰符 public abstract
    public abstract void a();
    public abstract void aa();
   // 定义一个方法名为sum,传入2个参数并返回int型的方法
    int sum(int a,int b);//方法的前面存在 默认修饰符 public abstract
}
class B implements A{
//接口的抽象方法,类实现时要全部重写
     @Override
     public void a() {
     }
     @Override
     public void aa() {
     }
     @Override
     public int sum(int a, int b) {
         return 0;
     }
 }
  1. 构造方法:接口没有构造方法。
  1. 成员方法:只能是抽象方法
    默认修饰符:public abstract
    建议:自己手动给出。
  • 接口的特点
  1. 在接口中与抽象类一样不对功能做具体实现;
  2. 接口不能实例化,可以通过间接实现实例化:父接口指向子类对象,这也是多态;
  3. 类作为接口的子类有2种选择:重写接口的所有抽象方法(推荐)或者作为抽象子类
  1. JDK1.7 接口中的方法全部是抽象方法,不存在非抽象方法
    JDK1.8之后允许给出方法的具体实现,但是这个方法要用关键字default修饰。(因为类与类是单继承,如果一个类想要继承多个类的功能时,由于一个类可以实现多个接口,如果接口可以给出方法的具体实现,把功能写到接口中,一个类实现多个接口就同时拥有了这些功能)。
    JDK1.8接口可以定义静态方法接口名.方法名调用。以上都是为了实现功能而不用重写的改变。

举例理解:

public class MyTest {
    public static void main(String[] args) {
        //JDK1.8之后,他允许给出方法的具体实现,但是这个方法得用default来修饰。
        //JDK1.8之后,接口中可以定义静态方法
        Zi zi = new Zi();
        zi.aa(); //子类实现接口功能
        zi.bb(); //子类实现接口功能
        //接口的静态方法使用接口名.静态方法名调用
        AA.test();
    }
}

interface AA{
    //接口给出方法的具体实现,要使用default修饰
   public default void aa(){
       System.out.println("aaa");
   }
   //接口定义静态方法
   public static void test(){
       System.out.println("abc");
   }
}

interface BB {
   default void bb(){
       System.out.println("bbb");
   }
}

class Zi implements AA,BB{
//Zi类实现接口AA,BB,不需要再写出接口的default修饰的方法是如何实现的
}
  1. 接口可以继承多个接口
interface AA {
    }
interface BB{
    default void bb(){
        System.out.println("bb方法");
    }
}
//接口可以继承多个接口
interface CC extends AA,BB{
    void cc();
}
  1. 一个类可以实现多个接口,这个类要把所有实现的接口中的所有抽象方法都要实现
interface AA {
    void aaa();
    void aa();
    }
interface BB{
    default void bb(){
        System.out.println("bb方法");
    }
}
//接口可以继承多个接口
interface CC extends AA,BB{
    void cc();
}

class MyDD implements AA,BB {
    @Override
    public void aaa() {}
    //一个类可以实现多个接口,这个类要把所有实现的接口中的所有抽象方法都要实现
    @Override
    public void aa() {}
    @Override
    public void bb() {}
}

类与类,类与接口,接口与接口的关系

类与类:

继承关系,只能单继承,可以多层继承。

类与接口:

实现关系,可以单实现,也可以多实现。
并且还可以在继承一个类的同时实现多个接口。

接口与接口:

继承关系,可以单继承,也可以多继承。

抽象类与接口的区别:

  • 成员区别

抽象类:
成员变量:可以变量,也可以常量
构造方法:有
成员方法:可以抽象,也可以非抽象
接口:
成员变量:只可以是静态常量
成员方法:只可以抽象,JDK1.8可以定义 default修饰的方法和静态方法。

  • 关系区别

类与类:继承,单继承
类与接口:实现,单实现,多实现
接口与接口:继承,单继承,多继承

  • 设计理念区别

抽象类——被继承体现的是:"is a"的关系;抽象类中定义的是该继承体系的共性功能。
接口——被实现体现的是:"like a"的关系;接口中定义的是该继承体系的扩展功能。

小练习:理解接口的使用方法

动物类:姓名,年龄,吃饭,睡觉。
动物培训接口:跳高
猫继承动物类
狗继承动物类
部分猫继承猫类并实现跳高接口
部分狗继承狗类并实现跳高接口
通过抽象类测试基本功能。
通过接口测试扩展功能。

首先定义接口和抽象类:

public abstract class Animal {
    String name;
    int age;
    public abstract void eat();
    public abstract void sleep();
}
public interface Myinterface {
    //接口中提供扩展功能
    public abstract void jump() ;
}

相关子类如下:

public class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("吃饭");
    }
    @Override
    public void sleep() {
        System.out.println("睡觉");
    }
}


class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("吃饭");
    }
    @Override
    public void sleep() {
        System.out.println("睡觉");
    }
}


public class TomCat extends Cat implements JumpInterface {
    @Override
    public void eat() {
        System.out.println("Tom猫爱吃鱼排");
    }
    @Override
    public void sleep() {
        System.out.println("Tom猫睡在沙发上");
    }
    public void catchMouse(){
        System.out.println("Tom猫抓老鼠");
    }
    @Override
    public void jump() {
        System.out.println("TomCat学会了跳高");
    }
}


class 二哈 extends Dog implements JumpInterface{
    @Override
    public void eat() {
        System.out.println("二哈 吃狗粮");
    }
    @Override
    public void sleep() {
        System.out.println("二哈在家里睡觉");
    }
    public void sledding(){
        System.out.println("二哈 拉雪橇");
    }
    @Override
    public void jump() {
        System.out.println("二哈学会了跳高");
    }
}


通过抽象类测试基本功能。
通过接口测试扩展功能。

public class MyTest {
    public static void main(String[] args) {
    二哈 eh = new 二哈();
        Dog dog = eh;
        dog.name = "二哈";
        dog.age = 2;
        System.out.println(dog.name);
        System.out.println(dog.age);
        dog.eat();
        dog.sleep();
        //向下转型。
        二哈 er = (二哈) dog;
        er.sledding();
        //er.jump(); //二哈学会了跳高
   //接口不能实例化,可以通过间接实现实例化:父接口JumpInterface指向子类对象eh,这也是多态
        JumpInterface jumpInterface = eh;//eh这个对象测试接口功能
        jumpInterface.jump();
       //两个地址同名,说明是同一个引用,因为只new了一次
        System.out.println(eh);//eh二哈类的一个对象
        System.out.println(er);//er是eh被父类引用后又向下转型的对象

        System.out.println("==============================");
        TomCat tomCat=new TomCat();//实例化一只tom猫
        Cat cat=tomCat;//父类Cat指向子类对象 实现多态
        cat.age=20;
        cat.name="tom cat";
        cat.eat();
        cat.sleep();
        //向下转型
        TomCat tomCat1= (TomCat) cat;
        tomCat1.catchMouse();//访问特殊功能
        jumpInterface=tomCat;
        jumpInterface.jump();
        //一个对象经过向上转型和向下转型之后仍然是这个对象,地址值不变,只有重新new才会有新的地址值
        System.out.println(tomCat==tomCat1);
        

执行结果:

二哈
2
二哈 吃狗粮
二哈在家里睡觉
二哈 拉雪橇
二哈学会了跳高
org.westos.demo5.二哈@4554617c
org.westos.demo5.二哈@4554617c
==============================
Tom猫爱吃鱼排
Tom猫睡在沙发上
Tom猫抓老鼠
TomCat学会了跳高
true

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页