打好Java基础,从使用内部类开始!

开发 后端
比起面向对象编程中其他的概念来,接口和内部类更深奥复杂,比如 C++ 就没有这些。将两者结合起来,可以解决 C++ 中用多重继承所能解决的问题,然后,多重继承在 C++ 中被证明是相当难以使用的,相比较而言,Java 的接口和内部类就容易理解多了!

本文转载自微信公众号「小菜良记」,作者蔡不菜丶 。转载本文请联系小菜良记公众号。

本文主要介绍 Java中内部类的用法

今天又周五了呀,正在想明天周六有啥安排的时候,一声惊讶声打断了我

小蔡小菜,你看看这组代码,好灵活啊

听到领桌小王的惊讶,我扭头看了下他的屏幕,这不就是内部类么。用的好当然就灵活啦,只是我们平常没怎么用。

内部类用的好真的好灵活呀,我对这一块还不是很熟悉,看来还得多学习学习!小菜,看你的样子好像挺了解的,你能给我讲讲吗?

看着小王如饥似渴的眼色,我不由有点心虚,内心活动也是极其复杂:我平时也没咋用,只是有个大概的了解,讲出来不就献丑了,连忙声道:

好说好说,不过今天都周五了,也不差这一时半会,咱们还是想想明天有啥活动,等下周来我再给你好好讲讲!

小王仿佛被我忽悠过去了,也没看到我眼神中的慌乱,答应了下来。

好在有惊无险,周末还能有啥安排,赶紧把内部类安排上!

初识

比起面向对象编程中其他的概念来,接口和内部类更深奥复杂,比如 C++ 就没有这些。将两者结合起来,可以解决 C++ 中用多重继承所能解决的问题,然后,多重继承在 C++ 中被证明是相当难以使用的,相比较而言,Java 的接口和内部类就容易理解多了!

一、内部类如何创建

内部类,顾名思义就是类中类,将类定义在外围类里面:

  1. public class Animal { 
  2.  
  3.     class Monkey{ 
  4.         private String name = "monkey"
  5.  
  6.         public String getName() { 
  7.             return name
  8.         } 
  9.     } 
  10.  
  11.     class Pig { 
  12.         private String color; 
  13.  
  14.         Pig(String color) { 
  15.             this.color = color; 
  16.         } 
  17.  
  18.         String getColor() { 
  19.             return color; 
  20.         } 
  21.     } 
  22.  
  23.     public void getAnimal(String note) { 
  24.         Monkey monkey = new Monkey(); 
  25.         Pig pig = new Pig(note); 
  26.         System.out.println(pig.getColor()); 
  27.     } 
  28.  
  29.     public static void main(String[] args) { 
  30.         Animal animal = new Animal(); 
  31.         animal.getAnimal("pink"); 
  32.     } 
  33. /* OUTPIT: 
  34. pink 
  35. */ 

因为Monkey和Pig两个类是定义在 Animal 类中,因此使用起这两个内部类跟使用普通类没什么区别。下面这组代码相信小伙伴也不陌生:

  1. public class Animal { 
  2.  
  3.     class Monkey{ 
  4.     } 
  5.  
  6.     class Pig { 
  7.     } 
  8.  
  9.     public Monkey getMonkey() { 
  10.         return new Monkey(); 
  11.     } 
  12.  
  13.     public Pig getPig() { 
  14.         return new Pig(); 
  15.     } 
  16.  
  17.     public static void main(String[] args) { 
  18.         Animal animal = new Animal(); 
  19.         Animal.Monkey monkey = animal.getMonkey(); 
  20.         Animal.Pig pig = animal.getPig(); 
  21.     } 

通过定义方法,来返回执行内部类的引用。不知道细心的小伙伴有没有注意到内部类的引用有点奇怪:Animal.Monkey。这也是内部类的区别之一,如果要在外部类的非静态方法之外获取某个内部类的对象,需要「具体指明这个对象的类型」:OuterClassName.InnerClassName

二、内外相连

内部类存在于外部类里层,因此也具有一定特权:内部类可以访问外围对象的所有成员,不需要任何特殊条件,此外,内部类还拥有外部类的所有元素的访问权。

  1. public class OuterArray { 
  2.     private Integer[] ints; 
  3.     private int next = 0; 
  4.  
  5.     public OuterArray(int size) { 
  6.         ints = new Integer[size]; 
  7.     } 
  8.  
  9.     public void add(int x) { 
  10.         if (next < ints.length) { 
  11.             ints[next++] = x; 
  12.         } 
  13.     } 
  14.  
  15.     class InnerArray { 
  16.         private int i = 0; 
  17.  
  18.         public boolean end() { 
  19.             return i == ints.length; 
  20.         } 
  21.  
  22.         public int current() { 
  23.             return ints[i]; 
  24.         } 
  25.  
  26.         public void next() { 
  27.             if (i < ints.length) { 
  28.                 i++; 
  29.             } 
  30.         } 
  31.     } 
  32.  
  33.     public static void main(String[] args) { 
  34.         OuterArray outerArray = new OuterArray(10); 
  35.         for (int i = 0; i < 10; i++) { 
  36.             outerArray.add(i); 
  37.         } 
  38.         InnerArray innerArray = outerArray.new InnerArray(); 
  39.         while (!innerArray.end()) { 
  40.             System.out.print(innerArray.current()+" "); 
  41.             innerArray.next(); 
  42.         } 
  43.     } 

上组代码中我们可以看到,InnerArray可以访问到OuterArray中的每一个属性,就像自己拥有它们一样,这带来了很大的方便。

三、new 和 this

这两个关键字我们肯定都不陌生了,我们平时用到最多的肯定就是new一个对象出来。

  1. public class OuterClass { 
  2.  
  3.     class InnerClass { 
  4.     } 
  5.  
  6.     public static void main(String[] args) { 
  7.         OuterClass outer = new OuterClass(); 
  8.     } 

当我们需要OuterClass对象的时候,我们顺手就来了个new OuterClass(),但是如果我们需要的是InnerClass对象,那么又该如何处理呢?答案便是:

  1. InnerClass inner = outer.new InnerClass(); 

可能觉得有点奇怪,为什么此处的new需要以OuterClass对象引用,这是因为内部类对象会暗暗地连接到创建它的外部类对象上,因此必须使用外部类的对象来创建内部类对象。如果你创建的是「嵌套类」(静态内部类),那么它就不需要对外部类对象的引用。

this关键字是用来生成对外部类对象的引用,这样产生的引用自动具有正确的类型:

  1. public class OuterClass { 
  2.  
  3.     class InnerClass { 
  4.         public OuterClass getOuterClass() { 
  5.             return OuterClass.this; 
  6.         } 
  7.     } 
  8.  
  9.     public static void main(String[] args) { 
  10.         OuterClass outer = new OuterClass(); 
  11.         InnerClass inner = outer.new InnerClass(); 
  12.         OuterClass outerClass = inner.getOuterClass(); 
  13.     } 

四、局部内部类

我们上面看到的内部类都是定义在外部类中,这也是内部类的典型用处。但是,我们也可以在一个方法里面或者任意的作用域里面定义内部类。这种也被称为局部内部类:

  1. public class OuterClass { 
  2.  
  3.     public Animal getPig(String color) { 
  4.         class Pig extends Animal { 
  5.             @Override 
  6.             public void getAnimal(String color) { 
  7.                 super.getAnimal(color); 
  8.             } 
  9.         } 
  10.         return new Pig(); 
  11.     } 
  12.  
  13.     public static void main(String[] args) { 
  14.         OuterClass outerClass = new OuterClass(); 
  15.         Animal pink = outerClass.getPig("pink"); 
  16.     } 

Pig类是getPig()方法的一部分,而不是OuterClass的一部分,所以在getPig()之外不能访问Pig类。

五、匿名内部类

在了解什么是匿名内部类之前,我们先看一组代码:

  1. public class OuterClass { 
  2.  
  3.     public Animal animal() { 
  4.         return new Animal(){ 
  5.             private String name = "monkey"
  6.             @Override 
  7.             public String toString() { 
  8.                 return "animal{" + 
  9.                         "name='" + name + '\'' + 
  10.                         '}'
  11.             } 
  12.         }; 
  13.     } 
  14.  
  15.     public static void main(String[] args) { 
  16.         OuterClass outerClass = new OuterClass(); 
  17.         System.out.println(outerClass.animal()); 
  18.     } 
  19. /* OUTPUT
  20. animal{name='monkey'
  21. */ 

animal()这个方法将返回值的生成与表示这个返回值的类定义结合在一起。而且这个类是匿名的,它没有名字,正常形式应该是这样的:

  1. public class OuterClass { 
  2.  
  3.     class Monkey extends Animal { 
  4.         private String name = "monkey"
  5.  
  6.         @Override 
  7.         public String toString() { 
  8.             return "animal{" + 
  9.                     "name='" + name + '\'' + 
  10.                     '}'
  11.         } 
  12.     } 
  13.      
  14.     public Animal animal() { 
  15.         return new Monkey(); 
  16.     } 

匿名类再访工厂:

  1. public interface Service { 
  2.     void method1(); 
  3. interface ServiceFactory{ 
  4.     Service getService(); 
  5.  
  6. class Implementation1 implements Service { 
  7.  
  8.     private Implementation1(){} 
  9.  
  10.     @Override 
  11.     public void method1() { 
  12.         System.out.println("Implementation1.method1()"); 
  13.     } 
  14.  
  15.     public static ServiceFactory factory = new ServiceFactory() { 
  16.         @Override 
  17.         public Service getService() { 
  18.             return new Implementation1(); 
  19.         } 
  20.     }; 
  21.  
  22. class Factories{ 
  23.     public static void main(String[] args) { 
  24.         ServiceFactory factory = Implementation1.factory; 
  25.         Service service = factory.getService(); 
  26.         service.method1(); 
  27.     } 

通过内部类获取外部类的实现,这样子Implementation1的构造器都可以是private的,并且没有任何必要去创建作为工厂的具体类,这样所产生的语法也更具有实际意义,也可以运用在单例模式中。

六、嵌套类

如果不需要内部类对象与外围类之间有联系,就可以将内部类声明为static,这通常称为嵌套类。普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象,然而,当内部类是static的时候,就意味着:

要创建嵌套类的对象,并不需要其外围类的对象

不能从嵌套类的对象中访问非静态的外围类对象

  1. public class NestClass { 
  2.      
  3.     static class InnerNestClass{ 
  4.     } 
  5.  
  6.     public static InnerNestClass get() { 
  7.         return new InnerNestClass(); 
  8.     } 
  9.  
  10.     public static void main(String[] args) { 
  11.         InnerNestClass innerNestClass = get(); 
  12.     } 

在main()方法中没有任何NestClass对象是必须的,而是使用选取static成员的普通语法来调用方法。

接口内部类

正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动是public和static的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外部类的接口:

  1. public interface ClassInterface { 
  2.  
  3.     void test(); 
  4.  
  5.     class Test implements ClassInterface { 
  6.  
  7.         @Override 
  8.         public void test() { 
  9.             System.out.println("接口中的嵌套类"); 
  10.         } 
  11.  
  12.         public static void main(String[] args) { 
  13.             new Test().test(); 
  14.         } 
  15.     } 

如果你想要的创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便,尽管在 Java 8 之后可以使用 default 来默认实现接口方法。

七、继承内部类

内部类作为一种类,被继承当然也是被允许的。但是因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,那个指向外围类对象的引用必须被初始化,而在导出类中不再存在可连接的默认对象:

可以看到,通过这样继承是会报错的,解决方法便是:

  1. class ExtendClass { 
  2.     class Inner{} 
  3.  
  4. class WithInner extends ExtendClass.Inner { 
  5.     public WithInner(ExtendClass extendClass) { 
  6.         extendClass.super(); 
  7.     } 

因此我们需要记住,如果要继承一个内部类的时候,必须在构造器内使用外部类.super(),这样才能提供了必要的引用,然后程序才能编译通过。

八、覆盖内部类?

当子类继承父类时,子类可以覆盖父类的方法。那么问题来了,内部类能否被覆盖?我们通过看一组代码来找找答案:

  1. public class Flower { 
  2.  
  3.     class Bud{ 
  4.         public Bud(){ 
  5.             System.out.println("Flower.Bud"); 
  6.         } 
  7.     } 
  8.  
  9.     public Flower(){ 
  10.         System.out.println("new Flower()"); 
  11.         new Bud(); 
  12.         test(); 
  13.     } 
  14.  
  15.     public void test() { 
  16.         System.out.println("Flower.test()"); 
  17.     } 
  18.  
  19. class Flower2 extends Flower{ 
  20.  
  21.     class Bud{ 
  22.         public Bud(){ 
  23.             System.out.println("Flower2.Bud"); 
  24.         } 
  25.     } 
  26.     public void test() { 
  27.         System.out.println("Flower2.test()"); 
  28.     } 
  29.  
  30.     public static void main(String[] args) { 
  31.         new Flower2(); 
  32.     } 
  33. /* OUTPUT 
  34. new Flower() 
  35. Flower.Bud 
  36. Flower2.test() 
  37. */ 

从这个例子中我们可以看到,当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化,这两个内部类是完全独立的两个实体,各自在自己的命名空间内。

九、为什么要使用内部类?

我们在回答这个问题之前先明白一件事情:

「每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响」

这句话很清楚的说明了内部类的能力,如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决,从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,为内部类有效地实现了"多重继承"。

呼~ 终于把内部类复习的差不多了,乍看时间,今天都周末了呀!看来周末又没安排计划咯,不过这周过的还挺充实的,把基础巩固了一下,周一的时候还可以跟小王好好唠唠,想到这里,小菜不禁又陷入无限的幻想!

 

责任编辑:武晓燕 来源: 小菜良记
相关推荐

2011-03-29 14:11:15

内部类

2020-01-15 11:14:21

Java算法排序

2020-12-14 10:23:23

Java内部类外部类

2009-07-29 09:18:49

Java内部类

2020-01-12 19:10:30

Java程序员数据

2016-05-10 11:22:13

软件定义IT基础

2009-06-11 13:08:29

Java内部类Java编程思想

2023-10-19 13:24:00

Java工具

2023-03-06 07:53:36

JavaN种内部类

2010-04-06 09:16:08

CentOS系统

2011-07-21 15:44:33

Java内部类

2019-12-23 14:32:38

Java内部类代码

2012-04-17 11:21:50

Java

2015-12-08 09:05:41

Java内部类

2020-04-17 10:26:27

IT成本领导者成本优化

2021-02-08 08:45:18

Java内部类Object类

2009-08-26 18:00:07

C#内部类

2010-02-05 15:32:33

Java内部类

2020-10-29 08:31:15

Java同步回调编程语言
点赞
收藏

51CTO技术栈公众号