成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Java 基礎(chǔ)鞏固:內(nèi)部類的字節(jié)碼學(xué)習(xí)和實(shí)戰(zhàn)使用場(chǎng)景

weij / 1642人閱讀

摘要:內(nèi)部類的使用場(chǎng)景上面介紹了中種內(nèi)部類的定義,接著我們介紹這些內(nèi)部類的一些使用場(chǎng)景。成員內(nèi)部類的使用場(chǎng)景普通內(nèi)部類可以訪問外部類的所有成員和方法,因此當(dāng)類需要使用類,同時(shí)需要訪問的成員方法時(shí),可以將作為的成員內(nèi)部類。

文章出自:安卓進(jìn)階學(xué)習(xí)指南
主要貢獻(xiàn)者:

Cloud9527

Alex_趙

Struggle

shixinzhang

讀完本文你將了解: [TOC]

通過反編譯介紹四種內(nèi)部類

結(jié)合實(shí)戰(zhàn)介紹內(nèi)部類的使用場(chǎng)景

背景介紹

大家好,這篇文章是 《安卓進(jìn)階技能樹計(jì)劃》 的第一部分 《Java 基礎(chǔ)系列》 的第三篇。

我們做這個(gè)活動(dòng),除了要保證知識(shí)點(diǎn)的全面、完整,還想要讓每一篇文章都有自己的思考,盡可能的將知識(shí)點(diǎn)與實(shí)踐結(jié)合,努力讓讀者讀了有所收獲。每位小伙伴都有工作在身,每個(gè)知識(shí)點(diǎn)都需要經(jīng)過思考、學(xué)習(xí)、寫作、提交、審核、修改、編輯、發(fā)布等多個(gè)過程,所以整體下來時(shí)間就會(huì)慢一些,這里先向各位道歉。

《Java 基礎(chǔ)系列》初步整理大概有 12 篇,主要內(nèi)容為:

抽象類和接口 (完成)

內(nèi)部類

修飾符

裝箱拆箱

注解

反射

泛型

異常 (完成)

集合

IO

字符串

其他

這一篇我們來聊聊內(nèi)部類。

“內(nèi)部類”聽起來是非常普遍的東西,有些朋友會(huì)覺得:這個(gè)太基礎(chǔ)了吧,有啥好說的,你又來糊弄我。

既然你這么自信,那就來試兩道筆試題吧!

第一道:要求使用已知的變量,在三個(gè)輸出方法中填入合適的代碼,在控制臺(tái)輸出30,20,10。

      class Outer {
            public int num = 10;
            class Inner {
                public int num = 20;
                public void show() {
                    int num = 30;
                    System.out.println(?);    //填入合適的代碼
                    System.out.println(??);
                    System.out.println(???);
                }
            }
        }

        class InnerClassTest {
            public static void main(String[] args) {
                Outer.Inner oi = new Outer().new Inner();
                oi.show();
            }    
        }

接招,第二題:補(bǔ)齊代碼 ,要求在控制臺(tái)輸出”HelloWorld

        interface Inter { 
            void show(); 
        }
        class Outer { 
            //補(bǔ)齊代碼 
        }
        class OuterDemo {
            public static void main(String[] args) {
                  Outer.method().show();
              }
        }

題目來自:https://www.cnblogs.com/zhang...

先思考幾秒,看看這些題你能否應(yīng)付得來。

在面試中常常遇到這樣的筆試題,咋一看這題很簡(jiǎn)單,還是會(huì)有很多人答不好。根本原因是很多人對(duì)“內(nèi)部類”的理解僅限于名稱。

“內(nèi)部類、靜態(tài)內(nèi)部類、匿名內(nèi)部類”是什么大家都清楚。但是當(dāng)轉(zhuǎn)換一下思維,不僅僅為了完成功能,而是要保證整個(gè)項(xiàng)目架構(gòu)的穩(wěn)定靈活可擴(kuò)展性,你會(huì)如何選擇呢?

這篇文章我們努力回答這些問題,也希望你可以說出你的答案。

四種內(nèi)部類介紹

定義在一個(gè)類中或者方法中的類稱作為內(nèi)部類。

內(nèi)部類又可以細(xì)分為這 4 種:

成員內(nèi)部類

局部?jī)?nèi)部類

匿名內(nèi)部類

靜態(tài)內(nèi)部類

1.成員內(nèi)部類

成員內(nèi)部類就是最普通的內(nèi)部類,它定義在一個(gè)類的內(nèi)部中,就如同一個(gè)成員變量一樣。如下面的形式:

public class OutClass2 {
    private int i = 1;
    public static String str = "outclass";

    class InnerClass { // 成員內(nèi)部類
        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) {
        //先創(chuàng)建外部類對(duì)象
        OutClass2 outClass = new OutClass2(); 
        //創(chuàng)建內(nèi)部類對(duì)象
        OutClass2.InnerClass in = outClass.new InnerClass();
        //內(nèi)部類對(duì)象調(diào)用自己的方法
        in.innerMethod();
    }
} 

因?yàn)閮?nèi)部類依附于外部類存在,所以需要外部類的實(shí)例來創(chuàng)建內(nèi)部類:

outClass.new InnerClass()

注意不是直接 new outClass.InnerClass() 。

成員內(nèi)部類可以無條件的訪問外部類的成員屬性和成員方法(包括 private 和 static 類型的成員),這是因?yàn)樵趦?nèi)部類中,隱式地持有了外部類的引用。

我們編譯上述的代碼,可以看到,會(huì)生成兩個(gè) class 文件:

這個(gè) OutClass2$InnerClass.class 就是內(nèi)部類對(duì)應(yīng)的字節(jié)碼文件,我們使用 AS 打開,會(huì)自動(dòng)進(jìn)行反編譯:

//
// 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);
    }
}

可以看到,在內(nèi)部類 OutClass2$InnerClass 的字節(jié)碼中,編譯器為我們生成了一個(gè)參數(shù)為外部類對(duì)象的構(gòu)造方法,這也解釋了內(nèi)部類為什么可以直接訪問外部類的內(nèi)容,因?yàn)槌钟型獠款惖囊?/strong>!

在這個(gè)不完整的反編譯字節(jié)碼中,我們可以看到,編譯器會(huì)為內(nèi)部類創(chuàng)建一個(gè)叫做 this$0 的對(duì)象,它是外部類的引用。

innerMethod() 中的 OutClass2.access$000(this.this$0)) 是什么意思呢?

為了幫助內(nèi)部類訪問外部類的數(shù)據(jù),編譯器會(huì)生成這個(gè) access$ 方法,參數(shù)是外部類的引用,如果外部類有 N 個(gè)成員,編譯器會(huì)生成多個(gè) access 方法,$ 符號(hào)后面的數(shù)字會(huì)會(huì)隨著不同的聲明順序而改變,可以理解為一種橋接方法

對(duì)比內(nèi)部類的 innerMethod() 的 java 代碼和字節(jié)碼我們可以得出這些結(jié)論:

在內(nèi)部類中,直接使用變量名,會(huì)按照從方法中的局部變量、到內(nèi)部類的變量、到外部類的變量的順序訪問

也就是說,如果在外部類、內(nèi)部類、方法中有重名的變量/方法,編譯器會(huì)把方法中直接訪問變量的名稱修改為方法的名稱

如果想在方法中強(qiáng)制訪問內(nèi)部類的成員變量/方法,可以使用 this.i,這里的 this 表示當(dāng)前的內(nèi)部類對(duì)象

如果想在方法中強(qiáng)制訪問外部類的成員變量/方法,可以使用 OutClass.this.i,這里的 OutClass.this 表示當(dāng)前外部類對(duì)象

成員內(nèi)部類就如同外部類的成員一樣,同樣可以被public、protected、private、缺?。╠efault)這些修飾符來修飾。

但是有一個(gè)限制是:成員內(nèi)部類不能創(chuàng)建靜態(tài)變量/方法。如果我們嘗試創(chuàng)建,編譯器會(huì)直接 say no。

為什么會(huì)這樣呢?

Stackoverflow 有一個(gè)回答很好:

“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. ”

我們知道要使用一個(gè)類的靜態(tài)成員,需要先把這個(gè)類加載到虛擬機(jī)中,而成員內(nèi)部類是需要由外部類對(duì)象 new 一個(gè)實(shí)例才可以使用,這就無法做到靜態(tài)成員的要求。

2.靜態(tài)內(nèi)部類

說完成員內(nèi)部類我們來看看靜態(tài)內(nèi)部類。

使用 static 關(guān)鍵字修飾的內(nèi)部類就是靜態(tài)內(nèi)部類,靜態(tài)內(nèi)部類和外部類沒有任何關(guān)系,可以看作是和外部類平級(jí)的類。

我們來反編譯個(gè)靜態(tài)內(nèi)部類看看。

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;
        }
    }
}

編譯后的靜態(tài)內(nèi)部類:

//
// 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.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());
    }
}

可以看到,靜態(tài)內(nèi)部類很干凈,沒有持有外部類的引用,我們要訪問外部類的成員只能 new 一個(gè)外部類的對(duì)象。

否則只能訪問外部類的靜態(tài)屬性和靜態(tài)方法,同理外部類只能訪問內(nèi)部類的靜態(tài)屬性和靜態(tài)方法。

3.局部?jī)?nèi)部類

局部?jī)?nèi)部類是指在代碼塊或者方法中創(chuàng)建的類。

它和成員內(nèi)部類的區(qū)別就是:局部?jī)?nèi)部類的作用域只能在其所在的代碼塊或者方法內(nèi),在其它地方是無法創(chuàng)建該類的對(duì)象。

public class OutClass4 {
    private String className = "OutClass";

    {
        class PartClassOne { // 局部?jī)?nèi)部類
            private void method() {
                System.out.println("PartClassOne " + className);
            }
        }
        new PartClassOne().method();
    }

    public void testMethod() {
        class PartClassTwo { // 局部類內(nèi)部類
            private void method() {
                System.out.println("PartClassTwo " + className);
            }
        }
        new PartClassTwo().method();
    }
}

上面的代碼中我們分別在代碼塊和方法中創(chuàng)建了兩個(gè)局部?jī)?nèi)部類,來看看編譯后的它是怎么樣的:

首先可以看到會(huì)創(chuàng)建兩個(gè) 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));
    }
}

可以看到生成的這兩個(gè)字節(jié)碼和成員內(nèi)部類生成的很相似,都持有了外部類的引用。

不過可惜的是出了它們聲明的作用域,就再也無法訪問它們,可以把局部?jī)?nèi)部類理解為作用域很小的成員內(nèi)部類。

4.匿名內(nèi)部類

先讓我們來看一段最常見的代碼

Car jeep=new Car();

在Java中操縱的標(biāo)識(shí)符實(shí)際是指向一個(gè)對(duì)象的引用,也就是說 jeep 是一個(gè)指向 Car 類對(duì)象的引用,而右面的 new Car() 才是真正創(chuàng)建對(duì)象的語句。

這可以將 jeep 抽象的理解為 Car 類對(duì)象的“名字”,而匿名內(nèi)部類顧名思義可以抽象的理解為沒有“名字”的內(nèi)部類:

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
    // TODO Auto-generated method stub
    }
});

上面代碼是 Android 中最常見的設(shè)置 button 的點(diǎn)擊事件,其中 new OnClickListener() {…} 就是一個(gè)匿名內(nèi)部類,在這里沒有創(chuàng)建類對(duì)象的引用,而是直接創(chuàng)建的類對(duì)象。大部分匿名類用于接口回調(diào)。

由于 javac 無法編譯 android 代碼,我們寫個(gè)這樣的匿名內(nèi)部類代碼來嘗試看看編譯后的結(jié)果。

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);
            }
        });
    }
}

上面的代碼中,我們創(chuàng)建了一個(gè)內(nèi)部接口,然后在 setDefaultClicker() 中創(chuàng)建了兩個(gè)匿名內(nèi)部類,編譯后的結(jié)果:

可以看到生成了三個(gè)額外的類,OutClass5$OnClickListener 是生成的成員內(nèi)部類字節(jié)碼,而 OutClass5$1OutClass5$2 則是兩個(gè)實(shí)現(xiàn) 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);
    }
}

從反編譯的代碼可以看出:創(chuàng)建的每個(gè)匿名內(nèi)部類編譯器都對(duì)應(yīng)生成一個(gè)實(shí)現(xiàn)接口的子類,同時(shí)創(chuàng)建一個(gè)構(gòu)造函數(shù),構(gòu)造函數(shù)的參數(shù)是外部類的引用,以及匿名函數(shù)中訪問的參數(shù)。

現(xiàn)在我們知道了:匿名內(nèi)部類也持有外部類的引用。

同時(shí)也理解了為什么匿名內(nèi)部類不能有構(gòu)造方法,只能有初始化代碼塊。 因?yàn)榫幾g器會(huì)幫我們生成一個(gè)構(gòu)造方法然后調(diào)用。

此外還可以看出,匿名內(nèi)部類中使用到的參數(shù)是需要聲明為 final 的,否則編譯器會(huì)報(bào)錯(cuò)。

可能有朋友會(huì)提問了:參數(shù)為什么需要是 final 的?

我們知道在 Java 中實(shí)際只有一種傳遞方式:即引用傳遞。一個(gè)對(duì)象引用被傳遞給方法時(shí),方法中會(huì)創(chuàng)建一份本地臨時(shí)引用,它和參數(shù)指向同一個(gè)對(duì)象,但卻是不同的,所以你在方法內(nèi)部修改參數(shù)的內(nèi)容,在方法外部是不會(huì)感知到的。

而匿名內(nèi)部類是創(chuàng)建一個(gè)對(duì)象并返回,這個(gè)對(duì)象的方法被調(diào)用的時(shí)機(jī)不確定,方法中有修改參數(shù)的可能,如果在匿名內(nèi)部類中修改了參數(shù),外部類中的參數(shù)是否需要同步修改呢?

因此,Java 為了避免這種問題,限制匿名內(nèi)部類訪問的變量需要使用 final 修飾,這樣可以保證訪問的變量不可變。

內(nèi)部類的使用場(chǎng)景

上面介紹了 Java 中 4 種內(nèi)部類的定義,接著我們介紹這些內(nèi)部類的一些使用場(chǎng)景。

1.成員內(nèi)部類的使用場(chǎng)景

普通內(nèi)部類可以訪問外部類的所有成員和方法,因此當(dāng)類 A 需要使用類 B ,同時(shí) B 需要訪問 A 的成員/方法時(shí),可以將 B 作為 A 的成員內(nèi)部類。

比如安卓開發(fā)中常見的在一個(gè) Activity 中有一個(gè) ListView,我們需要?jiǎng)?chuàng)建一個(gè)特定業(yè)務(wù)的 adapter,在這個(gè) adapter 中需要傳入數(shù)據(jù),你可以另建一個(gè)類,但如果只有當(dāng)前類需要使用到,完全可以將它創(chuàng)建在 Activity 中:

public class VideoListActivity extends AppCompatActivity{
    private ListView mVideoListView;
    private BaseAdapter mListAdapter;
    private List 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 內(nèi)部類說明這個(gè) adapter 只能在當(dāng)前類中使用
    private class VideoListAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return mVideoInfoData.size();   //訪問外部類數(shù)據(jù)
        }

        @Override
        public Object getItem(final int position) {
            return mVideoInfoData.get(position);    //訪問外部類數(shù)據(jù)
        }

        @Override
        public long getItemId(final int position) {
            return 0;
        }

        @Override
        public View getView(final int position, final View convertView, final ViewGroup parent) {
            return null;
        }
    }
}

這是一種簡(jiǎn)單的使用場(chǎng)景。

在 Java 中普通類(非內(nèi)部類)是不可以設(shè)為 private 或者 protected,只能設(shè)置成 public default。

而內(nèi)部類則可以,因此我們可以利用 private 內(nèi)部類禁止其他類訪問該內(nèi)部類,從而做到將具體的實(shí)現(xiàn)細(xì)節(jié)完全隱藏。

比如我們有一個(gè) Activity 既可以用作登錄也可以用作注冊(cè),我們可以這樣寫:

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();
    }

    //外界只能拿到基類,具體實(shí)現(xiàn)隱藏
   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();
    }

    /**
     * 定義操作規(guī)范
     */
    private interface ViewController {
        void initUi();

        void loadData();
    }

    private class LoginViewController implements ViewController {
        @Override
        public void initUi() {
            mTitleTv.setText("登錄");
            //顯示登錄需要的布局
        }

        @Override
        public void loadData() {
            //加載登錄需要的數(shù)據(jù)
        }
    }

    private class RegisterViewController implements ViewController {
        @Override
        public void initUi() {
            mTitleTv.setText("注冊(cè)");
            //顯示注冊(cè)需要的布局
        }

        @Override
        public void loadData() {
            //加載注冊(cè)需要的數(shù)據(jù)
        }
    }
}

解釋一下上面的代碼,由于要復(fù)用這個(gè)布局,所以先定義一個(gè)布局控制接口 ViewController,再創(chuàng)建兩個(gè)內(nèi)部類實(shí)現(xiàn)接口,分別負(fù)責(zé)登錄和注冊(cè)的布局控制和數(shù)據(jù)加載。

然后提供一個(gè)方法根據(jù)參數(shù)獲取具體的控制器實(shí)現(xiàn) getViewController(final int type),這個(gè)方法可以是 public 的,外界即使拿到這個(gè) activity 實(shí)例,也只能獲取到布局控制器基類,具體的實(shí)現(xiàn)被隱藏了,這在后期修改某一個(gè)頁(yè)面時(shí),不用擔(dān)心會(huì)對(duì)其他地方造成影響。

有朋友可能會(huì)說了:“這 2 個(gè)內(nèi)部類也可以定義成普通類呀”。

確實(shí)普通類也同樣能滿足需求,但是我們希望這 2 個(gè)類只是在這個(gè)公共支付信息頁(yè)面才用到,在外界看來是不可見或不可用的狀態(tài),這個(gè)時(shí)候內(nèi)部類就能滿足我們的需求。

這樣的場(chǎng)景在 簡(jiǎn)單工廠模式、迭代器設(shè)計(jì)模式、命令設(shè)計(jì)模式都有用到,有興趣的朋友可以去了解下。

2.靜態(tài)內(nèi)部類的使用場(chǎng)景

靜態(tài)內(nèi)部類只能訪問外部類的靜態(tài)變量和方法,但相對(duì)普通內(nèi)部類的功能更為完整,因?yàn)樗?strong>可以定義靜態(tài)變量/方法

當(dāng)類 A 需要使用類 B,而 B 不需要直接訪問外部類 A 的成員變量和方法時(shí),可以將 B 作為 A 的靜態(tài)內(nèi)部類。

比較常見的一種使用場(chǎng)景是:在基類 A 里持有靜態(tài)內(nèi)部類 B 的引用,然后在 A 的子類里創(chuàng)建特定業(yè)務(wù)的 B 的子類,這樣就結(jié)合多態(tài)和靜態(tài)內(nèi)部類的優(yōu)勢(shì),既能拓展,又能限制范圍。

我們經(jīng)常使用的 LayoutParams 就是靜態(tài)內(nèi)部類,由于不同的布局中參數(shù)不一樣,Android SDK 提供了很多種 LayoutParams:

ViewGroup.LayoutParams

WindowManager.LayoutParams 繼承上一層

RelativeLayout.LayoutParams

...

public interface WindowManager extends ViewManager {

    //...
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    //...
    }
}

ViewsetLayoutParams 中的參數(shù)類型是最上層的 ViewGroup.LayoutParams params,這樣子類就可以傳入符合自己特性的 LayoutParams 實(shí)現(xiàn):

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();
}

靜態(tài)內(nèi)部類的另一種使用場(chǎng)景是:實(shí)現(xiàn)單例模式。

記得有一年去點(diǎn)評(píng)面試,面試官讓我寫個(gè)靜態(tài)內(nèi)部類實(shí)現(xiàn)的單例模式,我寫的過程中不確定靜態(tài)內(nèi)部類是否可以有靜態(tài)成員,基礎(chǔ)有多差可想而知。

先來看一下如何實(shí)現(xiàn):

public class LocationManager{
    private static class ClassHolder {
        private static final LocationManager instance = new LocationManager();
    }
    public static LocationManager getInstance() {
        return ClassHolder.instance;
    }
}

我們知道靜態(tài)內(nèi)部類功能和普通類一致,所以有 static 成員不足為奇?,F(xiàn)在的問題是,為什么這種單例模式比較好?

原因有兩點(diǎn):

懶加載:類加載時(shí)不會(huì)創(chuàng)建實(shí)例,只有當(dāng) getInstance() 方法被調(diào)用時(shí)才去加載靜態(tài)內(nèi)部類以及其中持有的 LocationManager 實(shí)例

線程安全:JVM 加載類時(shí),可以確保 instance 變量只能初始化一次

3.匿名內(nèi)部類的使用場(chǎng)景

Android 開發(fā)中設(shè)置一個(gè)按鈕的點(diǎn)擊事件很簡(jiǎn)單,直接 new 一個(gè) View.OnClickListener 然后實(shí)現(xiàn)方法即可:

        mButton2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                    //...
            }
        });

結(jié)合前面談到的,編譯器會(huì)為每個(gè)匿名內(nèi)部類創(chuàng)建一個(gè) Class 文件。個(gè)人覺得在安卓開發(fā)中,有多個(gè)按鈕需要設(shè)置點(diǎn)擊事件時(shí),讓當(dāng)前類實(shí)現(xiàn) OnClickListener 接口然后在 onClick() 中根據(jù) id 判斷事件,比創(chuàng)建一大堆匿名內(nèi)部類要好些,你覺得呢?

之所以這樣寫,是因?yàn)槲覀儾恍枰钟羞@個(gè) new View.OnClickListener 的引用,只要?jiǎng)?chuàng)建了對(duì)象即可。

所以使用場(chǎng)景可以是:一個(gè)方法的返回值是接口,然后根據(jù)不同參數(shù)返回不同的實(shí)現(xiàn),我們不需要保存引用,直接 new 一個(gè)接口實(shí)現(xiàn)即可。

來看一個(gè)有趣的例子:

public class GirlFriendMaker {
    public interface GirlFriend {
        void sayHi();
    }

    public static GirlFriend giveMeAGirlFriend(final String name) {
        return new GirlFriend() {    //匿名內(nèi)部類
            @Override
            public void sayHi() {
                Log.i("來自女朋友的問候", "Hello I"m " + name);
            }
        };
    }
}
4.局部?jī)?nèi)部類

局部?jī)?nèi)部類只用于當(dāng)前方法或者代碼塊中創(chuàng)建、使用,一次性產(chǎn)品,使用場(chǎng)景比較少。

內(nèi)存泄漏

經(jīng)過前面的介紹我們知道,四種內(nèi)部類中除了靜態(tài)內(nèi)部類,只要訪問外部類的成員/方法,就會(huì)持有外部類的引用。

當(dāng)內(nèi)部類持有外部類的引用,同時(shí)生命周期比外部類要長(zhǎng)(比如執(zhí)行耗時(shí)任務(wù)、被其他長(zhǎng)生命周期對(duì)象持有),就會(huì)導(dǎo)致外部類該被回收時(shí)無法被回收,也就是內(nèi)存泄漏問題。

一個(gè) Android 開發(fā)中常見的內(nèi)部類導(dǎo)致內(nèi)存泄露的例子:

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;
                }
                //...
    }
}

這個(gè) Handler 持有外部類的引用,它發(fā)送的 runnable 對(duì)象,會(huì)被進(jìn)一步包裝為 message 對(duì)象,放入消息隊(duì)列,在被執(zhí)行、回收之前會(huì)一致持有引用,導(dǎo)致無法釋放。

解決辦法就是使用弱引用或者干脆將 Handler 設(shè)計(jì)為靜態(tài)內(nèi)部類。

總結(jié)

總的來說,內(nèi)部類一般用于兩個(gè)場(chǎng)景:

需要用一個(gè)類來解決一個(gè)復(fù)雜的問題,但是又不希望這個(gè)類是公共的

需要實(shí)現(xiàn)一個(gè)接口,但不需要持有它的引用

本篇文章介紹了 Java 開發(fā)中四種內(nèi)部類的概念、反編譯后的格式以及使用場(chǎng)景。相信看完這篇文章,你對(duì)開頭的兩道題已經(jīng)有了答案。

基礎(chǔ)就是這樣,不論你走的多遠(yuǎn),都需要及時(shí)回顧、彌補(bǔ),等工作中需要用到才補(bǔ),會(huì)錯(cuò)失很多機(jī)會(huì)。

這個(gè)系列的目的是幫助大家系統(tǒng)、完整的打好基礎(chǔ)、逐漸深入學(xué)習(xí),如果你對(duì)這些已經(jīng)很熟了,請(qǐng)不要吝嗇你的評(píng)價(jià),多多指出問題,我們一起做的更好!

文章同步發(fā)送于微信公眾號(hào):安卓進(jìn)化論,歡迎關(guān)注,第一時(shí)間獲取新文章。

參考資料

《Java編程思想》

http://blog.csdn.net/qq734227...

http://www.cnblogs.com/latter...

https://www.javaworld.com/art...

https://www.cnblogs.com/zhang...
http://www.jianshu.com/p/6a36...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/70698.html

相關(guān)文章

  • Python基礎(chǔ)題目大全,測(cè)試你的水平,鞏固知識(shí)(含答案)

    摘要:里,有兩種方法獲得一定范圍內(nèi)的數(shù)字返回一個(gè)列表,還有返回一個(gè)迭代器。在引用計(jì)數(shù)的基礎(chǔ)上,還可以通過標(biāo)記清除解決容器對(duì)象可能產(chǎn)生的循環(huán)引用的問題。列舉常見的內(nèi)置函數(shù)的作用,過濾函數(shù),循環(huán)函數(shù)累積函數(shù)一行代碼實(shí)現(xiàn)乘法表。 showImg(https://segmentfault.com/img/remote/1460000019294205); 1、為什么學(xué)習(xí)Python? 人生苦短?人間...

    huhud 評(píng)論0 收藏0
  • 小馬哥Java項(xiàng)目實(shí)戰(zhàn)訓(xùn)練營(yíng) 極客大學(xué)

    摘要:百度網(wǎng)盤提取碼一面試題熟練掌握是很關(guān)鍵的,大公司不僅僅要求你會(huì)使用幾個(gè),更多的是要你熟悉源碼實(shí)現(xiàn)原理,甚至要你知道有哪些不足,怎么改進(jìn),還有一些有關(guān)的一些算法,設(shè)計(jì)模式等等。 ??百度網(wǎng)盤??提取碼:u6C4?一、java面試題熟練掌握java是很關(guān)鍵的,大公司不僅僅要求你會(huì)使用幾個(gè)api,更多的是要你熟悉源碼實(shí)現(xiàn)原理,甚...

    不知名網(wǎng)友 評(píng)論0 收藏0
  • 最最最常見的Java面試題總結(jié)——第二周

    摘要:與都繼承自類,在中也是使用字符數(shù)組保存字符串,,這兩種對(duì)象都是可變的。采用字節(jié)碼的好處語言通過字節(jié)碼的方式,在一定程度上解決了傳統(tǒng)解釋型語言執(zhí)行效率低的問題,同時(shí)又保留了解釋型語言可移植的特點(diǎn)。 String和StringBuffer、StringBuilder的區(qū)別是什么?String為什么是不可變的? String和StringBuffer、StringBuilder的區(qū)別 可變性...

    yearsj 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

weij

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<