JAVA编程讲义.抽象类、接口和内部类

网友投稿 644 2022-05-29

前面我们学习了类、对象、封装、继承、多态等面向对象编程的基本概念,初步了解了面向对象程序设计理念,接下来我们继续学习面向对象编程的一些重要概念:抽象类、接口和内部类。其中,抽象类是从多个具体类中抽象出来的父类,可以作为子类的模板,实现更加丰富的类继承;接口是Java语言中实现多重继承的重要工具,Java是单继承的语言,而实际应用中某些类往往需要继承多个类的属性与行为,接口很好地解决了单继承的这一缺陷;内部类是定义在类中的类,它同样有着非常重要的作用,如更好地实现隐藏、实现多重继承、实现同一个类中两种同名方法的调用等。

7.1 抽象类

在面向对象程序设计中,常常会遇到这样的问题:对于父类中的某些方法,其不同子类中的这些方法肯定有所不同,这些方法必须在子类中重写,故而根本无需在父类中实现这些方法。面对这类问题,Java提供了抽象类,对一系列看上去不同但本质上相同的具体概念进行抽象,用来表征对问题领域进行分析、设计中得出的抽象概念,使类设计的体系结构更加清晰。

Java语言中提供了abstract关键字,表示抽象的意思。用abstract关键字修饰的类称为抽象类,抽象类有类似“模板”的作用,我们可以定义抽象类来表征某些子类或对象的广义属类的共有特征,但是抽象类不能直接使用new关键字创建对象,只能被继承,并在其子类中将其特征具体化;用abstract关键字修饰的方法称为抽象方法,它是一个不完整的方法,只有声明,没有方法体。抽象类可以包含抽象方法,也可以不包含。定义抽象类和抽象方法的具体语法如下:

abstract class 抽象类名{ // 定义抽象类,用abstract修饰

[访问修饰符]abstract 返回类型方法名称(参数); // 定义抽象方法,用abstract修饰,无方法体

}

通过abstract关键字标记,抽象类和抽象方法可以让开发人员明确该类或方法的作用,进而在继承抽象类的时候重写其抽象方法,以针对性地解决具体问题。定义抽象类或抽象方法的时候,需要注意以下几点:

  抽象类和抽象方法必须使用abstract修饰符来修饰,不能使用private修饰符,因为子类需要有继承抽象类的权限。

  由于抽象类是需要被继承的,所以抽象类不能用final修饰,即关键字abstract和final不能同时使用。

  抽象类不能被实例化,可以有构造方法和普通方法,且构造方法不能声明为抽象的。

  普通方法内有方法体,而抽象方法不能有方法体。

  抽象方法不能使用static修饰符来修饰,因为static修饰的方法可以通过类名调用,而调用一个没有方法体的方法会报错。

  如果一个类继承了抽象类,则该类必须实现抽象类中的全部抽象方法,除非子类也是抽象类。

接下来,我们通过具体问题来体会抽象类与抽象方法的具体应用。几何形状是大家熟悉不过的,它们形状千姿百态,有圆形、正方形、三角形等,这些几何形状都可以计算面积,但是计算方法却不同。可见,计算面积是几何形状的共有特征,而不同的几何形状又有不同的具体实现。于是,为了设计出结构清晰的几何形状面积计算类体系,我们可以先定义抽象的几何形状类Shape类,该类包含公共的颜色属性color,也包公共的面积计算方法getArea()方法,显然该方法也必须是抽象的。接着,我们进一步定义圆形类Circle类和正方形类Square类,让这两个类继承Shape类,并实现getArea()方法。

下面我们给出上述问题的具体实现,如例7-1所示。

例7-1 Demo0701.java

1  package com.aaa.p0701;

2

3  abstract class Shape { // 定义抽象的几何形状类Shape类

4   String color;

5   public Shape(String color){

6   this.color = color;

7   System.out.println("当前图形颜色是:" + color);

8   }

9   abstract double getArea(); // 求面积的抽象方法

10  }

11  class Circle extends Shape{ // 定义Shape类的子类Circle类

12   private final double PI = 3.1415926; // 声明圆周率常量

13   private double r; // 声明圆半径变量

14   public Circle(String color,double radius) {

15   super(color);

16   this.r = radius;

17   }

18   @Override

19   double getArea() { // 重写抽象类中的getArea()方法

20   return PI * r * r;

21   }

22  }

23  class Square extends Shape{ // 定义Shape类的子类Square类

24   private double e; // 声明正方形的边长

25   public Square(String color,double edge) {

26   super(color);

27   this.e = edge;

28   }

29   @Override

30   double getArea() { // 重写抽象类中的getArea()方法

31   return e * e;

32   }

33  }

34

35  public class Demo0701{

36   public static void main(String[] args) {

37   Circle circle = new Circle("红色",3);

38   System.out.println("圆的面积是:" + circle.getArea());

39   Shape square = new Square("绿色",4);

40   System.out.println("正方形的面积是:" + square.getArea());

41   }

42  }

程序运行结果如下:

当前图形颜色是:红色

圆的面积是:28.274333400000003

当前图形颜色是:绿色

正方形的面积是:16.0

示例7-1中,定义了抽象的Shape类,并在抽象类中定义了抽象的getArea()方法。在子类Circle类和Square类中分别实现了适合其自身特性的getArea()方法。

注意:抽象类不可以直接用new关键字创建对象,但并不能说明抽象类不可以创建对象。例如在例7-1中,main()方法中第39行代码处创建了抽象类Shape类的对象,但指向的是其子类的对象。

7.2 接口

Java是一门单继承的语言,一个子类只能继承一个父类。但是在编程实践中,对于某些类,只继承一个抽象类显然无法满足要求,需要实现多个抽象类的抽象方法才能解决问题,这就需要通过接口来实现。在Java中,允许通过一个类实现多个接口来实现类似于多重继承的概念。

7.2.1 接口的定义

接口可以看作是从多个相似的类中抽象出来的规范,不提供任何实现,体现了规范和实现分离的思想。例如,计算机上提供了USB插槽,只要一个硬件遵守USB规范,就能插入USB插槽,可以是鼠标、键盘、数据线等,计算机无须关心是和哪个硬件对象建立了联系。同样,软件系统的开发也需要采用规范和实现分离的思想,即采用面向接口的编程思想,从而降低软件系统模块之间的耦合度,提高系统的扩展性和可维护性。

在Java语言中,提供了interface关键字,用于声明接口,其语法格式如下:

[public]interface 接口名[extends 接口1,接口2...]{

[public][static][final]数据类型 常量名 = 值;

[public][abstract] 返回值的数据类型 方法名(参数列表);

默认方法...

}

在上述语法中,当interface关键字前加上public修饰符时,接口可以被任何类的成员访问。如果省略public,则接口只能被与它处在同一包中的成员访问。extends语句与类继承中的extends语句基本相同,不同点在于接口可以继承自多个父接口,父接口之间使用逗号分隔。

接口中定义的变量和方法都包含默认的修饰符,其中定义的变量默认声明为“public static final”,即全局静态常量,定义的方法默认声明为“public abstract”,即抽象方法。例如,定义一个Charge接口(收费接口),内有接口常量PORT_STYLE和成员方法getCharge(),代码如下:

interface Charge(){

int PORT_STYLE = 1; // 接口常量

void getCharge(); // 接口方法声明

}

7.2.2 接口实现

接口与抽象类相似,也包含抽象方法,因此不能直接实例化接口,即不能使用new创建接口的实例。但是,可以利用接口的特性来创造一个新的类,然后再用新类来创建对象,利用接口创建新类的过程称为接口的实现。实现接口的目的主要是在新类中重写抽象的方法,当然也可以使用抽象类来实现抽象方法的重写。

接口的实现需要使用implements关键字,即在声明一个类的同时用关键字implements来实现接口,实现接口的类一般称为接口的实现类,具体语法如下:

[修饰符]class 类名 implements 接口1,接口2, 接口3,...{ // 如果实现多个接口,以逗号隔开

...

}

一个类实现一个接口时,需要注意如下问题:

  如果实现接口的类不是抽象类,则该类必须实现接口的所有抽象方法。

  在类中实现接口的抽象方法时,必须使用与接口中完全一致的方法名,否则只是定义一个新的方法,而不是实现已有的抽象方法。

接下来,通过案例来演示接口的实现,如例7-2所示。

例7-2 Demo0702.java

1  package com.aaa.p070202;

2

3  interface PCI { // 定义PCI接口

4   String serialNumber = "9CC0AC186027";

5   void start();

6   void run();

7   void stop();

8  }

9  public class VideoCard implements PCI{ // 定义显卡类实现PCI接口

10   @Override

11   public void start() {

12   System.out.println("显卡开始启动");

13   }

14   @Override

15   public void run() {

16   System.out.println("显卡序列号是:" + serialNumber);

17   System.out.println("显卡开始工作");

18   }

19   @Override

20   public void stop() {

21   System.out.println("显卡停止工作");

22   }

23  }

24  public class Demo0702{

25   public static void main(String[] args) {

26   VideoCard videoCard=new VideoCard();

27   videoCard.start();

28   videoCard.run();

29   videoCard.stop();

30   }

31  }

程序的运行结果如下:

显卡序列号是:9CC0AC186027

显卡开始启动

显卡开始工作

显卡停止工作

从运行结果可以看到,程序中定义了一个PCI接口,其定义了一个全局常量serialNumber和3个抽象方法start()、run()、stop(),显卡类VideoCard实现了PCI接口的这3个抽象方法。在实现类的方法中调用接口的常量,在Demo0702测试类中创建了显卡类VideoCard的实例并输出运行结果。

7.2.3 接口的继承

在现实世界中,网络通信具有一定的标准,手机只有遵守相应的标准规范才可以使用相应的网络。然而,随着移动互联网技术的发展,网络通信标准从之前的2G、3G到目前的4G、5G,而且6G也已在研发之中。Java程序中的接口与网络通信标准类似,定义之后,随着技术的不断发展以及应用需求的不断增加,接口往往也需要更新迭代,主要是功能扩展,增加新的方法,以适应新的需求。但是,使用直接在原接口中增加方法的途径来扩展接口可能会带来问题:所有实现原接口的实现类都将因为原来接口的改变而不能正常工作。为了既能扩展接口,又不影响原接口的实现类,一种可行的方法是通过创建原接口的子接口来增加新的方法。

接口的继承与类的继承相似,都是使用extends关键字来实现继承,当一个接口继承父接口时,该接口会获得父接口中定义的所有抽象方法和常量。但是,接口的继承比类的继承要灵活,一个接口可以继承多个父接口,这样可以通过接口继承将多个接口合并为一个接口。接口继承的语法格式如下:

1  interface 接口名 extends 接口1,接口2,接口3,... {

2  ...

3  }

接下来,通过案例来演示接口的继承,如例7-3所示。

例7-3 Demo0703.java

1  package com.aaa.p070203;

2

3  interface I3G {

4   void onLine(); // 上网

5   void call(); // 打电话

6   void sendMsg(); // 发短信

7  }

8  interface I4G extends I3G{

9   void watchVideo(); // 看视频

10  }

11  class Nokia implements I3G{

12   @Override

13   public void call() {

14   System.out.println("打电话功能");

15   }

16   @Override

17   public void sendMsg() {

18   System.out.println("打发信息功能");

19   }

20   @Override

21   public void onLine() {

22   System.out.println("上网功能");

23   }

24  }

25  class Mi implements I4G{

26   @Override

27   public void call() {

28   System.out.println("打电话功能");

29   }

30   @Override

31   public void sendMsg() {

32   System.out.println("打发信息功能");

33   }

34   @Override

35   public void onLine() {

36   System.out.println("上网功能");

37   }

38   @Override

39   public void watchVideo() {

40   System.out.println("看视频功能");

41   }

42  }

43  public class Demo0703 {

44   public static void main(String[] args) {

45   System.out.println("Nokia手机使用第3代通信技术");

46   Nokia nokia = new Nokia();

47   nokia.call();

48   nokia.onLine();

49   nokia.sendMsg();

50   System.out.println("小米手机使用第4代通信技术");

51   Mi mi = new Mi();

52   mi.call();

53   mi.onLine();

54   mi.sendMsg();

55   mi.watchVideo();

56   }

57  }

程序的运行结果如图下:

Nokia手机使用第3代通信技术

打电话功能

上网功能

打发信息功能

小米手机使用第4代通信技术

打电话功能

上网功能

打发信息功能

看视频功能

从运行结果可以看到,I4G接口继承了I3G接口,直接继承了I3G接口中的3个抽象方法call()、onLine()、sendMsg(),并新增了一个抽象方法watchVide(),在main()方法中Nokia类实现I3G接口,从而实现父接口的3个抽象方法,而Mi类实现了I4G接口,实现了子接口的4个抽象方法。

编程技巧: 如果一个类同时继承类并继承某个接口,需要先extends父类,再implements接口,格式如下:

子类 extends 父类 implements [接口列表]{

...

}

7.2.4 利用接口实现多重继承

Java语言规定一个类只能继承一个父类,这给实际开发带来了许多困扰,因为许多类需要继承多个父类的成员才能满足需求,这种问题称为多重继承问题。然而,我们也不能将多个父类简单地合并成一个父类,因为每个父类都有自己的一套代码,合并到一起之后可能会出现同一方法的多种不同实现,由此会产生代码冲突,增加代码的不可靠性。有了接口以后多重继承问题就迎刃而解了,由于一个了可以实现多个接口,所以在程序设计的过程中我们可以把一些“特殊类”设计成接口,进而通过接口间接地解决多重继承问题。一个类实现多个接口时,在implements语句中分隔各个接口名,此时这些接口就可以被理解成特殊的类,而这种做法实际上就是使子类获得了多个父类的成员,并且由于接口成员没有实现细节,实现接口的类只能有一个具体的实现细节,从而避免了代码冲突,保证了Java代码的安全性和可靠性。

接下来,通过案例来演示利用接口实现多重继承,如例7-4所示。

例7-4 Demo0704.java

1  package com.aaa.p070204;

2

3  interface IFly { // 定义IFly接口

4   void takeoff(); // 起飞方法

5   void land(); // 落地方法

6   void fly(); // 飞行方法

7  }

8  interface ISail{ // 定义ISail接口

9   void dock(); // 停靠方法

10   void cruise(); // 航行方法

11  }

12  class Vehicle{ // 定义交通工具Vehicle类

13   private double speed;

14   void setSpeed(int sd){ // 设置速度方法

15   this.speed = sd;

16   System.out.println("设置速度为" + speed);

17   }

18   void speedUp(int num){ // 加速方法

19   this.speed += num;

20   System.out.println("加速" + num + ",速度变为" + speed);

21   }

22   void speedDown(int num){ // 减速方法

23   this.speed -= num;

24   System.out.println("减速" + num + ",速度变为" + speed);

25   }

26  }

27  class SeaPlane extends Vehicle implements IFly,ISail{ // 定义水上飞机类

28   public void takeoff() {

29   System.out.println("水上飞机开始起飞");

30   }

31   public void land() {

32   System.out.println("水上飞机开始落地");

33   }

34   public void fly() {

35   System.out.println("水上飞机可以飞行");

36   }

37   public void dock() {

38   System.out.println("水上飞机可以停靠");

39   }

40   public void cruise() {

41   System.out.println("水上飞机可以航行");

42   }

43  }

44  public class Demo0704 {

45   public static void main(String[] args) {

46   SeaPlane sp = new SeaPlane();

47   sp.takeoff();

48   sp.setSpeed(2)

49   sp.speedUp(2)

50   sp.fly();

51   sp.speedDown(2)

52   sp.land();

53   sp.cruise();

54   sp.speedDown(2)

55   sp.dock();

56   }

57  }

程序的运行结果如图下:

水上飞机开始起飞

设置速度为2

加速2,速度变为4

水上飞机可以飞行

减速2,速度变为2

水上飞机开始落地

水上飞机可以航行

减速2,速度变为0

水上飞机可以停靠

例7-4中,水上飞机类SeaPlane继承了交通工具类Vehicle,并且实现了IFly接口和ISail接口。从程序运行结果中可以看到,它时具有了交通工具的功能,还增加了飞行功能和航行功能。

7.2.5 接口默认方法

在程序开发中,如果之前创建了一个接口,并且已经被大量的类实现,当需要再添加新的方法以扩充这个接口的功能的时候,就会导致所有已经实现的子类都要重写这个方法。但是,在接口中使用默认方法就不会有这个问题,所以从 JDK8 开始新加了接口默认方法,便于接口的扩展。

接口默认方法是一个默认实现的方法,并且不强制实现类重写此方法,使用default关键字来修饰。接口新增的默认方法在实现类中可以直接使用。

接下来,通过案例来演示接口默认方法的使用,如例7-5所示。

例7-5 Demo0705.java

1  package com.aaa.p070205;

2

3  public interface ICat { // 定义ICat接口

4   void play(); // 抽象方法

5   default void run(){ // 默认方法

6   System.out.println("猫咪在跑,猫步...");

7   }

8  }

9  class BlackCat implements ICat{ // 黑猫类实现了ICat接口

10   @Override

11   public void play() { // 重写ICat接口的抽象方法

12   System.out.println("黑猫在玩耍...");

13   }

14  }

15  public class Demo0705 {

16   public static void main(String[] args) {

17   BlackCat cat = new BlackCat();

18   cat.play();

19   cat.run();

20   }

21  }

程序的运行结果如下:

黑猫在玩耍...

猫咪在跑,猫步...

例7-5中,ICat接口定义了抽象方法play()和默认方法run(), BlackCat类实现了ICat接口,并重写了抽象方法play(),通过测试类Demo0702中的main()方法创建了BlackCat类的实例,调用play()方法和run()方法后发现,ICat接口接口的默认方法run()可以被它的实现类的对象直接调用。

注意:接口允许定义多个默认方法,其子类可以实现多个接口,因此接口默认方法可能会出现同名情况,此时子类在实现或者调用默认方法时通常遵循以下原则:

(1)子类中的同名方法优先级最高。

(2)如果第一条无法进行判断,那么子接口的优先级更高;函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果接口B继承了接口A,那么接口B就比接口A更加具体。

7.2.6 接口实现多态

在之前的章节中,我们讲解了使用继承机制来实现多态。事实上,使用接口也同样可以实现多态。

接下来,通过某汽车销售店案例演示如何通过接口实现多态,如例7-6所示。

例7-6 Demo0706.java

1  package com.aaa.p070206;

2

3  interface ICar{ // 定义ICar接口

4   String showName();

5   double getPrice();

6  }

7  class Haval implements ICar{ // 定义Haval汽车类

8   @Override

9   public String showName() {

10   return "哈佛SUV";

11   }

12   @Override

13   public double getPrice() {

14   return 150000;

15   }

16  }

17  class GreatWall implements ICar{ // 定义GreatWall汽车类

18   @Override

19   public String showName() {

20   return "长城汽车";

21   }

22   @Override

23   public double getPrice() {

24   return 68000;

25   }

26  }

27  class CarShop{ // 定义汽车销售店CarShop类

28   private double money = 0; // 定义销售金额成员

29   public void sellCar(ICar car){ // 定义销售汽车方法

30   System.out.println("车型:" + car.showName() + "价格:" + car.getPrice());

31   money += car.getPrice();

32   }

33

34   public double getMoney(){ // 定义获取金额方法

35   return money;

36   }

37  }

38

39  public class Demo0706 { // 测试类

40   public static void main(String[] args) {

41   CarShop shop = new CarShop();

42   Haval haval = new Haval(); // Haval类的对象

43   shop.sellCar(haval);

44   GreatWall greatWall = new GreatWall(); // GreatWall类对象

45   shop.sellCar(greatWall);

46   System.out.println("销售总收入:" + shop.getMoney());

47   }

48  }

程序的运行结果如下:

车型:哈佛SUV价格:150000.0

车型:长城汽车价格:68000.0

销售总收入:218000.0

例7-6中,ICar接口定义了抽象方法showName()和getPrice(), Haval类和GreatWall类实现了ICar接口,汽车销售店类CarShop针对实现ICar接口的实现类进行销售并汇总金额。在测试类 Demo0705的main 方法中创建 CarShop类的实例shop、Haval类的实例haval、GreatWall类的greatWall,通过shop对象的sellCar()方法对汽车对象havel和greatWall销售,并调用getMoney()方法统计销售总收入。这样,我们便通过ICar接口实现了多态,

7.2.7 抽象类和接口的比较

抽象类与接口是Java中对于抽象类定义进行支持的两种机制,抽象类和接口都用于为对象定义共同的行为,二者比较如下:

  抽象类和接口都包含抽象方法。

  抽象类可以有非抽象方法,接口中如果要定义为非抽象方法,需要标注为接口默认方法。

  接口中只能有常量,不能有变量;抽象类中既可以有常量,也可以有变量。

  一个类可以实现多个接口,但只能继承一个抽象类。

在程序设计时,应该根据具体业务需求来确定是使用抽象类还是接口。如果子类需要从父类继承一些变量或继承一些抽象方法、非抽象方法,可以考虑使用抽象类;如果一个类不需要继承,只需要实现某些重要的抽象方法,可以考虑使用接口。

知识点拨:在面向接口编程的思想中,接口只用关心操作,但不用关心这些操作的具体实现细节,可以使开发者将主要精力用来程序设计。通过面向接口编程,可以降低类与类、类与接口、层与层之间的耦合度。当设计和实现分离的时候,面向接口编程是一种解决问题的很好方式。

7.3 内部类

大多数情况下,类被定义为一个独立的程序单元。但在某些情况下,也可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类(也称嵌套类),包含内部类的类也被称为外部类。内部类包括4种:成员内部类、局部内部类、静态内部类和匿名内部类。

一般情况下,内部类有如下几个属性:

  内部类和外部类由Java编译器编译后生成的两个类是独立的。

  内部类是外部类的一个成员,可以使用外部类的类变量和实例变量,也可以使用外部类的局部变量。

  内部类可以被protected或private修饰。当一个类中嵌套另一个类时,访问保护并不妨碍内部类使用外部类的成员。

  内部类被static修饰后,不能再使用局部范围中或其他内部类中的数据和变量。

7.3.1 成员内部类

成员内部类是最普通的内部类,它定义于另一个类的内部,与外部类的成员变量、成员方法同级。成员内部类可以访问外部类的所有成员,外部类同样可以其成员内部类的所有成员。但是,成员内部类是依附外部类而存在的,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。

在外部类外创建一个内部类对象的语法格式如下:

外部类名.内部类名 引用变量名 = new 外部类名().new 内部类名()

接下来,通过案例来演示成员内部类的使用,如例7-7所示。

例7-7 Demo0707.java

1  package com.aaa.p070301;

2

3  class Outer { // 定义外部类

4   private String name = "外部类Outer";

5   private int num = 666;

6

7   class Inner{ // 定义内部类

8   private String name = "内部类Inner"; // 定义类成员

9   public void accessOuter()

10   {

11   System.out.println("在成员内部类中访问内部类的name:" + name);

12   // Outer.this表示外部类对象

13   System.out.println("在成员内部类中访问外部类的name:" + Outer.this.name);

14   System.out.println("在成员内部类中访问外部类的num:" + num);

15   }

16   }

17  }

18

19  public class Demo0707 {

20   public static void main(String[] args) {

21   Outer.Inner inner = new Outer().new Inner();

22   inner.accessOuter();

23   }

24  }

程序的运行结果如下:

在成员内部类中访问内部类的name:内部类Inner

JAVA编程讲义.抽象类、接口和内部类

在成员内部类中访问外部类的name:外部类Outer

在成员内部类中访问外部类的num:666

例7-7中,外部类Outer中定义了一个成员内部类Inner,在Inner类的成员方法accessOuter ()中访问其自身的成员变量name以及其外部类Outer的成员变量name和num,由于内部类和外部类的成员变量name重名,所以不能直接访问,只能用“Other.this.name”的形式进行访问,其中“Outer.this”表示外部类对象,而num只存在于外部类,内部类可以直接访问。

注意:成员内部类不能定义静态变量、静态方法和静态内部类。

7.3.2 局部内部类

局部内部类是指在成员方法中定义的类,这种类是局部的,和局部变量类似,只能在该方法或条件的作用域内使用,超出这些作用域就无法引用。

局部内部类的优势是,对于外部类完全隐藏,即使是包含他的外部类也是不可见的,是不能直接访问的,只有在方法中才可以创建内部类的实例并访问类中的方法和属性。

局部内部类的特点如下:

  局部内部类不允许使用访问权限修饰符(public、private、protected)

  局部内部类对外部完全隐藏,除了创建这个类的方法可以访问它以外,其他地方均不能访问。

接下来,通过案例来演示局部内部类的使用,如例7-8所示。

例7-8 Demo0708.java

1  package com.aaa.p070302;

2

3  class Outer{ // 定义外部类

4   private static String name = "外部类Outer";

5   private int num = 666;

6   public void display(){

7   int count = 5;

8   //局部内部类即嵌套在方法里面

9   class Inner{

10   public void accessOuter(){

11   System.out.println("在局部内部类中访问外部方法的变量:" + (count));

12   System.out.println("在局部内部类中访问外部类的name:" + Outer.name);

13   System.out.println("在局部内部类中访问外部类的num:" + num);

14   }

15   }

16   //局部内部类,在方法内部调用

17   new Inner().accessOuter();

18   }

19  }

20  public class Demo0708 {

21   public static void main(String[] args) {

22   Outer outer = new Outer();

23   outer.dispaly();

24   }

25  }

程序的运行结果如下:

在局部内部类中访问外部方法的变量:5

在局部内部类中访问外部类的name:外部类Outer

在局部内部类中访问外部类的num:666

例7-8中,外部类Outer的display()方法定义了一个内部类Inner,Inner类只能在display()方法中创建其实例对象并调用自身方法accessOuter(),该方法调用了外部类Outer的成员变量name和num以及display()方法内的局部变量count。从运行结果中发现,都可以正常输出,但是如果把第11行代码中的“count”后面加上“++”之后,会编译报错,因为局部变量是随着方法的调用而调用,随着调用结束而消失,但是我们调用局部内部类时创建的对象依旧在堆内存中,并没有被回收,如果访问的局部变量不是用final修饰的,当方法调用完毕后,依然存在堆内存中的对象就会出现找不到局部变量的问题,而被final修饰的变量可以看成是一个常量,存在于常量池中,不会被立刻回收。所以,针对局部内部类来说,它可以访问方法中的局部变量但不能进行修改。

注意:JDK1.8之后,即使不加final修饰符,系统也会默认加上。

7.3.3 静态内部类

静态内部类是指用static关键字修饰的成员内部类。静态内部类可以包含静态成员和非静态成员(实例成员),根据静态成员不能访问非静态成员的规则,静态内部类不能直接访问外部类的非静态成员,只能访问外部类的静态成员(即类成员)。创建静态内部类对象的语法格式如下:

外部类名.内部类名 引用变量名 = new 外部类名.内部类名()

接下来,通过案例来演示静态内部类的使用,如例7-9所示。

例7-9 Demo0709.java

1  Package com.aaa.p070303;

2

3  class Outer{

4   private static String name = "外部类Outer"; // 定义类静态成员

5   private static int num = 666;

6   static class Inner { // 定义静态内部类

7   public static String name = "内部类Inner"; // 定义类静态成员

8   public void accessOuter() {

9   // 静态内部类成员方法中访问外部类私有成员变量

10   System.out.println("在静态内部类中访问外部类的name:"+Outer.name);

11   System.out.println("在静态内部类中访问外部类的num:" + num);

12   }

13   }

14  }

15  public class Demo0709 {

16   public static void main(String[] args) {

17   System.out.println("静态内部类:" + Outer.Inner.name);

18   Outer.Inner obj = new Outer.Inner(); // 创建静态内部类对象

19   obj. accessOuter();

20

21   }

22  }

程序的运行结果如下所示。

静态内部类:内部类Inner

在静态内部类中访问外部类的name:外部类Outer

在静态内部类中访问外部类的num:666

例7-9中,Outer外部类中定义了一个静态内部类Inner,Inner类包含了静态成员变量name和成员方法accessOuter ()。访问静态内部类的静态成员变量,可以使用“外部类名.静态内部类名.静态成员变量”的形式;访问静态内部类的实例成员,则要先创建静态内部类对象,通过“new 外部类名.静态内部类名()”的形式访问。如果将第5行代码的“static”去掉,则在第11行代码调用的时候会报错,因为num属于外部类的非静态变量,不可以被其静态内部类直接访问。

注意:静态内部类不需要依赖外部类就可以直接创建;静态内部类不可以使用任何外部类的非static成员(包括变量和方法)。

7.3.4 匿名内部类

匿名内部类是一个没有显式名字的内部类。本质上看,它会隐式地继承一个类或者实现一个接口。换句话说,匿名内部类是一个继承了某个类或者实现了某接口的子类匿名对象。创建匿名内部类的语法格式如下:

new 类名/接口名/抽象类名(){

… // 匿名内部类实现部分

}

匿名内部类具有局部内部类的所有特点,同时它还具有如下特点:

  匿名内部类必须继承一个类或者实现一个接口,类名前面不能有修饰符。

  匿名内部类没有类名,因此没有构造方法。

  匿名内部类创建之后只能使用一次,不能重复使用。

匿名内部类是我们平时编写代码时用得比较多的内部类,在编写事件监听的代码时使用匿名内部类不但可简化程序,而且可使代码更加容易维护。

接下来,通过案例来演示匿名内部类的使用,如例7-10所示。

例7-10 Demo0710.java

1  package com.aaa.p070304;

2

3  interface Inner { // 定义接口

4   void getName(String name);

5  }

6  public class Demo0710 {

7   public static void main(String[] args) {

8   new Inner(){ // 定义匿名类,并实现Inner接口

9   public void getName(String name) { // 重写getName()方法

10   System.out.println("我是匿名类的方法,获取name为:" + name);

11   }

12   }.getName("张三");

13   }

14  }

程序的运行结果如下:

我是匿名类的方法,获取name为:张三

例7-10中,在外部类Demo0710的main方法中创建了匿名内部类“Inner的对象”,并调用该类的成员方法getName (),传入参数“张三”,在创建Inner对象的时候,并没有给对象赋予名称,即“匿名”之意。

想一想:使用匿名内部类的优点和缺点有哪些?匿名内部类的使用场景有哪些?

7.4 本章小结

  抽象类的主要作用是建立对象的抽象模型,定义通用的方法,起到类似“模板”的作用。抽象类中包含一般方法和抽象方法。抽象方法是没有方法体的方法,由抽象类的子类来定义实现。抽象类不能直接产生对象。包含抽象方法的类必须是抽象类,但抽象类可以不包含抽象的方法。

  接口只规定了一个类的基本形式,不涉及任何实现细节。通过接口来创建类,称为接口的实现,该类为接口的实现类。Java语言中不允许类的多重继承,但可以通过接口实现多重继承。

  Java支持在一个类A中声明一个类B,这样的类B称为内部类,类A称为内部类的外部类。内部类可以分为成员内部类、局部内部类、静态内部类、匿名内部类。内部类隐藏可以不想让用户知道的操作,极高的封装性。内部类对象可以访问创建它的外部类对象的内容,为开发者在设计时提供了更多的思路和捷径。匿名内部类一般用在写事件监听的代码时候,可以简化代码并使代码容易维护。

7.5 理论习题与实践练习

1.填空题

1.1 Java中使用____________关键字,来表示抽象的意思。

1.2 Java中使用____________关键字,来实现接口的继承。

1.3 __________是定义在类中的类,主要作用是将逻辑上相关的类放在一起。

2.选择题

2.1 以下关于Java语言抽象类的说法正确的是( )

A.抽象类可以直接实例化  B.抽象类不能有子类

C.抽象类可以多继承  D.抽象类可以有非抽象方法

2.2 内部类不包括()

A.成员内部类  B.静态内部类  C.匿名内部类  D.超级内部类

2.3 下列选项中,用于定义接口的关键字是( )

A.interface B.implements C.abstract D.class

2.4 下列选项中,接口使用时不可以加的关键字是( )

A.public B.static C.private D.final

2.5 下列选项中,可以实现类的多重继承 的是( )

A.类 B.基本类型 C.抽象类  D.接口

3.思考题

3.1 请描述什么是抽象类?

3.2 请描述如何使用接口实现多态?

3.3 请描述向上转型和向下转型的区别?

3.4 什么是接口?请简述抽象类和接口的区别?

3.5  采用内部类的好处是什么?

4.编程题

4.1 陆地上最常用的交通工具是汽车,海上最常用的交通工具是轮船,空中最常用的交通工具是飞机。现在要求实现一种海陆空三栖航行的交通工具。可以通过接口实现需求,实现思路参考如下:

  创建汽车接口Motor,定义一个run (方法。

  创建轮船接口Steamer,定义一个sailing ( )方法。

  创建飞机接口P1ane,定义一个f1y (方法。

  创建类SuperVehic 1e实现上述三个接口。

  创建测试类进行测试。

4.2 模拟摄影师照相的过程,要求摄影师可以通过相机拍照也可以通过其他的设备照相,譬如:手机:摄影师可以拍人像、风景等任何物体。要求该系统具备良好的可维护性可扩展性。

  摄影师可以对任何物体拍照,并没有局限于一个具体类别,所以先设计一个接口。

  创建一个拍照接口,实现该接口的类都可以拍照,手机、相机、iPad等等。

  创建摄影师类,实现摄影方法。

  创建接口的实现类。

  创建测试类,运行系统。

Java

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python 中的按位运算符2 |【生长吧!Python!】 【生长吧!Python】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/278897
下一篇:Android中的绘图
相关文章