Java基礎最終彈---全彙總

2020-08-13 11:55:21

Java基礎全彙總


把之前的所有內容都總結寫到了一起方便檢視,有需要的可收藏呀,最後的函數式介面等不打算繼續更新了,因爲基本都是聯繫部分啦。接下來開始更新Java Web部分,秋招在即呀!加油!努力呀!

Java基礎

一、物件導向和封裝

類與物件、封裝、構造方法

1.物件導向思想

​ 物件導向思想就是在計算機程式設計過程中,參照現實中事物,將事物的屬性特徵、行爲特徵抽象出來,描述成計算機事件的設計思想。 物件導向的語言中,包含了三大基本特徵,即封裝繼承多型

1.1 類和物件

​ 類:是一組相關屬性和行爲的集合。可以看成是一類事物的模板,使用事物的屬性特徵和行爲特徵來描述該
類事物 。

​ 物件:是一類事物的具體體現。物件是類的一個範例,必然具備該類事物的屬性和行爲

1.2 類的定義

成員變數:對應事物的屬性 。位置在類中,方法外。

成員方法:對應事物的行爲

public class Student {
	//成員變數
	String name;//姓名
	int age;//年齡
        
    //成員方法  
	//學習的方法
	publi void study() {
		System.out.println("好好學習,天天向上");
	} 
    //吃飯的方法
	public void eat() {
		System.out.println("學習餓了要吃飯");
	}
}

1.3 物件的使用

建立物件:

類名 物件名 = new 類名();

使用物件存取類中的成員:

物件名.成員變數;
物件名.成員方法()

1.4 成員變數和區域性變數區別

public class Car{
    String color;//成員變數
    public void drive(){
        int speed = 80;//區域性變數
    }
}

2.封裝

屬性隱藏起來,若需要存取某個屬性,提供公共方法對其存取。

2.1 封裝的步驟

  • 使用 private 關鍵字來修飾成員變數。
  • 對需要存取的成員變數,提供對應的一對 get()方法 、 set()方法。

2.2 封裝的操作-private關鍵字

  • 使用 private 關鍵字來修飾成員變數。
public class Student {
	private String name;
	private int age;
}

提供get()方法 、 set()方法,可以存取成員變數。

public class Student {
	private String name;
	private int age;
    
	public void setName(String n) {
		name = n;
	} 
    public String getName() {
		return name;
	} 
    public void setAge(int a) {
		age = a;
	} 
    public int getAge() {
		return age;
	}
}

2.3 封裝優化-this關鍵字

this代表所在類的當前物件的參照(地址值),即物件自己的參照。

public class Student {
	private String name;
	private int age;
    
	public void setName(String name) {
		//name = name;
		this.name = name;
	} 
    public String getName() {
		return name;
	}
    public void setAge(int age) {
		//age = age;
		this.age = age;
	} 
    public int getAge() {
		return age;
    }
}

2.4 封裝優化-構造方法

當一個物件被建立時候,構造方法用來初始化該物件,給物件的成員變數賦初始值。 構造方法的寫法上,方法名與它所在的類名相同。它沒有返回值,所以不需要返回值型別,甚至不需要void。

public class Student {
	private String name;
	private int age;
    
	// 無參數構造方法
	public Student() {}
	// 有參數構造方法
	public Student(String name,int age) {
		this.name = name;
		this.age = age;
	}
}

標準程式碼:

public class Student {
	//成員變數
	private String name;
	private int age;
    
	//構造方法
	public Student() {}
	public Student(String name,int age) {
		this.name = name;
		this.age = age;
	} 
    
    //成員方法
	public void setName(String name) {
		this.name = name;
	} 
    public String getName() {
		return name;
	} 
    public void setAge(int age) {
		this.age = age;
	} 
    public int getAge() {
		return age;
	}
}

測試類:

public class TestStudent {
	public static void main(String[] args) {
	//無參構造使用
	Student s= new Student();
	s.setName("楊冪");
	s.setAge(18);
	System.out.println(s.getName()+"‐‐‐"+s.getAge());
        
	//帶參構造使用
	Student s2= new Student("趙麗穎",18);
	System.out.println(s2.getName()+"‐‐‐"+s2.getAge());
	}
}

二、常用API

Scanner類、Random類、ArrayList類

1.Scanner類

一個可以解析基本型別和字串的簡單文字掃描器。

1.1 參照型別使用步驟

  • 導包:使用import關鍵字導包,在類的所有程式碼之前導包,引入要使用的型別,java.lang包下的所有類無需匯入。
import 包名.類名;
java.util.Scanner;
  • 建立物件:使用該類的構造方法,建立一個該類的物件。
數據型別 變數名 = new 數據型別(參數列表);
Scanner sc = new Scanner(System.in);
  • 呼叫方法:呼叫該類的成員方法,完成指定功能。
變數名.方法名();
int i = sc.nextInt(); // 接收一個鍵盤錄入的整數

2.Random類

此類的範例用於生成僞亂數。

public int nextInt(int n) :返回一個僞亂數,範圍在 0 (包括)和 指定值 n (不包括)之間的
int 值

//1. 導包
import java.util.Random;
public class Demo01_Random {
	public static void main(String[] args) {
		//2. 建立鍵盤錄入數據的物件
		Random r = new Random();
        
		for(int i = 0; i < 3; i++){
			//3. 隨機生成一個數據
			int number = r.nextInt(10);
			//4. 輸出數據
			System.out.println("number:"+ number);
		}
	}
}

3.ArrayList類

java.util.ArrayList 是大小可變的陣列的實現,儲存在內的數據稱爲元素。此類提供一些方法來操作內部儲存
的元素。 ArrayList 中可不斷新增元素,其大小也自動增長。

3.1 ArrayList使用步驟

  • 檢視類:java.util.ArrayList :該類需要 import匯入使後使用。 ,表示一種指定的數據型別,叫做泛型。 E ,取自Element(元素)的首字母。在出現 E 的地方,我們使用一種參照數據型別將其替換即可,表示我們將儲存哪種參照型別的元素。
ArrayList<String>,ArrayList<Student>
  • 檢視構造方法:public ArrayList() :構造一個內容爲空的集合。
ArrayList<String> list = new ArrayList<String>();
//在JDK 7後,右側泛型的尖括號之內可以留空,但是<>仍然要寫。
ArrayList<String> list = new ArrayList<>();
  • 檢視成員方法:public boolean add(E e) : 將指定的元素新增到此集合的尾部。參數 E e ,在構造ArrayList物件時, 指定了什麼數據型別,那麼 add(E e) 方法中,只能新增什麼數據型別的物件。
public class Test02StudentArrayList {
	public static void main(String[] args) {
		//建立學生陣列
		ArrayList<String> list = new ArrayList<>();
		//建立學生物件
		String s1 = "曹操";
		String s2 = "劉備";
        
		//列印學生ArrayList集合
		System.out.println(list);
        
		//把學生物件作爲元素新增到集合
		list.add(s1);
		list.add(s2);
        
		//列印學生ArrayList集合
		System.out.println(list);
	}
}

3.2常用方法和遍歷

對於元素的操作,基本體現在——增、刪、查。常用的方法有:
public boolean add(E e) :將指定的元素新增到此集合的尾部。
public E remove(int index) :移除此集閤中指定位置上的元素。返回被刪除的元素。
public E get(int index) :返回此集閤中指定位置上的元素。返回獲取的元素。
public int size() :返回此集閤中的元素數。遍歷集合時,可以控制索引範圍,防止越界。

public class Demo01ArrayListMethod {
	public static void main(String[] args) {
		//建立集合物件
		ArrayList<String> list = new ArrayList<String>();
        
		//新增元素
		list.add("hello");
		list.add("world");
        
		//public E get(int index):返回指定索引處的元素
		System.out.println("get:"+list.get(0));
		System.out.println("get:"+list.get(1));
		//public int size():返回集閤中的元素的個數
		System.out.println("size:"+list.size());
		//public E remove(int index):刪除指定索引處的元素,返回被刪除的元素
		System.out.println("remove:"+list.remove(0));
		//遍歷輸出
		for(int i = 0; i < list.size(); i++){
			System.out.println(list.get(i));
		}
	}
}

String類、static關鍵字、Arrays類、Math類

1.String類

1.1 使用步驟

  • 檢視類:java.lang.String :此類不需要匯入。
  • 檢視構造方法:
    • public String() :初始化新建立的 String物件,以使其表示空字元序列。
    • public String(char[] value) :通過當前參數中的字元陣列來構造新的String。
    • public String(byte[] bytes) :通過使用平臺的預設字元集解碼當前參數中的位元組陣列來構造新的String。
// 無參構造
String str = new String();
// 通過字元陣列構造
char chars[] = {'a', 'b', 'c'};
String str2 = new String(chars);
// 通過位元組陣列構造
byte bytes[] = { 97, 98, 99 };
String str3 = new String(bytes);

1.2 常用方法

  • 判斷功能的方法
    • public boolean equals (Object anObject) :將此字串與指定物件進行比較。
    • public boolean equalsIgnoreCase (String anotherString) :將此字串與指定物件進行比較,忽略大小寫。
public class String_Demo01 {
	public static void main(String[] args) {
		// 建立字串物件
		String s1 = "hello";
		String s2 = "hello";
		String s3 = "HELLO";
		// boolean equals(Object obj):比較字串的內容是否相同
		System.out.println(s1.equals(s2)); // true
		System.out.println(s1.equals(s3)); // false
		//boolean equalsIgnoreCase(String str):比較字串的內容是否相同,忽略大小寫
		System.out.println(s1.equalsIgnoreCase(s2)); // true
		System.out.println(s1.equalsIgnoreCase(s3)); // true
	}
}
  • 獲取功能的方法
    • public int length () :返回此字串的長度。
    • public String concat (String str) :將指定的字串連線到該字串的末尾。
    • public char charAt (int index) :返回指定索引處的 char值。
    • public int indexOf (String str) :返回指定子字串第一次出現在該字串內的索引。
    • public String substring (int beginIndex) :返回一個子字串,從beginIndex開始擷取字串到字串結尾。
    • public String substring (int beginIndex, int endIndex) :返回一個子字串,從beginIndex到endIndex擷取字串。含beginIndex,不含endIndex。
public class String_Demo02 {
	public static void main(String[] args) {
		//建立字串物件
		String s = "helloworld";
		// int length():獲取字串的長度,其實也就是字元個數
		System.out.println(s.length());
		// String concat (String str):將將指定的字串連線到該字串的末尾.
		String s = "helloworld";
		String s2 = s.concat("**hello");
		System.out.println(s2);// helloworld**hello
		// char charAt(int index):獲取指定索引處的字元
		System.out.println(s.charAt(0));
		System.out.println(s.charAt(1));
		// int indexOf(String str):獲取str在字串物件中第一次出現的索引,沒有返回‐1
		System.out.println(s.indexOf("l"));
    	System.out.println(s.indexOf("owo"));
		// String substring(int start):從start開始擷取字串到字串結尾
		System.out.println(s.substring(0));
		// String substring(int start,int end):從start到end擷取字串。含start,不含end。
		System.out.println(s.substring(0, s.length()));
		System.out.println(s.substring(3,8));
	}
}
  • 轉換功能的方法 :
    • public char[] toCharArray () :將此字串轉換爲新的字元陣列。
    • public byte[] getBytes () :使用平臺的預設字元集將該 String編碼轉換爲新的位元組陣列。
    • public String replace (CharSequence target, CharSequence replacement) :將與target匹配的字串使用replacement字串替換。
public class String_Demo03 {
	public static void main(String[] args) {
		//建立字串物件
		String s = "abcde";
		// char[] toCharArray():把字串轉換爲字元陣列
		char[] chs = s.toCharArray();
		for(int x = 0; x < chs.length; x++) {
			System.out.println(chs[x]);
		} 
		// byte[] getBytes ():把字串轉換爲位元組陣列
		byte[] bytes = s.getBytes();
		for(int x = 0; x < bytes.length; x++) {
			System.out.println(bytes[x]);
		}
		// 替換字母it爲大寫IT
		String str = "itcast itheima";
		String replace = str.replace("it", "IT");
		System.out.println(replace); // ITcast ITheima
		}
}
  • 分割功能的方法
    • public String[] split(String regex) :將此字串按照給定的regex(規則)拆分爲字串陣列。
public class String_Demo03 {
	public static void main(String[] args) {
		//建立字串物件
		String s = "aa|bb|cc";
		String[] strArray = s.split("|"); // ["aa","bb","cc"]
		for(int x = 0; x < strArray.length; x++) {
			System.out.println(strArray[x]); // aa bb cc
		}
	}
}

2.static關鍵字

用來修飾的成員變數和成員方法,被修飾的成員是屬於類的,而不是單單是屬於某個物件的。

2.1 定義和使用

  • 類變數

當 static 修飾成員變數時,該變數稱爲類變數。該類的每個物件都共用同一個類變數的值。

static 數據型別 變數名;
static int numberID;   
  • 靜態方法

當 static 修飾成員方法時,該方法稱爲類方法

修飾符 static 返回值型別 方法名 (參數列表){
	// 執行語句
}
public static void showNum() {
	System.out.println("num:" + numberOfStudent);
}

靜態方法呼叫的注意事項:

  1. ​ 靜態方法可以直接存取類變數和靜態方法。
  2. ​ 靜態方法不能直接存取普通成員變數或成員方法。反之,成員方法可以直接存取類變數或靜態方法。
  3. ​ 靜態方法中,不能使用this關鍵字
  • 呼叫格式

被static修飾的成員可以並且建議通過類名直接存取

// 存取類變數
類名.類變數名;
// 呼叫靜態方法
類名.靜態方法名(參數)public class StuDemo2 {
	public static void main(String[] args) {
		// 存取類變數
		System.out.println(Student.numberOfStudent);
		// 呼叫靜態方法
		Student.showNum();
	}
}

2.2 靜態程式碼塊

定義在成員位置,使用static修飾的程式碼塊{ }。
位置:類中方法外。
執行:隨着類的載入而執行且執行一次,優先於main方法和構造方法的執行。

public class ClassName{
	static {
		// 執行語句
	}
}

3.Arrays類

java.util.Arrays 此類包含用來運算元組的各種方法,比如排序和搜尋等。

3.1 運算元組的方法

  • public static String toString(int[] a) :返回指定陣列內容的字串表示形式。
public static void main(String[] args) {
	// 定義int 陣列
	int[] arr = {2,34,35,4,657,8,69,9};
	// 列印陣列,輸出地址值
	System.out.println(arr); // [I@2ac1fdc4
	// 陣列內容轉爲字串
	String s = Arrays.toString(arr);
	// 列印字串,輸出內容
	System.out.println(s); // [2, 34, 35, 4, 657, 8, 69, 9]
}
  • public static void sort(int[] a) :對指定的 int 型陣列按數位升序進行排序。
public static void main(String[] args) {
	// 定義int 陣列
	int[] arr = {24, 7, 5, 48, 4, 46, 35, 11, 6, 2};
	System.out.println("排序前:"+ Arrays.toString(arr)); //排序前:[24,7,5,48,4,46,35,11, 6,2]
	// 升序排序
	Arrays.sort(arr);
	System.out.println("排序後:"+ Arrays.toString(arr));// 排序後:[2, 4, 5, 6, 7, 11, 24, 35, 46,48]
}

4.Math類

java.lang.Math 類包含用於執行基本數學運算的方法,如初等指數、對數、平方根和三角函數。

4.1 基本運算方法

  • public static double abs(double a) :返回 double 值的絕對值。
double d1 = Math.abs(5); //d1的值爲5
  • public static double ceil(double a) :返回大於等於參數的最小的整數。
double d1 = Math.ceil(3.3); //d1的值爲 4.0
  • public static double floor(double a) :返回小於等於參數最大的整數。
double d1 = Math.floor(3.3); //d1的值爲3.0
  • public static long round(double a) :返回最接近參數的 long。(相當於四捨五入方法)
long d1 = Math.round(5.5); //d1的值爲6.0

Object類、日期時間類、System類、StringBuilder類、包裝類

1.Object類

java.lang.Object類是Java語言中的根類,即所有類的父類別。它中描述的所有方法子類都可以使用。在物件範例化的時候,最終找的父類別就是Object。

如果一個類沒有特別指定父類別,那麼預設則繼承自Object類。

public class MyClass /*extends Object*/ {
  	// ...
}

Object類當中包含的方法有11個。主要學習其中的2個:toString和equals

1.1 toString方法

  • public String toString():返回該物件的字串表示。

toString方法返回該物件的字串表示,其實該字串內容就是物件的型別+@+記憶體地址值。由於toString方法返回的結果是記憶體地址,而在開發中,經常需要按照物件的屬性得到相應的字串表現形式,因此也需要重寫它。

  • 覆蓋重寫

如果不希望使用toString方法的預設行爲,則可以對它進行覆蓋重寫。例如自定義的Person類:

public class Person {  
    private String name;
    private int age;

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

    // 省略構造器與Getter Setter
}

1.2 equals方法

  • public boolean equals(Object obj):指示其他某個物件是否與此物件「相等」。

預設物件比較:如果沒有覆蓋重寫equals方法,那麼Object類中預設進行==運算子的物件地址比較,只要不是同一個物件,結果必然爲false。

物件內容比較:覆蓋重寫equals方法。

import java.util.Objects;

public class Person {	
	private String name;
	private int age;
	
    @Override
    public boolean equals(Object o) {
        // 如果物件地址一樣,則認爲相同
        if (this == o)
            return true;
        // 如果參數爲空,或者型別資訊不一樣,則認爲不同
        if (o == null || getClass() != o.getClass())
            return false;
        // 轉換爲當前型別
        Person person = (Person) o;
        // 要求基本型別相等,並且將參照型別交給java.util.Objects類的equals靜態方法取用結果
        return age == person.age && Objects.equals(name, person.name);
    }
}

在IntelliJ IDEA中,可以使用Code選單中的Generate…選項,也可以使用快捷鍵alt+insert,並選擇equals() and hashCode()進行自動程式碼生成。

1.3 Object類

JDK7新增了一個Objects工具類,它提供了一些方法來操作物件,它由一些靜態的實用方法組成,這些方法是null-save(空指針安全的)或null-tolerant(容忍空指針的),用於計算物件的hashcode、返回物件的字串表示形式、比較兩個物件。在比較兩個物件的時候,Object的equals方法容易拋出空指針異常,而Objects類中的equals方法就優化了這個問題。

  • public static boolean equals(Object a, Object b):判斷兩個物件是否相等。
public static boolean equals(Object a, Object b) {  
    return (a == b) || (a != null && a.equals(b));  
}

2.日期時間類

2.1 Date類

java.util.Date類表示特定的瞬間,精確到毫秒。

  • public Date():分配Date物件並初始化此物件,以表示分配它的時間(精確到毫秒)。
  • public Date(long date):分配Date物件並初始化此物件,以表示自從標準基準時間(稱爲「曆元(epoch)」,即1970年1月1日00:00:00 GMT)以來的指定毫秒數。

使用無參構造,可以自動設定當前系統時間的毫秒時刻;指定long型別的構造參數,可以自定義毫秒時刻。

import java.util.Date;

public class Demo01Date {
    public static void main(String[] args) {
        // 建立日期物件,把當前的時間
        System.out.println(new Date()); // Tue Jan 16 14:37:35 CST 2018
        // 建立日期物件,把當前的毫秒值轉成日期物件
        System.out.println(new Date(0L)); // Thu Jan 01 08:00:00 CST 1970
    }
}

常用方法:public long getTime() 把日期物件轉換成對應的時間毫秒值。

2.2 DateFormat類

java.text.DateFormat 是日期/時間格式化子類的抽象類,我們通過這個類可以幫我們完成日期和文字之間的轉換,也就是可以在Date物件與String物件之間進行來回轉換。

格式化:從Date物件轉換爲String物件。

解析:從String物件轉換爲Date物件。

  • 構造方法

由於DateFormat爲抽象類,不能直接使用,所以需要常用的子類java.text.SimpleDateFormat。這個類需要一個模式(格式)來指定格式化或解析的標準。

public SimpleDateFormat(String pattern):用給定的模式和預設語言環境的日期格式符號構造SimpleDateFormat。

  • 格式規則
標識字母(區分大小寫) 含義
y
M
d
H
m
s
import java.text.DateFormat;
import java.text.SimpleDateFormat;

public class Demo02SimpleDateFormat {
    public static void main(String[] args) {
        // 對應的日期格式如:2018-01-16 15:06:38
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }    
}
  • 常用方法

DateFormat類的常用方法有:

public String format(Date date):將Date物件格式化爲字串。

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
 把Date物件轉換成String
*/
public class Demo03DateFormatMethod {
    public static void main(String[] args) {
        Date date = new Date();
        // 建立日期格式化物件,在獲取格式化物件時可以指定風格
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
        String str = df.format(date);
        System.out.println(str); // 2008年1月23日
    }
}

public Date parse(String source):將字串解析爲Date物件。

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
 把String轉換成Date物件
*/
public class Demo04DateFormatMethod {
    public static void main(String[] args) throws ParseException {
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
        String str = "2018年12月11日";
        Date date = df.parse(str);
        System.out.println(date); // Tue Dec 11 00:00:00 CST 2018
    }
}

2.3 Calendar類

java.util.Calendar是日曆類,在Date後出現,替換掉了許多Date的方法。該類將所有可能用到的時間資訊封裝爲靜態成員變數,方便獲取。日曆類就是方便獲取各個時間屬性的。

  • 獲取方法

Calendar靜態方法

public static Calendar getInstance():使用預設時區和語言環境獲得一個日曆。

import java.util.Calendar;

public class Demo06CalendarInit {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
    }    
}
  • 常用方法

Calendar類中提供很多成員常數,代表給定的日曆欄位:

欄位值 含義
YEAR
MONTH 月(從0開始,可以+1使用)
DAY_OF_MONTH 月中的天(幾號)
HOUR 時(12小時制)
HOUR_OF_DAY 時(24小時制)
MINUTE
SECOND
DAY_OF_WEEK 週中的天(周幾,週日爲1,可以-1使用)

get/set方法

public int get(int field):返回給定日曆欄位的值。

public void set(int field, int value):將給定的日曆欄位設定爲給定值。

import java.util.Calendar;

public class CalendarUtil {
    public static void main(String[] args) {
        // 建立Calendar物件
        Calendar cal = Calendar.getInstance();
        // 設定年 
        int year = cal.get(Calendar.YEAR);
        // 設定月
        int month = cal.get(Calendar.MONTH) + 1;
        // 設定日
        int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日");
    }    
}
import java.util.Calendar;

public class Demo07CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 2020);
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2020年1月17日
    }
}

add方法

public abstract void add(int field, int amount):根據日曆的規則,爲給定的日曆欄位新增或減去指定的時間量。

import java.util.Calendar;

public class Demo08CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2018年1月17日
        // 使用add方法
        cal.add(Calendar.DAY_OF_MONTH, 2); // 加2天
        cal.add(Calendar.YEAR, -3); // 減3年
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2015年1月18日; 
    }
}

getTime方法

public Date getTime():返回一個表示此Calendar時間值(從曆元到現在的毫秒偏移量)的Date物件。

import java.util.Calendar;
import java.util.Date;

public class Demo09CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        Date date = cal.getTime();
        System.out.println(date); // Tue Jan 16 16:03:09 CST 2018
    }
}

3.System類

java.lang.System類中提供了大量的靜態方法,可以獲取與系統相關的資訊或系統級操作。

3.1 currentTimeMillis方法

public static long currentTimeMillis():返回以毫秒爲單位的當前時間。

currentTimeMillis方法就是 獲取當前系統時間與1970年01月01日00:00點之間的毫秒差值。

import java.util.Date;

public class SystemDemo {
    public static void main(String[] args) {
       	//獲取當前時間毫秒值
        System.out.println(System.currentTimeMillis()); // 1516090531144
    }
}

3.2 arraycopy方法

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):將陣列中指定的數據拷貝到另一個數組中。

陣列的拷貝動作是系統級的,效能很高。System.arraycopy方法具有5個參數,含義分別爲:

參數序號 參數名稱 參數型別 參數含義
1 src Object 源陣列
2 srcPos int 源陣列索引起始位置
3 dest Object 目標陣列
4 destPos int 目標陣列索引起始位置
5 length int 複製元素個數

將src陣列中前3個元素,複製到dest陣列的前3個位置上覆制元素前:

import java.util.Arrays;

public class Demo11SystemArrayCopy {
    public static void main(String[] args) {
        int[] src = new int[]{1,2,3,4,5};
        int[] dest = new int[]{6,7,8,9,10};
        System.arraycopy( src, 0, dest, 0, 3);
        /*程式碼執行後:兩個陣列中的元素髮生了變化
         src陣列元素[1,2,3,4,5]
         dest陣列元素[1,2,3,9,10]
        */
    }
}

4.StringBuilder類

由於String類的物件內容不可改變,對字串進行拼接操作,每次拼接,都會構建一個新的String物件,既耗時,又浪費空間。爲了解決這一問題,可以使用java.lang.StringBuilder類。StringBuilder又稱爲可變字元序列,它是一個類似於 String 的字串緩衝區,通過某些方法呼叫可以改變該序列的長度和內容。

StringBuilder是個字串的緩衝區,即它是一個容器,容器中可以裝很多字串。並且能夠對其中的字串進行各種操作。它的內部擁有一個數組用來存放字串內容,進行字串拼接時,直接在陣列中加入新內容。StringBuilder會自動維護陣列的擴容。

StringBuilder和StringBuffer的區別:

如果是單任務存取,就使用StringBuilder;如果是單任務存取,使用StringBuffer

4.1 構造方法

常用構造方法有2個:

  • public StringBuilder():構造一個空的StringBuilder容器。
  • public StringBuilder(String str):構造一個StringBuilder容器,並將字串新增進去。
public class StringBuilderDemo {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder();
        System.out.println(sb1); // (空白)
        // 使用帶參構造
        StringBuilder sb2 = new StringBuilder("itcast");
        System.out.println(sb2); // itcast
    }
}

4.2 常用方法

  • append方法

public StringBuilder append(...):新增任意型別數據的字串形式,並返回當前物件自身。

public class Demo02StringBuilder {
	public static void main(String[] args) {
		//建立物件
		StringBuilder builder = new StringBuilder();
		//public StringBuilder append(任意型別)
		StringBuilder builder2 = builder.append("hello");
		//對比一下
		System.out.println("builder:"+builder);
		System.out.println("builder2:"+builder2);
		System.out.println(builder == builder2); //true
	    // 可以新增 任何型別
		builder.append("world");
		builder.append(true);
		builder.append(100);
		//鏈式程式設計
		builder.append("hello").append("world").append(true).append(100);
		System.out.println("builder:"+builder);
	}
}
  • toString方法

public String toString():將當前StringBuilder物件轉換爲String物件。

通過toString方法,StringBuilder物件將會轉換爲不可變的String物件。

public class Demo16StringBuilder {
    public static void main(String[] args) {
        // 鏈式建立
        StringBuilder sb = new StringBuilder("Hello").append("World").append("Java");
        // 呼叫方法
        String str = sb.toString();
        System.out.println(str); // HelloWorldJava
    }
}

5.包裝類

Java提供了兩個型別系統,基本型別與參照型別,如果想要我們的基本型別像物件一樣操作,就可以使用基本型別對應的包裝類。

基本型別 對應的包裝類(位於java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

5.1 裝箱與拆箱

基本型別與對應的包裝類物件之間,來回轉換的過程稱爲」裝箱「與」拆箱「:

  • 裝箱:從基本型別轉換爲對應的包裝類物件。
Integer i = new Integer(4);//使用建構函式函數
Integer iii = Integer.valueOf(4);//使用包裝類中的valueOf方法
  • 拆箱:從包裝類物件轉換爲對應的基本型別。
int num = i.intValue();

5.2 自動裝箱與自動拆箱

由於我們經常要做基本型別與包裝類之間的轉換,從Java 5(JDK 1.5)開始,基本型別與包裝類的裝箱、拆箱動作可以自動完成。

Integer i = 4;//自動裝箱。相當於Integer i = Integer.valueOf(4);
i = i + 5;//等號右邊:將i物件轉成基本數值(自動拆箱) i.intValue() + 5;
//加法運算完成後,再次裝箱,把基本數值轉成物件。

5.3 基本型別與字串之間的轉換

  • 基本型別轉換String

基本型別直接與」」相連線即可;如:34+""

  • String轉換成對應的基本型別

除了Character類之外,其他所有包裝類都具有parseXxx靜態方法可以將字串參數轉換爲對應的基本型別:

  • public static byte parseByte(String s):將字串參數轉換爲對應的byte基本型別。
  • public static short parseShort(String s):將字串參數轉換爲對應的short基本型別。
  • public static int parseInt(String s):將字串參數轉換爲對應的int基本型別。
  • public static long parseLong(String s):將字串參數轉換爲對應的long基本型別。
  • public static float parseFloat(String s):將字串參數轉換爲對應的float基本型別。
  • public static double parseDouble(String s):將字串參數轉換爲對應的double基本型別。
  • public static boolean parseBoolean(String s):將字串參數轉換爲對應的boolean基本型別。
public class Demo18WrapperParse {
    public static void main(String[] args) {
        int num = Integer.parseInt("100");
    }
}

三、繼承與多型

繼承、抽象類

1.繼承

多個類中存在相同屬性和行爲時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行爲,只要
繼承那一個類即可。 多個類可以稱爲子類,單獨那一個類稱爲父類別超類(superclass或者基礎類別

  • 定義

繼承:就是子類繼承父類別的屬性和行爲,使得子類物件具有與父類別相同的屬性、相同的行爲。子類可以直接
存取父類別中的非私有的屬性和行爲。

  • 優點
    • 提高程式碼的複用性
    • 類與類之間產生了關係,是多型的前提

1.1 繼承的格式

通過 extends 關鍵字,可以宣告一個子類繼承另外一個父類別。

class 父類別 {
...
}
class 子類 extends 父類別 {
...
}
//定義員工類Employee,做爲父類別
class Employee {
	String name; // 定義name屬性
	// 定義員工的工作方法
	public void work() {
		System.out.println("盡心盡力地工作");
	}
} 

//定義講師類Teacher繼承員工類Employee
class Teacher extends Employee {
	// 定義一個列印name的方法
	public void printName() {
		System.out.println("name=" + name);
	}
} 

//定義測試類
public class ExtendDemo01 {
	public static void main(String[] args) {
		// 建立一個講師類物件
		Teacher t = new Teacher();
		// 爲該員工類的name屬性進行賦值
		t.name = "小明";
		// 呼叫該員工的printName()方法
		t.printName(); // name = 小明
		// 呼叫Teacher類繼承來的work()方法
		t.work(); // 盡心盡力地工作
	}
}

1.2 繼承後的特點-成員變數

  • 成員變數不重名

如果子類父類別中出現不重名的成員變數,這時的存取是沒有影響的

class Fu {
	// Fu中的成員變數。
	int num = 5;
} 
cass Zi extends Fu {
	// Zi中的成員變數
	int num2 = 6;
	// Zi中的成員方法
	public void show() {
		// 存取父類別中的num,
		System.out.println("Fu num="+num); // 繼承而來,所以直接存取。
		// 存取子類中的num2
		System.out.println("Zi num2="+num2);
	}
} 
class ExtendDemo02 {
	public static void main(String[] args) {
		// 建立子類物件
		Zi z = new Zi();
		// 呼叫子類中的show方法
		z.show();//Fu num = 5  Zi num2 = 6
	}
}
  • 成員變數重名

如果子類父類別中出現重名的成員變數,這時的存取是有影響的

class Fu {
	// Fu中的成員變數。
	int num = 5;
} 
class Zi extends Fu {
	// Zi中的成員變數
	int num = 6;
	public void show() {
		// 存取父類別中的num
		System.out.println("Fu num=" + num);
		// 存取子類中的num
		System.out.println("Zi num=" + num);
	}
} 
class ExtendsDemo03 {
	public static void main(String[] args) {
		// 建立子類物件
		Zi z = new Zi();
		// 呼叫子類中的show方法
		z.show();//Fu num = 6  Zi num = 6
	}
} 

子父類別中出現了同名的成員變數時,在子類中需要存取父類別中非私有成員變數時,需要使用 super 關鍵字,修飾父類別成員變數,類似於之前學過的this

  • 格式
super.父類別成員變數名
class Zi extends Fu {
	// Zi中的成員變數
	int num = 6;
	public void show() {
		//存取父類別中的num
		System.out.println("Fu num=" + super.num);
		//存取子類中的num
		System.out.println("Zi num=" + this.num);
	}
}

1.3 繼承後的特點-成員方法

  • 成員方法不重名

如果子類父類別中出現不重名的成員方法,這時的呼叫是沒有影響的。物件呼叫方法時,會先在子類中查詢有沒有對
應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類別中相應的方法。

class Fu{
	public void show(){
		System.out.println("Fu類中的show方法執行");
	}
} 
class Zi extends Fu{
	public void show2(){
		System.out.println("Zi類中的show2方法執行");
	}
} 
public class ExtendsDemo04{
	public static void main(String[] args) {
		Zi z = new Zi();
		//子類中沒有show方法,但是可以找到父類別方法去執行
		z.show();
		z.show2();
	}
}
  • 成員方法重名——重寫(Override)

**方法重寫:**子類中出現與父類別一模一樣的方法時(返回值型別,方法名和參數列表都相同),會出現覆蓋效
果,也稱爲重寫或者複寫。宣告不變,重新實現。

class Fu {
	public void show() {
		System.out.println("Fu show");
	}
}
class Zi extends Fu {
	//子類重寫了父類別的show方法
	public void show() {
		System.out.println("Zi show");
	}
} 
public class ExtendsDemo05{
	public static void main(String[] args) {
		Zi z = new Zi();
		// 子類中有show方法,只執行重寫後的show方法
		z.show(); // Zi show
	}
}

1.4 繼承後的特點-構造方法

  • 構造方法的定義格式和作用:
    • 構造方法的名字是與類名一致的。所以子類是無法繼承父類別構造方法的。
    • 構造方法的作用是初始化成員變數的。所以子類的初始化過程中,必須先執行父類別的初始化動作。子類的構造方法中預設有一個super(),表示呼叫父類別的構造方法,父類別成員變數初始化後,纔可以給子類使用。
class Fu {
	private int n;
	Fu(){
		System.out.println("Fu()");
	}
}
class Zi extends Fu {
	Zi(){
	// super(),呼叫父類別構造方法
	super();
	System.out.println("Zi()");
	}
} 
public class ExtendsDemo07{
	public static void main (String args[]){
		Zi zi = new Zi();//Fu() Zi()
	}
}

1.5 super和this

  • super和this的含義

    • super :代表父類別的儲存空間標識(可以理解爲父親的參照)。
    • this :代表當前物件的參照(誰呼叫就代表誰)。
  • super和this的用法

存取成員

this.成員變數 ‐‐ 本類的
super.成員變數 ‐‐ 父類別的
    
this.成員方法名() ‐‐ 本類的
super.成員方法名() ‐‐ 父類別的
class Animal {
	public void eat() {
		System.out.println("animal : eat");
	}
} 
class Cat extends Animal {
	public void eat() {
		System.out.println("cat : eat");
	} 
	public void eatTest() {
		this.eat(); // this 呼叫本類的方法
		super.eat(); // super 呼叫父類別的方法
	}
} 
public class ExtendsDemo08 {
	public static void main(String[] args) {
		Animal a = new Animal();
		a.eat();
		Cat c = new Cat();
		c.eatTest();
	}
}
輸出結果爲:
animal : eat
cat : eat
animal : eat

存取構造方法

this(...) ‐‐ 本類的構造方法
super(...) ‐‐ 父類別的構造方法

1.6 繼承的特點

  • Java只支援單繼承,不支援多繼承。
//一個類只能有一個父類別,不可以有多個父類別。
class C extends A{} //ok
class C extends A,B... //error
  • Java支援多層繼承(繼承體系)。
class A{}
class B extends A{}
class C extends B{}

2.抽象類

父類別中的方法,被它的子類們重寫,子類各自的實現都不盡相同。那麼父類別的方法宣告和方法主體,只有宣告還有
意義,而方法主體則沒有存在的意義了。我們把沒有方法主體的方法稱爲抽象方法。Java語法規定,包含抽象方法
的類就是抽象類

  • 定義
    • 抽象方法 : 沒有方法體的方法。
    • 抽象類:包含抽象方法的類

2.1 abstract

  • 抽象方法

使用 abstract 關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。

格式:

修飾符 abstract 返回值型別 方法名 (參數列表)
public abstract void run()
  • 抽象類

如果一個類包含抽象方法,那麼該類必須是抽象類。

定義:

abstract class 類名字 {
}
public abstract class Animal {
	public abstract void run()}
  • 抽象的使用

繼承抽象類的子類必須重寫父類別所有的抽象方法。否則,該子類也必須宣告爲抽象類。最終,必須有子類實現該父
類的抽象方法,否則,從最初的父類別到最終的子類都不能建立物件,失去意義。

介面、多型

1.介面

介面,是Java語言中一種參照型別,是方法的集合,如果說類的內部封裝了成員變數、構造方法和成員方法,那麼介面的內部主要就是封裝了方法,包含抽象方法(JDK 7及以前),預設方法和靜態方法(JDK 8),私有方法(JDK 9)。介面的定義,它與定義類方式相似,但是使用 interface 關鍵字。它也會被編譯成.class檔案,但一定要明確它並不是類,而是另外一種參照數據型別

  • 參照數據型別:陣列,類,介面。

介面的使用,它不能建立物件,但是可以被實現( implements ,類似於被繼承)。一個實現介面的類(可以看做是介面的子類),需要實現介面中所有的抽象方法,建立該類物件,就可以呼叫方法了,否則它必須是一個抽象類。

1.1 定義格式

public interface 介面名稱 {
	// 抽象方法
	// 預設方法
	// 靜態方法
	// 私有方法
}
  • 含有抽象方法

抽象方法:使用 abstract 關鍵字修飾,可以省略,沒有方法體。該方法供子類實現使用。

public interface InterFaceName {
	public abstract void method();
}
  • 含有預設方法和靜態方法

預設方法:使用 default 修飾,不可省略,供子類呼叫或者子類重寫。
靜態方法:使用 static 修飾,供介面直接呼叫。

public interface InterFaceName {
	public default void method() {
		// 執行語句
	} 
	public static void method2() {
		// 執行語句
	}
}
  • 含有私有方法和私有靜態方法

私有方法:使用 private 修飾,供介面中的預設方法或者靜態方法呼叫。

public interface InterFaceName {
	private void method() {
		// 執行語句
	}
}

1.2 基本的實現

1.2.1 實現

類與介面的關係爲實現關係,即類實現介面,該類可以稱爲介面的實現類,也可以稱爲介面的子類。實現的動作類似繼承,格式相仿,只是關鍵字不同,實現使用 implements 關鍵字。

  • 非抽象子類實現介面:
    • 必須重寫介面中所有抽象方法。
    • 繼承了介面的預設方法,即可以直接呼叫,也可以重寫。
class 類名 implements 介面名 {
	// 重寫介面中抽象方法【必須】
	// 重寫介面中預設方法【可選】
}
1.2.2 抽象方法的使用

(必須全部實現)

  • 定義介面:
public interface LiveAble {
	// 定義抽象方法
	public abstract void eat();
	public abstract void sleep();
}
  • 定義實現類:
public class Animal implements LiveAble {
	@Override
	public void eat() {
		System.out.println("吃東西");
	} 
	@Override
	public void sleep() {
		System.out.println("晚上睡");
	}
}
  • 定義測試類:
public class InterfaceDemo {
	public static void main(String[] args) {
		// 建立子類物件
		Animal a = new Animal();
		// 呼叫實現後的方法
		a.eat();//吃東西
		a.sleep();//晚上睡
	}
}
1.2.3 預設方法的使用

(可以繼承、可以重寫,二選一,但是隻能通過實現類的物件來呼叫)

1.繼承預設方法

  • 定義介面:
public interface LiveAble {
	public default void fly(){
		System.out.println("天上飛");
	}
}
  • 定義實現類:
public class Animal implements LiveAble {
	// 繼承,什麼都不用寫,直接呼叫
}
  • 定義測試類:
public class InterfaceDemo {
	public static void main(String[] args) {
		// 建立子類物件
		Animal a = new Animal();
		// 呼叫預設方法
		a.fly();//天上飛
	}
}

2.重寫預設方法

  • 定義介面:
public interface LiveAble {
	public default void fly(){
		System.out.println("天上飛");
	}
}
  • 定義實現類:
public class Animal implements LiveAble {
	@Override
	public void fly() {
		System.out.println("自由自在的飛");
	}
}
  • 定義測試類:
public class InterfaceDemo {
	public static void main(String[] args) {
		// 建立子類物件
		Animal a = new Animal();
		// 呼叫重寫方法
		a.fly();//自由自在的飛
	}
}
1.2.4 靜態方法的使用

靜態與.class 檔案相關,只能使用介面名呼叫,不可以通過實現類的類名或者實現類的物件呼叫。

  • 定義介面:
public interface LiveAble {
	public static void run(){
		System.out.println("跑起來~~~");
	}
}
  • 定義實現類:
public class Animal implements LiveAble {
	// 無法重寫靜態方法
}
  • 定義測試類:
public class InterfaceDemo {
	public static void main(String[] args) {
		// Animal.run(); // 【錯誤】無法繼承方法,也無法呼叫
		LiveAble.run(); //跑起來~~~
	}
}
1.2.5 私有方法的使用
  • 私有方法:只有預設方法可以呼叫。
  • 私有靜態方法:預設方法和靜態方法可以呼叫。

如果一個介面中有多個預設方法,並且方法中有重複的內容,那麼可以抽取出來,封裝到私有方法中,供預設方法
去呼叫。從設計的角度講,私有的方法是對預設方法和靜態方法的輔助。

  • 定義介面
public interface LiveAble {
	default void func(){
		func1();
		func2();
	} 
	private void func1(){
		System.out.println("跑起來~~~");
	} 
	private void func2(){
		System.out.println("跑起來~~~");
	}
}

1.3 介面的多實現

在繼承體系中,一個類只能繼承一個父類別。而對於介面而言,一個類是可以實現多個介面的,這叫做接
口的多實現。並且,一個類能繼承一個父類別,同時實現多個介面。

  • 實現
class 類名 [extends 父類別名] implements 介面名1,介面名2,介面名3... {
	// 重寫介面中抽象方法【必須】
	// 重寫介面中預設方法【不重名時可選】
}
1.3.1 抽象方法

介面中,有多個抽象方法時,實現類必須重寫所有抽象方法**。如果抽象方法有重名的,只需要重寫一次。**

  • 定義多個介面:
interface A {
	public abstract void showA();
	public abstract void show();
} 
interface B {
	public abstract void showB();
	public abstract void show();
}
  • 定義實現類:
public class C implements A,B{
	@Override
	public void showA() {
		System.out.println("showA");
	}
	@Override
	public void showB() {
		System.out.println("showB");
	} 
	@Override
	public void show() {
		System.out.println("show");
	}
}
1.3.2 預設方法

介面中,有多個預設方法時,實現類都可繼承使用。如果預設方法有重名的,必須重寫一次。

  • 定義多個介面:
interface A {
public default void methodA(){}
public default void method(){}
} 
interface B {
public default void methodB(){}
public default void method(){}
}
  • 定義實現類:
public class C implements A,B{
	@Override
	public void method() {
		System.out.println("method");
	}
}
1.3.3 靜態方法

介面中,存在同名的靜態方法並不會衝突,原因是隻能通過各自介面名存取靜態方法。

1.3.4 優先順序問題

當一個類,既繼承一個父類別,又實現若幹個介面時,父類別中的成員方法與介面中的預設方法重名,子類就近選擇執
行父類別的成員方法。

  • 定義介面:
interface A {
	public default void methodA(){
		System.out.println("AAAAAAAAAAAA");
	}
}
  • 定義父類別:
class D {
	public void methodA(){
		System.out.println("DDDDDDDDDDDD");
	}
}
  • 定義子類:
class C extends D implements A {
	// 未重寫methodA方法
}
  • 定義測試類:
public class Test {
	public static void main(String[] args) {
		C c = new C();
		c.methodA();//DDDDDDDDDDDD
	}
}

1.4 介面的多繼承

一個介面能繼承另一個或者多個介面,這和類之間的繼承比較相似。介面的繼承使用 extends 關鍵字,子介面繼
承父介面的方法。如果父介面中的預設方法有重名的,那麼子介面需要重寫一次。

  • 定義父介面:
interface A {
	public default void method(){
		System.out.println("AAAAAAAAAAAAAAAAAAA");
	}
} 
interface B {
	public default void method(){
		System.out.println("BBBBBBBBBBBBBBBBBBB");
	}
}
  • 定義子介面:
interface D extends A,B{
	@Override
	public default void method() {
		System.out.println("DDDDDDDDDDDDDD");
	}
}

1.5 其他成員特點

  • 介面中,無法定義成員變數,但是可以定義常數,其值不可以改變,預設使用public static final修飾。
  • 介面中,沒有構造方法,不能建立物件。
  • 介面中,沒有靜態程式碼塊。

2.多型

多型是繼封裝、繼承之後,物件導向的第三大特性。

  • 多型: 是指同一行爲,具有多個不同表現形式。

2.1 多型的體現

格式(父類別型別:指子類物件繼承的父類別型別,或者實現的父介面型別。 )

父類別型別 變數名 = new 子類物件;
變數名.方法名();
Fu f = new Zi();
f.method();

當使用多型方式呼叫方法時,首先檢查父類別中是否有該方法,如果沒有,則編譯錯誤;如果有,執行的是子類重寫後方法。

定義父類別:

public abstract class Animal {
	public abstract void eat();
}

定義子類:

class Cat extends Animal {
	public void eat() {
		System.out.println("吃魚");
	}
}
class Dog extends Animal {
	public void eat() {
	System.out.println("吃骨頭");
	}
}

定義測試類:

public class Test {
	public static void main(String[] args) {
		// 多型形式,建立物件
		Animal a1 = new Cat();
		// 呼叫的是 Cat 的 eat
		a1.eat();
		// 多型形式,建立物件
		Animal a2 = new Dog();
		// 呼叫的是 Dog 的 eat
		a2.eat();
	}
}

2.3 多型的優點

實際開發的過程中,父類別型別作爲方法形式參數,傳遞子類物件給方法,進行方法的呼叫,更能體現出多型的擴充套件性與便利。

定義父類別:

public abstract class Animal {
	public abstract void eat();
}

定義子類:

class Cat extends Animal {
	public void eat() {
		System.out.println("吃魚");
	}
} 
class Dog extends Animal {
	public void eat() {
		System.out.println("吃骨頭");
	}
}

定義測試類:

public class Test {
	public static void main(String[] args) {
		// 多型形式,建立物件
		Cat c = new Cat();
		Dog d = new Dog();
		// 呼叫showCatEat
		showCatEat(c);
		// 呼叫showDogEat
		showDogEat(d);
		//以上兩個方法, 均可以被showAnimalEat(Animal a)方法所替代而執行效果一致
		showAnimalEat(c);
		showAnimalEat(d);
	} 
    public static void showCatEat (Cat c){
		c.eat();
	} 
    public static void showDogEat (Dog d){
		d.eat();
	} 
    public static void showAnimalEat (Animal a){
		a.eat();
	}
}

2.4 參照型別的轉換

多型的轉型分爲向上轉型與向下轉型兩種。

  • 向上轉型

多型本身是子類型別向父類別型別向上轉換的過程,這個過程是預設的。 當父類別參照指向一個子類物件時,便是向上轉型。

父類別型別 變數名 = new 子類型別();
如:Animal a = new Cat();

向下轉型

  • 向下轉型

父類別型別向子類型別向下轉換的過程,這個過程是強制的。一個已經向上轉型的子類物件,將父類別參照轉爲子類參照,可以使用強制型別轉換的格式,便是向下轉型。

子類型別 變數名 = (子類型別) 父類別變數名;:Cat c =(Cat) a;

當使用多型方式呼叫方法時,首先檢查父類別中是否有該方法,如果沒有,則編譯錯誤。也就是說,不能呼叫子類擁有,而父類別沒有的方法。編譯都錯誤,更別說執行了。這也是多型給我們帶來的一點"小麻煩"。所以,想要呼叫子類特有的方法,必須做向下轉型。

定義類:

abstract class Animal {
	abstract void eat();
	} 
class Cat extends Animal {
	public void eat() {
		System.out.println("吃魚");
	} 
    public void catchMouse() {
		System.out.println("抓老鼠");
	}
} 
class Dog extends Animal {
	public void eat() {
		System.out.println("吃骨頭");
	} 
    public void watchHouse() {
		System.out.println("看家");
	}
}

定義測試類:

public class Test {
	public static void main(String[] args) {
		// 向上轉型
		Animal a = new Cat();
		a.eat(); // 呼叫的是 Cat 的 eat
		// 向下轉型
		Cat c = (Cat)a;
		c.catchMouse(); // 呼叫的是 Cat 的 catchMouse
	}
}
  • 轉型的異常

Java提供了 instanceof 關鍵字,給參照變數做型別的校驗。

變數名 instanceof 數據型別
如果變數屬於該數據型別,返回true。
如果變數不屬於該數據型別,返回false

final、許可權、內部類、參照型別

1.final關鍵字

Java提供了 final 關鍵字,用於修飾不可改變內容。

  • final: 不可改變。可以用於修飾類、方法和變數。
    • 類:被修飾的類,不能被繼承。
    • 方法:被修飾的方法,不能被重寫。
    • 變數:被修飾的變數,不能被重新賦值。

1.1 使用方式

  • 修飾類
final class 類名 {
}
  • 修飾方法
修飾符 final 返回值型別 方法名(參數列表){
	//方法體
}
  • 修飾變數

區域性變數——基本型別

基本型別的區域性變數,被final修飾後,只能賦值一次,不能再更改。

public class FinalDemo1 {
	public static void main(String[] args) {
		// 宣告變數,使用final修飾
		final int a;
		// 第一次賦值
		a = 10;
		// 第二次賦值
		a = 20; // 報錯,不可重新賦值
	}
}

區域性變數——參照型別

參照型別的區域性變數,被final修飾後,只能指向一個物件,地址不能再更改。但是不影響物件內部的成員變數值的修改。

public class FinalDemo2 {
	public static void main(String[] args) {
		// 建立 User 物件
		final User u = new User();
		// 建立 另一個 User物件
		u = new User(); // 報錯,指向了新的物件,地址值改變。
		// 呼叫setName方法
		u.setName("張三"); // 可以修改
	}
}

成員變數

成員變數涉及到初始化的問題,初始化方式有兩種,只能二選一 :

顯示初始化

public class User {
	final String USERNAME = "張三";
	private int age;
}

構造方法初始化

public class User {
	final String USERNAME ;
	private int age;
	public User(String username, int age) {
		this.USERNAME = username;
		this.age = age;
	}
}

被final修飾的常數名稱,一般都有書寫規範,所有字母都大寫

2.許可權修飾符

Java中提供了四種存取許可權,使用不同的存取許可權修飾符修飾時,被修飾的內容會有不同的存取許可權。

  • public:公共的。
  • protected:受保護的
  • default:預設的
  • private:私有的

3.內部類

3.1 概述

將一個類A定義在另一個類B裏面,裏面的那個類A就稱爲內部類,B則稱爲外部類

  • 成員內部類

定義在類中方法外的類。

class 外部類 {
	class 內部類{
	}
}

在描述事物時,若一個事物內部還包含其他事物,就可以使用內部類這種結構。比如,汽車類 Car 中包含發動機
類 Engine ,這時, Engine 就可以使用內部類來描述,定義在成員位置。

class Car { //外部類
	class Engine { //內部類
	}
}
  • 存取特點
    • 內部類可以直接存取外部類的成員,包括私有成員。
    • 外部類要存取內部類的成員,必須要建立內部類的物件。
外部類名.內部類名 物件名 = new 外部型別().new 內部型別()

定義類:

public class Person {
	private boolean live = true;
	class Heart {
		public void jump() {
			// 直接存取外部類成員
			if (live) {
				System.out.println("心臟在跳動");
			} else {
				System.out.println("心臟不跳了");
			}
		}
	} 
    public boolean isLive() {
		return live;
	} 
    public void setLive(boolean live) {
		this.live = live;
	}
}

定義測試類:

public class InnerDemo {
	public static void main(String[] args) {
		// 建立外部類物件
		Person p = new Person();
		// 建立內部類物件
		Heart heart = p.new Heart();
		// 呼叫內部類方法
		heart.jump();
		// 呼叫外部類方法
		p.setLive(false);
		// 呼叫內部類方法
		heart.jump();
	}
}
輸出結果:
心臟在跳動
心臟不跳了

3.2 匿名內部類(重點)

匿名內部類 :是內部類的簡化寫法。它的本質是一個帶具體實現的 父類別或者父介面的 匿名的 子類物件。開發中,最常用到的內部類就是匿名內部類了。以介面舉例,當你使用一個介面時,似乎得做如下幾步操作,

  1. 定義子類
  2. 重寫介面中的方法
  3. 建立子類物件
  4. 呼叫重寫後的方法

我們的目的,最終只是爲了呼叫方法,那麼能不能簡化一下 。匿名內部類就是做這樣的快捷方式。

  • 前提: 匿名內部類必須繼承一個父類別或者實現一個父介面
  • 格式
new 父類別名或者介面名(){
	// 方法重寫
	@Override
	public void method() {
	// 執行語句
	}
};
  • 使用方法(以介面爲例)

定義介面:

public abstract class FlyAble{
	public abstract void fly();
}

建立匿名內部類,並呼叫:

public class InnerDemo {
	public static void main(String[] args) {
		//1.等號右邊:是匿名內部類,定義並建立該介面的子類物件
		//2.等號左邊:是多型賦值,介面型別參照指向子類物件
		FlyAble f = new FlyAble(){
			public void fly() {
				System.out.println("我飛了~~~");
			}
		};
		//呼叫 fly方法,執行重寫後的方法
		f.fly();
	}
}

通常在方法的形式參數是介面或者抽象類時,也可以將匿名內部類作爲參數傳遞。

public class InnerDemo2 {
	public static void main(String[] args) {
		FlyAble f = new FlyAble(){
			public void fly() {
				System.out.println("我飛了~~~");
			}
		};
		// 將f傳遞給showFly方法中
		showFly(f);
	} 
    public static void showFly(FlyAble f) {
		f.fly();
	}
}

兩步合爲一步

public class InnerDemo3 {
	public static void main(String[] args) {
		//建立匿名內部類,直接傳遞給showFly(FlyAble f)
		showFly( new FlyAble(){
			public void fly() {
				System.out.println("我飛了~~~");
			}
		});
	} 
    public static void showFly(FlyAble f) {
		f.fly();
	}
}

4.參照型別用法總結

  • class作爲成員變數

類作爲成員變數時,對它進行賦值的操作,實際上,是賦給它該類的一個物件。

  • interface作爲成員變數

  • interface作爲方法參數和返回值型別

四、集合

Collection、泛型

1.Collection集合

集合:集合是java中提供的一種容器,可以用來儲存多個數據。

集合和陣列既然都是容器,它們有啥區別呢?

  • 陣列的長度是固定的。集合的長度是可變的。
  • 陣列中儲存的是同一型別的元素,可以儲存基本數據型別值。集合儲存的都是物件。而且物件的型別可以不一致。在開發中一般當物件多的時候,使用集合進行儲存。

1.1 集合框架

集合按照其儲存結構可以分爲兩大類,分別是單列集合java.util.Collection和雙列集合java.util.Map

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-upOZK0Oo-1597290664342)(E:\Java\JavaSE\1.Java基礎\06.集合\13.【Collection、泛型】-筆記\resource\01_集合框架介紹.bmp)]

  • Collection:單列集合類的根介面,用於儲存一系列符合某種規則的元素,它有兩個重要的子介面,分別是java.util.Listjava.util.Set。其中,List的特點是元素有序、元素可重複。Set的特點是元素無序,而且不可重複。List介面的主要實現類有java.util.ArrayListjava.util.LinkedListSet介面的主要實現類有java.util.HashSetjava.util.TreeSet

1.2 Collection常用功能

Collection是所有單列集合的父介面,因此在Collection中定義了單列集合(List和Set)通用的一些方法,這些方法可用於操作所有的單列集合。方法如下:

  • public boolean add(E e): 把給定的物件新增到當前集閤中 。
  • public void clear() :清空集閤中所有的元素。
  • public boolean remove(E e): 把給定的物件在當前集閤中刪除。
  • public boolean contains(E e): 判斷當前集閤中是否包含給定的物件。
  • public boolean isEmpty(): 判斷當前集合是否爲空。
  • public int size(): 返回集閤中元素的個數。
  • public Object[] toArray(): 把集閤中的元素,儲存到陣列中。
import java.util.ArrayList;
import java.util.Collection;

public class Demo1Collection {
    public static void main(String[] args) {
		// 建立集合物件、使用多型形式
    	Collection<String> coll = new ArrayList<String>();
    	// 使用方法,新增功能  boolean  add(String s)
    	coll.add("小李廣");
    	coll.add("掃地僧");
    	coll.add("石破天");
    	System.out.println(coll);

    	// boolean contains(E e) 判斷o是否在集閤中存在
    	System.out.println("判斷  掃地僧 是否在集閤中"+coll.contains("掃地僧"));

    	//boolean remove(E e) 刪除在集閤中的o元素
    	System.out.println("刪除石破天:"+coll.remove("石破天"));
    	System.out.println("操作之後集閤中元素:"+coll);
    	
    	// size() 集閤中有幾個元素
		System.out.println("集閤中有"+coll.size()+"個元素");

		// Object[] toArray()轉換成一個Object陣列
    	Object[] objects = coll.toArray();
    	// 遍歷陣列
    	for (int i = 0; i < objects.length; i++) {
			System.out.println(objects[i]);
		}

		// void  clear() 清空集合
		coll.clear();
		System.out.println("集閤中內容爲:"+coll);
		// boolean  isEmpty()  判斷是否爲空
		System.out.println(coll.isEmpty());  	
	}
}

2.Iterator迭代器

2.1 Iterator介面

在程式開發中,經常需要遍歷集閤中的所有元素。針對這種需求,JDK專門提供了一個介面java.util.IteratorIterator介面也是Java集閤中的一員,Collection介面與Map介面主要用於儲存元素,而Iterator主要用於迭代存取(即遍歷)Collection中的元素,因此Iterator物件也被稱爲迭代器。

獲取迭代器的方法:

  • public Iterator iterator(): 獲取集合對應的迭代器,用來遍歷集閤中的元素的。

迭代:即Collection集合元素的通用獲取方式。在取元素之前先要判斷集閤中有沒有元素,如果有,就把這個元素取出來,繼續在判斷,如果還有就再取出出來。一直把集閤中的所有元素全部取出。

Iterator介面的常用方法:

  • public E next():返回迭代的下一個元素。
  • public boolean hasNext():如果仍有元素可以迭代,則返回 true。
public class IteratorDemo {
  	public static void main(String[] args) {
        // 使用多型方式 建立物件
        Collection<String> coll = new ArrayList<String>();

        // 新增元素到集合
        coll.add("串串星人");
        coll.add("吐槽星人");
        coll.add("汪星人");
        //遍歷,使用迭代器 遍歷   每個集合物件都有自己的迭代器
        Iterator<String> it = coll.iterator();
        //  泛型指的是 迭代出 元素的數據型別
        while(it.hasNext()){ //判斷是否有迭代元素
            String s = it.next();//獲取迭代出的元素
            System.out.println(s);
        }
  	}
}

2.2 迭代器的實現原理

Iterator迭代器物件在遍歷集合時,內部採用指針的方式來跟蹤集閤中的元素。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-L1gAaMcc-1597290664349)(E:\Java\JavaSE\1.Java基礎\06.集合\13.【Collection、泛型】-筆記\resource\02_迭代器的實現原理(1)].bmp)

在呼叫Iterator的next方法之前,迭代器的索引位於第一個元素之前,不指向任何元素,當第一次呼叫迭代器的next方法後,迭代器的索引會向後移動一位,指向第一個元素並將該元素返回,當再次呼叫next方法時,迭代器的索引會指向第二個元素並將該元素返回,依此類推,直到hasNext方法返回false,表示到達了集合的末尾,終止對元素的遍歷。

2.3 增強for

增強for回圈(也稱for each回圈)是JDK1.5以後出來的一個高階for回圈,專門用來遍歷陣列和集合的。它的內部原理其實是個Iterator迭代器,所以在遍歷的過程中,不能對集閤中的元素進行增刪操作。

for(元素的數據型別  變數 : Collection集合or陣列){ 
  	//寫操作程式碼
}

它用於遍歷Collection和陣列。通常只進行遍歷元素,不要在遍歷的過程中對集合元素進行增刪操作。僅用做遍歷!

  • 遍歷陣列
public class NBForDemo1 {
    public static void main(String[] args) {
		int[] arr = {3,5,6,87};
       	//使用增強for遍歷陣列
		for(int a : arr){//a代表陣列中的每個元素
			System.out.println(a);
		}
	}
}
  • 遍歷集合
public class NBFor {
    public static void main(String[] args) {        
    	Collection<String> coll = new ArrayList<String>();
    	coll.add("小河神");
    	coll.add("老河神");
    	coll.add("神婆");
    	//使用增強for遍歷
    	for(String s :coll){//接收變數s代表 代表被遍歷到的集合元素
    		System.out.println(s);
    	}
	}
}

3.泛型

  • 泛型:可以在類或方法中預支地使用未知的型別。一般在建立物件時,將未知的型別確定具體的型別。當沒有指定泛型時,預設型別爲Object型別。

3.1 使用泛型的優點

  • 將執行時期的ClassCastException,轉移到了編譯時期變成了編譯失敗。
  • 避免了型別強轉的麻煩。

泛型是數據型別的一部分,我們將類名與泛型合併一起看做數據型別。

public class GenericDemo2 {
	public static void main(String[] args) {
        Collection<String> list = new ArrayList<String>();
        list.add("abc");
        list.add("itcast");
        // list.add(5);//當集合明確型別後,存放型別不一致就會編譯報錯
        // 集合已經明確具體存放的元素型別,那麼在使用迭代器的時候,迭代器也同樣會知道具體遍歷元素型別
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String str = it.next();
            //當使用Iterator<String>控制元素型別後,就不需要強轉了。獲取到的元素直接就是String型別
            System.out.println(str.length());
        }
	}
}

3.2 泛型的定義與使用

3.2.1 含有泛型的類
  • 定義格式:
修飾符 class 類名<代表泛型的變數> {  }
class ArrayList<E>{ 
    public boolean add(E e){ }

    public E get(int index){ }
   	....
}

使用泛型: 即什麼時候確定泛型。

  • 在建立物件的時候確定泛型

例:ArrayList<String> list = new ArrayList<String>();此時,變數E的值就是String型別,那麼我們的型別就可以理解爲:

class ArrayList<String>{ 
     public boolean add(String e){ }

     public String get(int index){  }
     ...
}

自定義泛型類:

public class MyGenericClass<MVP> {
	//沒有MVP型別,在這裏代表 未知的一種數據型別 未來傳遞什麼就是什麼型別
	private MVP mvp;
     
    public void setMVP(MVP mvp) {
        this.mvp = mvp;
    }
     
    public MVP getMVP() {
        return mvp;
    }
}
public class GenericClassDemo {
  	public static void main(String[] args) {		 
         // 建立一個泛型爲String的類
         MyGenericClass<String> my = new MyGenericClass<String>();    	
         // 呼叫setMVP
         my.setMVP("大鬍子登登");
         // 呼叫getMVP
         String mvp = my.getMVP();
         System.out.println(mvp);
         //建立一個泛型爲Integer的類
         MyGenericClass<Integer> my2 = new MyGenericClass<Integer>(); 
         my2.setMVP(123);   	  
         Integer mvp2 = my2.getMVP();
    }
}
3.2.2 含有泛型的方法
  • 定義格式:
修飾符 <代表泛型的變數> 返回值型別 方法名(參數){  }
public class MyGenericMethod {	  
    public <MVP> void show(MVP mvp) {
    	System.out.println(mvp.getClass());
    }
    
    public <MVP> MVP show2(MVP mvp) {	
    	return mvp;
    }
}

使用格式:呼叫方法時,確定泛型的型別

public class GenericMethodDemo {
    public static void main(String[] args) {
        // 建立物件
        MyGenericMethod mm = new MyGenericMethod();
        // 演示看方法提示
        mm.show("aaa");
        mm.show(123);
        mm.show(12.45);
    }
}
3.2.3 含有泛型的介面
  • 定義格式
修飾符 interface介面名<代表泛型的變數> {  }
public interface MyGenericInterface<E>{
	public abstract void add(E e);
	
	public abstract E getE();  
}
  • 使用格式

定義類時確定泛型的型別

public class MyImp1 implements MyGenericInterface<String> {
	@Override
    public void add(String e) {
        // 省略...
    }

	@Override
	public String getE() {
		return null;
	}
}
//此時,泛型E的值就是String型別。

始終不確定泛型的型別,直到建立物件時,確定泛型的型別

public class MyImp2<E> implements MyGenericInterface<E> {
	@Override
	public void add(E e) {
       	 // 省略...
	}

	@Override
	public E getE() {
		return null;
	}
}
//使用
public class GenericInterface {
    public static void main(String[] args) {
        MyImp2<String>  my = new MyImp2<String>();  
        my.add("aa");
    }
}

3.3 泛型萬用字元

當使用泛型類或者介面時,傳遞的數據中,泛型型別不確定,可以通過萬用字元<?>表示。但是一旦使用泛型的萬用字元後,只能使用Object類中的共性方法,集閤中元素自身方法無法使用。

  • 萬用字元基本使用

泛型的萬用字元:**不知道使用什麼型別來接收的時候,此時可以使用?,?表示未知萬用字元。**只能接受數據,不能往該集閤中儲存數據。

public static void main(String[] args) {
    Collection<Intger> list1 = new ArrayList<Integer>();
    getElement(list1);
    Collection<String> list2 = new ArrayList<String>();
    getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意型別

泛型不存在繼承關係 Collection list = new ArrayList();這種是錯誤的。

  • 萬用字元高階使用----受限泛型

JAVA的泛型中可以指定一個泛型的上限下限

泛型的上限

格式型別名稱 <? extends 類 > 物件名稱

意義只能接收該型別及其子類

泛型的下限

格式型別名稱 <? super 類 > 物件名稱

意義只能接收該型別及其父類別型

public static void main(String[] args) {
    Collection<Integer> list1 = new ArrayList<Integer>();
    Collection<String> list2 = new ArrayList<String>();
    Collection<Number> list3 = new ArrayList<Number>();//Number是Integer的父類別
    Collection<Object> list4 = new ArrayList<Object>();
    
    getElement(list1);
    getElement(list2);//報錯
    getElement(list3);
    getElement(list4);//報錯
  
    getElement2(list1);//報錯
    getElement2(list2);//報錯
    getElement2(list3);
    getElement2(list4);
  
}
// 泛型的上限:此時的泛型?,必須是Number型別或者Number型別的子類
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此時的泛型?,必須是Number型別或者Number型別的父類別
public static void getElement2(Collection<? super Number> coll){}

List、Set、數據結構、Collections

1.數據結構

1.1 棧

stack,又稱堆疊,它是運算受限的線性表,其限制是僅允許在標的一端進行插入和刪除操作,不允許在其他任何位置進行新增、查詢、刪除等操作。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-XScomJqD-1597290664351)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\堆疊.png)]

特點:

  • 先進後出(即,存進去的元素,要在後它後面的元素依次取出後,才能 纔能取出該元素)。

  • 棧的入口、出口的都是棧的頂端位置。

名詞:

  • 壓棧:就是存元素。即,把元素儲存到棧的頂端位置,棧中已有元素依次向棧底方向移動一個位置。
  • 彈棧:就是取元素。即,把棧的頂端位置元素取出,棧中已有元素依次向棧頂方向移動一個位置。

1.2 佇列

佇列queue,簡稱隊,它同堆疊一樣,也是一種運算受限的線性表,其限制是僅允許在表的一端進行插入,而在表的另一端進行刪除。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-psbFEkrR-1597290664354)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\佇列圖.bmp)]

特點:

  • 先進先出(即,存進去的元素,要在後它前面的元素依次取出後,才能 纔能取出該元素)。
  • 佇列的入口、出口各佔一側。例如,下圖中的左側爲入口,右側爲出口。

1.3 陣列

陣列:Array:是有序的元素序列,陣列是在記憶體中開闢一段連續的空間,並在此空間存放元素。

特點:

  • 查詢元素快:通過索引,可以快速存取指定位置的元素

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-uFHxBh9h-1597290664357)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\陣列查詢快.png)]

  • 增刪元素慢
    • 指定索引位置增加元素:需要建立一個新陣列,將指定新元素儲存在指定索引位置,再把原陣列元素根據索引,複製到新陣列對應索引的位置。[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-68OFzgfc-1597290664358)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\陣列新增.png)]
    • **指定索引位置刪除元素:**需要建立一個新陣列,把原陣列元素根據索引,複製到新陣列對應索引的位置,原陣列中指定索引位置元素不復制到新陣列中。[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-IJ6ypbDZ-1597290664359)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\陣列刪除.png)]

1.4 鏈表

鏈表linked list:由一系列結點node(鏈表中每一個元素稱爲結點)組成,結點可以在執行時i動態生成。每個結點包括兩個部分:一個是儲存數據元素的數據域,另一個是儲存下一個結點地址的指針域。我們常說的鏈表結構有單向鏈表與雙向鏈表,那麼這裏給大家介紹的是單向鏈表

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-s5qsaNGl-1597290664360)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\單鏈表結構特點.png)]

特點:

  • 多個結點之間,通過地址進行連線。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-3SuMd20J-1597290664362)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\單鏈表結構.png)]

  • 查詢元素慢:想查詢某個元素,需要通過連線的節點,依次向後查詢指定元素。

  • 增刪元素快:

    • 增加元素:只需要修改連線下個元素的地址即可。[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-4VcSyxtv-1597290664363)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\增加結點.png)]
    • 刪除元素:只需要修改連線下個元素的地址即可。[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-8XFRYlj2-1597290664364)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\刪除結點.bmp)]

1.5 紅黑樹

  • 二元樹binary tree :是每個結點不超過2的有序樹(tree)

我們要說的是二元樹的一種比較有意思的叫做紅黑樹,紅黑樹本身就是一顆二叉查詢樹,將節點插入後,該樹仍然是一顆二叉查詢樹。也就意味着,樹的鍵值仍然是有序的。

紅黑樹的約束:

  1. 節點可以是紅色的或者黑色的

  2. 根節點是黑色的

  3. 葉子節點(特指空節點)是黑色的

  4. 每個紅色節點的子節點都是黑色的

  5. 任何一個節點到其每一個葉子節點的所有路徑上黑色節點數相同

紅黑樹的特點:

​ 速度特別快,趨近平衡樹,查詢葉子元素最少和最多次數不多於二倍

2.List集合

Collection中的常用幾個子類(java.util.List集合、java.util.Set集合)

2.1 List介面介紹

在List集閤中允許出現重複的元素,所有的元素是以一種線性方式進行儲存的,在程式中可以通過索引來存取集閤中的指定元素。另外,List集合還有一個特點就是元素有序,即元素的存入順序和取出順序一致。

  • List介面特點:
    • 它是一個元素存取有序的集合。例如,存元素的順序是11、22、33。那麼集閤中,元素的儲存就是按照11、22、33的順序完成的)。
    • 它是一個帶有索引的集合,通過索引就可以精確的操作集閤中的元素(與陣列的索引是一個道理)。
    • 集閤中可以有重複的元素,通過元素的equals方法,來比較是否爲重複的元素。

2.2 List介面中常用方法

List作爲Collection集合的子介面,不但繼承了Collection介面中的全部方法,而且還增加了一些根據元素索引來操作集合的特有方法,如下:

  • public void add(int index, E element): 將指定的元素,新增到該集閤中的指定位置上。
  • public E get(int index):返回集閤中指定位置的元素。
  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element):用指定元素替換集閤中指定位置的元素,返回值的更新前的元素。
public class ListDemo {
    public static void main(String[] args) {
		// 建立List集合物件
    	List<String> list = new ArrayList<String>();
    	
    	// 往 尾部新增 指定元素
    	list.add("圖圖");
    	list.add("小美");
    	list.add("不高興");
    	System.out.println(list);
    	// add(int index,String s) 往指定位置新增
    	list.add(1,"沒頭腦");
    	// String remove(int index) 刪除指定位置元素  返回被刪除元素
    	System.out.println("刪除索引位置爲2的元素");
    	System.out.println(list.remove(2));
    	// String set(int index,String s) 在指定位置 進行 元素替代(改) 
    	// 修改指定位置元素
    	list.set(0, "三毛");
    	// String get(int index)  獲取指定位置元素 跟size() 方法一起用  來 遍歷的 
    	for(int i = 0;i<list.size();i++){
    		System.out.println(list.get(i));
    	}
    	//還可以使用增強for
    	for (String string : list) {
			System.out.println(string);
		}  	
	}
}

3.List的子類

3.1 ArrayList集合

java.util.ArrayList集合數據儲存的結構是陣列結構。元素增刪慢,查詢快,由於日常開發中使用最多的功能爲查詢數據、遍歷數據,所以ArrayList是最常用的集合

3.2 LinkedList集合

java.util.LinkedList集合數據儲存的結構是鏈表結構。方便元素新增、刪除的集合。LinkedList是一個雙向鏈表。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-r1QPp0H1-1597290664366)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\雙向鏈表.png)]

實際開發中對一個集合元素的新增與刪除經常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。這些方法我們作爲了解即可:

  • public void addFirst(E e):將指定元素插入此列表的開頭。
  • public void addLast(E e):將指定元素新增到此列表的結尾。
  • public E getFirst():返回此列表的第一個元素。
  • public E getLast():返回此列表的最後一個元素。
  • public E removeFirst():移除並返回此列表的第一個元素。
  • public E removeLast():移除並返回此列表的最後一個元素。
  • public E pop():從此列表所表示的堆疊處彈出一個元素。
  • public void push(E e):將元素推入此列表所表示的堆疊。
  • public boolean isEmpty():如果列表不包含元素,則返回true。

LinkedList是List的子類,List中的方法LinkedList都是可以使用,這裏就不做詳細介紹,我們只需要瞭解LinkedList的特有方法即可。在開發時,LinkedList集合也可以作爲堆疊,佇列的結構使用。(瞭解即可)

public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<String> link = new LinkedList<String>();
        //新增元素
        link.addFirst("abc1");
        link.addFirst("abc2");
        link.addFirst("abc3");
        System.out.println(link);
        // 獲取元素
        System.out.println(link.getFirst());
        System.out.println(link.getLast());
        // 刪除元素
        System.out.println(link.removeFirst());
        System.out.println(link.removeLast());

        while (!link.isEmpty()) { //判斷集合是否爲空
            System.out.println(link.pop()); //彈出集閤中的棧頂元素
        }

        System.out.println(link);
    }
}

4.Set介面

List介面不同的是,Set介面中元素無序,並且都會以某種規則保證存入的元素不出現重複。Set集合有多個子類,這裏我們介紹其中的java.util.HashSetjava.util.LinkedHashSet這兩個集合。

Set集合取出元素的方式可以採用:迭代器、增強for。

4.1 HashSet集合

java.util.HashSetSet介面的一個實現類,它所儲存的元素是不可重複的,並且元素都是無序的(即存取順序不一致)。java.util.HashSet底層的實現其實是一個java.util.HashMap支援。

HashSet是根據物件的雜湊值來確定元素在集閤中的儲存位置,因此具有良好的存取和查詢效能。保證元素唯一性的方式依賴於:hashCodeequals方法。

public class HashSetDemo {
    public static void main(String[] args) {
        //建立 Set集合
        HashSet<String>  set = new HashSet<String>();

        //新增元素
        set.add(new String("cba"));
        set.add("abc");
        set.add("bac"); 
        set.add("cba");  
        //遍歷
        for (String name : set) {
            System.out.println(name);
        }
    }
}
輸出結構:
cba
abc
bac

不能儲存重複元素。

4.2 HashSet集合儲存數據結構(雜湊表)

雜湊表:在JDK1.8之前,雜湊表底層採用陣列+鏈表實現,即使用鏈表處理衝突,同一hash值的鏈表都儲存在一個鏈表裡。但是當hash值相等的元素較多時,通過key值依次查詢的效率較低。而JDK1.8中,雜湊表儲存採用陣列+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減少了查詢時間。

簡單的來說,雜湊表是由陣列+鏈表+紅黑樹(JDK1.8增加了紅黑樹部分)實現的,如下圖所示。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-Svkrlufs-1597290664367)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\雜湊表.png)]

儲存流程圖:

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-kPmI99CS-1597290664368)(E:\Java\JavaSE\1.Java基礎\06.集合\14.【List、Set】-筆記\就業班-day03-List、Set、數據結構、Collections\img\雜湊流程圖.png)]

4.3 HashSet儲存自定義型別元素

給HashSet中存放自定義型別元素時,需要重寫物件中的hashCode和equals方法,建立自己的比較方式,才能 纔能保證HashSet集閤中的物件唯一。

建立自定義Student類

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Student student = (Student) o;
        return age == student.age &&
               Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
public class HashSetDemo2 {
    public static void main(String[] args) {
        //建立集合物件   該集閤中儲存 Student型別物件
        HashSet<Student> stuSet = new HashSet<Student>();
        //儲存 
        Student stu = new Student("於謙", 43);
        stuSet.add(stu);
        stuSet.add(new Student("郭德綱", 44));
        stuSet.add(new Student("於謙", 43));
        stuSet.add(new Student("郭麒麟", 23));
        stuSet.add(stu);

        for (Student stu2 : stuSet) {
            System.out.println(stu2);
        }
    }
}

4.4 LinkedHashSet

保證有序:在HashSet下面 下麪有一個子類java.util.LinkedHashSet,它是鏈表和雜湊表組合的一個數據儲存結構。

public class LinkedHashSetDemo {
	public static void main(String[] args) {
		Set<String> set = new LinkedHashSet<String>();
		set.add("bbb");
		set.add("aaa");
		set.add("abc");
        Iterator<String> it = set.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}
}
結果:
  bbb
  aaa
  abc

4.5 可變參數

JDK1.5之後,如果我們定義一個方法需要接受多個參數,並且多個參數型別一致,我們可以對其簡化成如下格式:

修飾符 返回值型別 方法名(參數型別... 形參名){  }
修飾符 返回值型別 方法名(參數型別[] 形參名){  }

後面這種定義,在呼叫時必須傳遞陣列,而前者可以直接傳遞數據即可。

public class ChangeArgs {
    public static void main(String[] args) {
        int[] arr = { 1, 4, 62, 431, 2 };
        int sum = getSum(arr);
        System.out.println(sum);
        //  6  7  2 12 2121
        // 求 這幾個元素和 6  7  2 12 2121
        int sum2 = getSum(6, 7, 2, 12, 2121);
        System.out.println(sum2);
    }

    /*
     * 完成陣列  所有元素的求和 原始寫法
     
      public static int getSum(int[] arr){
        int sum = 0;
        for(int a : arr){
            sum += a;
        }
        
        return sum;
      }
    */
    //可變參數寫法
    public static int getSum(int... arr) {
        int sum = 0;
        for (int a : arr) {
            sum += a;
        }
        return sum;
    }
}

5.Collections

5.1 常用功能

  • java.utils.Collections是集合工具類,用來對集合進行操作。部分方法如下:
  • public static <T> boolean addAll(Collection<T> c, T... elements):往集閤中新增一些元素。
  • public static void shuffle(List<?> list) 打亂順序:打亂集合順序。
  • public static <T> void sort(List<T> list):將集閤中元素按照預設規則排序。
  • public static <T> void sort(List<T> list,Comparator<? super T> ):將集閤中元素按照指定規則排序。
public class CollectionsDemo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        //原來寫法
        //list.add(12);
        //list.add(14);
        //採用工具類 完成 往集閤中新增元素  
        Collections.addAll(list, 5, 222, 12);
        System.out.println(list);
        //排序方法 
        Collections.sort(list);
        System.out.println(list);
    }
}
結果:
[5, 222, 1, 2]
[1, 2, 5, 222]

5.2 Comparator比較器

public static <T> void sort(List<T> list):將集閤中元素按照預設規則排序。

在JAVA中提供了兩種比較實現的方式,一種是比較死板的採用java.lang.Comparable介面去實現,一種是靈活的當我需要做排序的時候在去選擇的java.util.Comparator介面完成。

public class CollectionsDemo3 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("cba");
        list.add("aba");
        list.add("sba");
        list.add("nba");
        //排序方法  按照第一個單詞的降序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o2.charAt(0) - o1.charAt(0);
            }
        });
        System.out.println(list);
    }
}
//[sba, nba, cba, aba]

5.3 Comparable和Comparator介面的區別

  • Comparable:強行對實現它的每個類的物件進行整體排序。這種排序被稱爲類的自然排序,類的compareTo方法被稱爲它的自然比較方法。只能在類中實現compareTo()一次,不能經常修改類的程式碼實現自己想要的排序。實現此介面的物件列表(和陣列)可以通過Collections.sort(和Arrays.sort)進行自動排序,物件可以用作有序對映中的鍵或有序集閤中的元素,無需指定比較器。

  • Comparator:強行對某個物件進行整體排序。可以將Comparator 傳遞給sort方法(如Collections.sort或 Arrays.sort),從而允許在排序順序上實現精確控制。還可以使用Comparator來控制某些數據結構(如有序set或有序對映)的順序,或者爲那些沒有自然順序的物件collection提供排序。

Map集合

1.概述

我們常會看到這樣的一種集合:IP地址與主機名,身份證號與個人,系統使用者名稱與系統使用者物件等,這種一一對應的關係,就叫做對映。Java提供了專門的集合類用來存放這種物件關係的物件,即java.util.Map介面。我們通過檢視Map介面描述,發現Map介面下的集合與Collection介面下的集合,它們儲存數據的形式不同,如下圖。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-a72rqoSk-1597290664370)(E:\Java\JavaSE\1.Java基礎\06.集合\15.【Map】-筆記\就業班-day04-Map\img\Collection與Map.bmp)]

  • Collection中的集合,元素是孤立存在的(理解爲單身),向集閤中儲存元素採用一個個元素的方式儲存。
  • Map中的集合,元素是成對存在的(理解爲夫妻)。每個元素由鍵與值兩部分組成,通過鍵可以找對所對應的值。
  • Collection中的集合稱爲單列集合,Map中的集合稱爲雙列集合。
  • 需要注意的是,Map中的集合不能包含重複的鍵,值可以重複;每個鍵只能對應一個值。

2.Map常用子類

常用的HashMap集合、LinkedHashMap集合。

  • HashMap<K,V>:儲存數據採用的雜湊表結構,元素的存取順序不能保證一致。由於要保證鍵的唯一、不重複,需要重寫鍵的hashCode()方法、equals()方法。
  • LinkedHashMap<K,V>:HashMap下有個子類LinkedHashMap,儲存數據採用的雜湊表結構+鏈表結構。通過鏈表結構可以保證元素的存取順序一致;通過雜湊表結構可以保證的鍵的唯一、不重複,需要重寫鍵的hashCode()方法、equals()方法。

Map介面中的集合都有兩個泛型變數<K,V>,在使用時,要爲兩個泛型變數賦予數據型別。兩個泛型變數<K,V>的數據型別可以相同,也可以不同。

3.Map介面中的常用方法

  • public V put(K key, V value): 把指定的鍵與指定的值新增到Map集閤中。
  • public V remove(Object key): 把指定的鍵 所對應的鍵值對元素 在Map集閤中刪除,返回被刪除元素的值。
  • public V get(Object key) 根據指定的鍵,在Map集閤中獲取對應的值。
  • boolean containsKey(Object key) 判斷集閤中是否包含指定的鍵。
  • public Set<K> keySet(): 獲取Map集閤中所有的鍵,儲存到Set集閤中。
  • public Set<Map.Entry<K,V>> entrySet(): 獲取到Map集閤中所有的鍵值對物件的集合(Set集合)。
public class MapDemo {
    public static void main(String[] args) {
        //建立 map物件
        HashMap<String, String>  map = new HashMap<String, String>();

        //新增元素到集合
        map.put("黃曉明", "楊穎");
        map.put("文章", "馬伊琍");
        map.put("鄧超", "孫儷");
        System.out.println(map);

        //String remove(String key)
        System.out.println(map.remove("鄧超"));
        System.out.println(map);

        // 想要檢視 黃曉明的媳婦 是誰
        System.out.println(map.get("黃曉明"));
        System.out.println(map.get("鄧超"));    
    }
}

4.Map集合遍歷鍵找值方式

鍵找值方式:即通過元素中的鍵,獲取鍵所對應的值

分析步驟:

  1. 獲取Map中所有的鍵,由於鍵是唯一的,所以返回一個Set集合儲存所有的鍵。方法提示:keyset()
  2. 遍歷鍵的Set集合,得到每一個鍵。
  3. 根據鍵,獲取鍵所對應的值。方法提示:get(K key)
public class MapDemo01 {
    public static void main(String[] args) {
        //建立Map集合物件 
        HashMap<String, String> map = new HashMap<String,String>();
        //新增元素到集合 
        map.put("胡歌", "霍建華");
        map.put("郭德綱", "於謙");
        map.put("薛之謙", "大張偉");

        //獲取所有的鍵  獲取鍵集
        Set<String> keys = map.keySet();
        // 遍歷鍵集 得到 每一個鍵
        for (String key : keys) {
          	//key  就是鍵
            //獲取對應值
            String value = map.get(key);
            System.out.println(key+"的CP是:"+value);
        }  
    }
}

5.Entry鍵值對物件

我們已經知道,Map中存放的是兩種物件,一種稱爲key(鍵),一種稱爲value(值),它們在在Map中是一一對應關係,這一對物件又稱做Map中的一個Entry(項)Entry將鍵值對的對應關係封裝成了物件。即鍵值對物件,這樣我們在遍歷Map集合時,就可以從每一個鍵值對(Entry)物件中獲取對應的鍵與對應的值。

既然Entry表示了一對鍵和值,那麼也同樣提供了獲取對應鍵和對應值得方法:

  • public K getKey():獲取Entry物件中的鍵。
  • public V getValue():獲取Entry物件中的值。

在Map集閤中也提供了獲取所有Entry物件的方法:

  • public Set<Map.Entry<K,V>> entrySet(): 獲取到Map集閤中所有的鍵值對物件的集合(Set集合)。

6.Map集合遍歷鍵值對方式

鍵值對方式:即通過集閤中每個鍵值對(Entry)物件,獲取鍵值對(Entry)物件中的鍵與值。

操作步驟與圖解:

  1. 獲取Map集閤中,所有的鍵值對(Entry)物件,以Set集合形式返回。方法提示:entrySet()

  2. 遍歷包含鍵值對(Entry)物件的Set集合,得到每一個鍵值對(Entry)物件。

  3. 通過鍵值對(Entry)物件,獲取Entry物件中的鍵與值。 方法提示:getkey() getValue()

public class MapDemo02 {
    public static void main(String[] args) {
        // 建立Map集合物件 
        HashMap<String, String> map = new HashMap<String,String>();
        // 新增元素到集合 
        map.put("胡歌", "霍建華");
        map.put("郭德綱", "於謙");
        map.put("薛之謙", "大張偉");

        // 獲取 所有的 entry物件  entrySet
        Set<Entry<String,String>> entrySet = map.entrySet();

        // 遍歷得到每一個entry物件
        for (Entry<String, String> entry : entrySet) {
           	// 解析 
            String key = entry.getKey();
            String value = entry.getValue();  
            System.out.println(key+"的CP是:"+value);
        }
    }
}

7.HashMap儲存自定義型別鍵值

練習:每位學生(姓名,年齡)都有自己的家庭住址。那麼,既然有對應關係,則將學生物件和家庭住址儲存到map集閤中。學生作爲鍵, 家庭住址作爲值。

  • 編寫學生類:
public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
  • 編寫測試類:
public class HashMapTest {
    public static void main(String[] args) {
        //1,建立Hashmap集合物件。
        Map<Student,String>map = new HashMap<Student,String>();
        //2,新增元素。
        map.put(newStudent("lisi",28), "上海");
        map.put(newStudent("wangwu",22), "北京");
        map.put(newStudent("zhaoliu",24), "成都");
        map.put(newStudent("zhouqi",25), "廣州");
        map.put(newStudent("wangwu",22), "南京");
        
        //3,取出元素。鍵找值方式
        Set<Student>keySet = map.keySet();
        for(Student key: keySet){
            Stringvalue = map.get(key);
            System.out.println(key.toString()+"....."+value);
        }
    }
}

8.LinkedHashMap

我們知道HashMap保證成對元素唯一,並且查詢速度很快,可是成對元素存放進去是沒有順序的,那麼我們要保證有序,還要速度快怎麼辦呢?在HashMap下面 下麪有一個子類LinkedHashMap,它是鏈表和雜湊表組合的一個數據儲存結構。

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        map.put("鄧超", "孫儷");
        map.put("李晨", "範冰冰");
        map.put("劉德華", "朱麗倩");
        Set<Entry<String, String>> entrySet = map.entrySet();
        for (Entry<String, String> entry : entrySet) {
            System.out.println(entry.getKey() + "  " + entry.getValue());
        }
    }
}
輸出:
鄧超  孫儷
李晨  範冰冰
劉德華  朱麗倩  

五、異常和多執行緒

異常、執行緒

1.異常

  • 異常 :指的是程式在執行過程中,出現的非正常的情況,最終會導致JVM的非正常停止。

在Java等物件導向的程式語言中,異常本身是一個類,產生異常就是建立異常物件並拋出了一個異常物件。Java處理異常的方式是中斷處理。(異常指的並不是語法錯誤,語法錯了,編譯不通過,不會產生位元組碼檔案,根本不能執行)

1.1 異常體系

異常機制 機製其實是幫助我們找到程式中的問題,異常的根類是java.lang.Throwable,其下有兩個子類:java.lang.Errorjava.lang.Exception,平常所說的異常指java.lang.Exception

Throwable體系:

  • Error:嚴重錯誤Error,無法通過處理的錯誤,只能事先避免,好比絕症。
  • Exception:表示異常,異常產生後程式設計師可以通過程式碼的方式糾正,使程式繼續執行,是必須要處理的。好比感冒、闌尾炎。

Throwable中的常用方法:

  • public void printStackTrace():列印異常的詳細資訊。

    包含了異常的型別,異常的原因,還包括異常出現的位置,在開發和偵錯階段,都得使用printStackTrace。

  • public String getMessage():獲取發生異常的原因。

  • public String toString():獲取異常的型別和異常描述資訊(不用)。

1.2 異常分類

異常就是指Exception,因爲這類異常一旦出現,我們就要對程式碼進行更正,修復程式。

異常(Exception)的分類:根據在編譯時期還是執行時期去檢查異常?

  • 編譯時期異常:checked異常。在編譯時期,就會檢查,如果沒有處理異常,則編譯失敗。(如日期格式化異常)
  • 執行時期異常:runtime異常。在執行時期,檢查異常.在編譯時期,執行異常不會編譯器檢測(不報錯)。(如數學異常)

2.異常的處理

Java例外處理的五個關鍵字:try、catch、finally、throw、throws

2.1 拋出異常throw

throw用在方法內,用來拋出一個異常物件,將這個異常物件傳遞到呼叫者處,並結束當前方法的執行。

使用格式:

throw new 異常類名(參數);
throw new NullPointerException("要存取的arr陣列不存在");
throw new ArrayIndexOutOfBoundsException("該索引在陣列中不存在,已超出範圍");
public class ThrowDemo {
    public static void main(String[] args) {
        //建立一個數組 
        int[] arr = {2,4,52,2};
        //根據索引找對應的元素 
        int index = 4;
        int element = getElement(arr, index);

        System.out.println(element);
        System.out.println("over");
    }
    /*
     * 根據 索引找到陣列中對應的元素
     */
    public static int getElement(int[] arr,int index){ 
       	//判斷  索引是否越界
        if(index<0 || index>arr.length-1){
             /*
             判斷條件如果滿足,當執行完throw拋出異常物件後,方法已經無法繼續運算。
             這時就會結束當前方法的執行,並將異常告知給呼叫者。這時就需要通過異常來解決。 
              */
             throw new ArrayIndexOutOfBoundsException("哥們,角標越界了~~~");
        }
        int element = arr[index];
        return element;
    }
}

2.2 Objects非空判斷

  • public static <T> T requireNonNull(T obj):檢視指定參照物件不是null。
public static <T> T requireNonNull(T obj) {
    if (obj == null)
      	throw new NullPointerException();
    return obj;
}

2.3 宣告異常throws

宣告異常:將問題標識出來,報告給呼叫者。如果方法內通過throw拋出了編譯時異常,而沒有捕獲處理,那麼必須通過throws進行宣告,讓呼叫者去處理。

關鍵字throws運用於方法宣告之上,用於表示當前方法不處理異常,而是提醒該方法的呼叫者來處理異常(拋出異常).

宣告異常格式:

修飾符 返回值型別 方法名(參數) throws 異常類名1,異常類名2{   }	

throws用於進行異常類的宣告,若該方法可能有多種異常情況產生,那麼在throws後面可以寫多個異常類,用逗號隔開。

public class ThrowsDemo2 {
    public static void main(String[] args) throws IOException {
        read("a.txt");
    }

    public static void read(String path)throws FileNotFoundException, IOException {
        if (!path.equals("a.txt")) {//如果不是 a.txt這個檔案 
            // 我假設  如果不是 a.txt 認爲 該檔案不存在 是一個錯誤 也就是異常  throw
            throw new FileNotFoundException("檔案不存在");
        }
        if (!path.equals("b.txt")) {
            throw new IOException();
        }
    }
}

2.4 捕獲異常try…catch

如果異常出現的話,會立刻終止程式,所以我們得處理異常:

  1. 該方法不處理,而是宣告拋出,由該方法的呼叫者來處理(throws)。
  2. 在方法中使用try-catch的語句塊來處理異常。

try-catch的方式就是捕獲異常。

  • 捕獲異常:Java中對異常有針對性的語句進行捕獲,可以對出現的異常進行指定方式的處理。

捕獲異常語法如下:

try{
     編寫可能會出現異常的程式碼
}catch(異常型別  e){
     處理異常的程式碼
     //記錄日誌/列印異常資訊/繼續拋出異常
}

**try:**該程式碼塊中編寫可能產生異常的程式碼。

**catch:**用來進行某種異常的捕獲,實現對捕獲到的異常進行處理。

public class TryCatchDemo {
    public static void main(String[] args) {
        try {// 當產生異常時,必須有處理方式。要麼捕獲,要麼宣告。
            read("b.txt");
        } catch (FileNotFoundException e) {// 括號中需要定義什麼呢?
          	//try中拋出的是什麼異常,在括號中就定義什麼異常型別
            System.out.println(e);
        }
        System.out.println("over");
    }
    /*
     *
     * 我們 當前的這個方法中 有異常  有編譯期異常
     */
    public static void read(String path) throws FileNotFoundException {
        if (!path.equals("a.txt")) {//如果不是 a.txt這個檔案 
            // 我假設  如果不是 a.txt 認爲 該檔案不存在 是一個錯誤 也就是異常  throw
            throw new FileNotFoundException("檔案不存在");
        }
    }
}

Throwable類中定義了一些檢視方法:

  • public String getMessage():獲取異常的描述資訊,原因(提示給使用者的時候,就提示錯誤原因。

  • public String toString():獲取異常的型別和異常描述資訊(不用)。

  • public void printStackTrace():列印異常的跟蹤棧資訊並輸出到控制檯。

2.5 finally程式碼塊

finally:有一些特定的程式碼無論異常是否發生,都需要執行。另外,因爲異常會引發程式跳轉,導致有些語句執行不到。而finally就是解決這個問題的,在finally程式碼塊中存放的程式碼都是一定會被執行的。當我們在try語句塊中打開了一些物理資源(磁碟檔案/網路連線/數據庫連線等),我們都得在使用完之後,最終關閉開啓的資源。

try…catch…finally:自身需要處理異常,最終還得關閉資源。finally不能單獨使用。

public class TryCatchDemo4 {
    public static void main(String[] args) {
        try {
            read("a.txt");
        } catch (FileNotFoundException e) {
            //抓取到的是編譯期異常  拋出去的是執行期 
            throw new RuntimeException(e);
        } finally {
            System.out.println("不管程式怎樣,這裏都將會被執行。");
        }
        System.out.println("over");
    }
    /*
     *
     * 我們 當前的這個方法中 有異常  有編譯期異常
     */
    public static void read(String path) throws FileNotFoundException {
        if (!path.equals("a.txt")) {//如果不是 a.txt這個檔案 
            // 我假設  如果不是 a.txt 認爲 該檔案不存在 是一個錯誤 也就是異常  throw
            throw new FileNotFoundException("檔案不存在");
        }
    }
}

2.6 異常注意事項

多個異常使用捕獲又該如何處理呢?

  1. 多個異常分別處理。
  2. 多個異常一次捕獲,多次處理。
  3. 多個異常一次捕獲一次處理。

一般我們是使用一次捕獲多次處理方式

try{
     編寫可能會出現異常的程式碼
}catch(異常型別A  e){try中出現A型別異常,就用該catch來捕獲.
     處理異常的程式碼
     //記錄日誌/列印異常資訊/繼續拋出異常
}catch(異常型別B  e){try中出現B型別異常,就用該catch來捕獲.
     處理異常的程式碼
     //記錄日誌/列印異常資訊/繼續拋出異常
}

這種例外處理方式,要求多個catch中的異常不能相同,並且若catch中的多個異常之間有子父類別異常的關係,那麼子類異常要求在上面的catch處理,父類別異常在下面 下麪的catch處理。

  • 執行時異常被拋出可以不處理。即不捕獲也不宣告拋出。

  • 如果finally有return語句,永遠返回finally中的結果,避免該情況.

  • 如果父類別拋出了多個異常,子類重寫父類別方法時,拋出和父類別相同的異常或者是父類別異常的子類或者不拋出異常。

  • 父類別方法沒有拋出異常,子類重寫父類別該方法時也不可拋出異常。此時子類產生該異常,只能捕獲處理,不能宣告拋出

3.自定義異常

爲什麼需要自定義異常類:

Java中不同的異常類,分別表示着某一種具體的異常情況,那麼在開發中總是有些異常情況是SUN沒有定義好的,此時我們根據自己業務的異常情況來定義異常類。例如年齡負數問題,考試成績負數問題等等。

在上述程式碼中,發現這些異常都是JDK內部定義好的,但是實際開發中也會出現很多異常,這些異常很可能在JDK中沒有定義過,例如年齡負數問題,考試成績負數問題.那麼能不能自己定義異常呢?

什麼是自定義異常類:

在開發中根據自己業務的異常情況來定義異常類.

自定義一個業務邏輯異常: RegisterException。一個註冊異常類。

異常類如何定義:

  1. 自定義一個編譯期異常: 自定義類 並繼承於java.lang.Exception
  2. 自定義一個執行時期的異常類:自定義類 並繼承於java.lang.RuntimeException

我們模擬註冊操作,如果使用者名稱已存在,則拋出異常並提示:親,該使用者名稱已經被註冊。

首先定義一個登陸異常類RegisterException:

// 業務邏輯異常
public class RegisterException extends Exception {
    //空參構造
    public RegisterException() {
    }
    //@param message 表示異常提示
    public RegisterException(String message) {
        super(message);
    }
}

模擬登陸操作,使用陣列模擬數據庫中儲存的數據,並提供當前註冊賬號是否存在方法用於判斷。

public class Demo {
    // 模擬數據庫中已存在賬號
    private static String[] names = {"bill","hill","jill"};
   
    public static void main(String[] args) {     
        //呼叫方法
        try{
              // 可能出現異常的程式碼
            checkUsername("nill");
            System.out.println("註冊成功");//如果沒有異常就是註冊成功
        }catch(RegisterException e){
            //處理異常
            e.printStackTrace();
        }
    }

    //判斷當前註冊賬號是否存在
    //因爲是編譯期異常,又想呼叫者去處理 所以宣告該異常
    public static boolean checkUsername(String uname) throws LoginException{
        for (String name : names) {
            if(name.equals(uname)){//如果名字在這裏面 就拋出登陸異常
                throw new RegisterException("親"+name+"已經被註冊了!");
            }
        }
        return true;
    }
}

4.多執行緒

4.1 併發與並行

  • 併發:指兩個或多個事件在同一個時間段內發生。交替執行。
  • 並行:指兩個或多個事件在同一時刻發生(同時發生)。

4.2 執行緒與進程

  • 進程:是指一個記憶體中執行的應用程式,每個進程都有一個獨立的記憶體空間,一個應用程式可以同時執行多個進程;進程也是程式的一次執行過程,是系統執行程式的基本單位;系統執行一個程式即是一個進程從建立、執行到消亡的過程。

  • 執行緒:執行緒是進程中的一個執行單元,負責當前進程中程式的執行,一個進程中至少有一個執行緒。一個進程中是可以有多個執行緒的,這個應用程式也可以稱之爲多執行緒程式。

    簡而言之:一個程式執行後至少有一個進程,一個進程中可以包含多個執行緒

執行緒排程:

  • 分時排程

    所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間。

  • 搶佔式排程

    優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個(執行緒隨機性),Java使用的爲搶佔式排程。

    • 設定執行緒的優先順序

    • 搶佔式排程詳解

      大部分操作系統都支援多進程併發執行,現在的操作系統幾乎都支援同時執行多個程式。比如:現在我們上課一邊使用編輯器,一邊使用錄屏軟體,同時還開着畫圖板,dos視窗等軟體。此時,這些程式是在同時執行,」感覺這些軟體好像在同一時刻執行着「。

實際上,CPU(中央處理器)使用搶佔式排程模式在多個執行緒間進行着高速的切換。對於CPU的一個核而言,某個時刻,只能執行一個執行緒,而 CPU的在多個執行緒間切換速度相對我們的感覺要快,看上去就是在同一時刻執行。
其實,多執行緒程式並不能提高程式的執行速度,但能夠提高程式執行效率,讓CPU的使用率更高。

4.3 建立執行緒類

Java使用java.lang.Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的範例。每個執行緒的作用是完成一定的任務,實際上就是執行一段程式流即一段順序執行的程式碼。Java使用執行緒執行體來代表這段程式流。Java中通過繼承Thread類來建立啓動多執行緒的步驟如下:

  1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了執行緒需要完成的任務,因此把run()方法稱爲執行緒執行體。
  2. 建立Thread子類的範例,即建立了執行緒物件
  3. 呼叫執行緒物件的start()方法來啓動該執行緒

程式碼如下:

測試類:

public class Demo01 {
	public static void main(String[] args) {
		//建立自定義執行緒物件
		MyThread mt = new MyThread("新的執行緒!");
		//開啓新執行緒
		mt.start();
		//在主方法中執行for回圈
		for (int i = 0; i < 10; i++) {
			System.out.println("main執行緒!"+i);
		}
	}
}

自定義執行緒類:

public class MyThread extends Thread {
	//定義指定執行緒名稱的構造方法
	public MyThread(String name) {
		//呼叫父類別的String參數的構造方法,指定執行緒的名稱
		super(name);
	}
	/**
	 * 重寫run方法,完成該執行緒執行的邏輯
	 */
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(getName()+":正在執行!"+i);
		}
	}
}

執行緒、同步

1.執行緒

1.1 Thread類

java.lang.Thread 類

構造方法:

  • public Thread() :分配一個新的執行緒物件。
  • public Thread(String name) :分配一個指定名字的新的執行緒物件。
  • public Thread(Runnable target) :分配一個帶有指定目標新的執行緒物件。
  • public Thread(Runnable target,String name) :分配一個帶有指定目標新的執行緒物件並指定名字。

常用方法:

  • public String getName() :獲取當前執行緒名稱。
  • public void start() :導致此執行緒開始執行; Java虛擬機器呼叫此執行緒的run方法。
  • public void run() :此執行緒要執行的任務在此處定義程式碼。
  • public static void sleep(long millis) :使當前正在執行的執行緒以指定的毫秒數暫停(暫時停止執行)。
  • public static Thread currentThread() :返回對當前正在執行的執行緒物件的參照

1.2 建立執行緒的第二種方式

採用 java.lang.Runnable 也是非常常見的一種,我們只需要重寫run方法即可。
步驟如下

  1. 定義Runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體。
  2. 建立Runnable實現類的範例,並以此範例作爲Thread的target來建立Thread物件,該Thread物件纔是真正
    的執行緒物件。
  3. 呼叫執行緒物件的start()方法來啓動執行緒。

1.3 Thread和Runnable的區別

實現Runnable介面比繼承Thread類所具有的優勢:

  1. 適合多個相同的程式程式碼的執行緒去共用同一個資源。
  2. 可以避免java中的單繼承的侷限性。
  3. 增加程式的健壯性,實現解耦操作,程式碼可以被多個執行緒共用,程式碼和執行緒獨立。
  4. 執行緒池只能放入實現Runable或Callable類執行緒,不能直接放入繼承Thread的類。

2.執行緒安全

2.1 執行緒安全

執行緒安全問題都是由全域性變數及靜態變數引起的。若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則的話就可能影響執行緒安全。

2.2 執行緒同步

當我們使用多個執行緒存取同一資源的時候,且多個執行緒中對資源有寫的操作,就容易出現執行緒安全問題。要解決上述多執行緒併發存取一個資源的安全性問題: Java中提供了同步機制 機製(synchronized)來解決。

爲了保證每個執行緒都能正常執行原子操作,Java引入了執行緒同步機制 機製。有三種方式完成同步操作:

  • 同步程式碼塊。
  • 同步方法。
  • 鎖機制 機製

2.3 同步程式碼塊

同步程式碼塊synchronized 關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥存取。
格式:

synchronized(同步鎖){
	需要同步操作的程式碼
}

同步鎖:
物件的同步鎖只是一個概念,可以想象爲在物件上標記了一個鎖.

  • 鎖物件 可以是任意型別。
  • 多個執行緒物件 要使用同一把鎖。
public class Ticket implements Runnable{

    private int ticket = 100;
    Object lock = new Object();

    //執行賣票操作
    @Override
    public void run() {
        //每個視窗賣票的操作 視窗 永遠開啓
        while (true) {
            synchronized (lock) {
                if (ticket > 0) {
                    //有票 可以賣 出票操作 使用sleep模擬一下出票時間
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //獲取當前執行緒物件的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在賣:" + ticket--);
                }
            }
        }
    }
}

測試類:

public class test2 {
    public static void main(String[] args) {
        //建立執行緒任務物件
        Ticket ticket = new Ticket();
        //建立三個視窗物件
        Thread t1 = new Thread(ticket, "視窗1");
        Thread t2 = new Thread(ticket, "視窗2");
        Thread t3 = new Thread(ticket, "視窗3");
        //同時賣票
        t1.start();
        t2.start();
        t3.start();
    }
}

2.4 同步方法

同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A執行緒執行該方法的時候,其他執行緒只能在方法外等着

public synchronized void method(){
	可能會產生執行緒安全問題的程式碼
}

同步鎖是誰?

  • 對於非static方法,同步鎖就是this。
  • 對於static方法,我們使用當前方法所在類的位元組碼物件(類名.class)
public class Ticket1 implements Runnable{
    private int ticket = 100;

    @Override
    public void run() {
        while (true){
            sellTicket();
        }
    }
    public synchronized void sellTicket(){
        if (ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();
            System.out.println(name + "正在賣:"+ ticket--);
        }
    }
}

2.5 Lock鎖

java.util.concurrent.locks.Lock機制 機製提供了比synchronized程式碼塊和synchronized方法更廣泛的鎖定操作,同步程式碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現物件導向。Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了,如下:

  • public void lock() :加同步鎖。
  • public void unlock() :釋放同步鎖。
public class Ticket2 implements Runnable{
    private int ticket = 100;
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            lock.lock();
            if(ticket>0){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                String name = Thread.currentThread().getName();
                System.out.println(name+"正在賣:"+ticket--);
            }
            lock.unlock();
        }
    }
}

3.執行緒狀態

當執行緒被建立並啓動以後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態。線上程的生命週期中,
有幾種狀態呢?在API中 java.lang.Thread.State 這個列舉中給出了六種執行緒狀態:

執行緒狀態 導致狀態發生條件
NEW(新建) 執行緒剛被建立,但是並未啓動。還沒呼叫start方法。
Runnable(可 執行) 執行緒可以在java虛擬機器中執行的狀態,可能正在執行自己程式碼,也可能沒有,這取決於操作系統處理器。
Blocked(鎖阻 塞) 當一個執行緒試圖獲取一個物件鎖,而該物件鎖被其他的執行緒持有,則該執行緒進入Blocked狀 態;當該執行緒持有鎖時,該執行緒將變成Runnable狀態。
Waiting(無限 等待) 一個執行緒在等待另一個執行緒執行一個(喚醒)動作時,該執行緒進入Waiting狀態。進入這個 狀態後是不能自動喚醒的,必須等待另一個執行緒呼叫notify或者notifyAll方法才能 纔能夠喚醒。
Timed Waiting(計時 等待) 同waiting狀態,有幾個方法有超時參數,呼叫他們將進入Timed Waiting狀態。這一狀態將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的常用方法有Thread.sleep 、 Object.wait。
Teminated(被 終止) 因爲run方法正常退出而死亡,或者因爲沒有捕獲的異常終止了run方法而死亡。

3.1 Time Waiting(計時等待)

API中的描述爲:一個正在限時等待另一個執行緒執行一個(喚醒)動作的執行緒處於這一狀態。

3.2 BLOCKED(鎖阻塞)

API中的介紹爲:一個正在阻塞等待一個監視器鎖(鎖物件)的執行緒處於這一狀態。

3.3 Waiting(無限等待)

API中介紹爲:一個正在無限期等待另一個執行緒執行一個特別的(喚醒)動作的執行緒處於這一狀態。

執行緒池、Lambda表達式

1.等待喚醒機制 機製

1.1 執行緒間通訊

**概念:**多個執行緒在處理同一個資源,但是處理的動作(執行緒的任務)卻不相同。

爲什麼要處理執行緒間通訊:

多個執行緒併發執行時, 在預設情況下CPU是隨機切換執行緒的,當我們需要多個執行緒來共同完成一件任務,並且我們希望他們有規律的執行, 那麼多執行緒之間需要一些協調通訊,以此來幫我們達到多執行緒共同操作一份數據。

如何保證執行緒間通訊有效利用資源:

多個執行緒在處理同一個資源,並且任務不同時,需要執行緒通訊來幫助解決執行緒之間對同一個變數的使用或操作。 就是多個執行緒在操作同一份數據時, 避免對同一共用變數的爭奪。也就是我們需要通過一定的手段使各個執行緒能有效的利用資源。而這種手段即—— 等待喚醒機制 機製。

1.2 等待喚醒機制 機製

這是多個執行緒間的一種共同作業機制 機製。就是在一個執行緒進行了規定操作後,就進入等待狀態(wait()), 等待其他執行緒執行完他們的指定程式碼過後 再將其喚醒(notify());在有多個執行緒進行等待時, 如果需要,可以使用 notifyAll()來喚醒所有的等待執行緒。wait/notify 就是執行緒間的一種共同作業機制 機製。

等待喚醒中的方法

等待喚醒機制 機製就是用於解決執行緒間通訊的問題的,使用到的3個方法的含義如下:

  1. wait:執行緒不再活動,不再參與排程,進入 wait set 中,因此不會浪費 CPU 資源,也不會去競爭鎖了,這時的執行緒狀態即是 WAITING。它還要等着別的執行緒執行一個特別的動作,也即是「通知(notify)」在這個物件上等待的執行緒從wait set 中釋放出來,重新進入到排程佇列(ready queue)中
  2. notify:則選取所通知物件的 wait set 中的一個執行緒釋放;例如,餐館有空位置後,等候就餐最久的顧客最先入座。
  3. notifyAll:則釋放所通知物件的 wait set 上的全部執行緒。

注意:

哪怕只通知了一個等待的執行緒,被通知執行緒也不能立即恢復執行,因爲它當初中斷的地方是在同步塊內,而此刻它已經不持有鎖,所以她需要再次嘗試去獲取鎖(很可能面臨其它執行緒的競爭),成功後才能 纔能在當初呼叫 wait 方法之後的地方恢復執行。

總結如下:

  • 如果能獲取鎖,執行緒就從 WAITING 狀態變成 RUNNABLE 狀態;
  • 否則,從 wait set 出來,又進入 entry set,執行緒就從 WAITING 狀態又變成 BLOCKED 狀態

呼叫wait和notify方法需要注意的細節

  1. wait方法與notify方法必須要由同一個鎖物件呼叫。因爲:對應的鎖物件可以通過notify喚醒使用同一個鎖物件呼叫的wait方法後的執行緒。
  2. wait方法與notify方法是屬於Object類的方法的。因爲:鎖物件可以是任意物件,而任意物件的所屬類都是繼承了Object類的。
  3. wait方法與notify方法必須要在同步程式碼塊或者是同步函數中使用。因爲:必須要通過鎖物件呼叫這2個方法。

1.3 生產者與消費者問題

等待喚醒機制 機製其實就是經典的「生產者與消費者」的問題。

2.執行緒池

如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁建立執行緒就會大大降低系統的效率,因爲頻繁建立執行緒和銷燬執行緒需要時間。那麼有沒有一種辦法使得執行緒可以複用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務?在Java中可以通過執行緒池來達到這樣的效果。

2.1 執行緒池概念

**執行緒池:**其實就是一個容納多個執行緒的容器,其中的執行緒可以反覆 反復使用,省去了頻繁建立執行緒物件的操作,無需反覆 反復建立執行緒而消耗過多資源。

合理利用執行緒池能夠帶來三個好處:

  1. 降低資源消耗。減少了建立和銷燬執行緒的次數,每個工作執行緒都可以被重複利用,可執行多個任務。
  2. 提高響應速度。當任務到達時,任務可以不需要的等到執行緒建立就能立即執行。
  3. 提高執行緒的可管理性。可以根據系統的承受能力,調整執行緒池中工作線執行緒的數目,防止因爲消耗過多的記憶體,而把伺服器累趴下(每個執行緒需要大約1MB記憶體,執行緒開的越多,消耗的記憶體也就越大,最後宕機)。

2.2 執行緒池的使用

Java裏面執行緒池的頂級介面是java.util.concurrent.Executor,但是嚴格意義上講Executor並不是一個執行緒池,而只是一個執行執行緒的工具。真正的執行緒池介面是java.util.concurrent.ExecutorService

要設定一個執行緒池是比較複雜的,尤其是對於執行緒池的原理不是很清楚的情況下,很有可能設定的執行緒池不是較優的,因此在java.util.concurrent.Executors執行緒工廠類裏面提供了一些靜態工廠,生成一些常用的執行緒池。官方建議使用Executors工程類來建立執行緒池物件。

Executors類中有個建立執行緒池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回執行緒池物件。(建立的是有界執行緒池,也就是池中的執行緒個數可以指定最大數量)

獲取到了一個執行緒池ExecutorService 物件,那麼怎麼使用呢,在這裏定義了一個使用執行緒池物件的方法如下:

  • public Future<?> submit(Runnable task):獲取執行緒池中的某一個執行緒物件,並執行

    Future介面:用來記錄執行緒任務執行完畢後產生的結果。執行緒池建立與使用。

使用執行緒池中執行緒物件的步驟:

  1. 建立執行緒池物件。
  2. 建立Runnable介面子類物件。(task)
  3. 提交Runnable介面子類物件。(take task)
  4. 關閉執行緒池(一般不做)。

3.Lambda表達式

3.1 函數語言程式設計思想概述

物件導向的思想:

​ 做一件事情,找一個能解決這個事情的物件,呼叫物件的方法,完成事情.

函數語言程式設計思想:

​ 只要能獲取到結果,誰去做的,怎麼做的都不重要,重視的是結果,不重視過程

3.2 冗餘的Runnable程式碼

public class Demo01Runnable {
	public static void main(String[] args) {
    	// 匿名內部類
		Runnable task = new Runnable() {
			@Override
			public void run() { // 覆蓋重寫抽象方法
				System.out.println("多執行緒任務執行!");
			}
		};
		new Thread(task).start(); // 啓動執行緒
	}
}

對於Runnable的匿名內部類用法,可以分析出幾點內容:

  • Thread類需要Runnable介面作爲參數,其中的抽象run方法是用來指定執行緒任務內容的核心;
  • 爲了指定run的方法體,不得不需要Runnable介面的實現類;
  • 爲了省去定義一個RunnableImpl實現類的麻煩,不得不使用匿名內部類;
  • 必須覆蓋重寫抽象run方法,所以方法名稱、方法參數、方法返回值不得不再寫一遍,且不能寫錯;
  • 而實際上,似乎只有方法體纔是關鍵所在

3.3 體驗Lambda的更優寫法

藉助Java 8的全新語法,上述Runnable介面的匿名內部類寫法可以通過更簡單的Lambda表達式達到等效:

public class Demo02LambdaRunnable {
	public static void main(String[] args) {
		new Thread(() -> System.out.println("多執行緒任務執行!")).start(); // 啓動執行緒
	}
}

3.4 回顧匿名內部類

使用實現類

要啓動一個執行緒,需要建立一個Thread類的物件並呼叫start方法。而爲了指定執行緒執行的內容,需要呼叫Thread類的構造方法:

  • public Thread(Runnable target)

爲了獲取Runnable介面的實現物件,可以爲該介面定義一個實現類RunnableImpl

public class RunnableImpl implements Runnable {
	@Override
	public void run() {
		System.out.println("多執行緒任務執行!");
	}
}

然後建立該實現類的物件作爲Thread類的構造參數:

public class Demo03ThreadInitParam {
	public static void main(String[] args) {
		Runnable task = new RunnableImpl();
		new Thread(task).start();
	}
}

使用匿名內部類

這個RunnableImpl類只是爲了實現Runnable介面而存在的,而且僅被使用了唯一一次,所以使用匿名內部類的語法即可省去該類的單獨定義,即匿名內部類:

public class Demo04ThreadNameless {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("多執行緒任務執行!");
			}
		}).start();
	}
}

語意分析

仔細分析該程式碼中的語意,Runnable介面只有一個run方法的定義:

  • public abstract void run();

即制定了一種做事情的方案(其實就是一個函數):

  • 無參數:不需要任何條件即可執行該方案。
  • 無返回值:該方案不產生任何結果。
  • 程式碼塊(方法體):該方案的具體執行步驟。

同樣的語意體現在Lambda語法中,要更加簡單:

() -> System.out.println("多執行緒任務執行!")
  • 前面的一對小括號即run方法的參數(無),代表不需要任何條件;
  • 中間的一個箭頭代表將前面的參數傳遞給後面的程式碼;
  • 後面的輸出語句即業務邏輯程式碼。

3.5 Lambda標準格式

Lambda省去物件導向的條條框框,格式由3個部分組成:

  • 一些參數
  • 一個箭頭
  • 一段程式碼

Lambda表達式的標準格式爲:

(參數型別 參數名稱) -> { 程式碼語句 }

格式說明:

  • 小括號內的語法與傳統方法參數列表一致:無參數則留空;多個參數則用逗號分隔。
  • ->是新引入的語法格式,代表指向動作。
  • 大括號內的語法與傳統方法體要求基本一致。

3.6 Lambda省略格式

省略規則

在Lambda標準格式的基礎上,使用省略寫法的規則爲:

  1. 小括號內參數的型別可以省略;
  2. 如果小括號內有且僅有一個參,則小括號可以省略;
  3. 如果大括號內有且僅有一個語句,則無論是否有返回值,都可以省略大括號、return關鍵字及語句分號。

備註:掌握這些省略規則後,請對應地回顧本章開頭的多執行緒案例。

3.7 Lambda的使用前提

Lambda的語法非常簡潔,完全沒有物件導向複雜的束縛。但是使用時有幾個問題需要特別注意:

  1. 使用Lambda必須具有介面,且要求介面中有且僅有一個抽象方法
    無論是JDK內建的RunnableComparator介面還是自定義的介面,只有當介面中的抽象方法存在且唯一時,纔可以使用Lambda。
  2. 使用Lambda必須具有上下文推斷
    也就是方法的參數或區域性變數型別必須爲Lambda對應的介面型別,才能 纔能使用Lambda作爲該介面的範例。

備註:有且僅有一個抽象方法的介面,稱爲「函數式介面」。

六、File類與IO流

File類、遞回

1.File類

java.io.File 類是檔案和目錄路徑名的抽象表示,主要用於檔案和目錄的建立、查詢和刪除等操作。

1.1 構造方法

  • public File(String pathname) :通過將給定的路徑名字串轉換爲抽象路徑名來建立新的 File範例。
  • public File(String parent, String child) :從父路徑名字串和子路徑名字串建立新的 File範例。
  • public File(File parent, String child) :從父抽象路徑名和子路徑名字串建立新的 File範例
// 檔案路徑名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname); 

// 通過父路徑和子路徑字串
 String parent = "d:\\aaa";
 String child = "bbb.txt";
 File file3 = new File(parent, child);

// 通過父級File物件和子路徑字串
File parentDir = new File("d:\\aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child);

1.2 常用方法

1.2.1 獲取功能的方法
  • public String getAbsolutePath() :返回此File的絕對路徑名字串。

  • public String getPath() :將此File轉換爲路徑名字串。

  • public String getName() :返回由此File表示的檔案或目錄的名稱。

  • public long length() :返回由此File表示的檔案的長度。

public class FileGet {
    public static void main(String[] args) {
        File f = new File("d:/aaa/bbb.java");     
        System.out.println("檔案絕對路徑:"+f.getAbsolutePath());
        System.out.println("檔案構造路徑:"+f.getPath());
        System.out.println("檔名稱:"+f.getName());
        System.out.println("檔案長度:"+f.length()+"位元組");
    }
}
輸出結果:
檔案絕對路徑:d:\aaa\bbb.java
檔案構造路徑:d:\aaa\bbb.java
檔名稱:bbb.java
檔案長度:636位元組
1.2.2 絕對路徑和相對路徑
  • 絕對路徑:從碟符開始的路徑,這是一個完整的路徑。
  • 相對路徑:相對於專案目錄的路徑,這是一個便捷的路徑,開發中經常使用。
public class FilePath {
    public static void main(String[] args) {
      	// D槽下的bbb.java檔案
        File f = new File("D:\\bbb.java");
        System.out.println(f.getAbsolutePath());
      	
		// 專案下的bbb.java檔案
        File f2 = new File("bbb.java");
        System.out.println(f2.getAbsolutePath());
    }
}
輸出結果:
D:\bbb.java
D:\idea_project_test4\bbb.java
1.2.3 判斷功能的方法
  • public boolean exists() :此File表示的檔案或目錄是否實際存在。
  • public boolean isDirectory() :此File表示的是否爲目錄。
  • public boolean isFile() :此File表示的是否爲檔案。
1.2.4 建立刪除功能的方法
  • public boolean createNewFile() :當且僅當具有該名稱的檔案尚不存在時,建立一個新的空檔案。
  • public boolean delete() :刪除由此File表示的檔案或目錄。
  • public boolean mkdir() :建立由此File表示的目錄。
  • public boolean mkdirs() :建立由此File表示的目錄,包括任何必需但不存在的父目錄。

1.3 目錄的遍歷

  • public String[] list() :返回一個String陣列,表示該File目錄中的所有子檔案或目錄。

  • public File[] listFiles() :返回一個File陣列,表示該File目錄中的所有的子檔案或目錄。

public class FileFor {
    public static void main(String[] args) {
        File dir = new File("d:\\java_code");
      
      	//獲取當前目錄下的檔案以及資料夾的名稱。
		String[] names = dir.list();
		for(String name : names){
			System.out.println(name);
		}
        //獲取當前目錄下的檔案以及資料夾物件,只要拿到了檔案物件,那麼就可以獲取更多資訊
        File[] files = dir.listFiles();
        for (File file : files) {
            System.out.println(file);
        }
    }
}

2.遞回

2.1 概述

  • 遞回:指在當前方法內呼叫自己的這種現象。

  • 遞回的分類:

    • 遞回分爲兩種,直接遞回和間接遞回。
    • 直接遞回稱爲方法自身呼叫自己。
    • 間接遞回可以A方法呼叫B方法,B方法呼叫C方法,C方法呼叫A方法。
  • 注意事項

    • 遞回一定要有條件限定,保證遞回能夠停止下來,否則會發生棧記憶體溢位。
    • 在遞回中雖然有限定條件,但是遞回次數不能太多。否則也會發生棧記憶體溢位。
    • 構造方法,禁止遞回
public class Demo01DiGui {
	public static void main(String[] args) {
		// a();
		b(1);
	}

	/*
	 * 2.在遞回中雖然有限定條件,但是遞回次數不能太多。否則也會發生棧記憶體溢位。
	 * 4993
	 * 	Exception in thread "main" java.lang.StackOverflowError
	 */
	private static void b(int i) {
		System.out.println(i);
		//新增一個遞回結束的條件,i==5000的時候結束
		if(i==5000){
			return;//結束方法
		}
		b(++i);
	}

	/*
	 * 1.遞回一定要有條件限定,保證遞回能夠停止下來,否則會發生棧記憶體溢位。 Exception in thread "main"
	 * java.lang.StackOverflowError
	 */
	private static void a() {
		System.out.println("a方法");
		a();
	}
}

2.2 遞回累加求和

  • 計算1~n的和

分析:num的累和 = num + (num-1)的累和,所以可以把累和的操作定義成一個方法,遞回呼叫。

public class DiGuiDemo {
	public static void main(String[] args) {
		//計算1~num的和,使用遞回完成
		int num = 5;
      	// 呼叫求和的方法
		int sum = getSum(num);
      	// 輸出結果
		System.out.println(sum);
		
	}
  	/*
  	  通過遞回演算法實現.
  	  參數列表:int 
  	  返回值型別: int 
  	*/
	public static int getSum(int num) {
      	/* 
      	   num爲1時,方法返回1,
      	   相當於是方法的出口,num總有是1的情況
      	*/
		if(num == 1){
			return 1;
		}
      	/*
          num不爲1時,方法返回 num +(num-1)的累和
          遞回呼叫getSum方法
        */
		return num + getSum(num-1);
	}
}

2.3 遞回求階乘

public class DiGuiDemo {
  	//計算n的階乘,使用遞回完成
    public static void main(String[] args) {
        int n = 3;
      	// 呼叫求階乘的方法
        int value = getValue(n);
      	// 輸出結果
        System.out.println("階乘爲:"+ value);
    }
	/*
  	  通過遞回演算法實現.
  	  參數列表:int 
  	  返回值型別: int 
  	*/
    public static int getValue(int n) {
      	// 1的階乘爲1
        if (n == 1) {
            return 1;
        }
      	/*
      	  n不爲1時,方法返回 n! = n*(n-1)!
          遞回呼叫getValue方法
      	*/
        return n * getValue(n - 1);
    }
}

位元組流、位元組符

1.IO概述

數據的傳輸,可以看做是一種數據的流動,按照流動的方向,以記憶體爲基準,分爲輸入input輸出output ,即流向記憶體是輸入流,流出記憶體的輸出流。Java中I/O操作主要是指使用java.io包下的內容,進行輸入、輸出操作。

輸入也叫做讀取數據,輸出也叫做作寫出數據。

1.1 IO的分類

根據數據的流向分爲:輸入流輸出流

  • 輸入流 :把數據從其他裝置上讀取到記憶體中的流。
  • 輸出流 :把數據從記憶體 中寫出到其他裝置上的流。

格局數據的型別分爲:位元組流字元流

  • 位元組流 :以位元組爲單位,讀寫數據的流。
  • 字元流 :以字元爲單位,讀寫數據的流。

1.2 頂級父類別們

輸入流 輸出流
位元組流 位元組輸入流
InputStream
位元組輸出流
OutputStream
字元流 字元輸入流
Reader
字元輸出流
Writer

2.位元組流

位元組流可以傳輸任意檔案數據。在操作流的時候,我們要時刻明確,無論使用什麼樣的流物件,底層傳輸的始終爲二進制數據。

2.1 位元組輸出流OutputStream類

java.io.OutputStream抽象類是表示位元組輸出流的所有類的超類,將指定的位元組資訊寫出到目的地。它定義了位元組輸出流的基本共性功能方法。

  • public void close() :關閉此輸出流並釋放與此流相關聯的任何系統資源。
  • public void flush() :重新整理此輸出流並強制任何緩衝的輸出位元組被寫出。
  • public void write(byte[] b):將 b.length位元組從指定的位元組陣列寫入此輸出流。
  • public void write(byte[] b, int off, int len) :從指定的位元組陣列寫入 len位元組,從偏移量 off開始輸出到此輸出流。
  • public abstract void write(int b) :將指定的位元組輸出流。

close方法,當完成流的操作時,必須呼叫此方法,釋放系統資源。

2.2 FileOutputStream類

OutputStream有很多子類,我們從最簡單的一個子類開始。

java.io.FileOutputStream類是檔案輸出流,用於將數據寫出到檔案。

構造方法
  • public FileOutputStream(File file):建立檔案輸出流以寫入由指定的 File物件表示的檔案。
  • public FileOutputStream(String name): 建立檔案輸出流以指定的名稱寫入檔案。

當你建立一個流物件時,必須傳入一個檔案路徑。該路徑下,如果沒有這個檔案,會建立該檔案。如果有這個檔案,會清空這個檔案的數據。

public class FileOutputStreamConstructor throws IOException {
    public static void main(String[] args) {
   	 	// 使用File物件建立流物件
        File file = new File("a.txt");
        FileOutputStream fos = new FileOutputStream(file);
      
        // 使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("b.txt");
    }
}
寫出位元組數據
  • 寫出位元組write(int b) 方法,每次可以寫出一個位元組數據,程式碼使用演示:
public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("fos.txt");     
      	// 寫出數據
      	fos.write(97); // 寫出第1個位元組
      	// 關閉資源
        fos.close();
    }
}
輸出結果:
a
  • 寫出位元組陣列write(byte[] b),每次可以寫出陣列中的數據,程式碼使用演示:
public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("fos.txt");     
      	// 字串轉換爲位元組陣列
      	byte[] b = "我呀".getBytes();
      	// 寫出位元組陣列數據
      	fos.write(b);
      	// 關閉資源
        fos.close();
    }
}
輸出結果:
我呀
  • 寫出指定長度位元組陣列write(byte[] b, int off, int len) ,每次寫出從off索引開始,len個位元組,程式碼使用演示:
public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("fos.txt");     
      	// 字串轉換爲位元組陣列
      	byte[] b = "abcde".getBytes();
		// 寫出從索引2開始,2個位元組。索引2是c,兩個位元組,也就是cd。
        fos.write(b,2,2);
      	// 關閉資源
        fos.close();
    }
}
輸出結果:
cd
數據追加續寫

經過以上的演示,每次程式執行,建立輸出流物件,都會清空目標檔案中的數據。如何保留目標檔案中數據,還能繼續新增新數據呢?

  • public FileOutputStream(File file, boolean append): 建立檔案輸出流以寫入由指定的 File物件表示的檔案。
  • public FileOutputStream(String name, boolean append): 建立檔案輸出流以指定的名稱寫入檔案。

這兩個構造方法,參數中都需要傳入一個boolean型別的值,true 表示追加數據,false 表示清空原有數據。這樣建立的輸出流物件,就可以指定是否追加續寫了,程式碼使用演示:

public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("fos.txt"true);     
      	// 字串轉換爲位元組陣列
      	byte[] b = "abcde".getBytes();
		// 寫出從索引2開始,2個位元組。索引2是c,兩個位元組,也就是cd。
        fos.write(b);
      	// 關閉資源
        fos.close();
    }
}
檔案操作前:cd
檔案操作後:cdabcde
寫出換行

Windows系統裡,換行符號是\r\n 。把

以指定是否追加續寫了,程式碼使用演示:

public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件
        FileOutputStream fos = new FileOutputStream("fos.txt");  
      	// 定義位元組陣列
      	byte[] words = {97,98,99};
      	// 遍歷陣列
        for (int i = 0; i < words.length; i++) {
          	// 寫出一個位元組
            fos.write(words[i]);
          	// 寫出一個換行, 換行符號轉成陣列寫出
            fos.write("\r\n".getBytes());
        }
      	// 關閉資源
        fos.close();
    }
}

輸出結果:
a
b
c
  • 回車符\r和換行符\n
    • 回車符:回到一行的開頭(return)。
    • 換行符:下一行(newline)。
  • 系統中的換行:
    • Windows系統裡,每行結尾是 回車+換行 ,即\r\n
    • Unix系統裡,每行結尾只有 換行 ,即\n
    • Mac系統裡,每行結尾是 回車 ,即\r。從 Mac OS X開始與Linux統一。

2.3 位元組輸入流InputStream類

java.io.InputStream抽象類是表示位元組輸入流的所有類的超類,可以讀取位元組資訊到記憶體中。它定義了位元組輸入流的基本共性功能方法。

  • public void close() :關閉此輸入流並釋放與此流相關聯的任何系統資源。
  • public abstract int read(): 從輸入流讀取數據的下一個位元組。
  • public int read(byte[] b): 從輸入流中讀取一些位元組數,並將它們儲存到位元組陣列 b中 。

close方法,當完成流的操作時,必須呼叫此方法,釋放系統資源。

2.4 FileInputStream類

java.io.FileInputStream類是檔案輸入流,從檔案中讀取位元組。

構造方法
  • FileInputStream(File file): 通過開啓與實際檔案的連線來建立一個 FileInputStream ,該檔案由檔案系統中的 File物件 file命名。
  • FileInputStream(String name): 通過開啓與實際檔案的連線來建立一個 FileInputStream ,該檔案由檔案系統中的路徑名 name命名。

當你建立一個流物件時,必須傳入一個檔案路徑。該路徑下如果沒有該檔案,會拋出FileNotFoundException

  • 構造舉例,程式碼如下:
public class FileInputStreamConstructor throws IOException{
    public static void main(String[] args) {
   	 	// 使用File物件建立流物件
        File file = new File("a.txt");
        FileInputStream fos = new FileInputStream(file);
      
        // 使用檔名稱建立流物件
        FileInputStream fos = new FileInputStream("b.txt");
    }
}
讀取位元組數據
  1. 讀取位元組read方法,每次可以讀取一個位元組的數據,提升爲int型別,讀取到檔案末尾,返回-1,程式碼使用演示:
public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用檔名稱建立流物件
       	FileInputStream fis = new FileInputStream("read.txt");
      	// 讀取數據,返回一個位元組
        int read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
      	// 讀取到末尾,返回-1
       	read = fis.read();
        System.out.println( read);
		// 關閉資源
        fis.close();
    }
}
輸出結果:
a
b
-1

回圈改進讀取方式,程式碼使用演示:

public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用檔名稱建立流物件
       	FileInputStream fis = new FileInputStream("read.txt");
      	// 定義變數,儲存數據
        int b ;
        // 回圈讀取
        while ((b = fis.read())!=-1) {
            System.out.println((char)b);
        }
		// 關閉資源
        fis.close();
    }
}
輸出結果:
a
b

小貼士:

  1. 雖然讀取了一個位元組,但是會自動提升爲int型別。
  2. 流操作完畢後,必須釋放系統資源,呼叫close方法,千萬記得。
  1. 使用位元組陣列讀取read(byte[] b),每次讀取b的長度個位元組到陣列中,返回讀取到的有效位元組個數,讀取到末尾時,返回-1 ,程式碼使用演示:
public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用檔名稱建立流物件.
       	FileInputStream fis = new FileInputStream("read.txt"); // 檔案中爲abcde
      	// 定義變數,作爲有效個數
        int len ;
        // 定義位元組陣列,作爲裝位元組數據的容器   
        byte[] b = new byte[2];
        // 回圈讀取
        while (( len= fis.read(b))!=-1) {
           	// 每次讀取後,把陣列變成字串列印
            System.out.println(new String(b));
        }
		// 關閉資源
        fis.close();
    }
}

輸出結果:
ab
cd
ed

錯誤數據d,是由於最後一次讀取時,只讀取一個位元組e,陣列中,上次讀取的數據沒有被完全替換,所以要通過len ,獲取有效的位元組,程式碼使用演示:

public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用檔名稱建立流物件.
       	FileInputStream fis = new FileInputStream("read.txt"); // 檔案中爲abcde
      	// 定義變數,作爲有效個數
        int len ;
        // 定義位元組陣列,作爲裝位元組數據的容器   
        byte[] b = new byte[2];
        // 回圈讀取
        while (( len= fis.read(b))!=-1) {
           	// 每次讀取後,把陣列的有效位元組部分,變成字串列印
            System.out.println(new String(b,0,len));//  len 每次讀取的有效位元組個數
        }
		// 關閉資源
        fis.close();
    }
}

輸出結果:
ab
cd
e

3.字元流

3.1 字元輸入流Reader

java.io.Reader抽象類是表示用於讀取字元流的所有類的超類,可以讀取字元資訊到記憶體中。它定義了字元輸入流的基本共性功能方法。

  • public void close() :關閉此流並釋放與此流相關聯的任何系統資源。
  • public int read(): 從輸入流讀取一個字元。
  • public int read(char[] cbuf): 從輸入流中讀取一些字元,並將它們儲存到字元陣列 cbuf中 。

3.2 FileReader類

java.io.FileReader類是讀取字元檔案的便利類。構造時使用系統預設的字元編碼和預設位元組緩衝區。

  1. 字元編碼:位元組與字元的對應規則。Windows系統的中文編碼預設是GBK編碼表。

    idea中UTF-8

  2. 位元組緩衝區:一個位元組陣列,用來臨時儲存位元組數據。

構造方法
  • FileReader(File file): 建立一個新的 FileReader ,給定要讀取的File物件。
  • FileReader(String fileName): 建立一個新的 FileReader ,給定要讀取的檔案的名稱。

當你建立一個流物件時,必須傳入一個檔案路徑。類似於FileInputStream 。

  • 構造舉例,程式碼如下:
public class FileReaderConstructor throws IOException{
    public static void main(String[] args) {
   	 	// 使用File物件建立流物件
        File file = new File("a.txt");
        FileReader fr = new FileReader(file);
      
        // 使用檔名稱建立流物件
        FileReader fr = new FileReader("b.txt");
    }
}
讀取字元數據
  1. 讀取字元read方法,每次可以讀取一個字元的數據,提升爲int型別,讀取到檔案末尾,返回-1,回圈讀取,程式碼使用演示:
public class FRRead {
    public static void main(String[] args) throws IOException {
      	// 使用檔名稱建立流物件
       	FileReader fr = new FileReader("read.txt");
      	// 定義變數,儲存數據
        int b ;
        // 回圈讀取
        while ((b = fr.read())!=-1) {
            System.out.println((char)b);
        }
		// 關閉資源
        fr.close();
    }
}

雖然讀取了一個字元,但是會自動提升爲int型別。

  1. 使用字元陣列讀取read(char[] cbuf),每次讀取b的長度個字元到陣列中,返回讀取到的有效字元個數,讀取到末尾時,返回-1 ,程式碼使用演示:
public class FRRead {
    public static void main(String[] args) throws IOException {
      	// 使用檔名稱建立流物件
       	FileReader fr = new FileReader("read.txt");
      	// 定義變數,儲存有效字元個數
        int len ;
        // 定義字元陣列,作爲裝字元數據的容器
         char[] cbuf = new char[2];
        // 回圈讀取
        while ((len = fr.read(cbuf))!=-1) {
            System.out.println(new String(cbuf));
        }
		// 關閉資源
        fr.close();
    }
}
輸出結果:
黑馬
程式
員序

獲取有效的字元改進,程式碼使用演示:

public class FISRead {
    public static void main(String[] args) throws IOException {
      	// 使用檔名稱建立流物件
       	FileReader fr = new FileReader("read.txt");
      	// 定義變數,儲存有效字元個數
        int len ;
        // 定義字元陣列,作爲裝字元數據的容器
        char[] cbuf = new char[2];
        // 回圈讀取
        while ((len = fr.read(cbuf))!=-1) {
            System.out.println(new String(cbuf,0,len));
        }
    	// 關閉資源
        fr.close();
    }
}

輸出結果:
黑馬
程式
員

3.3 字元輸出流Writer

java.io.Writer抽象類是表示用於寫出字元流的所有類的超類,將指定的字元資訊寫出到目的地。它定義了位元組輸出流的基本共性功能方法。

  • void write(int c) 寫入單個字元。
  • void write(char[] cbuf)寫入字元陣列。
  • abstract void write(char[] cbuf, int off, int len)寫入字元陣列的某一部分,off陣列的開始索引,len寫的字元個數。
  • void write(String str)寫入字串。
  • void write(String str, int off, int len) 寫入字串的某一部分,off字串的開始索引,len寫的字元個數。
  • void flush()重新整理該流的緩衝。
  • void close() 關閉此流,但要先重新整理它。

3.4 FileWriter類

java.io.FileWriter類是寫出字元到檔案的便利類。構造時使用系統預設的字元編碼和預設位元組緩衝區。

構造方法
  • FileWriter(File file): 建立一個新的 FileWriter,給定要讀取的File物件。
  • FileWriter(String fileName): 建立一個新的 FileWriter,給定要讀取的檔案的名稱。

當你建立一個流物件時,必須傳入一個檔案路徑,類似於FileOutputStream。

  • 構造舉例,程式碼如下:
public class FileWriterConstructor {
    public static void main(String[] args) throws IOException {
   	 	// 使用File物件建立流物件
        File file = new File("a.txt");
        FileWriter fw = new FileWriter(file);
      
        // 使用檔名稱建立流物件
        FileWriter fw = new FileWriter("b.txt");
    }
}
基本寫出數據

寫出字元write(int b) 方法,每次可以寫出一個字元數據,程式碼使用演示:

public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件
        FileWriter fw = new FileWriter("fw.txt");     
      	// 寫出數據
      	fw.write(97); // 寫出第1個字元
      	fw.write('b'); // 寫出第2個字元
      	fw.write('C'); // 寫出第3個字元
      	fw.write(30000); // 寫出第4個字元,中文編碼表中30000對應一個漢字。
      
      	/*
        【注意】關閉資源時,與FileOutputStream不同。
      	 如果不關閉,數據只是儲存到緩衝區,並未儲存到檔案。
        */
        // fw.close();
    }
}
輸出結果:
abC田

小貼士:

  1. 雖然參數爲int型別四個位元組,但是隻會保留一個字元的資訊寫出。
  2. 未呼叫close方法,數據只是儲存到了緩衝區,並未寫出到檔案中。
關閉和重新整理

因爲內建緩衝區的原因,如果不關閉輸出流,無法寫出字元到檔案中。但是關閉的流物件,是無法繼續寫出數據的。如果我們既想寫出數據,又想繼續使用流,就需要flush 方法了。

  • flush :重新整理緩衝區,流物件可以繼續使用。
  • close:先重新整理緩衝區,然後通知系統釋放資源。流物件不可以再被使用了。

程式碼使用演示:

public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件
        FileWriter fw = new FileWriter("fw.txt");
        // 寫出數據,通過flush
        fw.write('刷'); // 寫出第1個字元
        fw.flush();
        fw.write('新'); // 繼續寫出第2個字元,寫出成功
        fw.flush();
      
      	// 寫出數據,通過close
        fw.write('關'); // 寫出第1個字元
        fw.close();
        fw.write('閉'); // 繼續寫出第2個字元,【報錯】java.io.IOException: Stream closed
        fw.close();
    }
}

小貼士:即便是flush方法寫出了數據,操作的最後還是要呼叫close方法,釋放系統資源。

寫出其他數據
  1. 寫出字元陣列write(char[] cbuf)write(char[] cbuf, int off, int len) ,每次可以寫出字元陣列中的數據,用法類似FileOutputStream,程式碼使用演示:
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件
        FileWriter fw = new FileWriter("fw.txt");     
      	// 字串轉換爲位元組陣列
      	char[] chars = "黑馬程式設計師".toCharArray();
      
      	// 寫出字元陣列
      	fw.write(chars); // 黑馬程式設計師
        
		// 寫出從索引2開始,2個位元組。索引2是'程',兩個位元組,也就是'程式'。
        fw.write(b,2,2); // 程式
      
      	// 關閉資源
        fos.close();
    }
}
  1. 寫出字串write(String str)write(String str, int off, int len) ,每次可以寫出字串中的數據,更爲方便,程式碼使用演示:
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件
        FileWriter fw = new FileWriter("fw.txt");     
      	// 字串
      	String msg = "黑馬程式設計師";
      
      	// 寫出字元陣列
      	fw.write(msg); //黑馬程式設計師
      
		// 寫出從索引2開始,2個位元組。索引2是'程',兩個位元組,也就是'程式'。
        fw.write(msg,2,2);	// 程式
      	
        // 關閉資源
        fos.close();
    }
}
  1. 續寫和換行:操作類似於FileOutputStream。
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用檔名稱建立流物件,可以續寫數據
        FileWriter fw = new FileWriter("fw.txt"true);     
      	// 寫出字串
        fw.write("黑馬");
      	// 寫出換行
      	fw.write("\r\n");
      	// 寫出字串
  		fw.write("程式設計師");
      	// 關閉資源
        fw.close();
    }
}
輸出結果:
黑馬
程式設計師

小貼士:字元流,只能操作文字檔案,不能操作圖片,視訊等非文字檔案。

當我們單純讀或者寫文字檔案時 使用字元流 其他情況使用位元組流

4.屬性集

java.util.Properties 繼承於Hashtable ,來表示一個持久的屬性集。它使用鍵值結構儲存數據,每個鍵及其對應值都是一個字串。該類也被許多Java類使用,比如獲取系統屬性時,System.getProperties 方法就是返回一個Properties物件。

4.1 Properties類

構造方法
  • public Properties() :建立一個空的屬性列表。
基本的儲存方法
  • public Object setProperty(String key, String value) : 儲存一對屬性。
  • public String getProperty(String key) :使用此屬性列表中指定的鍵搜尋屬性值。
  • public Set<String> stringPropertyNames() :所有鍵的名稱的集合。
public class ProDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 建立屬性集物件
        Properties properties = new Properties();
        // 新增鍵值對元素
        properties.setProperty("filename", "a.txt");
        properties.setProperty("length", "209385038");
        properties.setProperty("location", "D:\\a.txt");
        // 列印屬性集物件
        System.out.println(properties);
        // 通過鍵,獲取屬性值
        System.out.println(properties.getProperty("filename"));
        System.out.println(properties.getProperty("length"));
        System.out.println(properties.getProperty("location"));

        // 遍歷屬性集,獲取所有鍵的集合
        Set<String> strings = properties.stringPropertyNames();
        // 列印鍵值對
        for (String key : strings ) {
          	System.out.println(key+" -- "+properties.getProperty(key));
        }
    }
}
輸出結果:
{filename=a.txt, length=209385038, location=D:\a.txt}
a.txt
209385038
D:\a.txt
filename -- a.txt
length -- 209385038
location -- D:\a.txt
與流相關的方法
  • public void load(InputStream inStream): 從位元組輸入流中讀取鍵值對。

參數中使用了位元組輸入流,通過流物件,可以關聯到某檔案上,這樣就能夠載入文字中的數據了。文字數據格式:

filename=a.txt
length=209385038
location=D:\a.txt

載入程式碼演示:

public class ProDemo2 {
    public static void main(String[] args) throws FileNotFoundException {
        // 建立屬性集物件
        Properties pro = new Properties();
        // 載入文字中資訊到屬性集
        pro.load(new FileInputStream("read.txt"));
        // 遍歷集合併列印
        Set<String> strings = pro.stringPropertyNames();
        for (String key : strings ) {
          	System.out.println(key+" -- "+pro.getProperty(key));
        }
     }
}
輸出結果:
filename -- a.txt
length -- 209385038
location -- D:\a.txt

小貼士:文字中的數據,必須是鍵值對形式,可以使用空格、等號、冒號等符號分隔。

緩衝流、轉換流、序列化流、列印流

高效讀寫的緩衝流,能夠轉換編碼的轉換流,能夠持久化儲存物件的序列化流等等。這些功能更爲強大的流,都是在基本的流物件基礎之上建立而來的。

1.緩衝流

1.1 概述

緩衝流,也叫高效流,是對4個基本的FileXxx 流的增強,所以也是4個流,按照數據型別分類:

  • 位元組緩衝流BufferedInputStreamBufferedOutputStream
  • 字元緩衝流BufferedReaderBufferedWriter

緩衝流的基本原理,是在建立流物件時,會建立一個內建的預設大小的緩衝區陣列,通過緩衝區讀寫,減少系統IO次數,從而提高讀寫的效率。

1.2 位元組緩衝流

構造方法
  • public BufferedInputStream(InputStream in) :建立一個 新的緩衝輸入流。
  • public BufferedOutputStream(OutputStream out): 建立一個新的緩衝輸出流。

構造舉例,程式碼如下:

// 建立位元組緩衝輸入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 建立位元組緩衝輸出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
效率測試

查詢API,緩衝流讀寫方法與基本的流是一致的,我們通過複製大檔案(375MB),測試它的效率。

  1. 基本流,程式碼如下:
public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 記錄開始時間
      	long start = System.currentTimeMillis();
		// 建立流物件
        try (
        	FileInputStream fis = new FileInputStream("jdk9.exe");
        	FileOutputStream fos = new FileOutputStream("copy.exe")
        ){
        	// 讀寫數據
            int b;
            while ((b = fis.read()) != -1) {
                fos.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 記錄結束時間
        long end = System.currentTimeMillis();
        System.out.println("普通流複製時間:"+(end - start)+" 毫秒");
    }
}

十幾分鐘過去了...
  1. 緩衝流,程式碼如下:
public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 記錄開始時間
      	long start = System.currentTimeMillis();
		// 建立流物件
        try (
        	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
	     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
        ){
        // 讀寫數據
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 記錄結束時間
        long end = System.currentTimeMillis();
        System.out.println("緩衝流複製時間:"+(end - start)+" 毫秒");
    }
}

緩衝流複製時間:8016 毫秒

如何更快呢?

使用陣列的方式,程式碼如下:

public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
      	// 記錄開始時間
        long start = System.currentTimeMillis();
		// 建立流物件
        try (
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
		 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
        ){
          	// 讀寫數據
            int len;
            byte[] bytes = new byte[8*1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0 , len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 記錄結束時間
        long end = System.currentTimeMillis();
        System.out.println("緩衝流使用陣列複製時間:"+(end - start)+" 毫秒");
    }
}
緩衝流使用陣列複製時間:666 毫秒

1.3 字元緩衝流

構造方法
  • public BufferedReader(Reader in) :建立一個 新的緩衝輸入流。
  • public BufferedWriter(Writer out): 建立一個新的緩衝輸出流。

構造舉例,程式碼如下:

// 建立字元緩衝輸入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 建立字元緩衝輸出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
特有方法

字元緩衝流的基本方法與普通字元流呼叫方式一致,不再闡述,我們來看它們具備的特有方法。

  • BufferedReader:public String readLine(): 讀一行文字。
  • BufferedWriter:public void newLine(): 寫一行行分隔符,由系統屬性定義符號。

readLine方法演示,程式碼如下:

public class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
      	 // 建立流物件
        BufferedReader br = new BufferedReader(new FileReader("in.txt"));
		// 定義字串,儲存讀取的一行文字
        String line  = null;
      	// 回圈讀取,讀取到最後返回null
        while ((line = br.readLine())!=null) {
            System.out.print(line);
            System.out.println("------");
        }
		// 釋放資源
        br.close();
    }
}

newLine方法演示,程式碼如下:

public class BufferedWriterDemo throws IOException {
  public static void main(String[] args) throws IOException  {
    	// 建立流物件
  	BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
    	// 寫出數據
      bw.write("黑馬");
    	// 寫出換行
      bw.newLine();
      bw.write("程式");
      bw.newLine();
      bw.write("員");
      bw.newLine();
  	// 釋放資源
      bw.close();
  }
}
輸出效果:
黑馬
程式
員

2.轉換流

2.1 字元編碼和字元集

字元編碼

計算機中儲存的資訊都是用二進制數表示的,而我們在螢幕上看到的數位、英文、標點符號、漢字等字元是二進制數轉換之後的結果。按照某種規則,將字元儲存到計算機中,稱爲編碼 。反之,將儲存在計算機中的二進制數按照某種規則解析顯示出來,稱爲解碼 。比如說,按照A規則儲存,同樣按照A規則解析,那麼就能顯示正確的文字符號。反之,按照A規則儲存,再按照B規則解析,就會導致亂碼現象。

編碼:字元(能看懂的)–位元組(看不懂的)

解碼:位元組(看不懂的)–>字元(能看懂的)

  • 字元編碼Character Encoding : 就是一套自然語言的字元與二進制數之間的對應規則。

    編碼表:生活中文字和計算機中二進制的對應規則

字元集
  • 字元集 Charset:也叫編碼表。是一個系統支援的所有字元的集合,包括各國家文字、標點符號、圖形符號、數位等。

2.2 編碼引出的問題

在IDEA中,使用FileReader 讀取專案中的文字檔案。由於IDEA的設定,都是預設的UTF-8編碼,所以沒有任何問題。但是,當讀取Windows系統中建立的文字檔案時,由於Windows系統的預設是GBK編碼,就會出現亂碼。

public class ReaderDemo {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("E:\\File_GBK.txt");
        int read;
        while ((read = fileReader.read()) != -1) {
            System.out.print((char)read);
        }
        fileReader.close();
    }
}
輸出結果:
���

那麼如何讀取GBK編碼的檔案呢?

2.3 InputStreamReader類

轉換流java.io.InputStreamReader,是Reader的子類,是從位元組流到字元流的橋樑。它讀取位元組,並使用指定的字元集將其解碼爲字元。它的字元集可以由名稱指定,也可以接受平臺的預設字元集。

構造方法
  • InputStreamReader(InputStream in): 建立一個使用預設字元集的字元流。
  • InputStreamReader(InputStream in, String charsetName): 建立一個指定字元集的字元流。

構造舉例,程式碼如下:

InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
指定編碼讀取
public class ReaderDemo2 {
    public static void main(String[] args) throws IOException {
      	// 定義檔案路徑,檔案爲gbk編碼
        String FileName = "E:\\file_gbk.txt";
      	// 建立流物件,預設UTF8編碼
        InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));
      	// 建立流物件,指定GBK編碼
        InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK");
		// 定義變數,儲存字元
        int read;
      	// 使用預設編碼字元流讀取,亂碼
        while ((read = isr.read()) != -1) {
            System.out.print((char)read); // ��Һ�
        }
        isr.close();
      
      	// 使用指定編碼字元流讀取,正常解析
        while ((read = isr2.read()) != -1) {
            System.out.print((char)read);// 大家好
        }
        isr2.close();
    }
}

2.4 OutputStreamWriter類

轉換流java.io.OutputStreamWriter ,是Writer的子類,是從字元流到位元組流的橋樑。使用指定的字元集將字元編碼爲位元組。它的字元集可以由名稱指定,也可以接受平臺的預設字元集。

構造方法
  • OutputStreamWriter(OutputStream in): 建立一個使用預設字元集的字元流。
  • OutputStreamWriter(OutputStream in, String charsetName): 建立一個指定字元集的字元流。

構造舉例,程式碼如下:

OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");
指定編碼寫出
public class OutputDemo {
    public static void main(String[] args) throws IOException {
      	// 定義檔案路徑
        String FileName = "E:\\out.txt";
      	// 建立流物件,預設UTF8編碼
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
        // 寫出數據
      	osw.write("你好"); // 儲存爲6個位元組
        osw.close();
      	
		// 定義檔案路徑
		String FileName2 = "E:\\out2.txt";
     	// 建立流物件,指定GBK編碼
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
        // 寫出數據
      	osw2.write("你好");// 儲存爲4個位元組
        osw2.close();
    }
}

3.序列化

3.1 概述

Java 提供了一種物件序列化的機制 機製。用一個位元組序列可以表示一個物件,該位元組序列包含該物件的數據物件的型別物件中儲存的屬性等資訊。位元組序列寫出到檔案之後,相當於檔案中持久儲存了一個物件的資訊。

反之,該位元組序列還可以從檔案中讀取回來,重構物件,對它進行反序列化物件的數據物件的型別物件中儲存的數據資訊,都可以用來在記憶體中建立物件。

3.2 ObjectOutputStream類

java.io.ObjectOutputStream 類,將Java物件的原始數據型別寫出到檔案,實現物件的持久儲存。

構造方法
  • public ObjectOutputStream(OutputStream out): 建立一個指定OutputStream的ObjectOutputStream。

構造舉例,程式碼如下:

FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
序列化操作
  1. 一個物件要想序列化,必須滿足兩個條件:
  • 該類必須實現java.io.Serializable 介面,Serializable 是一個標記介面,不實現此介面的類將不會使任何狀態序列化或反序列化,會拋出NotSerializableException
  • 該類的所有屬性必須是可序列化的。如果有一個屬性不需要可序列化的,則該屬性必須註明是瞬態的,使用transient 關鍵字修飾。
public class Employee implements java.io.Serializable {
    public String name;
    public String address;
    public transient int age; // transient瞬態修飾成員,不會被序列化
    public void addressCheck() {
      	System.out.println("Address  check : " + name + " -- " + address);
    }
}

2.寫出物件方法

  • public final void writeObject (Object obj) : 將指定的物件寫出。
public class SerializeDemo{
   	public static void main(String [] args)   {
    	Employee e = new Employee();
    	e.name = "zhangsan";
    	e.address = "beiqinglu";
    	e.age = 20; 
    	try {
      		// 建立序列化流物件
          ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
        	// 寫出物件
        	out.writeObject(e);
        	// 釋放資源
        	out.close();
        	fileOut.close();
        	System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年齡沒有被序列化。
        } catch(IOException i)   {
            i.printStackTrace();
        }
   	}
}
輸出結果:
Serialized data is saved

3.3 ObjectInputStream類

ObjectInputStream反序列化流,將之前使用ObjectOutputStream序列化的原始數據恢復爲物件。

構造方法
  • public ObjectInputStream(InputStream in): 建立一個指定InputStream的ObjectInputStream。
反序列化操作1

如果能找到一個物件的class檔案,我們可以進行反序列化操作,呼叫ObjectInputStream讀取物件的方法:

  • public final Object readObject () : 讀取一個物件。
public class DeserializeDemo {
   public static void main(String [] args)   {
        Employee e = null;
        try {		
             // 建立反序列化流
             FileInputStream fileIn = new FileInputStream("employee.txt");
             ObjectInputStream in = new ObjectInputStream(fileIn);
             // 讀取一個物件
             e = (Employee) in.readObject();
             // 釋放資源
             in.close();
             fileIn.close();
        }catch(IOException i) {
             // 捕獲其他異常
             i.printStackTrace();
             return;
        }catch(ClassNotFoundException c)  {
        	// 捕獲類找不到異常
             System.out.println("Employee class not found");
             c.printStackTrace();
             return;
        }
        // 無異常,直接列印輸出
        System.out.println("Name: " + e.name);	// zhangsan
        System.out.println("Address: " + e.address); // beiqinglu
        System.out.println("age: " + e.age); // 0
    }
}

對於JVM可以反序列化物件,它必須是能夠找到class檔案的類。如果找不到該類的class檔案,則拋出一個 ClassNotFoundException 異常。

反序列化操作2

**另外,當JVM反序列化物件時,能找到class檔案,但是class檔案在序列化物件之後發生了修改,那麼反序列化操作也會失敗,拋出一個InvalidClassException異常。**發生這個異常的原因如下:

  • 該類的序列版本號與從流中讀取的類描述符的版本號不匹配
  • 該類包含未知數據型別
  • 該類沒有可存取的無參數構造方法

Serializable 介面給需要序列化的類,提供了一個序列版本號。serialVersionUID 該版本號的目的在於驗證序列化的物件和對應類是否版本匹配。

public class Employee implements java.io.Serializable {
     // 加入序列版本號
     private static final long serialVersionUID = 1L;
     public String name;
     public String address;
     // 新增新的屬性 ,重新編譯, 可以反序列化,該屬性賦爲預設值.
     public int eid; 

     public void addressCheck() {
         System.out.println("Address  check : " + name + " -- " + address);
     }
}

4.列印流

平時我們在控制檯列印輸出,是呼叫print方法和println方法完成的,這兩個方法都來自於java.io.PrintStream類,該類能夠方便地列印各種數據型別的值,是一種便捷的輸出方式。

4.1 PrintStream類

構造方法
  • public PrintStream(String fileName): 使用指定的檔名建立一個新的列印流。

構造舉例,程式碼如下:

PrintStream ps = new PrintStream("ps.txt")
改變列印流向

System.out就是PrintStream型別的,只不過它的流向是系統規定的,列印在控制檯上。不過,既然是流物件,我們就可以玩一個"小把戲",改變它的流向。

public class PrintDemo {
    public static void main(String[] args) throws IOException {
		// 呼叫系統的列印流,控制檯直接輸出97
        System.out.println(97);
      
		// 建立列印流,指定檔案的名稱
        PrintStream ps = new PrintStream("ps.txt");
      	
      	// 設定系統的列印流流向,輸出到ps.txt
        System.setOut(ps);
      	// 呼叫系統的列印流,ps.txt中輸出97
        System.out.println(97);
    }
}

七、網路程式設計

1.網路程式設計入門

1.1 軟體結構

  • C/S結構 :全稱爲Client/Server結構,是指用戶端和伺服器結構。常見程式有QQ、迅雷等軟體。
  • B/S結構 :全稱爲Browser/Server結構,是指瀏覽器和伺服器結構。常見瀏覽器有谷歌、火狐等。

兩種架構各有優勢,但是無論哪種架構,都離不開網路的支援。網路程式設計,就是在一定的協定下,實現兩臺計算機的通訊的程式。

1.2 網路通訊協定

  • **網路通訊協定:**通過計算機網路可以使多臺計算機實現連線,位於同一個網路中的計算機在進行連線和通訊時需要遵守一定的規則,這就好比在道路中行駛的汽車一定要遵守交通規則一樣。在計算機網路中,這些連線和通訊的規則被稱爲網路通訊協定,它對數據的傳輸格式、傳輸速率、傳輸步驟等做了統一規定,通訊雙方必須同時遵守才能 纔能完成數據交換。

  • TCP/IP協定: 傳輸控制協定/因特網互聯協定( Transmission Control Protocol/Internet Protocol),是Internet最基本、最廣泛的協定。它定義了計算機如何連入因特網,以及數據如何在它們之間傳輸的標準。它的內部包含一系列的用於處理數據通訊的協定,並採用了4層的分層模型,每一層都呼叫它的下一層所提供的協定來完成自己的需求。

TCP/IP協定中的四層分別是應用層、傳輸層、網路層和鏈路層,每層分別負責不同的通訊功能。

  • 鏈路層:鏈路層是用於定義物理傳輸通道,通常是對某些網路連線裝置的驅動協定,例如針對光纖、網線提供的驅動。
  • 網路層:網路層是整個TCP/IP協定的核心,它主要用於將傳輸的數據進行分組,將分組數據發送到目標計算機或者網路。
  • 運輸層:主要使網路程式進行通訊,在進行網路通訊時,可以採用TCP協定,也可以採用UDP協定。
  • 應用層:主要負責應用程式的協定,例如HTTP協定、FTP協定等。

1.3 協定分類

通訊的協定還是比較複雜的,java.net 包中包含的類和介面,它們提供低層次的通訊細節。我們可以直接使用這些類和介面,來專注於網路程式開發,而不用考慮通訊的細節。

java.net 包中提供了兩種常見的網路協定的支援:

  • UDP:用戶數據報協定(User Datagram Protocol)。UDP是無連線通訊協定,即在數據傳輸時,數據的發送端和接收端不建立邏輯連線。簡單來說,當一臺計算機向另外一臺計算機發送數據時,發送端不會確認接收端是否存在,就會發出數據,同樣接收端在收到數據時,也不會向發送端反饋是否收到數據。

    由於使用UDP協定消耗資源小,通訊效率高,所以通常都會用於音訊、視訊和普通數據的傳輸例如視訊會議都使用UDP協定,因爲這種情況即使偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。

    但是在使用UDP協定傳送數據時,由於UDP的面向無連線性,不能保證數據的完整性,因此在傳輸重要數據時不建議使用UDP協定。UDP的交換過程如下圖所示。

特點:數據被限制在64kb以內,超出這個範圍就不能發送了。

數據報(Datagram):網路傳輸的基本單位

  • TCP:傳輸控制協定 (Transmission Control Protocol)。TCP協定是面向連接的通訊協定,即傳輸數據之前,在發送端和接收端建立邏輯連線,然後再傳輸數據,它提供了兩臺計算機之間可靠無差錯的數據傳輸。

    在TCP連線中必須要明確用戶端與伺服器端,由用戶端向伺服器端發出連線請求,每次連線的建立都需要經過「三次握手」。

    • 三次握手:TCP協定中,在發送數據的準備階段,用戶端與伺服器之間的三次互動,以保證連線的可靠。
      • 第一次握手,用戶端向伺服器端發出連線請求,等待伺服器確認。
      • 第二次握手,伺服器端向用戶端回送一個響應,通知用戶端收到了連線請求。
      • 第三次握手,用戶端再次向伺服器端發送確認資訊,確認連線。整個互動過程如下圖所示。

​ 完成三次握手,連線建立後,用戶端和伺服器就可以開始進行數據傳輸了。由於這種面向連接的特性,TCP協定可以保證傳輸數據的安全,所以應用十分廣泛,例如下載檔案、瀏覽網頁等。

1.4 網路程式設計三要素

協定

  • **協定:**計算機網路通訊必須遵守的規則,已經介紹過了,不再贅述。

IP地址

  • IP地址:指網際網路協定地址(Internet Protocol Address),俗稱IP。IP地址用來給一個網路中的計算機裝置做唯一的編號。

IP地址分類

  • IPv4:是一個32位元的二進制數,通常被分爲4個位元組,表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之間的十進制整數,那麼最多可以表示42億個。

  • IPv6:由於網際網路的蓬勃發展,IP地址的需求量愈來愈大,但是網路地址資源有限,使得IP的分配越發緊張。

    爲了擴大地址空間,擬通過IPv6重新定義地址空間,採用128位元地址長度,每16個位元組一組,分成8組十六進制數,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,號稱可以爲全世界的每一粒沙子編上一個網址,這樣就解決了網路地址資源數量不夠的問題。

常用命令

  • 檢視本機IP地址,在控制檯輸入:
ipconfig
  • 檢查網路是否連通,在控制檯輸入:
ping 空格 IP地址
ping 220.181.57.216

特殊的IP地址

  • 本機IP地址:127.0.0.1localhost

埠號

網路的通訊,本質上是兩個進程(應用程式)的通訊。每台計算機都有很多的進程,那麼在網路通訊時,如何區分這些進程呢?

如果說IP地址可以唯一標識網路中的裝置,那麼埠號就可以唯一標識裝置中的進程(應用程式)了。

  • **埠號:用兩個位元組表示的整數,它的取值範圍是065535**。其中,01023之間的埠號用於一些知名的網路服務和應用,普通的應用程式需要使用1024以上的埠號。如果埠號被另外一個服務或應用所佔用,會導致當前程式啓動失敗。

利用協定+IP地址+埠號 三元組合,就可以標識網路中的進程了,那麼進程間的通訊就可以利用這個標識與其它進程進行互動。

2.TCP通訊程式

2.1 概述

TCP通訊能實現兩臺計算機之間的數據互動,通訊的兩端,要嚴格區分爲用戶端(Client)與伺服器端(Server)。

兩端通訊時步驟:

  1. 伺服器端程式,需要事先啓動,等待用戶端的連線。
  2. 用戶端主動連線伺服器端,連線成功才能 纔能通訊。伺服器端不可以主動連線用戶端。

在Java中,提供了兩個類用於實現TCP通訊程式:

  1. 用戶端:java.net.Socket 類表示。建立Socket物件,向伺服器端發出連線請求,伺服器端響應請求,兩者建立連線開始通訊。
  2. 伺服器端:java.net.ServerSocket 類表示。建立ServerSocket物件,相當於開啓一個服務,並等待用戶端的連線。

2.2 Socket類

Socket 類:該類實現用戶端通訊端,通訊端指的是兩臺裝置之間通訊的端點。

構造方法

  • public Socket(String host, int port) :建立通訊端物件並將其連線到指定主機上的指定埠號。如果指定的host是null ,則相當於指定地址爲回送地址。

    小貼士:回送地址(127.x.x.x) 是本機回送地址(Loopback Address),主要用於網路軟體測試以及本地機進程間通訊,無論什麼程式,一旦使用回送地址發送數據,立即返回,不進行任何網路傳輸。

構造舉例,程式碼如下:

Socket client = new Socket("127.0.0.1", 6666);

成員方法

  • public InputStream getInputStream() : 返回此通訊端的輸入流。
    • 如果此Scoket具有相關聯的通道,則生成的InputStream 的所有操作也關聯該通道。
    • 關閉生成的InputStream也將關閉相關的Socket。
  • public OutputStream getOutputStream() : 返回此通訊端的輸出流。
    • 如果此Scoket具有相關聯的通道,則生成的OutputStream 的所有操作也關聯該通道。
    • 關閉生成的OutputStream也將關閉相關的Socket。
  • public void close() :關閉此通訊端。
    • 一旦一個socket被關閉,它不可再使用。
    • 關閉此socket也將關閉相關的InputStream和OutputStream 。
  • public void shutdownOutput() : 禁用此通訊端的輸出流。
    • 任何先前寫出的數據將被髮送,隨後終止輸出流。

2.3 ServerSocket類

ServerSocket類:這個類實現了伺服器通訊端,該物件等待通過網路的請求。

構造方法

  • public ServerSocket(int port) :使用該構造方法在建立ServerSocket物件時,就可以將其系結到一個指定的埠號上,參數port就是埠號。

構造舉例,程式碼如下:

ServerSocket server = new ServerSocket(6666);

成員方法

  • public Socket accept() :偵聽並接受連線,返回一個新的Socket物件,用於和用戶端實現通訊。該方法會一直阻塞直到建立連線。

2.4 簡單的TCP網路程式

TCP通訊分析圖解

  1. 【伺服器端】啓動,建立ServerSocket物件,等待連線。
  2. 【用戶端】啓動,建立Socket物件,請求連線。
  3. 【伺服器端】接收連線,呼叫accept方法,並返回一個Socket物件。
  4. 【用戶端】Socket物件,獲取OutputStream,向伺服器端寫出數據。
  5. 【伺服器端】Scoket物件,獲取InputStream,讀取用戶端發送的數據。

到此,用戶端向伺服器端發送數據成功。

[外連圖片轉存失敗,源站可能有防盜鏈機制 機製,建議將圖片儲存下來直接上傳(img-mGkzfVoc-1597290664373)(E:/Java/JavaSE/1.Java基礎/09.網路程式設計/22.【網路程式設計】-筆記/就業班-day11-網路程式設計/img/5_簡單通訊.jpg)]

自此,伺服器端向用戶端回寫數據。

  1. 【伺服器端】Socket物件,獲取OutputStream,向用戶端回寫數據。
  2. 【用戶端】Scoket物件,獲取InputStream,解析回寫數據。
  3. 【用戶端】釋放資源,斷開連線。

用戶端向伺服器發送數據

伺服器端實現:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("伺服器端啓動 , 等待連線 .... ");
        // 1.建立 ServerSocket物件,系結埠,開始等待連線
        ServerSocket ss = new ServerSocket(6666);
        // 2.接收連線 accept 方法, 返回 socket 物件.
        Socket server = ss.accept();
        // 3.通過socket 獲取輸入流
        InputStream is = server.getInputStream();
        // 4.一次性讀取數據
      	// 4.1 建立位元組陣列
        byte[] b = new byte[1024];
      	// 4.2 據讀取到位元組陣列中.
        int len = is.read(b)// 4.3 解析陣列,列印字串資訊
        String msg = new String(b, 0, len);
        System.out.println(msg);
        //5.關閉資源.
        is.close();
        server.close();
    }
}

用戶端實現:

public class ClientTCP {
	public static void main(String[] args) throws Exception {
		System.out.println("用戶端 發送數據");
		// 1.建立 Socket ( ip , port ) , 確定連線到哪裏.
		Socket client = new Socket("localhost", 6666);
		// 2.獲取流物件 . 輸出流
		OutputStream os = client.getOutputStream();
		// 3.寫出數據.
		os.write("你好麼? tcp ,我來了".getBytes());
		// 4. 關閉資源 .
		os.close();
		client.close();
	}
}

伺服器向用戶端回寫數據

伺服器端實現:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("伺服器端啓動 , 等待連線 .... ");
        // 1.建立 ServerSocket物件,系結埠,開始等待連線
        ServerSocket ss = new ServerSocket(6666);
        // 2.接收連線 accept 方法, 返回 socket 物件.
        Socket server = ss.accept();
        // 3.通過socket 獲取輸入流
        InputStream is = server.getInputStream();
        // 4.一次性讀取數據
      	// 4.1 建立位元組陣列
        byte[] b = new byte[1024];
      	// 4.2 據讀取到位元組陣列中.
        int len = is.read(b)// 4.3 解析陣列,列印字串資訊
        String msg = new String(b, 0, len);
        System.out.println(msg);
      	// =================回寫數據=======================
      	// 5. 通過 socket 獲取輸出流
      	 OutputStream out = server.getOutputStream();
      	// 6. 回寫數據
      	 out.write("我很好,謝謝你".getBytes());
      	// 7.關閉資源.
      	out.close();
        is.close();
        server.close();
    }
}

用戶端實現:

public class ClientTCP {
	public static void main(String[] args) throws Exception {
		System.out.println("用戶端 發送數據");
		// 1.建立 Socket ( ip , port ) , 確定連線到哪裏.
		Socket client = new Socket("localhost", 6666);
		// 2.通過Scoket,獲取輸出流物件 
		OutputStream os = client.getOutputStream();
		// 3.寫出數據.
		os.write("你好麼? tcp ,我來了".getBytes());
      	// ==============解析回寫=========================
      	// 4. 通過Scoket,獲取 輸入流物件
      	InputStream in = client.getInputStream();
      	// 5. 讀取數據數據
      	byte[] b = new byte[100];
      	int len = in.read(b);
      	System.out.println(new String(b, 0, len));
		// 6. 關閉資源 .
      	in.close();
		os.close();
		client.close();
	}
}

八、JDK8新特性

函數式介面

1.函數式介面

函數式介面在Java中是指:有且僅有一個抽象方法的介面
函數式介面,即適用於函數語言程式設計場景的介面。而Java中的函數語言程式設計體現就是Lambda,所以函數式介面就是可
以適用於Lambda使用的介面。只有確保介面中有且僅有一個抽象方法,Java中的Lambda才能 纔能順利地進行推導。

1.1 格式

只要確保介面中有且僅有一個抽象方法即可 :

修飾符 interface 介面名稱 {
public abstract 返回值型別 方法名稱(可選參數資訊);
// 其他非抽象方法內容
}

由於介面當中抽象方法的 public abstract 是可以省略的,所以定義一個函數式介面很簡單:

public interface MyFunctionalInterface {
void myMethod();
}

1.2 @FunctionalInterface註解

與 @Override 註解的作用類似,Java 8中專門爲函數式介面引入了一個新的註解: @FunctionalInterface 。該註解可用於一個介面的定義上:

一旦使用該註解來定義介面,編譯器將會強制檢查該介面是否確實有且僅有一個抽象方法,否則將會報錯。需要注意的是,即使不使用該註解,只要滿足函數式介面的定義,這仍然是一個函數式介面,使用起來都一樣

2.函數語言程式設計

2.1 Lambda的延遲執行

有些場景的程式碼執行後,結果不一定會被使用,從而造成效能浪費。而Lambda表達式是延遲執行的,這正好可以作爲解決方案,提升效能。

效能浪費的日誌案例
注:日誌可以幫助我們快速的定位問題,記錄程式執行過程中的情況,以便專案的監控和優化。
一種典型的場景就是對參數進行有條件使用,例如對日誌訊息進行拼接後,在滿足條件的情況下進行列印輸出:

public class Demo01Logger {
    private static void log(int level, String msg) {
        if (level == 1) {
            System.out.println(msg);
		}
	} 
    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        log(1, msgA + msgB + msgC);
	}
}

這段程式碼存在問題:無論級別是否滿足要求,作爲 log 方法的第二個參數,三個字串一定會首先被拼接並傳入方
法內,然後纔會進行級別判斷。如果級別不符合要求,那麼字串的拼接操作就白做了,存在效能浪費。

體驗Lambda的更優寫法

@FunctionalInterface
public interface MessageBuilder {
    String buildMessage();
}
public class Demo02LoggerLambda {
    private static void log(int level, MessageBuilder builder) {
        if (level == 1) {
            System.out.println(builder.buildMessage());
        }
	}
	public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        log(1, ()> msgA + msgB + msgC );
    }
}

這樣一來,只有當級別滿足要求的時候,纔會進行三個字串的拼接;否則三個字串將不會進行拼接 。

證明Lambda的延遲

public class Demo03LoggerDelay {
    private static void log(int level, MessageBuilder builder) {
        if (level == 1) {
            System.out.println(builder.buildMessage());
        }
	} 
    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        log(2, ()> {
            System.out.println("Lambda執行!");
            return msgA + msgB + msgC;
        });
	}
}

從結果中可以看出,在不符合級別要求的情況下,Lambda將不會執行。從而達到節省效能的效果。

2.2 使用Lambda作爲參數和返回值

如果拋開實現原理不說,Java中的Lambda表達式可以被當作是匿名內部類的替代品。如果方法的參數是一個函數式介面型別,那麼就可以使用Lambda表達式進行替代。使用Lambda表達式作爲方法參數,其實就是使用函數式介面作爲方法參數。

3.常用函數式介面

3.1 Supplier介面

java.util.function.Supplier 介面僅包含一個無參的方法: T get() 。用來獲取一個泛型參數指定型別的物件數據。由於這是一個函數式介面,這也就意味着對應的Lambda表達式需要「對外提供」一個符合泛型型別的物件數據。

import java.util.function.Supplier;
public class Demo08Supplier {
    private static String getString(Supplier<String> function) {
        return function.get();
    } 
    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        System.out.println(getString(()> msgA + msgB));
    }
}

3.2 Consumer介面

Java.util.function.Consumer 介面則正好與Supplier介面相反,它不是生產一個數據,而是消費一個數據,其數據型別由泛型決定。

抽象方法:accept

Consumer 介面中包含抽象方法 void accept(T t) ,意爲消費一個指定泛型的數據。

import java.util.function.Consumer;
public class Demo09Consumer {
    private static void consumeString(Consumer<String> function) {
        function.accept("Hello");
    } 
    public static void main(String[] args) {
        consumeString(s ‐> System.out.println(s));
    }
}

預設方法:andThen

如果一個方法的參數和返回值全都是 Consumer 型別,那麼就可以實現效果:消費數據的時候,首先做一個操作,然後再做一個操作,實現組合。而這個方法就是 Consumer 介面中的default方法 andThen 。JDK的原始碼:

default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t)> { accept(t); after.accept(t); };
}

要想實現組合,需要兩個或多個Lambda表達式即可,而 andThen 的語意正是「一步接一步」操作。例如兩個步驟組合的情況:

import java.util.function.Consumer;
public class Demo10ConsumerAndThen {
    private static void consumeString(Consumer<String> one, Consumer<String> two) {
		one.andThen(two).accept("Hello");
    } 
    public static void main(String[] args) {
        consumeString(
            s ‐> System.out.println(s.toUpperCase()),
            s ‐> System.out.println(s.toLowerCase()));
    }
}

3.3 Predicate介面

3.4 Function介面

Stream流、方法參照