interface Inter {
void show();
class Outer {
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
题目来自:https://www.cnblogs.com/zhangyinhua/p/7260651.html
先思考几秒,看看这些题你能否应付得来。
在面试中常常遇到这样的笔试题,咋一看这题很简单,还是会有很多人答不好。根本原因是很多人对“内部类”的理解仅限于名称。
“内部类、静态内部类、匿名内部类”是什么大家都清楚。但是当转换一下思维,不仅仅为了完成功能,而是要保证整个项目架构的稳定灵活可扩展性,你会如何选择呢?
这篇文章我们努力回答这些问题,也希望你可以说出你的答案。
四种内部类介绍
定义在一个类中或者方法中的类称作为内部类。
内部类又可以细分为这 4 种:
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
1.成员内部类
成员内部类就是最普通的内部类,它定义在一个类的内部中,就如同一个成员变量一样。如下面的形式:
public class OutClass2 {
private int i = 1;
public static String str = "outclass";
class InnerClass {
private int i = 2;
public void innerMethod() {
int i = 3;
System.out.println("i=" + i);
System.out.println("i=" + this.i);
System.out.println("i=" + OutClass2.this.i);
System.out.println("str=" + str);
public class TestClass {
public static void main(String[] args) {
OutClass2 outClass = new OutClass2();
OutClass2.InnerClass in = outClass.new InnerClass();
in.innerMethod();
因为内部类依附于外部类存在,所以需要外部类的实例来创建内部类:
outClass.new InnerClass()
注意不是直接 new outClass.InnerClass()
。
成员内部类可以无条件的访问外部类的成员属性和成员方法(包括 private 和 static 类型的成员),这是因为在内部类中,隐式地持有了外部类的引用。
我们编译上述的代码,可以看到,会生成两个 class 文件:
这个 OutClass2$InnerClass.class
就是内部类对应的字节码文件,我们使用 AS 打开,会自动进行反编译:
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
package com.example.simon.androidlife.innerclass
import com.example.simon.androidlife.innerclass.OutClass2
class OutClass2$InnerClass {
private int i
OutClass2$InnerClass(OutClass2 var1) {
this.this$0 = var1
this.i = 2
public void innerMethod() {
byte var1 = 3
System.out.println("i=" + var1)
System.out.println("i=" + this.i)
System.out.println("i=" + OutClass2.access$000(this.this$0))
System.out.println("str=" + OutClass2.str)
可以看到,在内部类 OutClass2$InnerClass
的字节码中,编译器为我们生成了一个参数为外部类对象的构造方法,这也解释了内部类为什么可以直接访问外部类的内容,因为持有外部类的引用!
在这个不完整的反编译字节码中,我们可以看到,编译器会为内部类创建一个叫做 this$0
的对象,它是外部类的引用。
innerMethod()
中的 OutClass2.access$000(this.this$0))
是什么意思呢?
为了帮助内部类访问外部类的数据,编译器会生成这个 access
方法,参数是外部类的引用,如果外部类有N个成员,编译器会生成多个access方法,
符号后面的数字会会随着不同的声明顺序而改变,可以理解为一种桥接方法。
对比内部类的 innerMethod()
的 java 代码和字节码我们可以得出这些结论:
- 在内部类中,直接使用变量名,会按照从方法中的局部变量、到内部类的变量、到外部类的变量的顺序访问
- 也就是说,如果在外部类、内部类、方法中有重名的变量/方法,编译器会把方法中直接访问变量的名称修改为方法的名称
- 如果想在方法中强制访问内部类的成员变量/方法,可以使用
this.i
,这里的 this 表示当前的内部类对象 - 如果想在方法中强制访问外部类的成员变量/方法,可以使用
OutClass.this.i
,这里的 OutClass.this 表示当前外部类对象
成员内部类就如同外部类的成员一样,同样可以被public、protected、private、缺省(default)这些修饰符来修饰。
但是有一个限制是:成员内部类不能创建静态变量/方法。如果我们尝试创建,编译器会直接 say no。
为什么会这样呢?
Stackoverflow 有一个回答很好:
“if you’re going to have a static method, the whole inner class has to be static. Without doing that, you couldn’t guarantee that the inner class existed when you attempted to call the static method. ”
我们知道要使用一个类的静态成员,需要先把这个类加载到虚拟机中,而成员内部类是需要由外部类对象 new 一个实例才可以使用,这就无法做到静态成员的要求。
2.静态内部类
说完成员内部类我们来看看静态内部类。
使用 static
关键字修饰的内部类就是静态内部类,静态内部类和外部类没有任何关系,可以看作是和外部类平级的类。
我们来反编译个静态内部类看看。
java 代码:
public class Outclass3 {
private String name;
private int age;
public static class InnerStaticClass {
private String name;
public String getName() {
return name;
public int getAge() {
return new Outclass3().age;
编译后的静态内部类:
package com.example.simon.androidlife.innerclass;
import com.example.simon.androidlife.innerclass.Outclass3;
public class Outclass3$InnerStaticClass {
private String name;
public Outclass3$InnerStaticClass() {
public String getName() {
return this.name;
public int getAge() {
return Outclass3.access$000(new Outclass3());
可以看到,静态内部类很干净,没有持有外部类的引用,我们要访问外部类的成员只能 new 一个外部类的对象。
否则只能访问外部类的静态属性和静态方法,同理外部类只能访问内部类的静态属性和静态方法。
3.局部内部类
局部内部类是指在代码块或者方法中创建的类。
它和成员内部类的区别就是:局部内部类的作用域只能在其所在的代码块或者方法内,在其它地方是无法创建该类的对象。
public class OutClass4 {
private String className = "OutClass";
class PartClassOne {
private void method() {
System.out.println("PartClassOne " + className);
new PartClassOne().method();
public void testMethod() {
class PartClassTwo {
private void method() {
System.out.println("PartClassTwo " + className);
new PartClassTwo().method();
上面的代码中我们分别在代码块和方法中创建了两个局部内部类,来看看编译后的它是怎么样的:
首先可以看到会创建两个 class 类,打开看下:
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
package com.example.simon.androidlife.innerclass
import com.example.simon.androidlife.innerclass.OutClass4
class OutClass4$1PartClassOne {
OutClass4$1PartClassOne(OutClass4 var1) {
this.this$0 = var1
private void method() {
System.out.println("PartClassOne " + OutClass4.access$000(this.this$0))
package com.example.simon.androidlife.innerclass
import com.example.simon.androidlife.innerclass.OutClass4
class OutClass4$1PartClassTwo {
OutClass4$1PartClassTwo(OutClass4 var1) {
this.this$0 = var1
private void method() {
System.out.println("PartClassTwo " + OutClass4.access$000(this.this$0))
可以看到生成的这两个字节码和成员内部类生成的很相似,都持有了外部类的引用。
不过可惜的是出了它们声明的作用域,就再也无法访问它们,可以把局部内部类理解为作用域很小的成员内部类。
4.匿名内部类
先让我们来看一段最常见的代码
Car jeep=new Car();
在Java中操纵的标识符实际是指向一个对象的引用,也就是说 jeep
是一个指向 Car
类对象的引用,而右面的 new Car()
才是真正创建对象的语句。
这可以将 jeep
抽象的理解为 Car
类对象的“名字”,而匿名内部类顾名思义可以抽象的理解为没有“名字”的内部类:
匿名内部类的特点:
1.一个类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的事先或是覆盖。
3.类名没有意义,也就是不需要使用到。
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
上面代码是 Android 中最常见的设置 button 的点击事件,其中 new OnClickListener() {…}
就是一个匿名内部类,在这里没有创建类对象的引用,而是直接创建的类对象。大部分匿名类用于接口回调。
由于 javac 无法编译 android 代码,我们写个这样的匿名内部类代码来尝试看看编译后的结果。
public class OutClass5 {
private OnClickListener mClickListener;
private OutClass5 mOutClass5;
interface OnClickListener {
void onClick();
public OutClass5 setClickListener(final OnClickListener clickListener) {
mClickListener = clickListener;
return this;
public OutClass5 setOutClass5(final OutClass5 outClass5) {
mOutClass5 = outClass5;
return this;
public void setClickInfo(final String info, int type) {
setClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("click " + info);
setClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("click2 " + info);
上面的代码中,我们创建了一个内部接口,然后在 setDefaultClicker()
中创建了两个匿名内部类,编译后的结果:
可以看到生成了三个额外的类,OutClass5$OnClickListener
是生成的成员内部类字节码,而 OutClass5$1
和 OutClass5$2
则是两个实现 OnClickListener
的子类:
class OutClass5$1 implements OnClickListener {
OutClass5$1(OutClass5 var1, String var2) {
this.this$0 = var1;
this.val$info = var2;
public void onClick() {
System.out.println("click " + this.val$info);
class OutClass5$2 implements OnClickListener {
OutClass5$2(OutClass5 var1, String var2) {
this.this$0 = var1;
this.val$info = var2;
public void onClick() {
System.out.println("click2 " + this.val$info);
从反编译的代码可以看出:创建的每个匿名内部类编译器都对应生成一个实现接口的子类,同时创建一个构造函数,构造函数的参数是外部类的引用,以及匿名函数中访问的参数。
现在我们知道了:匿名内部类也持有外部类的引用。
同时也理解了为什么匿名内部类不能有构造方法,只能有初始化代码块。 因为编译器会帮我们生成一个构造方法然后调用。
此外还可以看出,匿名内部类中使用到的参数是需要声明为 final 的,否则编译器会报错。
可能有朋友会提问了:参数为什么需要是 final 的?
我们知道在 Java 中实际只有一种传递方式:即引用传递。一个对象引用被传递给方法时,方法中会创建一份本地临时引用,它和参数指向同一个对象,但却是不同的,所以你在方法内部修改参数的内容,在方法外部是不会感知到的。
而匿名内部类是创建一个对象并返回,这个对象的方法被调用的时机不确定,方法中有修改参数的可能,如果在匿名内部类中修改了参数,外部类中的参数是否需要同步修改呢?
因此,Java 为了避免这种问题,限制匿名内部类访问的变量需要使用 final 修饰,这样可以保证访问的变量不可变。
内部类的使用场景
上面介绍了 Java 中 4 种内部类的定义,接着我们介绍这些内部类的一些使用场景。
1.成员内部类的使用场景
普通内部类可以访问外部类的所有成员和方法,因此当类 A 需要使用类 B ,同时 B 需要访问 A 的成员/方法时,可以将 B 作为 A 的成员内部类。
比如安卓开发中常见的在一个 Activity 中有一个 ListView
,我们需要创建一个特定业务的 adapter,在这个 adapter 中需要传入数据,你可以另建一个类,但如果只有当前类需要使用到,完全可以将它创建在 Activity 中:
public class VideoListActivity extends AppCompatActivity{
private ListView mVideoListView;
private BaseAdapter mListAdapter;
private List<String> mVideoInfoData;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_list);
mVideoListView = (ListView) findViewById(R.id.video_list);
mVideoInfoData = Collections.EMPTY_LIST;
mListAdapter = new VideoListAdapter();
mVideoListView.setAdapter(mListAdapter);
private class VideoListAdapter extends BaseAdapter {
@Override
public int getCount() {
return mVideoInfoData.size();
@Override
public Object getItem(final int position) {
return mVideoInfoData.get(position);
@Override
public long getItemId(final int position) {
return 0;
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
return null;
这是一种简单的使用场景。
在 Java 中普通类(非内部类)是不可以设为 private
或者 protected
,只能设置成 public
default
。
而内部类则可以,因此我们可以利用 private
内部类禁止其他类访问该内部类,从而做到将具体的实现细节完全隐藏。
比如我们有一个 Activity 既可以用作登录也可以用作注册,我们可以这样写:
public class MultiplexViewActivity extends AppCompatActivity {
public static final String DATA_VIEW_TYPE = "view_type";
public static final int TYPE_LOGIN = 1;
public static final int TYPE_REGISTER = 2;
private TextView mTitleTv;
private ViewController mViewController;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multiplex_view);
int type = getIntent().getIntExtra(DATA_VIEW_TYPE, TYPE_LOGIN);
mViewController = getViewController(type);
initView();
public ViewController getViewController(final int type) {
switch (type) {
case TYPE_REGISTER:
return new RegisterViewController();
case TYPE_LOGIN:
default:
return new LoginViewController();
private void initView() {
mTitleTv = (TextView) findViewById(R.id.multiplex_title_tv);
mViewController.initUi();
* 定义操作规范
private interface ViewController {
void initUi();
void loadData();
private class LoginViewController implements ViewController {
@Override
public void initUi() {
mTitleTv.setText("登录");
@Override
public void loadData() {
private class RegisterViewController implements ViewController {
@Override
public void initUi() {
mTitleTv.setText("注册");
@Override
public void loadData() {
解释一下上面的代码,由于要复用这个布局,所以先定义一个布局控制接口 ViewController
,再创建两个内部类实现接口,分别负责登录和注册的布局控制和数据加载。
然后提供一个方法根据参数获取具体的控制器实现 getViewController(final int type)
,这个方法可以是 public
的,外界即使拿到这个 activity 实例,也只能获取到布局控制器基类,具体的实现被隐藏了,这在后期修改某一个页面时,不用担心会对其他地方造成影响。
有朋友可能会说了:“这 2 个内部类也可以定义成普通类呀”。
确实普通类也同样能满足需求,但是我们希望这 2 个类只是在这个公共支付信息页面才用到,在外界看来是不可见或不可用的状态,这个时候内部类就能满足我们的需求。
这样的场景在 简单工厂模式、迭代器设计模式、命令设计模式都有用到,有兴趣的朋友可以去了解下。
2.静态内部类的使用场景
静态内部类只能访问外部类的静态变量和方法,但相对普通内部类的功能更为完整,因为它可以定义静态变量/方法。
当类 A 需要使用类 B,而 B 不需要直接访问外部类 A 的成员变量和方法时,可以将 B 作为 A 的静态内部类。
比较常见的一种使用场景是:在基类 A 里持有静态内部类 B 的引用,然后在 A 的子类里创建特定业务的 B 的子类,这样就结合多态和静态内部类的优势,既能拓展,又能限制范围。
我们经常使用的 LayoutParams 就是静态内部类,由于不同的布局中参数不一样,Android SDK 提供了很多种 LayoutParams:
ViewGroup.LayoutParams
WindowManager.LayoutParams
继承上一层RelativeLayout.LayoutParams
- …
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
在 View
的 setLayoutParams
中的参数类型是最上层的 ViewGroup.LayoutParams params
,这样子类就可以传入符合自己特性的 LayoutParams 实现:
public void setLayoutParams(ViewGroup.LayoutParams params) {
if (params == null) {
throw new NullPointerException("Layout parameters cannot be null");
mLayoutParams = params;
resolveLayoutParams();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onSetLayoutParams(this, params);
requestLayout();
静态内部类的另一种使用场景是:实现单例模式。
记得有一年去点评面试,面试官让我写个静态内部类实现的单例模式,我写的过程中不确定静态内部类是否可以有静态成员,基础有多差可想而知。
先来看一下如何实现:
public class LocationManager{
private static class ClassHolder {
private static final LocationManager instance = new LocationManager();
public static LocationManager getInstance() {
return ClassHolder.instance;
我们知道静态内部类功能和普通类一致,所以有 static 成员不足为奇。现在的问题是,为什么这种单例模式比较好?
原因有两点:
- 懒加载:类加载时不会创建实例,只有当
getInstance()
方法被调用时才去加载静态内部类以及其中持有的 LocationManager
实例 - 线程安全:JVM 加载类时,可以确保
instance
变量只能初始化一次
3.匿名内部类的使用场景
Android 开发中设置一个按钮的点击事件很简单,直接 new 一个 View.OnClickListener
然后实现方法即可:
mButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
结合前面谈到的,编译器会为每个匿名内部类创建一个 Class 文件。个人觉得在安卓开发中,有多个按钮需要设置点击事件时,让当前类实现 OnClickListener
接口然后在 onClick()
中根据 id 判断事件,比创建一大堆匿名内部类要好些,你觉得呢?
之所以这样写,是因为我们不需要持有这个 new View.OnClickListener
的引用,只要创建了对象即可。
所以使用场景可以是:一个方法的返回值是接口,然后根据不同参数返回不同的实现,我们不需要保存引用,直接 new 一个接口实现即可。
来看一个有趣的例子:
public class GirlFriendMaker {
public interface GirlFriend {
void sayHi();
public static GirlFriend giveMeAGirlFriend(final String name) {
return new GirlFriend() {
@Override
public void sayHi() {
Log.i("来自女朋友的问候", "Hello I'm " + name);
4.局部内部类
局部内部类只用于当前方法或者代码块中创建、使用,一次性产品,使用场景比较少。
经过前面的介绍我们知道,四种内部类中除了静态内部类,只要访问外部类的成员/方法,就会持有外部类的引用。
当内部类持有外部类的引用,同时生命周期比外部类要长(比如执行耗时任务、被其他长生命周期对象持有),就会导致外部类该被回收时无法被回收,也就是内存泄漏问题。
一个 Android 开发中常见的内部类导致内存泄露的例子:
public class MainActivity extends AppCompatActivity {
public final int LOGIN_SUCCESS = 1;
private Context mContext;
private boolean isLongTimeNoMsg;
@SuppressWarnings("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
isLongTimeNoMsg = false;
switch (msg.what) {
case LOGIN_SUCCESS: {/
break;
这个 Handler 持有外部类的引用,它发送的 runnable 对象,会被进一步包装为 message 对象,放入消息队列,在被执行、回收之前会一致持有引用,导致无法释放。
解决办法就是使用弱引用或者干脆将 Handler 设计为静态内部类。
总的来说,内部类一般用于两个场景:
- 需要用一个类来解决一个复杂的问题,但是又不希望这个类是公共的
- 需要实现一个接口,但不需要持有它的引用
没有引用外部类的内部类,最好设置为 static 的,那样可以避免非静态内部类的弊端:持有外部引用、导致外部类更大。比如说一般的 Adapter, holder。
本篇文章介绍了 Java 开发中四种内部类的概念、反编译后的格式以及使用场景。相信看完这篇文章,你对开头的两道题已经有了答案。
基础就是这样,不论你走的多远,都需要及时回顾、弥补,等工作中需要用到才补,会错失很多机会。
这个系列的目的是帮助大家系统、完整的打好基础、逐渐深入学习,如果你对这些已经很熟了,请不要吝啬你的评价,多多指出问题,我们一起做的更好!
文章同步发送于微信公众号:安卓进化论,欢迎关注,第一时间获取新文章。
《Java编程思想》
http://blog.csdn.net/qq7342272/article/details/6671433
http://www.cnblogs.com/latter/p/5665015.html
https://www.javaworld.com/article/2077411/core-java/inner-classes.html
https://www.cnblogs.com/zhangyinhua/p/7260651.html
http://www.jianshu.com/p/6a362ea4dfd8
我的博客即将同步至腾讯云+社区,邀请大家一同入驻。
文章出自:安卓进阶学习指南 主要贡献者: Cloud9527 Alex_赵 Struggle shixinzhang 读完本文你将了解: 背景介绍四种内部类介绍成员内部类静态内部类局部内部类匿名内部类内部类的使用场景成员内部类的使用场景静态内部类的使用场景匿名内部类的使用场景局部内部类内存泄漏总结...
成员内部类
成员内部类就像是在外部类中定义了一个成员变量一样,因此成员内部类可以被public、protected、private或者包访问权限等多种权限修饰。同样的,如果是被public修饰的内部类,则可以在外部创建并调用,如果是被private修饰的内部类,则只能在外部类中创建并调用。
内部类中也可以定义若干字段与方法,它们同样可以被多种权限修饰,这点和普通的类是一样的。需要注意的一点是,如...
所谓内部类,即定义在另一个类中的类。那么,为什么会有内部类这个概念,他的使用场景又是什么呢?首先,来看一下内部类的特点:
1.它体现了一种代码的隐藏机制和访问控制机制,内部类与所在外部类有一定的关系,往往只有该外部类调用此内部类,所以没有必要专门用一个Java文件存放这个类。
public class Outer {
private int num;
privat...
在
java中一个类的
内部可以存在另一个类,这个类被称为
内部类,也被叫做类的嵌套,
内部类大体可以分为静态
内部类和非静态
内部类下面来简单研究下静态
内部类
public class Demo1 {
private static int i =1;
static class Inner{
public static void fun(){
System.out.println(i);
Java内部类是Java言语的一个很重要的概念,《Java编程思想》花了很大的篇幅来讲述这个概念。但是我们在实践中很少用到它,虽然我们在很多时候会被动的使用到它,但它仍然像一个幕后英雄一样,不为我们所知,不为我们所用。
本文不试图来讲述Java内部类的今生前世、来龙去脉,这些在网络上都已经汗牛充栋。如果读者想了解这些,可以在网络上搜索来学习。Java内部类总是躲在它的外部类里,像一个幕后英雄一样。但是幕后英雄也有用武之地,在很多时候,恰当的使用Java内部类能起到让人拍案叫绝的作用。本文试图谈一谈让这个幕后英雄也有用武之地的四个场景,希望引起大家对使用Java内部类的兴趣。
Class类本身还提供对于获取内部类字节码的方法,分别为getClasses和getDeclaredClasses(),其中getClasses()只能得到访问级别为public的内部类,而getDeclaredClasses()则能得到所有声明了的内部类。
由于内部类可以分为实例内部类,静态内部类,匿名内部类,前面提到的getClasses()和getDeclaredClasses()目前还都
public void subscribeQueue(Jedis jedis,String[] channels) {
// TODO Auto-generated method stub
class MyJedisPubSub extendsJedisPubSub{
内部类的定义:在java中,允许在一个类的内部定义类,称为内部类,这个内部类所在的类称为外部类。内部类定义在外部内的内部,通常只服务于外部类,对于其他外部是不具备可见性的。内部类应用场景:1.当某个类只为一个类提供服务时,可以将这个类定义成内部类 2.可以解决接口或者抽象类不能实例化的问题
成员内部类:在一个类中除了可以定义成员变量,成年方法,还可以定义类,这样的类称为成员内部类。在成员内部类中可以访问外部类的所有成员资源(包括私有成员),例如成员变量
8. boolean(1位)
其中,byte、short、int、long、float和double都是数字类型,用于存储数字;char用于存储Unicode字符;boolean用于存储布尔值(true或false)。
它们的
字节大小分别为:
1. byte:1
字节
2. short:2
字节
3. int:4
字节
4. long:8
字节
5. float:4
字节
6. double:8
字节
7. char:2
字节
8. boolean:1位
需要注意的是,boolean虽然只占用1位,但在内存中仍会被占用1
字节的空间。