深刻理解JAVA語言

1. 變量及其傳遞

基本類型變量(primitive type)和引用類型變量(reference type)

  1. 基本類型(primitive type):其值直接存於變量中。「在這裏」java

  2. 引用型(reference type) 的變量除佔據必定的內存空間外,它所引用的對象實體(由new 建立)也要佔據必定空間。「在那裏」,能夠理解爲指針git

代碼

MyDate m,n;
m=new MyDate();
n=m;

m和n都指向同一個對象,二者均可以理解爲一個指針。經過m和n均可以操縱同一個對象。程序員

字段變量(Field)與局部變量(Local variable)區別

從位置看

圖片描述

  1. 字段變量(Field):又稱成員變量(member variable),域變量,在類中;上圖的latitudelongitude都是字段變量。express

  2. 局部變量(Local variable):又稱本地變量(local variable),在方法中定義的變量或方法的參變量。上圖的argslima以及latInlonIn都是局部變量。編程

從內存角度看

Memory Model的創建流程以下所示數組

圖片描述

上圖首先在堆中新建SimpleLocation類的對象,該對象擁有兩個變量latitudelongtiude-->>安全

圖片描述

上圖展現調用函數SimpleLocation(double latIn,double lonIn)期間,內存中新建了臨時空間,如圖中constructor's scope所示。-->>app

圖片描述

如上所示,局部(臨時)變量laiInlonIn將值傳遞給SimpleLocation類的對象latitudelongtiude後,則對象的創建成功。該函數調用結束後,局部變量latInlonIn以及this會在內存中消失。該構造器(constructor) 先返回對象的地址(指針)給 lima 變量,而後消失。最終,lima將指向新對象。ide

生命週期:Field 是在隨着對象的建立,產生的在堆(The heap)中;Local variable 是隨着方法的調用期間按需生成相應的內存空間。
初始值:Filed 編譯器會自動賦初值,Local variable 須要顯式賦值,不然編譯沒法經過;函數

從語法角度看

  1. 字段變量屬於類,能夠用public,private,static,final 修飾。

  2. 局部變量不可以被訪問控制符及static修飾

  3. 均可以被final修飾

變量的傳遞(與c語言類似)

調用對象方法時,要傳遞參數。

  • 在傳遞參數時,Java 是值傳遞,即,是將表達式的值複製給形式參數。

  • 對於引用型變量,傳遞的值是引用值,而不是複製對象實體,能夠改變對象的屬性

方法的返回

  • 返回基本類型。

  • 返回引用類型。它就能夠存取對象實體。

代碼

Object getNewObject() {
Object obj=new Object();
return obj; }

調用時:

Object p= GetNewObject();

2. 多態和虛方法調用

多態(Polymorphism)

是指一個程序中相同的名字表示不一樣的含義的狀況。

1. 編譯時多態

重載(overload) (多個同名的不一樣方法)。

代碼

p.sayHello();
p.sayHello(「Wang」);

2. 運行時多態(更重要)

  • 覆蓋(override) (子類對父類方法進行覆蓋)

Polymorphism means that a variable of a supertype can refer to a subtype object.

  • 動態綁定(dynamic binding) ----虛方法調用(virtual method invoking)

  • 在調用方法時,程序會正確地調用子類對象的方法。

  • 多態優勢:大大提升了程序的抽象程度和簡潔性。

代碼

public class DynamicBindingDemo {
    public static void main(String[] args){
        m(new GraduateStudent());//傳遞的是子類對象,編譯經過
        m(new Student());//同上!
        m(new Person());//同上!
        m(new Object());
    }
    public static void m(Object x){
        System.out.println(x.toString());
    }
}
class GraduateStudent extends Student{
}
class Student extends Person{
    public String toString(){
        return "student";
    }
}
class Person{
    public String toString(){
        return "Person";
    }
}

運行結果:

student
student
Person
java.lang.Object@60e53b93

從以上測試代碼看來,雖然 m(Object x)定義的形參是Object類的,但容許實際參數傳遞子類對象:m(new GraduateStudent());,編譯經過。
另外:在類繼承關係鏈中,對同一個方法可能對應有多個實現,但在運行時由JVM自動搜索綁定哪一個實現。動態綁定流程:從最近的類(最低,最具體)找,直到 Object 類(最高,最抽象)。只要找到了實現方法就中止。好比如今GraduateStudent類中找,沒找到,就在Student類中找,結果找到了,就中止該搜索。
這裏須要注意區別構造函數的執行流程。

上溯造型

  • 上溯造型(upcasting) :是把派生類型(subclass)看成基本類型(upclass)處理.

It is always possible to cast an instance of a subclass to a variable of a superclass(knowns as upcasting) .

Person p = new Student();
void fun(Person p ){...}

在這裏,雖然P的聲明類型是Person,但它的實際類型是Student,Student是Person的子類。在被fun(Person p)調用時,仍能夠casting。這也是動態綁定的範疇。

涉及類型轉換的問題:

m(new Student());
//等價於
Object o = new Student();
m(o);

如今假設,要將o分配給一個Student類的變量,該如何作?
方法1

Student b = o;//編譯報錯,由於在編譯器看來,o是一個Object類的變量,並不必定是個Student類的變量

方法2

Student b = (Student)o;//這實際上是downcasting了,把一個Object類的對象向下映射爲Student類的,編譯經過。可是前提是得確保o變量的真實類型是Student類的,不然會拋出ClasCastException錯誤。

虛方法的調用

如何實現運行時的多態?(虛方法調用)

  • 子類重載了父類方法時,運行時系統根據調用該方法的真實類型(actual type)來決定選擇哪一個方法調用

  • 全部的非final方法都會自動地進行動態綁定

如何肯定動態類型?

  • instanceof 是 java 的關鍵字。

  • 用變量 instanceof 來判斷一個對象的真實類型(actual type)

  • 結果是boolean 值

代碼

Object myObject = new Circle();
if(myObject instanceof Circle){
    System.out.println("The circle diameter is " + ((Circle)myObject).getDiameter());
}

Object myObject = new Circle();新增一個Object類的myObject變量,可是,myObject的真實類型(actual type)是Circle()類型,因此myObject instanceof Circle 返回True

另外,爲何要進行對myObject進行downcasting,即 (Circle)myObject ?

編譯時,myObject 的聲明類型是Object便於編譯器決定採用哪一個方法,好比myObject.getDiameter()將會引發編譯錯誤,因此要類型轉換 (Circle)myObject

多說一句,爲何要將myObject 設定爲 Object對象?

將一個變量聲明爲父類類型,是爲了更好地抽象編程(generic programming),這樣myObject可以接受任何子類對象

什麼狀況不是虛方法調用

  • Java中,普通的方法是虛方法

  • 但static,private方法不是虛方法調用

三種非虛的方法

  • static的方法(從名字看,是靜態,與動態綁定相對),以聲明的類型爲準,與實例類型無關

  • private方法子類看不見,也不會被虛化

  • final方法子類不能覆蓋,不存在虛化問題

代碼

public class InvokeStaticMethod {
/*調用靜態方法來
*/
    public static void main(String[] args){
        Circle c = new Circle();
        Shape s = new Shape();
        Shape d = new Circle();
        
        doSomething(c);
        doSomething(s);
        doSomething(d);

        doSomethingVer2(c);
        doSomethingVer2((Circle)d);//必須強制類型轉換爲Circle()
    }
    static void doSomething(Shape s){//注意該方法聲明爲靜態,非虛調用,參量的聲明類型是Shape,因此只能匹配到Shape類型的參數。
        s.draw();
    }
    static void doSomethingVer2(Circle s){//注意該方法聲明爲靜態,形參只能匹配到Circle類型的參數;
        s.draw();
    }
}
class Shape {
    static void draw(){
        System.out.println("draw shape");
    }
}
class Circle extends Shape{
    static void draw(){
        System.out.println("draw circle");

    }
}

輸出結果:

draw shape
draw shape
draw shape
draw circle
draw circle

3. 對象的構造和初始化

構造方法(constructor)

  1. 對象都有構造方法

  2. 若是沒有,編譯器加一個default構造方法

  3. 類的成員變量和方法是能夠繼承的,可是類的構造器是不能繼承的。只能經過調用,又分爲顯式調用和隱式調用。

調用本類或父類的構造方法

  1. this調用本類的其餘構造方法。

  2. super調用直接父類的構造方法

  3. this或super要放在第一條語句,且只可以有一條

  4. 若是既不是調用this,也不是調用super方法,則編譯器會自動加上super(),也就是調用直接父類的無參方法。

能夠看出,原則上必須令全部父類的構造方法都獲得調用,不然對象的構建就不成功。

In any case, constructing an instance of a class invokes the constructors of all the superclasses along the inheritance chain. This is called constructor chaining.
--Introduction to Java Programming

構造方法的執行過程遵守如下步驟:

  1. 調用本類或父類的構造方法,直至最高一層(Object)

  2. 按照聲明順序執行字段的初始化賦值

  3. 執行構造函數中的各語句。

簡單地說: 先父類構造,再本類成員賦值,最後執行構造方法中的語句。

建立對象時初始化

  1. p = new NoConstructorTest(){{ a="A"; b="B"; }};

  2. 這樣能夠針對沒有相應構造函數但又要賦值。

  3. 注意雙括號。

代碼

public class NoConstructorTest {
    String a;
    String b;
    //no constructors
    public static void main(String[] args){
        NoConstructorTest p = new NoConstructorTest(){{ a="A"; b="B"; }};
        System.out.println("the instance can be given value without defining constructor.\na is:\n"+p.a+"\nb is: \n"+p.b);
    }
}

輸出結果

the instance can be given value without defining constructor.
a is:
A
b is: 
B

可見,該對象的初始化是成功的。

4. 對象清除與垃圾回收

遲點準備兩個例子做爲解析

5. 內部類與匿名類

  1. 內部類( inner class )是在所在類中的特殊成員

  2. 匿名類( anonymous class)是一種特殊的內部類,它沒有類名。

內部類(inner class)

  1. 內部類是所在類的成員。

  2. 編譯器生成xxxx$xxxx這樣的class文件

  3. 內部類不可以與外部類同名

  4. 優勢是:在某些程序中,使用內部類來簡化程序(好比減小source file的個數);內部類能夠引用所在外部類的屬性和方法。

內部類的使用

  • 在封裝它的類的內部使用內部類,與普通類的使用方式相同。

  • 在其餘地方使用類名前要冠之外部類的名字。在用new建立內部類實例時,也要在 new 前面冠以對象變量,能夠用 外部對象名.new 內部類名(參數)

在內部類中使用外部類的成員

  • 內部類中能夠直接訪問外部類的字段及方法。即便private也能夠,由於內部類本質上也是一個類成員。

  • 若是內部類中有與外部類同名的字段或方法,則能夠用 外部類名.this.字段及方法

代碼

class A {
    private int s=3;
    public class B{
        private int s=2;
        public void mb(int s){
            System.out.println(s);
            System.out.println(this.s);
            System.out.println(A.this.s);
        }
    }
}

內部類的修飾符

內部類與類中的字段、方法同樣是外部類的成員,它的前面也能夠有 訪問控制符和其餘修飾符。

  1. 訪問控制符:public , protected,默認及private。 注:外部類只可以使用public修飾或者默認

  2. final , abstract

static 修飾符

  • static修飾的內部類,其實是一種外部類。

  • 由於它與外部類的實例無關

static類的使用

  1. 實例化static類時,在 new前面不須要用對象實例變量;

  2. static類中不能訪問其外部類的非static的字段及方法,既只可以訪問static成員。

  3. static方法中不能訪問非static的域及方法,也不可以不帶前綴地new 一個非
    static的內部類。

局部類

在一個方法中也能夠定義類,這種類稱爲」方法中的內部類」 ,或者叫局部類(local class)

局部類的使用

  1. 同局部變量同樣,方法中的內部類,不可以被publicprivateprotectedstatic 修飾, 但能夠被 final 或者 abstract 修飾。

  2. 能夠訪問其外部類的成員

  3. 不可以訪問該方法的局部變量,除非是final局部變量

匿名類(anonymous class)

匿名類( anonymous class)是一種特殊的內部類

  • 它沒有類名,在定義類的同時就生成該對象的一個實例

  • 「一次性使用」的類

匿名類的使用

  1. 不取名字,直接用其父類或接口的名字。

    • 也就是說,該類是父類的子類,或者實現了一個接口

    • 編譯器生成 xxxxx$1之類的名字,其中1表示第一個匿名類。

  2. 類的定義的同時就建立實例,即類的定義前面有一個new

    • new 類名或接口名(){......}

    • 不使用關鍵詞class,也不使用extendsimplements

  3. 在構造對象時使用父類構造方法

    • 不可以定義構造方法,由於它沒有名字

    • 若是new對象時,要帶參數,則使用父類的構造方法

代碼

import javafx.application.Application;
        import javafx.event.ActionEvent;
        import javafx.event.EventHandler;
        import javafx.geometry.Pos;
        import javafx.scene.Scene;
        import javafx.scene.control.Button;
        import javafx.scene.layout.BorderPane;
        import javafx.scene.layout.HBox;
        import javafx.scene.layout.Pane;
        import javafx.scene.text.Text;
        import javafx.stage.Stage;

public class AnonymousHandlerDemo extends Application {
    @Override // Override the start method in the Application class
    public void start(Stage primaryStage) {
        Text text = new Text(40, 40, "Programming is fun");
        Pane pane = new Pane(text);

        // Hold four buttons in an HBox
        Button btUp = new Button("Up");
        Button btDown = new Button("Down");
        Button btLeft = new Button("Left");
        
        HBox hBox = new HBox(btUp, btDown , btLeft);
        hBox.setSpacing(10);
        hBox.setAlignment(Pos.CENTER);

        BorderPane borderPane = new BorderPane(pane);
        borderPane.setBottom(hBox);

        //在方法中定義的內部類稱爲 局部類(local class)
        //能夠引用方法中的變量, 好比text
        class EnableEventHandler
                implements EventHandler<ActionEvent>{
            public void handle(ActionEvent e) {
                text.setY(text.getY() > 10 ? text.getY() - 5 : 10);
            }
        }
        //注意:必須先定義了類,才能使用.
        //不然編譯器會找不到這個類而報錯.
        btUp.setOnAction(
                new EnableEventHandler());

        //對比上面,非匿名類
        //下面開始使用匿名類
        btDown.setOnAction(new EventHandler<ActionEvent>() {
            @Override // Override the handle method
            public void handle(ActionEvent e) {
                text.setY(text.getY() < pane.getHeight() ?
                        text.getY() + 5 : pane.getHeight());
            }
        });
        
        //可見,使用匿名類的好處是:精簡了先定義類,後使用類這一過程.
        //匿名類的使用是「一次性」的。
        
        //進一步簡化,採用Lambda表達式
        btLeft.setOnAction(e -> {
            //但得遵循SAM原則
            text.setX(text.getX() > 0 ? text.getX() - 5 : 0);
        });
        
        // Create a scene and place it in the stage
        Scene scene = new Scene(borderPane, 400, 350);
        primaryStage.setTitle("AnonymousHandlerDemo"); // Set title
        primaryStage.setScene(scene); // Place the scene in the stage
        primaryStage.show(); // Display the stage
    }
    /**
     * The main method is only needed for the IDE with limited
     * JavaFX support. Not needed for running from the command line.
     */
    public static void main(String[] args) {
        launch(args);
    }
}

運行截圖
圖片描述

6. Lambda 表達式

基本語法

Lambda表達式是從Java8增長的新語法

  • Lambda表達式(λ expression)的基本寫法

    • (參數)->結果

    • 好比:(String s) -> s.length()將會返回s的長度

    • x->x*x將會返回x*x的運算結果,參數的類型都省略了。

  • 大致上至關於其餘語言的「匿名函數」或「函數指針」 。

  • 在Java中它其實是「 匿名類的一個實例」,便是定義後立刻使用,更加簡潔和高效。

代碼

例子一

A dolt = new A(){
    public void run(){
        System.out.println("OK");
    }
}

寫成Lamdba表達式:

() -> { System.out.println(「OK」); }

例子二

A dolt = new A(){
    public double run(double x){
        return Math.sin(x);
    }
}

以上寫成Lambda表達式:

(x) -> Math.sin(x);

例子三

btn.addActionListener( e -> ... } ) );//編譯器可以自動識別e爲ActionEvent類。

以上三個例子說明:Lambda表達式是接口或者說是接口函數的簡寫,基本寫法是 (參數)->結果,這裏,參數是()(1個參數)(多個參數),結果是指 表達式語句{語句}

能寫成Lambda的接口的條件

因爲Lambda只能表示一個函數,因此

  • 能寫成Lambda的接口要求包含且最多隻能有一個抽象函數

  • 這樣的接口能夠用註記(但不強求)@FunctionalInterface來表示。稱爲函數式接口,用於對編譯器的提示:

    @FunctionalInterface
    interface A { double A( double x );}

代碼

Comparator<Person> compareAge = (p1, p2) -> p1.age-p2.age;
Arrays.sort(people, compareAge);

Lambda表達式,不只僅是簡寫了代碼, 更重要的是:它將代碼也當成數據來處理。

7. 裝箱、枚舉、註解

從JDK1.5起,增長了一些新的語法
大部分是編譯器自動翻譯的,稱爲Complier sugar

基本類型的包裝類(wrapper class)

基本類型並非對象,但經過使用Java API的包裝類可以把基本類型包裝成對象,從而方便某些方法的調用需求。

  1. 它將基本類型(primitive type) 包裝成Object(引用類型)

  2. Java的八種包裝類(wrapper class)以下:
    Boolean, Byte, Short, Character, Integer, Long, Float, Double

注:包裝類的名稱首字母亦是大寫。

  • 裝箱(Boxing)

    Integer I = new Integer(10);

    或簡寫爲

    Integer I = 10;//編譯器自動將基本類型包裝爲`Integer`對象
  • 拆箱(Unboxing)

    int i = I;
  • 上述過程,被編譯器譯爲:

Integer I= Integer.valueOf(10);
int i = I.intValue();

枚舉

枚舉(enum)是一種特殊的class類型

  • 在簡單的狀況下,用法與其餘語言的enum類似

    enum Light { Red, Yellow, Green };
    Light light = Light.Red;
  • 但實際上,編譯後,它生成了 class Light extendsjava.lang.Enum,因此能夠在enum定義體中,添加字段、方法、構造方法,能夠當作通常的class,更加靈活。

代碼

enum Direction{
    EAST("東",1), SOUTH("南",2),
    WEST("西",3), NORTH("北",4);
    private Direction(String desc, int num){//容許添加構造方法
    this.desc=desc; this.num=num; 
    }
    private String desc;
    private int num;
    public String getDesc(){ //容許添加通常方法
    return desc; 
    } 
    public int getNum(){ //容許添加通常方法
    return num; 
    }
}

註解(annotation)

又稱爲註記、標記、標註、註釋(不一樣於comments)
是在各類語法要素上加上附加信息,以供編譯器或其餘程序使用
全部的註解都是 java.lang.annotation.Annotation 的子類
經常使用的註解,如

  • @Override

  • @Deprecated 表示過期的方法

  • @SuppressWarnings 表示讓編譯器不產生警告

  • 自定義註解,這個不多見啦。

    public @interface Author {
        String name();
    }

8. 沒有指針的 JAVA 語言

引用(reference)實質就是指針(pointer),但在Java中,引用是安全的指針

Java標榜其中對C/C++一個很大的改進就是:Java對程序員屏蔽了變量地址的概念,減小指針誤用。

好比:

  • 會檢查空指引

  • 沒有指針運算 *(p+5)

  • 不能訪問沒有引用到的內存

  • 自動回收垃圾

C語言指針在Java中的體現

1. 傳地址 -> 對象

Java引用類型(reference type),引用自己就至關於指針,能夠用來修改對象的屬性、調用對象的方法。
如交換兩個整數,在C語言中:

void swap(int x, int y){ 
    int t=x; 
    x=y;
    y=t; 
} 
int a=8, b=9; 
swap(&a,&b);

在Java中,沒法實現該交換。但可使用一種變通的辦法,傳出一個有兩個份量x,y的對象。
變通辦法,但顯得很笨:

class Test{
    public static void swap2(final int [] arr, final int pos1, final int pos2){
        final int temp = arr[pos1];
        arr[pos1] = arr[pos2];
        arr[pos2] = temp;
    }
    public static void main(String [] args){
        int [] a ={1,2};
        swap2(a,0,1);
        System.out.println(a[0]+" "+a[1]);
    }
}

運行結果:

2 1

2. 指針運算 -> 數組

在C中的指針*(p+5) ,在Java中則能夠用 args[5]

3. 函數指針 -> 接口、Lambda表達式

例如上述:求積分、線程、回調函數、事件處理。

4. 指向結點的指針 -> 對象的引用

在鏈表中有該類:

class Node {
    Object data;
    Node next; 
}

next就是一個指向對象的指針。

5. 使用JNI

Java Native Interface(JNI) ,它容許Java代碼和其餘語言寫的代碼進行交互。

相關文章
相關標籤/搜索