Java(三)类:4.类的继承


一、继承方式

通过在类的声明中加入extends子句来创建一个类的子类。

class Father{
}
class Son extends Father{
}

(其实默认所有的类继承java.lang.Object类)

二、继承规则

1.默认继承

继承是子类利用父类中定义的成员方法成员变量(包括实例变量和类变量)就像它们属于子类本身一样。

默认继承:

  • 成员变量和成员方法

2.成员变量的继承

  • 继承成员变量(实例变量和类变量),只要声明了就可以。
class Animal {
	public static String birthday = "unknown";
	public int age = 10;
	public String name = "Animal";
}

public class Dog extends Animal {
	
	Dog(String name)
	{
		// 调用父类的成员变量
		System.out.println(super.name);
		this.name = name;
	}

	public static void main(String[] args) {
		Dog dog = new Dog("Odie");
		
		// 继承父类的成员变量name,修改了新的值		
		System.out.println(dog.name);
		
		// 继承父类的类变量birthday,值还一样
		System.out.println(dog.birthday);
		
		// 继承父类的实例变量age,值还一样
		System.out.println(dog.age);
		
		//System.out.println(super.name);
	}
}
/*
Animal
Odie
null
10
*/

3.构造函数的继承super(...)

  • Java在派生类的构造函数(不管有参还是无参)中自动调用基类的默认构造函数,即隐式调用super()。当然,你也可以显式调用super()
  • 当然指定执行super(arg)后就不会调用super()
  • 同构造函数重写this(...)一样
    • 只能在构造器中调用构造器,不能在非构造方法中调用
    • 调用时,super(...)必须放在第一位
    • 同一个构造器中不能调用两次构造器

(1)子类和父类构造函数隐式调用的例子

// 子类和父类都使用默认的构造函数,是可以的。
class Art {
}
class Drawing extends Art {
	public static void main(String[] args) {
		Drawing drawing = new Drawing();
	}
}
// 父类自定义无参构造函数,子类使用默认的构造函数
class Art {
	Art() {
		System.out.println("Art constructor()");
	}
}
class Drawing extends Art {
	public static void main(String[] args) {
		Drawing drawing = new Drawing();
	}
}
/*
Art constructor()
*/
// 父类使用默认的构造函数,子类自定义无参构造函数
class Art {
}
class Drawing extends Art {
	Drawing() {
		System.out.println("Drawing constructor()");
	}
	public static void main(String[] args) {
		Drawing drawing = new Drawing();
	}
}
/*
Drawing constructor()
*/
// 父类子类都自定义无参构造函数
class Art {
	Art() {
		System.out.println("Art constructor()");
	}
}
class Drawing extends Art {
	Drawing() {
		System.out.println("Drawing constructor()");
	}
	Drawing(String arg){
		System.out.println("Drawing constructor(" + "arg" + ")");
	}
	public static void main(String[] args) {
		Drawing drawing1 = new Drawing();
		Drawing drawing2 = new Drawing("arg");
	}
}
/*
Art constructor()
Drawing constructor()
Art constructor()
Drawing constructor(arg)
*/

(2)显示调用的例子

// 指定执行super(arg)后就不会调用super()
class Art {
	Art() {
		System.out.println("Art constructor()");
	}
	Art(String arg){
		System.out.println("Art constructor(" + "arg" + ")");
	}
}
class Drawing extends Art {
	Drawing() {
		super("arg");
		System.out.println("Drawing constructor()");
	}
	Drawing(String arg){
		super(arg);
		System.out.println("Drawing constructor(" + "arg" + ")");
	}
	public static void main(String[] args) {
		Drawing drawing1 = new Drawing();
		Drawing drawing2 = new Drawing("arg");
	}
}
/*
Art constructor(arg)
Drawing constructor()
Art constructor(arg)
Drawing constructor(arg)
*/

(3)构造出错

原因:父类自定义有参构造函数,就不会生成默认的无参构造函数,且未显示定义无参构造函数,而导致子类的构造器隐式调用出错。

报错信息:Implicit super constructor Animal() is undefined for default constructor. Must define an explicit constructor

// 错误示例
class Animal {
	public String name;
	
	// 不会自动生成Animal()了
	public Animal(String name)
	{
		this.name = name;
	}
}

public class Dog extends Animal {
	// 这里的Dog()哪怕不显示写,也会报错
	// 因为不写会生成默认的无参构造函数,而这个无参构造函数也会隐式调用super()
	Dog(){}

	public static void main(String[] args) {
		Dog dog = new Dog();
	}
}

解决方案:

  • 在父类显式写出了一个无参构造函数:这样super()就没问题了。
  • 在子类中显示指定调用父类的有参构造函数:这样就不会调用没有构造的Animal()
  • 删除父类的有参构造函数也可以是一个办法。
// 解决方案1:在父类显式写出了一个无参构造函数
class Animal {
	public String name;
	
	// 显式构造无参构造函数
	Animal(){}
	
	public Animal(String name)
	{
		this.name = name;
	}
}

public class Dog extends Animal {
	Dog(){}

	public static void main(String[] args) {
		Dog dog = new Dog();
	}
}
// 解决方案2:在子类中显示指定调用父类的有参构造函数
class Animal {
	public String name;
	
	public Animal(String name)
	{
		this.name = name;
	}
}

public class Dog extends Animal {
	// 显式调用super(arg)
	// 这里只是例子,一般是子类有参构造函数调用父类对应的有参构造函数。
	Dog(){
		super("something");
	}

	public static void main(String[] args) {
		Dog dog = new Dog();
	}
}
// 解决方案3:删除父类的有参构造函数
class Animal {
	public String name;
}

public class Dog extends Animal {
	// 删除后调用默认的无参构造函数
	Dog(){}

	public static void main(String[] args) {
		Dog dog = new Dog();
	}
}

4.实例方法的覆盖/重写(override)

实例方法和静态方法都可以被继承。

class Animal {
	public void bark()
	{
		System.out.println("~~~");
	}
	
	public static void drink()
	{
		System.out.println("animal drink");
	}
}

public class Dog extends Animal {
	public static void main(String[] args) {
		Dog dog = new Dog();
		dog.bark();
		dog.drink();
	}
}
/*
~~~
animal drink
*/

(1)重写override

意思:在子类中重新定义父类中已有的方法。

规则:

  • 子类覆盖的方法同父类的方法要保持名称、返回值类型、参数列表的统一。
  • 改写后的方法不能比被覆盖的方法有更严格的访问权限
  • 改写后的方法不能比被覆盖的方法产生更多的异常

写法:

  • super.func(...):调用父类的成员方法
  • @Override:重写方法的注记符,方便标出重写方法,没有也不报错。
class Animal {
	public void sleep()
	{
		System.out.println("zzz");
	}
	
	public void eat()
	{
		System.out.println("food");
	}
	
	public void bark()
	{
		System.out.println("~~~");
	}
}

public class Dog extends Animal {
	// 重写eat()
	@Override
	public void eat() {
		System.out.println("eat bone");
	}
	
	// 重写bark(),并调用父类的bark()
	@Override
	public void bark() {
		super.bark();
		System.out.println("woo!");
	}
	
	// 子类新增的方法
	public void play(){
		System.out.println("play!");
	}

	public static void main(String[] args) {
		// 注意构造,这里是用子类的构造函数构造
		Dog dog = new Dog();
		
		// 直接调用父类继承下来的sleep()
		dog.sleep();
		dog.eat();
		dog.bark();
		dog.play();
	}
}
/*
zzz
eat bone
~~~
woo!
play!
*/

(2)继承与重载(overload)、重写(override)

  • 重载
    在父类中重载的方法会很好地被子类继承下来,能和在子类中新重载的的同名方法一样被使用。

  • 重写
    可以重写父类中的方法(当然不可以重写子类中的方法,重写只能写继承来的方法)

class Animal {
	// 父类重载1
	void bark(int i)
	{
		System.out.println("Animal-bark(int)");
	}

	// 父类重载2	
	void bark(char c)
	{
		System.out.println("Animal-bark(char)");
	}
}

public class Dog extends Animal {
	// 子类重载3:与父类不同参数列表的bark
	void bark(String s)
	{
		System.out.println("Animal-bark(String)");
	}
	
	// 子类重写:重写bark(char c)
	@Override
	void bark(char c)
	{
		System.out.println("Dog-bark(char)");
	}
	
	public static void main(String[] args) {
		Dog dog = new Dog();
		dog.bark(0);
		dog.bark('a');
		dog.bark("S");
	}
}
/*
Animal-bark(int)
Dog-bark(char)
Animal-bark(String)
*/

三、继承与访问控制

  • 子类可以继承父类中访问权限设定为publicprotected"default"的成员变量和方法。
  • 但是不能继承访问权限为private的成员变量和方法。

四、向上造型和向下造型

1.理解

意思:

  • 类比基本数据类型
    int i = 100; long l = i;100用int类型表示就够了,用long表示更够了,表示的范围扩宽了(向上造型)。
    long l = 100; int i = (int)l;缩短了表示范围(向下造型)。
  • 继承和范围
    重写方法时就可以看出父类表示的“范围”比子类更大
    改写后的方法不能比被覆盖的方法有更严格的访问权限,改写后的方法不能比被覆盖的方法产生更多的异常。
  • 所以类:
    • 向上造型:扩大范围
      子类实例→父类类型(父类实例 = 子类实例),是自动转换。
      Father father = new Son();
    • 向下转型:缩小范围
      父类实例→子类类型(子类实例 = (子类)父类实例),是强制转换。
      Son son = (Son)father;

2.功能

(1)向上造型

  • 功能:主要是用来调用子类重写的方法和父类特有的方法。
  • 注意:
    • 调用同名变量,还是父类的成员变量值。(只跟实例的类型有关)。
    • 向上转型时,父类只能调用父类方法或者子类覆写后的方法,而子类中的单独方法则是无法调用的。当然在初始化时的构造函数可以调用。
class Animal {
	public String name = "Animal";
	
	public void bark() {
		System.out.println("Animal:???");
	}
}

public class Dog extends Animal {
	public String name = "Dog";
	
	public void bark() {
		System.out.println("Dog:woo!");
	}
	
	public static void main(String[] args) {
		// 向上造型
		Animal animal= new Dog();
		
		// 成员变量为父类自身
		System.out.println(animal.name);
		
		// 调用子类重写的方法
		animal.bark();
	}
}
/*
Animal
Dog:woo!
*/

易错点:容易误认为重写

class Animal {
	public void printSelf(Animal animal) {
		System.out.println("Animal itself");
	}
}

public class Dog extends Animal {
	public void printSelf(Dog dog) {
		System.out.println("Dog itself");
	}
	
	public static void main(String[] args) {
		// 向上造型
		Animal animal= new Dog();
		
		/**
		 * 无论你写什么,就是不调用子类的方法
		 * 因为这根本就不算是重写Overriding,注意参数不一致
		 */
		Dog dog = new Dog();
		Dog downDog = (Dog)animal;
		animal.printSelf(animal);
		animal.printSelf(dog);
		animal.printSelf((Dog)animal);
		animal.printSelf((Dog)dog);
		animal.printSelf((Animal)dog);
		animal.printSelf(downDog);
	}
}
/*
Animal itself
Animal itself
Animal itself
Animal itself
Animal itself
Animal itself
*/

(2)向下造型

  • 功能:主要是用来调用子类特有的方法和子类重写的方法。
  • 注意:
    • 向下造型只能转化由向上造型转化的实例
    • 调用的是子类的成员变量(只跟实例的类型有关)
    • 可以调用子类的特有的方法和重写的方法。
class Animal {
	public String name = "Animal";
	
	public void bark() {
		System.out.println("Animal:???");
	}
}

public class Dog extends Animal {
	public String name = "Dog";
	
	public void bark() {
		System.out.println("Dog:woo!");
	}
	
	public void eatBone() {
		System.out.println("eating bones!");
	}
	
	public static void main(String[] args) {
		// 向上造型
		Animal animal= new Dog();
		
		// 向下造型
		Dog dog = (Dog)(animal);
	
		// 调用的是子类的成员变量
		System.out.println(dog.name);
		
		// 调用子类的方法
		dog.eatBone();
	}
}
/*
Dog
eating bones!
*/

由非向上造型而转化的向下造型:虽然编译器不会提示,但是编译不会通过。(ClassCastException: Animal cannot be cast to Dog)
在这里插入图片描述

(3)如何访问成员变量

父类的成员变量

用向上造型,直接访问即可

子类的成员变量

  • 方式1:重写方法getXXX()
    可以通过重写方法getXXX()得到子类的成员变量值(所以不是白写get的啊)
  • 方式2:向下造型
    (更简单)如果不想重写get方法,也可以通过向下造型访问子类的成员变量。
class Animal {
	public String name = "Animal";
	
	public String getName() {
		return name;
	}
}

public class Dog extends Animal {
	public String name = "Dog";
	
	@Override
	public String getName() {
		return name;
	}
	
	public static void main(String[] args) {
		// 方式1:重写getName()
		Animal animal= new Dog();
		System.out.println(animal.getName());
		
		// 方式2:向下造型
		Dog dog = (Dog)animal;
		System.out.println(dog.name);
	}
}
/*
Dog
Dog
*/

3.用途举例

常用在泛型集合(声明为父类对象),集合内元素是子类的实例。这样就可以集合内存储的就是专用的重写方法。

import java.util.ArrayList;

class Animal {
	public String name = "Animal";
	
	public void bark() {
		System.out.println("Animal:???");
	}
}

class Dog extends Animal {
	public String name = "Dog";
	
	public void bark() {
		System.out.println("Dog:woo!");
	}
}

class Cat extends Animal {
	public String name = "Cat";
	
	public void bark() {
		System.out.println("Cat:miao~!");
	}
}

public class Zoo {
	public static void main(String[] args) {
		// 泛型集合,元素类型声明为Animal类
		ArrayList<Animal> zoo = new ArrayList<Animal>();
		
		// 添加一个Dog类实例
		zoo.add(new Dog());
		
		// 添加一个Cat类实例
		zoo.add(new Cat());
		
		// 让狗叫
		zoo.get(0).bark();
		
		// 让猫叫
		zoo.get(1).bark();
	}
}
/*
Dog:woo!
Cat:miao~!
*/

五、final关键字

七、final关键字

六、继承的构造顺序

  • 先执行父类的初始化的静态变量,再子类的初始化的静态变量
    PS:
    类内的main:先执行初始化的静态变量,再执行main内的语句
    类外的main:先执行main内的语句,再执行初始化的静态变量
  • 执行真正赋值成员变量(不管在构造函数前还是后)
  • 执行构造函数(先父类再子类)
/*类内的main*/
class Insect {
	int i = 9;
	int j;
	
	Insect() {
		print("i = " + i + ", j = " + j);
		j = 39;
	}
	
	static int x1 = print("static Insect.x1 initialized");
	
	static int print(String s) {
		System.out.println(s);
		return 47;
	}
}

class Beetle extends Insect {
	private int k = print("Beetle.k initialized");
	public Beetle() {
		System.out.println("Beetle constructor");
		System.out.println("k = " + k);
		System.out.println("j = " + j);
	}
	
	private static int x2 = print("static Beetle.x2 initialized");
	
	static int print(String s) {
		System.out.println(s);
		return 47;
	}
	
	public static void main(String[] args) {
		System.out.println("main begin...");
		Beetle b = new Beetle();
		System.out.println("main end...");
	}
}
/*
static Insect.x1 initialized
static Beetle.x2 initialized
main begin...
i = 9, j = 0
Beetle.k initialized
Beetle constructor
k = 47
j = 39
main end...
*/
/*类外的main*/
class Insect {
	int i = 9;
	int j;
	
	Insect() {
		print("i = " + i + ", j = " + j);
		j = 39;
	}
	
	static int x1 = print("static Insect.x1 initialized");
	
	static int print(String s) {
		System.out.println(s);
		return 47;
	}
}

class Beetle extends Insect {
	private int k = print("Beetle.k initialized");
	public Beetle() {
		System.out.println("Beetle constructor");
		System.out.println("k = " + k);
		System.out.println("j = " + j);
	}
	
	private static int x2 = print("static Beetle.x2 initialized");
	
	static int print(String s) {
		System.out.println(s);
		return 47;
	}
}

public class Demo {
	public static void main(String[] args) {
		System.out.println("main begin...");
		Beetle b = new Beetle();
		System.out.println("main end...");
	}
}
/*
main begin...
static Insect.x1 initialized
static Beetle.x2 initialized
i = 9, j = 0
Beetle.k initialized
Beetle constructor
k = 47
j = 39
main end...
*/

Reference:
Java 向上转型与向下转型

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值