国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Java常用知識點

2019-11-06 09:16:10
字體:
來源:轉載
供稿:網友

類的繼承

java只支持單繼承,不允許多重繼承 - 一個子類只能有一個父類 - 一個父類可以派生出多個子類 這里寫圖片描述 子類繼承了父類,就繼承了父類的方法和屬性。 在子類中,可以使用父類中定義的方法和屬性,也可以創建新的數據和方法。 因而,子類通常比父類的功能更多。 在Java 中,繼承的關鍵字用的是“extends”,即子類不是父類的子集,而是對父類的“擴展”。

關于繼承的規則: 子類不能繼承父類中私有的(PRivate)的成員變量和方法。

訪問控制

可以對Java類中定義的屬性和方法進行訪問控制—-規定不同的保護等級: public、protected、default、private 這里寫圖片描述

訪問控制舉例

class Parent{        private int f1 = 1;        int f2 = 2; //default        protected  int f3 = 3;        public  int f4 = 4;        private  void  fm1() {        System.out.println("in fm1() f1=" + f1);        }        void fm2() { //default        System.out.println("in fm2() f2=" + f2);        }        protected  void  fm3() {        System.out.println("in fm3() f3=" + f3);        }        public void fm4() {        System.out.println("in fm4() f4=" + f4);        }   }//設父類和子類在同一個包內class Child extends Parent{    private int c1 = 21;    public  int c2 = 22;         private void cm1(){    System.out.println("in cm1() c1=" + c1);    }    public  void cm2(){    System.out.println("in cm2() c2=" + c2);    }    public static void main(String args[]){        int i;         Parent  p = new Parent();               i = p.f2;           //  i = p.f3;i = p.f4;                      p.fm2();         // p.fm3();p.fm4();                Child  c = new Child();        i = c.f2;           //  i = c.f3;i = c.f4;              i = c.c1;           //  i = c.c2;               c.cm1();  // c.cm2(); c.fm2(); c.fm3();c.fm4()    }}12345678910111213141516171819202122232425262728293031323334353637383940411234567891011121314151617181920212223242526272829303132333435363738394041

訪問控制分析

父類Parent和子類Child在同一包中定義時: 這里寫圖片描述

覆蓋方法

在子類中可以根據需要對從父類中繼承來的方法進行改造—覆蓋方法(方法的重置、重寫),在程序執行時,子類的方法將覆蓋父類的方法。 覆蓋方法必須和被覆蓋方法具有相同的方法名稱、參數列表和返回值類型。 覆蓋方法不能使用比被覆蓋方法更嚴格的訪問權限。

覆蓋方法舉例

public class Person {     public String name;     public int age;    public String getInfo() {              return "Name: "+ name + "/n" +"age: "+ age;    }    } public class Student extends Person {                public String school;    public String getInfo() {   //覆蓋方法              return  "Name: "+ name + "/nage: "+ age               + "/nschool: "+ school;    }    public static void main(String args[]){        Student s1=new Student();        s1.name="Bob";        s1.age=20;        s1.school="school2";        System.out.println(s1.getInfo());   //Name:Bob  age:20  school:school2    }    }123456789101112131415161718192021123456789101112131415161718192021

說明: Person p1=new Person(); p1.getInfo(); //調用Person類的getInfo()方法 Student s1=new Student(); s1.getInfo(); //調用Student類的getInfo()方法 這是一種“多態性”:同名的方法,用不同的對象來區分調用的是哪一個方法。

覆蓋方法舉例

class Parent {    public void method1() {}}class Child extends Parent {    private void method1() {}  //非法,子類中的method1()的訪問權限private比被覆蓋方法的訪問權限public弱}public class UseBoth {    public void doOtherThing() {        Parent p1 = new Parent();        Child p2 = new Child();        p1.method1();        p2.method1();    }}12345678910111213141516171234567891011121314151617

關鍵字super

在Java類中使用super來引用父類的成分 super可用于訪問父類中定義的屬性 super可用于調用父類中定義的成員方法 super可用于在子類構造方法中調用父類的構造方法 super的追溯不僅限于直接父類

super舉例

  public class Person {    private String name;        private int age;    public String getInfo() {                       return "Name: " + name + "/nage: " + age;        }   }   public class Student extends Person {        private String school = "New Oriental";    public String getSchool(){ return school; }        public String getInfo() {        // 調用父類的方法        return super.getInfo() +"/nschool: " +school;        }   }1234567891011121314151612345678910111213141516

構造方法不能繼承

子類繼承父類所有的成員變量和成員方法,但不繼承父類的構造方法 在一個Java類中可以通過兩種方式獲得構造方法 使用系統默認的無參數構造方法 顯式定義一個或多個構造方法 一旦顯式定義了構造方法,則系統不再提供默認構造方法

調用父類構造方法

注意 注意 注意 重要的事說三遍:↓↓↓

在子類的構造方法中可使用super(參數列表)語句調用父類的構造方法 如果子類的構造方法中沒有顯示地調用父類構造方法,也沒有使用this關鍵字調用重載的其它構造方法,則系統默認調用父類無參數的構造方法 如果子類構造方法中既未顯式調用父類構造方法,而父類中又沒有無參的構造方法,則編譯出錯

調用父類構造方法舉例

      public class Person {    private String name;    private int age;    private Date birthDate;     public Person(String name, int age, Date d) {            this.name = name;            this.age = age;            this.birthDate = d;                }    public Person(String name, int age) {            this(name, age, null);    }    public Person(String name, Date d) {            this(name, 30, d);     }    public Person(String name) {            this(name, 30);    }    // ……   }   public class Student extends Person {    private String school;    public Student(String name, int age, String s) {             super(name, age);             school = s;    }    public Student(String name, String s) {              super(name);              school = s;    }         public Student(String s) {              /**             * 編譯出錯: no super(),             * 系統將調用父類無參數的構造方法。             * */              school = s;    }     }123456789101112131415161718192021222324252627282930313233343536373839404142123456789101112131415161718192021222324252627282930313233343536373839404142

子類對象的實例化過程

這里寫圖片描述

思考

1).為什么super(…)和this(…)調用語句不能同時在一個構造函數中出現? 2).為什么super(…)或this(…)調用語句只能作為構造函數中的第一句出現? 看個例子解答以上兩個問題。

this() super()是你如果想用傳入當前構造器中的參數或者構造器中的數據調用其他構造器或者控制父類構造器時使用的,在一個構造器中你只能使用this()或者super()之中的一個,而且調用的位置只能在構造器的第一行,在子類中如果你希望調用父類的構造器來初始化父類的部分,那就用合適的參數來調用super(),如果你用沒有參數的super()來調用父類的構造器(同時也沒有使用this()來調用其他構造器),父類缺省的構造器會被調用,如果父類沒有缺省的構造器,那編譯器就會報一個錯誤。 假如我們允許把this和super放置到任何位置。那么請看下面代碼:

class A{     A()   {        System.out.println("You call super class non-args constructor!");   }}class B extends A{      B()      {        //這里,編譯器將自動加上 super();        System.out.println("You call subclass constructor!");      }       B(String n)       {         super();         this();//實際就是調用了B(){...},而在B(){...}中編譯器自動加上了         //super();這樣就相當于兩次調用了super();也就是說對父類進//行了兩次初始化。而在實例化一個對象時,一個構造方法只能調用一次,這說明this和super不能同時存在一個構造方法中。同時因為系統沒有在第一行發現this()或super()調用,就會自動加上super(),如果沒有將this()和super()放在第一行就會產生矛盾。        //因為總有一個super()在第二句上。所以該程序不能通過編譯!!!         }12345678910111213141516171819202122232425261234567891011121314151617181920212223242526

結論

也就是說你必須在構造器的第一行放置super或者this構造器,否則編譯器會自動地放一個空參數的super構造器的,其他的構造器也可以調用super或者this,調用成一個遞歸構造鏈,最后的結果是父類的構造器(可能有多級父類構造器)始終在子類的構造器之前執行,遞歸的調用父類構造器。無法執行當前的類的構造器。也就不能實例化任何對象,這個類就成為一個無為類。 從另外一面說,子類是從父類繼承而來,繼承了父類的屬性和方法,如果在子類中先不完成父類的成員的初始化,則子類無法使用,應為在java中不允許調用沒初始化的成員。在構造器中是順序執行的,也就是說必須在第一行進行父類的初始化。而super能直接完成這個功能。This()通過調用本類中的其他構造器也能完成這個功能。 因此,this()或者super()必須放在第一行。

多態性

多態—在Java中,子類的對象可以替代父類的對象使用 一個變量只能有一種確定的數據類型 一個引用類型變量可能指向(引用)多種不同類型的對象

    Person p = new Student();    Object o = new Person();//Object類型的變量o,指向Person類型的對象    o = new Student(); //Object類型的變量o,指向Student類型的對象//父類類型的變量可以指向子類的對象1234512345

一個引用類型變量如果聲明為父類的類型,但實際引用的是子類對象,那么該變量就不能再訪問子類中添加的屬性和方法

    Student m = new Student();    m.school = “pku”;   //合法,Student類有school成員變量    Person e = new Student();     e.school = “pku”;   //非法,Person類沒有school成員變量12341234

屬性是在編譯時確定的,編譯時e為Person類型,沒有school成員變量, 因而編譯錯誤。

虛擬方法調用(Virtual Method Invocation)

//  正常的方法調用    Person e = new Person();    e.getInfo();    Student e = new Student();    e.getInfo();//  虛擬方法調用(多態情況下)    Person e = new Student();    e.getInfo();    //調用Student類的getInfo()方法1234567812345678

編譯時類型和運行時類型 編譯時e為Person類型,而方法的調用是在運行時確定的,所以調用的是Student類的getInfo()方法。—— 動態綁定

多態性應用舉例

方法聲明的形參類型為父類類型,可以使用子類的對象作為實參調用該方法

public class Test{     public void method(Person e) {               //……               e.getInfo();    }    public static  void main(Stirng args[]){               Test t = new Test();                Student m = new Student();                t.method(m); //子類的對象m傳送給父類類型的參數e    }}12345678910111234567891011

instanceof 操作符

x instanceof A:檢驗x是否為類A的對象,返回值為boolean型。 要求x所屬的類與類A必須是子類和父類的關系,否則編譯錯誤。 如果x屬于類A的子類B,x instanceof A值也為true。

public class Person extends Object {…}public class Student extends Person {…}public class Graduate extends Person {…}public void method1(Person e) {    if (e instanceof Person)         // 處理Person類及其子類對象    if (e instanceof Student)         //處理Student類及其子類對象    if (e instanceof Graduate)        //處理Graduate類及其子類對象}123456789101112123456789101112

對象類型轉換 (Casting )

基本數據類型的Casting: 小的數據類型可以自動轉換成大的數據類型 如long g=20; double d=12.0f 可以把大的數據類型強制轉換(casting)成小的數據類型 如 floate f=(float)12.0 int a=(int)1200L 對Java對象的強制類型轉換稱為造型 從子類到父類的類型轉換可以自動進行 從父類到子類的類型轉換必須通過造型(強制類型轉換)實現 無繼承關系的引用類型間的轉換是非法的 在造型前可以使用instanceof操作符測試一個對象的類型

對象類型轉換舉例

public class Test{                public void method(Person e) {    //設Person類中沒有getschool()方法              System.out.pritnln(e.getschool());   //非法,編譯時錯誤              if(e  instanceof  Student){                  Student me = (Student)e;  //將e強制轉換為Student類型                  System.out.pritnln(me.getschool());              }                    }               public static  void main(Stirng args[]){                  Test t = new Test();                   Student m = new Student();                   t.method(m);               }}1234567891011121314151612345678910111213141516

Object 類

Object類是所有Java類的根父類 如果在類的聲明中未使用extends關鍵字指明其父類,則默認父類為Object類

    public class Person {    ...    }    等價于:public class Person extends Object {...}12345671234567

例:

method(Object obj){…}//可以接收任何類作為其參數Object o=new Person;  method(o);123123

==操作符與equals方法

==操作符與equals方法的區別:

==:引用類型比較引用(是否指向同一個對象);

    Person p1=new Person();       Person p2=new Person();    if (p1==p2){…}123123

基本類型比較值; int a=5; if(a==6){…} 用”==”進行比較時,符號兩邊的數據類型必須一致(可自動轉換的基本數據類型除外),否則編譯出錯; equals()方法是Object類的方法,由于所有類都繼承Object類,也就繼承了equals()方法。只能比較引用類型,其作用與“==”相同,比較是否指向同一個對象。格式:obj1.equals(obj2) 特例:當用equals()方法進行比較時,對類File、String、Date及封裝類(Wrapper Class)來說,是比較類型及內容而不考慮引用的是否是同一個對象; 原因:在這些類中覆蓋了equals()方法。

toString 方法

toString()方法在Object類中定義,其返回值是String類型,返回類名和它的引用地址。 在進行String與其它類型數據的連接操作時,自動調用toString()方法

    Date now=new Date();    System.out.println(“now=”+now);      // 相當于                  System.out.println(“now=”+now.toString());    //now=Date@1223451234512345

可以根據需要在用戶自定義類型中重寫toString()方法 如String 類重寫了toString()方法,返回字符串的值。

    s1="hello";    System.out.println(s1);    //相當于    System.out.println(s1.toString());12341234

在ToString1.java中的類A里覆蓋toString方法,使其輸出類A對象的cint屬性值。 基本類型數據轉換為String類型時,調用了對應封裝類的 toString()方法int a=10; System.out.println(“a=”+a);

封裝類

針對八種基本定義相應的引用類型—封裝類 這里寫圖片描述

關鍵字static

描述

在Java類中聲明變量、方法和內部類時,可使用關鍵字static做為修飾符。 static標記的變量或方法由整個類(所有實例)共享,如訪問控制權限允許,可不必創建該類對象而直接用類名加‘.’調用。 static成員也稱類成員或靜態成員,如:類變量、類方法、靜態方法等。

類變量(class Variable)

類變量(類屬性)由該類的所有實例共享 這里寫圖片描述

public class Person {       private int id;       //類屬性類似于全局變量       public static int total = 0;       public Person() {    total++;    id = total;       } }123456789123456789

類屬性應用舉例

class Person {           private int id;           public static int total = 0;           public Person() {             total++;             id = total;           }           public static void main(String args[]){            Person Tom=new Person()    Tom.id=0;    total=100; // 不用創建對象就可以訪問靜態成員         } }public class OtherClass {            public static void main(String args[]) {             Person.total = 100;  // 不用創建對象就可以訪問靜態成員                           //訪問方式:類名.類屬性類名.類方法             System.out.println(Person.total);             Person c = new Person();              System.out.println(c.total);   //輸出101            }}12345678910111213141516171819202122231234567891011121314151617181920212223

類方法(class Method)

描述

在靜態方法里只能直接調用同類中其它的靜態成員(包括變量和方法),而不能直接訪問類中的非靜態成員。這是因為,對于非靜態的方法和變量,需要先創建類的實例對象后才可使用,而靜態方法在使用前不用創建任何對象。 靜態方法不能以任何方式引用this和super關鍵字。與上面的道理一樣,因為靜態方法在使用前不用創建任何實例對象,當靜態方法被調用時,this所引用的對象根本就沒有產生。 main() 方法是靜態的,因此JVM在執行main方法時不創建main方法所在的類的實例對象,因而在main()方法中,我們不能直接訪問該類中的非靜態成員,必須創建該類的一個實例對象后,才能通過這個對象去訪問類中的非靜態成員,這種情況,我們在以后的例子中會多次碰到。

類名.方法名()

沒有對象的實例時,可以用類名.方法名()的形式訪問由static標記的類方法。

class Person {       private int id;       private static int total = 0;       public static int getTotalPerson() {     return total;       }       public Person() {            total++;    id = total;       }}public class TestPerson {        public static void main(String[] args) {    System.out.println("Number of total is " +Person.getTotalPerson());    //沒有創建對象也可以訪問靜態方法    Person p1 = new Person();        System.out.println( "Number of total is "+ Person.getTotalPerson());        }}12345678910111213141516171819201234567891011121314151617181920

訪問類的static屬性

在static方法內部只能訪問類的static屬性,不能訪問類的非static屬性。

class Person {       private int id;       private static int total = 0;       public static int getTotalPerson() {     id++;   //非法    return total;       }       public Person() {            total++;    id = total;       }}123456789101112123456789101112

訪問static方法注意

因為不需要實例就可以訪問static方法,因此static方法內部不能有this,(也不能有super )

class Person {       private int id;       private static int total = 0;       public static void setTotalPerson(int total){        this.total=total;    //非法,在static方法中不能有this,也不能有super       }      public Person() {            total++;    id = total;       }}public class TestPerson {        public static void main(String[] args) {    Person.setTotalPerson();        }}1234567891011121314151612345678910111213141516

類屬性、類方法的設計思想

類屬性作為該類各個對象之間共享的變量。在設計類時,分析哪些類屬性不因對象的不同而改變,將這些屬性設置為類屬性。相應的方法設置為類方法。 如果方法與調用者無關,則這樣的方法通常被聲明為類方法,由于不需要創建對象就可以調用類方法,從而簡化了方法的調用

靜態初始化

一個類中可以使用不包含在任何方法體中的靜態代碼塊(static block ),當類被載入時,靜態代碼塊被執行,且只被執行一次,靜態塊經常用來進行類屬性的初始化。 static塊通常用于初始化static (類)屬性

class Person {    public static int total;    static {            total = 100;//為total賦初值     }    …… //其它屬性或方法聲明 }12345671234567

靜態初始化舉例

class Person {     public static int total;     static {        total = 100;        System.out.println("in static block!");     }}public class Test {    public static void main(String[] args) {        System.out.println("total = "+ Person.total);        System.out.println("total = "+ Person.total);    }}//輸出://in static block//total=100//total=1001234567891011121314151617181912345678910111213141516171819

單子 Singleton 設計模板(單例,單態)

描述

設計模式是在大量的實踐中總結和理論化之后優選的代碼結構、編程風格、以及解決問題的思考方式。設計模式就想是經典的棋譜,不同的棋局,我們用不同的棋譜,免得我們自己再去思考和摸索。 所謂類的單態設計模式,就是采取一定的方法保證在整個的軟件系統中,對某個類只能存在一個對象實例,并且該類只提供一個取得其對象實例的方法。如果我們要讓類在一個虛擬機中只能產生一個對象,我們首先必須將類的構造方法的訪問權限設置為private,這樣,就不能用new 操作符在類的外部產生類的對象了,但在類內部仍可以產生該類的對象。因為在類的外部開始還無法得到類的對象,只能調用該類的某個靜態方法以返回類內部創建的對象,靜態方法只能訪問類中的靜態成員變量,所以,指向類內部產生的該類對象的變量也必須定義成靜態的。

單子Singleton 設計模板

class Single{    private static Single onlyone = new Single();//私有的,只能在類的內部訪問    private String name;    public static Single getSingle() {   //getSingle()為static,不用創建對象                                  //即可訪問        return onlyone;    }    private Single() {}     //private的構造器,不能在類的外部創建該類的對象    }public class TestSingle{    public static void main(String args[]) {            Single  s1 = Single.getSingle();      //訪問靜態方法    Single  s2 = Single.getSingle();    if (s1==s2){                System.out.println("s1 is equals to s2!");                 }    }}1234567891011121314151617181912345678910111213141516171819

理解main方法的語法

描述

由于java虛擬機需要調用類的main()方法,所以該方法的訪問權限必須是public,又因為java虛擬機在執行main()方法時不必創建對象,所以該方法必須是static的,該方法接收一個String類型的數組參數,該數組中保存執行java命令時傳遞給所運行的類的參數。 這里寫圖片描述

命令行參數用法舉例

   public class CommandPara {       public static void main(String[] args) {    for ( int i = 0; i < args.length; i++ ) {               System.out.println("args[" + i + "] = " + args[i]);    }       }   }//運行程序CommandPara.javajava CommandPara  lisa  "bily"  "Mr Brown"//輸出結果:args[0] = lisaargs[1] = bilyargs[2] = Mr Brown123456789101112131415123456789101112131415

關鍵字 final

描述

在Java中聲明類、屬性和方法時,可使用關鍵字final來修飾。 final標記的變量(成員變量或局部變量)即成為常量,只能賦值一次。 final標記的類不能被繼承。提高安全性,提高程序的可讀性。 final標記的方法不能被子類重寫。增加安全性。 final標記的成員變量必須在聲明的同時或在每個構造方法中顯式賦值,然后才能使用。 final PI=3.14;

關鍵字final應用舉例

public final class Test{        public static int totalNumber= 5 ;        public final int id;        public Test(){            id = ++totalNumber;//只能在構造方法中給final變量賦值        }        public static void main(String[] args) {            Test t = new Test();            System.out.println(t.id);                   final int i = 10;            final int j;            j = 20;            j = 30;                        //非法            }}123456789101112131415123456789101112131415

抽象類(abstract class)

描述

隨著繼承層次中一個個新子類的定義,類變得越來越具體,而父類則更一般,更通用。類的設計應該保證父類和子類能夠共享特征。有時將一個父類設計得非常抽象,以至于它沒有具體的實例,這樣的類叫做抽象類。 用abstract關鍵字來修飾一個類時,這個類叫做抽象類;用abstract來修飾一個方法時,該方法叫做抽象方法。 抽象方法:只有方法的聲明,沒有方法的實現。以分號結束。 abstract int abstractMethod1( int a ); 含有抽象方法的類必須被聲明為抽象類。 抽象類不能被實例化。抽象類是用來被繼承的,抽象類的子類必須重寫父類的抽象方法,并提供方法體。 不能用abstract修飾私有方法,構造方法,靜態方法。

抽象類舉例

abstract class A{          abstract void m1( );       public void m2( ){        System.out.println("A類中定義的m2方法");       }}class B extends A{       void m1( ){        System.out.println("B類中定義的m1方法");       }}public class Test{       public static void main( String args[ ] ){        A c = new B( );        c.m1( );        c.m2( );       }}12345678910111213141516171819201234567891011121314151617181920

抽象類應用

抽象類是用來模型化那些父類無法確定全部實現,而是由其子類提供具體實現的對象的類。 這里寫圖片描述 在航運公司系統中,Vehicle類需要定義兩個方法分別計算運輸工具的燃料效率和行駛距離。

練習:

卡車(Truck)和駁船(RiverBarge)的燃料效率和行駛距離的計算方法完全不同。Vehicle類不能提供計算方法,但子類可以。 解決方案

    Java允許類設計者指定:超類聲明一個方法但不提供實現,該方法的實現由子類提供。這樣的方法稱為抽象方法。有一個或更多抽象方法的類稱為抽象類。Vehicle是一個抽象類,有兩個抽象方法。public abstract class Vehicle{    public abstract double calcFuelEfficiency();    //計算燃料效率的抽象方法    public abstract double calcTripDistance();  //計算行駛距離的抽象方法}public class Truck extends Vehicle{    public double calcFuelEfficiency( )   { //寫出計算卡車的燃料效率的具體方法   }    public double calcTripDistance( )    {  //寫出計算卡車行駛距離的具體方法   }}public class RiverBarge extends Vehicle{     public double calcFuelEfficiency( ) { //寫出計算駁船的燃料效率的具體方法  }     public double calcTripDistance( )  {  //寫出計算駁船行駛距離的具體方法}}123456789101112131415123456789101112131415

接 口

描述

有時必須從幾個類中派生出一個子類,繼承它們所有的屬性和方法。但是,Java不支持多重繼承。有了接口,就可以得到多重繼承的效果。 接口(interface)是抽象方法和常量值的定義的集合。 從本質上講,接口是一種特殊的抽象類,這種抽象類中只包含常量和方法的定義,而沒有變量和方法的實現。 接口定義舉例

public interface Runner {        int id = 1;        public void start();        public void run();        public void stop();}12345671234567

接口的特點:

用 interface 來定義。 接口中的所有成員變量都默認是由public static final修飾的。 接口中的所有方法都默認是由public abstract修飾的。接口沒有構造方法。 實現接口的類中必須提供接口中所有方法的具體實現內容。 多個無關的類可以實現同一個接口 一個類可以實現多個無關的接口 與繼承關系類似,接口與實現類之間存在多態性 接口也可以繼承另一個接口,使用extends關鍵字。 實現接口的類中必須提供接口中所有方法的具體實現內容。 多個無關的類可以實現同一個接口 一個類可以實現多個無關的接口 與繼承關系類似,接口與實現類之間存在多態性

定義Java類的語法格式:

    < modifier> class < name> [extends < superclass>]    [implements < interface> [,< interface>]* ] {        < declarations>*    }12341234

接口應用舉例

public interface Runner {    public void start();    public void run();    public void stop();}public class Person implements Runner {    public void start() {        // 準備工作:彎腰、蹬腿、咬牙、瞪眼         // 開跑    }    public void run() {        // 擺動手臂        // 維持直線方向    }    public void stop() {        // 減速直至停止、喝水。    }}12345678910111213141516171234567891011121314151617

這里寫圖片描述

一個類可以實現多個無關的接口

interface Runner { public void run();}interface Swimmer {public double swim();}class Animal  {public int eat(){…}} class Person extends Animal implements Runner,Swimmer{        public void run() {……}        public double swim()  {……}        public int eat() {……}}1234567812345678

與繼承關系類似,接口與實現類之間存在多態性

public class Test{    public static void main(String args[]){        Test t = new Test();        Person p = new Person();        t.m1(p);        t.m2(p);        t.m3(p);    }    public String m1(Runner f) { f.run(); }    public void  m2(Swimmer s) {s.swim();}    public void  m3(Animal a) {a.eat();}}123456789101112123456789101112

注意

如果實現接口的類中沒有實現接口中的全部方法,必須將此類定義為抽象類。 接口也可以繼承另一個接口,使用extends關鍵字。

interface MyInterface    {        String s=“MyInterface”;        public void absM1();    }    interface SubInterface extends MyInterface    {        public void absM2();    }    public class SubAdapter implements SubInterface    {        public void absM1(){System.out.println(“absM1”);}        public void absM2(){System.out.println(“absM2”);}    }12345678910111213141234567891011121314

實現類SubAdapter必須給出接口SubInterface以及父接口MyInterface中所有方法的實現。

接口和繼承的區別

記住一句話: 接口是“有 沒 有”的關系,繼承是“是 不 是”的關系。

內部類

描述

在Java中,允許一個類的定義位于另一個類的內部,前者稱為內部類 內部類和外層封裝它的類之間存在邏輯上的所屬關系 Inner class一般用在定義它的類或語句塊之內,在外部引用它時必須給出完整的名稱。 Inner class的名字不能與包含它的類名相同; Inner class可以使用包含它的類的靜態和實例成員變量,也可以使用它所在方法的局部變量;

內部類舉例

      class A {    private int s;    public class B{            public void mb() {        s = 100;             System.out.println("在內部類B中s=" + s);            }    }    public void ma() {        B i = new B();        i.mb();    }      }       public class Test {      public static void main(String args[]){            A o = new A();            o.ma();    }      } 12345678910111213141516171819201234567891011121314151617181920
public class A{        private int s = 111;        public class B {    private int s = 222;    public void mb(int s) {            System.out.println(s);              // 局部變量s            System.out.println(this.s);      // 內部類對象的屬性s            System.out.println(A.this.s);   // 外層類對象屬性s    }       }       public static void main(String args[]){    A a = new A();    A.B b = a.new B();    b.mb(333);         }}1234567891011121314151612345678910111213141516

內部類特性

Inner class可以聲明為抽象類 ,因此可以被其它的內部類繼承。也可以聲明為final的。 和外層類不同,Inner class可以聲明為private或protected; Inner class 可以聲明為static的,但此時就不能再使用外層封裝類的非static的成員變量; 非static的內部類中的成員不能聲明為static的,只有在頂層類或static的內部類中才可聲明static成員;

泛型

描述

下面是那種典型用法:

List myIntList = new ArrayList();// 1 myIntList.add(new Integer(0));// 2Integer x = (Integer) myIntList.iterator().next();// 3123123

第 3 行的類型轉換有些煩人。通常情況下,程序員知道一個特定的 list 里邊放的是什么類型的數據。但是,這個類型轉換是必須的(essential)。編譯器只能保證 iterator 返回的是 Object 類型。為了保證對 Integer 類型變量 賦值的類型安全,必須進行類型轉換(這是關鍵)。 當然,這個類型轉換不僅僅帶來了混亂,它還可能產生一個運行時錯誤 (run time error),因為程序員可能會犯錯。 程序員如何才能明確表示他們的意圖,把一個 list(集合) 中的內容限制 為一個特定的數據類型(這是關鍵)呢?這就是 generics 背后的核心思想。這是上面程 序片斷的一個泛型版本:

List<Integer> myIntList = new ArrayList<Integer>(); //1 myIntList.add(new Integer(0)); // 2Integer x = myIntList.iterator().next(); // 3123123

注意變量 myIntList 的類型聲明。它指定這不是一個任意的 List,而是 一個 Integer 的 List,寫作:List<Integer>。我們說 List 是一個帶一個類型 參數的泛型接口(這是關鍵)(a generic interface that takes a type parameter),本 例中,類型參數是 Integer。我們在創建這個 List 對象的時候也指定了一個 類型參數。 另一個需要注意的是第 3 行沒了類型轉換。 現在,你可能認為我們已經成功地去掉了程序里的混亂。我們用第 1 行的類型參數取代了第 3 行的類型轉換。然而,這里還有個很大的不同。 編譯器現在能夠在編譯時檢查程序的正確性。當我們說 myIntList 被聲明為 List<Integer>類型,這告訴我們無論何時何地使用 myIntList 變量,編譯器 保證其中的元素的正確的類型。 實際結果是,這可以增加可讀性和穩定性(robustness),尤其在大型的 程序中。

定義簡單的泛型

下面是從 java.util 包中的 List 接口和 Iterator 接口的定義中摘錄的片斷:

public interface List<E> {void add(E x); Iterator<E> iterator();}public interface Iterator<E> {E next();boolean hasNext();}1234567812345678

這些都應該是很熟悉的,除了尖括號中的部分,那是接口 List 和 Iterat or 中的形式類型參數的聲明(the declarations of the formal type param eters of the interfaces List and Iterator)。 類型參數在整個類的聲明中可用,幾乎是所有可以使用其他普通類型的 地方 在介紹那一節我們看到了對泛型類型聲明 List (the generic type decl aration List) 的調用,如 List<Integer>。在這個調用中(通常稱作一個參數 化類型 a parameterized type),所有出現的形式類型參數(formal type pa rameter,這里是 E)都被替換成實體類型參數(actual type argument)(這里 是 Integer)。 你可能想象,List<Integer>代表一個 E 被全部替換成 Integer 的版本:

public interface IntegerList {   void add(Integer x)   Iterator<Integer> iterator();}12341234

類型參數就跟在方法或構造函數中普通的參數一樣。就像一個方法有形 式參數(formal value parameters)來描述它操作的參數的種類一樣,一個 泛型聲明也有形式類型參數(formal type parameters)。當一個方法被調用, 實參(actual arguments)替換形參,方法體被執行。當一個泛型聲明被調用, 實際類型參數(actual type arguments)取代形式類型參數。 一個命名的習慣:推薦用簡練的名字作為形式類型參數的名字(如果可 能,單個字符)。最好避免小寫字母

泛型和子類繼承

讓我們測試一下我們對泛型的理解。下面的代碼片斷合法么?

List<String> ls = new ArrayList<String>(); //1List<Object> lo = ls; //21212

第 1 行當然合法,但是這個問題的狡猾之處在于第 2 行。 這產生一個問題: 一個 String 的 List 是一個 Object 的 List 么? 大多數人的直覺是回答: “當然!”。 好,在看下面的幾行

lo.add(new Object()); // 3String s = ls.get(0); // 4: 試圖把 Object 賦值給 String1212

這里,我們使用 lo 指向 ls。我們通過 lo 來訪問 ls,一個 String 的 list。 我們可以插入任意對象進去。結果是 ls 中保存的不再是 String。當我們試 圖從中取出元素的時候,會得到意外的結果。 java 編譯器當然會阻止這種情況的發生。第 2 行會導致一個編譯錯誤。 總之,如果 Foo 是 Bar 的一個子類型(子類或者子接口),而 G 是某種 泛型聲明,那么 G<Foo>是 G<Bar>的子類型并不成立!! 為了處理這種情況,考慮一些更靈活的泛型類型很有用。到現在為止我 們看到的規則限制比較大。

通配符(Wildcards)

考慮寫一個例程來打印一個集合(Collection)中的所有元素。下面是在老 的語言中你可能寫的代碼:

void printCollection(Collection c) { Iterator i = c.iterator();for (int k = 0; k < c.size(); k++) {  System.out.println(i.next()); }}123456123456

下面是一個使用泛型的幼稚的嘗試(使用了新的循環語法):

void printCollection(Collection<Object> c) { for (Object e : c) {  System.out.println(e); }}1234512345

問題是新版本的用處比老版本小多了。老版本的代碼可以使用任何類型 的 Collection 作為參數,而新版本則只能使用 Collection<Object>,我們剛 才闡述了,它不是所有類型的 collections 的父類。 那么什么是各種 collections 的父類呢?它寫作: Collection

void printCollection(Collection<?> c) {for (Object e : c) { System.out.println(e);}}1234512345

現在,我們可以使用任何類型的 collection 來調用它。注意,我們仍然 可以讀取 c 中的元素,其類型是 Object。這永遠是安全的,因為不管 colle ction 的真實類型是什么,它包含的都是 Object。 但是將任意元素加入到其中不是類型安全的:

Collection<?> c = new ArrayList<String>();c.add(new Object()); // 編譯時錯誤1212

因為我們不知道 c 的元素類型,我們不能向其中添加對象。 add 方法有類型參數 E 作為集合的元素類型。我們傳給 add 的任何參 數都必須是一個未知類型的子類。因為我們不知道那是什么類型,所以我 們無法傳任何東西進去。唯一的例外是 null,它是所有類型的成員。 另一方面,我們可以調用 get()方法并使用其返回值。返回值是一個未 知的類型,但是我們知道,它總是一個 Object

有限制的通配符(Bounded Wildcards)

考慮一個簡單的畫圖程序,它可以用來畫各種形狀,比如矩形和圓形。 為了在程序中表示這些形狀,你可以定義下面的類繼承結構:

public abstract class Shape {public abstract void draw(Canvas c);private int x, y, radius;public void draw(Canvas c) { // ... }}public class Circle extends Shape {}public class Rectangle extends Shape {private int x, y, width, height; public void draw(Canvas c) {// ... }}12345678910111234567891011

這些類可以在一個畫布(Canvas)上被畫出來:

public class Canvas { public void draw(Shape s) {  s.draw(this); }}1234512345

所有的圖形通常都有很多個形狀。假定它們用一個 list 來表示,Canva s 里有一個方法來畫出所有的形狀會比較方便:

public void drawAll(List<Shape> shapes) { for (Shape s : shapes) {s.draw(this); }}1234512345

現在,類型規則導致 drawAll()只能使用 Shape 的 list 來調用。它不能, 比如說對List<Circle>來調用。這很不幸,因為這個方法所作的只是從這個 list 讀取 shape,因此它應該也能對List<Circle>調用。我們真正要的是這 個方法能夠接受一個任意種類的 Shape: public void drawAll(List<? extends Shape> shapes) { //..} 這里有一處很小但是很重要的不同:我們把類型List<Shape>替換成 了List<? extends Shape>。現在 drawAll()可以接受任何 Shape 的子類的 List,所以我們可以對List<Circle>進行調用。 List<? extends Shape>是有限制通配符的一個例子。這里?代表一個 未知的類型,就像我們前面看到的通配符一樣。但是,在這里,我們知道 這個未知的類型實際上是 Shape 的一個子類。我們說 Shape 是這個通配符 的上限(upper bound)。 像平常一樣,要得到使用通配符的靈活性有些代價。這個代價是,現在 向 shapes 中寫入是非法的。比如下面的代碼是不允許的:

public void addRectangle(List<? extends Shape> shapes) { shapes.add(0, new Rectangle()); // compile-time error!}123123

你應該能夠指出為什么上面的代碼是不允許的。因為 shapes.add 的第 二個參數類型是 ? extends Shape ——一個 Shape 未知的子類。因此我 們不知道這個類型是什么,我們不知道它是不是 Rectangle 的父類;它可 能是也可能不是一個父類,所以這里傳遞一個 Rectangle 不安全。

泛型方法

考慮寫一個方法,它用一個 Object 的數組和一個 collection 作為參數, 完成把數組中所有 object 放入 collection 中的功能。 下面是第一次嘗試:

static void fromArrayToCollection(Object[] a,Collection<?> c) {for (Object o : a) { c.add(o); // 編譯期錯誤}}1234512345

現在,你應該能夠學會避免初學者試圖使用Collection<Object>作為集 合參數類型的錯誤了。或許你已經意識到使用Collection<?>也不能工作。 回憶一下,你不能把對象放進一個未知類型的集合中去。 解決這個問題的辦法是使用 generic methods。就像類型聲明,方法的 聲明也可以被泛型化——就是說,帶有一個或者多個類型參數。

static <T> void fromArrayToCollection(T[] a,Collection<T> c){for (T o : a) { c.add(o); // correct}}123456123456

我們可以使用任意集合來調用這個方法,只要其元素的類型是數組的元 素類型的父類。

Object[] oa = new Object[100];Collection<Object> co = new ArrayList<Object>(); fromArrayToCollection(oa, co);// T 指 ObjectString[] sa = new String[100];Collection<String> cs = new ArrayList<String>(); fromArrayToCollection(sa, cs);// T inferred to be String fromArrayToCollection(sa, co);// T inferred to be Object Integer[] ia = new Integer[100];Float[] fa = new Float[100];Number[] na = new Number[100];Collection<Number> cn = new ArrayList<Number>(); fromArrayToCollection(ia, cn);// T inferred to be Number fromArrayToCollection(fa, cn);// T inferred to be NumberfromArrayToCollection(na, cn);// T inferred to be Number fromArrayToCollection(na, co);// T inferred to be Object fromArrayToCollection(na, cs);// compile-time error1234567891011121314151617181920212223242526272829303112345678910111213141516171819202122232425262728293031

注意,我們并沒有傳送真實類型參數(actual type argument)給一個泛 型方法。編譯器根據實參為我們推斷類型參數的值。它通常推斷出能使調 用類型正確的最明確的類型參數。

枚舉

描述

在某些情況下,一個類的對象是有限而且固定的。例如季節類,只能有 4 個對象 手動實現枚舉類: private 修飾構造器 屬性使用 private final 修飾 把該類的所有實例都使用 public static final 來修飾

使用 enum 定義枚舉類

JDK 1.5 新增的 enum 關鍵字用于定義枚舉類 枚舉類和普通類的區別: 使用 enum 定義的枚舉類默認繼承了 java.lang.Enum 類 枚舉類的構造器只能使用 private 訪問控制符 枚舉類的所有實例必須在枚舉類中顯式列出(, 分隔 ; 結尾). 列出的實例系統會自動添加 public static final 修飾 所有的枚舉類都提供了一個 values 方法, 該方法可以很方便地遍歷所有的枚舉值 JDK 1.5 中可以在 switch 表達式中使用枚舉類的對象作為表達式, case 子句可以直接使用枚舉值的名字, 無需添加枚舉類作為限定 若枚舉只有一個成員, 則可以作為一種單子模式的實現方式

枚舉類的屬性

枚舉類對象的屬性不應允許被改動, 所以應該使用 private final 修飾 枚舉類使用 private final 修飾的屬性應該在構造器中為其賦值 若枚舉類顯式的定義了帶參數的構造器, 則在列出枚舉值時也必須對應的傳入參數

使用 Enum 定義的 Season

這里寫圖片描述

實現接口的枚舉類

和普通 Java 類一樣枚舉類可以實現一個或多個接口 若需要每個枚舉值在調用實現的接口方法呈現出不同的行為方式, 則可以讓每個枚舉值分別來實現該方法

枚舉類的方法

這里寫圖片描述

小練習

public enum SeasonEnum implements EnumInfo {    //這里相當于創建了4個對象.可以分別實現接口方法    /*SPRING(10,"春天"){        //實現接口的方法        @Override        public Integer getCode() {            return null;        }    },*/    SPRING(10,"春天"),    SUMMER(20,"夏天"),    AUTUMN(30,"秋天"),    WINTER(40,"冬天");    private final int code;    private final String desc;    //默認就是private    SeasonEnum(int code, String desc) {        this.code = code;        this.desc = desc;    }    //這是舉例,其實沒什么用.直接code就可以返回自己的code    @Override    public Integer getCode() {        switch (this) {            case SPRING:                return 10;            case SUMMER:                return 20;            case AUTUMN:                return 30;            case WINTER:                return 40;        }        return null;    }/** * 接口 * public interface EnumInfo {    public Integer getCode(); } */}12345678910111213141516171819202122232425262728293031323334353637383940414243444546471234567891011121314151617181920212223242526272829303132333435363738394041424344454647

Annotation(注釋)

從 JDK 5.0 開始, Java 增加了對元數據(MetaData) 的支持, 也就是 Annotation(注釋) Annotation 其實就是代碼里的特殊標記, 這些標記可以在編譯, 類加載, 運行時被讀取, 并執行相應的處理. 通過使用 Annotation, 程序員可以在不改變原有邏輯的情況下, 在源文件中嵌入一些補充信息. Annotation 可以像修飾符一樣被使用, 可用于修飾包,類, 構造器, 方法, 成員變量, 參數, 局部變量的聲明, 這些信息被保存在 Annotation 的 “name=value” 對中. Annotation 能被用來為程序元素(類, 方法, 成員變量等) 設置元數據

基本的 Annotation

使用 Annotation 時要在其前面增加 @ 符號, 并把該 Annotation 當成一個修飾符使用. 用于修飾它支持的程序元素 三個基本的 Annotation: @Override: 限定重寫父類方法, 該注釋只能用于方法 @Deprecated: 用于表示某個程序元素(類, 方法等)已過時 @SuppressWarnings: 抑制編譯器警告.

自定義 Annotation

定義新的 Annotation 類型使用 @interface 關鍵字 Annotation 的成員變量在 Annotation 定義中以無參數方法的形式來聲明. 其方法名和返回值定義了該成員的名字和類型. 可以在定義 Annotation 的成員變量時為其指定初始值, 指定成員變量的初始值可使用 default 關鍵字 沒有成員定義的 Annotation 稱為標記; 包含成員變量的 Annotation 稱為元數據 Annotation

提取 Annotation 信息

JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 該接口代表程序中可以接受注釋的程序元素 當一個 Annotation 類型被定義為運行時 Annotation 后, 該注釋才是運行時可見, 當 class 文件被載入時保存在 class 文件中的 Annotation 才會被虛擬機讀取 程序可以調用 AnnotationElement 對象的如下方法來訪問 Annotation 信息 這里寫圖片描述

JDK 的元 Annotation

JDK 的元 Annotation 用于修飾其他 Annotation 定義 @Retention: 只能用于修飾一個 Annotation 定義, 用于指定該 Annotation 可以保留多長時間, @Rentention 包含一個 RetentionPolicy 類型的成員變量, 使用 @Rentention 時必須為該 value 成員變量指定值: RetentionPolicy.CLASS: 編譯器將把注釋記錄在 class 文件中. 當運行 Java 程序時, JVM 不會保留注釋. 這是默認值 RetentionPolicy.RUNTIME:編譯器將把注釋記錄在 class 文件中. 當運行 Java 程序時, JVM 會保留注釋. 程序可以通過反射獲取該注釋 RetentionPolicy.SOURCE: 編譯器直接丟棄這種策略的注釋 @Target: 用于修飾 Annotation 定義, 用于指定被修飾的 Annotation 能用于修飾哪些程序元素. @Target 也包含一個名為 value 的成員變量. @Documented: 用于指定被該元 Annotation 修飾的 Annotation 類將被 javadoc 工具提取成文檔. @Inherited: 被它修飾的 Annotation 將具有繼承性.如果某個類使用了被 @Inherited 修飾的 Annotation, 則其子類將自動具有該注釋

反射

描述

JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。 JAVA反射(放射)機制:“程序運行時,允許改變程序結構或變量類型,這種語言稱為動態語言”。從這個觀點看,Perl,Python,Ruby是動態語言,C++,Java,C#不是動態語言。但是JAVA有著一個非常突出的動態相關機制:Reflection,用在Java身上指的是我們可以于運行時加載、探知、使用編譯期間完全未知的classes。換句話說,Java程序可以加載一個運行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),并生成其對象實體、或對其fields設值、或喚起其methods。

實例

通過一整套基礎測試例子,理解反射。 整體類結構如下: 這里寫圖片描述 Person.java

public class Person {    private String gender;    private String name;    @MyValidation(min = 18,max = 30)    private Integer age;    public Person() {        this("男人");        System.out.println("Person's this : "+ this);        System.out.println("Person constructor...");    }    public Person(String gender) {        this.gender = gender;        System.out.println("Person constructor...gender  init.." + this.gender );    }    /**     * 加法     * @param a     * @param b     * @return a,b的和     */    protected Integer add(Integer a , Integer b ){        System.out.println("arg1 : "+ a + "  arg2 : "+b + "  result : "+(a + b));        return a +b;    }    //getter setter ...}12345678910111213141516171819202122232425262728293031321234567891011121314151617181920212223242526272829303132

User.java

public class User extends Person{    private Integer id;    private String name;    //getter setter ...    private String concat(String a , String b ){        System.out.println("arg1 : "+ a + "  arg2 : "+b + "  result : "+a.concat(b));        return a.concat(b);    }}12345678910111213141234567891011121314

Man.java

public class Man extends Person{    private boolean girlFriend;    private String occupation;    public Man() {        //第一行肯定執行的是super()        System.out.println("Man's this : "+ this);        System.out.println("Man constructor....");    }    public Man(boolean girlFriend, String occupation) {        this.girlFriend = girlFriend;        this.occupation = occupation;        System.out.println("Man constructor....girlFriend..occupation..init...");    }    //getter setter ...}12345678910111213141516171819201234567891011121314151617181920

BaseDao.java

public interface BaseDao<T> {    public T find();}1234512345

BaseDaoImpl.java

public abstract class BaseDaoImpl<T> implements BaseDao<T> {    //clazz賦值是關鍵    private Class<T> clazz;    public BaseDaoImpl() {        /**         * this其實是代表的BaseDaoImpl的子類對象         * this.getClass();得到的就是子類的類名         */        System.out.println("DAO基類構造器 子類為 : "+ this.getClass());        clazz = getEntityClass.getEntity(this.getClass(),0);    }    @Override    public T find() {        System.out.println("DAO基類 find() 具體class為 : "+ clazz);        return null;    }}123456789101112131415161718192021123456789101112131415161718192021

UserDaoImpl.java

public class UserDaoImpl extends BaseDaoImpl<User> {    //執行父類方法}123123

MyValidation.java

/** * * 用于修飾 Annotation 定義, 用于指定被修飾的 Annotation 能用于修飾哪些 * 程序元素. @Target 也包含一個名為 value 的成員變量. */@Target({ElementType.METHOD,ElementType.FIELD})/** * RetentionPolicy.CLASS: 編譯器將把注釋記錄在 class 文件中. 當運行 Java 程序時, JVM 不會保留注釋. 這是默認值 * RetentionPolicy.RUNTIME:編譯器將把注釋記錄在 class 文件中. 當運行 Java 程序時, JVM 會保留注釋. 程序可以通過反射獲取該注釋 * RetentionPolicy.SOURCE: 編譯器直接丟棄這種策略的注釋 */@Retention(RetentionPolicy.RUNTIME)public @interface MyValidation {    //最小值    public int min();    //最大值    public int max();}123456789101112131415161718123456789101112131415161718

getEntityClass.java

public class getEntityClass {    /**     * 獲取定義Class時聲明的父類的泛型參數類型     * @param clazz 子類對應的Class對象     * @param index 要獲取的泛型參數索引     */    public static Class getEntity(Class clazz,int index){        /**         * 獲取父類泛型類型         */        Type type = clazz.getGenericSuperclass();        if (type != null){            /**             * ParameterizedType : 泛型類型             */            if (!(type instanceof ParameterizedType)) {                return Object.class;            }            ParameterizedType param = (ParameterizedType) type;            /**             * 獲取實際的泛型類型參數數組             */            Type[] types = param.getActualTypeArguments();//          System.out.println(Arrays.asList(types));            if (types != null && types.length > 0){                if (index > types.length || index < 0){                    return Object.class;                }                if (! (types[0] instanceof Class)){                    return Object.class;                }                return (Class) types[0];            }        }        return Object.class;    }}12345678910111213141516171819202122232425262728293031323334353637383940411234567891011121314151617181920212223242526272829303132333435363738394041

reflectionUtils.java

public class reflectionUtils {    /**     *     * @param obj 需要操作的對象     * @param methodName 操作的方法     * @param args 參數     * @return     */    public static Object invoke(Object obj,String methodName,Object ... args)            throws NoSuchMethodException,            InvocationTargetException,            IllegalaccessException {        Class clazz = obj.getClass();        Class[] classes = new Class[args.length];        for (int i = 0;i<args.length;i++){            classes[i] = args[i].getClass();        }        Method method = clazz.getDeclaredMethod(methodName,classes);        method.setAccessible(true);        return method.invoke(obj,args);    }    /**     *     * @param className 需要操作的對象的類路徑     * @param methodName 操作的方法     * @param args 參數     * @return     */    public static Object invoke(String className,String methodName,Object ... args)            throws ClassNotFoundException, IllegalAccessException,            InstantiationException, NoSuchMethodException, InvocationTargetException {        Class clazz = Class.forName(className);        Object obj = clazz.newInstance();        return invoke(obj,methodName,args);    }    /**     * @apiNote 執行當前對象的方法(包括繼承父類的方法)     * @param className 需要操作的對象的類路徑     * @param methodName 操作的方法     * @param args 參數     * @return     */    public static Object invokeThisAndSuper(String className,String methodName,Object ... args)            throws ClassNotFoundException,            IllegalAccessException,            InstantiationException,            InvocationTargetException, NoSuchMethodException {        Class clazz = Class.forName(className);        Class[] classes = new Class[args.length];        for (int i = 0;i<args.length;i++){            classes[i] = args[i].getClass();        }        Method method = null;        //繼承層級循環向上,一直找到所有類的父類Object,如果還找不到method就算了.        for (Class c = clazz;c != Object.class;c = c.getSuperclass()){            try {            method = c.getDeclaredMethod(methodName,classes);                if (method != null)                    break;            } catch (NoSuchMethodException e) {}        }        if (method == null){            throw new NoSuchMethodException("沒有這個方法...");        }        method.setAccessible(true);        Object obj = clazz.newInstance();        return method.invoke(obj,args);    }    /**     * @apiNote 設置字段的值(包括父類的字段)     * @param obj 操作的對象     * @param fieldName 對象的字段名稱     * @param val 設置的值     */    public static void setFieldVal(Object obj,String fieldName,Object val) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {        Class clazz = obj.getClass();        Field field = null;        //繼承層級循環向上,一直找到所有類的父類Object,如果還找不到fieldName對應的Field就算了.        for (Class f = clazz; clazz != Object.class;f = clazz.getSuperclass()){            try {                field = f.getDeclaredField(fieldName);            } catch (NoSuchFieldException e) {}            if (field != null)                break;        }        if (field == null){            throw new NoSuchFieldException("無法獲取字段");        }        //Object object = clazz.newInstance();        field.setAccessible(true);        field.set(obj,val);        //return obj;    }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103

PersonTest.java 目的:主要測試子類和父類構造方法之間的關系。

public class PersonTest {    public static void  main (String[] a){        Man m = new Man();        /**         * 執行結果:         * Person constructor...gender  init..男人         * Person's this : com.iboray.javacore.reflection.Man@f6f4d33         * Person constructor...         * Man's this : com.iboray.javacore.reflection.Man@f6f4d33         * Man constructor....         */    }}12345678910111213141234567891011121314

RefTest.java 目的:測試反射

public class RefTest {    public static void main(String[] a) throws Exception {//        testClass();//        testClassLoader();//        testReflection();//        testConstructor();//        testAnnotation();        testGenericReflection();        /*User u = new User();        Object obj = reflectionUtils.invoke(u,"concat","我是一個 : ","粉刷匠");        System.out.println("result : "+ obj);*/        /*Object obj = reflectionUtils.invokeThisAndSuper("com.iboray.javacore.reflection.User","add",3,4);        System.out.println("result : "+ obj);*/       /* Object obj = new User();        reflectionUtils.setFieldVal(obj,"name","我是紅領巾");        reflectionUtils.setFieldVal(obj,"gender","女");        User user = (User)obj;        System.out.println("result user.getName() : "+ user.getName());        System.out.println("result user.getGender() : "+ user.getGender());*/    }    /**     * 通過子類獲得泛型類型     */    public static void testGenericReflection(){        BaseDao dao = new UserDaoImpl();        dao.find();    }    /**     * 注解測試     */    public static void testAnnotation() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {        Class clazz = Class.forName("com.iboray.javacore.reflection.Person");        int val = 18;        Field age = clazz.getDeclaredField("age");        /**         * 獲取指定類型的注解         */        Annotation annotation = age.getAnnotation(MyValidation.class);        if (annotation != null){            /**             * 如果類型確實為MyValidation             */            if (annotation instanceof MyValidation){                //類型轉換                MyValidation myValidation = (MyValidation)annotation;                //獲取注解中的屬性值                if (val < myValidation.min() || val > myValidation.max())                    throw new RuntimeException("invalid age .... ");            }        }        age.setAccessible(true);        Object o = clazz.newInstance();        age.set(o,val);        System.out.println("自定義驗證注解后的對象 : "+ o);    }    /**     * Class 是對一個類的描述,是類的類類型,它是描述一個類的類     * 直白的說就是Class中的屬性是類名,方法名,字段名,構造器,實現的接口等等這些都是Class的屬性....     *     * 對于每個類而言,JRE 都為其保留一個不變的 Class 類型的對象。一個 Class 對象包含了特定某個類的有關信息。     * Class 對象只能由系統建立對象,一個類在 JVM 中只會有一個Class實例     * 每個類的實例都會記得自己是由哪個 Class 實例所生成     *     * 類的屬性:Field     * 類的方法:Method     * 類的構造器:Constructor     *     */    public static void testClass()            throws ClassNotFoundException,            NoSuchMethodException,            NoSuchFieldException,            IllegalAccessException, InstantiationException, InvocationTargetException {        //通過類的class 屬性獲取,該方法最為安全可靠.程序性能更高        Class clazz = User.class;        System.out.println(clazz);        User user = new User();        //通過實例對象的getClass獲取        clazz = user.getClass();        System.out.println(clazz);        //通過Class對象的forName()靜態方法forName()獲取        //但可能會拋出ClassNotFoundException異常        clazz = Class.forName("com.iboray.javacore.reflection.User");        System.out.println(clazz);        /**         * 實例化對象         */        Object obj = clazz.newInstance();        /**         * 獲取所有聲明的方法,包括private         */        Method[] methods = clazz.getDeclaredMethods();        for (Method m : methods){            System.out.println("Method : "+m);        }        /**         * 獲取指定方法         */        Method method = clazz.getDeclaredMethod("concat",String.class,String.class);        System.out.println("獲取指定的方法 concat : "+ method);        /**         * 獲取所有聲明字段         */        Field[] fields = clazz.getDeclaredFields();        for (Field f : fields){            System.out.println("Field : "+f);        }        /**         * 獲取指定聲明字段         */        Field field = clazz.getDeclaredField("name");        System.out.println("field : "+ field);        /**         * 設置值         * setAccessible 設置操作權限         */        field.setAccessible(true);        field.set(obj,"我是紅領巾");        /**         * 獲取指定對象的指定字段的值         */        Object val = field.get(obj);        System.out.println("字段值為 : "+val);        /**         * 通過method找到指定對象的get方法并獲取指定字段值         */        Method nameMethod = clazz.getDeclaredMethod("getName");        Object nameVal = nameMethod.invoke(obj);        System.out.println("獲取指定的方法 getName : "+ nameVal);    }    /**     * 反射構造器測試     */    public static void testConstructor() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {        Class clazz = Class.forName("com.iboray.javacore.reflection.Person");        Constructor[] constructors = clazz.getConstructors();        for (Constructor c : constructors){            System.out.println("所有構造器之 : "+ c);        }        Constructor constructor = clazz.getConstructor(String.class);        System.out.println("指定構造器 : "+ constructor);        Object object = constructor.newInstance("男人");        System.out.println("指定構造器實例化對象 : "+ object);    }    /**     * 類裝載器是用來把類(class)裝載進 JVM 的。     * JVM 規范定義了兩種類型的類裝載器:啟動類裝載器(bootstrap)和用戶自定義裝載器(user-defined class loader)。     * JVM在運行時會產生3個類加載器組成的初始化加載器層次結構     */    public static void testClassLoader(){        ClassLoader classloader;        //獲取系統缺省的ClassLoader        classloader = ClassLoader.getSystemClassLoader();        /**         * 結果:         *      系統缺省的ClassLoader : sun.misc.Launcher$AppClassLoader@54bedef2         * 表示:         *      系統類裝載器實例化自類sun.misc.Launcher$AppClassLoader         */        System.out.println("系統缺省的ClassLoader : "+classloader);        while (classloader != null) {            //取得父的ClassLoader            classloader = classloader.getParent();            System.out.println("系統缺省的ClassLoader : "+classloader);            /**             * 結果為兩條:             * 1. 系統缺省的ClassLoader : sun.misc.Launcher$ExtClassLoader@f6f4d33,             *      表示 : 系統類裝載器的parent實例化自類sun.misc.Launcher$ExtClassLoader             * 2. 系統缺省的ClassLoader : null,             *      表示 : 系統類裝載器parent的parent為bootstrap,無法直接獲取             *             *             */        }        try {            Class cl = Class.forName("java.lang.Object");            classloader = cl.getClassLoader();            /**             * 結果:             *     java.lang.Object's loader is  null             * 表示:             *     類Object是由bootstrap裝載的             */            System.out.println("java.lang.Object's loader is  " + classloader);            cl = Class.forName("com.iboray.javacore.reflection.RefTest");            classloader = cl.getClassLoader();            /**             * 結果:             *     RefTest's loader is  sun.misc.Launcher$AppClassLoader@54bedef2             * 表示:             *    表示用戶類是由系統類裝載器裝載的             */            System.out.println("RefTest's loader is  " + classloader);        } catch (Exception e) {            System.out.println("Check name of the class");        }    }    /**     * 反射概述     * Reflection(反射)是Java被視為動態語言的關鍵,反射機制允許程序在執行期借助于Reflection API取得任何類的內部信息,     * 并能直接操作任意對象的內部屬性及方法。     * Java反射機制主要提供了以下功能:     * 1. 在運行時構造任意一個類的對象     * 2. 在運行時獲取任意一個類所具有的成員變量和方法     * 3. 在運行時調用任意一個對象的方法(屬性)     * 4. 生成動態代理     */    public static void testReflection() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {        Class clazz = Class.forName("com.iboray.javacore.reflection.User");        Method method = clazz.getDeclaredMethod("concat",String.class,String.class);        /**         * 實例化操作對象         */        Object obj = clazz.newInstance();        /**         * 設置可訪問權限,private方法是無法直接執行的.         * 否則會報:         * java.lang.IllegalAccessException:         *   Class XX.XX.XX can not access a member of class XX.XX.XX with modifiers "private"異常         */        method.setAccessible(true);        /**         * 執行方法         * obj : 執行哪個對象的方法         * params : 參數列表         */        method.invoke(obj,new String[]{"我是:","王二麻子"});    }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260

動態代理

創建動態代理

Proxy 提供用于創建動態代理類和代理對象的靜態方法, 它也是所有動態代理類的父類. Proxy 提供了兩個方法來創建動態代理類和動態代理實例

使用動態代理實現 AOP

AOP(aspect Orient Program, 面向切面編程)

非模塊化的橫切關注點所帶來的問題

這里寫圖片描述

橫切關注點: 跨越應用程序多個模塊的功能.

這里寫圖片描述

代碼實現片段

這里寫圖片描述

問題

越來越多的非業務需求(日志和驗證)加入后, 原有的計算器方法急劇膨脹. 屬于系統范圍內的需求通常需要跨越多個模塊(橫切關注點), 這些類似的需求包括日志, 驗證, 事務等. 這里寫圖片描述

非模塊化的橫切關注點將會導致的問題

代碼混亂: 每個方法在處理核心邏輯的同時還必須兼顧其他多個關注點. 代碼分散: 以日志需求為例, 只是為了滿足這個單一需求, 就不得不在多個模塊里多次重復相同的日志代碼. 如果日志需求發生變化, 必須修改所有模塊.

使用動態代理模塊化橫切關注點

代理設計模式的原理: 使用一個代理將對象包裝起來, 然后用該代理對象取代原始對象. 任何對原始對象的調用都要通過代理. 代理對象決定是否以及何時將方法調用轉到原始對象上. 這里寫圖片描述

練習

模擬事務處理 UserDao.java

public interface UserDao {    public int div(Integer a , Integer b);    public int mul(Integer a , Integer b);}12345671234567

UserDaoImpl.java

public class UserDaoImpl implements UserDao {    @Override    public int div(Integer a, Integer b) {        return a / b;    }    @Override    public int mul(Integer a, Integer b) {        return a * b;    }}12345678910111234567891011

TransactionProxy.java

public class TransactionProxy implements InvocationHandler{    /**     * 1. 需要一個被代理對象 target     * 2. 需要一個類加載器classLoader.一般都與目標對象的采用同一個類加載器     * 3. 一般的Proxy.newProxyInstance ()返回值是一個被代理對象實現的接口的返回類型.當然也可以是其他的接口類型     *      注意:第二個參數 Class<?>[] 必須是接口列表     *      如果代理對象不需要實現其他額外的接口,則可以使用target.getClass().getInterfaces()獲取被代理對象的接口列表     * 小細節:     *  target如果是代理方法中的局部變量,則需要用final修飾,因為局部變量在方法執行完后是作為垃圾被回收的.但代理對象     * 指向的是這個被代理對象,所以通過final修飾局部變量的被代理對象時,可以防止意外發生.此示例沒有采用局部變量的方式     * 所以不需要final修飾被代理對象.     */    private Object target ;    public TransactionProxy(Object target) {        this.target = target;    }    /**     * 創建動態代理類     * @param target     * @return     */    public static Object createProxy(Object target){        return Proxy.newProxyInstance(                /**                 * ClassLoader:由動態代理產生的對象由哪個類加載器加載,一般都與目標對象的采用同一個類加載器                 * Class<?>[] Interfaces : 由動態代理產生的對象必須實現的接口的Class數組,必須是接口列表                 * InvocationHandler : 當具體調用代理對象的方法時,將產生什么行為                 */                target.getClass().getClassLoader(),                target.getClass().getInterfaces(),                new TransactionProxy(target));    }    /**     * 執行方法     * @param proxy 正在被返回的代理對象,一般情況不使用它,     *              否則在方法中使用會死循環.因為代理對象又會執行invoke方法.     * @param method 正在被調用的方法     * @param args 調用方法傳入的參數列表     * @return     * @throws Throwable     */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("事務開始......參數列表為:"+ Arrays.asList(args));        Object obj = null;        try {            /**             * 調用被代理類的目標方法             * target 為構造函數傳入的目標對象             */            obj = method.invoke(target,args);        }catch (Exception e ){            System.out.println("事務回滾......");            return 0;        }        System.out.println("事務提交......結果為:"+ obj);        return obj;    }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566

ProxyTest.java

public static void main(String[] args){        UserDao dao = (UserDao) TransactionProxy.createProxy(new UserDaoImpl());        int c = dao.div(15,5);        System.out.println("結果為 : "+c);        c = dao.mul(2,6);        System.out.println("結果為 : "+c);    }    /**     * 結果:     *      * 事務開始......參數列表為:[15, 5]     * 事務提交......結果為:3     * 結果為 : 3     * 事務開始......參數列表為:[2, 6]     * 事務提交......結果為:12     * 結果為 : 12     */12345678910111213141516171234567891011121314151617

invocationHandler的匿名實現

這里寫圖片描述

多線程(基礎)

概念-程序、進程與多任務

程序(program)是對數據描述與操作的代碼的集合,是應用程序執行的腳本。 進程(process)是程序的一次執行過程,是系統運行程序的基本單位。程序是靜態的,進程是動態的。系統運行一個程序即是一個進程從創建、運行到消亡的過程。 多任務(multi task)在一個系統中可以同時運行多個程序,即有多個獨立運行的任務,每個任務對應一個進程。

線程

線程(thread):比進程更小的運行單位,是程序中單個順序的流控制。一個進程中可以包含多個線程。 簡單來講,線程是一個獨立的執行流,是進程內部的一個獨立執行單元,相當于一個子程序。 一個進程中的所有線程都在該進程的虛擬地址空間中,使用該進程的全局變量和系統資源。 操作系統給每個線程分配不同的CPU時間片,在某一時刻,CPU只執行一個時間片內的線程,多個時間片中的相應線程在CPU內輪流執行。 這里寫圖片描述

創建多線程

每個Java程序啟動后,虛擬機將自動創建一個主線程 可以通過以下兩種方式自定義線程類: 創建 java.lang.Thread 類的子類,重寫該類的 run方 法 創建 java.lang.Runnable接 口的實現類,實現接口中的 run 方法

繼承 Thread 類

Thread:代表一個線程類 這里寫圖片描述 Thread 類 Thread類中的重要方法: run方法:包括線程運行時執行的代碼,通常在子類中重寫它。 start方法:啟動一個新的線程,然后虛擬機調用新線程的run方法 Thread 類代碼示例: 這里寫圖片描述

線程執行流程

這里寫圖片描述 創建多線程 這里寫圖片描述 問題:要定義的線程類已經顯式繼承了一個其他的類怎么辦? 答:實現Runnable接口

Runnable 接口

Runnable 接口中只有一個未實現的 run 方法,實現該接口的類必須重寫該方法。 Runnable 接口與 Thread 類之間的區別 Runnable 接口必須實現 run 方法,而 Thread 類中的run 方法是一個空方法,可以不重寫 Runnable 接口的實現類并不是真正的線程類,只是線程運行的目標類。要想以線程的方式執行 run 方法,必須依靠 Thread 類 Runnable 接口適合于資源的共享 這里寫圖片描述

線程的生命周期

線程的生命周期: - 指線程從創建到啟動,直至運行結束 - 可以通過調用 Thread 類的相關方法影響線程的運行狀態 這里寫圖片描述 線程的運行狀態 1、 新建(New) 當創建了一個Thread對象時,該對象就處于“新建狀態” 沒有啟動,因此無法運行 2、 可執行(Runnable) 其他線程調用了處于新建狀態線程的start方法,該線程對象將轉換到“可執行狀態” 線程擁有獲得CPU控制權的機會,處在等待調度階段。 3、 運行(Running) 處在“可執行狀態”的線程對象一旦獲得了 CPU 控制權,就會轉換到“執行狀態” 在“執行狀態”下,線程狀態占用 CPU 時間片段,執行run 方法中的代碼 處在“執行狀態”下的線程可以調用 yield 方法,該方法用于主動出讓 CPU 控制權。線程對象出讓控制權后回到“可執行狀態”,重新等待調度。 這里寫圖片描述 4、 阻塞(Blocking) 線程在“執行狀態”下由于受某種條件的影響會被迫出讓CPU控制權,進入“阻塞狀態”。 進入阻塞狀態的三種情況 - 調用sleep方法 1、 public void sleep(long millis) 2、 Thread類的sleep方法用于讓當前線程暫時休眠一段時間 參數 millis 的單位是毫秒 這里寫圖片描述 - 調用join方法 處在“執行狀態”的線程如果調用了其他線程的 join 方法,將被掛起進入“阻塞狀態” 目標線程執行完畢后才會解除阻塞,回到 “可執行狀態” 這里寫圖片描述 - 執行I/O操作 線程在執行過程中如果因為訪問外部資源(等待用戶鍵盤輸入、訪問網絡)時發生了阻塞,也會導致當前線程進入“阻塞狀態”。 4.1、 解除阻塞 睡眠狀態超時 調用 join 后等待其他線程執行完畢 I/O 操作執行完畢 調用阻塞線程的 interrupt 方法(線程睡眠時,調用該線程的interrupt方法會拋出InterruptedException) 這里寫圖片描述 5、死亡(Dead) 死亡狀態(Dead):處于“執行狀態”的線程一旦從run方法返回(無論是正常退出還是拋出異常),就會進入“死亡狀態”。 已經“死亡”的線程不能重新運行,否則會拋出IllegalThreadStateException 可以使用 Thread 類的 isAlive 方法判斷線程是否活著 這里寫圖片描述

線程調度

線程調度 按照特定機制為線程分配 CPU 時間片段的行為 Java程序運行時,由 Java 虛擬機負責線程的調度 線程調度的實現方式 分時調度模型:讓所有線程輪流獲得CPU的控制權,并且為每個線程平均分配CPU時間片段 搶占式調度模型:選擇優先級相對較高的線程執行,如果所有線程的優先級相同,則隨機選擇一個線程執行 。Java虛擬機采用此種調度模型。

線程的優先級

Thread類提供了獲取和設置線程優先級的方法 getPriority:獲取當前線程的優先級 setPriority:設置當前線程的優先級 Java語言為線程類設置了10個優先級,分別使用1~10內的整數表示 ,整數值越大代表優先級越高。每個線程都有一個默認的優先級,主線程的默認優先級是5。 Thread類定義的三個常量分別代表了幾個常用的優先級: MAX_PRIORITY::代表了最高優先級10 MIN_PRIORITY::代表了最低優先級1 NORM_PRIORITY::代表了正常優先級5 setPriority 不一定起作用,在不同的操作系統、不同的 JVM 上,效果也可能不同。操作系統也不能保證設置了優先級的線程就一定會先運行或得到更多的CPU時間。 在實際使用中,不建議使用該方法 這里寫圖片描述 這里寫圖片描述

線程同步

問題:通過多線程解決售票問題。 非線程安全示例: TicketWindow2.java

/** * 售票窗口 */public class TicketWindow2 implements Runnable {    //票數    int ticketNum = 10;    private  boolean isNext(){            //是否售完標識            boolean f = false;            if (ticketNum > 0) {                //每次少一張票                ticketNum--;                f = true;            }            try {                /**                 * Thread-1 暫停的時候ticketNum=9, Thread2進來也執行ticketNum--后ticketNum=9                 * 此時Thread-1 在1秒之后 執行后面的打印,會直接打印出                 * 窗口1 剩余 8 張票...                 * 窗口2 剩余 8 張票...                 */                Thread.currentThread().sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            if (ticketNum == 0)                f = false;            if (!f)                System.out.println(Thread.currentThread().getName() + " 票已售完 ");            else                System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 張票...");            return f;    }    @Override    public void run() {        for (;ticketNum > 0 ;){            //執行售票            isNext();        }    }}123456789101112131415161718192021222324252627282930313233343536373839404142123456789101112131415161718192021222324252627282930313233343536373839404142

測試方法

public static void main(String[] a ){TicketWindow2 tw2 = new TicketWindow2();        Thread t1 = new Thread(tw2,"窗口1");        Thread t2 = new Thread(tw2,"窗口2");        t2.start();        t1.start();    }1234567891012345678910

結果:

窗口1 剩余 8 張票… 窗口2 剩余 8 張票… 窗口1 剩余 6 張票… 窗口2 剩余 6 張票… 窗口1 剩余 4 張票… 窗口2 剩余 3 張票… 窗口1 剩余 2 張票… 窗口2 剩余 1 張票… 窗口1 票已售完 窗口2 票已售完

結果已反映了一切問題。多窗口售票是不安全的有問題的。那如何解決這些問題???

線程安全

多線程應用程序同時訪問共享對象時,由于線程間相互搶占CPU的控制權,造成一個線程夾在另一個線程的執行過程中運行,所以可能導致錯誤的執行結果。 這里寫圖片描述

Synchronized 關鍵字

為了防止共享對象在并發訪問時出現錯誤,Java中提供了“synchronized”關鍵字。 1、 synchronized關鍵字 確保共享對象在同一時刻只能被一個線程訪問,這種處理機制稱為“線程同步”或“線程互斥”。Java中的“線程同步”基于“對象鎖”的概念 2、 使用 synchronized 關鍵字 修飾方法:被“synchronized”關鍵字修飾的方法稱為”同步方法” 當一個線程訪問對象的同步方法時,被訪問對象就處于“鎖定”狀態,訪問該方法的其他線程只能等待,對象中的其他同步方法也不能訪問,但非同步方法則可以訪問

//定義同步方法 public synchronized void methd(){    //方法實現 }12341234

這里寫圖片描述 3、 使用 ”synchronized” 關鍵字:修飾部分代碼,如果只希望同步部分代碼行,可以使用“同步塊”

//同步塊 synchronized(obj){    //被同步的代碼塊 }12341234

同步塊的作用與同步方法一樣,只是控制范圍有所區別

修改示例

重新修改售票的例子,將isNext方法改為同步方法

    //票數    int ticketNum = 100;    /**     * 同步方法     */    private synchronized boolean isNext(){        /**         * 同步代碼塊         *///        synchronized (this) {        boolean f = false;        if (ticketNum > 0) {            ticketNum--;            f = true;        }        try {            /**             * Thread-1 暫停的時候ticketNum=9, Thread2進來也執行ticketNum--后ticketNum=9             * 此時Thread-1 在1秒之后 執行后面的打印,會直接打印出             * 窗口1 剩余 8 張票...             * 窗口2 剩余 8 張票...             */            Thread.currentThread().sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        if (ticketNum == 0)            f = false;        if (!f)            System.out.println(Thread.currentThread().getName() + " 票已售完 ");        else            System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 張票...");        return f;//        }    }1234567891011121314151617181920212223242526272829303132333435363712345678910111213141516171819202122232425262728293031323334353637

測試: 為了實現多線程的結果 我模擬了4個售票窗口,100張票 測試方法

public static void main(String[] a ){/**         * 這里可以看出 TicketWindow2 為線程共享參照對象,         * 也就是說,如果需要同步的話,那synchronize(參照對象)中的參照對象即為:TicketWindow2         */        TicketWindow2 tw2 = new TicketWindow2();        Thread t1 = new Thread(tw2,"窗口1");        Thread t2 = new Thread(tw2,"窗口2");        Thread t3 = new Thread(tw2,"窗口3");        Thread t4 = new Thread(tw2,"窗口4");        t2.start();        t1.start();        t4.start();        t3.start();    }1234567891011121314151617181912345678910111213141516171819

結果:

窗口2 剩余 99 張票… 窗口2 剩余 98 張票… … 窗口2 剩余 84 張票… 窗口3 剩余 83 張票… 窗口3 剩余 82 張票… … 窗口3 剩余 20 張票… 窗口4 剩余 19 張票… … 窗口4 剩余 1 張票… 窗口4 票已售完 窗口1 票已售完 窗口3 票已售完 窗口2 票已售完

線程通信

wait()方法: 中斷方法的執行,使本線程等待,暫時讓出 cpu 的使用權,并允許其他線程使用這個同步方法。 notify()方法: 喚醒由于使用這個同步方法而處于等待線程的 某一個結束等待 notifyall()方法: 喚醒所有由于使用這個同步方法而處于等待的線程結束等待

練習

通過交叉打印,練習線程通信方法 PrintWords.java

public class PrintWords implements Runnable{    private boolean a=true,b=false;    private char abc = 'a';    private synchronized void doPrint(){        if (abc <= 'z') {            String name = Thread.currentThread().getName();            System.out.println("Thread- " + name + "   :  " + abc);            abc++;            //等下個線程進來就可以換新上個等待的線程            notifyAll();            try {                //當前線程等待                wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        if (abc >= 'z'){            notifyAll();        }    }    @Override    public void run() {        while (abc <= 'z') {            doPrint();        }    }    public static void main(String[] a){        Runnable p = new PrintWords();        Thread ta = new Thread(p,"a");        Thread tb = new Thread(p,"b");        ta.start();        tb.start();    }}1234567891011121314151617181920212223242526272829303132333435363738394041424312345678910111213141516171819202122232425262728293031323334353637383940414243

測試結果

Thread- a : a Thread- b : b Thread- a : c Thread- b : d … Thread- a : k Thread- b : l … Thread- b : z


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 湛江市| 四会市| 诸城市| 凤城市| 胶南市| 金堂县| 凤翔县| 始兴县| 克山县| 赫章县| 林甸县| 北碚区| 安溪县| 清镇市| 镇巴县| 双桥区| 荔浦县| 郁南县| 上饶市| 胶州市| 龙游县| 棋牌| 凤冈县| 东乡族自治县| 河北省| 哈密市| 沂源县| 唐山市| 灌南县| 剑阁县| 沧源| 盱眙县| 五常市| 赣州市| 固始县| 溧水县| 安岳县| 尉氏县| 松原市| 博湖县| 宁远县|