1 包
大家能不能创建多个名称一样的类呢?当然是不可以的。但是包的出现,就解决了这个问题,包以分层方式保存并被明确的引入新的类定义,由此实现把类名空间划分为更多易管理的块的功能。包(package)是类的容器,用来保存划分的类名空间。例如,一个包允许你创建一个名为List的类,你可以把它保存在你自己的包中而不用考虑和其他地方的某个名为List的类相冲突。
这和Windows下的文件夹相似,很显然,在同一个文件夹下面,我们是不可以创建多个同名文件的,但是,如果这些同名文件位于不同的文件夹,那么就不会相互冲突,因为它们的路径是不一样的。
1.1 创建包
下面是package 声明的通用形式:
package pkg;
这里,pkg 是包名。例如,下面的声明创建了一个名为mypackage的包。
package mypackage;
Java用文件系统目录来存储包。例如,任何你声明的mypackage中的一部分的类的.class
文件被存储在一个mypackage目录中。记住这种情况是很重要的,目录名必须和包名严格匹配。
多个文件可以包含相同package声明。package声明仅仅指定了文件中定义的文件属于哪一个包。它不拒绝其他文件成为相同包的一部分。多数实际的包伸展到很多文件。
你可以创建包层次。为做到这点,只要将每个包名与它的上层包名用点号“.”分隔开就可以了。一个多级包的声明的通用形式如下:
package pkg1[.pkg2[.pkg3]];
包层次一定要在Java开发系统的文件系统中有所反映。例如,一个由下面语句定义的包:
package java.awt.image;
需要在你的Windows文件系统的java\awt\image (UNIX为java/awt/image,Macintosh为java:awt:image)中分别保存。一定要仔细选用包名。你不能在没有对保存类的目录重命名的情况下重命名一个包。
在Eclipse内建立好如上两个类后保存,找到工程所在目录,将会发现不光类文件以目录样式存在,编译后,class字节码文件也自动放在了相应的目录里。如图所示:
字节码文件也被自动放在了对应目录下:
1.2 使用包
当其他包里的类调用com.svse.bookstore内的类时,必须使用import关键字导入被引用类所在的包路径,导入分为只导入一个类及导入该包下所有类两种,如果不导包,那么就需要写类的完整路径。如下例所示:
/**该类位于demo包中*/ package demo;
/** 引入所需的类 */ import com.bookstore.Book; /** 导入包内所有类 */ import java.util.*;
public class Test { /** 导入所需类后,可以直接使用*/ Book book = new Book(); /** 如果没有导包,那么要写完整路径*/ com.bookstore.Publisher pub1 = new com.bookstore.Publisher(); /** 由于已经导入了util包,所以util包内的所有类均可直接使用*/ Vector v = new Vector(); List list = new ArrayList(); }
|
2 抽象类
看电影的时候经常会看到这么一类镜头:士兵们浴血沙场,一老将军重伤将要殒命,众人围上来痛哭,将军左手拉着女儿,右手拉一年轻小伙,说……,说什么?大家猜到了吧,要么就是“你们要替我报仇”,要么就是对小伙说“好好照顾她”!现在就来思考这两个问题,这是老者的两个愿望,对于老者来说他实现了这两个愿望了吗?没有,他希望他的晚辈能够实现;如果晚辈实现不了呢?可能当年的小伙临终前也会拉着某小伙说“替你爷爷报仇!”。
这是电影中我们常看到的镜头,当然,还有很多情况不是老者完成不了,而是他根本就不想完成。不管如何,总之一句话,老者没有实现这些愿望。
在Java中,有时我们也会遇到这种情况,例如我们设计一个图形类Shape(父类),其中有一个求出图形面积的方法getArea(),但是对于不同的图形(子类)来说,求面积的过程并不一样,这该怎么办呢?
可能有的同学会说,既然各种各样的图形求面积的方法不一样,那干脆父类不定义求面积方法,交由子类定义不就得了?其实,这不是一个好办法,我们已经学过继承了,我们知道,子类扩充的方法,父类引用子类的对象将无法使用,也就是说,假如Shape类引用了子类对象,那么该子类对象将无法调用getArea()方法,这就降低了程序的灵活性。那么怎么才能让父类不实现getArea()方法,而又能让Shape引用的子类对象能够使用getArea()方法呢?这就要用到抽象类来解决,我们需要在父类中把该方法写为抽象方法(虚方法),即不提供该方法的具体实现,交由子类实现。
要把一个方法写为抽象方法,需要在方法前加上abstract关键字,且方法没有方法体,如果某类中有方法是抽象方法,那么这个类是抽象类。
示例3.2:
package com.lesson3;
/** * 图形类 */ public abstract class Shape { public String shapeName;
/** 默认构造方法 */ public Shape() { }
/** 参数化构造方法 */ public Shape(String shapeName) { this.shapeName = shapeName; }
/** 抽象方法:求面积 */ public abstract double getArea();
/** 已实现方法:介绍 */ public void showShape() { System.out.println("正在操作的图形为:" + shapeName); } }
|
package com.lesson3;
/** * 长方形 */ public class Rectangle extends Shape {
public double width; public double length;
/** 默认构造方法 */ public Rectangle() { }
/** 参数化构造方法 */ public Rectangle(String shapeName, double width, double length) { super(shapeName); this.width = width; this.length = length; }
/** 实现父类抽象方法:求长方形面积 */ public double getArea() { System.out.println("长方形面积为:" + width * length); return width * length; } }
|
package com.lesson3;
/** * 圆环 */ public class Circle extends Shape { public double radius;// 半径 public static final double PI = 3.14;
/** 默认构造方法 */ public Circle() { }
/** 参数化构造方法 */ public Circle(String shapeName, double radius) { super(shapeName); this.radius = radius; }
/** 圆环面积 */ public double getArea() { System.out.println("圆环面积为:" + PI * radius * radius); return PI * radius * radius; } }
|
示例中,Shape类被定义为抽象类,其中有getArea()抽象方法,Rectangle类继承Shape类,实现了父类的抽象方法,Circle类也继承了Shape类,实现了getArea()方法。
如果子类没有实现父类的抽象方法,那么子类也是抽象的,类前要加abstract。
package com.lesson3;
/** * 三角形 */ public abstract class Triangle extends Shape { public double border; public double height;
/** 默认构造方法 */ public Triangle() { }
/** 参数化构造方法 */ public Triangle(String shapeName, double border, double height) { super(shapeName); this.border = border; this.height = height; } //抽象方法未实现 // public abstract double getArea(); }
|
抽象类要注意以下几点:
1、如果一个类是一个abstract类的子类,它必须具体实现父类的abstract 方法,否则子类也是抽象类。
2、如果一个类中含有abstract方法,那么这个类必须用abstract 来修饰(abstract 类也可以没有abstract方法)。
3、一个abstract 类只关心它的子类是否具有某种功能,并不关心功能的具体行为,功能的具体行为由子类负责实现,所以抽象类可以实现类型隐藏。
4、由于抽象类具有未实现的方法,所以不能创建对象,但可引用子类对象。
5、不能定义一个既是final的又是abstract的类,因为这是自相矛盾的,final类代表类不能被继承,而abstract类代表此抽象类需要子类继承来实现抽象方法。
现对以上类进行测试:
package com.lesson;
/** * 对以上图形进行测试 */ public class ShapeTest { public static void main(String[] args) { // 不能创建Shape抽象类与Triangle抽象类对象 // 长方形 Shape shape; shape = new Rectangle("长方形", 3, 4); shape.showShape(); shape.getArea(); // 圆环 shape = new Circle("圆环", 10); shape.showShape(); shape.getArea(); } }
|
运行结果如下:
3 接口
面向对象的特点主要概括为抽象性,继承性,封装性和多态性。
抽象性——指对现实世界中某一类实体或事件进行抽象,从中提取共同信息,找出共同规律,反过来又把它们集中在一个集合中,定义为所设计目标系统中的对象。
继承性——新的对象类由继承原有对象类的某些特性或全部特性而产生出来,派生类可以直接继承基类的共性,又允许派生类发展自己的个性。继承性简化了对新的对象类的设计。
封装性——是指对象的使用者通过预先定义的接口关联到某一对象的服务和数据时,无需知道这些服务是如何实现的。即用户使用对象时无需知道对象内部的运行细节。这样,以前所开发的系统中已使用的对象能够在新系统中重新采用,减少了新系统中分析,设计和编程的工作量,同时实现了信息隐藏。
多态性——是指不同类型的对象可以对相同的激励做出适当的不同响应的能力。多态性丰富了对象的内容,扩大了对象的适应性,改变了对象单一继承的关系。
大家知道,抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更为彻底,则可以提炼出一种更加特殊的“抽象类”——接口(interface),接口里不能包含普通方法,这和抽象类不同,在接口中,所有的方法都必须是抽象的。接口的概念就是建立在封装的基础之上的,而接口的继承、多态、以及接口在实际开发中的普遍应用,足以让其集Java面向对象特点为一体。
3.1 接口的概念
很多同学都有U盘,平时学习时把U盘往电脑上的USB接口一插,点开U盘所在文件夹即可对U盘里的内容进行查阅,非常方便,但是大家想过没有?我们的U盘品牌、容量各不相同,为什么电脑都能识别,并且都能提供一个或多个盘符,供用户操作呢?其实,这就是接口的应用了。
对于USB接口来说,它只需规定任何一个U盘过来的时候,必须实现读和写的功能。它不需要关注U盘是哪家厂商制造的,容量是多少,它需要关注的是,对于任意一款U盘来说,是不是满足USB接口定义的规范,如果是那么就能识别与操作它。由此可见,能正常使用的U盘,必然实现了USB接口制定的规范。
让规范和实现相分离,这就是使用接口的主要目的,更进一步说,有了接口,可以使软件系统中各组件之间面向接口耦合,这是一种松耦合的设计,为系统提供更好的可扩展性和可维护性。可以说,接口使得即插即用在程序里成为可能。因此接口定义的是多个类共同的公共行为规范,这些行为是与外部交流的通道,这就意味着接口里通常是定义一组公用的未实现的方法。
3.2 接口的用法
和之前学过的普通类与抽象类不同,接口使用的关键字不再是class(确切的说,接口只是一个规范,不再是一个有具体功能实现的类),定义一个接口需要使用interface关键字。接口的定义语法如下:
[public] interface 接口名 [extends 父接口名]{ 类型 常量字段名 = 值; … 返回类型 方法名(参数列表); … } |
可见在大多方面,接口的定义和类的定义有类似之处,但还是有如下特点:
1、接口的成员列表只能包含方法(其实是没有实现的抽象方法)及常量(public static final),而且常量必须在接口中定义的时候就初始化。
2、接口的所有成员默认都是public的,不再允许使用其他修饰符。
3、接口没有构造方法,也就是说,接口和抽象类一样不能创建自己的对象,但是它们均可引用实现类(或子类)对象。
4、接口可以继承接口,和类不同的是,接口继承其他接口的时候,是可以多继承的。
下例创建了一个USB接口及两个实现类。
示例3.3:
package com.usb; /** * usb接口
*/ public interface USB { /**读*/ public void read(); /**写*/ public void write(); } |
package com.usb; /** * 金士顿U盘
*/ public class Kingston implements USB { public void read() { System.out.println("金士顿U盘正在读取信息!"); } public void write() { System.out.println("金士顿U盘正在写入信息!"); } } |
package com.usb; /** * 苹果mp3
*/ public class Apple implements USB { public void read() { System.out.println("苹果mp3正在读取信息!"); } public void write() { System.out.println("苹果mp3正在写入信息!"); } } |
对于USB接口来说,其中有两个抽象方法read()和write(),注意这两个方法由于被放置在接口中,所以不需要再声明为abstract即已为抽象方法。Kingston类和Apple类分别实现了接口,实现了其中的抽象方法。为了生产这些产品,我们继续编写代码,创建一个简单的工厂类:
package com.svse.usb; /** * usb生产厂,可生产u盘、mp3等元件 */ public class UsbFactory { /** * 生产 */ public static USB createUsb(String type){ USB usb ; if("kingston".equals(type)){ usb = new Kingston(); }else if("apple".equals(type)){ usb = new Apple(); }else{ usb = null; //报告错误 } return usb; } } |
在UsbFactory类中,静态方法createUsb()通过接收并对比字符串参数type的值,来决定创建什么样的usb兼容元件,如果传递过来的是“kingston”,则创建金士顿u盘,如果传递的是“apple”,则创建苹果mp3,如果什么也没有传递,那么不创建任何对象,并报告错误。下面对这些类进行测试:
package com.usb; /** * 测试类
*/ public class Test { public static void main(String[] args) { //用工厂创建U盘,注意是面向接口而不是具体类的 USB usb ; usb = UsbFactory.createUsb("kingston"); usb.read(); usb.write(); //用工厂创建mp3 usb = UsbFactory.createUsb("apple"); usb.read(); usb.write(); } } |
在main方法中,使用UsbFactory工厂创建了u盘对象及mp3对象,均是由接口USB引用,我们的程序不再是面向某个或某些对象,而是是面向接口运转的。
运行程序,结果如下:
对于该示例还需要注意一点,就是所有的接口实现类,当实现接口中的抽象方法时,其修饰符必须是public的。
3.3 接口与抽象类
在Java中,继承体现的是“is a”的关系,而接口体现的则是“has a”聚合的含义。例如张三家昨晚被盗,今天被迫装了扇新门,这个门上安装了一个门铃,你去找张三玩,看到新门也许会说“你们家装了扇新防盗门呀!”,再仔细一看,也许会说“这门还有门铃呀!”。你肯定不会一看到门就说“你们家装了一扇门,还装了门铃”。为什么?因为门和门铃本身就是一体的,而我们在看待这个整体时,我们通常的思考是把它想为一个东西,而拥有其他的功能。所以,带门铃的门可以是一扇门(继承门),同时拥有铃的功能(实现铃接口);当然也可以夸张的把它想象为一个铃(继承铃)而同时又实现了门的防盗功能(实现门接口),但无论如何,它肯定不可能同时又是门又是铃。
Java语言在多数情况下是贴近于生活的,所以,在Java中,我们不可以多继承。但是,可以通过接口来解决这个问题。一个类可以实现多个接口,这是没有任何问题的。
下面通过示例,让门同时拥有报警功能及亮灯功能。
示例3.4:
package com.door; /** * 门 */ public class Door { /**门的作用*/ public void guard(){ System.out.println("门负责防护!"); } } |
package com.door; /** * 铃接口 * */ public interface Bell { /**铃的功能*/ public void alarm(); }
|
package com.door; /** * 电子壁灯 */ public interface Lamp { /**点灯*/ public void light(); } |
package com.door; /** * 张三家的大门 */ public class MyDoor extends Door implements Bell,Lamp{ /**实现alarm方法*/ public void alarm() { System.out.println("当撬门时发出警报!"); } /**实现light方法*/ public void light() { System.out.println("开门时壁灯自动亮起,关门后自动关闭!"); } } |
请关注这句话:“public class MyDoor extends Door implements Bell,Lamp”,MyDoor类继承了Door类,同时实现了Bell和Lamp两个接口。
接口与抽象类看似相仿,但通过以上讲解我们可以了解到,其实他们还是有很大的区别的,要掌握在一定情况下究竟是用抽象类还是用接口,必须要深刻理解他们两者的设计目的,其实,抽象类提供更多的是一种模板,子类继承父类、完善父类,体现的是一种对半成品加工改造完善为成品的过程,抽象类有助于代码复用。而接口体现的是一种规范,接口规定实现类必须要向外提供什么服务,接口有助于架构的分层。在一个程序中使用接口时,接口是多个模块间的耦合标准,在多个应用程序之间使用接口,接口是多个程序之间的通信标准,所以接口一旦定义,就不应该频繁变动,否则牵一发而动全身,可能系统的大部分类都要重写。
4 访问修饰符
访问修饰符是指在编写程序中的方法或属性的过程中,对方法或属性强加的限定符,访问修饰符可以决定在什么样的情况下你能够访问(使用)这些方法或属性,什么情况下不能访问。访问修饰符的出现,提高了Java程序的安全性,灵活使用访问修饰符,可以避免代码滥用、访问越界,有效控制程序结构。
在之前的学习过程中,我们创建的方法或属性,都是使用public(也曾经提到过private)修饰的,貌似我们也已经了解了public修饰符,其实,虽然前面的课程中使用了public修饰符,但从未深入讲解过有关访问修饰符的作用及其详细用法,在此专门列一节,对访问修饰符进行讲解。
4.1 类(接口)的访问修饰符
为了避免混淆,现先把类(包括接口)的定义时使用的访问修饰符列举如下:
·public,大家已经知道,public类必须定义在和类名同名的文件中,虽然一个文件内可以放置多个类,但并不推荐大家这么做,最好是一个文件内只定义一个类,且类名和文件名相同。如果一个类被定义为public的,那么这个类将能在任意地方使用,这就是之前我们定义的类一直都是public的原因。
·默认,如果一个类前没有加任何访问修饰符,那么这个类其实遵循了默认访问限制。也就是说,只有在同一个包中的类,才能使用该类。
示例3.5:
package com.pkg;
class Test { public Test(){ System.out.println("test"); } } |
Test类非常简单,类的访问权限为默认权限,下面进行测试:
package com.pkg;
public class Test1 { Test t1; public static void main(String[] args) { Test t2 ; } } |
编译代码,没有任何问题,这说明在同一个包下可以访问Test类。如果在其他包内的类访问Test类呢?
package com.pkg1;
import com.pkg.Test;
public class Test1 { Test t1; public static void main(String[] args) { Test t2 ; } } |
可以看到,其他包内的类是无法访问Test类的,Eclipse自动报告了错误。
4.2 方法及属性的访问修饰符
方法和属性的访问修饰符较多,按照访问的严格程度由低到高分为四个等级:
·private——私有权限
private修饰符可以修饰类的数据成员和方法成员,不可以修饰类本身和接口。private修饰符可以使被它修饰的对象不被类以外的任何东西接触到,换句话说,就是对外隐藏它所修饰的成员。从外面看来,类的内部好像根本不存在该private成员一样,无论是想通过类或实例对象进行直接访问,或者是试图以继承的方式来接触private成员,都是不可能的。因此,只有在类内可以自由访问该成员,一旦出了该类的范围,进入一个同包的继承子类、或者一个包外的继承子类,或者其它的类,该成员就变得不可触及了,专业术语称此现象为invisible。
在Java中,private天生就是final的,我们讨论继承和多态都是针对类的对外接口,包括puclic方法、protected方法和默认的friendly方法,而private是不被纳入考量的,从某种意义上,Java中的private方法纯粹是为方法而方法,是一种组织代码使之更清晰,更易维护的手段而已。由此可见,在继承中,从面向对象的角度考虑,基类中私有的东西对外界、对子类都是不可见的,子类根本不应该知道基类中任何私有的东西,于是子类继承、覆写基类方法或属性也应无从谈起,即使方法重名,那也应该仅仅看作一种巧合。
·默认修饰符(不用修饰符)——家庭权限
一个被修饰对象没有修饰符修饰它的时候,并不表明它真的没有修饰符,而说明它此时带了一个“默认修饰符”。默认修饰符可以修饰类的数据成员、方法成员以及类本身和接口。默认修饰符所规定的被访问范围比private稍大一些,达到了“包”一层的范围。
当一个默认修饰符修饰一个类成员时,表明该成员可以在同属一个包的某个类内里被自由访问,无论这个类是通过直接访问的方式或者是通过继承的方式来访问该成员。但是,一旦出了这个包,这个类成员就变得invisible了。也就是说,在非同一个包的某个类里面,获得一个该类的实例的话,通过这个实例是访问不到被默认修饰符修饰的该类的成员的;另一方面,如果非同一个包的某个类继承了该类,它同样无法访问该类(也就是其父类)的任何被默认修饰符修饰的成员。
当一个默认修饰符修饰一个类或接口的时候,表明它只可以被包内的其它类继承或(实例化)使用,而对于包外的任何类,它都是invisible的,因此就不能继承该类或者使用该类或者实现该接口。前一小节已经提到,要使一个类能被包外的类所继承或(实例化)来使用,一定要在class关键字前面加上public访问修饰符。
但是有一点例外,这就是在讲解接口部分内容时,我们提到的,接口中的方法不管你加不加public,其实都默认是public的。
·protected修饰符——家族权限
提到protected修饰符,就一定要想到继承的情况,因为protected关键字就是为了这种情况而创造的。protected修饰符可以修饰类的数据成员、方法成员,但不能修饰类本身和接口。protected修饰符修饰一个类的成员的时候,它所提供的被访问范围会比默认修饰符大一点点。大就大在下面的这种情况:非同一个包的某个子类继承了该类,将其作为父类的时候,虽然父类的默认修饰符修饰的成员对于子类不可见,但是此时被protected修饰符修饰的父类成员对子类来说却是可见的。
·public修饰符——完全开放
public修饰符可以修饰类的数据成员和方法成员,以及类本身和接口。public修饰符将提供最大的被访问范围。
一个被public修饰符修饰的类成员可以在任何其它类内被自由访问,不管该类是不是同一个包的,也不管该类是直接访问或者通过继承的方式来访问。
一个被public修饰符修饰的类或接口可以被同一个包或非同一个包的其它类自由继承和使用。
对于继承来说,一个覆盖其父类方法的子类方法的访问修饰符只能保持或扩大该方法的被访问范围,而不能使之缩小。对于接口来说,实现类中对接口方法的实现,必须是public访问修饰符修饰的。
针对上面讨论的四个等级,可以用表格表示如下:
关键字 | 同一个类中 | 同一个包中 | 派生类中 | 其他包中 |
private | √ |
|
|
|
默认(无修饰符) | √ | √ |
|
|
protected | √ | √ | √ |
|
public | √ | √ | √ | √ |
举例说明如下:
示例3.6:
package com.earth; /** * 基准类,在地球家庭包中 */ public class Father { private String _private = "_private"; String _friendly = "_friendly"; protected String _protected = "_protected"; public String _public = "_public";
public static void main(String[] args) { //在同一个类中进行测试 Father father = new Father(); System.out.println(father._private); System.out.println(father._friendly); System.out.println(father._protected); System.out.println(father._public); //测试结果:均可使用 } } |
首先创建一个基准类Father,位于com.svse.earth包中,后续其他类均对该类进行测试。在本类中进行测试时,显然会全部通过。下面同样在本包中创建另外一个类进行测试。
package com.svse.earth;
/** * 同一个包内进行测试,本类也位于地球家庭包 * 由于和Father在同一个包,所以除了Father的私有属性外,其他属性均可用 * @author svse * */ public class SamePkg { public static void main(String[] args) { // 在同一个包中进行测试 Father father = new Father(); System.out.println(father._private); // 这句将会报错 System.out.println(father._friendly); System.out.println(father._protected); System.out.println(father._public); // 测试结果:除private私有属性外,均可使用 } } |
同一个包,相当于是在一个家庭,所以家中的东西,个人私有的除外,均可使用。再来尝试不同的包:
package com.mars;
import com.earth.Father;
/** * 其他包中的类,位于火星家 * 要使用地球家的东西,首先要导包 * 由于不再一个包,由没有什么血缘关系,导致只能使用public的属性 */ public class OtherPkg { public static void main(String[] args) { // 在不同包中进行测试 Father father = new Father(); System.out.println(father._private); // 这句将会报错 System.out.println(father._friendly);// 这句将会报错 System.out.println(father._protected);// 这句将会报错 System.out.println(father._public); // 测试结果:除标示为public的属性外,均不能使用 } }
|
很显然,由于不是一个家庭,而且没有什么血缘关系,所以除了大家公开的内容外,其他一概不允许访问。如果位于不同包,但有血缘关系呢?仔细阅读代码,要小心了:
package com.mars;
import com.earth.Father;
/** * 其他包中的类,位于火星家 * 不过这个类很特殊,因为继承了Father,和Father有血缘关系 * 要使用地球家的东西,首先要导包 */ public class Son extends Father{ public static void main(String[] args) { // 在不同包中进行测试,首先创建父类对象 Father father = new Father();//或new Son();结果一样 System.out.println(father._private); // 这句将会报错 System.out.println(father._friendly);// 这句将会报错 System.out.println(father._protected);// 这句将会报错 System.out.println(father._public); //测试结果:除标示为public的属性外,均不能使用 //这是不是和讲解的相冲突呢?并非如此 //如果还是用父类的实例,无法体现出这层继承关系 //此时的引用也并不满足封装的要求 //protected修饰的属性,只给同一个包中的类和不是同一个包但是子类的类所访问。 //下面创建子类对象,继续访问,发现可以了 Son son = new Son(); System.out.println(son._private); // 这句将会报错 System.out.println(son._friendly);// 这句将会报错 System.out.println(son._protected); System.out.println(son._public); } }
|
如果在不同包,而且是父子关系,那么如果在子类中继续创建父类对象,或者通过父类引用子类对象,均不可使用除了public修饰外的其他访问修饰符,这是由于你既然继承了父类,那么在子类中创建父类对象是体现不出它们之间的关系的。protected修饰符正是为了解决父子之间的特殊关系而设计的一个修饰符,所以一旦我们创建子类对象,就可以访问父类中的受保护的属性和方法了。
上述示例中,仅仅对属性进行了测试,对于方法的访问修饰符来说,和属性是完全一样的,不再赘述。