深入解析Java中的内部类

概述

最近学习python,发现python是支持多继承的,这让我想起Java是通过内部类实现的这套机制。这篇文章不是讲如何通过内部类实现多继承,而是总结一下内部类的类型和使用方法。

Java内部类分为:

  • 非静态内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类

内部类在Android源码中被大量的使用,先介绍一下这四种内部类的共同点:

  • 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号。
  • 内部类不能用普通的方式访问。内部类是外部类的一个成员,因为内部类可以自由的访问外部类的成员,包括private成员。
  • 内部类声明为静态的,就不能随意的访问外部类的成员变量了,此时内部类只能访问到外部类的静态成员变量。

接下来,分别介绍一下这几种内部类。
非静态内部类

当一个类作为另一个类的非静态成员时,则这个类就是一个非静态内部类。
创建非静态内部类的示例代码如下:

class OutClass {
 class InnerClass {}
}

当我们用javac去编译的时候,发现生成了两个.class文件:OutClass.class和OutClass$InnerClass.class。如下图所示:

从外部类的非静态方法中实例化内部类

在外部类中访问内部类是很容易的,直接创建内部类对象,然后通过对象实例调用类内的方法即可。示例代码如下:

public class OutClass {
 private static int a = 0;

 public void makeInner() {
  InnerClass inClass = new InnerClass();
  inClass.seeOuter();
 }

 public static void main(String[] args) {
  OutClass oClass = new OutClass();
  oClass.makeInner();
 }

 class InnerClass {
  public void seeOuter() {
   System.out.println(a);
   a++;
  }
 }
}

运行结果如下:

0

从外部类的静态方法中实例化内部类

在外部类中访问内部类是比较简单的,可以直接new出内部类对象,但是如果想在外部类的外部使用内部类,接不能直接new内部类名的方式了,而是需要如下方式:

OutClass.InnerClass innerClass = new OutClass().new InnerClass();

也就是说,在外部调用非静态内部类,需要先实例化外部类,然后通过外部类对象再去实例化内部类。示例代码如下:

public class OutClass {
 private static int a = 0;

 public void makeInner() {
  InnerClass inClass = new InnerClass();
  inClass.seeOuter();
 }

 public static void main(String[] args) {
  OutClass oClass = new OutClass();
  oClass.makeInner();

  OutClass.InnerClass innerClass = new OutClass().new InnerClass();
  innerClass.seeOuter();
 }

 class InnerClass {
  public void seeOuter() {
   System.out.println(a);
   a++;
  }
 }
}

运行结果:

0
1

内部类的this引用

普通的类可以使用this引用当前的对象,内部类也是如此。但是假若内部类想引用外部类当前的对象呢?可以使用如下方式:

外部类名.this

示例代码如下:

public class OutClass {
 private static int a = 0;

 public void makeInner() {
  InnerClass inClass = new InnerClass();
  inClass.seeOuter();
 }

 public static void main(String[] args) {
  OutClass oClass = new OutClass();
  oClass.makeInner();

  OutClass.InnerClass innerClass = new OutClass().new InnerClass();
  innerClass.seeOuter();
 }

 class InnerClass {
  public void seeOuter() {
   System.out.println(this);
   System.out.println(OutClass.this);
  }
 }
}


静态内部类

上面介绍了非静态内部类,接下来我们学习神马是静态内部类。

静态内部类就是在外部类中扮演一个静态成员的角色,创建静态内部类和创建非静态内部类的形式很相似,只是class前面多了一个static修饰符。

注意,外部类是不可能使用static修饰符进行修饰的。

示例代码如下:

class OutClass {
 static class InnerClass {
 }
}

用javac命令编译一下,可以看到一样都是有两个.class文件,如下图所示:

从外部类的非静态方法中实例化静态内部类

从外部类中访问静态内部类,和在外部类中访问非静态内部类是一样的。但是,需要注意一点,此时静态内部类只能访问外部类的静态成员,无法访问非静态成员了。

示例代码如下:

public class OutClass {
 private static int a = 0;
 private int b = 1;

 public void makeInner() {
  InnerClass inClass = new InnerClass();
  inClass.seeOuter();
 }

 public static void main(String[] args) {
  OutClass oClass = new OutClass();
  oClass.makeInner();

 }

 static class InnerClass {
  public void seeOuter() {
   System.out.println(this);
   System.out.println(a);
   // System.out.println(b);
  }
 }
}

执行结果如下:

OutClass$InnerClass@79a340
0

从外部类静态方法中实例化静态内部类

注意:
    因为静态内部类是外部类的静态成员,而静态成员是跟类绑定,而不是跟类实例化的对象绑定。所以,在外部类的静态方法中实例化内部类,是不需要先实例化外部类的。

示例代码如下:

public class OutClass {
 private static int a = 0;
 private int b = 1;

 public void makeInner() {
  InnerClass inClass = new InnerClass();
  inClass.seeOuter();
 }

 public static void main(String[] args) {
  OutClass oClass = new OutClass();
  oClass.makeInner();

  OutClass.InnerClass inClass = new OutClass.InnerClass();
  inClass.seeOuter();
 }

 static class InnerClass {
  public void seeOuter() {
   System.out.println(this);
   System.out.println(a);
   // System.out.println(b);
  }
 }
}

匿名内部类

匿名内部类在Android应用开发中简直是泛滥,各种listener对象的实现很多都是通过匿名内部类。

匿名内部类从名字上就可以知道这是代表没有类名的内部类,通常用来简化代码。

相信写Java的同学都使用过线程,那Thread的时候我们可以传一个Runnable对象,也可以传一个匿名内部类。示例代码如下:

public class OutClass {
 public void testAnonymousClass() {
  Thread t = new Thread(new Runnable() {

   @Override
   public void run() {
    for (int i = 0; i < 10; i ++) {
     System.out.println(i);
     try {
      Thread.sleep(500);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   }
  });
  t.start();

  System.out.println("another thread is running...");
 }

 public static void main(String[] args) {
  OutClass oClass = new OutClass();
  oClass.testAnonymousClass();
 }
}

执行结果如下:

another thread is running...
时间: 2015-07-28

Java内部类的继承(全)

下面通过实例代码给大家分享下有关JAVA内部类的继承,具体详解如下: Java内部类的构造器必须连接到指向其外围类对象的引用(构造内部类必须给它一个外部类对象的引用,内部类依赖于外部类对象),所以在继承内部类的时候,需要在导出类的构造器中手动加入对基类构造器的调用. 因为,在导出类实例化时,并不存在一个外围类对象,以让导出类的实例去连接到它. 所以,我们需要创建一个外围类,然后用一个特定的语法来表明内部类与外围类的关系. 在下例子中,需要给导出类InheritInner一个来自内部类的外围类中的

深入解析Java中的Class Loader类加载器

类加载的过程 类加载器的主要工作就是把类文件加载到JVM中.如下图所示,其过程分为三步: 1.加载:定位要加载的类文件,并将其字节流装载到JVM中: 2.链接:给要加载的类分配最基本的内存结构保存其信息,比如属性,方法以及引用的类.在该阶段,该类还处于不可用状态: (1)验证:对加载的字节流进行验证,比如格式上的,安全方面的: (2)内存分配:为该类准备内存空间来表示其属性,方法以及引用的类: (3)解析:加载该类所引用的其它类,比如父类,实现的接口等. 3.初始化:对类变量进行赋值. 类加载器

整理java读书笔记十五之java中的内部类

内部类是指在一个外部类的内部再定义一个类.类名不需要和文件夹相同. *内部类可以是静态static的,也可用public,default,protected和private修饰.(而外部顶级类即类名和文件名相同的只能使用public和default). 前言 Java从JDK1.1的时候,就开始引入内部类的概念了,那么小编也通过这篇博客来分享一下Java中有关内部类的一些特性.  什么是内部类? 在很多情况下,类被定义成一个独立的程序单元,但是有时候也会把一个类放在另一个类的内部定义,这个定义在

讲解Java中如何构造内部类对象以及访问对象

通过反射构造内部类对象 首先在 javalang 包下写一个包含内部类的类: package javalang; public class Outer { public static class Inner1{} } 注意这个类是 public static,后面我们慢慢把这些修饰符去掉. 要想通过反射来创建 Inner1 对象,首先要获得 Inner1 的 Class 对象.我们在 Outer 中写上 main 方法: public class Outer { public static cl

全面解读Java编程中的内部类

Java内部类及其实例化 在 Java 中,允许在一个类(或方法.语句块)的内部定义另一个类,称为内部类(Inner Class),有时也称为嵌套类(Nested Class). 内部类和外层封装它的类之间存在逻辑上的所属关系,一般只用在定义它的类或语句块之内,实现一些没有通用意义的功能逻辑,在外部引用它时必须给出完整的名称. 使用内部类的主要原因有: 内部类可以访问外部类中的数据,包括私有的数据. 内部类可以对同一个包中的其他类隐藏起来. 当想要定义一个回调函数且不想编写大量代码时,使用匿名(

java基础学习笔记之类加载器

类加载器 java类加载器就是在运行时在JVM中动态地加载所需的类,java类加载器基于三个机制:委托,可见,单一. 把classpath下的那些.class文件加载进内存,处理后成为字节码,这些工作是类加载器做的. 委托机制指的是将加载类的请求传递给父加载器,如果父加载器找不到或者不能加载这个类,那么再加载他. 可见性机制指的是父加载器加载的类都能被子加载器看见,但是子加载器加载的类父加载器是看不见的. 单一性机制指的是一个类只能被同一种加载器加载一次. 默认类加载器 系统默认三个类加载器:

对Java的面对对象编程中对象和引用以及内部类的理解

最近利用下班的时候看了看的think in java感觉再看 和第一次看大不一样 接下来说一下java中对象和引用的关系,以及内部类的概念. 1.java中一切都是对象  在java中是什么来操作者对象呢?答案是引用,这就好比C或者C++中的指针. 如果用拥有一个引用,那么此时你必须让其和一个对象关联在一起,否则这个引用并不会像你想象的那样任由你的控制,例如你创建了一个String的引用: String s ; 而此时并未与任何对象关联,如果此时你去做一些操作,如调用String的一些方法,肯定

举例讲解Java的内部类与类的加载器

内部类 class A { //Inner1 要在 A 初始化后 才能使用,即要被A的对象所调用 class Inner1 { int k = 0; // static int j = 0; //A加载后,Inner1没有加载,所以这个 静态变量j 无法立即使用,报错 final int z = 0; /*static void say1() { }*/ void say2() { } } //Inner2 在A加载好后就可以使用了 static class Inner2 { int k = 0

解析Java虚拟机中类的初始化及加载器的父委托机制

类的初始化 在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值. 在程序中,静态变量的初始化有两种途径: 1.在静态变量的声明处进行初始化: 2.在静态代码块中进行初始化. 没有经过显式初始化的静态变量将原有的值. 一个比较奇怪的例子: package com.mengdd.classloader; class Singleton { // private static Singleton mInstance = new Singleton();// 位置1 // 位置1输

Java虚拟机装载和初始化一个class类代码解析

在 java 应用程序开发中,只有被 java 虚拟机装载的 Class 类型才能在程序中使用.只要生成的字节码符合 java 虚拟机的指令集和文件格式,就可以在 JVM 上运行,这为 java 的跨平台性提供条件.下面,我们来看看虚拟机是如何装载和初始化一个 class 类的. 装载一个类 学习过C/C++语言的读者知道,C/C++源代码必须首先别编译成本地的机器代码,然后还需要一个链接代码过程.该链接过程的主要任务就是:合并不同的源码文件产出的中间代码,并最终获得一个可直接执行的应用程序.然

实例解析Java中的构造器初始化

1.初始化顺序 当Java创建一个对象时,系统先为该对象的所有实例属性分配内存(前提是该类已经被加载过了),接着程序开始对这些实例属性执行初始化,其初始化顺序是:先执行初始化块或声明属性时制定的初始值,再执行构造器里制定的初始值. 在类的内部,变量定义的先后顺序决定了初始化的顺序,即时变量散布于方法定义之间,它们仍就会在任何方法(包括构造器)被调用之前得到初始化. class Window { Window(int maker) { System.out.println("Window(&quo

jQuery移动web开发中的页面初始化与加载事件

页面初始化事件(pagebeforecreate.pagecreate) Jquery Mobile会自动基于page"内的增强的约定自动初始化一些插件.例如:给一个input输入框约定了type=range属性会自动生成一个自定义滑动条.这些自动初始化的行为是受"page"插件控制的,它在执行前后部署部署事件,允许你在初始化前后操作页面,或者甚至自己提供初始化行为,禁止自动初始化.注以下的页面初始化事件在每个"page"只被触发一次,与每次页面在显示或者

通过实例解析js简易模块加载器

前端模块化 关注前端技术发展的各位亲们,肯定对模块化开发这个名词不陌生.随着前端工程越来越复杂,代码越来越多,模块化成了必不可免的趋势. 各种标准 由于javascript本身并没有制定相关标准(当然es6已经有了import和export),所以在模块化方面诞生了各种不同的规范.主要有AMD规范(随requirejs诞生而普及),CMD规范(随seajs的出现而普及),commonjs(主要用于node,并不适合前端).至于以上几种规范的异同,无耻的我在这里就不多费口水了,请还不了解的亲们自行

vue初始化动画加载的实例

1.在入口文件index.html中加入loading动画: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="renderer" content="webkit|ie-comp|ie-stand"> <meta content="width=device-width, initial-sca

概述如何实现一个简单的浏览器端js模块加载器

在es6之前,js不像其他语言自带成熟的模块化功能,页面只能靠插入一个个script标签来引入自己的或第三方的脚本,并且容易带来命名冲突的问题.js社区做了很多努力,在当时的运行环境中,实现"模块"的效果. 通用的js模块化标准有CommonJS与AMD,前者运用于node环境,后者在浏览器环境中由Require.js等实现.此外还有国内的开源项目Sea.js,遵循CMD规范.(目前随着es6的普及已经停止维护,不论是AMD还是CMD,都将是一段历史了) 浏览器端js加载器 实现一个简

详解js异步文件加载器

我们经常会遇到这种场景,某些页面依赖第三方的插件,而这些插件比较大,不适合打包到页面的主js里(假设我们使用的是cmd的方式,js会打包成一个文件),那么这个时候我们通常会异步获取这些插件文件,并在下载完成后完成初始化的逻辑. 以图片上传为例,我们可能会用到plupload.js这个插件,那么我们会这么写: !window.plupload ? $.getScript( "/assets/plupload/plupload.full.min.js", function() { self

详解webpack模块加载器兼打包工具

 什么是 webpack? webpack是近期最火的一款模块加载器兼打包工具,它能把各种资源,例如JS(含JSX).coffee.样式(含less/sass).图片等都作为模块来使用和处理. 我们可以直接使用 require(XXX) 的形式来引入各模块,即使它们可能需要经过编译(比如JSX和sass),但我们无须在上面花费太多心思,因为 webpack 有着各种健全的加载器(loader)在默默处理这些事情,这块我们后续会提到. 你可以不打算将其用在你的项目上,但没有理由不去掌握它,因为以近