枫神之路--Java 的继承机制


  几乎所有面向对象的高级程序设计语言都有继承机制,并且用这一非常简单而强大的机制实现代码重用,提高代码效率。在Java语言中,被继承的类成为超类(Superclass),与此对应的是其子类(Subclass);在c++中我们通常简称基类(Baseclass)和子类。但实质都是一样。

  接下来,我从几个方面谈谈我对Java类继承的理解:

  1. ​​​使用关键字extends
  2. 继承中成员访问控制
  3. 巧用super简化构造函数
  4. 超类引用能引用子类对象
  5. 继承中的重写机制--抽象类
  6.  final实现封装特性

Part one : 使用关键字 extends


  在c++中,继承有三种形式,分别是公有继承,私有继承,保护继承。这三种继承都会改变继承后子类对基类成员的访问可见性。除此之外,还有友元类与友元函数等。总体而言,c++的继承机制,说好听点,就是严谨细致;说不好听点,就显得十分烦琐,而且不太容易熟记(当然大神除外!)。所以Java继承中,继承方式只要extends一种,也就是只能使用关键字extends声明继承关系。需要注意一点:Java只允许单继承,但允许多层次继承和多宽度继承:一个子类只能有一个超类,一个超类可以有多个子类。

  • public 表示公有继承;
  • private 表示私有继承;
  • protected 表示保护继承;

   usingExtends1.java 使用 extends 

class Superclass { //超类
	String name;     //超类一个表名字的String类型成员
	public void showName()  //成员函数
	{
		System.out.println("My name is "+ name);
	}

}

class Subclass extends Superclass{  //子类继承超类的成员函数
	public void sayHi()          //子类新添加函数
	{
		System.out.println("Hello!");
	}
	
	public static void main(String args[])  //main函数
	{
		Subclass subcls =  new Subclass();  //创建子类对象,并调用超类和子类的成员函数
		subcls.sayHi();
		subcls.showName();
	}
}


// output:
Hello!
My name is null

解释:上面的例子是一个非常简单的关于Java继承的小程序,创建的子类Subclass成功继承了超类的成员变量和成员函数,并且实现了扩展,调用了自己新添加的函数sayHi(),与extends英文的意思不谋而合。以此小程序来展现Java的继承机制中extends的使用,但是问题来了,子类是如何访问超类中的成员变量name的,下面做了一些小的修改:

  usingExtends2.java 

class Superclass {
	String name;
	Superclass(String nam1){name = nam1;};   //定义了超类的构造函数
	public void showName(String nam)
	{
		System.out.println("My name is "+ nam);
	}

}

class Subclass extends Superclass{
	Subclass(String nam)   //子类的构造函数,super()的使用接下来详细介绍
	{
		super(nam);
	}
	public void sayHi()
	{
		System.out.println("Hello!");
	}
	
	public static void main(String args[])
	{
		Subclass subcls =  new Subclass("David Lee");
		subcls.sayHi();
		subcls.showName(subcls.name);   //子类对象成功引用了超类的类成员变量name!!!
	}
}

正如我们所看到的,子类对象subcls调用了超类的showName(),并且成功的访问了超类的成员变量name。初学Java的时候,这点需要注意:Java的继承机制虽然在形式上简化了,但是继承的访问权限限制,以及成员变量可见性还是要谙熟于心滴。上面的usingExtends2.java程序中,正是因为超类中name变量未用private修饰。使超类成员变量对于同包的子类(派生类)完全可见,并不是像c++中的,当类中成员变量未用限制修饰符修饰的时候,默认为private。下面,我整理一个表格理解Java这种继承机制的访问限制。


    Part two :  继承中成员访问限制       


          

                            

1.如果类成员没有显示的访问修饰符,那么其在所在包中可见,包外不可见。(本包public,外包private

2.如果类成员被private修饰,那么在同一类中可见。

3.如果类成员被protected修饰,那么扩充了默认访问范围,不仅对包内可见,对其全部子类(包括其他包的子类)都是可以访问的。

4.如果类成员被public修饰,那么该成员就是一种赤裸裸的存在,可被其他任何代码一览无余!!!

注意:修饰类只能是默认或public;而修饰类成员可用默认、private、protected、public。

关于protected的访问限制,大部分Java书籍对此介绍的都十分笼统,只是简单说明,同一包中成员和子类可以访问protected成员。可能会让不少初学者感到疑惑。对此,需要明确知道两点:

  • 基类的protected成员是包内可见的,并且对子类可见;

  • 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。

更详细的理解请参考 protected 关键字的真正内涵


 Part three :  巧用super简化构造函数      


3.1 fruit.java 未实例化创建子类对象。通过类名直接调用static函数。

package fruit;   

public class fruit {  //超类fruit类,包含一个default型成员变量name
	String name;
	protected String isName()  //成员函数
	{
		System.out.println("This is a/an " + name);
		return name;
	}
}

class banana extends fruit{  //子类banana
	
	static String color;    //static 成员变量color
	static private int genre = 1;
	public static void isColor()  //子类新扩展成员函数
	{
		System.out.println("My color is " + color + ".");
		System.out.println("There is only "+ genre + " genre of banana.");
	}
	
	public static void main(String args[])  //main()函数中没有实例化对象
	{
		banana.isColor();  //直接使用类名调用子类 static 函数 isColor();
	}
}

 

3.2 fruit.java 使用默认构造函数,利用super()实现带参数的构造函数,赋值构造函数!

package fruit;   

public class fruit {  //超类fruit类,包含一个default型成员变量name
	String name;
	
	fruit(){ name = "Fruit";}  //默认构造函数
	fruit(String nam){name=nam;}  //带参数构造函数
	fruit(fruit FU){ this.name = FU.name;}  //复制构造函数
	
	protected void isName()  //成员函数
	{
		System.out.println("This is a/an " + name);
	}
	
	public void showInfo()  
	{
		
		System.out.println("Info\nname: "+this.name);
		
	}
}

class banana extends fruit{  //子类banana
	
	banana(){           //子类默认构造函数,自动调用超类fruit的没有参数的构造函数。
		color = "Yellow";
		genre = 1;
		}
	banana(String nam, String color, int sum)  //子类带参数的构造函数
	{
		super(nam);
		this.color = color;
		genre = sum;
		
	}
	
	banana(banana ba)  //子类复制构造函数
		super(ba);
		this.color = ba.color;
		this.genre = ba.genre;
	}
	
	String color;    //扩展成员变量color 
	private int genre;  //扩展成员变量genre
	
	public  void rename(String nam)
	{
		super.name = nam;
	}
	/*public  void isColor()  //子类新扩展成员函数
	{
		System.out.println("My color is " + color + ".");
		System.out.println("There is/are only "+ genre + " genre of banana.");
	}*/
	
	public void showInfo() {
		super.showInfo();
		System.out.println("color: " + color);
		System.out.println("Genre: " + genre);
		System.out.println("That's it.");
	
	}
	public static void main(String args[])  
	{
		banana bana = new banana();
		banana bana_one = new banana("Argentina Banana","Light Yellow",2);
		banana bana_two = new banana(bana_one);
		bana.showInfo();
		bana_one.showInfo();
	}
}

运行结果:

需要注意的是:创建子类对象时,无论是默认的构造函数或带参数的构造函数,还是复制构造函数都是先调用超类的对应的构造函数,并且如果使用super()自动调用超类构造函数的话,必须是super()位于子类相应构造函数的第一行。super除了这一功能以外,还有一个功能就是在子类中rename()函数调用了super.name,以及子类showInfo()函数对超类的super.showInfo()进行了改写,这两个地方主要是用到了super.超类成员变量 or super.超类成员函数实现对超类可见成员的快速访问。

 


Part four:  超类引用能引用子类对象  


对此我们将改进的 fruit.java 的 main()做了一些修改。

public static void main(String args[])  
	{
		banana bana = new banana();
		banana bana_one = new banana("Argentina Banana","Light Yellow",2);
		banana bana_two = new banana(bana_one);
		
		fruit fu;  //创建一个超类对象
		fu = bana_two;  //将子类对象赋给超类对象
		fu.showInfo();  //fu调用的是子类的showInfo()还是超类的showInfo()呢?
	}

程序结果:

这一步通过超类引用子类对象的过程也称向上转型!!!


Part five:   继承中的重写机制--抽象类


如何理解抽象类:

1.抽象类需要使用关键字abstract声明

2.抽象类一定要被继承,所以不能使用private修饰成员。只能使用public&&protected

3.抽象类也可允许普通成员函数,只是比普通类多加入了抽象特性

4.抽象类不能实例化,必须先通过子类覆写抽象成员函数,向上转型

5.抽象类也能使用static 关键字

6.抽象类不能用final关键字修饰,因为抽象类必须要有子类,不热就毫无存在意义

7.抽象类中构造函数调用同普通类继承一样,先超类,后子类

下面创建一个fruit 抽象类:

package javaFileStreamPractice;



abstract class fruit {  //超类fruit类,包含一个default型成员变量name
	String name;
	
	fruit(){ name = "Fruit";}  //默认构造函数
	fruit(String nam){name=nam;System.out.println("Fruit's constructor has been called.");}  //带参数构造函数
	fruit(fruit FU){ this.name = FU.name;}  //复制构造函数
	
	protected void isName()  //成员函数
	{
		System.out.println("This is a/an " + name);
	}
	
	public abstract void rename(String nam);
	public abstract void showInfo();
}

class banana extends fruit{  //子类banana
	
	banana(){           //子类默认构造函数,自动调用超类fruit的没有参数的构造函数。
		color = "Yellow";
		genre = 1;
		}
	banana(String nam, String color, int sum)  //子类带参数的构造函数
	{
		super(nam);
		this.color = color;
		genre = sum;
		System.out.println("Banana's constructor has been called.");
		
	}
	
	banana(banana ba) {  //子类复制构造函数
		super(ba);
		this.color = ba.color;
		this.genre = ba.genre;
	}
	
	String color;    //扩展成员变量color 
	private int genre;  //扩展成员变量genre
	
	public  void rename(String nam)  //override
	{
		super.name = nam;
	}
	
	
	public void showInfo() {
		//super.showInfo();
		System.out.println("Info\nThis is "+name);
		System.out.println("color: " + color);
		System.out.println("Genre: " + genre);
		System.out.println("That's it.");
	
	}
	public static void main(String args[])  
	{
		fruit fu = new banana("Argentina Banana","Light Yellow", 25);  //创建子类对象,向上转型
		fu.showInfo();  //调用重写的抽象函数showInfo()
		fu.rename("Chinese Sweat Banana"); //调用重写的抽象函数rename()
		fu.showInfo();
	}
}

Part six:    final实现封装特性


final是Java中保留的关键字,意为“最后的,最终的”,从字面上理解就是,被final修饰的变量是最终的,被final修饰的成员函数是最终的,被final修饰的类也是最终的。简单解释一下这三个最终如何帮助类实现封装特性。

 

1、final修饰类中变量

无论属性是基本类型还是引用类型,final所起的作用都是变量里面存放的“值”不能变。

这个值,对于基本类型来说,变量里面放的就是实实在在的值,如1,“abc”等。而引用类型变量里面放的是个地址,所以用final修饰引用类型变量指的是它里面的地址不能变,并不是说这个地址所指向的对象或数组的内容不可以变,这个一定要注意。

final修饰属性,声明变量时可以不赋值,而且一旦赋值就不能被修改了。对final属性可以在三个地方赋值:声明时、初始化块中、构造方法中。总之一定要赋值。

2、final修饰类中的方法

作用:可访问,但不可重写!!!实现私密方法的封装!!!

3、final修饰类

作用:类不可以被继承。实现类的封装特性!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值