這篇文章不是面面俱到的基礎知識集合,只是我個人的學習筆記,說人話就是:這篇文章是JavaSE基礎知識全解的真子集,且以下所有內容僅代表個人觀點,不一定正確,歡迎辯證~
文章目錄
- 先從簡化的Java記憶體模型開始
- 堆疊區
- 基本資料型別的存盤
- 參考資料型別的存盤
- 堆區
- 方法區
- 面向物件要點(沒物件的好好學、有物件的好好看)
- 類基礎
- 方法中的不定長引數
- 這就是this?
- 回傳一個this試試?
- this的特殊用法
- 看完之后就想靜靜的靜態關鍵字static
- static與JavaBean
- 單例模式
- 什么是單例模式
- 單例模式的實作步驟
- 舉例
- 記憶體分析
- 構造塊和靜態代碼塊
- 終于到final了
- 訪問控制
- 封裝
- 繼承
- 繼承的幾大特點
- 老漢的遺產并不是全給娃娃的噢
- 子類如何使用父類的構造方法?
- 方法重寫
- 繼承中的構造塊
- 多型
- 多型核心——重寫父類方法并通過父類參考呼叫
- 參考型別的型別轉換
- 動態系結
- 靜態與多型
- 多型的使用場景
- 抽象類
- 基本概念
- abstract會和一些關鍵字鬧矛盾哦!
- 繼續用男孩女孩舉栗子
- 介面類
- 介面類與抽象類的對比
- Java8之后產生的新特性
- 讓男孩來展示一下介面類的方方面面
- 特殊類
- 普通內部類
- 普通內部類的使用方式
- 靜態內部類
- 區域(方法)內部類
- 使用方法
- 最常用的內部類——匿名內部類
- 列舉類
- 基本用法
- 常用方法
- 列舉類實作介面的方式
- 注解
- 注解類的宣告方式
- 元注解
- @Retention
- @Documented
- @Target
- @Inherited
- Java8新增元注解:@Repeatable
- Java的一些常見預制注解
- 包裝類
- 自動拆箱機制
- 常量池
先從簡化的Java記憶體模型開始
- 這是一個簡化版的Java記憶體模型,該模型中把記憶體分為了堆疊區、堆區、方法區,

堆疊區
- 主要用于存放方法執行時的各種基本資料型別和參考資料型別,
- 例如圖中,main方法中宣告了參考型別p,那么堆疊區中就會為p申請一塊記憶體,
- 對于基本資料型別和參考資料型別,他們在堆疊區中存盤的內容是不一樣的:
基本資料型別的存盤
- 基本資料型別在堆疊區中存盤的是值,并且對于相同的值,基本資料型別共用一塊記憶體,什么意思呢?看下圖:

- 像"1"這樣的基本資料型別的值,我們稱為直接量,int b = 1;時,會先查找堆疊中是否存在直接量"1",如果找到了,那么直接使用這個直接量,
- 所以真正存盤在堆疊區中的其實是直接量,相同的直接量,不會重復占用多個記憶體空間,這就是上面代碼a和b使用同一地址的原因,而不同的值會被分配到不同的地址去,所以如果a=1、b=2,那么他們的地址又是不同的,
- 不同型別的相同值,也是會被分配到不同地址,很好理解嘛,因為他們需要的記憶體大小都不一樣嘛,比如1.0和1.0F,他們的在堆疊中的地址是不一樣的,
- 盡管這個特性看起來很像是“參考”,但是我們需要避免這樣去稱呼他們,因為“參考”指的是利用堆疊區中的記憶體地址,指向堆區中的資料,
參考資料型別的存盤
- 學過C的就很容理解參考型別的存盤方式,沒錯,就是很像指標~
- 參考資料型別本身存盤的是指向堆區的地址(實際上存的是地址的hash),而參考的物件的真正的值是存放在堆區中的,
堆區
- 堆區用于存放物件的實體(通俗的說法的是存放new出來的東西),例如陣列、實體化后的成員變數等,
- 參考資料型別的值就是堆區中某個實體的地址hash,
方法區
- 方法區存放編譯后的代碼、各類靜態資源等,
面向物件要點(沒物件的好好學、有物件的好好看)
類基礎
方法中的不定長引數
- 這個東西似乎很少用到,所以可能很多人不知道,如果入參宣告為不定長引數,那么呼叫該方法的時候,可以傳入0-N個該型別的實參,舉例如下:
public class Test{
public static void main(String[] args){
Test test = new Test();
test.testPara("1");
test.testPara("1", 1);
test.testPara("1", 2, 3);
}
public void testPara(String str, int... args){
System.out.println(args.length);
}
}
- 上面代碼中,main方法對testPara的三次呼叫都是可行的,也就是的0-N個該型別引數都可以,
- 此外,可以很顯然地看見,不定長引數其實就是一個陣列,但是形參寫
int... args和寫int[] args還是會有很大不一樣,具體的區別可以自己琢磨琢磨, - 最后,最重要的一點是,如果要使用不定長引數,那么引數串列中只能存在一個不定長引數,并且該不定長引數必須放在形參串列的最后面,
這就是this?
回傳一個this試試?
- 相信小學畢業的小伙伴們都知道this的意思是“當前物件”,所以它本質上是一個物件(實體)的參考,也正因為如此,this也是可以作為回傳值的噢,
class A{
A getA(){
return this;
}
public static void main(String[] args){
A a1 = new A();
A a2 = a1.getA();
//A a2 = a1;
}
}
- 如上代碼,兩行宣告a2的代碼,執行結果是一模一樣的,希望通過這個例子,能讓小學2年級的小伙伴們更深刻地this代表的“當前物件”是什么意思,
this的特殊用法
- this可以用于構造方法中呼叫同一個類中的另一個建構式,這個時候可能就沒法用this代表“當前物件”來理解了,所以我將其稱為特殊用法,舉例如下:
class A {
int num;
int sum;
A(int num) {
this.num = num;
System.out.println("這是無參構造");
}
A(int num, int sum) {
this(num);
this.sum = sum;
System.out.println("這是有參構造");
}
A getA() {
return this;
}
public static void main(String[] args) {
A a1 = new A(1, 2);
A a2 = a1.getA();
System.out.println("" + a2.num + a2.sum);
}
}
- 上面代碼的執行結果是12,而
this(num);就是剛提到的用this呼叫構造方法,值得注意的是,該陳述句只能放在構造方法的第一行,否則直接編譯報錯, - 這個用法應該幾乎不會用到,但是類似的關鍵字super可是會經常這樣用的哦~這個下文會提到
看完之后就想靜靜的靜態關鍵字static
- 說到static,大家都會說:哎呀~就是修飾成員變數和成員方法嘛,讓它變成類變數、類方法嘛,但是喃,我發現static在一些地方的用法,之前被我忽略咯,
static與JavaBean
- 我們知道JavaBean是Java封裝的產物,其主要由私有成員變數、getter和setter方法組成,不過我們平時用到的幾乎都是物件層級的JavaBean,也就是new出來物件之后,再來使用,
- 但其實JavaBean也可以在類層級上,使用static實作,以滿足需要全域唯一JavaBean的需求,舉例如下:
public class JavaBean {
private int normal;
private static int sta;
public void print() {
System.out.println("sta:" + sta + ". normal:" + normal);
}
public static void staticPrint(){
System.out.println("sta:" + sta);
//System.out.println("normal:" + normal);
}
public int getNormal() {
return normal;
}
public void setNormal(int normal) {
this.normal = normal;
}
public static int getSta() {
return sta;
}
public static void setSta(int sta) {
JavaBean.sta = sta;
}
public static void main(String[] args){
JavaBean.setSta(21);
JavaBean.staticPrint();
JavaBean javaBean = new JavaBean();
javaBean.print();
}
}
- 執行結果為:
sta:21
sta:21. normal:0
- 通過這種方式,能夠實作對全域唯一的變數(靜態變數)的封裝,并且有兩個點需要注意一下:
- 要封裝靜態成員,那么所有涉及到該成員的方法都應該是靜態的(好廢話哦),例如setter和getter,
- 別忘了靜態方法里面可不能使用實體成員哦(還是好廢話哦),例如被我注釋掉的代碼,
- 有人就問了,這個東西有啥用呢?別著急,接下來就是大名鼎鼎的設計模式之一:單例模式,
單例模式
什么是單例模式
- 單例模式很好理解,其實就是通過一定的封裝,讓一個類在使用的時候,始終只會有一個實體,
- 單例模式可進一步分為餓漢式和懶漢式,
- 餓漢式在類加載時就進行初始化,
- 懶漢式在get方法被初次呼叫的時候再進行初始化,
- 實際開發中推薦使用餓漢式,原因和多執行緒的問題有關,
單例模式的實作步驟
- 私有化構造方法;
- 宣告本型別別的參考指向本型別別的物件,并使用private static;
- 提供公有的get方法回傳參考變數,使用public static,
舉例
//懶漢式
public class House {
private String owner;
private static House house = null;
private House() {
System.out.println("被創建了");
}
public static House getInstance() {
if (null == house) {
house = new House();
}
return house;
}
public String getOwner() {
return owner;
}
public static House getHouse() {
return house;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
//餓漢式
public class House {
private String owner;
private static final House house = new House();
private House() {
System.out.println("被創建了");
}
public static House getInstance() {
return house;
}
public String getOwner() {
return owner;
}
public static House getHouse() {
return house;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
//測驗類
public class SingletonTest {
public static void main(String[] args) {
House s1 = House.getInstance();
House s2 = House.getInstance();
s1.setOwner("angel");
s2.setOwner("小鈺");
System.out.println(s1.getOwner());
System.out.println(s2.getOwner());
System.out.println(s1 == s2); // true
}
}
- 執行結果為
被創建了
小鈺
小鈺
true
- 可以發現s1和s2參考的物件是相同的,這就是單例模式的特點,我們最常用到的Windows的任務管理器,就是一個典型的單例物件,
記憶體分析

- 首先java編譯后,方法位元組碼會存放在方法區,同時存放在方法區的還有一個靜態參考變數house,
- 同時執行main方法的時候,方法中的區域變數會存放在堆疊區中,
- 而new出來的House會存放在堆區中,所以此時參考變數house會指向堆區中的House實體,
- 而參考變數s1和s2,獲取到的其實都是house的值,也就是說他們也都是指向的同一個House實體,
構造塊和靜態代碼塊
- 在類中直接使用{}括起來的一段代碼,叫做構造塊,如果在前面用static修飾,則叫做靜態代碼塊,兩者有什么用呢?直接看例子:
public class Person {
private static int legs;
private String name;
//代碼塊,在每次生成物件實體之前執行,先于構造方法執行
{
System.out.println("生成一個人");
}
//靜態代碼塊,在類加載的時候執行,先于構造塊執行
static {
System.out.println("加載人類");
Person.legs = 2;
}
public Person(String name) {
System.out.println("實體化了一個人");
setName(name);
}
public static int getLegs() {
return legs;
}
public static void setLegs(int legs) {
Person.legs = legs;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show() {
System.out.println("我的名字叫" + this.name + ",我有" + Person.legs + "條腿");
}
}
//測驗類
public class Test {
public static void main(String[] args){
Person p1 = new Person("angel");
Person p2 = new Person("小鈺");
p1.show();
p2.show();
}
}
- 執行結果是
加載人類
生成一個人
實體化了一個人
生成一個人
實體化了一個人
我的名字叫angel,我有2條腿
我的名字叫小鈺,我有2條腿
- 不難發現,構造塊會在每次執行構造方法之前執行,而靜態代碼塊只會在第一次加載類的時候執行,
- 因此,一般構造塊用于在構造方法執行前,對某些變數進行統一賦值;靜態代碼塊一般用于對靜態成員,實作類似于“構造方法”一樣的效果,
- 如果一個方法中寫了多個構造塊,則執行時,按照從上到下的順序依次執行,靜態代碼塊同理,
終于到final了
- final關鍵字平時接觸得挺多的,這里大概提一下就好了:
- 修飾類:表示該類無法被繼承,
- 修飾方法:表示該類的子類中,不能重寫該方法,
- 修飾變數:表示變數只能被初始化,不能被賦值,
- final經常和static一起用在成員變數上,從而實作“常量”的功能,
訪問控制
- 訪問控制大家也比較了解,不多說,推薦一張表格,看完之后身心舒暢,
| 修飾符 | 本類 | 同一個包中的類 | 子類 | 其他類 |
|---|---|---|---|---|
| public | 可以訪問 | 可以訪問 | 可以訪問 | 可以訪問 |
| protected | 可以訪問 | 可以訪問 | 可以訪問 | 不能訪問 |
| 默認 | 可以訪問 | 可以訪問 | 不能訪問 | 不能訪問 |
| private | 可以訪問 | 不能訪問 | 不能訪問 | 不能訪問 |
封裝
- java的一個特性,這個特性很簡單,就不展開講了,一句話就是私有化成員,提供getter和setter,
繼承
- java的第二個特性
繼承的幾大特點
老漢的遺產并不是全給娃娃的噢
- 子類不能繼承父類的構造方法,即子類的實體不能呼叫父類的構造方法,這句話其實有點廢話,因為子類的類名和父類就不一樣呀,當然,父類的構造方法在子類的構造方法中通過super訪問,
- 私有成員變數和方法不能被直接繼承,但是可以間接訪問,舉個栗子來說明一下:
public class Person {
private String name;
public int height;
private String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show() {
System.out.println("我的名字叫:" + this.getName() + ",身高為:" + this.height);
}
}
public class Girl extends Person {
}
public class Test {
public static void main(String[] args){
Girl girl = new Girl();
//girl.name;
//girl.getName();
girl.height = 10;
girl.setName("angel");
girl.show();
}
}
- 運行測驗類的main方法,結果是
我的名字叫:angel,身高為:10
- 也就是說,子類雖然不能直接繼承父類的私有成員和私有方法,但是這些成員和方法在堆區中是存在的,可以通過繼承父類的其他方法來間接訪問,
子類如何使用父類的構造方法?
- 子類的構造方法被呼叫時,默認都會自動呼叫父類的無參構造方法,來初始化從父類中繼承的成員變數,相當于在子類構造方法的第一行寫上
super();的效果, - 同時,也可以顯示地呼叫父類的構造方法,只需要在子類構造方法中的第一行使用
super();即可,實參串列可根據父類構造方法自行填寫,舉個栗子:
public class Person {
private String name;
public Person(String name) {
setName(name);
System.out.println("呼叫了Person(String name)");
}
public Person() {
System.out.println("呼叫了Person()");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show() {
System.out.println("我的名字叫:" + this.getName());
}
}
public class Girl extends Person {
public Girl(){
}
public Girl(String name){
super(name);
}
}
public class Test {
public static void main(String[] args){
Girl yu = new Girl("小鈺");
Girl angel = new Girl();
angel.setName("angel");
yu.show();
angel.show();
}
}
- 執行結果是
呼叫了Person(String name)
呼叫了Person()
我的名字叫:小鈺
我的名字叫:angel
- 闊以看到,即使呼叫無參構造
Girl();,也依然會呼叫Person();, - 并且可以通過
super(name);來構造父類中的私有成員變數,
方法重寫
- 方法重寫大家都經常用,這里提一下有幾個原則:
- 方法名、引數串列必須相同,
- 當且僅當重寫方法的回傳型別是父類方法的回傳型別的子類時,回傳值型別可以不同,
- 訪問權限必須大于等于父類方法,
- 拋出的例外必須等于或小于父類的例外,比如父類拋NullPointException,則子類不能拋Exception,反之可以,
繼承中的構造塊
- 當構造塊和靜態代碼塊遇上繼承之后,是什么結果呢?
- 不舉栗子了,直接上結論,執行的先后順序是:
- 父類的靜態代碼塊;
- 子類的靜態代碼塊;
- 父類的構造塊;
- 父類的構造方法;
- 子類的構造塊;
- 子類的構造方法,
多型
- 多型是Java的第三大特性,相信各位已入門的小伙伴是能夠知道多型的作用滴,簡要來說就是讓一個父類的參考指向不同的子類,從而在不同的情況下產生不同的效果,
- 當父型別別的參考指向子型別別的物件時(多型),方法呼叫會有以下特點:
- 父型別別的參考可以直接呼叫父類獨有的方法,
- 父型別別的參考不可以直接呼叫子類獨有的方法,需要強制型別轉換,
- 動態系結:對于父子類都有的非靜態方法來說,編譯階段編譯父類的方法,運行階段呼叫子類重寫的版本,
- 對于父子類都有的靜態方法,編譯和運行階段都呼叫父類版本,也就是說靜態方法不存在多型的特性,至于呼叫哪個方法,之和參考型別本身有關,
- 下面按順序詳細說明一下多型的這四種特點,
多型核心——重寫父類方法并通過父類參考呼叫
- 這個可能是大家平時最常用到的多型的特性,這里給個栗子感受一下就好:
public class Person {
public void introduce(){
System.out.println("俺是一個人");
}
}
public class Boy extends Person {
@Override
public void introduce(){
System.out.println("我是男孩,帥氣的男孩!");
}
}
public class Girl extends Person {
@Override
public void introduce(){
System.out.println("我是女孩,漂亮的女孩~");
}
}
public class Test {
public static void main(String[] args){
Person person = new Girl();
person.introduce();
person = new Boy();
person.introduce();
}
}
- 運行結果是,可以發現,同一個父類參考person的同一個方法,可以產生不同的結果,
我是女孩,漂亮的女孩~
我是男孩,帥氣的男孩!
參考型別的型別轉換
- 參考資料型別之間的轉換方式有兩種:自動型別轉換和強制型別轉換,
- 自動型別轉換在子類轉父類時觸發,所以父類參考指向子類時,并不需要強轉,
- 強制型別轉換在父類轉子類時使用,強轉之后父類可以呼叫子類的獨有方法,
- 非常重要的一點:強轉的目標型別與該參考指向的型別不一樣時,雖然編譯能通過,但運行時會拋出ClassCast例外,
- 為了避免這一點,可以使用
參考變數 instanceof 資料型別,當參考變數指向的資料型別和instanceof的資料型別相同時,運算式回傳true,否則回傳false,
- 為了避免這一點,可以使用
- 小小驗證一下,在上一個測驗類下面追加幾行代碼:
public class Test {
public static void main(String[] args){
Person person = new Girl();
person.introduce();
person = new Boy();
person.introduce();
System.out.println("驗證一下型別轉換");
person = new Girl();
Girl girl = (Girl)person;
Boy boy = (Boy)person;
}
}
- 執行結果如下,可以看到因為person指向的是girl的實體,所以強轉為boy是會出現ClassCast例外,
我是女孩,漂亮的女孩~
我是男孩,帥氣的男孩!
驗證一下型別轉換
Exception in thread "main" java.lang.ClassCastException: class com.ObjectOriented.Polymorphism.Girl cannot be cast to class com.ObjectOriented.Polymorphism.Boy (com.ObjectOriented.Polymorphism.Girl and com.ObjectOriented.Polymorphism.Boy are in unnamed module of loader 'app')
at com.ObjectOriented.Polymorphism.Test.main(Test.java:15)
- 然后修改為如下,就不會報錯了:
public class Test {
public static void main(String[] args){
Person person = new Girl();
person.introduce();
person = new Boy();
person.introduce();
System.out.println("驗證一下型別轉換");
person = new Girl();
Girl girl = (Girl)person;
if(person instanceof Boy){
Boy boy = (Boy)person;
}
}
}
動態系結
- 動態系結的意思上面已經解釋過了,這里再舉例感受一下什么叫動態系結~
public class Person {
/*public void introduce(){
System.out.println("俺是一個人");
}*/
}
- 接上面的例子,其他所有東西都不改,就把Person類中的introduce()方法注掉,大家會發現Test類中的
person.introduce();的編譯就報錯了, - 相反,如果你只注釋掉Girl類中的introduce,就不會有問題,因為相當于沒有override嘛,就變成了呼叫Person類的introduce,
- 這就是動態系結產生的結果,大家都知道,我們實際運行的時候,執行的其實是Girl類中的introduce,但是編譯階段卻只關心Person類中有沒有introduce,至于運行的時候的introduce,是運行時才動態“賦予”的,
靜態與多型
- 這一節其實我都不想寫,上面說得蠻清楚的,但是不寫的話就破壞隊形了=,=
- 但還是舉個例子吧哈哈哈
public class Person {
public static void introduce(){
System.out.println("俺是一個人");
}
}
- 其他都不變,就把Person類的introduce方法改為靜態,就會發現,哇!@Override報錯了,好的,那就把@Override都去掉,順便把Girl類和Boy類的introduce方法也改為靜態,
public class Girl extends Person {
public static void introduce(){
System.out.println("我是女孩,漂亮的女孩~");
}
}
public class Boy extends Person {
public static void introduce(){
System.out.println("我是男孩,帥氣的男孩!");
}
}
public class Test {
public static void main(String[] args){
Person person = new Girl();
person.introduce();
Person.introduce();
Girl.introduce();
}
}
- 結果是:
俺是一個人
俺是一個人
我是女孩,漂亮的女孩~
- 原因就不再重復了,上面說得hin清楚,
多型的使用場景
- 通過在某個方法的形參串列中,放入父類形參,呼叫方法時,傳入子類實參,從而實作多型,
- 配合抽象類,實作模版設計模式,
抽象類
基本概念
- 抽象類不能實體化,類體中可以宣告抽象方法,
- 抽象類的作用在于通過子類繼承,并強制且規范地重寫抽象方法,從而實作多型,
- 抽象類雖然不能實體化,但是可以寫構造方法,寫構造方法的意義是提供給子類進行呼叫,
abstract會和一些關鍵字鬧矛盾哦!
- abstract關鍵字不能和下列關鍵字共同使用:
- private
- final
- static
- 原因都差不多,因為abstract的意義在于被繼承和重寫,而上述關鍵字會影響到類和方法的可繼承性或可重新性,
繼續用男孩女孩舉栗子
public abstract class Person {
private int age;
Person(int age){
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public abstract void introduce();
}
public class Girl extends Person {
Girl(int age) {
super(age);
}
@Override
public void introduce() {
System.out.println("我是女孩,漂亮的女孩~芳齡:" + super.getAge());
}
}
public class Boy extends Person {
public Boy(int age){
super(age);
}
@Override
public void introduce(){
System.out.println("我是男孩,帥氣的男孩!芳齡:" + super.getAge());
}
}
public class Test {
public static void main(String[] args){
Person person = new Girl(24);
person.introduce();
person = new Boy(23);
person.introduce();
}
}
- 運行結果如下,可以發現,abstract的作用在于規范和強制,抽象類中的成員變數和成員方法在于提取共性,抽象類很常用,但具體的思想只可意會,不可言傳~百煉成鋼,
我是女孩,漂亮的女孩~芳齡:24
我是男孩,帥氣的男孩!芳齡:23
介面類
- 介面類的出現,是為了彌補單繼承的缺陷,
介面類與抽象類的對比
- 介面類支持多實作,抽象類只能支持單繼承,
- 介面類中不能有構造方法,抽象類中可以有構造方法,
- 介面類中只能有常量(final static),抽象類中可以有普通成員變數,
- 介面類只能有抽象方法,抽象類中可以有普通成員方法,
Java8之后產生的新特性
- Java8新特性:介面類支持default修飾的默認方法,實作類可以不用重寫該方法,而直接使用該默認方法,
- Java8新特性:介面類支持靜態方法,
- Java9新特性:介面中允許宣告普通的私有方法,用于實作僅供默認方法呼叫的方法,
讓男孩來展示一下介面類的方方面面
public interface Person {
//介面類中指定定義常量,并且注釋中的幾個關鍵字是可以省略的
/*public static final*/int LEG_COUNT = 2;
//介面類中只能有抽象方法,并且注釋中的關鍵字可以省略
/*public abstract*/ void introduce();
}
public interface Animal {
//默認方法,實作類可以自行選擇重寫或不重寫
/*public*/ default void eat(){
cry();
run();
System.out.println("一口吃掉蘋果");
}
//私有方法,供默認方法呼叫
private void cry(){
System.out.println("嗷嗚");
}
//靜態方法,通過類名呼叫
/*public*/ static void run(){
System.out.println("跑啊跑,跑到外婆橋");
}
}
public class Boy implements Person, Animal {
@Override
public void introduce() {
System.out.println("我是男孩,帥氣的男孩!俺有" + Person.LEG_COUNT + "條腿哦~");
}
}
public class Test {
public static void main(String[] args){
Boy boy = new Boy();
boy.introduce();
boy.eat();
Animal.run();
}
}
- 運行結果如下,這次講解都放到注釋里面了,所以就不在這里費口舌啦,
我是男孩,帥氣的男孩!俺有2條腿哦~
嗷嗚
跑啊跑,跑到外婆橋
一口吃掉蘋果
跑啊跑,跑到外婆橋
特殊類
普通內部類
普通內部類的使用方式
- 和普通的類一樣,可以定義成員變數、成員方法、構造方法等,其實就是除了位置比較特殊,其他的地方都和普通的類是一樣的
- 同樣可以使用final、abstract、private、public、protected等關鍵字修飾,
- 對于在外部類中的不重名成員變數,內部類可以直接訪問,
- 普通內部類的實體化和重名成員變數的訪問方式比較特殊,下面請看一段VCR(x)…請看一段代碼:
public class Car {
private String color;
Car(String color){
this.color = color;
}
public class Wheel{
private String color;
public void paintWheel(String color){
this.color = color;
System.out.println("想要涂上的顏色是:" + color);
System.out.println("車輪子變成了:" + this.color);
System.out.println("車身子的顏色還是:" + Car.this.color);
}
}
}
public class Test {
public static void main(String[] args){
Car car = new Car("紅色");
Car.Wheel carWheel = car.new Wheel();
carWheel.paintWheel("藍色");
}
}
- 運行結果如下,我們可以發現,除了實體化內部類以及內部類重名成員變數的使用方法有點區別以外,其他的方面和普通的類沒有太大區別,
- 內部類實體化方式:
外部類名.內部類名 內部類實體名 = 外部類實體名.new 內部類構造方法; - 內部類重名變數使用方法:方法中的形參還是直接使用(因為就近原則),內部類的變數使用
this.變數名,外部類的重名變數使用外部類名.this.變數名
- 內部類實體化方式:
想要涂上的顏色是:藍色
車輪子變成了:藍色
車身子的顏色還是:紅色
靜態內部類
- 在宣告方式上,和普通內部類的唯一區別就是在類名前加了個static關鍵字,使用時,實體化方法變成:
外部類名.內部類名 內部類實體名 = new 外部類名.內部類構造方法 - 靜態內部類中,可以訪問外部類中的靜態成員變數和方法,但是不能訪問非靜態成員變數和方法,原因還是老規矩,
- 靜態內部內中,訪問外部內的靜態成員和方法,使用
外部類名.來訪問;訪問內部類的靜態成員和方法 ,使用內部類名.來訪問, - 如果非得訪問外部類的非靜態成員,也不是不行,可以在靜態內部類的方法中實體化外部類,然后再通過參考來訪問,
- 靜態內部內中,訪問外部內的靜態成員和方法,使用
區域(方法)內部類
- 直接先上例子再來說明吧,不然容易暈乎,
public class Car {
private String color;
public void create(String color){
//Java8新特性:自動添加final關鍵字
/*final*/ int wheelCount = 4;
//區域內部類無需訪問修飾符
class Wheel{
private String color;
public Wheel(){
System.out.println("創建輪子");
}
public void paint(String color){
this.color = color;
System.out.println(wheelCount + "個輪子涂成"+this.color);
}
}
this.color = color;
System.out.println("車子涂成"+this.color);
Wheel wheel = new Wheel();
wheel.paint("綠色");
}
}
public class Test {
public static void main(String[] args){
Car car = new Car();
car.create("紅色");
}
}
- 執行結果如下,可以看到,區域內部類定義在外部類的成員方法中,
車子涂成紅色
創建輪子
輪子涂成綠色
使用方法
- 區域內部類只在外部類的對應方法中可以使用,
- 區域內部類不能使用訪問控制符和static關鍵字,
- 區域內部類可以訪問外部類的對應方法中的區域變數,但該變數必須是final,
- 如上面例子中的wheelCount,因為wheelCount在內部類中被使用了,所以wheelCount必須是final修飾的,
- 原因是因為外部類的區域變數,被區域內部類使用時,區域內部類會將該變數拷貝一份(所以內外部類的該變數的地址是不同的),為了防止發生類似“臟讀”的現象,該變數必須是final的,
最常用的內部類——匿名內部類
- 當想要讓重寫的方法不要額外占用方法區記憶體空間的時候,可以使用匿名內部類,
- 使用普通的實作類或者子類來重寫方法,需要一直占用方法區記憶體,而匿名內部類在方法執行完之后,會釋放方法區的記憶體,
- 使用方式:
介面/父型別別 參考變數名 = new 介面/父型別別() { 方法的重寫 };, - 舉栗如下:
public interface Person {
void introduce();
}
public class Test {
public static void main(String[] args){
Person person = new Person() {
@Override
public void introduce() {
System.out.println("就是這么牛逼");
}
};
person.introduce();
}
}
- 執行結果如下,其實Java8之后增加了新特性——lambda運算式,所以上面這種寫法以后就會逐漸淘汰啦~至于lambda運算式,就不在這篇文章里面講了,
就是這么牛逼
列舉類
基本用法
- 其實在Java1.5之前,列舉類都是大家自己寫的,寫法和單例模式很像,單例模式只提供一個成員實體,而列舉類提供多個不同類的成員實體,
- 那么Java1.5之后產生的列舉類,是怎么樣的呢?直接上代碼!
public enum Week {
//列舉型別要求所有列舉值放在類的最前面
MON("周一"),TUE("周二"),THUR("周三"),SAT("周四");
private final String desc;
Week(String desc) {
this.desc = desc;
}
public String getDesc(){
return desc;
}
}
public class Test {
public static void main(String[] args) {
Week tue = Week.TUE;
System.out.println(Week.MON.getDesc());
System.out.println(tue.getDesc());
}
}
- 運行結果如下:
周一
周二
- 其實,列舉型別就是語法省略后的本類物件的參考,例如
MON("周一");,其實代表的是public static final Week MON = new Week("周一"); - 使用方法也是完全和訪問靜態成員變數的方法一樣,
- 列舉類可以自定義構造方法,但是前提必須得是private修飾的,
- 列舉型別最好使的地方就是配合switch,具體就不多說了,懂的自然懂~
常用方法
- 所有列舉型別都默認繼承自java.lang.Enum類,這個類中有一些方法可供大家使用:
| 方法名 | 說明 |
|---|---|
| static T[] values() | 回傳當前列舉類中所有的物件 |
| String toString() | 回傳當前列舉類物件的標識名稱 |
| int ordinal() | 獲取列舉物件在列舉類中的索引位置 |
| static T valueOf(String str) | 將引數指定的字串名轉為對應的列舉類物件,如果對應的列舉型別不存在,則拋出非法引數例外 |
| int compareTo(E o) | 比較兩個列舉物件在定義時的順序,<0:呼叫該方法的列舉型別在實參列舉型別的前面;=0:相同列舉型別;>0:呼叫該方法的列舉型別在實參列舉型別的后面 |
- 續上面的例子,來感受感受這幾個方法:
//Week類不變,Test中的main方法增加幾行代碼
public class Test {
public static void main(String[] args) {
Week tue = Week.TUE;
System.out.println(Week.MON.getDesc());
System.out.println(tue.getDesc());
Week[] weeks = Week.values();
System.out.println(weeks[3].toString() + "的索引是:" + weeks[1].ordinal());
Week thur = Week.valueOf("THUR");
System.out.println(thur + "和" + Week.SAT.toString() + "比較的結果是:" + thur.compareTo(Week.SAT));
}
}
- 運行結果如下:
周一
周二
SAT的索引是:1
THUR和SAT比較的結果是:-1
列舉類實作介面的方式
- 第一種方式和以前一樣,在列舉類(例如Week類)中重寫方法,此時所有列舉型別都會呼叫該方法,這種就不舉例了,沒有區別,
- 第二種是讓每一個列舉類都單獨重寫方法,通過匿名內部類即可實作該需求,具體說來是什么樣子呢?請看下面:
//增加一個介面類
public interface WeekInterface {
void workday();
}
//相較于上一個例子,實作了一個介面類,并且使用匿名內部類重寫了介面方法
public enum Week implements WeekInterface {
//列舉型別要求所有列舉值放在類的最前面
MON("周一"){
@Override
public void workday(){
System.out.println("周一是作業日呀!");
}
},TUE("周二"){
@Override
public void workday(){
System.out.println("周二還是作業日呀!");
}
},THUR("周三"){
@Override
public void workday(){
System.out.println("周三怎么還是作業日呀!");
}
},SAT("周四"){
@Override
public void workday(){
System.out.println("周四還是作業日!好氣哦!");
}
};
private final String desc;
Week(String desc) {
this.desc = desc;
}
public String getDesc(){
return desc;
}
}
//測驗類增加一點代碼
public class Test {
public static void main(String[] args) {
Week tue = Week.TUE;
System.out.println(Week.MON.getDesc());
System.out.println(tue.getDesc());
Week[] weeks = Week.values();
System.out.println(weeks[3].toString() + "的索引是:" + weeks[1].ordinal());
Week thur = Week.valueOf("THUR");
System.out.println(thur + "和" + Week.SAT.toString() + "比較的結果是:" + thur.compareTo(Week.SAT));
System.out.println("------------------------------------------------------------------------");
WeekInterface weekInterface = Week.MON;
weekInterface.workday();
Week.SAT.workday();
}
}
- 執行結果如下:
周一
周二
SAT的索引是:1
THUR和SAT比較的結果是:-1
------------------------------------------------------------------------
周一是作業日呀!
周四還是作業日!好氣哦!
- 可能列舉類的匿名內部類的撰寫方法看起來有點難以接受,其實很好理解,因為列舉型別本來就是簡寫的程序,拿周一舉個例子,通過下面的對比,大家應該就能明白了:
//標準的匿名內部類
public static final Week MON = new Week("周一"){
@Override
public void workday(){
System.out.println("周一是作業日呀!");
}
};
//列舉類中簡化的匿名內部類
MON("周一"){
@Override
public void workday(){
System.out.println("周一是作業日呀!");
}
};
注解
- 注解這個玩意兒大家可能經常用到,但是應該不是每個人都去了解過,他其實也是一種類,一種“特殊的介面類”,
- 注解通常能夠修飾包、類、 成員方法、成員變數、構造方法、引數、區域變數的宣告,
- 所有注解都自動繼承了一個介面類——Annotation類,
- 注解類的宣告語法如下:
訪問修飾符 @interface 注解名稱 {
注解成員;
}
- 注解這東西基本概念比較簡單,但是今后在各類框架、多執行緒等開發中可是起著舉足輕重的作用!
注解類的宣告方式
public @interface Goods {
//這個其實是一個名字為type的String型別成員變數,但是是通過“無形參方法”的形式宣告的
/*public*/ String type();
double price() default 1.0;
}
@Goods(type = "food", price = 1.1)
public class Food {
}
- 這個例子就沒有運行結果了,其中需要提一下的是,注解類中看起來很像宣告成員方法的代碼,其實宣告的是成員變數,
元注解
- 元注解是用在注解類上的注解,
- 五種常見元注解:@Retention、@Documented、@Target、@Inherited、@Repeatable,
@Retention
- 用于描述注解的生命周期,有一個成員變數value,取值如下:
- RetentionPolicy.SOURCE:注解僅在原始碼階段保留,編譯時將會忽略,
- RetentionPolicy.CLASS:被保留到編譯后的位元組碼檔案,不會加載進JVM,默認方式,
- RetentionPolicy.RUNTIME:保留到程式運行階段,加載進JVM,可以在運行階段通過反射機制獲取到注解,
@Documented
- JavaDoc工具默認不會包括注解內容,如果想讓JavaDoc檔案說明某個注解類的使用地方,則用@Documented來注解該注解類,
- 如果要讓這個注解生效,必須讓@Retention的值為RetentionPolicy.RUNTIME,
@Target
- 該注解用于限制某個注解類能夠使用的地方,該注解有一個成員變數,型別為ElementType[],具體取值如下:
- ElementType.ANNOTATION_TYPE:可以對注解類進行注解
- ElementType.CONSTRUCTOR:可以對構造方法進行注解
- ElementType.FIELD:可以對屬性(成員變變數)進行注解
- ElementType.LOCAL_VARIABLE:可以對區域變數進行注解
- ElementType.METHOD:可以對方法進行注解
- ElementType.PACKAGE:可以對包進行注解
- ElementType.PARAMETER:可以對方法形參進行注解
- ElementType.TYPE:可以對類進行注解,例如類、介面、列舉
- Java8新增:ElementType.TYPE_PARAMETER:可對型別變數的宣告(如泛型)進行注解
- Java8新增:中ElementType.TYPE_USE:可對使用型別的任何陳述句進行注解
- 需要注意的是,這個注解使用的時候,需要指定的值是一個陣列,所以需要用陣列的方式,來放入N個值,
@Inherited
- 該注解注解了某個注解類之后,如果該注解類使用在了一個超類上,且該超類的某個子類沒有任何注解,則子類自動繼承超類的注解,
Java8新增元注解:@Repeatable
- 在不加該注解的情況下,同一個注解只能對一個元素注解一次,但是在使用了該注解之后,即可多次使用同一注解,
- 這個注解的使用方式可能比較難理解,下面舉個例子:
//這個類使用@Repeatable注解,該注解需要通過反射機制傳入GoodsTypes注解類
@Repeatable(GoodsTypes.class)
public @interface Goods {
//這個其實是一個名字為type的String型別成員變數,但是是通過“無形參方法”的形式宣告的
/*public*/ String type();
double price() default 1.0;
}
//該注解類需要一個Goods陣列型別的成員變數,并且標識名稱必須為value
public @interface GoodsTypes {
Goods[] value();
}
//使用注解之后,@Goods就可以多次使用了,其實可以理解為@Repeatable注解就是簡化了下面注釋掉的@GoodsTypesj'j'n'b
@Goods(type = "food", price = 1.1)
@Goods(type = "vehicle", price = 11)
//@GoodsTypes({@Goods(type = "food", price = 1.1), @Goods(type = "vehicle", price = 11)})
public class Food {
}
Java的一些常見預制注解
- 檔案注釋用的:
- @author、@version、@see、@since、@param、@return、@exception
- @Override:限定重寫方法
- @Deprecated:表示方法或者類已過時
- @SuppressWarnings:抑制編譯器警告
包裝類
自動拆箱機制
- 猜猜下面的結果是啥

- 結果如下:
true
false
- 為什么呢?
- 對于第一個比較:大家可能第一時間會想到==比較的是地址,所以理應為false,但是包裝類在進行運算子運算的時候,會觸發“自動拆箱機制”,也就是說
a + b這東西,計算結果會從Integer變為int,而Long與int進行比較,Long也會變為long,所以最終就變成了基本資料型別的比較,結果自然是true, - 對于第二個比較:這個比較簡單,因為Long類重寫了equals方法,自己去看一下就知道了,如果比較的是非Long型別,則直接為false,
- 對于第一個比較:大家可能第一時間會想到==比較的是地址,所以理應為false,但是包裝類在進行運算子運算的時候,會觸發“自動拆箱機制”,也就是說
常量池
- 猜猜下面的結果是啥

- 結果是
true - 有的小伙伴又懵了:怎么著?這次還不比地址么?
- 不,這次真的比的是地址,但卻是就是同一個地址,為什么呢?因為包裝類有一個叫常量池的東西,對于-128~127的整數,對應的包裝類都作為常量存放在了記憶體當中,所以a和b其實都是直接指向的記憶體中的這些常量,地址也就理所當然是相同的了,
- 同理,如果將a和b改為128,再進行測驗,結果就是
false,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/184863.html
標籤:其他
