黑马程序员-Java基础:继承和多态

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

day08-09:继承和多态


一、继承

1.概述:提取多个类中的共性内容,组成一个新的类,这个新类和原来类之间就产生了一种关系,并且原来的多个类都具备新类中的内容,这种关系就称为继承。
举例:现实生活中当提到猫,狗时,我们都清楚它们是一种动物,那么就可以说猫,狗继承自动物。
类似这种: is-a关系都可以称为继承关系,被继承的类称为父类或超类(father/super class),继承自父类的类称为子类或基类(child/base class)。
例如:Cat is-a animal, 小明 is-a 学生…
注:判断是否使用继承:类之间是否具备is-a关系

2.继承的书写格式
Java中通过extends 关键字来实现类与类之间的继承关系
格式:class 子类名 extends 父类名 { }
示例:

class Person {}

class Student extends Person {}

class Teacher extends Person {}

注:该示例仅仅用于说明Java的继承书写格式,因此没有给出类的具体实现。

3.继承的简单案例
需求:
<1>定义一个类Person
该类中包含两个成员变量:name, age
两个构造方法:空参构造,带参构造
一个普通方法:show():用于打印所有成员变量
<2>定义一个类Student继承Person
<3>定义一个测试类Demo,在测试类中测试两个类。
代码:

/*
 * Person类:父类
 */
public class Person {
    /*
     * 成员变量
     */
    String name;
    int age;
    //空参构造
    public Person() {
    }
    //带参构造
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //普通方法show()
    public void show() {
        System.out.println("Person:" + name + "," + age);
    }
}
/*
 * Student类:子类
 * 在学生类中没有定义任何成员,主要为了说明继承的特点
 */
public class Student extends Person {
}
public class Demo {

    /*
     * 测试类:创建Student类对象,并通过Student对象访问Person类中的成员 
     * 1.访问Person类中的成员变量
     * 2.调用Person类中的成员方法
     */
    public static void main(String[] args) {
        Student s = new Student();
        // 1.访问Person类中的成员变量
        s.name = "小明";
        s.age = 18;
        System.out.println(s.name);
        System.out.println(s.age);
        // 2.调用Person类中的成员方法
        s.show();
    }
}

测试结果:

小明
18
Person:小明,18

4.继承的好处和弊端
好处:
<1>提高了代码的复用性
<2>提高了代码的可维护性
<3>让类与类之间产生了关系,是多态的前提条件
弊端:
<1>打破了类的封装性
<2>使类之间的耦合性增强,当父类发生改变时,会对子类产生影响

程序开发的原则:低耦合,高内聚
耦合是指类与类之间的关系,内聚是指类独立完成某项功能的能力

5.Java中继承的特点
<1>Java中只支持单继承,不支持多继承
<2>Java可以支持多层继承
代码解释:

class A{
}

class B{
}
/*
 * 下面的代码编译无法通过
 * 原因:Java只支持单继承,C无法既继承A,又继承B
 */
class C extends A,B{
}
class A{
}

class B extends A{
}
/*
 *  下面代码可以编译通过
 *  原因:Java支持多层继承,C通过继承B间接继承了A
 */
class C extends B{
}

6.继承中的注意事项
<1>子类不能继承父类的私有成员,因为私有成员只能在本类中访问
代码验证:

class Father{
    /*在父类中定义两个私有成员*/
    private int num = 10;

    private void method(){
        System.out.println("Father method.");
    }
}
//子类中没有成员
class Child extends Father{
}

public class ExtendDemo{
    public static void main(String[] args) {
        //在测试类中创建子类对象
        Child c = new Child();
        //通过子类对象变量调用父类的私有成员
        //编译报错: num has private access in Father
        c.num = 200;
        //编译报错: cannot find symbol c.method();
        c.method();
    }
}

<2>子类无法继承父类的构造方法,但可以通过super访问
<3>不要为了部分功能而使用继承,需要确定类之间是否具备is-a关系

7.继承关系中子父类成员的关系
<1>成员变量的关系
问题1:如何在子类方法中访问父类中的非私有成员变量?
代码解答:
情况1:子父类中成员变量不同名的情况

class Father {
    int a = 100;
}

class Child extends Father {
    int b = 200;

    public void childMethod() {
        System.out.println("访问子类中的变量:" + b);
        System.out.println("访问父类中的变量:" + a);
    }
}

class ExtendDemo2 {
    public static void main(String[] args) {
        Child c = new Child();
        c.childMethod();
    }
}

运行结果:
这里写图片描述

情况2:子类中成员变量与父类成员变量同名的情况

class Father {
    int a = 100;
}

class Child extends Father {
    int a = 200;

    public void childMethod() {
        int a = 300;

        System.out.println("访问子类中的局部变量:" + a);
        System.out.println("访问子类中的成员变量:" + this.a);
        System.out.println("访问父类中的成员变量:" + super.a);
    }
}

class ExtendDemo2 {
    public static void main(String[] args) {
        Child c = new Child();
        c.childMethod();
    }
}

运行结果:
这里写图片描述
由上面的示例代码可知,要在子类中访问父类成员变量,可以使用super关键字

变量的就近访问原则:在方法内使用一个变量时,先在方法中寻找该变量,若没找到则在本类成员变量中寻找该变量,若仍然找不到则在父类成员变量中寻找该变量,以上位置都无法找到该变量,则报错。
类似的方法的调用也有就近访问原则,先在调用方法的对象中查找该方法,没有则到父类中找该方法,然后间接父类,依次类推,没找到就报错。

<2>构造方法的关系
A.虽然子类无法继承父类的构造方法,但子类的构造方法默认会去访问父类的空参构造方法,这样做的原因在于初始化子类数据之前先初始化父类的数据
代码示例:

class Father{
    private int a;
    private int b;

    public Father() {
        System.out.println("父类空参构造方法被调用.");
    }

    public Father(int a, int b) {
        this.a = a;
        this.b = b;
        System.out.println("父类带参构造方法被调用.");
    }
}

class Child extends Father {
    public Child() {
        System.out.println("子类空参构造方法被调用.");
    }

    public Child(int a, int b) {
        System.out.println("子类带参构造方法被调用.");
    }
}

public class ExtendDemo2{
    public static void main(String[] args) {
        Child c = new Child();
        System.out.println("---------------");
        Child c2 = new Child(1,2);
    }
}

运行结果:
这里写图片描述
从程序的运行结果可以看到,在调用子类构造方法创建对象时,默认会调用父类的空参构造方法。

B.父类中如果没有空参构造方法,那么子类需要显示调用父类的其他构造方法,通过super语句:super(参数…);
代码示例:对上面的代码示例做了如下修改

class Father{
    private int a;
    private int b;
//注释掉父类的空参构造
//  public Father() {
//      System.out.println("父类空参构造方法被调用.");
//  }
    //保留父类的带参构造
    public Father(int a, int b) {
        this.a = a;
        this.b = b;
        System.out.println("父类带参构造方法被调用.");
    }
}

class Child extends Father {
    public Child() {
        //子类空参构造中显示调用父类带参构造
        super(1,2);
        System.out.println("子类空参构造方法被调用.");
    }

    public Child(int a, int b) {
        //子类带参构造中显示调用父类带参构造
        super(1,2);
        System.out.println("子类带参构造方法被调用.");
    }
}

public class ExtendDemo2{
    public static void main(String[] args) {
        Child c = new Child();
        System.out.println("---------------");
        Child c2 = new Child(1,2);
    }
}

运行结果:
这里写图片描述
在上面的代码中如果不通过super语句显示调用父类的带参构造方法,则程序无法编译通过。
因此,子类的构造方法中一定会调用父类的构造方法。

super和this关键字对比:
this代表本类对象的引用,super代表父类对象的引用
this.成员变量(成员方法) : 在类中调用本类成员
super.父类成员 : 在子类中调用父类成员
this(参数) : 在本类构造方法中调用本类其他构造方法
super(参数) : 在子类构造方法中调用父类构造方法

<3>成员方法的关系
方法重写:子类中出现了和父类中一模一样的方法声明,即返回值类型,方法名,参数列表都相同,这种情况称为方法重写,或方法覆盖
使用特点示例
思考如下代码:

class Father{
    //在父类中定义方法method()
    public void method() {
        System.out.println("父类中的方法");
    }
}

class Child extends Father {
    //在子类中定义与父类相同声明的方法method()
    public void method() {
        System.out.println("子类中的方法");
    }
}

public class ExtendDemo2{
    public static void main(String[] args) {
        Child c = new Child();
        //通过子类对象调用方法method()
        c.method();
    }
}

运行结果:
这里写图片描述
从运行结果发现,最终调用的是子类的方法,而不是父类的方法,这种现象就是方法的重写。

方法重写的应用:当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
方法重写的注意事项:
父类中私有的方法不能重写,因为父类的私有方法子类无法继承;
子类重写父类方法时访问权限不能更低,最好与父类一致;
父类静态方法,子类也必须通过静态方法进行重写。

8.final 关键字
final 是最终的意思,可以用来修饰类,成员变量,成员方法
被final修饰的类无法被继承
被final修饰的变量只能被赋值一次,之后无法再改变,即称为常量
被final修饰的方法无法被子类重写

final修饰基本类型变量时,是值不能被改变;修饰引用类型变量时,是地址不能被改变

二、多态

1.概述
多态是指某一个事物在不同时刻表现出来的不同状态。
例如:水可以有气态,液态,固态,但它们的本质都是水
动物可以指猫,狗,老虎…..虽然它们名字不同,但都属于动物的一种
那么Java中如何体现多态呢?
下面用一个需求的两种解决方案来说明
需求:分别定义一个猫类,狗类,老虎类,鸟类,来模拟现实中的动物,这些动物都有一个行为叫进食,创建它们的对象并让所有动物进食。
分析:很显然每一种动物都有各自喜欢吃的食物,所以虽然它们都有吃的行为,但具体的行为方式却不尽相同。
代码实现:非多态的方式

//定义一个猫类
class Cat{
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
//定义一个狗类
class Dog{
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
//定义一个老虎类
class Tiger {
    public void eat() {
        System.out.println("老虎吃肉");
    }
}
//定义一个鸟类
class Bird {
    public void eat() {
        System.out.println("鸟吃虫子");
    }
}

class DuotaiDemo{
    public static void main(String[] args) {
        /*
         * 创建4种动物的对象
         */
        Cat cat = new Cat();
        Dog dog = new Dog();
        Tiger tiger = new Tiger();
        Bird bird = new Bird();
        /*
         * 调用各自的进食方法
         */
        cat.eat();
        dog.eat();
        tiger.eat();
        bird.eat();
    }
}

输出结果:
这里写图片描述

分析:通过结果,我们很开心的发现这段程序让所有动物吃到了想吃的东西,那么接下来需求改变了,又来了其他动物,马,大象,猪……不计其数的动物都要吃各自的喜欢的食物,我们当然可以用上面的代码去解决,再添加新的类,然后创建新动物类的对象,调用进食方法,但是可以想象代码的长度,而且每加入一个新的动物都要对代码做大量修改。

下面用一种更好的方式来改进上面的代码:
由于每种动物都要进食,而且它们都属于动物类型的一种,那么可以定义一个动物类,让所有的动物继承自这个类。
代码实现:多态方式

//定义一个动物类
class Animal {
    //并没有给出到底吃什么
    public void eat() {
    }
}
//定义一个猫类,继承动物类
class Cat extends Animal{
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
//定义一个狗类,继承动物类
class Dog extends Animal{
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
//定义一个老虎类,继承动物类
class Tiger extends Animal{
    public void eat() {
        System.out.println("老虎吃肉");
    }
}
//定义一个鸟类,继承动物类
class Bird extends Animal{
    public void eat() {
        System.out.println("鸟吃虫子");
    }
}

class DuotaiDemo{
    public static void main(String[] args) {
        /*
         * 创建一个动物类型的数组
         */
        Animal[] animals = new Animal[4];
        //将动物数组中的元素赋值为特定的动物对象
        animals[0] = new Cat();
        animals[1] = new Dog();
        animals[2] = new Tiger();
        animals[3] = new Bird();
        /*
         * 让所有动物进食
         */
         for(int i=0; i<animals.length; i++) {
            animals[i].eat();
         }
    }
}

运行结果:
这里写图片描述

理解上面代码的关键就在于Animal类型的引用实际指向的对象是Animal的子类对象,而这正式多态的意义所在。可以看到通过多态改进后的代码可维护性和可扩展性都大大增强了,此时如果再加入其他动物类,只需要让新的动物继承Animal,重写eat()方法,然后将新动物的对象加入animals数组即可。

由上面的例子得出多态的前提条件
有继承或者实现关系
有方法重写
父类引用指向子类对象

2.多态的好处和缺点
好处:提高了代码的可维护性和可扩展性
缺点:不能通过父类的引用使用子类特有的功能

向上转型:当父类引用指向子类对象时,编译器会将子类对象当作父类使用
向下转型:当需要在多态中使用子类特有方法时,需要将父类引用强制转型为子类引用,但这种操作的前提是必须保证父类引用实际指向的是子类对象,否则会发生类型转换异常。

3.多态中成员访问的特点
通过多态访问成员变量:通过父类的引用变量只能访问父类的成员变量,而不能访问子类的成员变量,否则编译不通过。
通过多态访问成员方法:通过父类引用只能调用父类中定义的方法,当子类重写了该方法时,则调用子类重写后的方法。无法调用子类特有的方法。
通过多态访问静态方法:因为静态的成员都是与类相关的,所有不存在多态的问题,通过类名调用即可。
构造方法:创建子类对象时需要调用子类构造方法,而子类构造方法中会默认调用父类构造方法。

三、抽象类

1.抽象方法的概念:没有方法体的方法就是抽象方法,即没有方法的具体实现。
格式:修饰符 abstract 方法名 (参数列表) ;
2.抽象类的概念:被abstract关键字修饰的类称为抽象类。
凡是包含抽象方法的类必须定义为抽象类,但抽象类中不一定包含抽象方法。
3.抽象类的特点:
<1>抽象类和抽象方法必须用abstract关键字修饰
<2>抽象类中不一定包含抽象方法,但有抽象方法的类必须是抽象类
<3>抽象类不能实例化,但可以有构造方法
<4>抽象类的子类要么声明为抽象类,要么重写父类所有抽象方法。
除了以上四个特点抽象类和普通类没有什么区别,也可以定义成员变量和成员方法。
一个简单的抽象类案例:

/*
    需求:定义一个抽象类Person
        成员变量:姓名,年龄
        成员方法:吃饭,睡觉
        定义两个类继承Person,一个Student,一个Teacher
        Student有特有方法:study
        Teacher有特有方法:teach
*/
abstract class Person {
    //成员变量
    private String name;
    private int age;
    //空参构造
    public Person() {}
    //带参构造
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //成员变量的getXxx()/setXxx()方法
    public String getName(){
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    //抽象方法
    public abstract void eat();
    public abstract void sleep();
}

class Teacher extends Person {

    public Teacher(){}

    public Teacher(String name, int age) {
        super(name, age);
    }
    //重写父类的抽象方法eat()
    public void eat() {
        System.out.println(getName() + " is eating.");
    }
    //重写父类的抽象方法sleep()
    public void sleep() {
        System.out.println(getName() + " is sleeping.");
    }
    //子类特有方法
    public void teach() {
        System.out.println(getName() + " is teaching.");
    }
}

class Student extends Person {

    public Student(){}

    public Student(String name, int age) {
        super(name, age);
    }
    //重写父类的抽象方法eat()    
    public void eat() {
        System.out.println(getName() + " is eating.");
    }
    //重写父类的抽象方法sleep()
    public void sleep() {
        System.out.println(getName() + " is sleeping.");
    }
    //子类特有方法
    public void study() {
        System.out.println(getName() + " is study.");
    }
}
//测试类
class PersonDemo {
    public static void main(String[] args) {
        Teacher t = new Teacher("比向东老师", 18);
        Student s = new Student("小明", 6);

        t.eat();
        t.sleep();
        t.teach();

        s.eat();
        s.sleep();
        s.study();
    }
}

4.抽象类的几个小问题
<1>抽象类有构造方法,不能实例化,那么构造方法有什么用?
答:用于子类访问父类数据的初始化
<2>一个类如果没有抽象方法,却定义为了抽象类,有什么用?
答:为了禁止创建该类的对象
<3>abstract不能和哪些关键字共存?
答:final,private,static

四、接口

1.概述
在现实生活中有些人抽烟,有些人不抽烟,那么当我们要在Java中描述一个人类的时候(Person类),如何去表示哪些人抽烟哪写人不抽烟呢?
由于抽烟仅仅是一个行为,而这个行为不是所有人都具备,那么就可以将抽烟这个行为定义为一个接口,然后由抽烟的人去实现这个接口。
从上面的例子可以知道,当需要为某一个类添加扩展功能时,就需要为这个扩展功能定义一个接口,然后由需要扩展的类来实现具体的功能。

2.接口的特点
成员特点
<1>成员变量:只能是常量
接口中成员变量都有默认修饰符:public static final
<2>构造方法:没有构造方法
<3>成员方法:只能是抽象方法
接口中成员方法默认修饰符:public abstract

使用特点:
<1>定义一个接口时,用关键字interface修饰:

interface 接口名{ }

<2>一个类实现接口时,用implements 关键字

class 类名 implements 接口名 {}

<3>接口不能实例化
<4>接口的实现类特点:可以是抽象类或具体类,如果是具体类,必须重写(或者说实现更恰当)接口中所有方法

接口的简单示例

interface Smoke {
    public abstract void smoke();
}

interface Study {
    public abstract void study();
}

class Worker implements Smoke {
    public void smoke(){
        System.out.println("工人抽烟");
    }
}

class Student implements Study{
    public void study() {
        System.out.println("学生学习");
    }   
}

public class InterfaceDemo {
    public static void main(String[] args) {
        Worker w = new Worker();
        w.smoke();
        Student s = new Student();
        s.study();
    }
}

运行结果:
这里写图片描述

3.类与类,类与接口,接口与接口之间的关系
<1>类与类之间:继承关系,只能单继承,可以多层继承
<2>类与接口之间:实现关系,可以单实现,也可以多实现
还可以在继承一个类的同时实现多个接口
<3>接口与接口之间:继承关系,可以单继承,也可以多继承

五、内部类

1.概念:把类定义在另一个类的内部,该类就被称为内部类
举例:

class A {
    class B{
    }
}

如上面代码所示,类B就是A的内部类,其实内部类是类中成员的一种,类中的成员有成员变量,成员方法,当然也可以有成员类,就像人的体内有心脏,肝脏,那么心脏就属于人这个类的内部类。

2.内部类的访问规则
<1>在内部类中可以直接访问外部类的成员,包括私有成员,因为内部类本身也属于类的成员
<2>外部类要想访问内部类的成员,必须创建内部类的对象

3.内部类的分类
<1>成员内部类:位于类中方法外,和成员变量位置相同
创建内部类对象的方式:
非静态成员内部类:

外部类名.内部类名 对象名 = new 外部类名.new 内部类名();

静态成员内部类:

外部类名.内部类名 对象名 = new 外部类名.内部类名();

<2>局部内部类:位于方法体内
局部内部类访问局部变量必须加final修饰。

4.匿名内部类
局部内部类的简化形式
前提:存在一个类或者接口
格式:

 new 类名或接口名(){
    重写方法
}

本质:匿名内部类的本质是继承该类或者实现该接口的子类的匿名对象。
代码示例:

        interface Person {
            public abstract void study();
        }

        class PersonDemo {
            public void method(Person p) {
                p.study();
            }
        }

        class PersonTest {
            public static void main(String[] args) {
                PersonDemo pd = new PersonDemo();
                pd.method(new Person() {
                    public void study() {
                        System.out.println("好好学习,天天向上");
                    }
                });
            }
        }
六、包

包其实就是文件夹
1.作用:用于区分同名的类,并按照功能或模块对类进行分类管理
**2.定义方式:**package 包名;
注:多级包用 . 分隔
注意事项:
<1>package语句必须在文件中的第一条有效语句
<2>在一个java文件中,只能有一个package
<3>如果没有package,默认就是无包名
3.带包的编译和运行:

javac -d . HelloWorld.java

4.导包
<1>Java就提供了关键字import用于包的导入,如果在类中需要用到其他包中的类,则用import将需要的类导入。
<2>格式

import 包名…类名;
import 包名…*;(不建议)

<3>package,import,class的顺序

package > import > class

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值