java數據類型之基本類型和引用類型的概念的完全理解(網上搜集總結)

這篇文章得沉下心,仔細看,一天看完這一篇,我覺得你這一天都是賺的。

一.首先java的數據類型分類爲:

java的世界裏面應該是萬物皆對象,爲什麼會存在基本類型呢?

(1)由於性能問題不得不用java基於性能的考慮,用c寫的基本數據類型,基本數據類型存在棧裏(存取速度比堆要快,僅次於直接位於CPU中的寄存器速度比堆存儲特別快,再有就是基本類型定義的變量創建和銷燬很快,而類定義的變量還需要JVM去銷燬。
(2)爲了滿足面向java面向對象裝箱和拆箱 包裝類型將基本類型的值包裝在對象中,這樣就可以使用對象的方法操作基本類型,類型之間的轉換需要使用包裝類型的方法才能完成,因此需要有包裝類型。

裝箱:將基本類型轉換成包裝類的過程

拆箱:將包裝類轉換成基本類型的過程

  (3)【對象與基本數據類型的區別】

基本數據類型在棧中進行分配,而對象類型在堆中進行分配。

所有方法的參數都是在傳遞引用而非本身的值(基本類型例外)。

對象之間的賦值只是傳遞引用,基本類型之間的賦值是創建新的拷貝。

二.八大基本類型

Java中的八個基礎類型有:byte、short、int、long、float、double、char和boolean.

先來說第一個問題:

(1).類型轉換數據溢出問題

首先:1個字節佔8位。

分爲如下幾類:

      整型:byte、short、int、long 分別佔用1、2、4、8個字節的空間;

      浮點型:long、float 分別佔用4、8個字節;

      char型:char 佔用2個字節;

      boolean型:boolean 佔用1位.

其中需要穿插一個概念:+++++++字節和位+++++++

位概念

二進制數系統中,每個0或1就是一個位(bit),位是數據存儲的最小單位。其中8bit就稱爲一個字節(Byte)。計算機中的CPU位數指的是CPU一次能處理的最大位數。例如32位計算機的CPU一次最多能處理32位數據。

二進制位:

二進制位簡稱“位”,是二進制記數系統中表示小於2的整數的符號,一般用1或 0表示,是具有相等概率的兩種狀態中的一種。

二進制位的位數可表示一個機器字的字長,一個二進制位包含的信息量稱爲一比特。 

比特(BIT,binary system):

計算機專業術語,是信息量單位,是由英文BIT音譯而來。同時也是二進制數字中的位,信息量的度量單位,爲信息量的最小單位。在需要作出不同選擇的情況下把備選的刺激數量減少半所必需的信息。即信號的信息量(比特數)等於信號刺激量以2爲底數的對數值。L.哈特萊1928年認爲對信息量選用對數單位進行度量最合適。如二進制數0100就是4比特。

拓展問題:

(1).爲什麼一個要設計1個字節=8Bit?

所謂字節,原意就是用來表示一個完整的字符的。最初的計算機性能和存儲容量都比較差,所以普遍採用4位BCD編碼(這個編碼出現比計算機還早,最早是用在打孔卡上的)。BCD編碼表示數字還可以,但表示字母或符號就很不好用,需要用多個編碼來表示。後來又演變出6位的BCD編碼(BCDIC),以及至今仍在廣泛使用的7位ASCII編碼。不過最終決定字節大小的,是大名鼎鼎的System/360。當時IBM爲System/360設計了一套8位EBCDIC編碼,涵蓋了數字、大小寫字母和大部分常用符號,同時又兼容廣泛用於打孔卡的6位BCDIC編碼。System/360很成功,也奠定了字符存儲單位採用8位長度的基礎,這就是1字節=8位的由來

以下兩個問題的鏈接:https://www.jianshu.com/p/d0a8ff006f5c

(2)我們都知道一個二進制8位能表示的最大值是 1111 1111 == 255,但爲什麼最大表示到127?

因爲對於計算機來說,一個二進制的數字它的最高位是符號位,0表示正數,1表示負數。
所以 1111 1111 表示的 -127, 而 0111 1111 表示的是127,範圍區間應該是[-127,127]之間

(3).我們都知道一個Byte能表達的數字範圍是[-128,127],那麼這個-128是怎麼來的呢?

這個涉及到原碼、反碼、和補碼的相關知識,這裏稍微拓展一下,自己想了解更清楚可以深入學習。

背景:原碼,反碼,補碼的產生過程,就是爲了解決,計算機做減法和引入符號位(正號和負號)的問題。

相關鏈接:https://blog.csdn.net/zhiwen_a/article/details/81192087

+++++++字節和位結束+++++++

知道字節和佔位的關係後繼續來說類型轉換數值溢出的問題:

由於存儲位數不一樣所以,不同基本類型存儲的數值範圍不一樣

類型轉換的時候由於類型使用的字節空間大小不一樣,所以向上轉換:可以直接進行隱式轉換,向下轉換,可能會出現數據溢出情況。如下轉換邏輯:

å¨è¿éæå¥å¾çæè¿°

詳情轉換邏輯看這個:https://blog.csdn.net/weixin_44736274/article/details/90769042

具體轉換java測試demo例子看這個:https://blog.csdn.net/jreffchen/article/details/81015884

(2).值傳遞還是引用傳遞問題

(1)基本類型的創建:聲明並初始化基本數據類型的局部變量時,變量名以及字面量值都是存儲在棧中,而且是真實的內容。

具體過程:比如  int age=50;

首先JVM創建一個名爲age的變量,存於局部變量表中,然後去棧中查找是否存在有字面量值爲50的內容,如果有就直接把age指向這個地址,如果沒有,JVM會在棧中開闢一塊空間來存儲“50”這個內容,並且把age指向這個地址。因此我們可以知道:
我們聲明並初始化基本數據類型的局部變量時,變量名以及字面量值都是存儲在棧中,而且是真實的內容。

640?wx_fmt=png

(2)引用類型的創建:引用數據類型的對象/數組,變量名存在棧中,變量值存儲的是對象的地址,並不是對象的實際內容。

具體過程:比如 Person p=new Person();

在執行Person per;時,JVM先在虛擬機棧中的變量表中開闢一塊內存存放per變量,在執行per=new Person()時,JVM會創建一個Person類的實例對象並在堆中開闢一塊內存存儲這個實例,同時把實例的地址值賦值給per變量。因此可見:
對於引用數據類型的對象/數組,變量名存在棧中,變量值存儲的是對象的地址,並不是對象的實際內容。

640?wx_fmt=png

詳細相關鏈接:https://mp.csdn.net/postedit/100006266

(3)值傳遞和引用傳遞理解

這兩個傳遞都可以理解爲,外部變量調用一個方法的時候,方法內部操作會不會改變這個變量的值

用代碼來demo一下所有情況:

基本類型以int爲例:

1.int基本類型,方法內重新賦值和方法內自增參數,都不會影響外部參數的值

public class IntPassByValue {
    public static void main(String[] args) {
        int a = 6;
        System.out.println("輸出開始的值:" + a);
        changeIntValue(a);
        System.out.println("輸出結束1的值:" + a);
        addIntValue(a);
        System.out.println("輸出結束2的值:" + a);
    }

    /**
     *重新賦值
     */
    static void changeIntValue(int a) {
        a = 18;
        System.out.println("輸出重新賦值方法中間的值:" + a);
    }

    /**
     *內容自增
     */
    static void addIntValue(int a) {
        a += 1;
        System.out.println("輸出自增方法中間的值:" + a);
    }
}

結果,調用的方法內操作不會影響外部參數

輸出開始的值:6
輸出重新賦值方法中間的值:18
輸出結束1的值:6
輸出自增方法中間的值:7
輸出結束2的值:6

2.類引用類型

public class ClassPassByReference {
    public static void main(String[] args) {
        Student student = new Student(1, "張三");
        System.out.println("輸出開始的值:" + student.toString());
        changeClassWholeValue(student);
        System.out.println("輸出結束1的值:" + student.toString());
        changeClassLocalValue(student);
        System.out.println("輸出結束2的值:" + student.toString());
    }

    /**
     *修改引用指向的地址
     */
    static void changeClassWholeValue(Student student) {
        student = new Student(2,"李四");
        System.out.println("輸出重新賦值方法中間的值:" + student.toString());
    }

    /**
     *修改引用指向的地址對應的內容
     */ 
    static void changeClassLocalValue(Student student) {
        /*只修改name,不改id*/
        student.setName("王五");
        System.out.println("輸出自增方法中間的值:" + student.toString());
    }
}

結果:

輸出開始的值:Student{id=1, name='張三'}
輸出重新賦值方法中間的值:Student{id=2, name='李四'}
輸出結束1的值:Student{id=1, name='張三'}
輸出自增方法中間的值:Student{id=1, name='王五'}
輸出結束2的值:Student{id=1, name='王五'}

附上student類:

public class Student {

    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

3.引用類型String類

public class StringPassByReference {
    public static void main(String[] args) {
        String a = "最初1";
        System.out.println("輸出開始的值:" + a);
        changeStringWholeValue(a);
        System.out.println("分割線+++++++string結束++++++++輸出結束1的值:" + a);

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("stringBuilder開始");
        System.out.println("輸出stringBuilder開始的值:" + stringBuilder);
        changeStringBuilderWholeValue(stringBuilder);
        System.out.println("輸出stringBuilder修改1的值:" + stringBuilder);
        changStringBuilderLocalValue(stringBuilder);
        System.out.println("輸出stringBuilder修改2的值:" + stringBuilder);
    }

    /**
     * 修改引用指向的地址
     */
    static void changeStringWholeValue(String a) {
        a = "修改1";
        System.out.println("輸出重新賦值方法中間的值:" + a);
    }

    /**
     * 修改StringBuilder引用指向的地址
     */
    static void changeStringBuilderWholeValue(StringBuilder a) {
        StringBuilder changeStringBuilder = new StringBuilder();
        changeStringBuilder.append("修改指向引用地址");
        a = changeStringBuilder;
        System.out.println("輸出修改StringBuilder引用指向的地址:" + a.toString());
    }

    /**
     * 修改StringBuilder引用指向的地址對應的內容
     */
    static void changStringBuilderLocalValue(StringBuilder a) {
        a.append("增加str");
        System.out.println("輸出修改StringBuilder引用指向的地址對應的內容:" + a);
    }
}

結果:1.由於String是不可變的源碼string用final修飾,所以不管怎麼操作string都是新生成一個str地址,調用方法操作不會影響到外部參數。2.StringBuilder如果在方法內改變引用參數的指向,是不會影響外部的參數。3.StringBuilder在方法內使用當前引用地址,修改這個地址的內容是會影響外部參數內容的。

輸出開始的值:最初1
輸出重新賦值方法中間的值:修改1
分割線+++++++string結束++++++++輸出結束1的值:最初1
輸出stringBuilder開始的值:stringBuilder開始
輸出修改StringBuilder引用指向的地址:修改指向引用地址
輸出stringBuilder修改1的值:stringBuilder開始
輸出修改StringBuilder引用指向的地址對應的內容:stringBuilder開始增加str
輸出stringBuilder修改2的值:stringBuilder開始增加str

總結:1.形參和實參,外部的參數是實參,調用方法的時候形參是新的引用參數,代替了實參。(詳情自己瞭解)2.類型分爲基本類型和引用類型兩種。3.基本類型是把內容放在棧內的變量上,新建內容的話就需要新建變量的話,調用方法的話,形參和實參是兩個不同的引用變量,所以內容改變不互相影響,所以基本類型調用的方法內操作形參不會影響到外部實參。4.引用類型類是把引用創建在棧上,地址指向堆上創建的內容,類調用方法形參代表的實參是類的地址,如果改變形參的地址指向,是不會影響到外部實參的地址和內容。如果根據形參修改指向地址的內容,那麼會影響到外部實參的內容。5.引用類型有個String,由於string是final類型,創建一個string就不能再改變(這個具體可以深入瞭解),所以每次修改形參,都會生成一個新的地址和內容,不會修改原來指向地址的內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章