目录

7.1 类的封装

7.2  类的继承

7.2.1  extends 关键字

7.2.2  方法的重写

1.重写的实现

2.super 关键字

7.2.3  所有类的父类——Object类

7.3  类的多态

7.3.1  方法的重载

7.3.2  向上转型

7.3.3  向下转型

7.3.4  instanceof  关键字

7.4  抽象类与接口

7.4.1  抽象类与抽象方法

7.4.2  接口的声明及实现

7.4.3  多重继承

7.4.4  区分抽象类与接口

7.5  访问控制

7.5.1  访问控制符

7.5.2  JAVA 类包

7.5.3  final关键字

1. final 类

2. final 方法

3. final 变量

7.6  内部类

7.6.1  成员内部类

1、成员内部类简介

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

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

7.6.2  局部内部类

7.6.3  匿名内部类

7.6.4  静态内部类

7.6.5  内部类的继承

7.7  小结


7.1 类的封装

封装是面向对象编程的核心思想,将对象的属性和行为封装起来,其级体就是类本节将谭福介绍如何将类封装。

例7.1 代码

package Leiduixiang;   //包名
public class Restaurant1 {   //创建类public static void main(String[] args) {   //主方法// TODO Auto-generated method stub
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);      //输出厨师的名字
System.out.println("**请让厨师给我切一点葱花。***");   //让厨师给我切一点葱花
System.out.println(cookName+"切葱花");   //让厨师切葱花}
}

例7.1 结果

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

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

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

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

例7.2  代码

package Leiduixiang;
public class Restaurant2 {   //创建类public static void main(String[] args) {   //主方法// TODO Auto-generated method stub
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 washvegetables() {    // 厨师洗蔬菜System.out.println(name+"洗蔬菜");    //输出厨师洗蔬菜}void cooking(String dish) {    // 厨师烹饪顾客点的菜washvegetables();    //洗蔬菜cutOnion();     //切葱花System.out.println(name+"开始烹饪"+dish);  //输出name + "开始烹饪" + dish}
}

例7.2  结果

将厨师单独封装成一个类,将厨师的工作定义成厨师类的行为。

例7.3  代码

package Leiduixiang;    //类包
public class Restaurant3 {   //创建类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() {     //创建Cook2()this.name="Tom Cruise";    // 厨师的名字叫Tom Cruise}private void cutOnion() {     // 厨师切葱花System.out.println(name+"切葱花");   //输出Tom Cruise切葱花}private void washVegetables() {     // 厨师洗蔬菜System.out.println(name+"洗蔬菜");    //Tom Cruise洗蔬菜}void cooking(String dish) {   // 厨师烹饪顾客点的菜washVegetables();    //洗蔬菜cutOnion() ;      //切洋葱System.out.println(name+"开始烹饪"+dish);    // 输出厨师烹饪顾客点的菜}
}

例7.3  结果

被封装后的结果的完全不一样的。

例7.4  代码

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

例7.4  结果

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

7.2  类的继承

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

7.2.1  extends 关键字

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

child extends parents

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

例7.5  代码

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

例7.5  结果

7.2.2  方法的重写

1.重写的实现

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

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

例7.6  代码

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

例7.6  结果

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

2.super 关键字

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

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

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

例7.7  代码

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

例7.7  结果

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方法。由于所有的类都是Object 类的子类,所以任何类都可以重写Object类中的方法。

Object的重要方法:

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

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

例7.8  代码

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

例7.8  结果

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

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

例7.9  代码

class V { // 自定义类v
}
public class OverWriteEquals {    //创建类public static void main(String[] args) {     //主方法String s1 = "123";      // 实例化两个对象,s1内容相同String s2 = "123";      // 实例化两个对象,s2内容相同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.9  结果

从本实例的结果中可以看出,在自定义的类中使用equals0方法进行比较时,将返回false, 因为equals0方法的默认实现是使用“==”运算符比较两个对象的引用地址,而不是比较对象的内容,所以要想真正做到比较两个对象的内容,需要在自定义类中重写equals()方法。

7.3  类的多态

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

7.3.1  方法的重载

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

例7.10  代码

public class OverLoadTest {     //创建类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));   //输出结果System.out.println("调用add(int,int)方法:" + add(1, 2));   //输出结果System.out.println("调用add(double,double)方法:" + add(2.1, 3.3));   //输出结果System.out.println("调用add(int a, double b)方法:" + add(1, 3.3));   //输出结果System.out.println("调用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));     //输出结果System.out.println("调用add(int... a)不定长参数方法:" + add(2, 3, 4));   //输出结果}}

例7.10  结果

7.3.2  向上转型

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

例7.11  代码

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

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

7.3.3  向下转型

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

例7.12  代码

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

例7.12  结果

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

7.3.4  instanceof  关键字

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

instanceof的语法格式如下:
myobject: 某类的对象引用。
ExampleClass: 某个类。

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

例7.13  代码

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

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

7.4  抽象类与接口

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

7.4.1  抽象类与抽象方法

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

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

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

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

例7.14  代码

package tset714;         //类包public abstract class Market {    //创建类public String name;     //商场名称public String goods;   //商品名称public abstract void shop();  //抽象方法,用来输出信息}
public class TaobaoMarket extends Market{   //创建子类  @Overridepublic void shop() {     //购物// TODO Auto-generated method stubSystem.out.println(name+"网购"+goods);   //输出网购+goods}
}
public class WallMarket extends Market{  //创建子类@Overridepublic void shop() {    //购物// TODO Auto-generated method stub System.out.println(name+"实体店购买"+goods);   //输出实体店购买+goods}
}
public class GoShopping {    //创建类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();    //创建商店}
}

例7.14  结果

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

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

7.4.2  接口的声明及实现

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

接口使用interface 关键字进行定义,其语法如下:
[修饰符] interface 接口名[extends 父接口名列表] {[public] [static] [final] 常量;
[public] [abstract] 方法;
}
修饰符:可选,用于指定接口的访问权限,可选值为public。 如果省略则使用默认的访问权限。
接口名: 必选参数,用于指定接口的名称,接口名必须是合法的Java标识符。一般情况下,要求首字母大写。

extends 父接口名列表:可选参数,用于指定要定义的接口继承于哪个父接口。当使用extends关键字时,父接口名为必选参数。
方法:接口中的方法只有定义而没有被实现。

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

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

例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 QuadrangleUseInterface {     //创建类public static void main(String[] args) {    //主方法drawTest[] d = {    // 接口也可以进行向上转型操作new SquareUseInterface(), new ParallelogramgleUseInterface() };   //新建数组for (int i = 0; i < d.length; i++) {    //控制长度,累加d[i].draw();     // 调用draw()方法}}
}

例7.15  结果

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

7.4.3  多重继承

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

例7.16  代码

package Jiating;    //类包
public interface Father {   //定义一个接口void smoking();  //抽烟的方法void goFishing();    //钓鱼的方法
}
public interface Mother {    //定义一个接口void watchTV();    //看电视的方法void cooking();   //做饭的方法
}
public class Son implements Father,Mother{       //继承IFather接口和IMother接口@Overridepublic void watchTV() {      //重写watchTV()方法System.out.println("我喜欢看电视");    //输出我喜欢看电视}@Overridepublic void cooking() {   //重写cooking()方法System.out.println("我喜欢做饭");    //输出我喜欢做饭}@Overridepublic void smoking() {       //重写smokeing()方法System.out.println("我喜欢抽烟");   //输出我喜欢抽烟}@Overridepublic void goFishing() {    //重写fishing()方法System.out.println("我喜欢钓鱼");   //输出我喜欢钓鱼}public static void main(String[] args) {    //主方法System.out.println("儿子喜欢做的事有:");     //输出儿子喜欢做的事有Mother mother =new Son();     // 通过子类创建IMather接口对象mother.cooking();     // 使用接口对象调用子类中实现的方法mother.watchTV();     // 使用接口对象调用子类中实现的方法Father father = new Son();     // 通过子类创建IFather接口对象father.smoking();     // 使用接口对象调用子类中实现的方法father.goFishing();    // 使用接口对象调用子类中实现的方法}
}

例7.16  结果

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种,这些控制符控制着类和类的成员变量以及成员方法的访问权限。

使用访问控制符时,遵循的原则。

(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中的import关键字指定。

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

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

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

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

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 {   //创建类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()方法}
}

例7.18  结果

从本实例中可以看出,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的字段只占据段不能改变的存储空间。 为了深入了解final关键字。

例7.19  代码

import static java.lang.System.out;    //导入import static java.lang.System.out类
import java.util.Random;   //导入import java.util.Random
class Test {   //类名int i = 0;  //定义i=0}
public final class FinalData {    //创建类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 FinalData();     //创建新数组data.test=new Test();      //输出结果//可以对指定为final的引用中的成员变量赋值//但不能将定义为final的引用指向其他引用 //data.VALUE_2++;    //输出2++//不能改变定义为final的常量值data.test2=new Test();     //可以将没有定义为 final的引用指向其他 for (int i = 0; i < data.a.length; i++) {     //控制长度//a[i]=9; //不能对定义为final的数组赋值}out.println(data);    //输出结果out.println("data2");      //输出data2结果out.println(new FinalData());    //输出数组结果out.println(data);   //输出结果}
}

例7.19  结果

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

例7.20  代码

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

例7.20  结果

从本实例的运行结果中可以看出,定义为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关键字。

例7.21  代码

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

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

在例7.21的主方法中如果不使用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下 面修改例7.21,在项目中创建InterfaceInner 类,并定义接口OutInterface,使内部类InnerClass实现这个接口,最后使doit(方法返回值类型为该接口。

例7.22  代码

interface OutInterface {//定义一个接口public void f();     //  普通类 }
public class InterfaceInner {    //创建类  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 ("访问内部类构造方法");    //输出访问内部类构造方法}
}

例7.22  结果

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

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

如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字。
例7.23在项目中创建TheSameName类,在类中定义成员变量x,再定义一个内部类Inner,在内部类中也创建x变量,并在内部类的doit()方法中分别操作两个x变量。

例7.23  代码

public class TheSameName {   //创建类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关键字便可获取外部类的一个引用。
图7.31给出了例7.23在内存中变量的布局情况。

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

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

7.6.2  局部内部类

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

例7.24  代码

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

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

7.6.3  匿名内部类

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

例7.25  代码

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

从例7.25中可以看出,笔者将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)不能从静态内部类的对象中访问非静态外部类的对象。

staticinnerclass内部类

public class staticinnerclass {

int x=100;

static class inner {

void doitinner(){  //system.out.println("外部类"+x); //不能调用外部类的成员变量x

}

}

}

上面代码中,在内部类的doitInner()方法中调用成员变量x,由于Inner 被修饰为static形式,而成员变量x却是非static 类型的,所以在doitInner()方法中不能调用x变量。
进行程序测试时,如果在每一个 Java文件中都设置一个主方法, 将出现很多额外代码,而程序本身并不需要这些主方法,为了解决这个问题,可以将主方法写入静态内部类中。

例7.26  代码

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

如果编译例7.26中的类,将生成一个名称为Santiniassnerclass$inner的独立类和一个Staticinnerclass类,只要使用java staticClass$inner就可以运行主方法中的内容,这样当完成测试。需要将所有.class文件打包时,只要刪除StaticClasslnner独立类即可。

7.6.5  内部类的继承

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

例7.27  代码

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

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

7.7  小结

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

第七章 面向对象核心技术相关推荐

  1. JAVA第七章-- 面向对象核心技术总结

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

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

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

  3. 第七章 面向对象核心

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

  4. Python第七章-面向对象高级

    面向对象高级 一. 特性 特性是指的property. property这个词的翻译一直都有问题, 很多人把它翻译为属性, 其实是不恰当和不准确的. 在这里翻译成特性是为了和属性区别开来. 属性是指的 ...

  5. 第七章 面向对象 总结

    目录 1.类的封装 2.类的继承: 1.extends关键字 3.方法的重写 super关键字 Object类--所有类的父类 1.getClass()方法 2.toString()方法 3.equa ...

  6. 第七章 面向对象分析---建立动态模型

    建立动态模型 开发交互式系统,动态模型非常重要 步骤: 编写典型交互行为脚本(依据用例描述) 从脚本中提取事件及相关对象,用顺序图表达 确定对象状态及状态间转换关系,用状态图描绘 结合ATM系统的实例 ...

  7. 第七章、 面向对象基础--下(续) 内部类、枚举、注解

    文章目录 内容 学习目标 第七章 面向对象基础--下(续) 7.7 内部类 7.7.1 概述 7.7.1 非静态成员内部类 练习1:语法练习题 练习2:简单面试题 练习题3:高难面试题 7.7.2 静 ...

  8. java学习笔记-第七章:面向对象编程(基础部分)

    第七章:面向对象编程(基础部分) 总体内容 类与对象 引出类与对象 类与对象概述 类与对象的关系示意图 属性概念及其细节 类与对象快速入门案例 对象内存布局 类与对象内存分配机制 引申:java内存的 ...

  9. PHP核心技术与最佳实践 读书笔记 第二章 面向对象的设计原则

    2019独角兽企业重金招聘Python工程师标准>>> 第二章 面向对象的设计原则 2.1 面向对象设计的五大原则 单一职责原则 接口隔离原则 开放-封闭原则 替换原则 依赖倒置原则 ...

最新文章

  1. jpa删除数据后数据库无修改_java – JPA不删除数据库行
  2. 视频动作识别--Convolutional Two-Stream Network Fusion for Video Action Recognition
  3. 《HTML5游戏编程核心技术与实战》一2.6 其他全局属性
  4. 普大喜奔 | Azure 免费送网站SSL证书啦!
  5. Python中os模块使用方法
  6. python如何定位路径_selenium_webdriver(python)查看文件路径,鼠标定位
  7. 记录一个crontab的中使用python脚本的坑
  8. 什么!爬虫要违法了?别慌:守住规则,大胆去爬
  9. Ruby / Rails代码气味基础01
  10. 油猴插件的介绍和安装详解脚本的介绍和添加举例
  11. audio播放器进度条
  12. LIO-SAM后端中的回环检测及位姿计算
  13. PHP极其强大的图片处理库Grafika详细教程(1):图像基本处理
  14. 42. 注入篇——Havij、Pangolin使用
  15. Unity 学习 Tilemap
  16. Java设计一个测桃花模块_20145209刘一阳《JAVA程序设计》第十五周补充测试
  17. 一个数如果恰好等于它的因子之和,这个数就称为 完数 。例如6=1+2+3.编程 找出1000以内的所有完数。
  18. 无论创业还是做人,你都需要知道什么是MVPPMF
  19. 1.找到适合你的学习方法
  20. Docker删除Exited镜像

热门文章

  1. 分享一些好用的mac软件
  2. 电脑秘籍奇妙的140个技巧2
  3. 2022年R1快开门式压力容器操作考题及答案
  4. 读书之----《白夜行》
  5. C语言if条件语句教案,C语言公开课教案.ppt
  6. 如何将PDF转换为PPT格式
  7. 什么是电极的极化电压?
  8. 【JavaScript】23_浅拷贝和深拷贝 + 对象的复制 + 数组的常用方法
  9. 敏捷开发方法之Scrum
  10. RobotFramework(RF)万能关键字-Evaluate