1. 程式人生 > 其它 >javaSE進階

javaSE進階

檔案類與IO流

File類

構造方法(並不真正建立檔案):

File(Stirng filename): 其中的引數是檔名,預設放在當前程式同一目錄
File(Stirng 路徑, String filename):
Fiel(String dir ,Stirng filemane): 其dir指目錄
注意: 注意區別相對路徑(相對於src)和絕對路徑(磁碟目錄)

常用方法:

1.boolean createNewFile() 建立一個檔案
2.long length() 測量檔案長度(單位是位元組)
3.boolean isFile() 判斷檔案是否是檔案而不是目錄
4.boolean exists() 判斷檔案是否存在


5.String getParent() 獲取檔案的父目錄
6.String getAbsolutePath() 獲取檔案的絕對路徑
7.boolean delete() 刪除檔案

建立檔案:

package 檔案類與IO流.creat_file;

import java.io.File;
import java.io.IOException;

public class Create_file {

	public static void main(String args[]) throws IOException{
		File file = new File("src\\", "aa.txt");  //只建立檔案物件不能建立檔案,需要呼叫方法
		//檔案構造方法,第一個引數是路徑(絕對路徑或者相對路徑如相對路徑),第二個引數是檔名(需加字尾)
	
		/* 執行可執行檔案
		 * Runtime 類 
		 * 用此類的靜態方法建立一個物件
		 * 呼叫 exec (String 命令或者可執行檔案路徑)
		 *  */
		File note = new File("E:\\1.txt");
			System.out.println("檔案是否建立成功"+file.createNewFile());
			System.out.println("檔案是否刪除成功"+note.delete());
			System.out.println(note.getAbsolutePath());
			System.out.println(note.getParent());		
	}
}

InputStream,OutputStream,位元組流IO流

位元組流主要是用於處理檔案(如圖片,視訊音樂等),因為這些檔案都採用二進位制來儲存

檔案位元組輸入輸出流

1.輸入輸出都是相對與程式來說的:即輸入流是從外部獲取資料到程式,輸出流是程式輸出資料到外部
2.輸入輸出流在使用後都要關閉,先用的後關,後用的先關

構造方法

將檔案位元組流繫結到檔案 1.FileInputStream(String 檔名):
2.FileInputStream(File 檔案物件):
1.FileOutputStream(File 檔案物件): 將檔案位元組輸出流繫結到檔案
2.FileOutputStream(String 檔名):


3.FileOutputStream(File 檔案物件, boolean append): 第二個引數設定是否追加,預設是重寫
4.FileOutputStream(File 檔案物件, boolean append):

常用方法:

輸入流方法:

1.int read() 從源中每次讀取一個位元組的資料,並返回位元組(0~255), 讀到檔案末尾返回-1
2.int read(byte[] b) 從源中讀取b.length個字元到b位元組陣列中,返回實際讀取的位元組數 ,讀到檔案末尾返回-1
3.int read(byte[] int off, int len) 從源中讀取 len個位元組到位元組陣列b中,並返回實際讀取的位元組數, 參數off指定從陣列的某個位置開始存放讀取的陣列
4.void close() 關閉流,如果不關閉,有可能繫結的資源不允許另一個程式操作繫結的資源

輸出流方法:

1.void write(int n) 每次向繫結的資源中寫入一個位元組n
2.void write(byte b[]) 每次向繫結的資源中寫入一個位元組陣列
3.void write(byte[] b , int, off ,int len) 每次向從位元組陣列中的off位置開始向繫結資源中寫入len個位元組
4.void close()
總結: 由於位元組流是以位元組為單位進行資料處理,但漢字是以多個位元組儲存的,所以用位元組流處理中文就會出現亂碼現象,所以在處理漢字時用字元流進行處理

InputStreamReader,InputStreamWriter,字元IO流

字元流主要用於處理漢字時出現亂碼問題的

1.構造方法:

InputStreamReader(InputStream in, String charsetName) ;  //建立一個指定字符集的輸入流,常用來解決中文亂碼問題
OutputStreamWriter(OutputStream out, String charsetName) //建立一個指定字符集的輸出流

檔案字元輸入輸出流

構造方法
字元輸入流:

FileReader(Stirng 檔名); FileReader(File 檔案物件);

字元輸出流:

FileWriter(String 檔名); FileWriter(File 檔案物件);
FielWriter(Sring 檔名, boolean append): FileWriter(File 檔名, boolean append);
常用的方法和位元組流基本一致,
注意!!! 但對於Writer 的write() 方法將資料首先寫入緩衝區,每當緩衝區溢位時內容才會被自動寫入繫結資源,如果關閉流緩衝區內容也會被立刻寫入,也可以呼叫flush()方法立刻寫入
總結: 位元組流主要用於圖片,視訊等二進位制的檔案,字元流用於文字檔案

讀寫檔案:

package 串實驗;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class Test_String {

	public static void main(String[] args) throws IOException {
        // 繫結檔案物件,注意: 此檔案已經存在
		File file = new File("src/串實驗/readme.txt");
		 // 獲取檔案字元輸入流
		 FileReader in = new FileReader(file);
		 char[] chars = new char[1024];
         // 將讀取的內容放入字元陣列
		 in.read(chars);
		 in.close();
		 String str = new String(chars);
		 System.out.println("讀取到的檔案內容為:"+str);
        
        // 繫結檔案輸出流
        FileWriter out = new FileWriter(file,true);  // 設定為追加模式的檔案輸出流
		 out.write("\n這是追加內容呀!");   // 以追加的方式想檔案輸出內容
		 out.flush();  // 立即重新整理快取區, 重新整理的話輸出流不關閉之前是不會
	}

}

緩衝流ButteredReader和ButteredWriter

用於增強流的讀寫能力

構造方法

ButteredReader(Reader in);
ButteredWriter(Writer out);

常用方法:

1.String readLine(): 一次讀取一行資料
2.void writeLine(): 一次寫入一行資料

序號化和反序列化

序列化:將物件轉換成位元組流

反序列化: 將位元組流轉換成物件

構造方法:

ObjectInputStream(InoutStream in): 建立一個輸入物件的位元組流,引數 in 是要繫結的位元組流或者是位元組流的子類 ObjectOutputStream(OutputSream out):建立一個輸出物件的位元組流,其中引數out是要繫結的位元組流或者是其子類

常用方法:

1.void writeObject(Object object): 將物件寫入物件所繫結的輸出流中
2.Object readObject() 將輸入流中的物件讀取出來並且返回

Serializble介面 (可序列化介面)

在使用writeObject(Object object)方法將物件序列化時,要序列化的物件必須實現Serializble介面將物件設定成可序列化狀態,

public class Test{
    public static void mian(){
    //建立檔案輸出流,為寫入物件做準備
        FileOutputStream file_out =new FileOutputStream(new File(E:\\1.txt));
    //建立物件流將檔案流file_out繫結
        ObjectOutputStream o_out = new ObjectOutputStream(file_out);
    //將貓物件序列化寫入檔案
        o_out.writeObject(new Cat());
    }
} 
//貓物件,注意要實現介面Serializble
class Cat implements Serializble{
}

IO之Propertest類與配置檔案讀取

properties檔案介紹


在開發專案時,專案通常會依賴一些中介軟體,這些中介軟體的配置不是硬編碼寫在程式碼中,而是使用配置檔案方式儲存
常用配置檔案有以下幾種:

  • properties檔案: SpringBoot應用都有應用此配置檔案
  • xml檔案: 如MyBattis的配置檔案
  • JSON檔案

properties檔案通常採用鍵值對儲存,key=value

server.port=8081
spring.application.name=order-api

Properties類的使用


Properties類的介紹

Properties類表示一組持久儲存的屬性,屬性由屬性名和屬性值組成.Properties可以儲存到流(以流的方式寫入)或者從流中載入,屬性列表中每個鍵及其對應的值都是字串
Properties可以當成Map集合使用,但是一般不會這樣做

常用方法:

properties.getProperty(String key)  //根據鍵值獲取屬性
properties.load(InputStream inStream)   //根據輸入流(一般是properties檔案的輸入流)載入檔案內容
properties.propertyNames() //獲取 key的集合
properties.store(OutputStream out, String comments)   //向檔案中寫入內容

Properties檔案讀取

當Properties檔案放在專案下時,properties檔案會隨著java程式在編譯生成位元組碼時一起打包在同一個目錄下,故可以通過類載入器來獲取檔案流進而讀取檔案內容

 //從類載入器載入載入檔案,由於在生成位元組碼時Properties檔案會隨著java檔案編譯被一起打包到同一目錄下,故可以通過類載入器讀取到檔案
  InputStream in =PropertiesDemo01.class.getClassLoader().getResourceAsStream("application.properties");
  //建立properties物件通過載入properties檔案的輸入流來載入檔案內容
  Properties properties = new Properties();
  //Properties物件載入的檔案流
  properties.load(in);
  //取出所有的屬性名(key)集合
  Set<String> propertySet = properties.stringPropertyNames();
  for(String key:propertySet){   //遍歷集合中的key
 	System.out.println(key+":"+properties.getProperty(key));  
  }

如檔案中含有中文,出現亂碼問題則改為字元流

InputStreamReader in = new InputStreamReader(PropertiesDemo01.class.getClassLoader().getResourceAsStream("application.properties"),"utf8");

Properties檔案寫入

Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
FileOutputStream out = new FileOutputStream(new File("src/test.properties"));
properties.store(out,"註釋");

集合

  • 概念: 物件的容器,定義了對多個物件進行操作的常用方法,可實現陣列的功能
  • 與陣列的區別:
  • (1) 陣列長度固定,集合長度不固定
  • (2) 陣列可以儲存基本資料型別和引用型別,集合只能儲存引用型別(但是基本資料型別可以裝箱轉換)

Collection集合體系

常用方法

  • boolean add(Object obj) 在集合尾部新增一個物件
  • boolean addAll(Collection c) 將一個集合的所有物件新增到此集合中
  • void clear() 清空此集合中所有物件
  • boolean remove(Object o) 移除此集合中的o物件
  • Object[] toArray() 將集合中的所有元素以陣列形式返回
  • boolean isEmpty() 判斷此集合是否為空
  • boolean contains(Object o) 判斷此集合中是否包含o物件
  • boolean equals(Object o) 判斷此集合是否與o物件具有相同的引用
  • int size() 返回此集合中元素個數
  • Iterator iterator()方法: 獲取當前集合的迭代器

迭代器的使用以及方法:

  • boolean hasNext()方法:當迭代器的下一位置不為null時,返回true,否則返回false (即判斷下一次集合中是否還有元素)
  • Object next()方法: 獲得迭代器位置下移,返回下移後的元素
  • void remove()方法:刪除迭代器所指向的元素(注意:在迭代過程中只能用此方法刪除元素)

List介面

特點:有序,有下標,元素可重複

常用方法(除了Collection原有的方法外)

  • void add(int index,Object obj): 在列表的指定位置新增元素(第一個元素的下標為0)
  • void addAll(int index, Collection c) :從指定位置開始將c中所有元素新增到列表中
  • Object get(int index):放回指定下表的元素
  • int indexOf(Object o):放回指定元素的索引,不存在返回-1
  • Objcet remove(int index):移除指定下標的元素
  • void removeRange(int fromIndex,int toIndex):含頭不含尾的移除指定下標到指定下標的元素
  • Object set(int index,Object o):將指定索引位置的物件替換為o
  • Object[] toArray():按照列表中元素的順序返回包含列表所有元素的陣列
  • ListIterator ListIterator():返回列表的列表迭代器

ListIterator常用方法(相比較於Iterator新增):

(1)boolean hasPrevious():逆向遍歷,如果前一個有元素則返回true,否則返回false
(2)Object previous():迭代器指標前移,返回前移後的元素
(3)int NextIndex():返回對next()呼叫後返回元素的索引
(4)int PreviousIndex():返回對previous()呼叫後的返回元素的索引
(5)void set(Object o):將迭代器指標所指向的位置的元素替換為o
(6)void add(Object)

ArrayList陣列列表實現類

  • 實現結構:使用陣列結構實現
  • 特點: 查詢快,修改慢

LinkedList連結串列實現類

  • 實現結構:雙向連結串列實現
  • 特點:查詢慢,修改快

Vector向量實現類(不推薦用)

  • 特點: 執行緒安全,但是執行效率低,有更好的代替

Set介面

特點:無序,無下標,不可重複元素

常用方法(與Collection基本一致)

  • int HashCode()獲取雜湊碼值
    關於雜湊碼:* 如果兩個物件具有相同的引用(即equals的返回值為true),則雜湊碼一定相同,但雜湊碼相同,物件不一定具有相同的引用 *
  • boolean equals(Object o),判斷是否是同一個物件(如果該類沒有重寫此方法的的話)

迭代器(沒有ListIterator迭代器)的使用以及方法:

  • boolean hasNext()方法:當迭代器的下一位置不為null時,返回true,否則返回false (即判斷下一次集合中是否還有元素)
  • Object next()方法: 獲得迭代器位置下移,返回下移後的元素
  • void remove()方法:刪除迭代器所指向的元素(注意:在迭代過程中只能用此方法刪除元素)

HashSet雜湊集合實現類

  • 實現結構:採用陣列+連結串列+(JDK1.8之後加入紅黑樹)

  • 特點:元素唯一,根據雜湊碼HashCode和equals()方法和equals()方法來去重

TreeSet樹集合實現類

  • 實現結構:紅黑樹
  • 特點:
    (1) 基於排序來去重按順序儲存
    (2) 實現了SortedSet介面,對集合元素自動排序
    (3) 元素物件的型別必須實現Comparable介面,指定排序規則
    (4) 通過CompareTo()方法來確定是否為重複元素
  • 要求:
    元素必須是可以比較的,即實現Comparable介面
    或使用TreeSet(Comparator com)構造器來建立集合為集合新增比較器

    ps:
    (1)Compareable介面:只有一個int ComparaTo(E o)方法,呼叫物件和引數比較,一般來說,呼叫者大於引數則返回正數,小於則返回負數,等於返回0
    (2)Comparator介面: 只有一個int compara(E o1,E o2)方法
public class Student implements Comparable<Student>{
 //實現介面的所有抽象方法
     public int comparaTo(Student o){
      //比較規則
      reutrn 整數;
     }
} 
//建立自定義比較器的樹集合
TreeSet<Student> treeSet = new TreeSet<>(new Comparator<Student>(){
        //實現介面的所有抽象方法
        public int compara(Student o1,Student o2){
          //自定義的比較規則
          return 整數;
        }
}); 

Map<K,V>集合體系

常用方法:

  • V get(Object key):根據引數的鍵值返回對用的Value
  • V put(K key,V value)向集合中加入鍵值對
  • void putAll(Map<K,V>)將引數中的Map集合新增
  • Set keySet():將Map集合中的鍵以Set的形式返回
  • Set<Map.Entry<K,V>> entrySet(): 將Map集合中的鍵值對整體以Set集合的形式返回
  • Collection values():將Map集合中的Value以Collection集合的形式返回
  • V remove(Object key)
  • boolean containsKey()
  • boolean containsValue()
  • int size()
  • boolean isEmpty()

重要巢狀介面和遍歷

  • 遍歷方式:
    (1) 遍歷Map可通過keySet()轉化成鍵的Set集合遍歷
for(Set<V> key:map.entrySet()){
//列印Key以及對應的Value
        System.out.println(key+"----"+map.get(key));
}

(2) 通過entrySet()轉化成鍵值對(Map.Entry)Set集合對來遍歷

//獲得map的Entry 集合
Set<Map.Entry<K,V>> entrySet = map.entrySet();
for(Map.Entry<K,V> entry:entrySet){
//列印鍵值對
        System.out.println(entry);
}

其中通過鍵值對來遍歷效率要更高,因為通過鍵集合來遍歷需要遍歷兩次

  • Map巢狀介面Entry<K,V>:
    該類是Map集合的一個內部介面,表示Map的一個鍵值對,該介面提供了一些方法可以得到對應map集合的一下key或者value,以及修改Key對應的Value
    如: V setValue(V value)方法,將此鍵對應下的值替換為引數的Value

HashMap集合

  • 特點:
    (1) JDk1.2版本,執行緒不安全(不同步),執行效率高,允許null作為Key或Value的值
    (2) 儲存結構:雜湊表(陣列+連結串列+JDK1.8加入紅黑樹),去重依據:根據Key的equals()和HasCode()
  • 原始碼分析:
    (1)屬性: 初始容量,擴容因子(**指的當集合內元素個數>集合的容量擴容因子 的時候,集合的的容量會擴大至原來的2倍**)*
 // 當新增一個元素時,初始容量為1<<4即16,不存在元素容量為0,便於節省空間
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
static final int MAXIMUM_CAPACITY = 1 << 30;   //集合的最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;  //預設的擴容因子為0.75
static final int TREEIFY_THRESHOLD = 8;  //JDK1.8 , 當連結串列的長度大於8時,調整為紅黑樹
static final int MIN_TREEIFY_CAPACITY = 64; //JDk1.8,當連結串列的長度大於8時,並且元素個數大於64時,調整為紅黑樹
static final int UNTREEIFY_THRESHOLD = 6;  //JDK1.8 ,當連結串列的長度小於6時,調整為連結串列
transient Node<K,V>[] table;//雜湊表中的(連結串列)陣列

(2) 構造器:

//無參構造:預設擴容因子為0.75,不過剛建立好時 table是null,size是0
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    //指定初始容量和擴容因子的構造器
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

(3) 方法:

//put(K key,V value)方法
public V put(K key, V value) {   
        return putVal(hash(key), key, value, false, true);//hash方法用來確定位置
    }
//put方法中呼叫的putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0) 
//如果集合中還未新增元素,則呼叫呼叫resize()方法重置,為集合分配初始容量(即16)
            n = (tab = resize()).length; 
            
        if ((p = tab[i = (n - 1) & hash]) == null)
        //如果集合為空,則為建立一個Node加入陣列中
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
//如果集合的元素個數已經超過了擴容閾值(擴容因子*集合容量),則重新分配集合容量,為原來的2倍
            resize();
        afterNodeInsertion(evict);
        return null;
    }
  • HashSet與HashMap的關係的
    通過檢視原始碼可發現,HashSet實際上就是使用了HashMap的結構,只不過Value為null
 public HashSet() {
        map = new HashMap<>();
    }
 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

Hashtable集合

特點: JDK1.0版本,執行緒安全,執行效率低,不允許null作為Key或者Value
重要子類:Properties:要求key,value都是String,通常用於配置檔案的讀取

TreeMap集合

  • 特點:實現了SortedMap介面,可以對key自動排序
  • 儲存結構:採用紅黑樹,去重規則是根據compara(Objcect o1,Object o2)即元素自身的比較器或元素的ComparaTo(Object o)方法即使元素實現了comparabale介面

TreeMap與TreeSet的關係

通過檢視原始碼,可發現TreeSet實際上是呼叫了TreeHap來實現的

 public TreeSet() {
        this(new TreeMap<>());
    }
  public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

Collections工具類

  • 概念:集合工具類,定義了除了存取以外的集合常用方法

常用方法:

  • public static void sort(List list)//升序排列(元素必須實現Comparable介面)
  • public static void reverse(List list) //反轉元素集合順序
  • public static void shuffle(List list) //隨機重置集合元素的順序
  • public static int binarySearch(List list , T object)//二分查詢(前提元素已經是排序好的),返回索引值
  • public static void copy(List copy_list,List list)//複製,不過兩個引數的元素個數必須相同

補充擴充套件:

(1) 當把陣列轉換成集合時,該轉化的集合是一個受限集合,不能新增或者刪除元素

String[] names = {"zhan","wenwen"};
List<Stirng> list = Arrays.asList(names);  //此集合不能新增或者刪除元素

(2) 把基本型別陣列轉化成集合時,需要注意由於基本型別陣列也是引用型別資料,故轉化後的集合只有一個元素(即該基本資料型別陣列)

int [] nums = {100,200,300};
List<Integer> list = Arrays.asList(nums);  //錯誤!!!! int[] 並不等同於Integer
List<int[]> list = Arrays.asList(nums);//此集合只要一個元素,該元素是一個數組,nums
/*********或者修改為如下所示*****************/
Integer[] nums = {100,200,300};
List<Integer> list = Arrays.asList(nums);  //此集合包含三個元素,都是Integer物件

多執行緒

執行緒概述:

  1. 執行緒是CPU的基本排程單位,程序是作業系統的基本排程的單位,一個程序由一個或者多個執行緒組成

  2. 在同一個程序中的執行緒資源共享,但是程序之間相互獨立

  3. 執行緒搶佔式執行

  4. 執行緒的組成:

(1)時間片: 作業系統(OS)會為每個執行緒分配執行時間
(2)執行資料:

  • 堆空間:儲存執行緒中使用的物件,多個執行緒可以共享堆中的物件
  • 棧空間:儲存執行緒中使用的區域性變數,每個執行緒都有擁有獨立的棧

建立執行緒

1.繼承Thread類建立執行緒,並且重寫run()方法:

public class MyThread extends Thread{
        void run(){
        //這裡寫該執行緒要執行的操作
        }
}
  1. 實現介面runnable
public class Myrunnable implement Runnable{
        void run(){
        //需要執行的操作
        }
}
class MyTread {
  public static void main(){
      //使用Thread的有參構造器來建立執行緒
      Thread thread1 = new Thread(new Myrunnable());
    //也可不實現Runnable,直接使用匿名類
    Thread tread2 = new Thread(new Runnable(){
        run(){
        //匿名類建立的執行緒的操作
        }
      });
     }
}

構造方法:

1.Thread():無參構造
2.Thread(Runnable run):有參構造,繫結的介面Runnable的執行緒,執行方法由Runnble提供,降低耦合性
3.Thread(String name):引數代表建立執行緒時將其命名
4.Thread(Runnable run,String name):繫結介面,並且將其執行緒命名

常用方法:

1.void start():啟動執行緒,注意!thread.start()與thread.run()有本質區別,雖然有時候執行結果是一樣的
2.static Thread currentThread(): 獲得當前執行緒
3.Stirng getId():獲得執行緒ID,每個執行緒ID是唯一的且由OS提供,不能更改
3.Srting getName(): 獲得執行緒的名稱,執行緒名稱可以自定義

執行緒的優先順序(Priority):

執行緒的優先順序為1-10,預設為5,優先順序越高,表示獲得CPU的機會越多

設定優先順序的方法:執行緒對像.setPriority(int newPriority),其中的引數表示設定的優先順序

執行緒的4(6)種狀態:

注:執行緒的執行結束時間片到期不代表執行緒銷燬,run()方法執行完畢執行緒才真正銷燬;
某個執行緒的run()方法還沒結束但是時間片到期了,其他執行緒也能搶佔CPU進而執行

常用方法:

(1)啟動

void start():啟動執行緒,注意!thread.start()與thread.run()有本質區別,雖然有時候執行結果是一樣的

(2)sheep休眠:執行緒時間片用完後就休息(等待)一段時間再爭取

static void sleep(long 等待毫秒數):,時間片每用完一次就休息一會

(3)yield放棄(避讓):主動放棄爭取本次時間片讓給其他執行緒機會,回到就緒狀態進行下一次的時間片的爭取

static void yield():主動將當前執行緒放棄

(4)join加入:在一個執行緒中加入另一個執行緒後,當前執行緒會進入堵塞狀態,等待加入的執行緒執行完以後再執行當前執行緒

final void join():,如addThread.join(),代表在當前執行緒中加入addThread執行緒

執行緒屬性

前臺執行緒

守護執行緒(即後臺執行緒)

守護執行緒即為前臺執行緒服務的執行緒

方法:

void setDaemon(true):,前臺執行緒
注意:當前臺執行緒結束後,守護執行緒無論是否執行完畢都會自動結束,java中的垃圾回收器就算一個守護執行緒

執行緒安全問題

描述:多個執行緒共同操作同一空間,會出現資料丟失的情況
當多個執行緒併發訪問臨界資源,如果破壞原子操作,可能會出現資料不一致

  • 共享資源: 即共享資源,一次僅允許一個執行緒使用才能保證其正確性
  • 原子操作: 不可分割的多部操作,被視為一個整體,其順序和步驟不可打亂或者預設
    例子:
public  class Thread_safe {
    private static int index = 0;
    public static void main(String[] args) throws InterruptedException {
        //共享資源
        String[] s = new String[5];
        //執行緒a
        Thread a = new Thread(new Runnable(){
        	@Override
        	public void run() {
            //原子操作
            s[index++] = "hello";    
        	}
        },"a");
        //執行緒b
        Thread b = new Thread(new Runnable(){
            @Override
            public void run() {
                //原子操作
                s[index++] = "world";
            }
        },"b");
        
        a.start();
        b.start();
        /*加入執行緒是保證a,b,執行緒完畢執行完畢在執行主執行緒的列印語句 */
        a.join();
        b.join();
        System.out.println(Arrays.toString(s));
    }
}

在沒有發生安全問題時的輸出結果應該為:
[world, hello, null, null, null]或
[hello, world, null, null, null],發生安全問題後則結果為:[world, null, null, null, null]或[world, null, null, null, null]

常見的Java執行緒安全類:如StringButter,Hashtble,Vector等

synchronized 關鍵字的使用

在使用synchronized新增鎖時一定要注意是否是同一個物件鎖,不可使用new 建立一個物件鎖如:

/*注意!這樣的鎖沒意義,每次都會建立一個新的鎖物件*/
synchronized(new Object()){
//原子操作
}

(1)synchronized(){程式碼塊}:,同步程式碼塊,給程式碼塊新增鎖
(2)synchronized 方法(){}:修飾為同步方法(注意:當同步方法為非靜態方法時,鎖物件是this,當是靜態方法時鎖物件是類即 類.class)

執行緒通訊(堵塞執行緒以及喚醒執行緒)

通過synchronized可以對方法和程式碼塊上鎖,同樣的也可以通過鎖物件呼叫waitin()方法來將執行緒加入等待佇列(加入等待佇列的執行緒無法搶奪CPU,直到使用notify()方法將其喚醒)
從而釋放該執行緒鎖佔用的鎖,使得其他執行緒有機會搶奪CPU

常用方法:

注意:呼叫這幾個方法的物件都是鎖
1.鎖物件.wait():將執行緒持有的鎖釋放,執行緒加入等待佇列,等待佇列的執行緒不能參與CPU的爭奪
2.notify():
(1)隨機喚醒等待佇列的一個執行緒,喚醒的執行緒不一定只是有爭奪CPU的機會並不一定能奪得CPU;
(2)喚醒的執行緒執行時將從wait()處開始執行

3.notifyAll():喚醒等待佇列的全部執行緒

常見問題:

假設有四個執行緒:兩個存錢執行緒(晨晨,明明),兩個取錢執行緒(冰冰,莉莉),要求有銀行卡里沒錢時不能取錢,銀行卡里有錢時不能存錢

  • 問題1:標記沒進行二次判斷
    加入在判斷時使用如下程式碼進行存錢的操作
public synchronized void subMonney(int m){
        //如果沒錢
        if(!flag) {  
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO 自動生成的 catch 塊
                e.printStackTrace();
            }
        } 
        this.monney -= m;
         System.out.println(Thread.currentThread().getName()+"取出"+m+"剩餘:"+this.monney);
         //修改標記
         flag = false;
         //喚醒執行緒
         this.notifyAll();
         
    }

例如:
當兩個取錢執行緒處於等待佇列時,晨晨執行成功,修改了餘額標記,喚醒莉莉
接著莉莉開始執行,執行成功後,喚醒了冰冰,即使已經修改了標記,但是方法中的if語句已經執行完畢,不會再次判斷,而是直接從this.wait()語句之後開始執行,於是導致再次進行了取錢操作導致餘額變成了負數導致程式與預期不一樣
解決方案:將if換成while可以多次判斷

  • 問題2: 全部陷入等待佇列導致死鎖
    例如:
    前兩次執行了取錢操作均失敗,都進入等待佇列
    第三次晨晨存錢成功,修改標記,喚醒冰冰
    第四次明明存錢失敗,釋放鎖,進入等待佇列
    第五次晨晨存錢失敗,釋放鎖,進入等待佇列
    第六次冰冰取錢成功,修改標記,喚醒莉莉(問題所在,當喚醒的是功能相同的執行緒時,最終都兩個取錢執行緒操作失敗都進入等待佇列形成死鎖)
    第七次莉莉取錢失敗,釋放鎖進入等待佇列
    第八次冰冰也取錢失敗釋放鎖進入等待佇列
    解決方案:將notify()方法替換成notifyAll()喚醒全部執行緒

高階執行緒

執行緒池

執行緒池:即執行緒的容器

常用API

Executor:執行緒池的頂級介面
ExecutorService:執行緒池服務介面,用於管理執行緒池
Executos,執行緒池工廠類,用於建立執行緒池

ExecutorService的常用方法

submit(Runnable task):向執行緒池提交任務
shutdown():關閉執行緒池,如果不關閉程式不會停止

Executors的常用方法

(1)建立固定大小的執行緒池: staitc newFixedThreadPool(int 個數)
(2)建立快取執行緒池:static newCachedThreadPool()

/*常見的建立執行緒池並使用的格式如下*/
ExecutorService es = Executors.newFixedThreadPool(n); //n為執行緒個數
//建立快取執行緒池
ExecutorService es2 = Executors.newCachedThreadPool();
Runnablme task = new Runnable(){
        void run(){
        //執行緒執行任務
        }
};
//提交任務
es.submit(task);
//關閉執行緒池
es.shutdown();

Callable介面和Future介面

Callable介面:

使用方法和Runable介面類似,其實現方法是call()方法,與run()方法的區別:call()方法可以丟擲異常宣告和擁有返回值,而run()方法都不可以
但由於Thread的構造方法中沒有Callable介面的引數,故沒法直接繫結到執行緒

//使用匿名內部類建立callable上轉型物件
Callable<V> callable = new Callable<V>(){
        <V> call(){
        //執行語句
        }
}
/*無法直接新增執行緒
故需要使用FutureTask<V>類(此類實現了Runnable介面和Future介面)進行轉換*/
FutureTask<V> task = new FutureTask<V>(callable);
/* 由於FutureTask<T>實現了Runnable介面故可以新增到執行緒中*/
Thread th = new Thread(task);

注: 不過執行緒池的submit()方法可以直接新增Callable介面物件

Future介面:

Future介面表示執行任務的結果,如ExecutorSerVice中的submit()方法的返回型別就是Future
常見方法:get():獲取future物件的結果
故常見寫法如下:

//建立執行緒池
ExecutorService es = Executors.newCachedThreadPool();
//建立Future<V>物件接收執行緒池的接收結果
Future<Integer> future = es.submit(new Callable<Integer>(){
        public Integer call()throws Exception{
                int sum=0;
                for(int i=1;i<=100;i++){
                    sum+=i;
                }
                return sum;
            }
});
//列印獲取結果
        System.out.println(future.get());

執行緒的同步和非同步

同步(有等待):形容一次方法的呼叫,同步一旦開始,必須等待該方法返回才能繼續.
非同步(無等待):形容一次方法呼叫,非同步一旦開始,像是一次訊息傳遞,呼叫者告知後立刻返回.兩者競爭時間片,併發執行.

Lock介面

ReentrantLock(實現類,即重入鎖)

功能與synchronized類似
JDK5加入與synchronized比較,顯示定義,需要手動釋放,結構更靈活,功能更強大,效能更優越
區別:synchronized採用悲觀鎖,而Lock採用樂觀鎖(樂觀鎖通俗說就是不堵塞其他執行緒假設不會衝突,如果衝突就不斷重試)

ReentrantReadWriteLock(讀寫鎖):可分別分配讀鎖和寫鎖

互斥規則:

  • 寫-寫,互斥堵塞
  • 讀-寫,互斥,讀堵塞寫,寫堵塞讀
  • 讀-讀,不互斥,不堵塞
//建立讀寫鎖
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//分配讀鎖
ReadLock readlock = rwl.ReadLock();
//分配寫鎖
WriteLock writelock = rwl.WriteLock();
/*讀寫鎖的獲取鎖的方式和釋放鎖的方式和重入鎖一致 */
  • ReentrantReadLock 讀鎖
    可併發執行,即允許多個執行緒同時拿到讀鎖
  • ReentantWriteLock 寫鎖
    不可併發執行,一次只允許一個執行緒獲得寫鎖

註解與反射

註解Annotation

如: @Override

Annotation的作用:

  • 不是程式本身,對程式做出解釋
  • 可以被其他程式讀取

Annotation的格式

  • 以@註解名在程式碼中存在,可以有引數值,例如:@SuppressWarmings(value="unchecked")
  • Annotation可以作用在method,fleld(欄位),TYPE(型別),package,class,上面,相當於給他們添加了額外的輔助資訊,我們可以通過反射機制程式設計實現對這些元資料的訪問

內建註解

  • @Override:定義在java,.lang.Override中,此註解用於修飾方法中,表示這個方法是要覆蓋父類方法的
  • Deprecated: 此註解可以修飾方法,類,屬性,表示已經過時,不推薦使用,通常是危險的或者是有更好的替代
  • @SuppressWarmings:用於抑制編譯器的警告資訊,與前兩個註解不同,此註解必須要有引數,引數是已經定義好的

元註解

元註解用於對其他註解的定義修飾

  • @Target:用於描述註解的使用範圍
  • @Retention:表示需要在什麼級別保留該註解資訊,用於描述註解的宣告週期(source<class<runntime)
  • @Document:說明該註解被包含在javadoc中
  • @Inherited:說明子類可以繼承父類的中的該註解
    JDk中@Override註解的原始碼
//定義此註解的使用範圍
@Target(ElementType.METHOD)
//定義此註解的生命週期
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

自定義註解

  • 自定義註解的格式:public @註解名{定義內容}
  • 其中每一個(抽象)方法實際上是聲明瞭一個配置引數
  • 可以通過default來宣告引數的預設值
  • 如果只要一個引數成員,一般引數名為value,因為只有一個引數且名為value時使用可隱式賦值引數
  • 註解元素必須要有值
//自定義註解
@Target(Element.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @myAnnotation{
    //註解引數: 引數型別+引數名();
    String name() default "";
}
class Test{
//如果引數沒有預設值,就必須給引數賦值
    @myAnnotation("自定義的註解")
    public Test(){
    }
}
  • 返回值型別只能是引數的型別(返回值只能是Class,String,enum)

Reflection反射機制

動態VS靜態語言

  • 動態語言:是指在執行是可以改變其結構的語言,像新的函式,物件,甚至程式碼可以被引進.如JavaScript,PHP,Python,C#等
  • 靜態語言:與動態語言相對應,執行時結構不能改變的語言,如: C C++,Java(但是java也有一定的動態性,可利用反射機制獲得類似動態語言的特性)

反射機制提供的功能

  • 在執行時判斷一個物件所屬的類
  • 在執行時構造一個任意類的物件
  • 在執行時判斷一個類具有的屬性和方法
  • 在執行時獲取泛型資訊
  • 在執行時呼叫任意一個物件的屬性和方法
  • 在執行時處理註解
  • 生成動態代理

反射的優缺點

  • 優點:可以實現動態建立物件和編譯,具有很大的靈活性
  • 缺點:對效能有影響,使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且他滿足我們的要求,這類操作總是慢於我們相同的操作

Class 類

一個類只有一個Class物件,並且類在載入後由系統建立,類的結構資訊會封裝在Class物件中

在載入完類之後,在堆記憶體區中就產生了一個Class型別的物件(一個類中只有一個Class物件),這個物件包含了完整的類結構資訊,我們可以通過這個Class物件看到類的結構,這個物件就像一面鏡子,通過這個鏡子看到類的結構,我們形象的稱之為:反射

建立物件的方式:

  • 正常方式: (1)引入包類名稱 (2)通過new 例項化 (取得例項化物件)
  • 反射方式 (1)例項化物件 (2) getClass()方法 (3)得到完整的"包類名稱"(即獲得一個Class 物件)
    獲得Class物件的幾種常用方式:
    方式一:通過物件.getClass()
    方式二:通過Class.forName("包類名")
    方式三:通過類名.class

常用方法

  • static Class forName(String name) 返回指定類名的Class物件
  • Class newInstance() 呼叫構造器返回Class物件的例項
  • Class getSuperClass() 返回當前Class物件的父類的Class物件
  • Class[] getInterface()
  • ClassLoader getClassLoader() 返回該類的類載入器

類的載入過程

(1)Load:類的載入:將類的Class檔案讀入記憶體,併為之建立一個Class檔案,此過程由類載入器完成

(2)Link:類的連結:將類的二進位制資料合併到JRE中

  • 驗證:確保載入的類的資訊符合JVM規範,沒有安全問題
  • 準備: 正式為靜態變數分配記憶體並設定初始值,這些記憶體都在方法區中分配
  • 解析:虛擬機器常量池的符號引用(常量名)替換為直接引用(地址)的過程

(3)Initialize:類的初始化:JVM負責對類進行初始化

  • 執行類構造器()方法的過程,類構造器()方法是由編譯期自動收集所有類變數的賦值動作和靜態程式碼塊的語句合併產生的(此類構造器與物件的構造器即構造方法不是同一個東西)
  • 當初始化一個類時,如果返現其父類還未初始化會先初始化父類
  • JVM會保證一個類的構造器的()方法在多執行緒環境中被正確加鎖和同步
    如:
public class ClassLoader {
    public static void main(String[] args) {
        new A();
        System.out.println("m="+A.m);
    }
}
class A{
/*(2)在類的連結完畢後JVM會將類進行初始化,自動將所有的類變數和靜態程式碼塊按程式碼順序收集整合,故按順序執行賦值語句, m=100,m=200*/
    static {
        m = 100;
        System.out.println("A類靜態程式碼塊");
    }
/*(1)實際上下面這條語句的執行在兩個過程,首先在類的連結過程中執行 static int m=0,即正式為m分配記憶體,並且為其設定初始值0 */
    static int m = 200;  
    public A() {
        System.out.println("A類的無參構造");
    }
}

輸出結果:
A類靜態程式碼塊
A類的無參構造
m=200

什麼時候會發生類的初始化?
  • 類的主動引用(一定會發生類的初始化):
    -> 當虛擬機器啟動,先初始化main()方法所在的類
    -> new 一個類的物件
    -> 呼叫類的靜態成員和靜態方法(除了final常量)
    -> 使用java.lang.reflect包的方法對類進行反射呼叫
    -> 當初始化一個類,如果其類的父類沒有被初始化,則會先初始化他的父類
  • 類的被動引用(不會發生類的初始化):
    -> 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化,如:當通過子類引用父類的靜態變數,不會導致子類初始化
    Son.m //其中m為父類的類變數,但是父類初始化
    -> 通過陣列定義類引用,不會觸發此類的初始化
    -> 引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)

類載入器

類載入器作用是用來把類裝載進記憶體.JVM規定了如下型別的載入器

  • 引導類載入器:用C++編寫,是JVM自帶的載入器,負責java平臺核心庫的裝載,該類無法直接獲取
  • 擴充套件類載入器:負責jre/lib/ext目錄下的jar包或者指定目錄下的jar包裝裝入工作庫
  • 系統類載入器: 負責java-classpath或者-D java.class.path所指的目錄下的類與jar包裝入工作,是最常用的載入器
    測試程式碼:
           //獲取系統類載入器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //獲取系統類載入器的父類載入器-->拓展類載入器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
        //獲取擴充套件類的父類載入器-->根載入器(用C++編寫)
        ClassLoader root = parent.getParent();
        System.out.println(root);
        //測試當前類的載入器是哪個載入器
        ClassLoader classLoader = TestLoader.class.getClassLoader();
        System.out.println(classLoader);
        //獲取JDK內建類的載入器
        ClassLoader jdkLoader =Object.class.getClassLoader(); 
        System.out.println(jdkLoader);

執行結果:
jdk.internal.loader.ClassLoaders$AppClassLoader@2077d4de 系統類載入器
jdk.internal.loader.ClassLoaders$PlatformClassLoader@3830f1c0 擴充套件類載入器
null 由於是根載入器,即引導類載入器,無法獲取,所以為空
jdk.internal.loader.ClassLoaders$AppClassLoader@2077d4de 使用者自定義的類也是由系統類載入器載入
null JDK內建類的載入器也是根載入器

獲取類的結構

通過反射能獲取到類的結構有:

  • Field(屬性)
  • Method
  • Constructor(構造方法)
  • Annotation
  • SuperClass
  • Interface
Field[] fields = c1.getFields();   //獲得public 的屬性
Field[] fields2 = c1.getDeclaredFields();  //獲的全部屬性
Field[] fields = c1.getFields();   //獲得public 的屬性
Field[] fields2 = c1.getDeclaredFields();  //獲的全部屬性
//獲得指定屬性的值
//Field name = c1.getField("name"); //獲得public 的屬性
Field name = c1.getDeclaredField("name"); //獲得全部
//獲得方法
Method[] methods = c1.getMethods();  //獲取所所有的public方法,包括父類的
Method[] methods2 = c1.getDeclaredMethods(); //只獲得自己的全部方法
//獲得指定方法,提供方法名和引數
Method met = c1.getMethod("getName", null);
Method met2 = c1.getMethod("setName", String.class);
Constructor<?>[] constructors = c1.getDeclaredConstructors(); //獲得所有的public 構造器
Constructor<?> constrcutor = c1.getConstructor(String.class,String.class); //獲得指定的構造器

即不帶有Declared的只能獲取public 的,帶有Declared自己所擁有的(不包括父類)

反射動作

有了類的Class物件,就可以:

(1)構造物件
  • 有無引數構造: 呼叫Class物件的newInstance()方法
    -> 類必須有無參構造方法
    -> 無參構造方法的訪問許可權必須足夠
  • 沒有無參構造:
    步驟如下: (1)通過Class類的getDeclareConstructot(Class ...paremeterTypes)取得指定Constructor物件, (2)Constrcutor物件呼叫new Instance()方法
//獲得Class物件
Class<User> c1 = User.class;
//構造一個物件
User user1 = c1.getDeclaredConstructor().newInstance();//newInstance()方法過時,本質是呼叫了無參構造
//呼叫有參構造
Constructor<User> constructors = c1.getDeclaredConstructor(String.class,int.class);
User user2 = (User) constructors.newInstance("雯雯",1234);
(2)呼叫普通方法

首先獲取通過Class物件獲取到Method物件,然後Method呼叫invoke(方法所在的物件,實參列表)方法

Constructor<User> constructors = c1.getDeclaredConstructor(String.class,int.class);
   User user2 = (User) constructors.newInstance("雯雯",1234);
//通過反射呼叫普通方法
       Method getName = c1.getDeclaredMethod("getName");
              System.out.println(getName.invoke(user2));
       Method setName = c1.getDeclaredMethod("setName", String.class);
              setName.invoke(user2, "麗麗")
(3) 操作屬性

首先通過Class物件獲取Field物件,Field物件呼叫Set(欄位所在屬性類的物件,欄位的值)方法

//通過反射操作屬性
     User user3 =(User) c1.getDeclaredConstructor().newInstance();  //建立物件
     Field name = c1.getDeclaredField("name"); 
           //不能直接訪問,需要設定訪問許可權開放
           name.setAccessible(true);
           name.set(user3, "瀟瀟");
           System.out.println(user3.getName());

設定可訪問許可權

Constructor Method Field 都有一個setAccessible(boolean flage)方法,預設情況下是false,即使訪問許可權不開放,private修飾的成員是不可見的,但設定為true之後,將開放訪問許可權,使得哪怕是private成員也可見(頻繁使用反射建議開放)

效能對比:

普通方式>反射(開放許可權)>反射(不開放許可權)

反射獲取註解與ORM(對像關係對映)

物件關係對映:將實體類對映成一個表,其中

  • 類和表結構對應
  • 屬性和欄位對應
  • 物件和記錄對應

(重點)反射獲取註解

註解的做作用域物件呼叫getAnnotation(Class Element)方法

//定義類名的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
    String value();
}

//定義屬性的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface TableField{
    String columnName();
    String type();
    int length();
}
//實體類
@Table(value = "db_student")
public class Student {
    @TableField(columnName = "db_No",type = "int", length = 10)
    private int stuNo;
    @TableField(columnName = "db_name",type = "varchar", length = 10)
    private String name;
    @TableField(columnName = "db_age",type = "int", length = 10)
    private int age;
    }
/********************************************************/
Class<Student> c1 = Student.class;
        //獲得類的註解
        Table table =(Table) c1.getAnnotation(Table.class);
        //獲得name屬性的註解
        Field f1 = c1.getDeclaredField();  //先獲得欄位物件
        TableField name = f1.getAnnotation(TableField.class);  //通過欄位物件獲取註解
        //獲得註解的值
        name.columnName();
        name.type();
        name.length();

反射操作泛型

(1) java採用泛型擦除的機制來引入泛型,Java中泛型僅僅是給編譯器javac使用的,確保資料的安全性和免去強制轉換型別的機制,但是,一旦編譯完成,所有和泛型有關的型別全部擦除
如:

List<String> list1 = new ArrayList();
List<Integer> list2 = new ArrayList();
System.out.println(list1.getClass()==list2.getClass());  //結果為true

(2) 通過反射操作這些型別,java新增了ParamerterizedType,GenericArrayType,TypeVariable和WildcardType幾種型別代表不能被歸一到Class類中的型別但是又和原始型別齊名的型別

  • ParamerterizedType: 表示引數化型別,即泛型
  • GenericArrayType: 表示泛型或者引用型別變數的陣列
  • TypeVariable:是各種型別變數的公共父介面
  • WildcardType:代表一種萬用字元的型別表示式
//獲得Class物件方法
        Method m1 = Test.class.getMethod("test01",Map.class,List.class);
        //Class[] parameterTypes = m1.getParameterTypes();  //獲得引數型別陣列,元素為形參的型別(無型別引數)
        Type[] parameterTypes= m1.getGenericParameterTypes();  //獲得通用的引數型別陣列,元素為形參的型別(有型別引數)
        for (Type parameterType : parameterTypes) {//遍歷方法的每個引數型別
            System.out.println("#方法的引數型別為:"+parameterType);
            if(parameterType instanceof ParameterizedType) {  //如果引數為泛型引數
                //獲得真實引數型別
                Type[] actualTypeArguments = ((ParameterizedType) parameterType).getActualTypeArguments();
                for (Type type : actualTypeArguments) {
                    System.out.println("*引數的型別引數為:"+type);
                }
            }
        }

Lambda表示式

1. Lambda表示式的簡介

1.1. Lambda表示式的概念

lambda表示式, 是Java8的一個新特性, 也是Java8中最值得學習的新特性之一。

lambda表示式, 從本質來講, 是一個匿名函式。 可以使用使用這個匿名函式, 實現介面中的方法。
對介面進行非常簡潔的實現, 從而簡化程式碼。

1.2. Lambda表示式的使用場景

通常來講, 使用lambda表示式, 是為了簡化介面實現的。

關於介面實現, 可以有很多種方式來實現。 例如: 設計介面的實現類、 使用匿名內部類。 但是
lambda表示式, 比這兩種方式都簡單。

/**
* @Author 千鋒大資料教學團隊
* @Company 千鋒好程式設計師大資料
* @Date 2020/4/
* @Description
*/
public class Program {
 public static void main(String[] args) {
 // 無參、無返回值的函式式介面
 interfaceImpl();
}
 private static void interfaceImpl() {
 // 1. 使用顯式的實現類物件
 SingleReturnSingleParameter parameter1 = new Impl();
 // 2. 使用匿名內部類實現
 SingleReturnSingleParameter parameter2 = new
SingleReturnSingleParameter() {
 @Override

1.3. Lambda表示式對介面的要求

雖然說, lambda表示式可以在一定程度上簡化介面的實現。 但是, 並不是所有的介面都可以使用
lambda表示式來簡潔實現的。

lambda表示式畢竟只是一個匿名方法。 當實現的介面中的方法過多或者多少的時候, lambda表示式
都是不適用的。

lambda表示式,只能實現 函式式介面

1.4. 函式式介面

1.4.1. 基礎概念

如果說, 一個介面中, 要求實現類 必須 實現的抽象方法, 有且只有一個! 這樣的介面, 就是函式式接

口。

 public int test(int a) {
 return a * a;
}
};
 // 3. 使用lambda表示式實現
 SingleReturnSingleParameter parameter3 = a -> a * a;
 System.out.println(parameter1.test(10));
 System.out.println(parameter2.test(10));
 System.out.println(parameter3.test(10));
}
 private static class Impl implements SingleReturnSingleParameter {
 @Override
 public int test(int a) {
 return a * a;
}
}
}
// 這個介面中, 有且只有一個方法, 是實現類必須實現的, 因此是一個函式式介面
interface Test1 {
 void test();
}
// 這個介面中, 實現類必須要實現的方法, 有兩個! 因此不是一個函式式介面
interface Test2 {
 void test1();
 void test2();
}
// 這個介面中, 實現類必須要實現的方法, 有零個! 因此不是一個函式式介面
interface Test3 {
}

思考題: 下面的兩個介面是不是函式式介面?

1.4.2. @FunctionalInterface

是一個註解, 用在介面之前, 判斷這個介面是否是一個函式式介面。 如果是函式式介面, 沒有任何問

題。 如果不是函式式介面, 則會報錯。 功能類似於 @Override。

1.4.3. 系統內建的若干函式式介面

// 這個介面中, 雖然沒有定義任何的方法, 但是可以從父介面中繼承到一個抽象方法的。 是一個函式式
介面
interface Test4 extends Test1 {

}
// 這個介面, 雖然裡面定義了兩個方法, 但是defualt方法子類不是必須實現的。
// 因此, 實現類實現這個介面的時候, 必須實現的方法只有一個! 是一個函式式介面。
interface Test5 {
 void test5();
 default void test() {}
}
// 這個介面中的 toString 方法, 是Object類中定義的方法。
// 此時, 實現類在實現介面的時候, toString可以不重寫的! 因為可以從父類Object中繼承到!
// 此時, 實現類在實現介面的時候, 有且只有一個方法是必須要重寫的。 是一個函式式介面!
interface Test6 {
 void test6();
 String toString();
}
interface Test7 {
String toString();
}
interface Test8 {
void test();
default void test1() {}
static void test2() {}
String toString();
}
@FunctionalInterface
interface FunctionalInterfaceTest {
 void test();
}

介面名字

數 返回值 特殊介面

Predicate T boolean

IntPredicate: 引數int, 返回值 boolean
LongPredicate: 引數 long, 返回值 boolean
DoublePredicate: 引數 double, 返回值 boolean

Consumer T void

IntConsumer: 引數int, 返回值 void
LongConsumer: 引數 long, 返回值 void
DoubleConsumer: 引數 double, 返回值 void

Function<T, R> T R

IntFunction<R>: 引數 int, 返回值 R
IntToDoubleFunction: 引數 int, 返回值 double
IntToLongFunction: 引數 int, 返回值 long
LongFunction<R>: 引數 long, 返回值 R
LongToIntFunction: 引數 long, 返回值 int
LongToDoubleFunction: 引數 long, 返回值double
DoubleFunction<R>: 引數 double, 返回值 R
DoubleToIntFunction: 引數 double, 返回值 int
DoubleToLongFunction: 引數 double, 返回值long

Supplier 無 T

BooleanSupplier: 引數無, 返回值 boolean
IntSupplier: 引數無, 返回值 int
LongSupplier: 引數無, 返回值 long
DoubleSupplier: 引數無, 返回值 double

UnaryOperator T T

IntUnaryOperator: 引數 int, 返回值 int
LongUnaryOperator: 引數 long, 返回值 long
DoubleUnaryOperator: 引數 double, 返回值 double

BinaryOperator

IntBinaryOperator: 引數 int, int, 返回值 int
LongBinaryOperator: 引數 long, long, 返回值
long
DoubleBinaryOperator: 引數 double, double, 返
回值 double

BiPredicate<L, R> L,
R

boolean

BiConsumer<T, U>

T,

U void^

BiFunction<T, U, R>

T,

U R^

2. Lambda表示式的語法

2.1. Lambda表示式的基礎語法

lambda表示式, 其實本質來講, 就是一個匿名函式。 因此在寫lambda表示式的時候, 不需要關心方
法名是什麼。

實際上, 我們在寫lambda表示式的時候, 也不需要關心返回值型別。

我們在寫lambda表示式的時候, 只需要關注兩部分內容即可: 引數列表方法體

lambda表示式的基礎語法:

引數部分 : 方法的引數列表, 要求和實現的介面中的方法引數部分一致, 包括引數的數量和型別。

方法體部分 : 方法的實現部分, 如果介面中定義的方法有返回值, 則在實現的時候, 注意返回值的返

回。

-> : 分隔引數部分和方法體部分。

(引數)  -> {
方法體
};
/**
* @Author 千鋒大資料教學團隊
* @Company 千鋒好程式設計師大資料
* @Date 2020/4/
* @Description
*/
public class Syntax {
 public static void main(String[] args) {
 // 1. 無參、無返回值的方法實現
 NoneReturnNoneParameter lambda1 = () -> {
 System.out.println("無參、無返回值方法的實現");
};
 lambda1.test();
 // 2. 有參、無返回值的方法實現
 NoneReturnSingleParameter lambda2 = (int a) -> {
 System.out.println("一個引數、無返回值方法的實現: 引數是 " + a);
};
 lambda2.test(10);
 // 3. 多個引數、無返回值方法的實現
 NoneReturnMutipleParameter lambda3 = (int a, int b) -> {
 System.out.println("多個引數、無返回值方法的實現: 引數a是 " + a + ", 引數b
是 " + b);
};
 lambda3.test(10, 20);

2.2. Lambda表示式的語法進階

在上述程式碼中, 的確可以使用lambda表示式實現介面, 但是依然不夠簡潔, 有簡化的空間。

2.2.1. 引數部分的精簡

引數的型別

由於在介面的方法中,已經定義了每一個引數的型別是什麼。 而且在使用lambda表示式實
現介面的時候, 必須要保證引數的數量和型別需要和介面中的方法保持一致。 因此, 此時
lambda表示式中的引數的型別可以省略不寫。
注意事項:
如果需要省略引數的型別, 要保證: 要省略, 每一個引數的型別都必須省略不寫。 絕
對不能出現, 有的引數型別省略了, 有的引數型別沒有省略。

引數的小括號

如果方法的引數列表中的引數數量 有且只有一個 ,此時,引數列表的小括號是可以省略不寫

的。

注意事項:

只有當引數的數量是一個的時候, 多了、少了都不能省略。

省略掉小括號的同時, 必須要省略引數的型別。

 // 4. 無參、有返回值的方法的實現
 SingleReturnNoneParameter lambda4 = () -> {
 System.out.println("無參、有返回值方法的實現");
 return 666;
};
 System.out.println(lambda4.test());
 // 5. 一個引數、有返回值的方法實現
 SingleReturnSingleParameter lambda5 = (int a) -> {
 System.out.println("一個引數、有返回值的方法實現: 引數是 " + a);
 return a * a;
};
 System.out.println(lambda5.test(9));
 // 6. 多個引數、有返回值的方法實現
 SingleReturnMutipleParameter lambda6 = (int a, int b) -> {
 System.out.println("多個引數、有返回值的方法實現: 引數a是 " + a + ", 引數b
是 " + b);
 return a * b;
};
 System.out.println(lambda6.test(10, 20));
}
}
// 多個引數、無返回值的方法實現
NoneReturnMutipleParameter lambda1 = (a, b) -> {
System.out.println("多個引數、無返回值方法的實現: 引數a是 " + a + ", 引數
b是 " + b);
};

2.2.2. 方法體部分的精簡

方法體大括號的精簡

當一個方法體中的邏輯, 有且只有一句的情況下, 大括號可以省略。

return的精簡
如果一個方法中唯一的一條語句是一個返回語句, 此時在省略掉大括號的同時, 也必須省略
掉return。

3. 函式引用

lambda表示式是為了簡化介面的實現的。 在lambda表示式中, 不應該出現比較複雜的邏輯。 如果在
lambda表示式中出現了過於複雜的邏輯, 會對程式的可讀性造成非常大的影響。 如果在lambda表達
式中需要處理的邏輯比較複雜, 一般情況會單獨的寫一個方法。 在lambda表示式中直接引用這個方法
即可。

或者, 在有些情況下, 我們需要在lambda表示式中實現的邏輯, 在另外一個地方已經寫好了。
此時我們就不需要再單獨寫一遍, 只需要直接引用這個已經存在的方法即可。

函式引用: 引用一個已經存在的方法, 使其替代lambda表示式完成介面的實現。

3.1. 靜態方法的引用

語法:

類::靜態方法

注意事項:

在引用的方法後面, 不要新增小括號。

引用的這個方法, 引數(數量、型別) 和 返回值, 必須要跟介面中定義的一致。

示例:

// 有參、無返回值的方法實現
NoneReturnSingleParameter lambda2 = a -> {
 System.out.println("一個引數、無返回值方法的實現: 引數是 " + a);
};
// 有參、無返回值的方法實現
NoneReturnSingleParameter lambda2 = a -> System.out.println("一個引數、無
返回值方法的實現: 引數是 " + a);
SingleReturnMutipleParameter lambda3 = (a, b) -> a + b;
/**
* @Author 千鋒大資料教學團隊
* @Company 千鋒好程式設計師大資料
* @Date 2020/4/
* @Description

3.2. 非靜態方法的引用

語法:

物件::非靜態方法

注意事項:

在引用的方法後面, 不要新增小括號。

引用的這個方法, 引數(數量、型別) 和 返回值, 必須要跟介面中定義的一致。

示例:

*/
public class Syntax1 {
 // 靜態方法的引用
 public static void main(String[] args) {
 // 實現一個多個引數的、一個返回值的介面
 // 對一個靜態方法的引用
 // 類::靜態方法
 SingleReturnMutipleParameter lambda1 = Calculator::calculate;
 System.out.println(lambda1.test(10, 20));
}
 private static class Calculator {
 public static int calculate(int a, int b) {
 // 稍微複雜的邏輯:計算a和b的差值的絕對值
 if (a > b) {
 return a - b;
}
 return b - a;
}
}
}
/**
* @Author 千鋒大資料教學團隊
* @Company 千鋒好程式設計師大資料
* @Date 2020/4/
* @Description
*/
public class Syntax2 {
 public static void main(String[] args) {
 // 對非靜態方法的引用,需要使用物件來完成
 SingleReturnMutipleParameter lambda = new
Calculator()::calculate;
 System.out.println(lambda.test(10, 30));
}
 private static class Calculator {
 public int calculate(int a, int b) {
 return a > b? a - b : b - a;
}
}
}

3.3. 構造方法的引用

使用場景

如果某一個函式式介面中定義的方法, 僅僅是為了得到一個類的物件。 此時我們就可以使用

構造方法的引用, 簡化這個方法的實現。

語法

類名::new
注意事項
可以通過介面中的方法的引數, 區分引用不同的構造方法。
示例
/**
* @Author 千鋒大資料教學團隊
* @Company 千鋒好程式設計師大資料
* @Date 2020/4/
* @Description
*/
public class Syntax3 {
 private static class Person {
 String name;
 int age;
 public Person() {
 System.out.println("一個Person物件被例項化了");
}
 public Person(String name, int age) {
 System.out.println("一個Person物件被有參的例項化了");
 this.name = name;
 this.age = age;
}
}
 @FunctionalInterface
 private interface GetPerson {
 // 僅僅是希望獲取到一個Person物件作為返回值
 Person test();
}
 private interface GetPersonWithParameter {
 Person test(String name, int age);
}
 public static void main(String[] args) {
 // lambda表示式實現介面
 GetPerson lambda = Person::new; // 引用到Person類中的無參構造方
法,獲取到一個Person物件
 Person person = lambda.test();
 GetPersonWithParameter lambda2 = Person::new;  // 引用到Person類
中的有參構造方法,獲取到一個Person物件
 lambda2.test("xiaoming", 1);
}
}

3.4. 物件方法的特殊引用

如果在使用lambda表示式,實現某些介面的時候。 lambda表示式中包含了某一個物件, 此時方法體
中, 直接使用這個物件呼叫它的某一個方法就可以完成整體的邏輯。 其他的引數, 可以作為呼叫方法
的引數。 此時, 可以對這種實現進行簡化。

/**
* @Author 千鋒大資料教學團隊
* @Company 千鋒好程式設計師大資料
* @Date 2020/4/
* @Description
*/
public class Syntax {
 public static void main(String[] args) {
 // 如果對於這個方法的實現邏輯,是為了獲取到物件的名字
 GetField field = person -> person.getName();
 // 對於物件方法的特殊引用
 GetField field = Person::getName;
 // 如果對於這個方法的實現邏輯,是為了給物件的某些屬性進行賦值
 SetField lambda = (person, name) -> person.setName(name);
 SetField lambda = Person::setName;
 // 如果對於這個方法的實現邏輯,正好是引數物件的某一個方法
 ShowTest lambda2 = person -> person.show();
 ShowTest lambda2 = Person::show;
}
}
interface ShowTest {
 void test(Person person);
}
interface SetField {
 void set(Person person, String name);
}
interface GetField {
 String get(Person person);
}
class Person {
 private String name;
 public void setName(String name) {
 this.name = name;
}
 public String getName() {
 return name;
}
 public void show() {  
 }

4. Lambda表示式需要注意的問題

這裡類似於區域性內部類、匿名內部類,依然存在閉包的問題。

如果在lambda表示式中,使用到了區域性變數,那麼這個區域性變數會被隱式的宣告為 final。 是一個常
量, 不能修改值。

5. Lamb da表示式的例項

5.1. 執行緒的例項化

5.2. 集合的常見方法

5.3. 集合的流式程式設計

Thread thread = new Thread(() -> {
 // 執行緒中的處理
});
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "千鋒", "大資料", "好程式設計師", "嚴選", "高薪");
// 按照條件進行刪除
list.removeIf(ele -> ele.endsWith(".m"));
// 批量替換
list.replaceAll(ele -> ele.concat("!"));
// 自定義排序
list.sort((e1, e2) -> e2.compareTo(e1));
// 遍歷
list.forEach(System.out::println);
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "千鋒", "大資料", "好程式設計師", "嚴選", "高薪");

網路程式設計

1.1概述

計算機網路:

計算機網路是指將地理位置不同的具有獨立功能的多臺計算機及其外部裝置,通過通訊線路連線起來,在網路作業系統,網路管理軟體及網路通訊協議的管理和協調下,實現資源共享和資訊傳遞的計算機系統。

網路程式設計的目的:

無線電臺,傳播交流資訊,資料交換,通訊

需要做什麼:
  1. 如何準確定位網路上的一臺主機 192.168.16.124:埠
  2. 找到了這個主機,如何傳傳輸資料
    Java web: 網頁程式設計 B/S
    網路程式設計: TCP/IP C/S

1.2網路通訊的要素

如何實現網路的通訊?

通訊的地址:

  • IP
規則:網路通訊的協議

TCP/IP參考模型

小結:
  1. 網路程式設計的有兩個主要問題
    • 如何準確的定位網路上的一臺或者多型計算機
    • 找到主機後如何進行通訊
  2. 網路程式設計中的要素
    • IP和埠號
    • 網路通訊協議:TCP UDP

IP

ip地址:InteAddress

  • 唯一定位一臺網路上的計算機
  • 127.0.0.1 :本機localhost
  • ip地址的分類
    • ipv4/ipv6
      • **IPV4 **: 127.0.0.1,由4個位元組組成,0-255
      • IPV6: 2409:8a5c:4027:bd50:4168:a92e:8054:524b ,128位,8個16進位制數的4位數整數
    • 公網(網際網路)-私網(區域網)
      如:192.168.xxx.xxx
  • 域名,為了方便記憶,實際上就是ip地址

埠(Prot)

埠表示計算機上的一個程式的程序

  • 不同的程序有不同的埠號!用來區分軟體
  • 埠被規定 0-65535
  • 單個協議下埠號不能衝突
  • 埠分類:
  • 公用埠 0-1024
    • http:80
    • https:443
    • ftp: 21
    • Telent:23
  • 程式註冊埠
    • Tomcat: 8080
    • MySQL: 3306
    • Oracle : 1521
  • 動態,私有埠
    檢視所有埠 [cmd命令]
    檢視指定埠所被佔用的程序id(PID) [cmd命令]

通訊協議

網路通訊協議: 速率,傳輸位元速率,程式碼結構,傳輸控制
問題: 太繁雜,於是有了分層的概念
套接字Socket = 埠Prot + IP地址
其中,IP地址用來標識網路上的唯一的一臺計算機,而埠是用來唯一標識程式的程序

TCP/IP協議:實際上是一組協議

兩個重要的協議

  • TCP:使用者傳輸協議
  • UDP:使用者輸出報協議

TCP和UDP對比

TCP:相當於打電話
  • 連結,穩定

  • 三次握手,四次揮手

    最少需要三次握手,保證穩定連結!
    A : 滴滴
    B : 在
    A : 在幹嘛?
    四次揮手
    A : 我要斷開了
    B : 我知道你要斷開了
    B : 你確定要斷開了嘛
    A : 我真的要斷開!
    
  • 客戶端,服務端

  • 傳輸完成,釋放連結,效率低

UDP:相當於發短息
  • 不連線,不穩定
  • 客戶端,服務端:沒有明確的界限
  • 不管對方是否接受,都能傳送

TCP

實現簡單通訊

//服務端
public class TcpServer {

    public static void main(String[] args) throws Exception{
        Socket socket = null;
        ServerSocket server = null;
        InputStream in = null; 
        ByteArrayOutputStream out = null;
        try {
            //建立一個程式埠服務
            server = new ServerSocket(4444);
            while(true) {
                //建立一個接受server 服務的連結socket
                socket = server.accept();
                // 建立服務連結的輸入流
                in = socket.getInputStream();
                //位元組陣列輸出流
                out = new ByteArrayOutputStream();
                int len;
                //緩衝陣列
                byte[] buffer = new byte[1024];
                //輸入流的資料讀取,即服務連結的資料,放入buffer中
                while((len = in.read(buffer))!=-1) {
                    //輸出流寫入buffer的資料
                    out.write(buffer, 0, len);
                }
                //列印輸出流中的資料
                System.out.println(out.toString());
            }
        }finally {
            out.close();
            in.close();
            socket.close();
            server.close();
        }
   }
}
//客戶端
public class TcpClient {
    public static void main(String[] args) throws Exception{
        OutputStream out = null;
        //建立一個埠服務連結
        Socket socket = new Socket("127.0.0.1", 4444);
             //建立一個服務連結的輸出流
             out = socket.getOutputStream();
             //向服務連結中輸出資訊
             out.write("你好,Tcp!".getBytes());
             //關閉流
            out.close();
            socket.close();
        }
}

UDP

發簡訊,只需要知道地址,不需要建立連結

public class Receive {  //接收端
    public static void main(String[] args) throws Exception {     
        InetAddress ip = InetAddress.getByName("localhost");
//        DatagramSocket socket = new DatagramSocket(9090,ip);
        //注意:用於接收的套接字也不要加IP,有IP地址的是用於傳送的
        DatagramSocket socket = new DatagramSocket(9090);
    
        //DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 9090);
        //建立用於接收的資料包,注意! 不要埠和IP
        byte[] buf = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        
        socket.receive(packet);
        
        System.out.println("傳送者的地址為:"+packet.getSocketAddress());
        System.out.println("接受到的資訊為:"+new String(buf));
        
        socket.close();
    }
}
public class Send {  //傳送端
    public static void main(String[] args) throws Exception {
        InetAddress ip = InetAddress.getLocalHost();
        //注意:傳送端的套接字不要和接受端埠一致,否則會造成端口占用
        DatagramSocket socket = new DatagramSocket(9999);
        
        //建立用於用於傳送的資料包,需要提供傳送的地址和埠號    
        byte[] buf = "你好!udp".getBytes();
        DatagramPacket packet = new DatagramPacket(buf,buf.length,ip,9090);
        
        socket.send(packet);
        socket.close();
    }
}