java中类的继承和多态

1.类的继承

1.1定义:

       在Java语言中,继承的基本思想是可以从已有的类派生出新类。不同的类可能会有一些共同的特征和行为,可以将这些共同的特征和行为统一放在一个类中,使它们可以被其他类所共享。

例如,可以将人(person)定义为一个类,因为员工(employee)具有人的所有的特征和行为,则可以将员工类定义为人的子类,这就叫继承。

在类的层次结构中,被继承的类称为父类(parent class)或超类(super class),而继承得到的类称为子类(sub class)或派生类(derived class)。子类继承父类的状态和行为,同时也可以具有自己的特征。

1.2继承的实现:

public class a extends b {

//类体定义

}

在这里,a为子类,b为父类;

1.3关于类继承的解释:

(1)子类继承父类中非private的成员变量和成员方法。例如,在Employee类中可以使用从父类继承来的name和age属性,还可以调用从父类继承来的方法,如sayHello()方法。子类还可以定义自己的成员变量和成员方法,如Employee类定义了一个表示工资的变量salary,还定义了computeSalary()方法。

(2)定义类时若缺省extends关键字,则所定义的类为java.lang.Object类的直接子类。在Java语言中,一切类都是Object类的直接或间接子类。例如,Person类是Object类的子类,也继承了Object类中定义的方法。Employee类、Person类和Object之间的类层次关系如图7-1所示。前面定义的所有类都是Object的子类。

java中类的继承和多态_第1张图片

(3)Java仅支持单重继承,即一个类至多只有一个直接父类。在Java中可以通过接口实现其他语言中的多重继承。

注意:

父类中定义的private成员变量和方法不能被子类继承,因此在子类中不能直接使用。如果父类中定义了公共的访问方法和修改方法,子类可以通过这些方法来访问或修改它们。

2.方法覆盖:

2.1定义:

在子类中可以定义与父类中的名字、参数列表、返回值类型都相同的方法,这时子类的方法就叫作覆盖(overriding)或重写了父类的方法。

如果子类覆盖了父类的方法,在调用相同的方法时,调用的是子类的方法。

为了避免在覆盖方法时写错方法头,可以使用@Override注解语法,即在要覆盖的方法前面添加@Override。

@Override注解表示其后的方法必须是覆盖父类的一个方法。如果具有该注解的方法没有覆盖父类的方法,编译器将报告一个错误。例如,toString如果被错误地写成tosrting,将报告一个编译错误。如果没有使用注解,编译器不会报告错误。使用注解可以避免错误。

2.2注意点

(1)private方法不能覆盖。只有非private的实例方法才可以覆盖,如果在子类中定义了一个方法在父类中是private的,则这两个方法无关。

(2)父类中static方法可以被继承,但不能被覆盖。如果子类中定义了与父类中的static方法完全一样的方法,那么父类中的方法被隐藏。父类中被隐藏的static方法仍然可以使用“类名.方法名()”形式调用。

方法重载是在一个类中定义多个名称相同但参数不同的方法。而方法覆盖是在子类中为父类中的同名方法提供一个不同的实现。要在子类中定义一个覆盖的方法,方法的参数和返回值类型都必须与父类中的方法相同。

public class Parent {
    public void  display(double i ){
        System.out.println(i);
    }
}

public class Child extends Parent{
    public void  display(double i ){
        System.out.println(2*i);
    }
}

public class Test {
    public static void main(String[] args) {
        Child child = new Child();
        child.display(10.0);
        child.display(10);
    }
}

Parent类中定义了display()方法,Child类的display()与Parent类的display()参数和返回值类型都相同,是方法覆盖,但实现不同。Test类的main()方法中对Child类对象obj的display()方法的两次调用(参数类型不同)结果都为20.0,说明调用的都是Child类中覆盖的方法。

如果将Child类中display()方法的参数改为int i,再次执行程序,输出结果是20.0和10.0。这说明Child类中定义的display()方法不是对父类的方法覆盖,而是父类中继承来的display()方法的重载,因此当为display()方法传递一个double型参数时,将执行父类中的方法。

在子类中可以定义与父类中同名的成员变量,这时子类的成员变量会隐藏父类的成员变量。

3.super关键字:

java中类的继承和多态_第2张图片

4.调用父类的构造方法:

子类不能继承父类的构造方法。要创建子类对象,需要使用默认构造方法或为子类定义构造方法。

4.1子类的构造方法:

Java语言规定,在创建子类对象时,必须先创建该类的所有父类对象。因此,在编写子类的构造方法时,必须保证它能够调用父类的构造方法。

在子类的构造方法中调用父类的构造方法有两种方式:

java中类的继承和多态_第3张图片

4.1构造方法的调用过程:

在任何情况下,创建一个类的实例时,将会沿着继承链调用所有父类的构造方法,这叫作构造方法链。下面代码定义了Vehicle类、Bicycle类和ElectricBicycle类,代码演示了子类和父类构造方法的调用。

java中类的继承和多态_第4张图片

java中类的继承和多态_第5张图片

5.封装性与访问修饰符:

5.1定义:

封装性是面向对象的一个重要特征。在Java语言中,对象就是一组变量和方法的封装体。通过对象的封装,用户不必了解对象是如何实现的,只须通过对象提供的接口与对象进行交互就可以。封装性实现了模块化和信息隐藏,有利于程序的可移植性和对象的管理。

对象的封装是通过下面两种方式实现的。

(1)通过包实现封装性。在定义类时使用package语句指定类属于哪个包。包是Java语言最大的封装单位,定义了程序对类的访问权限。

(2)通过类或类的成员访问权限实现封装性。

5.2类的访问权限:

类(包括接口和枚举等)的访问权限通过修饰符public实现,定义哪些类可以使用该类。public类可以被任何其他类使用,而缺省访问修饰符的类仅能被同一包中的类使用。

5.3类成员的访问权限:

类成员的访问权限包括成员变量和成员方法的访问权限。共有4个修饰符,分别是private、缺省的、protected和public,这些修饰符控制成员可以在程序的哪些部分被访问。

5.3.1private访问修饰符

用private修饰的成员称为私有成员,私有成员只能被这个类本身访问,外界不能访问。private修饰符最能体现对象的封装性,从而可以实现信息的隐藏。

java中类的继承和多态_第6张图片

5.3.2缺省访问修饰符:

缺省访问修饰符的成员,一般称为包可访问的。这样的成员可以被该类本身和同一个包中的类访问。其他包中的类不能访问这些成员。对于构造方法,如果没有加访问修饰符,也只能被同一个包的类产生实例。

5.3.3protected访问修饰符:

当成员被声明为protected时,一般称为保护成员。该类成员可以被这个类本身、同一个包中的类以及该类的子类(包括同一个包以及不同包中的子类)访问。如果一个类有子类且子类可能处于不同的包中,为了使子类能直接访问父类的成员,那么应该将其声明为保护成员,而不应该声明为私有或默认的成员。

5.3.4public访问修饰符:

用public修饰的成员一般称为公共成员,公共成员可以被任何其他的类访问,但前提是类是可访问的。

java中类的继承和多态_第7张图片

6.防止类扩展和方法覆盖:

6.1final修饰类:

如果一个类使用final修饰,则该类就为最终类(final class),最终类不能被继承。

定义为final的类隐含定义了其中的所有方法都是final的。因为类不能被继承,因此也就不能覆盖其中的方法。有时为了安全的考虑,防止类被继承,可以在类的定义时使用final修饰符。在Java类库中就有一些类声明为final类,如Math类和String类都是final类,它们都不能被继承。

6.2final修饰方法:

如果一个方法使用final修饰,则该方法不能被子类覆盖。

class a{

  public final void method(){}
}
class b extends  a{


public void method(){}//该语句发生编译错误


}

6.3final修饰变量:

用final修饰的变量包括类的成员变量、方法的局部变量和方法的参数。一个变量如果用final修饰,则该变量为常值变量,一旦赋值便不能改变。

对于类的成员变量一般使用static与final组合定义类常量。这种常量称为编译时常量,编译器可以将该常量值代入任何可能用到它的表达式中,这可以减轻运行时的负担。

如果使用final修饰方法的参数,则参数的值在方法体中只能被使用而不能被改变,

java中类的继承和多态_第8张图片

7.抽象类:

前面章节中定义的类可以创建对象,它们都是具体的类。在Java中,还可以定义抽象类。抽象类(abstract class)是包含抽象方法的类。

假设要开发一个图形绘制系统,需要定义圆类(Circle)、矩形类(Rectangle)和三角形类(Triangle)等,这些类都需要定义求周长和面积的方法,这些方法对不同的图形有不同的实现。这时就可以设计一个更一般的类,如几何形状类(Shape),在该类中定义求周长和面积的方法。由于Shape不是一个具体的形状,这些方法就不能实现,因此要定义为抽象方法(abstract method)。

定义抽象方法需要在方法前加上abstract修饰符。抽象方法只有方法的声明,没有方法的实现。包含抽象方法的类必须定义为抽象类,定义抽象类需要的类前加上abstract修饰符。下面定义的Shape类即为抽象类,其中定义了两个抽象方法。

java中类的继承和多态_第9张图片

在抽象类中可以定义非抽象的方法。可以创建抽象类的子类,抽象类的子类还可以是抽象类,只有非抽象的子类才能使用new创建该类的对象。抽象类中可以没有抽象方法,但仍然需要被子类继承,才能实例化。

注意:因为abstract类必须被继承而final类不能被继承,所以final和abstract不能在定义类时同时使用。

java中类的继承和多态_第10张图片

这里定义的Circle类继承了抽象类Shape类,由于Circle类不是抽象类,因此它必须实现抽象类中getArea()和getPerimeter()两个方法,此外,它还定义了构造方法和其他普通方法。

8.对象转换与多态:

8.1对象转换:

继承关系使一个子类继承父类的特征,并且附加一些新特征。子类是它父类的特殊化,每个子类的实例也都是它父类的实例,但反过来不成立。因此,子类对象和父类对象在一定条件下也可以相互转换,这种类型转换一般称为对象转换或造型(casting)。对象转换也有自动转换和强制转换之分。

由于子类继承了父类的数据和行为,因此子类对象可以作为父类对象使用,即子类对象可以自动转换为父类对象。可以将子类型的引用赋值给父类型的引用。

假设parent是一个父类型引用,child是一个子类型(直接或间接)引用,则下面的赋值语句是合法的:parent = child;//子类对象自动转换为父类对象。

这种转换称为向上转换(up casting)。向上转换指的是在类的层次结构图中,位于下方的类(或接口)对象都可以自动转换为位于上方的类(或接口)对象,但这种转换必须是直接或间接类(或接口)。

反过来,也可以将一个父类对象转换成子类对象,这时需要使用强制类型转换。强制类型转换需要使用转换运算符“()”。

java中类的继承和多态_第11张图片

java中类的继承和多态_第12张图片

8.2instanceof运算符

instanceof运算符用来测试一个实例是否是某种类型的实例,这里的类型可以是类、抽象类、接口等。instanceof运算符的格式为:

该表达式返回逻辑值。如果variable是TypeName类型或父类型的实例,则返回true,否则返回false。

java中类的继承和多态_第13张图片

if (p instanceof Employee) {
    Employee emp = (Employee) p;
    // 进行后续操作
} else {
    // 处理类型不匹配的情况
}

8.3多态与动态绑定:

多态(polymorphism)就是多种形式,是指Java程序中一个类或多个类中可以定义多个同名方法,这多个同名方法完成的操作不同,这就是多态。多态性是指在运行时系统判断应该执行哪个方法的代码的能力。Java语言支持两种类型的多态:

(1)静态多态:也叫编译时多态,是通过方法重载实现的。

(2)动态多态:也叫运行时多态,是通过方法覆盖实现的。

将方法调用与方法体关联起来称方法绑定(binding)。若在程序执行前进行绑定,叫前期绑定,如C语言的函数调用都是前期绑定。若在程序运行时根据对象的类型进行绑定,则称后期绑定或动态绑定。Java中除static方法和final方法外都是后期绑定。

对重载的方法,Java运行时系统根据传递给方法的参数个数和类型确定调用哪个方法,而对覆盖的方法,运行时系统根据实例类型决定调用哪个方法。对子类的一个实例,如果子类覆盖了父类的方法,运行时系统调用子类的方法,如果子类继承了父类的方法,则运行时系统调用父类的方法。

有了方法的动态绑定,就可以编写只与基类交互的代码,并且这些代码对所有的子类都可以正确运行。假设抽象类Shape定义了getArea()方法,其子类Circle、Rectangle和Square都各自实现了getArea()方法。下面的例子说明了多态和方法动态绑定的概念。

java中类的继承和多态_第14张图片

  • 在Java中,多态是面向对象编程的一个重要概念,它允许使用父类类型的引用来引用子类对象,并根据实际对象的类型来调用相应的方法。多态性使得我们可以编写更灵活、可扩展和可维护的代码。

    多态性的实现依赖于两个关键概念:继承和方法重写。

    1. 继承:子类可以继承父类的属性和方法。子类可以扩展或重写父类的方法,也可以添加自己的方法。通过继承,子类可以具备父类的行为和特性。

    2. 方法重写:子类可以重写(覆盖)父类的方法,即在子类中重新定义与父类中具有相同名称和参数列表的方法。子类重写的方法可以具有不同的实现,但方法签名必须与父类方法相同。

    多态性的实现方式有两种:编译时多态和运行时多态。

    1. 编译时多态:通过父类类型的引用指向子类对象,编译器在编译时只检查父类中定义的方法和属性,而不检查实际对象的类型。这意味着,通过父类引用只能调用父类中定义的方法和属性。

    2. 运行时多态:在运行时,Java虚拟机会根据实际对象的类型来调用相应的方法。即使使用父类类型的引用,如果实际对象是子类对象,那么调用的将是子类中重写的方法。这使得我们可以在不修改现有代码的情况下,通过替换实际对象来改变程序的行为。

    多态性的优势在于它提高了代码的可扩展性和可维护性。通过使用多态,我们可以编写通用的代码,减少重复的代码,并且可以方便地添加新的子类来扩展程序的功能。此外,多态性还支持面向接口编程,使得代码更加灵活和可测试。

    class Animal {
        public void makeSound() {
            System.out.println("Animal is making a sound");
        }
    }
    
    class Dog extends Animal {
        @Override
        public void makeSound() {
            System.out.println("Dog is barking");
        }
    }
    
    class Cat extends Animal {
        @Override
        public void makeSound() {
            System.out.println("Cat is meowing");
        }
    }
    
    public class PolymorphismExample {
        public static void main(String[] args) {
            Animal animal1 = new Dog();
            Animal animal2 = new Cat();
    
            animal1.makeSound(); // 输出:Dog is barking
            animal2.makeSound(); // 输出:Cat is meowing
        }
    }

    在上面的示例中,我们定义了一个Animal类作为父类,以及DogCat类作为子类。父类Animal有一个makeSound()方法,子类DogCat分别重写了这个方法。

    PolymorphismExample类的main方法中,我们创建了一个Animal类型的引用animal1,并将其指向一个Dog对象。同样,我们创建了另一个Animal类型的引用animal2,并将其指向一个Cat对象。

    当我们调用animal1.makeSound()时,由于animal1引用指向的是Dog对象,所以会调用Dog类中重写的makeSound()方法,输出"Dog is barking"。

    同样地,当我们调用animal2.makeSound()时,由于animal2引用指向的是Cat对象,所以会调用Cat类中重写的makeSound()方法,输出"Cat is meowing"。

    这个示例展示了多态性的特性,即通过父类类型的引用调用子类对象的方法,根据实际对象的类型来决定调用哪个方法。这种灵活性使得我们可以编写更通用和可扩展的代码。

你可能感兴趣的:(java,IDEA,java,开发语言)