Java提高篇 —— Java內部類詳解

一、簡介

?

? ? ? ?內部類是一個非常有用的特性但又比較難理解使用的特性。

? ? ? ?內部類我們從外面看是非常容易理解的,無非就是在一個類的內部在定義一個類。

public class OuterClass {private String name ;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}class InnerClass{public InnerClass(){name = "chenssy";age = 23;}}
}

? ? ? ?在這里InnerClass就是內部類,對于初學者來說內部類實在是使用的不多,但是隨著編程能力的提高,我們會領悟到它的魅力所在,它可以使用能夠更加優雅的設計我們的程序結構。在使用內部類之間我們需要明白為什么要使用內部類,內部類能夠為我們帶來什么樣的好處。

?

二、為什么要使用內部類?

?

? ? ? ?為什么要使用內部類?在《Think in java》中有這樣一句話:使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對于內部類都沒有影響。

? ? ? ?在我們程序設計中有時候會存在一些使用接口很難解決的問題,這個時候我們可以利用內部類提供的、可以繼承多個具體的或者抽象的類的能力來解決這些程序設計問題。可以這樣說,接口只是解決了部分問題,而內部類使得多重繼承的解決方案變得更加完整。

public interface Father {}public interface Mother {}public class Son implements Father, Mother {}public class Daughter implements Father{class Mother_ implements Mother{}
}

? ? ? ?其實對于這個實例我們確實是看不出來使用內部類存在何種優點,但是如果Father、Mother不是接口,而是抽象類或者具體類呢?這個時候我們就只能使用內部類才能實現多重繼承了。

? ? ? ?其實使用內部類最大的優點就在于它能夠非常好的解決多重繼承的問題,但是如果我們不需要解決多重繼承問題,那么我們自然可以使用其他的編碼方式,但是使用內部類還能夠為我們帶來如下特性(摘自《Think in java》):

? ? ? ?1、內部類可以用多個實例,每個實例都有自己的狀態信息,并且與其他外圍對象的信息相互獨立。

? ? ? ?2、在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或者繼承同一個類。

? ? ? ?3、創建內部類對象的時刻并不依賴于外圍類對象的創建。

? ? ? ?4、內部類并沒有令人迷惑的“is-a”關系,他就是一個獨立的實體。

? ? ? ?5、內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。

?

三、內部類基礎

?

? ? ? ?在這個部分主要介紹內部類如何使用外部類的屬性和方法,以及使用.this與.new。

? ? ? ?當我們在創建一個內部類的時候,它無形中就與外圍類有了一種聯系,依賴于這種聯系,它可以無限制地訪問外圍類的元素。

public class OuterClass {private String name ;private int age;/**省略getter和setter方法**/public class InnerClass{public InnerClass(){name = "chenssy";age = 23;}public void display(){System.out.println("name:" + getName() +"   ;age:" + getAge());}}public static void main(String[] args) {OuterClass outerClass = new OuterClass();OuterClass.InnerClass innerClass = outerClass.new InnerClass();innerClass.display();}
}
--------------
Output:
name:chenssy   ;age:23

? ? ? ?在這個應用程序中,我們可以看到內部了InnerClass可以對外圍類OuterClass的屬性進行無縫的訪問,盡管它是private修飾的。這是因為當我們在創建某個外圍類的內部類對象時,此時內部類對象必定會捕獲一個指向那個外圍類對象的引用,只要我們在訪問外圍類的成員時,就會用這個引用來選擇外圍類的成員。

? ? ? ?其實在這個應用程序中我們還看到了如何來引用內部類:引用內部類我們需要指明這個對象的類型:OuterClasName.InnerClassName。同時如果我們需要創建某個內部類對象,必須要利用外部類的對象通過.new來創建內部類:?OuterClass.InnerClass innerClass = outerClass.new InnerClass();。

? ? ? ?同時如果我們需要生成對外部類對象的引用,可以使用OuterClassName.this,這樣就能夠產生一個正確引用外部類的引用了。當然這點實在編譯期就知曉了,沒有任何運行時的成本。

public class OuterClass {public void display(){System.out.println("OuterClass...");}public class InnerClass{public OuterClass getOuterClass(){return OuterClass.this;}}public static void main(String[] args) {OuterClass outerClass = new OuterClass();OuterClass.InnerClass innerClass = outerClass.new InnerClass();innerClass.getOuterClass().display();}
}
-------------
Output:
OuterClass...

? ? ? ?到這里了我們需要明確一點,內部類是個編譯時的概念,一旦編譯成功后,它就與外圍類屬于兩個完全不同的類(當然他們之間還是有聯系的)。對于一個名為OuterClass的外圍類和一個名為InnerClass的內部類,在編譯成功后,會出現這樣兩個class文件:OuterClass.class和OuterClass$InnerClass.class。

? ? ? ?在Java中內部類主要分為:成員內部類、局部內部類、匿名內部類、靜態內部類

?

四、成員內部類

?

? ? ? ?成員內部類也是最普通的內部類,它是外圍類的一個成員,所以他是可以無限制的訪問外圍類的所有 成員屬性和方法,盡管是private的,但是外圍類要訪問內部類的成員屬性和方法則需要通過內部類實例來訪問。

在成員內部類中要注意兩點:

? ? ? ?第一:成員內部類中不能存在任何static的變量和方法;

? ? ? ?第二:成員內部類是依附于外圍類的,所以只有先創建了外圍類才能夠創建內部類。

public class OuterClass {private String str;public void outerDisplay(){System.out.println("outerClass...");}public class InnerClass{public void innerDisplay(){//使用外圍內的屬性str = "chenssy...";System.out.println(str);//使用外圍內的方法outerDisplay();}}/*推薦使用getxxx()來獲取成員內部類,尤其是該內部類的構造函數無參數時 */public InnerClass getInnerClass(){return new InnerClass();}public static void main(String[] args) {OuterClass outer = new OuterClass();OuterClass.InnerClass inner = outer.getInnerClass();inner.innerDisplay();}
}
--------------------
chenssy...
outerClass...

PS:推薦使用getxxx()來獲取成員內部類,尤其是該內部類的構造函數無參數時 。

?

五、局部內部類

?

? ? ? ?有這樣一種內部類,它是嵌套在方法和作用于內的,對于這個類的使用主要是應用與解決比較復雜的問題,想創建一個類來輔助我們的解決方案,到那時又不希望這個類是公共可用的,所以就產生了局部內部類,局部內部類和成員內部類一樣被編譯,只是它的作用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效。

? ? ? ?對于局部內部類實在是想不出什么好例子,所以就引用《Think in java》中的經典例子了。

定義在方法里:

public class Parcel5 {public Destionation destionation(String str){class PDestionation implements Destionation{private String label;private PDestionation(String whereTo){label = whereTo;}public String readLabel(){return label;}}return new PDestionation(str);}public static void main(String[] args) {Parcel5 parcel5 = new Parcel5();Destionation d = parcel5.destionation("chenssy");}
}

定義在作用域內:

public class Parcel6 {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("chenssy");String string = ts.getSlip();}}public void track(){internalTracking(true);}public static void main(String[] args) {Parcel6 parcel6 = new Parcel6();parcel6.track();}
}

?

六、匿名內部類

?

在做Swing編程中,我們經常使用這種方式來綁定事件:

button2.addActionListener(  new ActionListener(){  public void actionPerformed(ActionEvent e) {  System.out.println("你按了按鈕二");  }  });

我們咋一看可能覺得非常奇怪,因為這個內部類是沒有名字的,在看如下這個例子:

public class OuterClass {public InnerClass getInnerClass(final int num,String str2){return new InnerClass(){int number = num + 3;public int getNumber(){return number;}};        /* 注意:分號不能省 */}public static void main(String[] args) {OuterClass out = new OuterClass();InnerClass inner = out.getInnerClass(2, "chenssy");System.out.println(inner.getNumber());}
}interface InnerClass {int getNumber();
}----------------
Output:
5

這里我們就需要看清幾個地方:

? ? ? ?1、?匿名內部類是沒有訪問修飾符的。

? ? ? ?2、?new 匿名內部類,這個類首先是要存在的。如果我們將那個InnerClass接口注釋掉,就會出現編譯出錯。

? ? ? ?3、?注意getInnerClass()方法的形參,第一個形參是用final修飾的,而第二個卻沒有。同時我們也發現第二個形參在匿名內部類中沒有使用過,所以當所在方法的形參需要被匿名內部類使用,那么這個形參就必須為final。

? ? ? ?4、?匿名內部類是沒有構造方法的。因為它連名字都沒有何來構造方法。

?

? ? ? ?上面第一個形參定義成final的,而第二個卻沒有,那么形參為什么要定義為final呢?在網上找到本人比較如同的解釋:

? ? ? ?這是一個編譯器設計的問題,如果你了解java的編譯原理的話很容易理解。 ?

? ? ? ?首先,內部類被編譯的時候會生成一個單獨的內部類的.class文件,這個文件并不與外部類在同一class文件中。 ?

? ? ? ?當外部類傳的參數被內部類調用時,從java程序的角度來看是直接的調用例如:?

public void dosome(final String a,final int b){  class Dosome{public void dosome(){System.out.println(a+b)}};  Dosome some=new Dosome();  some.dosome();  
}  

? ? ? ?從代碼來看好像是那個內部類直接調用的a參數和b參數,但是實際上不是,在java編譯器編譯以后實際的操作代碼是:

class Outer$Dosome{  public Dosome(final String a,final int b){  this.Dosome$a=a;  this.Dosome$b=b;  }  public void dosome(){  System.out.println(this.Dosome$a+this.Dosome$b);  }  
}

? ? ? ?從以上代碼看來,內部類并不是直接調用方法傳進來的參數,而是內部類將傳進來的參數通過自己的構造器備份到了自己的內部,自己內部的方法調用的實際是自己的屬性而不是外部類方法的參數。

? ? ? ?這樣理解就很容易得出為什么要用final了,因為兩者從外表看起來是同一個東西,實際上卻不是這樣,如果內部類改掉了這些參數的值也不可能影響到原參數,然而這樣卻失去了參數的一致性,因為從編程人員的角度來看他們是同一個東西,如果編程人員在程序設計的時候在內部類中改掉參數的值,但是外部調用的時候又發現值其實沒有被改掉,這就讓人非常的難以理解和接受,為了避免這種尷尬的問題存在,所以編譯器設計人員把內部類能夠使用的參數設定為必須是final來規避這種莫名其妙錯誤的存在。(簡單理解就是,拷貝引用,為了避免引用值發生改變,例如被外部類的方法修改等,而導致內部類得到的值不一致,于是用final來讓該引用不可改變)

? ? ? ?因為匿名內部類,沒名字,是用默認的構造函數的,無參數的,那如果需要參數呢?則需要該類有帶參數的構造函數:

public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); } public Inner getInner(final String name, String city) { return new Inner(name, city) { private String nameStr = name; public String getName() { return nameStr; } }; } 
} abstract class Inner { Inner(String name, String city) { System.out.println(city); } abstract String getName(); 
} 

? ? ? ?注意這里的形參city,由于它沒有被匿名內部類直接使用,而是被抽象類Inner的構造函數所使用,所以不必定義為final。

? ? ? ?而匿名內部類通過實例初始化,可以達到類似構造器的效果:

public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); System.out.println(inner.getProvince()); } public Inner getInner(final String name, final String city) { return new Inner() { private String nameStr = name; private String province; // 實例初始化 { if (city.equals("gz")) { province = "gd"; }else { province = ""; } } public String getName() { return nameStr; } public String getProvince() { return province; } }; } 
} interface Inner { String getName(); String getProvince(); 
} 

?

七、靜態內部類

?

? ? ? ?Static可以修飾成員變量、方法、代碼塊,其他它還可以修飾內部類,使用static修飾的內部類我們稱之為靜態內部類,不過我們更喜歡稱之為嵌套內部類。靜態內部類與非靜態內部類之間存在一個最大的區別,我們知道非靜態內部類在編譯完成之后會隱含地保存著一個引用,該引用是指向創建它的外圍內,但是靜態內部類卻沒有。沒有這個引用就意味著:

? ? ? ?1、?它的創建是不需要依賴于外圍類的。

? ? ? ?2、?它不能使用任何外圍類的非static成員變量和方法。

public class OuterClass {private String sex;public static String name = "chenssy";/***靜態內部類*/static class InnerClass1{/* 在靜態內部類中可以存在靜態成員 */public static String _name1 = "chenssy_static";public void display(){/* * 靜態內部類只能訪問外圍類的靜態成員變量和方法* 不能訪問外圍類的非靜態成員變量和方法*/System.out.println("OutClass name :" + name);}}/*** 非靜態內部類*/class InnerClass2{/* 非靜態內部類中不能存在靜態成員 */public String _name2 = "chenssy_inner";/* 非靜態內部類中可以調用外圍類的任何成員,不管是靜態的還是非靜態的 */public void display(){System.out.println("OuterClass name:" + name);}}/*** @desc 外圍類方法* @author chenssy* @data 2013-10-25* @return void*/public void display(){/* 外圍類訪問靜態內部類:內部類. */System.out.println(InnerClass1._name1);/* 靜態內部類 可以直接創建實例不需要依賴于外圍類 */new InnerClass1().display();/* 非靜態內部的創建需要依賴于外圍類 */OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();/* 方位非靜態內部類的成員需要使用非靜態內部類的實例 */System.out.println(inner2._name2);inner2.display();}public static void main(String[] args) {OuterClass outer = new OuterClass();outer.display();}
}
----------------
Output:
chenssy_static
OutClass name :chenssy
chenssy_inner
OuterClass name:chenssy

? ? ? ?上面這個例子充分展現了靜態內部類和非靜態內部類的區別。

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446950.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446950.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446950.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Ubuntu修改界面的大小

命令 xrandr 就會顯示ubuntu支持的屏幕比例使用命令 xrandr --size 1680x1050 切換屏幕大小

Java提高篇 —— String緩沖池

一、String緩沖池 首先我們要明確,String并不是基本數據類型,而是一個對象,并且是不可變的對象。查看源碼就會發現String類為final型的(當然也不可被繼承),而且通過查看JDK文檔會發現幾乎每一個修改String對…

C++最新使用開源openssl實現輸入是文件,輸出是文件的AES加解密的代碼

AES.h頭文件 #include <cstring> #include <fstream> #include <iostream> #include <openssl/aes.h>//AES文件加密函數 int aes_encrypt_file(const std::string &original_backup_file_path,const std::string &encrypted_file_path,const …

Java基礎 —— JVM內存模型與垃圾回收

目錄一、概述二、運行時數據區方法區運行時常量池堆棧本地方法棧程序計數器三、對象訪問四、垃圾回收如何定義垃圾1、引用計數法2、可達性分析垃圾回收方法1、Mark-Sweep標記-清除算法2、Copying復制算法3、Mark-Compact標記-整理算法4、Generational Collection 分代收集垃圾收…

Report Design

ERP_ENT_STD-CSDN博客

規范化流程化提交自己代碼到遠程gitlab服務器

流程 進入到build目錄使用make命令進行編譯 make -j 6&#xff0c;前提是cmake命令已經執行../format.py rungit add .. 添加文件git checkout -b xxx 創建xxx分支&#xff0c;其中xxx是分支名字git branch 查看分支git commit -m "[chy/backup] modify parameters for…

Java提高篇 ——Java注解

目錄一、注解注解的定義注解的應用元注解RetentionDocumentedTargetInheritedRepeatable注解的屬性Java 預置的注解DeprecatedOverrideSuppressWarningsSafeVarargsFunctionalInterface二、注解的提取三、注解與反射四、注解的使用場景五、親手自定義注解完成某個目的六、注解應…

linux使用openssl查看文件的md5數值

代碼 #include <stdio.h> #include <openssl/md5.h>std::string get_file_md5(const char *path){unsigned char digest [MD5_DIGEST_LENGTH];std::ifstream file(path, std::ios::in | std::ios::binary); //打開文件MD5_CTX md5_ctx;MD5_Init(&md5_ctx);cha…

Android 性能優化四個方面總結

目錄一、四個方面二、卡頓優化1、Android系統顯示原理2、卡頓根本原因3、性能分析工具&#xff08;1&#xff09;Profile GPU Rendering&#xff08;2&#xff09;TraceView&#xff08;3&#xff09;Systrace UI 性能分析4、優化建議&#xff08;1&#xff09;布局優化&#x…

pycharm/clion/idea等產品多含代碼左移右移操作

左移 選中多行代碼后&#xff0c;按下Tab鍵&#xff0c;一次縮進四個字符 右移 鼠標選中多行代碼后&#xff0c;同時按住shiftTab鍵&#xff0c;一次左移四個字符

Android 開源框架選擇

目錄一、前言二、APP的整體架構三、技術選型的考量點四、日志記錄能力五、JSON解析能力1、gson2、jackson3、Fastjson4、LoganSquare六、數據庫操作能力1、ActiveAndroid2、ormlite3、greenDAO4、Realm七、網絡通信能力1、android-async-http2、OkHttp3、Volley4、Retrofit八、…

使用opensll的md5對于string進行加密

代碼 #include <openssl/md5.h>#include <sstream> #include <iomanip> #include <iostream>void get_string_md5(const std::string& await_md5_string) {unsigned char md5[MD5_DIGEST_LENGTH];MD5(reinterpret_cast<unsigned const char*&g…

Android Studio 自定義Gradle Plugin

一、簡介 之前公司的一個項目需要用到Gradle插件來修改編譯后的class文件&#xff0c;今天有時間就拿出來整理一下&#xff0c;學習一下Gradle插件的編寫還是一件十分有意義的事。 二、Gradle插件類型 一種是直接在項目中的gradle文件里編寫&#xff0c;這種方式的缺點是無法復…

C++創建臨時文件

命令 std::string original_backup_file std::tmpnam(nullptr);

Android Studio Gradle兩種更新方式

第一種、Android Studio自動更新 第一步&#xff1a;修改gradle版本 修改項目根目錄/gradle/wrapper/gradle-wrapper.properties最后一行的地址&#xff1a; distributionUrlhttps://services.gradle.org/distributions/gradle-3.3-all.zip新gradle地址從官方下載的地方有。…

C++使用openssl實現aes加解密,其中加密是string到文件,解密是文件到string,切合項目背景

代碼 使用md5對于用戶輸入的密碼進行保護,也使得密碼的長度固定crypto_util.h#pragma once#include <string>namespace hsm{ namespace mgmt{void get_md5_digest(const std::string &data,uint8_t result[16]);void aes_encrypt_to_file(const std::string &fi…

Android Canvas的drawText()和文字居中方案

自定義View是繪制文本有三類方法&#xff1a; // 第一類 public void drawText (String text, float x, float y, Paint paint) public void drawText (String text, int start, int end, float x, float y, Paint paint) public void drawText (CharSequence text, int start…

IntelliJ IDEA配置Tomcat

查找該問題的童鞋我相信IntelliJ IDEA&#xff0c;Tomcat的下載&#xff0c;JDK等其他的配置都應該完成了&#xff0c;那我直接進入正題了。 1、新建一個項目 2、由于這里我們僅僅為了展示如何成功部署Tomcat&#xff0c;以及配置完成后成功運行一個jsp文件&#xff0c;我僅勾…

C++對于文件的相關操作 創建、讀寫、刪除代碼

創建 /*** brief 創建文件:在密碼設備內部創建用于存儲用戶數據的文件* param pucFileName 緩沖區指針&#xff0c;用于存放輸入的文件名&#xff0c;最大長度128字節* param uiNameLen 文件名長度* param uiFileSize 文件所占存儲空間的長度*/void CreateFile(sdf_uint8_t…

Android開發之Path詳解

目錄一、xxxTo方法1、lineTo(float x, float y)2、moveTo(float x, float y)3、arcTo3.1、arcTo(RectF oval, float startAngle, float sweepAngle)3.2、arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)3.3、arcTo(float left, float top, float r…