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

简介: 本文关键词: 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声明

 

目录
相关文章
|
2月前
|
Java
java基础(8)数据类型的分类
Java数据类型分为基本数据类型(8种)和引用数据类型。基本类型包括byte, short, int, long, float, double, boolean, char。每种类型有固定占用空间大小,如int占用4字节。字符编码如ASCII和Unicode用于将文字转换为计算机可识别的二进制形式。
74 2
|
2月前
|
存储 Java
java基础(7)变量以及变量的分类
Java变量是内存中存储数据的基本单元,包含数据类型、名称和字面值。变量的数据类型决定了分配的内存空间大小。变量声明格式为“数据类型 变量名;”,变量名应符合标识符命名规范。变量可以重新赋值,但数据类型需一致。变量可以一行声明多个,作用域决定了变量的可用范围。变量分为局部变量和成员变量,局部变量定义在方法体内,成员变量定义在方法体外、类体内。
42 2
|
3月前
|
存储 Java
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
|
20天前
|
Java 编译器
Java重复定义变量详解
这段对话讨论了Java中变量作用域和重复定义的问题。学生提问为何不能重复定义变量导致编译错误,老师通过多个示例解释了编译器如何区分不同作用域内的变量,包括局部变量、成员变量和静态变量,并说明了使用`this`关键字和类名来区分变量的方法。最终,学生理解了编译器在逻辑层面检查变量定义的问题。
Java重复定义变量详解
|
3月前
|
存储 缓存 Java
Java本地高性能缓存实践问题之如何定义Caffeine的缓存
Java本地高性能缓存实践问题之如何定义Caffeine的缓存
|
1月前
|
Oracle Java 关系型数据库
重新定义 Java 对象相等性
本文探讨了Java中的对象相等性问题,包括自反性、对称性、传递性和一致性等原则,并通过LaptopCharger类的例子展示了引用相等与内容相等的区别。文章还介绍了如何通过重写`equals`方法和使用`Comparator`接口来实现更复杂的相等度量,以满足特定的业务需求。
24 3
|
1月前
|
Java
让星星⭐月亮告诉你,Java异常分类[Throwable(Error/Exception(RuntimeException/其他异常)) 检查时异常 非检查时异常]
本文深入解析了Java异常处理机制,重点介绍了`Throwable`类及其子类`Error`和`Exception`,并通过实例代码、流程图和表格详细解释了异常的分类、区别及处理方法,帮助读者掌握异常处理的关键技巧,提升程序的稳定性和健壮性。
47 1
|
1月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
19 1
|
1月前
|
Java
Java 中 IO 流的分类详解
【10月更文挑战第10天】不同类型的 IO 流具有不同的特点和适用场景,我们可以根据具体的需求选择合适的流来进行数据的输入和输出操作。在实际应用中,还可以通过组合使用多种流来实现更复杂的功能。
48 0
|
2月前
|
存储 安全 Java
Java 常用集合分类
Java 常用集合分类
26 3
下一篇
无影云桌面