noteless 头像

java内部类深入详解 内部类的分类 特点 定义方式 使用

 本文关键词:

java内部类 内部类的分类 特点  定义方式 使用   外部类调用内部类 多层嵌套内部类  内部类访问外部类属性  接口中的内部类  内部类的继承  内部类的覆盖  局部内部类 成员内部类 静态内部类 匿名内部类

内部类定义

将一个类定义放到另一个类的内部,这就是内部类

内部类与组合是完全不同的概念

内部类指的是类的定义在内部

看起来像一种代码隐藏机制

但是,远不止于此,因为他了解外部类 并且能够通信

内部类的代码,可以操作创建它的外部类的对象

所以可以认为内部类提供了某种进入其外部类的窗口

 

内部类特点

 

内部类访问外部类不需要任何特殊条件,拥有外部类所有的访问权

也就是对于内部类访问外部类的元素这件事情上

他就相当于是外部类本身一样随便访问

内部类的创建依赖外部类对象

可以直接访问外部类的变量

也可以直接指明

外部类类名.this.变量名

this通常是多余的,可以省略

内部类不仅能够访问包含他的外部类,还可以访问局部变量

但是局部变量必须被声明为final

因为局部内部类会将调用的变量进行拷贝,为了保证一致性,所以变量必须为final

内部类就是隐匿在外部类内部的一个独立的个体,不存在is a  like a

内部类的对象必定秘密的捕获了一个指向外部类对象的引用

然后以此访问外部类的成员,编译器处理了所有的细节,对我们来说都是透明的

 

public class O {

    
    class I{
        
        O get() {
            return O.this;
        }
    }
    
    
    public static void main(String[] args) {

        O outer = new O();
        O.I inner = outer.new I();
        System.out.println(outer == inner.get());

    }

}

 

打印结果为:

true

 

内部类持有的外部类对象就是外部类对象本身,内存地址是相同的

 

外部类的作用域之外,可以使用  outerClass.innerClass  方式引用内部类

可以对同一个包中其他类隐藏

内部类可以声明为私有的

每个类都会产生一个.class文件,包含了类的元信息

如果内部类是匿名的,编译器会简单的产生一个数字作为标识符形如 Outer$1.class

否则就是形如  外部类$内部类.class   ,虚拟机看来与其他类无差,这也是编译器做的工作

普通的类(外部类)只能用public修饰符修饰,或者不写修饰符 使用默认的,但是内部类可以使用private 与protected

 

内部类可以达到类似"多重继承"的效果,

每个内部类都能独立的继承自一个(接口的)实现

无论外部类是否已经继承了某个(接口的)实现

也就是说 单个外部类,可以让多个内部类以不同的方式实现同一个接口或者继承同一个类

一个外部类可以创建多个内部类,这是不是就达到了类似"多重继承"的效果呢

 

 

内部类分类

  1. 成员内部类
  2. 局部内部类
  3. 匿名内部类
  4. 静态内部类

 

成员内部类

成员内部类也叫实例内部类。每一个外部类对象都需要一个内部类的实例,内部类离不开外部类存在

既然是成员内部类,和成员属性成员方法地位上自然没有什么不同

每个外部类对象都有一个内部类对象,自然持有外部类的引用

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();//注意是对象.new

 

局部内部类

局部内部类不能用public或者private或者protected访问说明符,作用域被限定在了声明这个局部内部类中了

很好理解,局部的就跟方法变量一样,限定在了{}之中,自然就不需要设置访问说明符了,而且你可以想下,也只有类以及类的成员有访问修饰符,局部变量有访问修饰符么

局部类可以对外面完全的隐藏起来,即使是外部类的其他的代码也不能访问他

局部内部类虽然被限定在局部代码块{} 里面,但是他也是可以访问外部类的属性的,不要被分类迷惑了

 

匿名内部类

匿名内部类就是局部内部类的进一步隐藏,局部内部类定义了之后在局部区域内仍旧可以创建多个对象

匿名内部类声明一个类之后就只能创建一个对象了,因为他并没有类名字

形式为:

new xxxClass  (){    //或者new xxxInterface()
//.......

}

表示创建一个类的对象,这个类是xxxClass  子类或者实现了xxxInterface 接口的类

也可以说匿名内部类就是创建了一个匿名类的子类对象

构造方法名字和类名是相同的,匿名内部类显然是没有构造方法的,因为连名字都没有

既然没有构造方法想要构造参数,就只能把参数传递给外部的构造器,通过外部类的构造器绕一圈,本身内部类可以访问外部类所有的属性,去把值操作起来

当然外部类自然可以搞点属性根据业务逻辑单独给内部类用

如果是实现接口,不能带任何的参数的,因为接口都没有构造方法的呀

 不过还可以通过初始化代码块达到类似的初始化效果,想必大家还记得初始化代码块是什么吧

不过也仅仅是达到类似的效果,而且,相当于只有一个"构造方法",因为即使你写了多个初始化代码块,还不是构造对象的时候一起执行嘛

小技巧,匿名内部类的参数传递

 fun(new ArrayList<String>(){{add("a");add("b");add("c");}});

也就是:
fun(new ArrayList<String>(){    

               {

                 add("a");

                 add("b");

                 add("c");

               }

          }

);

 

  1. 构造了一个匿名内部类,内部类没有更新重写增加任何的方法
  2. 设置了一个初始化块  {}  ,初始化块会在每个对象构造的时候执行
  3. 代码块中调用add方法增加对象

 

 

静态内部类

 

如果使用内部类只是为了将一个类隐藏到一个类的内部

并不需要内部类引用外部类的对象

可以将内部类声明为static,以便取消产生的引用

只有内部类可以声明为static

静态内部类的对象除了没有对生成他的外部类的对象的引用特权外,其他的内部类一样

通过  外部类 . 内部类   来访问

刚才已经说了显然,静态内部类不会持有外部类的引用

静态的创建形式:

 

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

 

 

 

内部类的继承

内部类的构造器必须连接到指向外部类对象的引用

但是在继承的时候

那个指向外部类对象的"隐匿的"引用必须被初始化

而在派生类中不再存在可连接的默认对象

所以你要解决这个问题,否则的话就会出错

说的就是要包含指向外部类的引用

必须是带参数的,而且参数类型是外部类 在这里面调用super

public class InnerInherit extends OutClass.Inner {

    InnerInherit(OutClass out){
        out.super();
    }

    public static void main(String[] args){
        OutClass out = new OutClass();
        InnerInherit ii = new InnerInherit(out);
    }
}


class OutClass {
    class Inner{
    }
}

 

可以看得到,虽然只是继承内部类

但是想要生成一个构造器,不仅仅是需要传递一个外部类的引用

必须在构造器中使用:

enclosingClassReference.super();

说白了就是,内部类的对象依赖外部类的对象

内部类的子类的对象,也仍旧是依赖外部类的对象的

 

内部类的加载时机

 

package test.b;

public class Outer {

    Outer(){
         System.out.println("Outer构造方法");
     }
     
     {
         
         System.out.println("Outer初始化代码块");
     }
     static{
         
         System.out.println("Outer静态代码块");
     }
     
     class Inner{
         
         Inner(){
             System.out.println("Inner构造方法");
         }
         
         {
             
             System.out.println("Inner初始化代码块");
         }

        
     }
     
     public static void main(String[] args) {
         Outer outer = new Outer();
         System.out.println("----------");
         //Outer.Inner inner = outer.new Inner();

    }

}

 

 

 

打印结果:

Outer静态代码块
Outer初始化代码块
Outer构造方法
----------

显然,内部类没有被初始化,放开注释

打印结果:

Outer静态代码块
Outer初始化代码块
Outer构造方法
----------
Inner初始化代码块
Inner构造方法

 

所以可以说内部类是懒加载的 用到了才加载初始化

 

而且,可以创建多个内部类的实例

Outer.Inner inner1 = outer.new Inner();
Outer.Inner inner2 = outer.new Inner();
Outer.Inner inner3 = outer.new Inner();
Outer.Inner inner4 = outer.new Inner();

 

这是可以的,完全没问题,每个实例有自己的状态信息,与外部类对象信息独立

 

内部类的覆盖情况

两个类之间的继承和他们各自的内部类没有关系,不存在覆盖的情况

两个类之间的继承关系  比如  B extends A  ,每个类中都有C这个内部类

他们两者中的C是没有什么关系的

 

示例:

类A  拥有内部类C 并且有一个C的对象,构造方法中初始化

类B继承A,并且B中也有一个内部类C

public class A {

    private C c;
     A(){
         System.out.println("A  constructor");
         c = new C();
     }
     
     protected class C{
             C(){
                 System.out.println("A ....C  constructor");
             }
     }
     
     
     public static void main(String[] args) {

    }

}


public class B extends A{

    B(){
         System.out.println("B  constructor");

    }
     class C{
         C(){
             System.out.println("B ....C  constructor");
         }
}
     
     
     public static void main(String[] args) {

        new B();
     }

}

 

创建类B new B();

打印信息:

A  constructor
A ....C  constructor
B  constructor

创建B的对象,需要调用父类的构造方法

所以会打印A  constructor  然后构造方法中创建C对象,然后是A ....C  constructor   显然,这并不是B类中的C

所以说:

两个类之间的继承,不存在内部类被覆盖的情况

虽然B继承了A  A有C  B也有C

但是两个内部类是完全独立的两个实体

各自在各自的命名空间中

上面的例子中创建一个对象,有父类,调用父类的构造方法,父类的构造方法调用父类的C的构造方法,也找不到任何方法会要调用子类的C

主函数修改下:

public static void main(String[] args) {

    //new B();
     A a = new B();
     System.out.println("#############");
     
     B b = new B();
     System.out.println("#############");

    a.new C();
     System.out.println("#############");

    b.new C();
     System.out.println("#############");

}

 

 

打印结果为:

A  constructor
A ....C  constructor
B  constructor
#############
A  constructor
A ....C  constructor
B  constructor
#############
A ....C  constructor
#############
B ....C  constructor
#############

 

上面两段很正常,都是创建B对象,自然步骤一样

当创建a.new C(); 的时候使用的是A的C

当创建b.new C(); 的时候使用的是B的C

显然,

创建内部类对象时,到底是父类中的还是子类中的 

是由:   .new 前面的类型决定的,也就是定义的类型,而不是实际指向的类型

 

多层嵌套的内部类


多层嵌套的内部类,他能透明的访问所有他所嵌入的外围类的所有成员

public class NestedClass {

    private String NestedClassName = "NestedClass";
     
     public class NestedClass1{
         private String NestedClass1Name = "NestedClass1";

        public class NestedClass2{
             private String NestedClass2Name = "NestedClass2";

            public class NestedClass3{
                 public void print() {
                     System.out.println("NestedClassName:   "+NestedClassName);
                     System.out.println("NestedClass1Name:   "+NestedClass1Name);
                     System.out.println("NestedClass1Name:   "+NestedClass2Name);
                 }
             }
         }
     }
     public static void main(String[] args) {
         NestedClass nestedClass = new NestedClass();
         NestedClass.NestedClass1 nestedClass1 = nestedClass.new NestedClass1();
         NestedClass.NestedClass1.NestedClass2 nestedClass2 = nestedClass1.new NestedClass2();
         NestedClass.NestedClass1.NestedClass2.NestedClass3 nestedClass3 = nestedClass2.new NestedClass3();
         nestedClass3.print();
         
         
     }

}

 

打印信息

NestedClassName:   NestedClass
NestedClass1Name:   NestedClass1
NestedClass1Name:   NestedClass2

 

从代码中可以看的出来,多层内部类和一层内部类创建格式是一样的

外部类名.内部类名 对象名 = 外部类对象.new 内部类名();

这个外部类指的就是他的外部,如果他的外部仍旧是别人的内部类,那就依次往外找就好了

从打印信息可以看得出来,不管有几层,内部类,可以访问到他外面的所有的类的属性信息

 

接口中的内部类

一般情况下

接口中不允许放置任何代码,但是嵌套类可以作为接口的一部分

放到接口中的任何类都自动的是public 和 是 static 的

因为类是static,只是将嵌套类置于接口的命名空间内,并不违反接口的规则

你甚至可以接口中的内部类实现外部接口

如果你想要创建某些公共代码,使得他们可以被某个接口的所有不同实现所共用

那么使用接口内部的嵌套类会显得很方便

示例:

public class Test {

    public static void main(String[] args) {
         // 接口中的内部类都是默认 public static 的
         Fly bird = new Fly.DemoFly();
         bird.fly();

        Fly bigBird = new BigBird();
         bigBird.fly();
     }

}

interface Fly {

    public void fly();

    class DemoFly implements Fly {

        @Override
         public void fly() {
             System.out.println("一般的鸟都这么飞~");

        }

    }
}

class BigBird implements Fly {

    @Override
     public void fly() {
         System.out.println("大鸟都这么飞~");
     }

}

 

打印信息:

一般的鸟都这么飞~
大鸟都这么飞~

 

可以看得出来,直接通过内部类,接口的静态内部类,可以提供一个默认的实现

这就是提供了编程接口的同时,又提供了一个默认的实现,多给力

 

 

内部类中不能有静态属性以及静态方法以及静态代码块

class A{
    
    class B{
        private static int a= 0;//IDE会提示报错的  
    }
}

非静态的内部类型,不能声明静态的filed 除非标记为常量,也就是用final声明

 

posted @ 2018-07-06 10:43  noteless  阅读(6806)  评论(0编辑  收藏  举报