面向对象编程(十六)——内部类详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象编程(十六)——内部类详解相关的知识,希望对你有一定的参考价值。

一、内部类(innerclasses)

  一般情况,我们把类定义成独立的单元。有些情况下,我们把一个类放在另一个类的内部定义,称为内部类

  在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类局部内部类匿名内部类静态内部类

1. 内部类的作用

  • 内部类提供了更好的封装,只能让外部类直接访问,不允许同一个包中的其他类直接访问
  • 内部类可以直接访问外部类的私用属性。内部类被当成其外部类的成员。但是外部类不能访问内部类的内部属性

2. 内部类的使用场合

  由于内部类提供了更好的封装特性,并且可以很方便的访问外部类的属性,所以,通常内部类在只为所在外部类提供服务的情况下优先使用。

3. 内部类的基本结构

实例1:内部类的基本结构

 1 //外部类
 2 class Out {
 3     private int age = 12;
 4      
 5     //内部类
 6     class In {
 7         public void print() {
 8             System.out.println(age);
 9         }
10     }
11 }
12  
13 public class Demo {
14     public static void main(String[] args) {
15         Out.In in = new Out().new In();
16         in.print();
17         //或者采用下种方式访问
18         /*
19         Out out = new Out();
20         Out.In in = out.new In();
21         in.print();
22         */
23     }
24 }

运行结果:12

从上面的例子不难看出,内部类其实严重破坏了良好的代码结构,但为什么还要使用内部类呢?

因为内部类可以随意使用外部类的成员变量(包括私有)而不用生成外部类的对象,这也是内部类的唯一优点

如同心脏可以直接访问身体的血液,而不是通过医生来抽血

 

程序编译过后会产生两个.class文件,分别是Out.class和Out$In.class

其中$代表了上面程序中Out.In中的那个 .

Out.In in = new Out().new In()可以用来生成内部类的对象,这种方法存在两个小知识点需要注意:

  1.开头的Out是为了标明需要生成的内部类对象在哪个外部类当中

  2.必须先有外部类的对象才能生成内部类的对象,因为内部类的作用就是为了访问外部类中的成员变量

实例2:内部类中的变量访问形式

 1 class Out {
 2     private int age = 12;
 3      
 4     class In {
 5         private int age = 13;
 6         public void print() {
 7             int age = 14;
 8             System.out.println("局部变量:" + age);
 9             System.out.println("内部类变量:" + this.age);
10             System.out.println("外部类变量:" + Out.this.age);
11         }
12     }
13 }
14  
15 public class Demo {
16     public static void main(String[] args) {
17         Out.In in = new Out().new In();
18         in.print();
19     }
20 }

运行结果:

局部变量:14
内部类变量:13
外部类变量:12

从实例1中可以发现,内部类在没有同名成员变量和局部变量的情况下,内部类会直接访问外部类的成员变量,而无需指定Out.this.属性名

否则,内部类中的局部变量会覆盖外部类的成员变量

而访问内部类本身的成员变量可用this.属性名,访问外部类的成员变量需要使用Out.this.属性名

二、内部类的分类

主要分为两大类:成员内部类和匿名内部类。

1. 成员内部类

可以使用private、protected、public 任意进行修饰。

成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

 1 class Circle {
 2     double radius = 0;
 3      
 4     public Circle(double radius) {
 5         this.radius = radius;
 6     }
 7      
 8     class Draw {     //内部类
 9         public void drawSahpe() {
10             System.out.println("drawshape");
11         }
12     }
13 }

这样看起来,类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。

 1 class Circle {
 2     private double radius = 0;
 3     public static int count =1;
 4     public Circle(double radius) {
 5         this.radius = radius;
 6     }
 7      
 8     class Draw {     //内部类
 9         public void drawSahpe() {
10             System.out.println(radius);  //外部类的private成员
11             System.out.println(count);   //外部类的静态成员
12         }
13     }
14 }

不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

外部类.this.成员变量
外部类.this.成员方法

虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:

class Circle {
    private double radius = 0;
 
    public Circle(double radius) {
        this.radius = radius;
        getDrawInstance().drawSahpe();   //必须先创建成员内部类的对象,再进行访问
    }
     
    private Draw getDrawInstance() {
        return new Draw();
    }
     
    class Draw {     //内部类
        public void drawSahpe() {
            System.out.println(radius);  //外部类的private成员
        }
    }
}

成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:

 1 public class Test {
 2     public static void main(String[] args)  {
 3         //第一种方式:
 4         Outter outter = new Outter();
 5         Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建
 6          
 7         //第二种方式:
 8         Outter.Inner inner1 = outter.getInnerInstance();
 9     }
10 }
11  
12 class Outter {
13     private Inner inner = null;
14     public Outter() {
15          
16     }
17      
18     public Inner getInnerInstance() {
19         if(inner == null)
20             inner = new Inner();
21         return inner;
22     }
23       
24     class Inner {
25         public Inner() {
26              
27         }
28     }
29 }

内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。

成员内部类又分为非静态内部类静态内部类。

1.1 非静态内部类

非静态内部类和普通成员差不多。

注意:

  • 非静态内部类必须存在一个外部类对象里。因此,如果有一个非静态内部类对象,那么一定存在对象的外部类对象。非静态内部类对象单独属于外部类的某个对象
  • 非静态内部类可以使用外部类的成员,但是外部类不能直接访问非静态内部类成员。
  • 非静态内部类不能有静态方法、静态属性、静态初始化块。
  • 静态成员不能访问非静态成员,外部类的静态方法、静态代码块不能访问非静态内部类。包括不能使用非静态内部类定义变量、创建实例。
  • 成员变量访问要点:
    • 内部类里方法的局部变量:变量名
    • 内部类属性:this.变量名
    • 外部类属性:外部类名.this.变量名
  • 内部类的访问:
    • 外部类中定义内部类:new innerClass()
    • 外部类以外的地方使用非静态内部类。

1.2 静态内部类

静态内部类类似于静态成员。

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

注意:

  • 当一个静态内部类对象存在,并不一定存在对应的外部类对象。因此,静态内部类的实例方法不能直接访问外部类的实例方法。
  • 静态内部类看作外部类的一个静态成员。因此,外部类的方法中可以通过:静态内部类.名字 访问静态内部类的静态成员。通过new 静态内部类() 访问静态内部类的实例。

实例3:静态内部类

 1 class Out {
 2     private static int age = 12;
 3      
 4     static class In {
 5         public void print() {
 6             System.out.println(age);
 7         }
 8     }
 9 }
10  
11 public class Demo {
12     public static void main(String[] args) {
13         Out.In in = new Out.In();
14         in.print();
15     }
16 }

运行结果:12

可以看到,如果用static 将内部内静态化,那么内部类就只能访问外部类的静态成员变量,具有局限性

其次,因为内部类被静态化,因此Out.In可以当做一个整体看,可以直接new 出内部类的对象(通过类名访问static,生不生成外部类对象都没关系)

2. 局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

定义在方法内部。作用域只限于本方法。用的非常少。

 1 class People{
 2     public People() {
 3          
 4     }
 5 }
 6  
 7 class Man{
 8     public Man(){
 9     }
10      
11     public People getWoman(){
12         class Woman extends People{   //局部内部类
13             int age =0;
14         }
15         return new Woman();
16     }
17 }

注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

3. 匿名内部类

  • 匿名内部类也就是没有名字的内部类。正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。
  • 但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口
  • 匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。
  • 适合于那种只需要使用一次的类。比如,键盘监听操作等等。

语法:

new 父类构造器(实参列表)实现接口(){

//匿名内部类类实体

}

实例1:不使用匿名内部类来实现抽象方法

abstract class Person {
    public abstract void eat();
}
 
class Child extends Person {
    public void eat() {
        System.out.println("eat something");
    }
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Child();
        p.eat();
    }
}

运行结果:eat something

可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用

但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?

这个时候就引入了匿名内部类。

实例2:匿名内部类的基本实现

abstract class Person {
    public abstract void eat();
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}

运行结果:eat something

可以看到,我们直接将抽象类Person中的方法在大括号中实现了

这样便可以省略一个类的书写

并且,匿名内部类还能用于接口上。

实例3:在接口上使用匿名内部类

 1 interface Person {
 2     public void eat();
 3 }
 4  
 5 public class Demo {
 6     public static void main(String[] args) {
 7         Person p = new Person() {
 8             public void eat() {
 9                 System.out.println("eat something");
10             }
11         };
12         p.eat();
13     }
14 }

运行结果:eat something

 由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现

最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口。

实例4:Thread类的匿名内部类实现

 1 public class Demo {
 2     public static void main(String[] args) {
 3         Thread t = new Thread() {
 4             public void run() {
 5                 for (int i = 1; i <= 5; i++) {
 6                     System.out.print(i + " ");
 7                 }
 8             }
 9         };
10         t.start();
11     }
12 }

运行结果:1 2 3 4 5

实例5:Runnable接口的匿名内部类实现

 1 public class Demo {
 2     public static void main(String[] args) {
 3         Runnable r = new Runnable() {
 4             public void run() {
 5                 for (int i = 1; i <= 5; i++) {
 6                     System.out.print(i + " ");
 7                 }
 8             }
 9         };
10         Thread t = new Thread(r);
11         t.start();
12     }
13 }

运行结果:1 2 3 4 5

 


相关链接:

Java内部类详解

java中的内部类总结

领略Java内部类的“内部”

Java内部类的使用小结

以上是关于面向对象编程(十六)——内部类详解的主要内容,如果未能解决你的问题,请参考以下文章

Java面向对象之内部类的详解

11面向对象(类抽象类接口作为方法的参数类型和返回值类型链式编程packageimport关键字成员内部类(静态内部类))

Java内部类之间的闭包和回调详解

PHP面向对象编程

黑马程序员————面向对象多态,内部类

JAVA——面向对象——内部类