Java虚拟机(JVM)内存底层分析

发布时间 2023-07-04 20:11:12作者: 师大无语

对象和类的详解

类:我们叫做class。 对象:我们叫做Object,instance(实例)

总结

1.类可以看成一类对象的模板,对象可以看成该类的一个具体实例。

2.类是用于描述同一类型的对象的一个抽象概念,类中定义了这一类对象所应具有的共同的属性、方法。

类的定义:对于一个类来说,有三种成员:属性field、方法method、构造器constructor

属性(field 成员变量)

1.属性用于定义该类或该类对象包含的数据或者说静态特征。属性作用范围是整个类体。

2.在定义成员变量时可以对其初始化,如果不对其初始化,Java使用默认的值对其初始化。

属性定义格式:[修饰符] 属性类型 属性名 = [默认值] ;

构造方法(构造器 constructor)

构造器用于对象的初始化,而不是创建对象!

声明格式:[修饰符] 类名(形参列表){ //n条语句}

构造器4个要点:

1.构造器通过new关键字调用!!

2.构造器虽然有返回值,但是不能定义返回值类型(返回值的类型肯定是本类),不能在构造器里使用return返回某个值。

3.如果我们没有定义构造器,则编译器会自动定义一个无参的构造方法。如果已定义则编译器不会自动添加!

4.构造器的方法名必须和类名一致!

构造方法的重载

构造方法也是方法。与普通方法一样,构造方法也可以重载。

JAVA虚拟机内存模型概念

Java虚拟机的内存可以分为三个区域:栈stack、堆heap、方法区method area

虚拟机栈(简称:栈)的特点如下:

1.栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)

2.JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)

3.栈属于线程私有,不能实现线程间的共享!

4.栈的存储特性是“先进后出,后进先出”

5.栈是由系统自动分配,速度快!栈是一个连续的内存空间!

堆的特点如下:

1.堆用于存储创建好的对象和数组(数组也是对象)

2.JVM只有一个堆,被所有线程共享

3.堆是一个不连续的内存空间,分配灵活,速度慢!

4.堆被所有的线程所共享,在堆上的区域,会被垃圾回收器做进一步划分,例如新生代、老年代的划分。

方法区(也是堆)特点如下:

1.方法区是JAVA虚拟机规范,可以有不同的实现。

i. JDK7以前是“永久代”

ii. JDK7部分去除“永久代”,静态变量、字符串常量池都挪到了堆内存中

iii. JDK8是“元数据空间”和堆结合起来。

2.JVM只有一个方法区,被所有线程共享!

3.方法区实际也是堆,只是用于存储类、常量相关的信息!

4.用来存放程序中永远是不变或唯一的内容。(类信息【Class对象,反射机制中会重点讲授】、静态变量、字符串常量等)

5.常量池主要存放常量:如文本字符串、final常量值。

参数传值机制

Java中,方法中所有参数都是“值传递”,也就是“传递的是值的副本”。 也就是说,我们得到的是“原参数的复印件,而不是原件”。

1.基本数据类型参数的传值:传递的是值的副本。 副本改变不会影响原件。

2. 引用类型参数的传值:传递的是值的副本。但是引用类型指的是“对象的地址”。

因此,副本和原参数都指向了同一个“地址”,改变“副本指向地址对象的值,也意味着原参数指向对象的值也发生了改变”。

垃圾回收机制(Garbage Collection)

垃圾回收原理和算法

内存管理

Java的内存管理很大程度就是:堆中对象的管理,其中包括对象空间的分配和释放。

1.对象空间的分配:使用new关键字创建对象即可

2.对象空间的释放:将对象赋值null即可。

垃圾回收过程

任何一种垃圾回收算法一般要做两件基本事情:

1.发现无用的对象

2.回收无用对象占用的内存空间。

垃圾回收机制保证可以将“无用的对象”进行回收。

无用的对象指的就是没有任何变量引用该对象。Java的垃圾回收器通过相关算法发现无用对象,并进行清除和整理。

垃圾回收相关算法

1.引用计数法

堆中的每个对象都对应一个引用计数器,当有引用指向这个对象时,引用计数器加1,而当指向该对象的引用失效时(引用变为null),引用计数器减1,最后如果该对象的引用计算器的值为0时,则Java垃圾回收器会认为该对象是无用对象并对其进行回收。

优点是算法简单,缺点是“循环引用的无用对象”无法别识别。

2.引用可达法(根搜索算法)

程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

通用的分代垃圾回收机制

分代垃圾回收机制,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。

我们将对象分为三种状态:年轻代、年老代、永久代。同时,将处于不同状态的对象放到堆中不同的区域。

1. 年轻代

所有新生成的对象首先都是放在Eden区。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是Minor GC,每次 Minor GC 会清理年轻代的内存,算法采用效率较高的复制算法,频繁的操作,但是会浪费内存空间。当“年轻代”区域存放满对象后,就将对象存放到年老代区域。

2. 年老代

在年轻代中经历了N(默认15)次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。年老代对象越来越多,我们就需要启动Major GCFull GC(全量回收),来一次大扫除,全面清理年轻代区域和年老代区域。

3. 永久代

用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响。JDK7以前就是“方法区”的一种实现。JDK8以后已经没有“永久代”了,使用metaspace元数据空间和堆替代。

Minor GC:

用于清理年轻代区域。Eden区满了就会触发一次Minor GC。清理无用对象,将有用对象复制到“Survivor1”、“Survivor2”区中。

Major GC

用于清理年老代区域。

Full GC

用于清理年轻代、年老代区域。 成本较高,会对系统性能产生影响。

JVM调优和Full GC

在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC

1.年老代(Tenured)被写满

2.永久代(Perm)被写满

3.System.gc()被显式调用

4.上一次GC之后Heap的各域分配策略动态变化

内存泄漏:指堆内存由于某种原因程序未释放,造成内存浪费,导致运行速度减慢甚至系统崩溃等。

开发中容易造成内存泄露的操作:

1.创建大量无用对象

比如:大量拼接字符串时,使用了String而不是StringBuilder

2. 静态集合类的使用

HashMapVectorList等的使用最容易出现内存泄露,这些静态变量的生命周期 和应用程序一致,所有的对象也不能被释放。

3. 各种连接对象(IO流对象、数据库连接对象、网络连接对象)未关闭

IO流对象、数据库连接对象、网络连接对象等连接对象属于物理连接,和硬盘或者网 络连接,不使用的时候一定要关闭。

4. 监听器的使用不当

释放对象时,没有删除相应的监听器

其他要点

  1. 程序员无权调用垃圾回收器。

2.程序员可以调用System.gc(),该方法只是通知JVM,并不是运行垃圾回收器。尽量少用,会申请启动Full GC,成本高,影响系统性能。

3.Object对象的finalize方法,是Java提供给程序员用来释放对象或资源的方法,但是尽量少用

this关键字

this的用法:

1.普通方法中,this总是指向调用该方法的对象。

2.构造方法中,this总是指向正要初始化的对象。

this的其他要点:

1.this()调用重载的构造方法,避免相同的初始化代码。但只能在构造方法中用,并且必须位于构造方法的第一句。

2.this不能用于static方法中。

3.this是作为普通方法的“隐式参数”,由系统传入到方法中。

static 关键字

静态变量(类变量)、静态方法(类方法)static声明的属性或方法。

静态变量/静态方法生命周期和类相同,在整个程序执行期间都有效。它有如下特点:

1.为该类的公用变量,属于类,被该类的所有实例共享,在类载入时被初始化。

2.static变量只有一份。

3.一般用“类名.类变量/方法”来调用。

4.static方法中不可直接访问非static的成员

静态初始化块

1.构造方法用于对象的普通属性初始化。

2.静态初始化块,用于类的初始化操作,初始化静态属性。

3.在静态初始化块中不能直接访问非static成员。

变量的分类和作用域

变量有三种类型:局部变量、成员变量(也称为实例变量)和静态变量。

类型 声明位置 从属于 生命周期(作用域)

局部变量 方法或语句块内部 方法/语句块 从声明处开始,到方法或语句块 结束

成员变量(实例变量) 类内部,方法外部 对象 对象创建,成员变量也跟着创建。 对象消失,成员变量也跟着消失;

静态变量(类变量) 类内部,static修饰 类 类被加载,静态变量就有效;

包机制(packageimport)

包(package)相当于文件夹对于文件的作用。用于管理类、用于解决类的重名问题。

package的使用有两个要点:

1.通常是类的第一句非注释性语句。

2.包名:域名倒着写即可,便于内部管理类。

注意事项

1.写项目时都要加包,不要使用默认包。

2.com.gaocom.gao.car,这是两个完全独立的包。只是逻辑上看,后者是前者的一部分。

IDEA项目中新建包

1.src目录上单击右键,选择new->package

2.package窗口上输入包名即可

3.即可在src下面看到包

4.接下来,我们就可以在包上单击右键,新建类

JDK中的主要包

Java中的常用包 说明

java.lang 包含一些Java语言的核心类,如StringMathIntegerSystemThread

java.awt 包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)

java.net 包含执行与网络相关的操作的类。

java.io 包含能提供多种输入/输出功能的类。

java.util 包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。

导入类import

如果要使用其他包的类,需使用import,从而在本类中直接通过类名来调用,否则就需要书写类的完整包名和类名。

注意要点

1.Java会默认导入java.lang包下所有的类,因此这些类我们可以直接使用。

2.如果导入两个同名的类,只能用包名+类名来显示调用相关类:java.util.Date date = new java.util.Date();

静态导入(static import): 其作用是用于导入指定类的静态属性和静态方法,这样我们可以直接使用静态属性和静态方法。

面向对象三大特征:继承、封装、多态

继承有两个主要作用:

1.代码复用,更加容易实现类的扩展

2.方便建模

instanceof 运算符

instanceof是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象时,返回true;否则,返回false

继承使用要点

1.父类也称作超类、基类。 子类:派生类等。

2.Java中只有单继承,没有像C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。

3.Java中类没有多继承,接口有多继承。

4.子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)

5.如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object

方法重写override:子类重写父类的方法,可以用自身行为替换父类行为。重写是实现多态的必要条件。

方法重写需要符合下面的三个要点:

1.= =:方法名、形参列表相同。

2.≤:返回值类型和声明异常类型,子类小于等于父类。

3.≥:访问权限,子类大于等于父类。

final关键字

final关键字的作用:

1.修饰变量: 被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。

final int MAX_SPEED = 120;

2.修饰方法:该方法不可被子类重写。但是可以被重载!

final void study(){}

3.修饰类: 修饰的类不能被继承。比如:MathString等。

final class A {}

 

继承和组合

 

除了继承,“组合”也能实现代码的复用!“组合”核心是“将父类对象作为子类的属性”。

 

组合比较灵活。继承只能有一个父类,但是组合可以有多个属性。所以,有人声称“组合优于继承,开发中可以不用继承”,但是,不建议大家走极端。

 

Object类详解:所有类都是Object类的子类,也都具备Object类的所有特性。

 

Object类基本特性

 

1.Object类是所有类的父类,所有的Java对象都拥有Object类的属性和方法。

 

2.如果在类的声明中未使用extends,则默认继承Object类。

 

Object

 

public class Person {

 

    ...

 

}

 

//等价于:

 

public class Person extends Object {

 

    ...

 

}

 

toString方法

 

Object类中定义有public String toString()方法,其返回值是 String 类型。

 

Object类中toString方法的源码为:

 

public String toString() {

 

    return getClass().getName() + "@" + Integer.toHexString(hashCode());

 

}

 

根据如上源码得知,默认会返回“类名+@+16进制的hashcode”。在打印输出或者用字符串连接对象时,会自动调用该对象的toString()方法。

 

IDEA快捷键和相关操作:

 

1.类的结构视图:alt+7

 

2.看类的源码:ctrl+左键

 

3.自动生成构造器、getset方法、equals等:alt+insert

 

4.查看错误:alt+enter

 

快捷输出常见字符串:

 

a) main public static void main(String[] args){}

 

b) sout System.out.println();

 

c) soutm System.out.println(“描述:所在类中的,所在方法”);

 

==equals方法

 

1.==代表比较双方是否相同。如果是基本类型则表示值相等,如果是引用类型则表示地址相等即是同一个对象。

 

2.equals()提供定义“对象内容相等”的逻辑。比如,我们在公安系统中认为id相同的人就是同一个人、学籍系统中认为学号相同的人就是同一个人。

 

3.equals()默认是比较两个对象的hashcode。但,可以根据自己的要求重写equals方法。

 

super关键字

 

1.super“可以看做”是直接父类对象的引用。可通过super来访问父类中被子类覆盖的方法或属性。

 

2.使用super调用普通方法,语句没有位置限制,可以在子类中随便调用。

 

3.在一个类中,若是构造方法的第一行没有调用super(...)或者this(...); 那么Java默认都会调用super(),含义是调用父类的无参数构造方法。

 

继承树追溯

 

属性/方法查找顺序:(比如:查找变量h)

 

1.查找当前类中有没有属性h

 

2.依次上溯每个父类,查看每个父类中是否有h,直到Object

 

3.如果没找到,则出现编译错误。

 

4.上面步骤,只要找到h变量,则这个过程终止。

 

构造方法调用顺序:

 

1.构造方法第一句总是:super()来调用父类对应的构造方法。所以,流程就是:先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。

 

注:静态初始化块调用顺序,与构造方法调用顺序一样,不再重复。

 

封装(encapsulation)封装是面向对象三大特征之一。

 

封装的作用和含义

 

我们程序设计要追求“高内聚,低耦合”。

 

高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。

 

编程中封装的具体优点:

 

1.提高代码的安全性。

 

2.提高代码的复用性。

 

3.“高内聚”:封装细节,便于修改内部代码,提高可维护性。

 

4.“低耦合”:简化外部调用,便于调用者使用,便于扩展和协作

 

封装的实现—使用访问控制符

 

1.Java是使用访问控制符来控制哪些细节需要封装,哪些细节需要暴露的。

 

2.Java4种访问控制符分别为privatedefaultprotectedpublic

 

【注】关于protected的两个细节:

 

1.若父类和子类在同一个包中,子类可访问父类的protected成员,也可访问父类对象的protected成员。

 

2.若子类和父类不在同一个包中,子类可访问父类的protected成员,不能访问父类对象的protected成员。

 

开发中封装的简单规则:

 

1.属性一般使用private访问权限。

 

2.属性私有后, 提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作(注意:boolean变量的get方法是is开头!)。

 

3.方法:一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。

 

多态(polymorphism)多态指的是同一个方法调用,由于对象不同可能会有不同的行为。

 

多态的要点:

 

1.多态是方法的多态,不是属性的多态(多态与属性无关)。

 

2.多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。

 

3.父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。

 

对象的转型(casting)

 

父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。

 

向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。

 

这时,我们就需要进行类型的强制转换,我们称之为向下转型。

 

抽象方法和抽象类

 

抽象方法

 

1.使用abstract修饰的方法,没有方法体,只有声明。

 

2。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。

 

抽象类

 

1.包含抽象方法的类就是抽象类。

 

2.通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。

 

抽象类的使用要点:

 

1.有抽象方法的类只能定义成抽象类

 

2.抽象类不能实例化,即不能用new来实例化抽象类。

 

3.抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。

 

4.抽象类只能用来被继承。

 

5.抽象方法必须被子类实现。

 

接口interface

 

接口就是一组规范(就像我们人间的法律一样),所有实现类都要遵守。

 

为什么需要接口?接口和抽象类的区别?

 

1.接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离。

 

2.接口是两个模块之间通信的标准,通信的规范。如果能把你要设计的模块之间的接口定义好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。

 

接口和实现类不是父子关系,是实现规则的关系。

 

比如:我定义一个接口RunnableCar实现它就能在地上跑,Train实现它也能在地上跑,飞机实现它也能在地上跑。

 

就是说,如果它是交通工具,就一定能跑,但是一定要实现Runnable接口。

 

声明格式:

 

[访问修饰符] interface 接口名  [extends 父接口1,父接口2]  {

 

常量定义;   

 

方法定义;

 

}

 

定义接口的详细说明:

 

1.访问修饰符:只能是public或默认。

 

2.接口名:和类名采用相同命名机制。

 

3.extends:接口可以多继承。

 

4.常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。

 

5.方法:接口中的方法只能是:public abstract。 省略的话,也是public abstract

 

要点

 

1.子类通过implements来实现接口中的规范。

 

2.接口不能创建实例,但是可用于声明引用变量类型。

 

3.一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。

 

4.JDK1.8(不含8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。

 

5.JDK1.8(含8)后,接口中可以包含普通的静态方法、默认方法。

 

接口中定义静态方法和默认方法(JDK8)

 

JAVA8之前,接口里的方法要求全部是抽象方法。

 

JAVA8(含8)之后,以后允许在接口里定义默认方法和静态方法。

 

Java 8及以上新版本,允许给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做默认方法(也称为扩展方法)。

 

默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。

 

作为替代方式,接口可以提供默认方法的实现,所有这个接口的实现类都可以得到默认方法。

 

JDK8新特性_静态方法

 

JAVA8以后,我们也可以在接口中直接定义静态方法的实现。这个静态方法直接从属于接口(接口也是类,一种特殊的类),可以通过接口名调用。

 

如果子类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于子类。可以通过子类名直接调用。

 

接口的多继承

 

接口支持多继承。和类的继承类似,子接口extends父接口,会获得父接口中的一切。

 

interface A {

 

    void testa();

 

}

 

interface B {

 

    void testb();

 

}

 

/**接口可以多继承:接口C继承接口AB*/

 

interface C extends A, B {

 

    void testc();

 

}

 

public class Test implements C {

 

    public void testc() {

 

}

 

    public void testa() {

 

    }

 

    public void testb() {

 

    }

 

}

 

字符串String类详解

 

String是最常用的类,要掌握String类常见的方法,它底层实现也需要掌握好,不然在工作开发中很容易犯错。

 

1.String类又称作不可变字符序列。

 

2.String位于java.lang包中,Java程序默认导入java.lang包下的所有类。

 

3.Java字符串就是Unicode字符序列,例如字符串“Java”就是4Unicode字符’J’、’a’、’v’、’a’组成的。

 

4.Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是String类的一个实例。

 

String类和常量池

 

常量池分了以下三种:

 

1. 全局字符串常量池

 

2. class文件常量池

 

3. 运行时常量池(Runtime Constant Pool)

 

String类的常用方法列表

 

方法                   解释说明

 

char charAt(int index)            返回字符串中第index个字符

 

boolean equals(String other)         如果字符串与other相等,返回true;否 则,返回false

 

boolean equalsIgnoreCase(String other)     如果字符串与other相等(忽略大小写),则 返回true;否则,返回false

 

int indexOf(String str)            返回从头开始查找第一个子字符串str在字符 串中的索引位置。如果未找到子字符串str 则返回-1

 

lastIndexOf()                返回从末尾开始查找第一个子字符串str在字 符串中的索引位置。如果未找到子字符串str 则返回-1

 

int length()                 返回字符串的长度。

 

String replace(char oldChar,char newChar)     返回一个新串,它是通过用 newChar 替换此 字符串中出现的所有oldChar而生成的。

 

boolean startsWith(String prefix)         如果字符串以prefix开始,则返回true

 

boolean endsWith(String prefix)         如果字符串以prefix结尾,则返回true

 

String substring(int beginIndex)         返回一个新字符串,该串包含从原始字符串 beginIndex到串尾。

 

String substring(int beginIndex,int endIndex)   返回一个新字符串,该串包含从原始字符串 beginIndex到串尾或endIndex-1的所有字符。

 

String toLowerCase()            返回一个新字符串,该串将原始字符串中的所 有大写字母改成小写字母。

 

String toUpperCase()             返回一个新字符串,该串将原始字符串中的所 有小写字母改成大写字母。

 

String trim()                 返回一个新字符串,该串删除了原始字符串头 部和尾部的空格。

 

字符串相等的判断

 

1.equals方法用来检测两个字符串内容是否相等。如果字符串st内容相等,则s.equals(t)返回true,否则返回false

 

2.要测试两个字符串除了大小写区别外是否是相等的,需要使用equalsIgnoreCase方法。

 

3.判断字符串是否相等不要使用==

 

内部类

 

我们把一个类放在另一个类的内部定义,称为内部类(inner class)

 

内部类的两个要点:

 

1.内部类提供了更好的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问。

 

2.内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。但外部类不能访问内部类的内部属性。

 

注意:

 

内部类只是一个编译时概念,一旦我们编译成功,就会成为完全不同的两个类。对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。编译完成后会出现Outer.classOuter$Inner.class两个类的字节码文件。

 

所以内部类是相对独立的一种存在,其成员变量/方法名可以和外部类的相同。

 

非静态内部类

 

1.非静态内部类对象必须寄存在一个外部类对象里。因此,如果有一个非静态内部类对象那么一定存在对应的外部类对象。非静态内部类对象单独属于外部类的某个对象。

 

2.非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问非静态内部类成员。

 

3.非静态内部类不能有静态方法、静态属性和静态初始化块。

 

4.成员变量访问要点:

 

1)内部类属性:this.变量名。

 

2)外部类属性:外部类名.this.变量名。

 

内部类的访问:

 

\1. 外部类中定义内部类:new Inner()

 

\2. 外部类以外的地方使用非静态内部类:Outer.Inner varname = new Outer().new Inner()

 

静态内部类定义方式:

 

static class  ClassName {

 

//类体

 

}

 

使用要点:

 

1.静态内部类可以访问外部类的静态成员,不能访问外部类的普通成员。

 

2.静态内部类看做外部类的一个静态成员。

 

匿名内部类:适合那种只需要使用一次的类。比如:键盘监听操作等等。在安卓开发、awtswing开发中常见。

 

语法:

 

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

 

          //匿名内部类类体!

 

}

 

注意

 

1.匿名内部类没有访问修饰符。

 

2.匿名内部类没有构造方法。因为它连名字都没有那又何来构造方法呢。

 

局部内部类

 

1.定义在方法内部的,作用域只限于本方法,称为局部内部类。

 

2.局部内部类在实际开发中应用很少。