Java—面向对象(3)继承

Java—面向对象(3)继承

1.什么是继承

继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

关键字:extends

public class 子类 extends 父类

{

添加方法和域

}

例:一个学生类继承人类

class Person {
	String name;
	int age;
}

//学生通过extends继承Person,即Person为Student的父类
class Student extends Person{
	//学生类中的自己特有方法
	public void study(){
		System.out.println(name+"同学正在学习。。。。");
	}
}

继承的好处:

1、继承的出现提高了代码的复用性,提高软件开发效率。

2、继承的出现让类与类之间产生了关系,提供了多态的前提。

下面是关于继承应该注意的几个关键点:

  1. 和传统的理解不同,子类并不是父类的一个子集。实际上,一个子类通常比它的父类包含更多的信息和方法。
  2. 父类中的私有数据域在该类之外是不可访问的。因此,不能在子类中直接使用。但 是,如果父类中定义了公共的访问器 / 修改器,那么可以通过这些公共的访问器 / 修改器来访问和修改它们。
  3. 不是所有的 “是一种”(is-a) 关系都该用继承来建模。例如:正方形是一种矩形,但是不应该定义一个 Square 类来扩展 Rectangle 类,因为 widthheight 属性并不适合于正方形。应该定义一个继承自 CeometricObject 类的 Square 类,并为正方形的边定义一个 side 属性。
  4. 继承是用来为 “是一种” 关系( is-a) 建模的。不要仅仅为了重用方法这个原因而盲目地扩展一个类。例如:尽管 Person 类和 Tree 类可以共享类似高度和重量这样的通用特性,但是从 Person 类扩展出 Tree 类是毫无意义的。一个父类和它的子类之间必须存在 “是一种”(is-a) 关系。
  5. 某些程序设计语言是允许从几个类派生出一个子类的。这种能力称为多继承。但是在 Java 中是不允许多继承的。一个 Java 类只可能直接继承自一个父类。这种限制称为单一继承。如果使用 extends 关键字来定义一个子类,它只允许有一个父类。然而,多继承是可以通过接口来间接实现的。

2.单继承和多重继承

单继承:一个子类只有一个父类,且只有父子两代。

多重继承:一个子类继承父类,父类又有父类,继承的代数不限。

Java中一个父类可以有多个子类即不同类继承同一个类,但一个子类不能有多个父类(多继承)。原因:多继承虽然能使子类同时拥有多个父类的特征,但是其缺点也是很显著的,主要有两方面:

  1. 如果在一个子类继承的多个父类中拥有相同名字的实例变量,子类在引用该变量时将产生歧义,无法判断应该使用哪个父类的变量。
  2. 如果在一个子类继承的多个父类中拥有相同方法,子类中有没有覆盖该方法,那么调用该方法时将产生歧义,无法判断应该调用哪个父类的方法。

在这里插入图片描述

3.父子类中成员变量和方法特点

子类在继承父类会继承父类中所有非私有的成员,当父子类中成员变量不同时,都可以正常访问。

class Fu
{
	//Fu中的成员变量。
	int num = 5;
}
class Zi extends Fu
{
	//Zi中的成员变量
	int num2 = 6;
	void show()
	{
		//访问父类中的num
		System.out.println("Fu num="+num);
		//访问子类中的num2
		System.out.println("Zi num2="+num2);
	}
}
class Demo 
{
	public static void main(String[] args) 
	{
		Zi z = new Zi(); //创建子类对象
		z.show(); //调用子类中的show方法
	}
}
//代码说明:Fu类中的成员变量是非私有的,子类中可以直接访问,若Fu类中的成员变量私有了,子类是不能直接访问的。

当子父类中出现了同名成员变量时,在子类中若要访问父类中的成员变量,必须使用关键字super来完成。

class Fu
{
	//Fu中的成员变量。
	int num = 5;
}
class Zi extends Fu
{
	//Zi中的成员变量
	int num = 6;
	void show()
	{
		//子父类中出现了同名的成员变量时
		//在子类中需要访问父类中非私有成员变量时,需要使用super关键字
		//访问父类中的num
		System.out.println("Fu num="+super.num);
		//访问子类中的num2
		System.out.println("Zi num2="+this.num);
	}
}
class Demo5 
{
	public static void main(String[] args) 
	{
		Zi z = new Zi(); //创建子类对象
		z.show(); //调用子类中的show方法
	}
}

当在程序中通过对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。

class Fu{
	public void show(){
		System.out.println("Fu类中的show方法执行");
	}
}
class Zi extends Fu{
	public void show2(){
		System.out.println("Zi类中的show2方法执行");
	}
}
public  class Test{
	public static void main(String[] args) {
		Zi z = new Zi();
		z.show(); //子类中没有show方法,但是可以找到父类方法去执行
		z.show2();
	}
}

子类中出现与父类一模一样的方法时,会出现覆盖操作,也称为 (override) 重写、复写或者覆盖。

class Fu
{
	void show()
	{
		System.out.println("Fu show");
	}
}
class Zi extends Fu
{
	//子类复写了父类的show方法
	void show()
	{
		System.out.println("Zi show");
	}
}

覆盖的应用:

当子类需要父类的功能,而功能主体子类有自己特有内容时,可以复写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容。

重写需要注意的细节问题:

1、子类方法覆盖父类方法,必须要保证权限大于等于父类权限。

2、静态只能覆盖静态,或者被静态覆盖。

3、写法上稍微注意:必须一模一样:函数的返回值类型 函数名 参数列表都要一样。

4.子父类中构造函数特点

在创建子类对象时,父类的构造函数会先执行,因为子类中所有构造函数的第一行有默认的隐式super()语句,调用本类中的构造函数用this(实参列表)语句,调用父类中的构造函数用super(实参列表)。

为什么子类对象初始化都要访问父类中的构造函数?因为子类继承了父类的内容,所以创建对象时必须要先看父类是如何对内容进行初始化的。

当父类中没有空参数构造函数时,子类的构造函数必须有显示的super语句指定要访问的父类中的构造函数。

class Fu extends Object
{
	Fu()
	{
		//super();
		//显示初始化。
		System.out.println("fu constructor run..A..");
	}
	Fu(int x)
	{
		//显示初始化。
		System.out.println("fu constructor run..B.."+x);
	}
}
class Zi extends Fu
{
	Zi()
	{
		//super();
		System.out.println("zi constructor run..C..");
	}
	Zi(int x)
	{
		super();
		System.out.println("zi constructor run..D.."+x);
	}
}
class Demo
{
	public static void main(String[] args)
	{
		new Zi();
		new Zi(3);
	}
}

5.抽象类

抽象方法,只声明而未实现的方法我们称为抽象方法,所有的抽象方法都需要用abstract关键字声明,包含抽象方法的类也需要使用abstract关键字声明,抽象类和普通类相比,区别在于抽象类带有抽象方法,抽象方法可以只声明,而不需要方法体。

抽象类的特点:

1、抽象类和抽象方法都需要被abstract修饰。抽象方法一定要定义在抽象类中。

2、抽象类不可以创建实例,原因:调用抽象方法没有意义。

3、只有覆盖了抽象类中所有的抽象方法后,其子类才可以实例化。否则该子类还是一个抽象类。

分析事物时,发现了共性内容,就出现向上抽取。会有这样一种特殊情况,就是功能声明相同,但功能主体不同。那么这时也可以抽取,但只抽取方法声明,不抽取方法主体。那么此方法就是一个抽象方法。

描述狗:行为:吼叫。

描述狼:行为:吼叫。

狗和狼之间有共性,可以进行向上抽取。抽取它们的所属共性类型:犬科。由于狗和狼都具有吼叫功能,但是他们具体怎么吼叫却不一样。这时在描述犬科时,发现了有些功能(吼叫)不具体,这些不具体的功能,需要在类中标识出来,通过java中的关键字abstract(抽象)。

abstract class 犬科 
{
	static abstract void 吼叫();//抽象函数。需要abstract修饰,并分号;结束
}
class Dog extends 犬科
{
	void 吼叫()
	{
		System.out.println("汪汪汪汪");
	}
}
class Wolf extends 犬科
{
	void 吼叫()
	{
		System.out.println("嗷嗷嗷嗷");
	}
}

6.接口

当一个抽象类中的所有方法都是抽象方法时,那么这个抽象类就可以使用另外一种接口这种机制来体现。

使用interface来定义一个接口:

[修饰符] interface 接口名 [extends 父接口名列表]{

[public] [static] [final] 常量;
[public] [abstract] 方法;
}

接口中成员的特点:

1、接口中可以定义变量,但是变量必须有固定的修饰符修饰,public static final 所以接口中的变量也称之为常量。

2、接口中可以定义方法,方法也有固定的修饰符,public abstract

3、接口中的成员都是公共的。

4、接口不可以创建对象。

5、子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。

interface Demo//定义一个名称为Demo的接口。
{
	public static final int NUM = 3;
	public abstract void show1();
	public abstract void show2();
}
//定义子类去覆盖接口中的方法。子类必须和接口产生关系,类与类的关系是继承,类与接口之间的关系是 实现。通过 关键字 implements
class DemoImpl implements Demo//子类实现Demo接口。
{
	//重写接口中的方法。
	public void show1(){}
	public void show2(){}
}

接口多实现:

interface A
{
	void show1();
}
interface B
{
	void show2();
}
class C implements A,B// 多实现。同时实现多个接口。
{
	public void show1(){}
	public void show2(){}
}

接口的多继承

多个接口之间可以使用extends进行继承。

interface A{
	void show();
}
interface B{
	void show1();
}
interface C{
	void show2();
}
interface D extends A,B,C{
	void show3();
}

在开发中如果多个接口中存在相同方法,这时若有个类实现了这些接口,那么就要实现接口中的方法,由于接口中的方法是抽象方法,子类实现后也不会发生调用的不确定性。

猜你喜欢

转载自blog.csdn.net/m0_49297152/article/details/107570480