java快速入門


1.注釋

注釋的重要性不言而喻,我們不管寫什么代碼注釋必不可少,那么java的注釋的書寫方式與注釋模板是怎么進行的呢?我們來看一下。

package frist;
/*
 * @Description HelloWorld類
 * @Author 王延領
 **/
class HelloWorld {
    /*
    這是我們Java程序的主入口,
    main方法也是程序的主線程。
    */
    public static void main(String[] arg)
    {
        //輸出
       System.out.println("wyl");
    }
}

1.1 注釋

以上可以看出java的注釋主要有三種
單行注釋:只能注釋當前行,以//開始,直到行結束

 //輸出

多行注釋:注釋一段文字,以/*開始, */結束!

 /*
    這是我們Java程序的主入口,
    main方法也是程序的主線程。
 */

文檔注釋:用於生產API文檔,配合JavaDoc。

/*
 * @Description HelloWorld類
 * @Author 王延領
 **/

1.2 idea注釋模版配置

1.2.1 定義java文件頭部的注釋

File => setting => editor => File and Code Templates-class -Includes
image-20210712143531809

/**
  * @創建人 王延領
  *@創建時間 ${DATE}
  *描述 Todo
**/

以上當你創建一個class的時候就會帶上以上信息了

1.2.2 給java類中的方法添加上注釋

第一步勾選Enable Live Templates
首先要在上一步中勾選中 Enable Live Templates
image-20210712144107882
第二步新建一個Group
其次要打開LiveTemplates 然后新建一個Group
如圖:
img
在彈窗口中輸入你想要的group名稱,wyl
image-20210712144433114
其中:Abbreviation 為快捷鍵,當輸入w的時候就會提示對應的方法注釋模板,j為類的注釋模板
image-20210712144649673
Templete Text
注釋內容,$$ 為動態模板參數點擊Edit Vaariables 選擇對應動態值。

image-20210712144955611

/*
 * @描述: TODO
 * @作者 王延領
 * @時間 2021/7/12
 * @版本 1.0
 */
public class wyl {
    /**
     *@描述
     *@參數 [str]
     *@返回值 [java.lang.String]
     *@創建人 王延領
     *@創建時間 2021/7/12
     *@修改人和其它信息
     */
    public String CommentTemplate(String str)
    {
        return str;
    }
}

2.關鍵字

關鍵字 說明
private 一種訪問控制方式:私用模式
protected 一種訪問控制方式:保護模式
public 一種訪問控制方式:共用模式
abstract 表明類或者成員方法具有抽象屬性
class
extends 表明一個類型是另一個類型的子類型,這里常見的類型有類和接口
final 用來說明最終屬性,表明一個類不能派生出子類,或者成員方法不能被覆蓋,或者成員域的值不能被改變
implements 表明一個類實現了給定的接口
interface 接口
native 用來聲明一個方法是由與計算機相關的語言(如C/C++/FORTRAN語言)實現的
new 用來創建新實例對象
static 表明具有靜態屬性
strictfp 用來聲明FP_strict(單精度或雙精度浮點數)表達式遵循IEEE 754算術規范
synchronized 表明一段代碼需要同步執行
transient 聲明不用序列化的成員域
volatile 表明兩個或者多個變量必須同步地發生變化
break 提前跳出一個塊
continue 回到一個塊的開始處
return 從成員方法中返回數據
do 用在do-while循環結構中
while 用在循環結構中
if 條件語句的引導詞
else 用在條件語句中,表明當條件不成立時的分支
for 一種循環結構的引導詞
instanceof 用來測試一個對象是否是指定類型的實例對象
switch 分支語句結構的引導詞
case 用在switch語句之中,表示其中的一個分支
default 默認,例如,用在switch語句中,表明一個默認的分支
try 嘗試一個可能拋出異常的程序塊
catch 用在異常處理中,用來捕捉異常
throw 拋出一個異常
throws 聲明在當前定義的成員方法中所有需要拋出的異常
import 表明要訪問指定的類或包
package
boolean 基本數據類型之一,布爾類型
byte 基本數據類型之一,字節類型
char 基本數據類型之一,字符類型
double 基本數據類型之一,雙精度浮點數類型
float 基本數據類型之一,單精度浮點數類型
int 基本數據類型之一,整數類型
long 基本數據類型之一,長整數類型
short 基本數據類型之一,短整數類型
super 表明當前對象的父類型的引用或者父類型的構造方法
this 指向當前實例對象的引用
void 聲明當前成員方法沒有返回值
goto 保留關鍵字,沒有具體含義
const 保留關鍵字,沒有具體含義

3.數據類型

image-20210712151302116

3.1.數據類型轉換

3.1.1自動類型轉換

自動類型轉換:容量小的數據類型可以自動轉換為容量大的數據類型。
image-20210712152354656
注:如果低級類型為char型,向高級類型(整型)轉換時,會轉換為對應ASCII碼值

3.1.2 強制類型轉換

強制類型轉換,又被稱為造型,用於顯式的轉換一個數值的類型.
轉換方式為:(type)var ,運算符“()”中的type表示將值var想要轉換成的目標數據類型。 條件是轉換的數據類型必須是兼容的。

double x = 3.14;
int nx = (int)x; //值為3
char c = 'a';
int d = c+1;
System.out.println(d); //98
System.out.println((char)d); //b

3.1.3.包裝類過渡類型轉換

eg1:int i=Integer.parseInt(“123”)
說明:此方法只能適用於字符串轉化成整型變量
eg2: float f=Float.valueOf(“123”).floatValue()
說明:上例是將一個字符串轉化成一個Float對象,然后再調用這個對象的floatValue()方法返回其對應的float數值。
eg3: boolean b=Boolean.valueOf(“123”).booleanValue()
說明:上例是將一個字符串轉化成一個Boolean對象,然后再調用這個對象的booleanValue()方法返回其對應的boolean數值。
eg4:double d=Double.valueOf(“123”).doublue()
說明:上例是將一個字符串轉化成一個Double對象,然后再調用這個對象的doublue()方法返回其對應的double數值。
eg5: long l=Long.valueOf(“123”).longValue()
說明:上例是將一個字符串轉化成一個Long對象,然后再調用這個對象的longValue()方法返回其對應的long數值。
eg6: char=Character.valueOf(“123”).charValue()
說明:上例是將一個字符串轉化成一個Character對象

4.常量、變量、運算符

常量

變量是什么:就是可以變化的量!
我們通過變量來操縱存儲空間中的數據,變量就是指代這個存儲空間!空間位置是確定的,但是里面放
置什么值不確定!Java是一種強類型語言,每個變量都必須聲明其類型。

//數據類型 變量名 = 值;可以使用逗號隔開來聲明多個同類型變量。

注意事項:
每個變量都有類型,類型可以是基本類型,也可以是引用類型。
變量名必須是合法的標識符。
變量聲明是一條完整的語句,因此每一個聲明都必須以分號結束
變量作用域
類變量(靜態變量: static variable):獨立於方法之外的變量,用 static 修飾。
實例變量(成員變量:member variable):獨立於方法之外的變量,不過沒有 static 修飾。
局部變量(lacal variable):類的方法中的變量。

變量

常量(Constant):初始化(initialize)后不能再改變值!不會變動的值。

final 常量名=值;
final double PI=3.14;

命名規范

  1. 所有變量、方法、類名:見名知意
  2. 類成員變量:首字母小寫和駝峰原則 : monthSalary
  3. 局部變量:首字母小寫和駝峰原則
  4. 常量:大寫字母和下划線:MAX_VALUE
  5. 類名:首字母大寫和駝峰原則: Man, GoodMan
  6. 方法名:首字母小寫和駝峰原則: run(), runRun()

運算符

Java 語言支持如下運算符:
算術運算符: +,-,,/,%,++,--
賦值運算符 =
關系運算符: >,<,>=,<=,==,!= instanceof
邏輯運算符: &&,||,!
位運算符: &,|,^,~ , >>,<<,>>> (了解!!!)
條件運算符 ?:
擴展賦值運算符:+=,-=,
=,/=

5.java流轉控制

if...else、while、do...while、for、switch...case 在這就不累述了。
跳轉:
return
return從一個方法返回,並把控制權交給調用它的語句序;或者直接結束當前的程序;
break
break語句在for、while、do···while循環語句中,經常用於強行退出當前循環;
continue
continue語句用於跳過此次循環,執行下次循環;

6.方法

那么什么是方法呢?
Java方法是語句的集合,它們在一起執行一個功能。
方法是解決一類問題的步驟的有序組合
方法包含於類或對象中
方法在程序中被創建,在其他地方被引用
設計方法的原則:方法的本意是功能塊,就是實現某個功能的語句塊的集合。我們設計方法的時候,最
好保持方法的原子性,就是一個方法只完成1個功能,這樣利於我們后期的擴展。
方法的優點
使程序變得更簡短而清晰。
有利於程序維護。
可以提高程序開發的效率。
提高了代碼的重用性。

定義

修飾符 返回值類型 方法名(參數類型 參數名){
...
方法體
...
return 返回值;
}

修飾符:修飾符,這是可選的,告訴編譯器如何調用該方法。定義了該方法的訪問類型。
返回值類型 :方法可能會返回值。returnValueType 是方法返回值的數據類型。有些方法執行所需
的操作,但沒有返回值。在這種情況下,returnValueType 是關鍵字void。
方法名:是方法的實際名稱。方法名和參數表共同構成方法簽名。
參數類型:參數像是一個占位符。當方法被調用時,傳遞值給參數。這個值被稱為實參或變量。參
數列表是指方法的參數類型、順序和參數的個數。參數是可選的,方法可以不包含任何參數。
形式參數:在方法被調用時用於接收外界輸入的數據。
實參:調用方法時實際傳給方法的數據。
方法體:方法體包含具體的語句,定義該方法的功能。

方法的重載

就是說一個類的兩個方法擁有相同的名字,但是有不同的參數列表。

可變參數

在方法聲明中,在指定參數類型后加一個省略號(...) 。
一個方法中只能指定一個可變參數,它必須是方法的最后一個參數。任何普通的參數必須在它之前聲
明。

typeName... parameterName

遞歸

自己調用自己

7.數組

數組的定義:

數組是相同類型數據的有序集合.
數組描述的是相同類型的若干個數據,按照一定的先后次序排列組合而成。
其中,每一個數據稱作一個數組元素,每個數組元素可以通過一個下標來訪問它們.

數組的四個基本特點:

  1. 其長度是確定的。數組一旦被創建,它的大小就是不可以改變的。
  2. 其元素必須是相同類型,不允許出現混合類型。
  3. 數組中的元素可以是任何數據類型,包括基本類型和引用類型。
  4. 數組變量屬引用類型,數組也可以看成是對象,數組中的每個元素相當於該對象的成員變量

數組聲明

dataType[] arrayRefVar; // 首選的方法
或
dataType arrayRefVar[]; // 效果相同,但不是首選方法

創建數組

arrayRefVar = new dataType[1 arraySize];

數組的元素是通過索引訪問的。數組索引從 0 開始,所以索引值從 0 到 arrayRefVar.length-1。

三種初始化

靜態初始化
除了用new關鍵字來產生數組以外,還可以直接在定義數組的同時就為數組元素分配空間並賦值。

int[] a = {1,2,3};
Man[] mans = {new Man(1,1),new Man(2,2)};

動態初始化
數組定義、為數組元素分配空間、賦值的操作、分開進行。

int[] a = new int[2];
a[0]=1;
a[1]=2;

數組的默認初始化
數組是引用類型,它的元素相當於類的實例變量,因此數組一經分配空間,其中的每個元素也被按照實
例變量同樣的方式被隱式初始化。

public static void main(String[] args) {
int[] a=new int[2];
boolean[] b = new boolean[2];
String[] s = new String[2];
System.out.println(a[0]+":"+a[1]); //0,0
System.out.println(b[0]+":"+b[1]); //false,false
System.out.println(s[0]+":"+s[1]); //null, null
}

數組邊界

下標的合法區間:[0, length-1],如果越界就會報錯;

for 和For-Each 循環

for(type element: array){
System.out.println(element);
}
for (int i = 1; i < myList.length; i++) {
System.out.println(myList[i]);
}

多維數組

type[][] typeName = new type[typeLength1][1 typeLength2];

Arrays 類

數組的工具類java.util.Arrays
java.util.Arrays 類能方便地操作數組. 使用之前需要導包!
具有以下常用功能:
給數組賦值:通過 fill 方法。
對數組排序:通過 sort 方法,按升序。
比較數組:通過 equals 方法比較數組中元素值是否相等。
查找數組元素:通過 binarySearch 方法能對排序好的數組進行二分查找法操作。
轉換為list: 通過asList(a)進行轉換

8.面向對象

萬物皆為對象!!!對象是抽象概念的具體實例。

以類的方式組織代碼,以對象的組織(封裝)數據就是面向對象

繼承

繼承是java面向對象編程技術的一塊基石,因為它允許創建分等級層次的類。

class 父類 {
}
class 子類 extends 父類 {
}

public interface A {
    public void eat();
    public void sleep();
}
 
public interface B {
    public void show();
}
 
public class C implements A,B {
}

為什么要繼承,因為有重復。所以才繼承,進而我們就知道了。父類就是公共部分的定義或規則

Java 不支持多繼承(只能繼承一個類),但支持多重繼承。

image-20210809181238809

特點

  • 子類擁有父類非 private 的屬性、方法。

  • 子類可以擁有自己的屬性和方法,即子類可以對父類進行擴展。

  • 子類可以用自己的方式實現父類的方法。

  • Java 的繼承是單繼承,但是可以多重繼承,單繼承就是一個子類只能繼承一個父類,多重繼承就是,例如 B 類繼承 A 類,C 類繼承 B 類,所以按照關系就是 B 類是 C 類的父類,A 類是 B 類的父類,這是 Java 繼承區別於 C++ 繼承的一個特性。

  • 提高了類之間的耦合性(繼承的缺點,耦合度高就會造成代碼之間的聯系越緊密,代碼獨立性越差)

super 與 this 關鍵字

class Animal {
  void eat() {
    System.out.println("animal : eat");
  }
}
 
class Dog extends Animal {
  void eat() {
    System.out.println("dog : eat");
  }
  void eatTest() {
    this.eat();   // this 調用自己的方法
    super.eat();  // super 調用父類方法
  }
}
 
public class Test {
  public static void main(String[] args) {
    Animal a = new Animal();
    a.eat();
    Dog d = new Dog();
    d.eatTest();
  }
}
animal : eat
dog : eat
animal : eat

final關鍵字
final 關鍵字聲明類可以把類定義為不能繼承的,即最終類;

class SuperClass {
  private int n;
  SuperClass(){
    System.out.println("SuperClass()");
  }
  SuperClass(int n) {
    System.out.println("SuperClass(int n)");
    this.n = n;
  }
}
// SubClass 類繼承
class SubClass extends SuperClass{
  private int n;
  
  SubClass(){ // 自動調用父類的無參數構造器
    System.out.println("SubClass");
  }  
  
  public SubClass(int n){ 
    super(300);  // 調用父類中帶有參數的構造器
    System.out.println("SubClass(int n):"+n);
    this.n = n;
  }
}
// SubClass2 類繼承
class SubClass2 extends SuperClass{
  private int n;
  
  SubClass2(){
    super(300);  // 調用父類中帶有參數的構造器
    System.out.println("SubClass2");
  }  
  
  public SubClass2(int n){ // 自動調用父類的無參數構造器
    System.out.println("SubClass2(int n):"+n);
    this.n = n;
  }
}
public class TestSuperSub{
  public static void main (String args[]){
    System.out.println("------SubClass 類繼承------");
    SubClass sc1 = new SubClass();
    SubClass sc2 = new SubClass(100); 
    System.out.println("------SubClass2 類繼承------");
    SubClass2 sc3 = new SubClass2();
    SubClass2 sc4 = new SubClass2(200); 
  }
}
------SubClass 類繼承------
SuperClass()
SubClass
SuperClass(int n)
SubClass(int n):100
------SubClass2 類繼承------
SuperClass(int n)
SubClass2
SuperClass()
SubClass2(int n):200

構造函數

子類是不繼承父類的構造器(構造方法或者構造函數)的,它只是調用(隱式或顯式)。如果父類的構造器帶有參數,則必須在子類的構造器中顯式地通過 super 關鍵字調用父類的構造器並配以適當的參數列表。

如果父類構造器沒有參數,則在子類的構造器中不需要使用 super 關鍵字調用父類構造器,系統會自動調用父類的無參構造器。

重寫(Override)與重載(Overload)

重寫(Override)

重寫是子類對父類的允許訪問的方法的實現過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫

class Animal{
   public void move(){
      System.out.println("動物可以移動");
   }
}
 
class Dog extends Animal{
   public void move(){
      System.out.println("狗可以跑和走");
   }
}
 
public class TestDog{
   public static void main(String args[]){
      Animal a = new Animal(); // Animal 對象
      Animal b = new Dog(); // Dog 對象
      a.move();// 執行 Animal 類的方法
      b.move();//執行 Dog 類的方法
   }
}
動物可以移動
狗可以跑和走

方法的重寫規則

  • 參數列表與被重寫方法的參數列表必須完全相同。

  • 返回類型與被重寫方法的返回類型可以不相同,但是必須是父類返回值的派生類(java5 及更早版本返回類型要一樣,java7 及更高版本可以不同)。

  • 訪問權限不能比父類中被重寫的方法的訪問權限更低。例如:如果父類的一個方法被聲明為 public,那么在子類中重寫該方法就不能聲明為 protected。

  • 父類的成員方法只能被它的子類重寫。

  • 聲明為 final 的方法不能被重寫。

  • 聲明為 static 的方法不能被重寫,但是能夠被再次聲明。

  • 子類和父類在同一個包中,那么子類可以重寫父類所有方法,除了聲明為 private 和 final 的方法。

  • 子類和父類不在同一個包中,那么子類只能夠重寫父類的聲明為 public 和 protected 的非 final 方法。

  • 重寫的方法能夠拋出任何非強制異常,無論被重寫的方法是否拋出異常。但是,重寫的方法不能拋出新的強制性異常,或者比被重寫方法聲明的更廣泛的強制性異常,反之則可以。

  • 構造方法不能被重寫。

  • 如果不能繼承一個類,則不能重寫該類的方法。

重載(Overload)

重載(overloading) 是在一個類里面,方法名字相同,而參數不同。返回類型可以相同也可以不同。

每個重載的方法(或者構造函數)都必須有一個獨一無二的參數類型列表。

最常用的地方就是構造器的重載。

public class Overloading {
    public int test(){
        System.out.println("test1");
        return 1;
    }
 
    public void test(int a){
        System.out.println("test2");
    }   
 
    //以下兩個參數類型順序不同
    public String test(int a,String s){
        System.out.println("test3");
        return "returntest3";
    }   
 
    public String test(String s,int a){
        System.out.println("test4");
        return "returntest4";
    }   
 
    public static void main(String[] args){
        Overloading o = new Overloading();
        System.out.println(o.test());
        o.test(1);
        System.out.println(o.test(1,"test3"));
        System.out.println(o.test("test4",1));
    }
}

重載規則:

  • 被重載的方法必須改變參數列表(參數個數或類型不一樣);

  • 被重載的方法可以改變返回類型;

  • 被重載的方法可以改變訪問修飾符;

  • 被重載的方法可以聲明新的或更廣的檢查異常;

  • 方法能夠在同一個類中或者在一個子類中被重載。

  • 無法以返回值類型作為重載函數的區分標准。

    區別點 重載方法 重寫方法
    參數列表 必須修改 一定不能修改
    返回類型 可以修改 一定不能修改
    異常 可以修改 可以減少或刪除,一定不能拋出新的或者更廣的異常
    訪問 可以修改 一定不能做更嚴格的限制(可以降低限制)

多態

多態是同一個行為具有多個不同表現形式或形態的能力。

多態就是同一個接口,使用不同的實例而執行不同操作。

多態性是對象多種表現形式的體現。

多態的優點

  1. 消除類型之間的耦合關系
  2. 可替換性
  3. 可擴充性
  4. 接口性
  5. 靈活性
  6. 簡化性

多態存在的三個必要條件

繼承
重寫
父類引用指向子類對象:Parent p = new Child();

class Shape {
    void draw() {}
}
 
class Circle extends Shape {
    void draw() {
        System.out.println("Circle.draw()");
    }
}
 
class Square extends Shape {
    void draw() {
        System.out.println("Square.draw()");
    }
}
 
class Triangle extends Shape {
    void draw() {
        System.out.println("Triangle.draw()");
    }
}

虛函數

虛函數的存在是為了多態。

Java 中其實沒有虛函數的概念,它的普通函數就相當於 C++ 的虛函數,動態綁定是Java的默認行為。如果 Java 中不希望某個函數具有虛函數特性,可以加上 final 關鍵字變成非虛函數。

多態的實現方式

方式一:重寫:

方式二:接口

方式三:抽象類和抽象方法

抽象類

擁有抽象方法的類就是抽象類,抽象類要使用abstract關鍵字聲明.

abstract class A{//定義一個抽象類
	public void fun(){//普通方法
		System.out.println("存在方法體的方法");
	}
	public abstract void print();//抽象方法,沒有方法體,有abstract關鍵字做修飾
	
}

繼承抽象類

我們可以通過以下方式繼承 Employee 類的屬性

抽象類的使用原則
(1)抽象方法必須為public或者protected(因為如果為private,則不能被子類繼承,子類便無法實現該方法),缺省情況下默認為public;
(2)抽象類不能直接實例化,需要依靠子類采用向上轉型的方式處理;
(3)抽象類必須有子類,使用extends繼承,一個子類只能繼承一個抽象類;
(4)子類(如果不是抽象類)則必須覆寫抽象類之中的全部抽象方法(如果子類沒有實現父類的抽象方法,則必須將子類也定義為為abstract類。);

package com.wz.abstractdemo;

abstract class A{//定義一個抽象類
	
	public void fun(){//普通方法
		System.out.println("存在方法體的方法");
	}
	
	public abstract void print();//抽象方法,沒有方法體,有abstract關鍵字做修飾
	
}
//單繼承
class B extends A{//B類是抽象類的子類,是一個普通類

	@Override
	public void print() {//強制要求覆寫
		System.out.println("Hello World !");
	}
	
}
public class TestDemo {

	public static void main(String[] args) {
		A a = new B();//向上轉型
		
		a.print();//被子類所覆寫的過的方法
	}
}
Hello World !

封裝

封裝(英語:Encapsulation)是指一種將抽象性函式接口的實現細節部分包裝、隱藏起來的方法。

封裝可以被認為是一個保護屏障,防止該類的代碼和數據被外部類定義的代碼隨機訪問。

要訪問該類的代碼和數據,必須通過嚴格的接口控制。

封裝最主要的功能在於我們能修改自己的實現代碼,而不用修改那些調用我們代碼的程序片段。

適當的封裝可以讓程式碼更容易理解與維護,也加強了程式碼的安全性。

封裝的優點

  1. 良好的封裝能夠減少耦合。
  2. 類內部的結構可以自由修改。
  3. 可以對成員變量進行更精確的控制。
  4. 隱藏信息,實現細節。

接口

在JAVA編程語言中是一個抽象類型,是抽象方法的集合,接口通常以interface來聲明。一個類通過繼承接口的方式,從而來繼承接口的抽象方法。

接口與類相似點

  1. 一個接口可以有多個方法。
  2. 接口文件保存在 .java 結尾的文件中,文件名使用接口名。
  3. 接口的字節碼文件保存在 .class 結尾的文件中。
  4. 接口相應的字節碼文件必須在與包名稱相匹配的目錄結構中。

接口與類的區別

  1. 接口不能用於實例化對象。
  2. 接口沒有構造方法。
  3. 接口中所有的方法必須是抽象方法,Java 8 之后 接口中可以使用 default 關鍵字修飾的非抽象方法。
  4. 接口不能包含成員變量,除了 static 和 final 變量。
  5. 接口不是被類繼承了,而是要被類實現。
  6. 接口支持多繼承。

接口特性

  1. 接口中每一個方法也是隱式抽象的,接口中的方法會被隱式的指定為 public abstract(只能是 public abstract,其他修飾符都會報錯)。
  2. 接口中可以含有變量,但是接口中的變量會被隱式的指定為 public static final 變量(並且只能是 public,用 private 修飾會報編譯錯誤)。
  3. 接口中的方法是不能在接口中實現的,只能由實現接口的類來實現接口中的方法。

抽象類和接口的區別

  1. 抽象類中的方法可以有方法體,就是能實現方法的具體功能,但是接口中的方法不行。

  2. 抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是 public static final 類型的。

  3. 接口中不能含有靜態代碼塊以及靜態方法(用 static 修飾的方法),而抽象類是可以有靜態代碼塊和靜態方法

  4. 一個類只能繼承一個抽象類,而一個類卻可以實現多個接口。

[可見度] interface 接口名稱 [extends 其他的接口名] {
        // 聲明變量
        // 抽象方法
}
/* 文件名 : NameOfInterface.java */
import java.lang.*;
//引入包
 
public interface NameOfInterface
{
   //任何類型 final, static 字段
   //抽象方法
}

接口有以下特性

  • 接口是隱式抽象的,當聲明一個接口的時候,不必使用abstract關鍵字。
  • 接口中每一個方法也是隱式抽象的,聲明時同樣不需要abstract關鍵字。
  • 接口中的方法都是公有的。

枚舉

枚舉是一個特殊的類,一般表示一組常量.每個枚舉都是通過 Class 在內部實現的,且所有的枚舉值都是 public static final 的。

enum Color
{
    RED, GREEN, BLUE;
}
 
public class Test
{
    // 執行輸出結果
    public static void main(String[] args)
    {
        Color c1 = Color.RED;
        System.out.println(c1);
    }
}
RED

values(), ordinal() 和 valueOf() 方法

enum 定義的枚舉類默認繼承了 java.lang.Enum 類,並實現了 java.lang.Seriablizable 和 java.lang.Comparable 兩個接口。

values(), ordinal() 和 valueOf() 方法位於 java.lang.Enum 類中:

  • values() 返回枚舉類中所有的值。

  • ordinal()方法可以找到每個枚舉常量的索引,就像數組索引一樣。

  • valueOf()方法返回指定字符串值的枚舉常量。

    enum Color
    {
        RED, GREEN, BLUE;
    }
     
    public class Test
    {
        public static void main(String[] args)
        {
            // 調用 values()
            Color[] arr = Color.values();
            // 迭代枚舉
            for (Color col : arr)
            {
                // 查看索引
                System.out.println(col + " at index " + col.ordinal());
            }
            // 使用 valueOf() 返回枚舉常量,不存在的會報錯 IllegalArgumentException
            System.out.println(Color.valueOf("RED"));
            // System.out.println(Color.valueOf("WHITE"));
        }
    }
    
    RED at index 0
    GREEN at index 1
    BLUE at index 2
    RED
    

    枚舉成員

    枚舉跟普通類一樣可以用自己的變量、方法和構造函數,構造函數只能使用 private 訪問修飾符,所以外部無法調用。

    enum Color
    {
        RED, GREEN, BLUE;
     
        // 構造函數
        private Color()
        {
            System.out.println("Constructor called for : " + this.toString());
        }
        public void colorInfo()
        {
            System.out.println("Universal Color");
        }
    }
    

包(package)

為了更好地組織類,Java 提供了包機制,用於區別類名的命名空間。

包的 3 個作用如下

  1. 區分相同名稱的類。

  2. 能夠較好地管理大量的類。

  3. 控制訪問范圍。

定義

 ```java

package 包名;
```
Java 包的命名規則如下:

  • 包名全部由小寫字母(多個單詞也全部小寫)。
  • 如果包名包含多個層次,每個層次用“.”分割。
  • 包名一般由倒置的域名開頭,比如 com.baidu,不要有 www。
  • 自定義包不能 java 開頭

包導入

\\如果使用不同包中的其它類,需要使用該類的全名(包名+類名)
    example.Test test = new example.Test();
\\import 包名+類名;
import example.Test;\\or
import example.*;

系統包

說明
java.lang Java 的核心類庫,包含運行 Java 程序必不可少的系統類,如基本數據類型、基本數學函數、 字符串處理、異常處理和線程類等,系統默認加載這個包
java.io Java 語言的標准輸入/輸出類庫,如基本輸入/輸出流、文件輸入/輸出、過濾輸入/輸出流等
java.util 包含如處理時間的 Date 類,處理動態數組的 Vector 類,以及 Stack 和 HashTable 類
java.awt 構建圖形用戶界面(GUI)的類庫,低級繪圖操作 Graphics 類、圖形界面組件和布局管理 (如 Checkbox 類、Container 類、LayoutManger 接口等),以及用戶界面交互控制和事 件響應(如 Event 類)
java.awt.image 處理和操縱來自網上的圖片的 Java 工具類庫
java.wat.peer 很少在程序中直接用到,使得同一個 Java 程序在不同的軟硬件平台上運行
java.net 實現網絡功能的類庫有 Socket 類、ServerSocket 類
java.lang.reflect 提供用於反射對象的工具
java.util.zip 實現文件壓縮功能
java.awt.datatransfer 處理數據傳輸的工具類,包括剪貼板、字符串發送器等
java.sql 實現 JDBC 的類庫
java.rmi 提供遠程連接與載入的支持
java. security 提供安全性方面的有關支持

9.異常處理

異常處理的概念

是編程語言或計算機硬件里的一種機制,用於處理軟件或信息系統中出現的異常狀況(即超出程序正常執行流程的某些特殊條件)。

關鍵字

Java異常機制用到的幾個關鍵字:try、catch、finally、throw、throws。

try -- 用於監聽。將要被監聽的代碼(可能拋出異常的代碼)放在try語句塊之內,當try語句塊內發生異常
時,異常就被拋出。
catch -- 用於捕獲異常。catch用來捕獲try語句塊中發生的異常。
finally -- finally語句塊總是會被執行。它主要用於回收在try塊里打開的物力資源(如數據庫連接、網絡
連接和磁盤文件)。只有finally塊,執行完成之后,才會回來執行try或者catch塊中的return或者throw語
句,如果finally中使用了return或者throw等終止方法的語句,則就不會跳回執行,直接停止。
throw -- 用於拋出異常。
throws -- 用在方法簽名中,用於聲明該方法可能拋出的異常。

 try{
        可能會發生的異常
    }catch(異常類型 異常名(變量)){
        針對異常進行處理的代碼
    }catch(異常類型 異常名(變量)){
        針對異常進行處理的代碼
    }...
    [finally{
        釋放資源代碼;
    }]

Error與Exception區別

Error(錯誤)是系統中的錯誤,程序員是不能改變的和處理的,是在程序編譯時出現的錯誤,只能通過修改程序才能修正。一般是指與虛擬機相關的問題,如系統崩潰,虛擬機錯誤,內存空間不足,方法調用棧溢等。對於這類錯誤的導致的應用程序中斷,僅靠程序本身無法恢復和和預防,遇到這樣的錯誤,建議讓程序終止。
Exception(異常)表示程序可以處理的異常,可以捕獲且可能恢復。遇到這類異常,應該盡可能處理異常,使程序恢復運行,而不應該隨意終止異常。

throw與throws區別

throw:指的是在方法中人為拋出一個異常對象(這個異常對象可能是自己實例化或者拋出已存在的);
throw ThrowableInstance;
throws:在方法的聲明上使用,表示此方法在調用時必須處理異常。
throw new NullPointerException("demo");
image-20210728140018762

​ Java異常層次結構圖(網上獲取)

10.集合框架

所有集合類都位於 java.util 包下。Java的集合類主要由兩個接口派生而出:Collection 和 Map,Collection 和 Map 是 Java 集合框架的根接口,這兩個接口又包含了一些子接口或實現類。

集合框架被設計成要滿足以下幾個目標:

  • 該框架必須是高性能的。基本集合(動態數組,鏈表,樹,哈希表)的實現也必須是高效的。
  • 該框架允許不同類型的集合,以類似的方式工作,具有高度的互操作性。
  • 對一個集合的擴展和適應必須是簡單的。

集合框架都包含如下內容:

  • 接口:是代表集合的抽象數據類型。例如 Collection、List、Set、Map 等。之所以定義多個接口,是為了以不同的方式操作集合對象

  • 實現(類):是集合接口的具體實現。從本質上講,它們是可重復使用的數據結構,例如:ArrayList、LinkedList、HashSet、HashMap。

  • 算法:是實現集合接口的對象里的方法執行的一些有用的計算,例如:搜索和排序。這些算法被稱為多態,那是因為相同的方法可以在相似的接口上有着不同的實現。

    image-20210728172402028

Collection是一個基本的集合接口,Collection中可以容納一組集合元素(Element)

Collection 接口

Collection 是最基本的集合接口,一個 Collection 代表一組 Object,即 Collection 的元素, Java不提供直接繼承自Collection的類,只提供繼承於的子接口(如List和set)。

List

List接口是一個有序, 元素可重復的 Collection,使用此接口能夠精確的控制每個元素插入的位置,能夠通過索引(元素在List中位置,類似於數組的下標)來訪問List中的元素,第一個元素的索引為 0,而且允許有相同的元素。

  1. ArrayList

底層數據結構是數組,查改快,增刪慢。

非線程安全,效率高

方法:

image-20210728173620540

排序

import java.util.Collections;  // 引入 Collections 類
Collections.sort(sites); *// 字母排序*
  1. Vector

底層數據結構是數組,查改快,增刪慢。

線程安全,效率低

  1. LinkedList

底層數據結構是鏈表,查改慢,增刪快。

非線程安全,效率高

以下情況使用 LinkedList :

  • 你需要通過循環迭代來訪問列表中的某些元素。
  • 需要頻繁的在列表開頭、中間、末尾等位置進行添加和刪除元素操作。

LinkedList 繼承了 AbstractSequentialList 類。

LinkedList 實現了 Queue 接口,可作為隊列使用。

LinkedList 實現了 List 接口,可進行列表的相關操作。

LinkedList 實現了 Deque 接口,可作為隊列使用。

LinkedList 實現了 Cloneable 接口,可實現克隆。

LinkedList 實現了 java.io.Serializable 接口,即可支持序列化,能通過序列化去傳輸。

方法:

image-20210728174512617

Set

Set 接口存儲一組唯一,無序的對象。

  1. HashSet

底層數據結構是哈希表。(無序,唯一)

依賴兩個方法:hashCode()和equals() 保證元素唯一性

// 引入 HashSet 類      
import java.util.HashSet;

public class RunoobTest {
    public static void main(String[] args) {
    HashSet<String> sites = new HashSet<String>();
        sites.add("Google");
        sites.add("Runoob");
        sites.add("Taobao");
        sites.add("Zhihu");
        sites.add("Runoob");  // 重復的元素不會被添加
        System.out.println(sites);
    }
}

以上代碼只會輸出一個Runoob。

  1. LinkedHashSet

底層數據結構是鏈表和哈希表。(FIFO插入有序,唯一)

1.由鏈表保證元素有序

2.由哈希表保證元素唯一

  1. TreeSet

底層數據結構是紅黑樹。(唯一,有序)

如何保證元素排序的呢? 自然排序,比較器排序

Set和List的區別

  • Set 接口實例存儲的是無序的,不重復的數據。List 接口實例存儲的是有序的,可以重復的元素。
  • Set檢索效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變 <實現類有HashSet,TreeSet>
  • List和數組類似,可以動態增長,根據實際存儲的數據的長度自動增長List的長度。查找元素效率高,插入刪除效率低,因為會引起其他元素位置改變 <實現類有ArrayList,LinkedList,Vector>

image-20210728172343233

Map與Collection是並列關系。Map提供鍵(key)到值(value)的映射。一個Map中不能包含相同的鍵,每個鍵只能映射一個值。

  1. HashMap

無序,非線程安全,效率高。HashMap允許null值(key和value都允許)。

image-20210728174945196

  1. HashTable

無序,線程安全,效率低。除構造函數外,HashTable的所有 public 方法聲明中都有 synchronized關鍵字,而HashMap的源碼中則沒有。HashTable不允許null值(key和value都允許)。

  1. TreeMap

有序,非線程安全,效率高(O(logN)),但比不上HashMap (O(1))。

11.流(Stream)、文件(File)和IO

Java.io 包中定義了多個流類型(類或抽象類)來實現輸入/輸出功能;

img

可以從不同的角度對其進行分
類:
1.按數據流的方向不同可以分為輸入流【InputStream(字節流),Reader(字符流)】和輸出流【OutPutStream(字節流),Writer(字符流)】
2.按照處理數據單位不同可以分為字節流【一個字節(Byte)是8位(bit))】和字符流【一個字符是2個字節】
3.按照功能不同可以分為節點流和處理流

image-20210905011146261

4.按照操作對象分

img

InputStream 和 OutputStream

img

img

import java.io.*;
 
public class fileStreamTest {
    public static void main(String[] args) {
        try {
            byte bWrite[] = { 11, 21, 3, 40, 5 };
            OutputStream os = new FileOutputStream("test.txt");
            for (int x = 0; x < bWrite.length; x++) {
                os.write(bWrite[x]); // writes the bytes
            }
            os.close();
 
            InputStream is = new FileInputStream("test.txt");
            int size = is.available();
 
            for (int i = 0; i < size; i++) {
                System.out.print((char) is.read() + "  ");
            }
            is.close();
        } catch (IOException e) {
            System.out.print("Exception");
        }
    }
}

上面的程序首先創建文件test.txt,並把給定的數字以二進制形式寫進該文件,同時輸出到控制台上。

以上代碼由於是二進制寫入,可能存在亂碼,你可以使用以下代碼實例來解決亂碼問題:

//文件名 :fileStreamTest2.java
import java.io.*;
 
public class fileStreamTest2 {
    public static void main(String[] args) throws IOException {
        File f = new File("a.txt");
        FileOutputStream fop = new FileOutputStream(f);
        // 構建FileOutputStream對象,文件不存在會自動新建
        OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
        // 構建OutputStreamWriter對象,參數可以指定編碼,默認為操作系統默認編碼,windows上是gbk
        writer.append("中文輸入");
        // 寫入到緩沖區
        writer.append("\r\n");
        // 換行
        writer.append("English");
        // 刷新緩存沖,寫入到文件,如果下面已經沒有寫入的內容了,直接close也會寫入
        writer.close();
        // 關閉寫入流,同時會把緩沖區內容寫入文件,所以上面的注釋掉
        fop.close();
        // 關閉輸出流,釋放系統資源
        FileInputStream fip = new FileInputStream(f);
        // 構建FileInputStream對象
        InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
        // 構建InputStreamReader對象,編碼與寫入相同
        StringBuffer sb = new StringBuffer();
        while (reader.ready()) {
            sb.append((char) reader.read());
            // 轉成char加到StringBuffer對象中
        }
        System.out.println(sb.toString());
        reader.close();
        // 關閉讀取流
        fip.close();
        // 關閉輸入流,釋放系統資源
 
    }
}

Reader 流與Writer流

img

img

Reader ,Write與InputStream ,OutputStream: 唯一的區別就在於讀的數據單位不同分別為(16bit),(8bit)

創建讀取目錄:

import java.io.File;
 
public class CreateDir {
    public static void main(String[] args) {
        String dirname = "/tmp/user/java/bin";
        File d = new File(dirname);
        // 現在創建目錄
        d.mkdirs();
    }
}
import java.io.File;
 
public class DirList {
    public static void main(String args[]) {
        String dirname = "/tmp";
        File f1 = new File(dirname);
        if (f1.isDirectory()) {
            System.out.println("目錄 " + dirname);
            String s[] = f1.list();
            for (int i = 0; i < s.length; i++) {
                File f = new File(dirname + "/" + s[i]);
                if (f.isDirectory()) {
                    System.out.println(s[i] + " 是一個目錄");
                } else {
                    System.out.println(s[i] + " 是一個文件");
                }
            }
        } else {
            System.out.println(dirname + " 不是一個目錄");
        }
    }
}

刪除

import java.io.File;
 
public class DeleteFileDemo {
    public static void main(String[] args) {
        // 這里修改為自己的測試目錄
        File folder = new File("/tmp/java/");
        deleteFolder(folder);
    }
 
    // 刪除文件及目錄
    public static void deleteFolder(File folder) {
        File[] files = folder.listFiles();
        if (files != null) {
            for (File f : files) {
                if (f.isDirectory()) {
                    deleteFolder(f);
                } else {
                    f.delete();
                }
            }
        }
        folder.delete();
    }
}

緩存流

是處理流的一種,它是要“套接”在相應的節點流之上,對讀寫的數據提供了緩沖的功能,避免頻繁讀寫硬盤, 提高了讀寫的效率。同時增加了一些新的方法。

BufferedReader(Reader in)
BufferedReader(Reader in,int sz) //sz 為自定義緩沖區的大小
BufferedWriter(Writer out)
BufferedWriter(Writer out,int sz)
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in,int size)
BufferedOutputStream(InputStream in)
BufferedOutputStream(InputStream in,int size)

BufferedInputStream

package com.kuang.chapter;
import java.io.*;
public class TestBufferStream {
public static void main(String args[]) {
FileInputStream fis = null;
File f = new File("a.txt");
try {
fis = new FileInputStream( f);
// 在FileInputStream節點流的外面套接一層處理流BufferedInputStream
BufferedInputStream bis = new BufferedInputStream(fis);
int c = 0;
System.out.println((char) bis.read());
System.out.println((char) bis.read());
bis.mark(100);// 在第100個字符處做一個標記
for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
System.out.print((char) c);
}
System.out.println();
bis.reset();// 重新回到原來標記的地方
for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
System.out.print((char) c);
}
bis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}

BufferedReader

package com.kuang.chapter;
import java.io.*;
public class TestBufferStream{
public static void main(String args[]){
try{
BufferedWriter bw = new BufferedWriter(new FileWriter("a\\Student.txt"));
//在節點流FileWriter的外面再套一層處理流BufferedWriter
String s = null;
for(int i=0;i<100;i++){
s = String.valueOf(Math.random());//“Math.random()”將會生成一系列介於0~1之間的隨機數。
// static String valueOf(double d)這個valueOf()方法的作用就是把
一個double類型的數轉換成字符串
//valueOf()是一個靜態方法,所以可以使用“類型.靜態方法名”的形式來調用
bw.write(s);//把隨機數字符串寫入到指定文件中
bw.newLine();//調用newLine()方法使得每寫入一個隨機數就換行顯示
}
bw.flush();//調用flush()方法清空緩沖區
BufferedReader br = new BufferedReader(new FileReader("a:\\Student.txt"));
//在節點流FileReader的外面再套一層處理流BufferedReader
while((s = br.readLine())!=null){
//使用BufferedReader處理流里面提供String readLine()方法讀取文件中的數據時是一行一行讀取的
//循環結束的條件就是使用readLine()方法讀取數據返回的字符串為空值后則表
示已經讀取到文件的末尾了。
System.out.println(s);
}
bw.close();
br.close();
}catch(Exception e){
e.printStackTrace();
}
}
}

轉換流

InputStreamReader 和 OutputStreamWriter 用於字節數據到字符數據之間的轉換
InputStreamReader 需要和 InputStream “套接” 。
OutputStreamWriter 需要和 OutputStream “套接” 。
轉換流在構造時可以指定其編碼集合

import java.io.*;
public class TestTransform1 {
public static void main(String args[]) {
try {
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:/char.txt"));
    osw.write("熊方園真煩人");// 把字符串寫入到指定的文件中去
    System.out.println(osw.getEncoding());// 使用getEncoding()方法取得當前系統的默認字符編碼
    osw.close();
    osw = new OutputStreamWriter(new FileOutputStream("D:\\java\\char.txt", true), "utf-8");// 如果在調用FileOutputStream的構造方法時沒有加入true,那么新加入的字符    串就會替換掉原來寫入的字符串,在調用構造方法時指定了字符的編碼
    osw.write("不想搭理她");// 再次向指定的文件寫入字符串,新寫入的字符串加入到原來字符串的后面
    System.out.println(osw.getEncoding());
    osw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

數據流

數據流 DataInputStream DataOutputStream 【分別繼承自InputStream 和 OutputStream】等-提供將基礎數據類型寫入到文件中,或者讀取出來.提供了可以存取與機器無關的Java原始類型數據(int,double等)的方法。

public static void main(String args[]){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//在調用構造方法時,首先會在內存里面創建一個ByteArray字節數組
DataOutputStream dos = new DataOutputStream(baos);
//在輸出流的外面套上一層數據流,用來處理int,double類型的數
try{
    dos.writeDouble(Math.random());//把產生的隨機數直接寫入到字節數組
    ByteArray中
    dos.writeBoolean(true);//布爾類型的數據在內存中就只占一個字節
    ByteArrayInputStream bais = new
    ByteArrayInputStream(baos.toByteArray());
        System.out.println(bais.available());
    DataInputStream dis = new DataInputStream(bais);
    System.out.println(dis.readDouble());//先寫進去的就先讀出來,調用readDouble()方法讀取出寫入的隨機數
    System.out.println(dis.readBoolean());//后寫進去的就后讀出來,這里面的讀取順序不能更改位置,否則會打印出不正確的結果
    dos.close();
    bais.close();
}catch(Exception e){
e.printStackTrace();
}
}

打印流

打印流是輸出信息最方便的類,注意包含字節打印流PrintStream和字符打印流:PrintWriter。打印流提供了非常方便的打印功能,
可以打印任何類型的數據信息,例如:小數,整數,字符串。


對象流

對象的輸入輸出流的作用: 用於寫入對象 的信息和讀取對象的信息。 使得對象持久化。
ObjectInputStream : 對象輸入流
ObjectOutPutStream :對象輸出流

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

//創建要寫入磁盤的類,這個類需要實現接口 Serializable(可系列化的)
class Student implements Serializable{
    // 在這里保證了serialVersionUID 的唯一性,防止屬性變量的臨時改變,從而造成寫入id與讀取id不同
    private static final long serialVersionUID = 1L;
    int id ; //額外需要添加一個屬性
    String name ;
    transient String sex; //transient修飾屬性,表示暫時的,則這個屬性不會被寫入磁盤
    transient int age;
    public Student(String name,String sex,int age){
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
}

public class objectIO {

    /**
     * @param args
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // TODO Auto-generated method stub

        createObj();
        readObj();
    }
    //(一)先寫入對象
    public static void createObj() throws IOException {
        //1.創建目標路徑
        File file = new File("C:\\Users\\bg\\Desktop\\objTest.txt");
        //2.創建流通道
        FileOutputStream fos = new FileOutputStream(file);
        //3.創建對象輸出流
        ObjectOutputStream objOP = new ObjectOutputStream(fos);
        //4.創建類對象,並初始化
        Student stu = new Student("瑪麗蘇", "男", 18);
        //5.向目標路徑文件寫入對象
        objOP.writeObject(stu);
        //6.關閉資源
        objOP.close();
    }
    //再讀取對象
    public static void readObj() throws IOException, ClassNotFoundException {
        File file = new File("C:\\Users\\bg\\Desktop\\objTest.txt");
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream objIP = new ObjectInputStream(fis);
        //讀取對象數據,需要將對象流強制轉換為 要寫入對象的類型
        Student stu = (Student)objIP.readObject();
        System.out.println("\n name:"+stu.name+"\n sex:"+stu.sex+"\n age:"+stu.age);
        objIP.close();
    }
}

流的關閉順序

  1. 一般情況下是:先打開的后關閉,后打開的先關閉
  2. 另一種情況:看依賴關系,如果流a依賴流b,應該先關閉流a,再關閉流b。例如,處理流a依賴節點流b,應該先關閉處理流a,再關閉節點流b
  3. 可以只關閉處理流,不用關閉節點流。處理流關閉的時候,會調用其處理的節點流的關閉方法。

12.多線程

進程與線程

線程的創建

繼承Thread類,實現Runnable接口,實現Callable接口

1.繼承Thread類

public class ThreadCreateDemo1 {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); //調用start()方法啟動線程,線程不一定立即執行,CPU安排調度
    }
}
class MyThread extends Thread {//繼承Thread類
    @Override
    public void run() {//重寫run()方法,編寫線程執行體
        super.run();
        System.out.println("hellow_world!");
    }
}

2.實現Runnable接口

public class ThreadCreateDemo2 {
    //創建線程對象,調用start()方法啟動線程
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("通過Runnable創建的線程!");
    }
}

上述兩種創建方式,工作時性質一樣。但是建議使用*實現Runable接口*方式。解決單繼承的局限性。

3.實現Callable接口

public class ThreadCreateDemo3 implements Callable<Integer>{
    // 實現call方法,作為線程執行體
    public Integer call(){
        int i = 0;
        for ( ; i < 100 ; i++ ){
            System.out.println(Thread.currentThread().getName()+ "\t" + i);
        }
        // call()方法可以有返回值
        return i;
    }
    public static void main(String[] args) {
        // 創建Callable對象
        ThreadCreateDemo3 myCallableTest = new ThreadCreateDemo3();
        // 使用FutureTask來包裝Callable對象
        FutureTask<Integer> task = new FutureTask<Integer>(myCallableTest);
        for (int i = 0 ; i < 100 ; i++){
            System.out.println(Thread.currentThread().getName()+ " \t" + i);
            if (i == 20){
                // 實質還是以Callable對象來創建、並啟動線程
                new Thread(task , "callable").start();
            }
        }
        try{
            // 獲取線程返回值
            System.out.println("callable返回值:" + task.get());
        }
        catch (Exception ex){
            ex.printStackTrace();
        }
    }
}
  1. 實現Callable接口,需要返回值類型

  2. 重寫call方法,需要拋出異常

  3. 創建目標對象

  4. 創建執行服務:ExecutorService ser = Executors.newFixedThreadPool(1);

  5. 提交執行:Future result1 = ser.submit(t1);

  6. 獲取結果:boolean r1 = result1.get()

  7. 關閉服務:ser.shutdownNow();
    總結

  8. 不過實現Runnable接口與實現Callable接口的方式基本相同,只是Callable接口里定義的方法有返回值,可以聲明拋出異常而已。 因此可以將實現Runnable接口和實現Callable接口歸為一種方式。

  9. Runnable、Callable接口的方式創建多線程,所以非常適合多個相同線程來處理同一份資源的情況,如果需要訪問當前線程,則必須使用Thread.currentThread()方法

  10. 采用繼承Thread類的方式創建多線程,因為線程類已經繼承了Thread類,所以不能再繼承其他父類

生命周期

線程被創建並啟動以后,它既不是一啟動就進入了執行狀態,也不是一直處於執行狀態新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態

下載

image-20210809153146504
Thread.State:

  1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。

  2. 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱為“運行”。
    線程對象創建后,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片后變為運行中狀態(running)。

  3. 阻塞(BLOCKED):表示線程阻塞於鎖。

  4. 等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。

  5. 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間后自行返回。

  6. 終止(TERMINATED):表示該線程已經執行完畢

線程的優先級

Java提供一個線程調度器來監控程序中啟動后進入就緒狀態的所有線程,線程調度
器按照優先級決定應該調度哪個線程來執行。
線程的優先級用數字表示,范圍從1~10.
hread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
使用以下方式改變或獲取優先級
getPriority() . setPriority(int xxx)

線程方法

1 public void start() 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
2 public void run() 如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作並返回。
3 public final void setName(String name) 改變線程名稱,使之與參數 name 相同。
4 public final void setPriority(int priority) 更改線程的優先級。
5 public final void setDaemon(boolean on) 將該線程標記為守護線程或用戶線程。
6 public final void join(long millisec) 等待該線程終止的時間最長為 millis 毫秒。
7 public void interrupt() 中斷線程。
8 public final boolean isAlive() 測試線程是否處於活動狀態。
9 public static void yield() 線程禮讓: 暫停當前正在執行的線程對象,並執行其他線程。
10 public static void sleep(long millisec) 線程休眠: 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和准確性的影響。
11 public static boolean holdsLock(Object x) 當且僅當當前線程在指定的對象上保持監視器鎖時,才返回 true。
12 public static Thread currentThread() 返回對當前正在執行的線程對象的引用。
13 public static void dumpStack() 將當前線程的堆棧跟蹤打印至標准錯誤流。

停止線程:jdk提供了stop,但不建議使用可以自己去停止它

守護(daemon)線程

線程分為前台線程與后台線程(用戶線程與守護線程)
虛擬機必須確保用戶線程執行完畢
虛擬機不用等待守護線程執行完畢

並發,隊列 和 鎖,死鎖

同一個對象被多個線程同時操作就是並發。

多個線程訪問同一個對象, 並且某些線程還想修改這個對象 .這時候我們就需要線程同步 . 線程同步其實就是一種等待機制 , 多個需要同時訪問此對象的線程進入這個對象的等待池 形成隊列, 等待前面線程使用完畢 , 下一個線
程再使用。

上面的並發問題我們會加一個鎖(synchronized)來解決。我鎖上門的時候你們都別進來。但是加上鎖之后會有以下為:

  1. 一個線程持有鎖會導致其他所有需要此鎖的線程掛起 ;

  2. 在多線程競爭下 , 加鎖 , 釋放鎖會導致比較多的上下文切換 和 調度延時,引起性能問題 ;

  3. 如果一個優先級高的線程等待一個優先級低的線程釋放鎖 會導致優先級倒置 , 引起性能問題 .

    java 死鎖產生的四個必要條件:

  • 1、互斥使用,即當資源被一個線程使用(占有)時,別的線程不能使用

  • 2、不可搶占,資源請求者不能強制從資源占有者手中奪取資源,資源只能由資源占有者主動釋放。

  • 3、請求和保持,即當資源請求者在請求其他的資源的同時保持對原有資源的占有。

  • 4、循環等待,即存在一個等待隊列:P1占有P2的資源,P2占有P3的資源,P3占有P1的資源。這樣就形成了一個等待環路。

    死鎖的情況下如果打破上述任何一個條件,便可讓死鎖消失。

import java.util.Date;
 
public class LockTest {
   public static String obj1 = "obj1";
   public static String obj2 = "obj2";
   public static void main(String[] args) {
      LockA la = new LockA();
      new Thread(la).start();
      LockB lb = new LockB();
      new Thread(lb).start();
   }
}
class LockA implements Runnable{
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockA 開始執行");
         while(true){
            synchronized (LockTest.obj1) {
               System.out.println(new Date().toString() + " LockA 鎖住 obj1");
               Thread.sleep(3000); // 此處等待是給B能鎖住機會
               synchronized (LockTest.obj2) {
                  System.out.println(new Date().toString() + " LockA 鎖住 obj2");
                  Thread.sleep(60 * 1000); // 為測試,占用了就不放
               }
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
class LockB implements Runnable{
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockB 開始執行");
         while(true){
            synchronized (LockTest.obj2) {
               System.out.println(new Date().toString() + " LockB 鎖住 obj2");
               Thread.sleep(3000); // 此處等待是給A能鎖住機會
               synchronized (LockTest.obj1) {
                  System.out.println(new Date().toString() + " LockB 鎖住 obj1");
                  Thread.sleep(60 * 1000); // 為測試,占用了就不放
               }
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

結果

Tue May 05 10:51:06 CST 2015 LockB 開始執行
Tue May 05 10:51:06 CST 2015 LockA 開始執行
Tue May 05 10:51:06 CST 2015 LockB 鎖住 obj2
Tue May 05 10:51:06 CST 2015 LockA 鎖住 obj1

解決

import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
 
public class UnLockTest {
   public static String obj1 = "obj1";
   public static final Semaphore a1 = new Semaphore(1);
   public static String obj2 = "obj2";
   public static final Semaphore a2 = new Semaphore(1);
 
   public static void main(String[] args) {
      LockAa la = new LockAa();
      new Thread(la).start();
      LockBb lb = new LockBb();
      new Thread(lb).start();
   }
}
class LockAa implements Runnable {
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockA 開始執行");
         while (true) {
            if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
               System.out.println(new Date().toString() + " LockA 鎖住 obj1");
               if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
                  System.out.println(new Date().toString() + " LockA 鎖住 obj2");
                  Thread.sleep(60 * 1000); // do something
               }else{
                  System.out.println(new Date().toString() + "LockA 鎖 obj2 失敗");
               }
            }else{
               System.out.println(new Date().toString() + "LockA 鎖 obj1 失敗");
            }
            UnLockTest.a1.release(); // 釋放
            UnLockTest.a2.release();
            Thread.sleep(1000); // 馬上進行嘗試,現實情況下do something是不確定的
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
class LockBb implements Runnable {
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockB 開始執行");
         while (true) {
            if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
               System.out.println(new Date().toString() + " LockB 鎖住 obj2");
               if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
                  System.out.println(new Date().toString() + " LockB 鎖住 obj1");
                  Thread.sleep(60 * 1000); // do something
               }else{
                  System.out.println(new Date().toString() + "LockB 鎖 obj1 失敗");
               }
            }else{
               System.out.println(new Date().toString() + "LockB 鎖 obj2 失敗");
            }
            UnLockTest.a1.release(); // 釋放
            UnLockTest.a2.release();
            Thread.sleep(10 * 1000); // 這里只是為了演示,所以tryAcquire只用1秒,而且B要給A讓出能執行的時間,否則兩個永遠是死鎖
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
Tue May 05 10:59:13 CST 2015 LockA 開始執行
Tue May 05 10:59:13 CST 2015 LockB 開始執行
Tue May 05 10:59:13 CST 2015 LockB 鎖住 obj2
Tue May 05 10:59:13 CST 2015 LockA 鎖住 obj1
Tue May 05 10:59:14 CST 2015LockB 鎖 obj1 失敗
Tue May 05 10:59:14 CST 2015LockA 鎖 obj2 失敗
Tue May 05 10:59:15 CST 2015 LockA 鎖住 obj1
Tue May 05 10:59:15 CST 2015 LockA 鎖住 obj2
  1. synchronized 與 Lock 的對比
    Lock是顯式鎖(手動開啟和關閉鎖,別忘記關閉鎖)synchronized是隱式鎖,出了作用域自動釋放
  2. Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖使用Lock鎖,JVM將花費較少的時間來調度線程,性能更好。並且具有更好的擴展性(提供更多的子類)
  3. 優先使用順序:
    Lock > 同步代碼塊(已經進入了方法體,分配了相應資源)> 同步方法(在方
    法體之外)

線程通訊

線程通信的目標是使線程間能夠互相發送信號。另一方面,線程通信使線程能夠等待其他線程的信號。

線程的通信方式

  1. volatile
  2. Wait/Notify機制
  3. join方式
  4. threadLocal
  5. CountDownLatch 並發工具
  6. CyclicBarrier 並發工具

volatile

public class Volatile implements Runnable {
  private static volatile Boolean flag = true;

  @Override
  public void run() {
    while (flag) {
      System.out.println(Thread.currentThread().getName() + " - 執行");
    }
    System.out.println("線程結束");
  }

  public static void main(String[] args) {
    Thread t = new Thread(new Volatile());
    t.start();
    try {
      Thread.sleep(5);
      flag = false;
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
Thread-0 - 執行
Thread-0 - 執行
Thread-0 - 執行
Thread-0 - 執行
Thread-0 - 執行
線程結束

**WaitNotify **


public class WaitNotify {
  // 狀態鎖
  private static Object lock = new Object();
  private static Integer i = 0;

  public void odd() {
    while (i < 10) {
      synchronized (lock) {
        if (i % 2 == 1) {
          System.out.println(Thread.currentThread().getName() + " - " + i);
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          i++;
          lock.notify();
        } else {
          try {
            lock.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

  public void even() {
    while (i < 10) {
      synchronized (lock) {
        if (i % 2 == 0) {
          System.out.println(Thread.currentThread().getName() + " - " + i);
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          i++;
          lock.notify();
        } else {
          try {
            lock.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

  public static void main(String[] args) {

    WaitNotify waitNotify = new WaitNotify();

    Thread t1 = new Thread(() -> waitNotify.odd(), "線程1");
    Thread t2 = new Thread(() -> waitNotify.even(), "線程2");

    t1.start();
    t2.start();
  }
}

join

package threadCommunication;
 
public class JoinTest extends Thread {
    @Override
    public void run() {
        try {
            int sleepTime = (int) (Math.random() * 1000);
            System.out.println(sleepTime);
            Thread.sleep(sleepTime);
            System.out.println("JoinTest end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        JoinTest j = new JoinTest();
        j.start();
        j.join();//當前線程main等待線程對象(j)銷毀
        System.out.println("main end");
    }

threadLocal

package sync; 
public class SequenceNumber { 
 // 定義匿名子類創建ThreadLocal的變量 
 private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() { 
 // 覆蓋初始化方法 
 public Integer initialValue() { 
 		return 0; 
 	} 
 }; 
 // 下一個序列號 
 public int getNextNum() { 
     seqNum.set(seqNum.get() + 1); 
     return seqNum.get(); 
 } 
 private static class TestClient extends Thread { 
     private SequenceNumber sn; 
     public TestClient(SequenceNumber sn) { 
     this.sn = sn; 
 } 
 // 線程產生序列號 
 public void run() { 
     for (int i = 0; i < 3; i++) { 
         System.out.println("thread[" + Thread.currentThread().getName() + "] sn[" + 			sn.getNextNum() + "]"); 
         } 
 	} 
 } 
 /** 
 * @param args 
 */ 
 public static void main(String[] args) { 
     SequenceNumber sn = new SequenceNumber(); 
     // 三個線程產生各自的序列號 
     TestClient t1 = new TestClient(sn); 
     TestClient t2 = new TestClient(sn); 
     TestClient t3 = new TestClient(sn); 
     t1.start(); 
     t2.start(); 
     t3.start(); 
 } 
}
thread[Thread-1] sn[1] 
thread[Thread-1] sn[2] 
thread[Thread-1] sn[3] 
thread[Thread-2] sn[1] 
thread[Thread-2] sn[2] 
thread[Thread-2] sn[3] 
thread[Thread-0] sn[1]
thread[Thread-0] sn[2] 
thread[Thread-0] sn[3]

**CountDownLatch **CountDownLatch可以代替wait/notify的使用,並去掉synchronized

import java.util.concurrent.CountDownLatch;

public class CountDown {
  private static Integer i = 0;
  final static CountDownLatch countDown = new CountDownLatch(1);

  public void odd() {
    while (i < 10) {
      if (i % 2 == 1) {
        System.out.println(Thread.currentThread().getName() + " - " + i);
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        i++;
        countDown.countDown();
      } else {
        try {
          countDown.await();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }

  public void even() {
    while (i < 10) {
      if (i % 2 == 0) {
        System.out.println(Thread.currentThread().getName() + " - " + i);
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        i++;
        countDown.countDown();
      } else {
        try {
          countDown.await();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }

  public static void main(String[] args) {

    CountDown countDown = new CountDown();

    Thread t1 = new Thread(() -> countDown.odd(), "線程1");
    Thread t2 = new Thread(() -> countDown.even(), "線程2");

    t1.start();
    t2.start();
  }
}

CyclicBarrier

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest {
  public static void main(String[] args) {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
    new Thread(() -> {
      System.out.println(Thread.currentThread().getName() + ": 准備...");
      try {
        cyclicBarrier.await();
      } catch (InterruptedException e) {
        e.printStackTrace();
      } catch (BrokenBarrierException e) {
        e.printStackTrace();
      }
      System.out.println("全部啟動完畢!");
    }, "線程1").start();

    new Thread(() -> {
      System.out.println(Thread.currentThread().getName() + ": 准備...");
      try {
        cyclicBarrier.await();
      } catch (InterruptedException e) {
        e.printStackTrace();
      } catch (BrokenBarrierException e) {
        e.printStackTrace();
      }
      System.out.println("全部啟動完畢!");
    }, "線程2").start();

    new Thread(() -> {
      System.out.println(Thread.currentThread().getName() + ": 准備...");
      try {
        cyclicBarrier.await();
      } catch (InterruptedException e) {
        e.printStackTrace();
      } catch (BrokenBarrierException e) {
        e.printStackTrace();
      }
      System.out.println("全部啟動完畢!");
    }, "線程3").start();
  }
}
線程3: 准備...
線程2: 准備...
線程1: 准備...
全部啟動完畢!
全部啟動完畢!
全部啟動完畢!

線程池

經常創建和銷毀、使用量特別大的資源,比如並發情況下的線程,對性能影響很大。提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷毀、實現重復利用。

ExecutorService 和 Executors

  1. ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
    void execute(Runnable command) :執行任務/命令,沒有返回值,一般用來執
    行Runnable

  2. Future submit(Callable task):執行任務,有返回值,一般又來執行
    Callable

  3. void shutdown() :關閉連接池

Executors:工具類、線程池的工廠類,用於創建並返回不同類型的線程池

13.注解

Java 注解(Annotation)又稱 Java 標注,是 JDK5.0 引入的一種注釋機制。

作用

不是程序本身 , 可以對程序作出解釋.(這一點和注釋(comment)沒什么區別)

可以被其他程序(比如:編譯器等)讀取.

可以附加在package , class , method , field 等上面 , 相當於給他們添加了額外的輔助信息

我們可以通過反射機制實現對這些元數據的訪問

格式

注解是以"@注釋名"在代碼中存在的

還可以添加一些參數值 , 例如:@SuppressWarnings(value="unchecked")

內置注解

image-20210809164852100
Java 定義了一套注解,共有 10 個,java7之前3 個在 java.lang 中, 4 個在 java.lang.annotation 中,后續增加三個

作用在代碼的注解( java.lang )是
@Override - 檢查該方法是否是重寫方法。如果發現其父類,或者是引用的接口中並沒有該方法時,會報編譯錯誤。
@Deprecated - 標記過時方法。如果使用該方法,會報編譯警告。
@SuppressWarnings - 指示編譯器去忽略注解中聲明的警告。
作用在其他注解的注解(或者說 元注解)是( java.lang.annotation)注解
@Retention - 標識這個注解怎么保存,是只在代碼中,還是編入class文件中,或者是在運行時可以通過反射訪問。
@Documented - 標記這些注解是否包含在用戶文檔中。
@Target - 標記這個注解應該是哪種 Java 成員。
@Inherited - 標記這個注解是繼承於哪個注解類(默認 注解並沒有繼承於任何子類)
java7之后增加的注解
@SafeVarargs - Java 7 開始支持,忽略任何使用參數為泛型變量的方法或構造函數調用產生的警告。
@FunctionalInterface - Java 8 開始支持,標識一個匿名函數或函數式接口。
@Repeatable - Java 8 開始支持,標識某注解可以在同一個聲明上使用多次。
package com.annotation;
//測試內置注解
import java.util.ArrayList;
import java.util.List;
//所有類默認繼承Object類
public class Test1 extends Object {
    //@Override 表示方法重寫
    //--> 查看JDK幫助文檔
    //--> 測試名字不同產生的效果
    @Override
    public String toString() {
    	return super.toString();
    }
     //方法過時了, 不建議使用 , 可能存在問題 , 並不是不能使用!
    //--> 查看JDK幫助文檔
    @Deprecated
    public static void stop(){
    	System.out.println("測試 @Deprecated");
    }
    //@SuppressWarnings 抑制警告 , 可以傳參數
    //--> 查看JDK幫助文檔
    //查看源碼:發現 參數類型 和 參數名稱 , 並不是方法!
    @SuppressWarnings("all")
    public void sw(){
   	 List list = new ArrayList();
    }
    public static void main(String[] args) {
   	 stop();
    }
}   

元注解

package com.annotation;
import java.lang.annotation.*;
//測試元注解
public class Test2 {
@MyAnnotation
public void test(){
}
}
//定義一個注解
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited
@Documented
@interface MyAnnotation{
}

自定義注解

  1. 使用 @interface自定義注解時 , 自動繼承了java.lang.annotation.Annotation接口
    @ interface用來聲明一個注解 , 格式 : public @ interface 注解名 { 定義內容 }

  2. 其中的每一個方法實際上是聲明了一個配置參數.
    方法的名稱就是參數的名稱.

  3. 返回值類型就是參數的類型 ( 返回值只能是基本類型,Class , String , enum ).
    可以通過default來聲明參數的默認值

  4. 如果只有一個參數成員 , 一般參數名為value

  5. 注解元素必須要有值 , 我們定義注解元素時 , 經常使用空字符串,0作為默認值 .

注解參數的可支持數據類型:
1.所有基本數據類型(int,float,boolean,byte,double,char,long,short)
2.String類型
3.Class類型
4.enum類型
5.Annotation類型
6.以上所有類型的數組

package annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 水果名稱注解
 * @author peida
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default "";
}
package annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 水果顏色注解
 * @author peida
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * 顏色枚舉
     * @author peida
     *
     */
    public enum Color{ BULE,RED,GREEN};
    
    /**
     * 顏色屬性
     * @return
     */
    Color fruitColor() default Color.GREEN;

}
package annotation;

import annotation.FruitColor.Color;

public class Apple {
    
    @FruitName("Apple")
    private String appleName;
    
    @FruitColor(fruitColor=Color.RED)
    private String appleColor;
    
    
    
    
    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }
    public String getAppleColor() {
        return appleColor;
    }
    
    
    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }
    public String getAppleName() {
        return appleName;
    }
    
    public void displayName(){
        System.out.println("水果的名字是:蘋果");
    }
}

//設置默認值
package annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 水果供應者注解
 * @author peida
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供應商編號
     * @return
     */
    public int id() default -1;

    /**
     * 供應商名稱
     * @return
     */
    public String name() default "";

    /**
     * 供應商地址
     * @return
     */
    public String address() default "";
}

14.反射

反射機制

image-20210809170425919

Java的反射機制的實現要借助於4個類:class,Constructor,Field,Method;
其中class代表的時類對 象,Constructor-類的構造器對象,Field-類的屬性對象,Method-類的方法對象。通過這四個對象我們可以粗略的看到一個類的各個組 成部分。

獲取類的方法

//調用運行時類本身的.class屬性
Class clazz = String.class;

//通過運行時類的對象獲取
Person p = new Person();

Class clazz = p.getClass();

//通過Class的靜態方法獲取:體現反射的動態性
String className = “java.util.commons”;

Class clazz = Class.forName(className);

//通過類的加載器
String className = “java.util.commons”;

ClassLoader classLoader = this.getClass().getClassLoader();

Class clazz = classLoader.loadClass(className);

得到構造器的方法

Constructor getConstructor(Class[] params) -- 獲得使用特殊的參數類型的公共構造函數, 
 
Constructor[] getConstructors() -- 獲得類的所有公共構造函數 
 
Constructor getDeclaredConstructor(Class[] params) -- 獲得使用特定參數類型的構造函數(與接入級別無關) 
 
Constructor[] getDeclaredConstructors() -- 獲得類的所有構造函數(與接入級別無關)

獲取字段

Field getField(String name) -- 獲得命名的公共字段 
 
Field[] getFields() -- 獲得類的所有公共字段 
 
Field getDeclaredField(String name) -- 獲得類聲明的命名的字段 
 
Field[] getDeclaredFields() -- 獲得類聲明的所有字段

獲取方法的信息

Method getMethod(String name, Class[] params) -- 使用特定的參數類型,獲得命名的公共方法 
 
Method[] getMethods() -- 獲得類的所有公共方法 
 
Method getDeclaredMethod(String name, Class[] params) -- 使用特寫的參數類型,獲得類聲明的命名的方法 
 
Method[] getDeclaredMethods() -- 獲得類聲明的所有方法

通過 Class 類獲取成員變量、成員方法、接口、超類、構造方法等

package com.ys.reflex;
public class Person {
    //私有屬性
    private String name = "Tom";
    //公有屬性
    public int age = 18;
    //構造方法
    public Person() {
    }
    //私有方法
    private void say(){
        System.out.println("private say()...");
    }
    //公有方法
    public void work(){
        System.out.println("public work()...");
    }
}
//獲得類完整的名字
String className = c2.getName();
System.out.println(className);//輸出com.ys.reflex.Person

//獲得類的public類型的屬性。
Field[] fields = c2.getFields();
for(Field field : fields){
   System.out.println(field.getName());//age
}

//獲得類的所有屬性。包括私有的
Field [] allFields = c2.getDeclaredFields();
for(Field field : allFields){
    System.out.println(field.getName());//name    age
}

//獲得類的public類型的方法。這里包括 Object 類的一些方法
Method [] methods = c2.getMethods();
for(Method method : methods){
    System.out.println(method.getName());//work waid equls toString hashCode等
}

//獲得類的所有方法。
Method [] allMethods = c2.getDeclaredMethods();
for(Method method : allMethods){
    System.out.println(method.getName());//work say
}

//獲得指定的屬性
Field f1 = c2.getField("age");
System.out.println(f1);
//獲得指定的私有屬性
Field f2 = c2.getDeclaredField("name");
//啟用和禁用訪問安全檢查的開關,值為 true,則表示反射的對象在使用時應該取消 java 語言的訪問檢查;反之不取消
f2.setAccessible(true);
System.out.println(f2);

//創建這個類的一個對象
Object p2 =  c2.newInstance();
//將 p2 對象的  f2 屬性賦值為 Bob,f2 屬性即為 私有屬性 name
f2.set(p2,"Bob");
//使用反射機制可以打破封裝性,導致了java對象的屬性不安全。
System.out.println(f2.get(p2)); //Bob

//獲取構造方法
Constructor [] constructors = c2.getConstructors();
for(Constructor constructor : constructors){
    System.out.println(constructor.toString());//public com.ys.reflex.Person()
}

反射方法執行

public class Apple {

    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常的調用
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        //使用反射調用
        Class clz = Class.forName("com.wyl.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM