目录

7.1 类的封装

代码7_1

结果

代码7_2

结果

代码7-3

结果

代码7_4

结果

7.2 类的继承

7.2.1 extends 关键字

代码7_5

结果

7.2.2 方法的重写

1.重写的实现

代码7-6

结果

代码7-7

结果

7.2.3 所有类的父类——Object类

代码7-8

结果

代码7-9

结果

7.3 类的多态

7.3.1 方法的重载

代码7-10

结果

7.3.2 向上转型

代码7_11

7.3.3 向下转型

代码7-12

7.3.4 instanceof关键字

代码7-13

7.4 抽象类与接口

7.4.1 抽象类与抽象方法

代码7-14

7.4.2接口的声明及实现

代码7-15

7.4.3 多重继承

代码7-16

7.4.4 区分抽象类与接口

7.5 访问控制

7.5.1 访问控制符

7.5.2 Java 类包

7.5.3 final 关键字

代码7-17

代码7_18

代码7_19

代码7_20

7.6 内部类

7.6.1成员内部类

代码H7_21

代码7_22

代码7_23

7.6.2 局部内部类

代码7_24

7.6.3 匿名内部类

代码7_25

7.6.4 静态内部类

代码7_26

7.6.5 内部类的继承

代码7_27

7.7小结


7.1 类的封装

封装是面向对象编程的核心思想,将对象的属性和行为封装起来,其载体就是类

代码7_1

public class H7_1 {//创建类public static void main(String[] args) {//主函数String cookName="Tom Cruise";                          //厨师的名字叫 Tom Cruise System.out.println("**请让厨师为我做一份香辣肉丝。***");//输出结果System.out.println(cookName +"切葱花");              //输出切葱花"System.out.println(cookName +"洗蔬菜");          //输出洗蔬菜"System.out.println(cookName+"开始烹饪"+"香辣肉丝");//输出开始烹饪"+"香辣肉丝System.out.println("**请问厨师叫什么名字?***"); //输出**请问厨师叫什么名字?***System.out.println(cookName);//调用cookNameSystem.out.println("**请让厨师给我切一点葱花。***"); //输出**请让厨师给我切一点葱花。***System.out.println(cookName +"切葱花");      //输出切葱花}
}

结果

System.out.println("**请让厨师给我切一点葱花。***");

System.out.println(cookName+"搅鸡蛋");   //被乱改之后

System.out.println("**请让厨师为我做一份清蒸鱼。***");

System.out.println(cookName+"你是我的小呀小苹果~");//被乱改之后

代码7_2

public class H7_2 {//创建类public static void main(String[] args) {//主函数Cook1 cook = new Cook1();              // 创建厨师类的对象System.out.println("**请让厨师为我做一份香辣肉丝。***");  //输出cook.cooking("香辣肉丝");                 // 厨师烹饪香辣肉丝System.out.println("**你们的厨师叫什么名字?***");//输出**你们的厨师叫什么名字?***System.out.println(cook.name);      // 厨师回答自己的名字System.out.println("**请让厨师给我切一点葱花。***");//输出**请让厨师给我切一点葱花。***cook.cutOnion();      // 厨师去切葱花}}class Cook1 {   //创建Cook1类String name;// 厨师的名字public Cook1() {            //普通类this.name = "Tom Cruise"; // 厨师的名字叫Tom Cruise}void cutOnion() {       // 厨师切葱花System.out.println(name + "切葱花");  //输出厨师切葱花}void washVegetavles() {    // 厨师洗蔬菜System.out.println(name + "洗蔬菜");  //输出厨师洗蔬菜}void cooking(String dish) {// 厨师烹饪顾客点的菜washVegetavles();     //洗蔬菜cutOnion();             //切洋葱System.out.println(name + "开始烹饪" + dish); //输出name + "开始烹饪" + dis
}}

结果

将厨师单独封装成一个类, 将厨师的工作定义成厨师类的行为,当我们想让厨师做菜,只能通过调用对象成员方法的方式实现,而我们却不知道这个方法到底是怎么写的,所以就无法随意修改了。餐馆没有义务告诉我们厨师的任何信息,并且厨师也不会随意受我们差遣,所以说厨师有些属性和行为是不予公开的 。

代码7-3

public class H7_3 {public static void main(String[] args) {Cook2 cook = new Cook2();// 创建厨师类的对象System.out.println("**请让厨师为我做一份香辣肉丝。***");cook.cooking("香辣肉丝");// 厨师烹饪香辣肉丝System.out.println("**你们的厨师叫什么名字?***");System.out.println(cook.name);// 厨师回答自己的名字System.out.println("**请让厨师给我切一点葱花。***");cook.cutOnion();// 厨师去切葱花}
}class Cook2 {private String name;//厨师的名字public Cook2() {this.name = "Tom Cruise";//厨师的名字叫Tom Cruise}private void cutOnion() {//厨师切葱花System.out.println(name + "切葱花");}private void washVegetavles() {//厨师洗蔬菜System.out.println(name + "洗蔬菜");}void cooking(String dish) {//厨师烹饪顾客点的菜washVegetavles();cutOnion();System.out.println(name + "开始烹饪" + dish);}}

结果

其实按照日常生活场景来讲,顾客去餐馆吃饭,下单的是服务员,上菜的也是服务员, 顾客跟本没有接触厨师的机会,所以厨师这个角色是对顾客隐藏起来的,被封装在餐馆的类当中。

代码7_4

public class H7_4 {//主函数private Cook2 cook = new Cook2();// 餐厅封装的厨师类public void takeOrder(String dish) {// 下单cook.cooking(dish);// 通知厨师做菜System.out.println("您的菜好了,请慢用。");//输出"您的菜好了,请慢用。"}public String saySorry() {// 拒绝顾客请求return "抱歉,餐厅不提供此项服务。";//输出"抱歉,餐厅不提供此项服务。"}public static void main(String[] args) {//主函数H7_4 water = new H7_4();// 创建餐厅对象,为顾客提供服务System.out.println("**请让厨师为我做一份香辣肉丝。***");//输出"**请让厨师为我做一份香辣肉丝。***"water.takeOrder("香辣肉丝");// 服务员给顾客下单System.out.println("**你们的厨师叫什么名字?***");//输出"**你们的厨师叫什么名字?***"System.out.println(water.saySorry());// 服务员给顾客善意的答复System.out.println("**请让厨师给我切一点葱花。***");//输出"**请让厨师给我切一点葱花。***"System.out.println(water.saySorry());//服务员给善意的答复顾客
}
}

结果

从这个例子我们就能看出,作为顾客,我始终是和服务员进行交流,再由服务员与厨师进行交流整个过程中,顾客与厨师是完全没有交集的。作为顾客,我不知道我品尝的美食是由哪位厨师用何种方法烹任出来的,这种编程模式,就是封装。
        将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。

7.2 类的继承

继承在面向对 象开发思想中是一个非常重要的概念,它使整个程序架构具有-定的弹性,在程
序中复用已经定义完善的类不仅可以减少软件开发周期,还可以提高软件的可维护性和可扩展性。资源包本节将详细讲解类的继承。在第6章中曾简要介绍过继承,其基本思想是基于某个父类的扩展,制定出一个新的子类,子类可以继承承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父空中的某些方法。例如,平行四边形是特殊的四边形,可以说平行四边形类继承了四边形类,这时江边形类将所有四边形具有的属性和方法都保留下来,并基于四边形类展了一些新的平行四边形类特有的属性和方法。

7.2.1 extends 关键字

在Java中,让一个类继承另一个类,用extends关键字,语法如下:

child extends parents

这里child这个类作为子类继承了parents 这个类,并继承parents中的属性和方法。
举一个简单的例子:每个人都用过计算机,最常见的计算机就是台式机。后来随着科技的发展,计算机变得越来越小,台式机改良成了可移动的笔记本电脑,笔记本电脑又改良成了更轻薄的平板电脑。我们可以把普通计算机看成一个类,那么笔记本电脑和平板电脑都是这个类衔生出的子类。

注意:Java中的类只支持单继承,即一个子类只能继承一个父类。

代码7_5

class Computer {// 父类:电脑String screen="液晶显示屏";    //定义初值void startup() {       //返回参数System.out.println("电脑正在开机,请等待...");//输出电脑正在开机,请等待...}}
public class H7_5 extends Computer {//父类:电脑String battery = "5000毫安电池";// 子类独有的属性public static void main(String[] args) {//主函数Computer pc = new Computer();// 电脑类System.out.println("computer的屏幕是:" + pc.screen);//输出"computer的屏幕是:" + pc.screenpc.startup();//输出startupH7_5 ipad = new H7_5();// 平板电脑类System.out.println("pad的屏幕是:" + ipad.screen);// 子类可以直接使用父类属性System.out.println("pad的电池是:" + ipad.battery);// 子类独有的属性ipad.startup();// 子类可以直接使用父类方法}}

结果

从这个结果可以看出,Pad类继承了Computer类之后,虽然没有定义任何成员方法,但仍可以调用父类的方法。这个方法是从父类那继承过来的。

7.2.2 方法的重写

1.重写的实现

继承并不只是扩展父类的功能,还可以重写父类的成员方法。重写(还可以称为覆盖)就是在子类中将父类的成员方法的名称保留,重新编写成员方法的实现内容,更改成员方法的存储权限,或是修改成员方法的返回值类型(重写父类成员方法的返回值类型是基于J2SE 5.0版本以上编译器提供的新功能)。在继承中还有种特殊的重写方式, 子类与父类的成员方法返回值、 方法名称、参数类型及个教完全相同,唯一不同的是方法实现内容,这种特殊重写方式被称为重构。

注意:当重写父类方法时,修改方法的修饰权限只能从小的范围到大的范围改变。

子类重写父类的方法还可以修改方法的返回值类型,但这只是在J2SE 5.0以上的版本中支持的面功能,但这种重写方式需要遵循一个原则,即重写的返回值类型必须是父类中同一方法返回值关主的子类。

代码7-6

class Computer2 {// 父类:电脑void showPicture() {//显示图片System.out.println("鼠标点击");//输出鼠标点击}
}
public class H7_6 extends Computer2{//子类继承父类void showPicture() {//显示图片System.out.println("手指点击触摸屏");//输出手指点击触摸屏}public static void main(String[] args) {//主函数Computer2 pc = new Computer2();// 电脑类System.out.print("pc打开图片:");//输出pc打开图片:pc.showPicture();// 调用方法H7_6 ipad = new H7_6();// 平板电脑类System.out.print("ipad打开图片:");//输出ipad打开图片:ipad.showPicture();// 重写父类方法Computer2 computerpad = new H7_6();// 父类声明,子类实现System.out.print("computerpad打开图片:");//输出computerpad打开图片:computerpad.showPicture();// 调用父类方法,实现子类重写的逻辑}
}

结果

从这个结果我们可以看出,虽然子类调用了父类的方法,但实现的是子类重写后的逻辑, 而不是父类原有的逻辑。如果父类声明的对象是由子类实例化的,那么这个对象所调用的方法也是被子类重写过的。

注意:在Java语言中,一个类只可以有一个父类!

2.super 关键字
如果子类重写了父类的方法,就再也无法调用到父类的方法了吗?如果想在子类的方法中实现父类原有的方法怎么办?为了解决这种需求,Java 提供了super 关键字。
super关键字的使用方法与this关键字类似。this 关键字代表本类对象,super 关键宇代表父类对象,使用方法如下:

super.property; //调用父类的属性

super.method(); //调用父类的方法

代码7-7

class Computer3 {// 父类:电脑String  sayHello(){//定义一个方法return "欢迎使用";//返回一个字符串}
}
public class H7_7 extends Computer3{//子类继承父类String sayHello() {// 子类重写父类方法return super.sayHello() + "平板电脑";// 调用父类方法,在其结果后添加字符串}public static void main(String[] args) {//主函数Computer3 pc = new Computer3();// 电脑类System.out.println(pc.sayHello());//输出pc.sayHello()H7_7 ipad = new H7_7();// 平板电脑类System.out.println(ipad.sayHello());//输出ipad.sayHello()}
}

结果

注意:如果子类构造方法中使用类似super()的构造方法,其他初始化代码只能写在super()之后,不能写在前面,否则会报错。

7.2.3 所有类的父类——Object类

在开始学习使用class关键字定义类时,就应用了继承原理,因为在Java中,所有的类都直接或间接继承了java.lang.Object 类。Object 类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。当创建一一个类时,总是在继承,除非某个类已经指定要从其他类继承,否则它就是从java.lang.Object类继承而来的,可见Java中的每个类都源于java.lang.Object 类,如String、Integer 等类都是继承于Object类;除此之外自定义的类也都继承于Object类。由于所有类都是Object子类,所以在定义类时,省略了extends Object关键字,如图7.10所示便描述了这一原则。
在Object类中主要包括clone(、finalize(、equalsO、toString0等方法,其中常用的两个方法为equals()和 toString0://blog.csdn.net/weixin_6557方法。由于所有的类都是Object 类的子类,所以任何类都可以重写Object类中的方法。

注意:Object类中的getClass()、notify()、notifyAll()、wait()等方法不能被重写,因为这些方法被定义为final类型。

Object的重要方法:

1. getClass( )方法
getClass0方法是Object类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName0方法可以取得类的名称。
getClass( ) . getName( );
可以将getClass0方法与toString0方法联合使用。

2. toString( )方法
toString0方法的功能是将一一个对象返回为字符串形式, 它会返回一 个String 实例。在实际的应用中通常重写toString( )方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString0方法。的重要方法。

代码7-8

public class H7_8 {//创建类public String toString() {                       //重写toString()方法return "在" + getClass().getName() + "类中重写toString()方法";//输出"在" + getClass().getName() + "类中重写toString()方法}public static void main(String[] args) {//主函数System.out.println(new H7_8());   //打印本类对象}
}

结果

在本实例中重写父类Object的toString( )方法,在子类的toString( )方法中使用Object 类中曲getClass( )方法获取当前运行的类名,定义一段输出字符串,当用户打印ObjectInstance类对象时,将自动调用toString( )方法。

3. equals()方法
        前面章节曾讲解过equals()方法,当时是比较“==”运算符与equalsl()方法,说明“==”比较的是两个对象的引用是否相等,而equals0方法比较的是两个对象的实际内容。

代码7-9

class V { // 自定义类V
}
public class H7_9 {//创建类public static void main(String[] args) {//主函数String s1 = "123"; // 实例化两个对象,内容相同String s2 = "123"; // 实例化两个对象,内容相同System.out.println(s1.equals(s2)); // 使用equals()方法调用V v1 = new V(); // 实例化两个V类对象V v2 = new V();// 实例化两个V类对象System.out.println(v1.equals(v2)); // 使用equals()方法比较v1与v2对象}
}

结果

7.3 类的多态

多态意为一个名字可具有多种语义,在程序设计语言中,多态性是指“一种定义,多种实现”例如,运算符“+”作用于两个整型量时是求和,而作用于两个字符型量时则是将其连接在一起。利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。类的多态性可以从两方面体现:一是方法的重载,二是类的上下转型。

7.3.1 方法的重载

在第6章中曾学习过构造方法,知道构造方法的名称由类名决定,所以构造方法只有一一个名称,但如果希望以不同的方式来实例化对象,就需要使用多个构造方法来完成。由于这些构造方法都需要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到“方法重载”。虽然方法重载起源于构造方法,但是它也可以应用到其他方法中。本节将讲述方法的重载。
东法的重获就是在网个类中允许网时存在个以 上的同名方法,只要这些方法的参数个数或类型不同即可。

代码7-10

public class H7_10 {//创建类// 定义一个方法public static int add(int a) {// 定义一个方法return a;//定义a}public static int add(int a, int b) {// 定义与第一个方法参数个数不同的方法return a + b;//定义a+b}public static double add(double a, double b) {// 定义与第一个方法相同名称、参数类型不同的方法return a + b;//定义a+b}public static int add(int a, double b) {// 定义一个成员方法return (int) (a + b);//定义a+b}public static int add(double a, int b) {// 这个方法与前一个方法参数次序不同return (int) (a + b);//定义a+b}public static int add(int... a) {// 定义不定长参数int s = 0;//定义s初值for (int i = 0; i < a.length; i++) {// 根据参数个数循环操作s += a[i];// 将每个参数的值相加}return s;// 将计算结果返回}public static void main(String args[]) {//主方法System.out.println("调用add(int)方法:" + add(1));//输出"调用add(int)方法:" + add(1)System.out.println("调用add(int,int)方法:" + add(1, 2));//输出"调用add(int,int)方法:" + add(1, 2)System.out.println("调用add(double,double)方法:" + add(2.1, 3.3));//输出"调用add(double,double)方法:" + add(2.1, 3.3)System.out.println("调用add(int a, double b)方法:" + add(1, 3.3));//输出"调用add(int a, double b)方法:" + add(1, 3.3)System.out.println("调用add(double a, int b) 方法:" + add(2.1, 3));//输出"调用add(double a, int b) 方法:" + add(2.1, 3)System.out.println("调用add(int... a)不定长参数方法:"+ add(1, 2, 3, 4, 5, 6, 7, 8, 9));//输出"调用add(int... a)不定长参数方法:"+ add(1, 2, 3, 4, 5, 6, 7, 8, 9)System.out.println("调用add(int... a)不定长参数方法:" + add(2, 3, 4));//输出"调用add(int... a)不定长参数方法:" + add(2, 3, 4)}}

结果

注意:虽然在方法重载中可以使两个方法的返回类型不同,但只有返回类型不同并不足以区分两个方法的重载,还需要通过参数的个数以及参数的类型来设置。

7.3.2 向上转型

对象类型的转换在Java编程中经常遇到,主要包括向上转型与向下转型操作。本节将首先介绍向上转型。
        因为平行四边形是特殊的四边形,也就是说平行四边形是四边形的一种,那么就可以将平行四边形对象看作是一一个四边形对象。例如,鸡是家禽的一种, 而家禽是动物中的一类,那么也可以将鸡对象看作是一个动物对象。

代码7_11

class Quadrangle { // 四边形类public static void draw(Quadrangle q) { // 四边形类中的方法// SomeSentence}
}
public class H7_11 extends Quadrangle{//子类继承父类public static void main(String args[]) {//主函数H7_11 p = new H7_11(); // 实例化平行四边形类对象引用draw(p); // 调用父类方法}}

平行四边形类继承了四边形类,四边形类存在一一个 draw0方法,它的参数是Quadranol(四边形类)类型,而在平行四边形类的主方法中调用draw0时给予的参数类型却是Prllelogram (平行四边形类)类型的。这里一直在强调- 一个问题, 就是平行四边形也是种类型的四边形, 所以可以物平行四边形类的对象看作是个四边形类的对象, 这就相当于“Quadrangleobj = new Parallelogram0;”,就是把子类对象赋值给父类类型的变量,这种技术被称为“向上转型”。试想一.下正方形类对象可以作为draw0方法的参数,梯形类对象同样也可以作为draw0方法的参数,如果在四边形类的draw0方法中根据不同的图形对象设置不同的处理,就可以做到在父类中定义一个方法完成各个子类的功能,这样可以使同一份代码毫无差别地运用到不同类型之上,这就是多态机制的基本思想。平行四边形类继承了四边形类,平行四边形类 与四边承图都是将顶级类设置在页面的顶部,然后逐渐向下,所以将子类对形类的关系对象看作是父类对象被称为“向上转型”。由于向上转型是从一个较具体的类到较抽象的类的转换,所以它总是安全的,如可以说平行四边形是特殊的四边形,但不能说四边形是平行四边形。

结果

7.3.3 向下转型

通过向上转型可以推理出向下转型是将较抽象类转换为较具体的类。这样的转型通常会出现问题,例如,不能说四边形是平行四边形的一种、 所有的鸟都是鸽子,因为这非常不合乎逻辑。可以说子类对象总是父类的一个实例, 但父类对象不一定是子类的实例。

代码7-12

class Quadrangle {//父类,四边形类public static void draw(Quadrangle q) {//四边形类中的办法// SomeSentence}
}
public class H7_12 extends Quadrangle {//平行四边形类,继承了四边形类public static void main(String args[]) {//主函数draw(new H7_12());// 将平行四边形类对象看作是四边形对象,称为向上转型操作Quadrangle q = new H7_12();// 将父类对象赋予子类对象H7_12 p = (H7_12) q; // 将父类对象赋予子类对象Parallelogram p = (Parallelogram) q;//将父类对象赋予子类对象,并强制转换为子类型}
}

结果

如果将父类对象直接赋予子类,会发生编译器错误,因为父类对象不一定是子类的实例。例如,一个四边形不一定就是指平行四边形,它也许是梯形,也许是正方形,也许是其他带有四条边的不规则图形。
        越是具体的对象具有的特性越多,越抽象的对象具有的特性越少。在做向下转型操作时,将特性范围小的对象转换为特性范围大的对象肯定会出现问题,所以这时需要告知编译器这个四边形就是平行四边形。将父类对象强制转换为某个子类对象,这种方式称为显式类型转换。

7.3.4 instanceof关键字

当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生Class,CastException异常,所以在执行向下转型之前需要养成一个 良好的习惯,就是判断父类对象是否子类对象的实例。这个判断通常使用instanceof操作符来完成。可以使用instanceof操作符判断是口一个类实现了某个接口,也可以用它来判断一个实例对象是否属于个类。
instanceof的语法格式如下:                                                                                                              myobject instanceof ExampleClass
myobject: 某类的对象引用。
ExampleClass: 某个类。

使用instanceof操作符的表达式返回值为布尔值。如果返回值为true, 说明myobject对象对ExampleClas的实例对象;如果返回值为false, 说明myobjet对象不是ExmpleClas的实例对象。

注意:instanceof是java语言的关键字,在java语言中的关键字都为小写。

代码7-13

class Square extends Quadrangle {//Square继承Quadrangle// SomeSentence}class Anything {//普通类// SomeSentence}
public class H7_13 extends Quadrangle {//H7_13继承Quadranglepublic static void main(String args[]) {//主方法Quadrangle q = new Quadrangle(); // 实例化父类对象// 判断父类对象是否为H7_13子类的一个实例if (q instanceof H7_13) {// 判断父类对象是否为H7_13子类的一个实例H7_13 p = (H7_13) q; // 进行向下转型操作}if (q instanceof Square) {        // 判断父类对象是否为H7_13子类的一个实例Square s = (Square) q; // 进行向下转型操作}// 由于q对象不为Anything类的对象,所以这条语句是错误的// System.out.println(q instanceof Anything);}
}

结果

符与向下转型操作结合使用。在程序中定义了两个子类,即平行四边形类和正方形类,这两个类分别继承四边形类。在主方法中首先创建四边形类对象,然后使用instanceof操作符判断四边形类对象是否为平行四边形类的一 个实例,是否为正方形类的一 个实例,如果判断结果为true,将进行向下转型操作。

7.4 抽象类与接口

通常可以说四边形具有4条边,或者更具体点,平行四边形是具有对边平行且相等特性的特殊四边形,等腰三角形是其中两条边相等的三角形,这些描述都是合乎情理的,但对于图形对象却不能使用具体的语言进行描述,它有几条边,究竟是什么图形,没有人能说清楚,这种类在Java中被定义为抽象类。

7.4.1 抽象类与抽象方法

在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是在上方的类越抽象,如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中,并不需要将父类初始化对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化对象,因为图形类不能抽象出任何一种具体图形,但它的子类却可以。
Java 中定义抽象类时,需要使用 abstract 关键字,其语法如下:

[权限修饰符] abstract class 类名{
类体

使用absrnat关键字定义的类称为抽象类,而使用abtect关键字定义的方法称为抽象方法,抽象方法的定义语法如下:
从上面的语法可以看出,抽象方法是直接以分号结尾的,它没有方法体,抽象方法本身没有任何意义,除非它被重写, 而承载这 个抽象方法的抽象类必须被继承, 实际上,抽象类除了被继承之外没有任何意义。

继承抽象类的所有子类都需要将抽象类中的抽象方法进行覆盖,这样在多态机制中,就可以将父类修改为抽象类,将draw(方法设置为抽象方法,然后每个子类都重写这个方法来处理。

注意:构造方法不能定义为抽象方法。

代码7-14

public abstract class Market {//创建类public String name;//商场名称public String goods;   //商品名称public abstract void shop();//抽象方法,用来输出信息
}
public class TaobaoMarket extends Market{//TaobaoMarket继承Market@Overridepublic void shop() {//购物System.out.println(name+"网购"+goods); //输出网购+goods}
}
public class WallMarket extends Market{//WallMarket继承Market@Overridepublic void shop() {//购物System.out.println(name+"实体店购买"+goods);//输出实体店购买+goods}
}
public class H7-14 {//创建类public static void main(String[] args) {//主方法Market market = new WallMarket();// 使用派生类对象创建抽象类对象market.name = "沃尔玛";//沃尔玛market.goods = "七匹狼西服";//七匹狼西服market.shop();//创建商店market = new TaobaoMarket();// 使用派生类对象创建抽象类对象market.name = "淘宝"; //淘宝market.goods = "韩都衣舍花裙";//韩都衣舍碎花裙market.shop();//创建商店}
}

结果

综上所述,使用抽象类和抽象方法时,需要遵循以下原则:
(1)在抽象类中,可以包含抽象方法,也可以不包含抽象方法,但是包含了抽象方法的类必须被定义为抽象类。
(2)抽象类不能直接实例化,即使抽象类中没有声明抽象方法,也不能实例化。
(3)抽象类被继承后,子类需要实现其中所有的抽象方法。
(4)如果继承抽象类的子类也被声明为抽象类,则可以不用实现父类中所有的抽象方法。

使用抽象类时,可能会出现这样的问题:程序中会有太多冗余的代码,同时这样的父类局限性很大,例如,上面的例子中,也许某个不需要shop()方法的子类也必须重写shop()方法。如果将这个shop()方法从父类中拿出,放在别的类里,又会出现新问题,就是某些类想要实现“买衣服”的场景,竟然需要继承两个父类。Java 中规定,类不能同时继承多个父类,面临这种问题时,接口的概念便出现了。

7.4.2接口的声明及实现

使用抽象类时,可能会出现这样的问题:程序中会有太多冗余的代码,同时这样的父类局限性很大,例如,上面的例子中,也许某个不需要shop()方法的子类也必须重写shop()方法。如果将这个shop()方法从父类中拿出,放在别的类里,又会出现新问题,就是某些类想要实现“买衣服”的场景,竟然需要继承两个父类。Java 中规定,类不能同时继承多个父类,面临这种问题时,接口的概念便出现了。
接口使用interface 关键字进行定义,其语法如下:

[修饰符] interface 接口名[extends 父接口名列表] {
    [public] [static] [final] 常量;
    [public] [abstract] 方法;
}

修饰符:可选,用于指定接口的访问权限,可选值为public。 如果省略则使用默认的访问权限。
接口名: 必选参数,用于指定接口的名称,接口名必须是合法的Java标识符。一般情况下,要求首字母大写。                                                                                                                             extends 父接口名列表:可选参数,用于指定要定义的接口继承于哪个父接口。当使用extends关键字时,父接口名为必选参数。
方法:接口中的方法只有定义而没有被实现。

一个类实现一个接口可以使用implements 关键字,代码如下:

public class Parallelogram extends Quadrangle implements drawTest{
    ...//

}

接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。对于1小节中遗留的问题,可以将draw()方法封装到一个接口中,这样可以让一个类既能继承图形类,又能实现draw()方法接口,这就是接口存在的必要性。

代码7-15

interface drawTest { // 定义接口public void draw(); // 定义方法
}
class ParallelogramgleUseInterface implements drawTest {// 定义平行四边形类,该类实现了drawTest接口public void draw() { // 由于该类实现了接口,所以需要覆盖draw()方法System.out.println("平行四边形.draw()");//输出平行四边形}
}
class SquareUseInterface implements drawTest {//定义正方形类,该类实现了drawTest接口public void draw() {// 由于该类实现了接口,所以需要覆盖draw()方法System.out.println("正方形.draw()");// 输出正方形}
}
public class H7-15 {//创建类public static void main(String[] args) {//主方法drawTest[] d = { // 接口也可以进行向上转型操作new SquareUseInterface(), new ParallelogramgleUseInterface() };//新建数组for (int i = 0; i < d.length; i++) {//控制长度,累加d[i].draw(); // 调用draw()方法}}
}

结果

在本实例中,正方形类与平行四边形类分别实现了drawTest接口,所以需要覆盖接口中的方法。在调用draw()方法时,首先将平行四边形类对象与正方形类对象向上转型为drawTest接口形式。这里也许很多读者会有疑问,接口是否可以向上转型?其实在Java 中无论是将一个类向上转型为父类对象,还是向上转型为抽象父类对象,或者向上转型为该类实现接口,都是没有问题的。然后使用d[i]数组中的每一个对象调用draw(), 由于向上转型,所以d[i]数组中的每一个对象分别代表正方形类对象与平行四边形类对象,最后结果分别调用正方形类与平行四边形类中覆盖的draw()方法。

7.4.3 多重继承

在Java中类不允许多重继承,但使用接口就可以实现多重继承,因为一个类可以同时实现多个接口,这样可以将所有需要实现的接口放置在implements关键字后并使用逗号“,”隔开,但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中所有的方法。

代码7-16

public interface IFather {//定义一个接口void smoking();//抽烟的方法void goFishing();//钓鱼的方法
}
public interface IMother {//定义一个接口void watchTV();//看电视的方法void cooking();//做饭的方法
}
public class H7-16 implements IFather,IMother{ //创建一个Me类继承IFather接口和IMother接口@Overridepublic void watchTV() {// 重写watchTV()方法System.out.println("我喜欢看电视");//输出我喜欢看电视}@Overridepublic void cooking() {// 重写cook()方法System.out.println("我喜欢做饭");//输出我喜欢做饭}@Overridepublic void smoking() {// 重写smoke()方法System.out.println("我喜欢抽烟"); //输出我喜欢抽烟}@Overridepublic void goFishing() {// 重写goFishing()方法System.out.println("我喜欢钓鱼");//输出我喜欢钓鱼}public static void main(String[] args) {//主方法IFather father = new H7-16();// 通过子类创建IFather接口对象System.out.println("爸爸的爱好:"); //输出爸爸的爱好father.smoking();// 使用接口对象调用子类中实现的方法father.goFishing();// 使用接口对象调用子类中实现的方法IMother mother =new H7-16();// 通过子类创建IMather接口对象System.out.println("\n妈妈的爱好:"); //输出妈妈的爱好mother.cooking();// 使用接口对象调用子类中实现的方法mother.watchTV();// 使用接口对象调用子类中实现的方法}
}
}

结果

注意:使用多重继承时,可能出现变量或方法名冲突的情况,解决该问题时,如果变量冲突,则需要明确指定变量的接口,即通过“接口名.变量”实现时,则只要实现一个方法即可。

7.4.4 区分抽象类与接口

抽象类和接口都包含可以由子类继承实现的成员,但抽象类是对根源的抽象,而接口是对动抽象。抽象类的功能要远超过接口,那为什么还要使用接口呢?这主要是由于定义抽象类的个高(因为每个类只能继承一个类,在这个类中,必须继承或编写出其子类的所有共性,因此,虽然接口在功能上会弱化许多,但它只是针对一个动作的描述,而且可以在一个类中同时实现多个接这样会降低设计阶段的难度。
抽象类和接口的区别主要有以下几点。
(1)子类只能继承一个抽象类,但可以实现任意多个接口。
(2)一个类要实现一个接口必须实现接口中的所有方法,而抽象类不必。
(3)抽象类中的成员变量可以是各种类型,而接口中的成员变量只能是public static final 的(4)接口中只能定义抽象方法,而抽象类中可以定义非抽象方法。(5)抽象类中可以有静态方法和静态代码块等,接口中不可以。
(6)接口不能被实例化,没有构造方法,但抽象类可以有构造方法。

抽象类与接口的不同

7.5 访问控制

前面多次提到了public、private、包等关键字或者概念,这些都是用来控制类、方法或者变量的访问范围的,Java 中主要通过访问控制符、类包和final关键字对类、方法或者变量的访问范围行控制,本节将对Java中访问控制知识进行详细讲解

7.5.1 访问控制符

前面介绍了面向对象的几个基本特性,其中包括封装性,封装实际上有两方面的含义:把该隐藏的隐藏起来、把该暴露的暴露出来,这两个方面都需要通过使用Java 提供的“访问控制符”来实现,本节将对Java 中的访问控制符进行详细讲解。
Java 中的访问控制符主要包括 public、protected、private 和default(缺省)等4种,这些控制符控制着类和类的成员变量以及成员方法的访问权限。

Java语言中的访问控制符权限

注意:声明类时,如果不使用public修饰符设置类的权限,则这个类默认为default(缺省)修饰。

使用访问控制符时,需要遵循以下原则。                                                                                       (1)大部分顶级类都使用 public 修饰;
(2)如果某个类主要用作其他类的父类,该类中包含的大部分方法只是希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰;
(3)类中的绝大部分属性都应该使用private修饰,除非一些static或者类似全局变量的属性才考虑使用 public 修饰;
(4)当定义的方法只是用于辅助实现该类的其他方法(即工具方法),应该使用private 修(5)希望允许其他类自由调用的方法应该使用public 修饰。

7.5.2 Java 类包

在Java义好类,通Java编译器进行编译之后,都会生成一个扩展名为.class的文件,当这个程序的规模逐渐庞大时,就很容易发生类名称冲突的现象。那么JDKAPI中提供了成千上万具有各种功能的类,又是如何管理的呢?Java 中提供了一种管理类文件的机制,就是类包。
Java中每个接口或类都来自不同的类包,无论是JavaAPI中的类与接口还是自定义的类与接口都需要隶属于某一个类包,这个类包包含了一些类和接口。如果没有包的存在,管理程序中的类名称将是一件非常麻烦的事情,如果程序只由一个类定义组成,并不会给程序带来什么影响,但是随着程序代码的增多,难免会出现类同名的问题。例如,在程序中定义一个 Login 类,因业务需要,还要定义一个名称为Login的类,但是这两个类所实现的功能完全不同,于是问题就产生了,编译器不会允许存在同名的类文件。解决这类问题的办法是将这两个类放置在不同的类包中,实际上 Java中类的完整名称是包名与类名的组合。

(1)在项目的sre节点上单击鼠标右键,选择“New- Package"命令。
(2)弹出New Java Package对话框,在Name文本框中输入新建的包名,如om.migy然后单击"Finish” 按钮。

(3)在Eclipse 中创建类时,可以在新建立的包上单击鼠标右键,选择“New" 命令,这样街建的类会默认保存在该包中。另外也可以在NewJavaClass对话框中指定新建类所在的包。在Java中包名设计应与文件系统结构相对应,如一个包名为com.mingrisoft,那么该包中的类位于com文件夹下的mingrisoft子文件夹下。没有定义包的类会被归纳在预设包(默认包)中。在实际开发中,应该为所有类设置包名,这是良好的编程习惯。

在类中定义包名的语法如下:
package包名1[.包名2[.包名3...]];
在上面的语法中,包名可以设置多个,包名和包名之间使用.分割,包名的个数没有限制,其中前面的包名包含后面的包名。
在类中指定包名时需要将package放置在程序的第一行, 它必须是文件中的第一行非注释代码,当使用package关键字为类指定包名之后,包名会成为类名中字的部分,预示着这个类必须指定全名,例如,在使用位于com.mingrisoft包下的Dog.java类时,需要使用形如com.mingrisoft.Dog这样的格式。

注意:Java包的命名规则是全部使用小写字母,另外,由于包名将转换为文件的名称,所以包名中不能包含特殊字符。

定义完包之后,如果使用包中的类,可以使用Java中的import关键字指定。

import包名1[.包名2[.包名3...] ].类名;

在使用import关键字时,可以指定类的完整描述,但如果为了使用包中更多的类,则可以在包名后面加.*,这表示可以在程序中使用包中的所有类。

import com.lzw.*;           //指定import com.lzw包中的所有类在程序中都可以使用                  import com.lzw.math       //指定import com.lzw包中的math类在程序中都可以使用

注意:如果类定义中已经导入com.lzw.Math类,在类体中还想使用其他包中的Math类时,则必须使用完整的带有包格式的类名,比如,这种情况再使用java.lang包的Math类时就要使用全名格式java. lang.Math。
        在程序中添加import关键字时,当使用import指定了一个包中的所有类,并不会指定这个包的子包中的类,如果用到这个包中的子类,则需要再次对子包单独引用。

7.5.3 final 关键字

1.final

定义为final的类不能被继承。
如果希望一个类不允许任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为final形式。                                                                                                                                        final类的语法如下:
        final class 类名{ }
        如果将某个类设置为fnal 形式,则类中的所有方法都被隐式地设置为final 形式,但是final类中的成员变量可以被定义为final 或非final形式。

代码7-17

final class FinalClass {//final类int a = 3; //定义初值void doit() {//调用 doit()方法}public static void main(String args[]) {//主方法FinalClass f = new FinalClass();//新建数组f.a++;//累加System.out.println(f.a);//输出结果}
}

结果

2.final方法

首先,读者应该了解定义为final的方法不能被重写。
将方法定义为final 类型可以防止子类修改该类的定义与实现方式,同时定义final 的方法的执行效率要高于非final方法。在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法, 所以一个定义为private的方法隐式被指定为final类型,这样无需将一个定义为private的方法再定义为final类型。例如下面的语句:

private final void test() {
    ...//省略些程序代码
}
 但是在父类中被定义为private final的方法似乎可以被子类覆盖

代码7_18

class Parents{//创建父类private final void doit() {//调用final类System.out.println("父类.doit()");//输出调用父类}final void doit2() {//调用doit2()方法System.out.println("父类.doit2()");//输出调用父类}public void doit3() {//调用doit3()方法System.out.println("父类.doit3()");//输出调用父类}
}
class Sub extends Parents {//Sub继承Parents类public final void doit() { //在子类中定义一个doit()方法System.out.println("子类.doit()");//输出调用子类}// final void doit2(){ //final 方法不能覆盖 //  System.out.println("子类.doit2()"); //输出调用子类// }public void doit3() {//调用doit3()方法System.out.println("子类.doit3()");//输出调用子类}
}
public final class FinalMethod {//创建类public static void main(String[] args) {//主方法Sub s=new Sub(); //实例化 s.doit(); //调用 doit()方法 Parents p=s; //执行向上转型操作 //p.doit(); //不能调用private方法 p.doit2(); //调用 doit2()方法p.doit3();//调用 doit3()方法}
}

结果

从本实例中可以看出,final 方法不能被覆盖,例如,doit2()方法不能在子类中被重写,但是在父类中定义了一个 private final 的doit()方法,同时在子类中也定义了一个doit()方法,从表面来看,子类中的doit()方法覆盖了父类的doit()方法,但是覆盖必须满足一个对象向 上转型为它的基本类型并调用相同方法这样一个条件。 例如,在主方法中使用“Parents p=s;"语句执行向上转型操作,对象P只能调用正常覆盖的doit3()方法,却不能调用doit()方法,可见子类中的doit()方法并不是正常覆盖,而是生成一个新的方法。

3.final变量

final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final定义的变量为常量。例如,在类中定义PI值,可以使用如下语句:

final double PI=3.14;
当在程序中使用PI这个常量时,它的值就是3.14,如果在程序中再次对定义为final的常量赋值,编译器将不会接受。

final 关键字定义的变量必须在声明时对其进行赋值操作。final 除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组也可以被看作一个对象来引用, 所以final可以修饰数组。一旦一个对象引用被修饰为final 后,它只能恒定指向一个对象, 无法将其改变以指向另一个对象。一 个既是satic又是final的字段只占据段不能改变的存储空间。

代码7_19

import static java.lang.System.out; //导入要用到的包
import java.util.Random;//导入要用到的包
class Test {//类名int i = 0;//定义i=0}
public final class H7_19 { //创建类static Random rand =new Random(); //创建新数组private final int VALUE_1 = 9;//声明一个final常量private static final int VALUE_2 = 10;//声明一个 final、static常量private final Test test = new Test(); //声明一个 final引用private Test test2 = new Test();//声明一个不是 final 的引用private final int[] a = {1,2,3,4,5,6 }; //声明一个定义为final 的数组private final int i4 = rand.nextInt(20);//声明一个final常量private static final int i5= rand.nextInt(20); //声明一个final常量public String toString() {//调用toString()return i4 +" "+i5+" ";//输出结果}
public static void main(String[] args){//主方法FinalData data = new H7_19(); //创建新数组data.test=new Test();//输出结果//可以对指定为final的引用中的成员变量赋值//但不能将定义为final的引用指向其他引用 //data.VALUE_2++;//不能改变定义为final的常量值data.test2=new Test(); //可以将没有定义为 final的引用指向其他 for (int i = 0; i < data.a.length; i++) { //控制长度//a[i]=9; //不能对定义为final的数组赋值}out.println(data);//输出data结果out.println("data2");//输出data2结果out.println(new H7_19());//输出数组结果out.println(data);//输出data结果}
}

结果

在本实例中,被定义为final的常量定义时需要使用大写字母命名,并且中间使用下划线进行连接,这是Iana中的编码规则。同时,定义为fnal的数据无论是常量、对象引用还是数组,在主的数中都不可以被改变。
        我们知道一个被定义为final 的对象引用只能指向唯一一个对象, 不可以将它再指向其他对象,但是一个对象本身的值却是可以改变的,那么为了使一个常量真正做到不可更改,可以将常量声明为static final。

代码7_20

import java.util.Random;//导入要用到的包
import static java.lang.System.out; //导入要用到的包
public class H7_20 {//创建类private static Random rand = new Random();//实例化一个random类对象private final int a1 = rand.nextInt(10);//随机产生0~10之间的随机数赋予定义为final的a1private static final int a2 = rand.nextInt(10);//随机产生0~10之间的随机数赋予定义为static final的a2public static void main(String[] args){//主方法FinalStaticData fdata = new H7_20 ();//实例化一个对象out.println("重新实例化对象调用a1的值:"+ fdata.a1);//调用定义为final的a1out.println("重新实例化对象调用a2的值:"+ fdata.a2);//调用定义为static final的a2FinalStaticData fdata2= new H7_20 ();//实例化另外一个对象out.println("重新实例化对象调用a1的值:"+ fdata2.a1);//输出结果out.println("重新实例化对象调用a2的值:"+ fdata2.a2);//输出结果}
}

结果

从本实例的运行结果中可以看出,定义为final 的常量不是恒定不变的,将随机数赋予定义为fnal的常量,可以做到每次运行程序时改变al的值。但是a2与al不同,由于它被声明为static final形式,所以在内存中为a2开辟了一个恒定不变的区域,当再次实例化一个FinalStaticData对象时,仍然指向a2这块内存区域,所以a2的值保持不变。a2是在装载时被初始化,并不是每次创建新对象时都被初始化,而al会在重新实例化对象时被更改。

技巧:在Java中定义全局常量,通常使用public static final修饰,这样的常量只能在定义时被赋值。

可以将方法的参数定义为final类型,这预示着无法在方法中更改参数引用所指向的对象。
最后总结一下在程序中final 数据可以出现的位置。图7.27清晰地表明了在程序中哪些位置可以定义final 数据。

7.6 内部类

前面曾经学习过在一个 文件中定义两个类,但其中任何一个类都不在另一个类的内部,而如果在类中再定义一个类,则将在类中再定义的那个类称为内部类,这里可以想像一下汽车和发动机的关系,很显然,此处不能单独用属性或者方法表示一个发动机,发动机是一个类, 而发动机又在汽车之中,汽车也是一个类,正如同内部类在外部类之中,这里的发动机类就好比是一个内部类。中部类可分为成员内部类、局部内部类以及匿名类。本节将对内部类的使用进行讲解。

7.6.1成员内部类

1.成员内部类简介

在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量。本节首先介的成员内部类。

成员内部类的语法如下:

public class OuterClass{     //外部类
    private class InnerClass {   //内部类
      //...
    }
}

在内部类中可以随意使用外部类的成员方法以及成员变量,尽管这些类成员被修饰为private图7.28充分说明了内部类的使用,尽管成员变量i以及成员方法g()都在外部类中被修饰为private但在内部类中可以直接使用外部类中的类成员。

内部类的实例一定要绑定在外部类的实例上,如果从外部类中初始化一个内 部类对象,那么内部类对象就会绑定在外部类对象上。内部类初始化方式与其他类初始化方式相同,都是使用new关键字。

代码H7_21


public class H7_21 {//创建类innerClass in = new innerClass();//在外部类实例化内部类对象引用public void ouf() {//  普通类in.inf();     //在外部类方法中调用内部类方法}class innerClass{//创建innerClassinnerClass(){//内部类构造方法}public void inf() { //内部类成员方法}int y = 0;//定义内部类成员变量}public innerClass doit(){ //创建innerClassdoit()方法in.y = 4;//定义y=4return new innerClass();//新建innerClass}public static void main(String[] args) {//主方法OuterClass out = new H7_21();//创建新数组OuterClass.innerClass in = out.doit();//创建新数组doit()OuterClass.innerClass in2 = out.new innerClass();//创建新数组innerClass()}
}

结果

例题中的外部类创建内部类实例与其他类创建对象引用时相同。内部类可以访问它的外面类成员,但内部类的成员只有在内部类的范围之内是可知的,不能被外部类使用。图7.29说明了内部类ImerClass对象与外部类OuterClass对象的关系.

内部类对象与外部类对象关系非常紧密,内外可以交互使用彼此类中定义的变量。

注意:如果在外部类和非静态方法之外实例化内部类对象,需要使用外部类。内部类的形成指定该对象的类型。

在例题的主方法中如果不使用doit( )方 法返回内部类对象引用,可以直接使用内部类实例化内部类对象,但由于是在主方法中实例化内部类对象,必须在new操作符之前提供一个外部类的引用。
例如,在主方法中实例化一个内 部类对象。

public static void main (String args[]) {//主函数
    OuterClass out=new ”OuterClass() ;//实例化一个对象
    OuterClass. innerClass in=out.doit() ;//实例化一个对象
    OuterClass. innerClass in2=out.new innerClass();//实例化内部类对象
}

从上面代码可以看出,在实例化内部类对象时,不能在new操作符之前使用外部失实明化内部类对象,而应该使用外部类的对象来创建其内部类的对象。

注意:内部类对象会依赖于外部类对象,除非已经存在一个外部类对象,否则类中不会出现内部类对象。

2.内部类向上转型为接口

如果将一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程。可以在外部提供一个接口, 在接口中声明个方法。如果在实现该接口的内部类中实现该接口的方法,就可以定义多个内部类以不同的方式实现接口中的同一个方法,而在一般的类中是不能多次实现接口中同一个方法的,这种技巧经常被用在Swing编程中,可以在-一个类中做出多个不同的响应事件(Swing编程技术会在后文中详细介绍)。

代码7_22

interface OutInterface {//定义一个接口public void f(); //普通类}
public classH7_22 {//创建类public static void main(String[] args) {//主方法OutClass2 out = new OutClass2();//实例化一个OutClass2对象OutInterface  outinter = out.doit();//调用doit()方法,返回一个OutInterface 接口outinter.f();//调用f()方法}}class OutClass2 { //定义一个内部类实现OutInterface接口private class InnerClass implements OutInterface {//定义一个内部类实现OutInterface接口InnerClass(String s) {//返回参数System.out.println(s);// 输出结果}public  void f() {//实现接口中的f()方法System.out.println("访问内部类中的f()方法");//输出访问内部类中的f()方法}}public  OutInterface doit() {//定义一个方法,返回值类型OutInterface接口return new InnerClass ("访问内部类构造方法");//输出访问内部类构造方法}
}

结果

从上述实例中可以看出,OuterClass2 类中定义了一个修饰权限为private的内部类,这个内部类实现了Outlnterface 接口,然后修改doit(方法,使该方法返回个Outlnterface 接口。由于内部类InmerClass修饰权限为private,所以除了OuterClass2 类可以访问该内部类之外,其他类都不能访问,而可以访问doit(方法。由于该方法返回一一个外部接口类型,这个接口可以作为外部使用的接口。它包含一个f()方法,在继承此接口的内部类中实现了该方法,如果某个类继承了外部类,由于内部的权限不可以向下转型为内部类InnerClass,同时也不能访问f0方法,但是却可以访问接口中的f0方法。例如,InterfaceInner 类中最后一条语句,接口引用调用f0方法,从执行结果可以看出,这条语句执行的是内部类中的f()方法,很好地对继承该类的子类隐藏了实现细节,仅为编写子类的人留下个接口和一个外部类,同时也可以调用f()方法,但是f()方法的具体实现过程却被很好地隐藏了,这就是内部类最基本的用途。

注意:非内部类不能被声明为private或protected访问类型。

3.使用this关键字获取内部类与外部类的引用

如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字。

代码7_23


public class H7_23 {//创建类private int x;    //定义private int x方法private class Inner{    //普通方法private int x = 9;    //定义private int x=9public void doit(int x) {       //调用的是形参x x++;     //调用的是形参xthis.x++;     //调用内部类的变量xTheSameName.this.x++;     //调用外部类的变量x}}
}

结果

在类中,如果遇到内部类与外部类的成员变量重名的情况,可以使用this关键字进行处理。例如,在内部类中使用this.x语句可以调用内部类的成员变量x,而使用TheSameName.this.x语句可以调用外部类的成员变量x,即使用外部类名称后跟一个点操作符和this关键字便可获取外部类的一个引用。

读者应该明确一点,在内存中所有对象均被放置在堆中,方法以及方法中的形参或局部变量放置在栈中。在图中,栈中的doit( )方法指向内部类的对象,而内部类的对象与外部类的对象是相互依赖的,Outer.this对象指向外部类对象。

综上所述,使用成员内部类时,应该遵循以下原则:
(1)可以有各种修饰符,可以用private、 public、 protected、 static、 final、 abstract等修饰;
(2)如果内部类有static限定,就是类级别的,否则为对象级别。类级别可以通过外部类直接访问,对象级别需要先生成外部的对象后才能访问;
(3)内外部类不能同名;
(4)非静态内部类中不能声明任何static成员;
(5)内部类可以互相调用。

7.6.2 局部内部类

内部类不仅可以在类中进行定义,也可以在类的局部位置定义如在类的方法或任意的作用域中均可以定义内部类

代码7_24


public interface H7_24 {  //创建类
}
class OuterClass3 {    //普通类public OutInterface2 doit(final String x) {        // doit()方法参数为final类型class InnerClass2 implements H7_24 {     // 在doit()方法中定义一个内部类InnerClass2(String s) {   // 在doit()方法中定义一个内部类s = x;         //定义s=xSystem.out.println(s);   //输出s}}return new InnerClass2("doit");     // 输出结果doit()方法中定义一个内部类}
}

从上述代码中可以看出,内部类被定义在了doit()方法内部。但是有一点值得注意,内部类InnerClass2是doit()方法的一部分, 并非OuterClass3类中的一部分, 所以在doit()方法的外部不能间该内部类,但是该内部类可以访问当前代码块的常量以及此外部类的所有成员。
        有的读者会注意到例题中的一个修改细节,就是将doit()方法的参数设置为fnal类型。如果需要在方法体中使用局部变量,该局部变量需要被设置为final 类型,换句话说,在方法中定义的内部类只能访问方法中final类型的局部变量,这是因为在方法中定义的局部变量相当于一个常量, 它的生命周期超出方法运行的生命周期,由于该局部变量被设置为final,所以不能在内部类中改变该局部变量的值。

7.6.3 匿名内部类

该例题中定义的内部类再次进行修改,在doit()方法中将return语向和内部类定义语句合并在一起,下面通过一个实例说明。

代码7_25


public interface H7_25 {   //接口
}
class OuterClass4 {      //类名public H7_25 doit() {    // 定义doit()方法return new H7_25() {     // 声明匿名内部类private int i = 0;     //定义i=0public int getValue() {    //int匿名内部类  return i;       //输出结果}};}}

从例题中可以看出,笔者将doit0方法修改得有一些莫名其妙, 但这种写法确实被Java编译认可,在dit0方法内部首先返回一个OutercC的引用,然后在retrm语向中插入一个定义内类的代码, 由于这个类没有名称,所以这里将该内部类称为匿名内部类。实质上这种内部类的作就是创建一 个实现于OutInterface2接口的匿名类的对象。
匿名类的所有实现代码都需要在大括号之间进行编写。

return new A() {
    ...//内部类体
};

其中,A指类名。

由于匿名内部类没有名称,所以匿名内部类使用默认构造方法来生成Outinterface2对象。在匿名内部类定义结束后,需要加分号标识,这个分号并不是代表定义内部类结束的标识,而是代表创建OutInterface2引用表达式的标识。
说明:匿名内部类编译以后,会产生以“外部类名S5序号”为名称的icass文件,序号以1-n排列,分别代表1-n个匿名内部类。

使用匿名内部类时应该遵循以下原则:
(1)匿名类没有构造方法;
(2)匿名类不能定义静态的成员;
(3)匿名类不能用private pulie. protecte. stati. final. abstract 等修饰;
(4)只可以创建一个匿名类实例。

7.6.4 静态内部类

在内部类前添加修饰符static,这个内部类就变为静态内部类了。一个静态内部类中可以声明静态成员,但是在非静态内部类中不可以声明静态成员。静态内部类有一一个最大的特点,就是不能你用外部类的非静态成员,所以静态内部类在程序开发中比较少见。
        可以这样认为,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象,但如果内部类被定义为static,就会有更多的限制。静态内部类具有以下两个特点:
(1)如果创建静态内部类的对象,不需要创建其外部类的对象;
(2)不能从静态内部类的对象中访问非静态外部类的对象.

代码7_26


public class H7_26 { {//创建类int x = 100;//定义x的值为100static class Inner{//普通类名void doitInner() {//doitInner() 调用方法// System.out.println("外部类"+x);//输出外部类+x}public static void main(String args[]) {//主方法System.out.println();//换行}}
}

结果

7.6.5 内部类的继承

内部类和其他普通类一样可以被继承,但是继承内部类比继承普通类复杂,需要设置专门的语法来完成。

代码7_27


public class H7_27 extends ClassA.ClassB { // 继承内部类ClassBpublic H7_27(ClassA a) {//继承类中内部类a.super();//构造方法体中使用 a.super()}
}class ClassA {//类名Aclass ClassB {//内部类名B}
}

在某个类继承内部类时,必须硬性给子这个类一个带参数的构造方法,并且该构造方法的参数必须是该内部类的外部类引用,就像例子中的ClassAa,同时在构造方法体中使用a.super()语句。

7.7小结

通过对本章的学习,读者可以了解继承与多态的机制,掌握重载、类型转换等技术,学会使用接口与抽象类,从而对继承和多态有一个比较深入的了解。另外,本章还介绍了 Java 语言中的包、 final关键字的用法以及内部类,尽管读者已经了解过本章所讲的部分知识点,但还是建议初学者仔细揣摩继承与多态机制,因为继承和多态本身是比较抽象的概念,深入理解需要一段时间,使用多态机制必须扩展自己的编程视野,将编程的着眼点放在类与类之间的共同特性以及关系上,使软件开发具有更快的速度、更完善的代码组织架构,以及更好的扩展性和维护性。

JAVA第七章-- 面向对象核心技术总结相关推荐

  1. 第七章 面向对象核心技术

    目录 7.1 类的封装 7.2  类的继承 7.2.1  extends 关键字 7.2.2  方法的重写 1.重写的实现 2.super 关键字 7.2.3  所有类的父类--Object类 7.3 ...

  2. 第七章 面向对象核心技术总结

    目录 7.1 类的封装 代码7_1 结果 代码7_2 结果 代码7-3 结果 代码7_4 结果 7.2 类的继承 7.2.1 extends 关键字 代码7_5 结果 7.2.2 方法的重写 代码7- ...

  3. java第七章jdbc课后简答题_java学习路线流程

    第一阶段JavaSE: 第一章:jdk的安装与配置 第二章:Java基础语法: 变量.常量.数据类型.关键字 运算符和表达式:运算符:+.-.*./ 表达式:正则表达式 关系及逻辑运算符:关系运算符: ...

  4. java第七章jdbc课后简答题_jsp编程基础第七章习题

    第七章数据库访问 一.选择题 1.下面哪一项不是JDBC的工作任务?() A)与数据库建立连接B)操作数据库,处理数据库返回的结果 C)在网页中生成表格D)向数据库管理系统发送SQL语句 2.下面哪一 ...

  5. java第七章学习笔记:访问控制---java世界的卫兵

    [因为每一章都篇幅比较长(10多页,难免有的地方会写错字,如发现请指正,本人不胜感激)] [今天看到我居然有一名粉丝,内心还是有点小激动的,之间我是一枚不是很喜欢技术的妹纸,但是从现在开始,我从被动学 ...

  6. Java六-七章总结

    1-1 介绍类与对象         类(class)和对象(object)是两种以计算机为载体的计算机语言的合称.对象是对客观事物的抽象,类是对对象的抽象.类是一种抽象的数据类型. 1.类与对象的定 ...

  7. Java第五章——面向对象(二)

    第五章面向对象(中) 1.访问控制修饰符 2.继承 3.组合 4.重载 5.覆盖(覆写) 重点: 封装(java访问控制权限) 继承(定义.子类访问控制权限) 类的组合的实现(与继承的区别) 多态(重 ...

  8. java第七章jdbc课后简答题_javaEE简答题答案

    一.简答题(30分,6题*5分) (一)第一章概述 1.三层体系结构的优点有哪些?p2 (1)安全性高(2)易维护(3)快速响应(4)系统扩展灵活 2.两层体系结构的缺点有哪些?p2-3 (1)安全性 ...

  9. 第七章 面向对象核心

    目录 7.1 类的封装 7.2 类的继承 7.2.1 extends关键词 7.2.2 方法的重写 7.2.3 所有类的父类--Object类 7.3 类的多态 7.3.1 方法的重载 7.3.2 向 ...

最新文章

  1. 深入理解 Spring Cloud 核心组件与底层原理!
  2. Linux中光盘使用的文件类型,linux下mount命令使用详解---linux挂载光盘等文件系统...
  3. java filesystem_Java FileSystem isReadOnly()用法及代码示例
  4. RFT属于软件测试管理工具,IBM Rational FunctionalTester RFT 软件功能测试工具_FunctionalTester_领测软件测试网...
  5. 联想m100显示耗材_RTX3070显卡搭档高性能显示器,畅玩精美游戏大作!
  6. texshop 使用技巧
  7. SQL面试题--(26~46)
  8. 分享400多道算法题,来挑战吧
  9. 超级实用的内存泄漏归纳分析心得
  10. LC60 Permutation Sequence
  11. 人均劳动生产率的单位_生产率的单位是什么?
  12. 什么是视频结构化?视频结构化有什么作用
  13. Ubuntu 16.04升级到Ubuntu 16.10的方法:
  14. 如何用IED编写股票的量化策略?
  15. 如何将u盘两个分区合并?u盘怎么合并一个区
  16. 桌面HTML更换图标,怎样更改桌面图标?更改桌面图标方法【图文详解】
  17. 担心数据丢失 or 系统备份? Win10系统备份还原轻松学会
  18. WuThreat身份安全云-TVD每日漏洞情报-2022-12-26
  19. KNN 在手写识别中的应用(Java 实现)
  20. 为什么说真正的高手都有窄门思维?

热门文章

  1. 程序员都会的五大算法之五(分支限界法),恶补恶补恶补!!!
  2. C语言行列式计算--高万禄
  3. 漆包线制作空芯线圈电感方法
  4. 内容安全策略CSP(Content-Security-Policy)
  5. 活法 - 第一章 实现理想
  6. JavaWeb第五次:选择题+填空题+判断题
  7. 自动售货机方案/设计/开发/项目
  8. vtk能干什么(适用范围)
  9. 生产管理电子看板,打造目视化精益工厂
  10. 开车路上,万一遇到紧急情况,如何处理?开车紧急避险手册速收藏