雪习新知识:Java 内部类


本文出自 http://blog.csdn.net/zhaizu/article/details/49176543,转载请注明出处。

嵌套类,内部类,静态内部类,静态嵌套类,匿名类,成员类,局部类,傻傻分不清?

各种类,各种累!本文为你抽丝剥茧,庖丁解牛,娓娓道来。

首先声明一下,本文要讲的不是一个文件里面并列的两个类,而是在一个类里面定义另外一个类

1. 几个例子

例1:Adapter

public class ListViewActivity extends Activity {

    // some stuff

    class MyAdapter extends BaseAdapter {
        // some stuff
    }
}

例2:AlertDialog.Builder

用法:

public class ListViewActivity extends Activity {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("对话框");
        AlertDialog dialog = builder.create();
        dialog.show();
}

AlertDialog.Builder 对应的 SDK 源代码:

public class AlertDialog extends Dialog implements DialogInterface {

    // unrelated code

    public static class Builder {
        private final AlertController.AlertParams P;
        private int mTheme;
        ...
    }

    // unrelated code
}

例3:Thread

new Thread() { 
 
     @Override 
     public void run() { 
         // TODO Auto-generated method stub 
     } 
}.start(); 

例4:Button.OnClickListener

((Button)findViewById(R.id.start)).setOnClickListener(new Button.OnClickListener() { 
    @Override 
    public void onClick(View v) { 
    } 
});

其中,OnClickListener 是 interface,对应的源代码在 View.java 中:

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    
    // more code
    
    public interface OnClickListener {
        void onClick(View v);
    }
   
    // more code
}

例5:

public class Parcel4 { 
    public Destination destination(String s) { 
        class PDestination implements Destination { 
            private String label; 
 
            private PDestination(String whereTo) { 
                label = whereTo; 
            } 
 
            public String readLabel() { 
                return label; 
            } 
        } 
        return new PDestination(s); 
    } 
 
    public static void main(String[] args) { 
        Parcel4 p = new Parcel4(); 
        Destination d = p.destination("Tasmania"); 
    } 
} 

public class Parcel5 { 
    private void internalTracking(boolean b) { 
        if (b) { 
            class TrackingSlip { 
                private String id; 
                TrackingSlip(String s) { 
                    id = s; 
                } 
                String getSlip() { 
                    return id; 
                } 
            } 
            TrackingSlip ts = new TrackingSlip("slip"); 
            String s = ts.getSlip(); 
        } 
    } 
 
    public void track() { 
        internalTracking(true); 
    } 
 
    public static void main(String[] args) { 
        Parcel5 p = new Parcel5(); 
        p.track(); 
    } 
} 

例6:Android Guide 对于 Fragment 的官方文档 Example (注意 TitlesFragment 是 static 的):

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    ...
    }
}

public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}

例7:Handler

// Handler声明为static类,对外部类成员的引用改由WeakReference实现,如:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tipTv = (TextView) findViewById(R.id.tiptv);

        mHandler = new MyHandler(tipTv);
        mHandler.sendEmptyMessageDelayed(MSG_TICK, 2000);
    }


    static class MyHandler extends Handler {
        private final WeakReference<TextView> mTvRef;

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mTvRef.get() instanceof TextView) {
                mTvRef.get().setText("test");
            }
        }

        MyHandler(TextView textView) {
            mTvRef = new WeakReference<TextView>(textView);
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

例8:Google 给的 listview 优化的建议:

static class ViewHolder {
    TextView text;
    ImageView icon;
}

public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.list_item_icon_text, null);

            holder = new ViewHolder();
            holder.text = (TextView) convertView.findViewById(R.id.text);
            holder.icon = (ImageView) convertView.findViewById(R.id.icon);

            convertView.setTag(holder);
        } else {
        }
        holder.text.setText(DATA[position]);
        holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);

        return convertView;
    }

2. 眼花缭乱的各种类

那么问题来了,以上例子中,哪几个属于内部类?

要判断某个类是不是内部类,就要知道内部类的定义。

那么问题来了,谁下的内部类的定义才是最权威的?

《Thinking in Java》,《Effective Java》,《Java 核心技术》《xxx天精通Java》,《Java 高手进阶》,还是某某博客?

都不是!

Java 技术领域有 3 本书:

  • Java Virtual Machine Specification
  • Java Language Specification
  • Design Patterns: Elements of Reusable Object-Oriented Software

从底层的虚拟机原理,到包罗万象的编程语言规范,再到顶层的系统设计,应有尽有,比人类历史上销量第一的《圣经》和第二的《毛主席语录》都权威。

Java Language Specification(jls8,8.1.3节)对内部类的定义:

An inner class is a nested class that is not explicitly or implicitly declared static.

翻译成中文是:内部类是没有显式或隐式的 static 修饰的嵌套类(nested class)。
将上述定义分割成 3 个重点:

  • explicitly or implicitly
    看到 explicitly or implicitly 这个词组,不知道大家有没有想到 Android 中 Activity 的跳转方式?Activity 的跳转方式有显式(explicit)和隐式(implicit) 2 种方式;在这里,explicitly 是指带 static 修饰符,implicitly 是指虽然没带 static 但是等效于带 static 的情况,实际是指在类内部声明接口的情况。
  • static
    请看第3节
  • nested class
    请看第 3 节

3. 嵌套类(nested class)

在这里插入图片描述

Java 允许在一个类里面定义另外一个类,里面的这个类被称为嵌套类。形式如下:

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}

其中,根据根据嵌套类前面有无 static 修饰符,可以将嵌套类分为静态嵌套类(static nested class)和非静态嵌套类(non-static nested class),后者又称为内部类(inner class)。

class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}

3.1 内部类(inner class)

3.1.1 局部内部类(local class)

局部内部类是指定义在语句块内的类,语句块是指成对大括号形成的块,可以是一个方法体,或者 if 语句,或者 for 循环语句。

下面这个例子(引自 Orcale 官方文档 并做了简单修改),作用是验证电话号码位数是否合法,在 validatePhoneNumber 方法体内定义了局部内部类 PhoneNumber:

public class LocalClassExample {
  
    static String regularExpression = "[^0-9]";
  
    public static void validatePhoneNumber(
        String phoneNumber1, String phoneNumber2) {
      
        final int numberLength = 11;
        
        // Valid in JDK 8 and later:
       
        // int numberLength = 11;
       
        class PhoneNumber {
            
            String formattedPhoneNumber = null;

            PhoneNumber(String phoneNumber){
                // numberLength = 7;
                String currentNumber = phoneNumber.replaceAll(
                  regularExpression, "");
                if (currentNumber.length() == numberLength)
                    formattedPhoneNumber = currentNumber;
                else
                    formattedPhoneNumber = null;
            }

            public String getNumber() {
                return formattedPhoneNumber;
            }
            
            // Valid in JDK 8 and later:

//            public void printOriginalNumbers() {
//                System.out.println("Original numbers are " + phoneNumber1 +
//                    " and " + phoneNumber2);
//            }
        }

        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
        
        // Valid in JDK 8 and later:

//        myNumber1.printOriginalNumbers();

        if (myNumber1.getNumber() == null) 
            System.out.println("First number is invalid");
        else
            System.out.println("First number is " + myNumber1.getNumber());
        if (myNumber2.getNumber() == null)
            System.out.println("Second number is invalid");
        else
            System.out.println("Second number is " + myNumber2.getNumber());

    }

    public static void main(String... args) {
        validatePhoneNumber("123-456-78901", "456-7890");
    }
}

上述例子首先过滤掉所有非 0-9 数字的字符,然后检查剩下的数字是否够 11 位,输出如下:

First number is 12345678901
Second number is invalid
Accessing Members of an Enclosing Class

和内部类一样,局部内部类不能定义或声明 static 类型的成员方法或成员变量(常量除外)。定义在 static 方法内部的类,如 PhoneNumber,只能引用外部类的 static 成员。比如,如果不把 regularExpression 声明为 static,Java 编译器会抛出类似“non-static variable regularExpression cannot be referenced from a static context.”的异常信息。

局部内部类不能是 static 的,可以引用语句块的实例成员,而且不能包含 static 类型的成员。

在一个语句块中不能声明 interface,因为 interface 天生就是 static 的。例如,下面的代码是无法编译的因为 interface HelloThere 在 greetInEnglish 方法体内:

public void greetInEnglish() {
        interface HelloThere {
           public void greet();
        }
        class EnglishHelloThere implements HelloThere {
            public void greet() {
                System.out.println("Hello " + name);
            }
        }
        HelloThere myGreeting = new EnglishHelloThere();
        myGreeting.greet();
    }

局部内部类中不允许声明 static 类型的成员方法或成员 interface。下面的代码段无法编译因为方法 EnglishGoodbye.sayGoodbye 是 static 的,编译器会报错:“modifier ‘static’ is only allowed in constant variable declaration”:

    public void sayGoodbyeInEnglish() {
        class EnglishGoodbye {
            public static void sayGoodbye() {
                System.out.println("Bye bye");
            }
        }
        EnglishGoodbye.sayGoodbye();
    }

局部内部类可以拥有的 static 类型的成员是常量(常量是基本类型的数据结构,或者是字符串,被 final 修饰,而且能在编译期间通过常量表达式赋初值,通常是字符串或者算术表达式)。下列代码段能够编译通过因为 EnglishGoodbye.farewell 是常量:

    public void sayGoodbyeInEnglish() {
        class EnglishGoodbye {
            public static final String farewell = "Bye bye";
            public void sayGoodbye() {
                System.out.println(farewell);
            }
        }
        EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye();
        myEnglishGoodbye.sayGoodbye();
    }

3.1.2 成员内部类(member class)

3.2 静态嵌套类(static nested class)

3.2.0 interface

首先来说下嵌套的 interface,interface 属于 class 的一种,之所以把它放在此节,是因为 interface 是一种隐式的静态嵌套类。思考如下两点:

  • View.OnClickListener,OnClickListener 是定义在 View.java 内部的 interface,我们的使用方法是 class Clazz implements View.OnClickListener,这可不就是 static nested class 的用法吗?!
  • 所有的 interface,不管是嵌套在某个类内部的 interface,还是单独定义在某个文件中名称与文件名相同的 interface,是可以声明成员变量的,而且变量是 static final 即常量类型(注意这是到 IT 笔试/面试题),这也佐证 interface 是 static 的。至于我们为什么很少在 interface 中声明成员变量,那是因为 interface 的初衷是赋予子类某些“行为”即子类实现 interface 的方法,完成自己独特的“行为”,通过超类(父类或 interface)指针指向子类对象的形式,在运行过程中自己去调用子类的独特的方法,这就是“多态”,这就是“面向接口编程”。
    ###3.2.1 实例化方式

与内部类的实例化相反,静态嵌套类的实例化不依赖其外部类的实例,即不需要先实例化外部类,然后才能由外部类实例创建静态嵌套类实例。而且,静态嵌套类只能访问其外部类的静态成员。

/* 下面程序演示如何在java中创建静态嵌套类和内部类 */
class OuterClass {
   private static String msg = "GeeksForGeeks";

   // 静态嵌套类
   public static class StaticNestedClass{

       // 静态嵌套类只能访问外部类的静态成员
       public void printMessage() {

         // 试着将msg改成非静态的,这将导致编译错误 
         System.out.println("Message from nested static class: " + msg); 
       }
    }

    // 内部类
    public class InnerClass {

       // 不管是静态方法还是非静态方法都可以在内部类中访问
       public void display() {
          System.out.println("Message from non-static nested class: "+ msg);
       }
    }
} 

class Main {
    // 怎么创建静态嵌套类和内部类的实例
    public static void main(String args[]){

       // 创建静态嵌套类的实例
       OuterClass.StaticNestedClass printer = new OuterClass.StaticNestedClass();

       // 创建静态嵌套类的非静态方法
       printer.printMessage();   

       // 为了创建内部类,我们需要外部类的实例
       OuterClass outer = new OuterClass();        
       OuterClass.InnerClass inner = outer.new InnerClass();

       // 调用内部类的非静态方法
       inner.display();

       // 我们也可以结合以上步骤,一步创建的内部类实例
       OuterClass.InnerClass innerObject = new OuterClass().new InnerClass();

       // 同样我们现在可以调用内部类方法
       innerObject.display();
    }
}

3.2.2 使用场景

静态嵌套类的使用场景,可以参考其语法,即,静态嵌套类不依赖外部类的具体实例,外部类的所有实例公用的部分。

以计算器类 Caculator 为例,Caculator 中的运算符 Operator 我们打算使用嵌套类来实现。Operator 的成员变量 PLUS、MINUS 等变量,显然是所有 Caculator 公用的,这时我们最好将其声明为 static,这样所有的运算符都可以通过 Caculator.Operator.PLUS 的方式调用。

此外,可以使用静态嵌套类实现单例模式(更多实现方式见《单例模式》)。实例代码如下:

public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        public static Singleton INSTANCE = new Singleton();
    }

    public Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

3.2.3 优先使用静态嵌套类

内部类实例会持有其外部实例的引用,这样会增大内部类实例占用的空间。而且,GC 根据可达性原则回收对象,内部实例引用外部实例会增加外部实例存活的时间,不利于外部实例被及时回收,不利于性能提升。

比较稳妥的方法是,所有的内部类全部声明成 static 的,然后根据 IDE 的提示进行修改,实在无法进行下去的(如内部类引用了外部类的某个非静态成员,而该静态成员无法改成 static 类型),则使用内部类,否则就使用静态嵌套类。

在使用 Firebug 进行代码检查时,Firebug 会提示将内部类改为静态嵌套类,并将该问题归为性能大类,截图如下:
在这里插入图片描述

给出的理由如下:

This class is an inner class, but does not use its embedded reference to the object which created it. This reference makes the instances of the class larger, and may keep the reference to the creator object alive longer than necessary. If possible, the class should be made static.

关于 Fragment 是否应该声明为 static,stackoverflow 上有过一篇帖子,可供参考:What is the design logic behind Fragments as static inner classes vs standalone public classes?

本文出自 http://blog.csdn.net/zhaizu/article/details/49176543,转载请注明出处。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值