1.不可變集合
1.1 什麼是不可變集合
是一個長度不可變,內容也無法修改的集合
1.2 使用場景
如果某個資料不能被修改,把它防禦性地複製到不可變集合中是個很好的實踐。
當集合物件被不可信的庫呼叫時,不可變形式是安全的。
簡單理解:
不想讓別人修改集合中的內容
比如說:
1,鬥地主的54張牌,是不能新增,不能刪除,不能修改的
2,鬥地主的打牌規則:單張,對子,三張,順子等,也是不能修改的
3,用程式碼獲取的作業系統硬體資訊,也是不能被修改的
1.3 不可變集合分類
- 不可變的list集合
- 不可變的set集合
- 不可變的map集合
1.4 不可變的list集合
public class ImmutableDemo1 {
public static void main(String[] args) {
/*
建立不可變的List集合
"張三", "李四", "王五", "趙六"
*/
//一旦建立完畢之後,是無法進行修改的,在下面的程式碼中,只能進行查詢操作
List<String> list = List.of("張三", "李四", "王五", "趙六");
System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
System.out.println(list.get(3));
System.out.println("---------------------------");
for (String s : list) {
System.out.println(s);
}
System.out.println("---------------------------");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------------");
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("---------------------------");
//list.remove("李四");
//list.add("aaa");
list.set(0,"aaa");
}
}
1.5 不可變的Set集合
public class ImmutableDemo2 {
public static void main(String[] args) {
/*
建立不可變的Set集合
"張三", "李四", "王五", "趙六"
細節:
當我們要獲取一個不可變的Set集合時,裡面的引數一定要保證唯一性
*/
//一旦建立完畢之後,是無法進行修改的,在下面的程式碼中,只能進行查詢操作
Set<String> set = Set.of("張三", "張三", "李四", "王五", "趙六");
for (String s : set) {
System.out.println(s);
}
System.out.println("-----------------------");
Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-----------------------");
//set.remove("王五");
}
}
1.6 不可變的Map集合
1.6.1:鍵值對個數小於等於10
public class ImmutableDemo3 {
public static void main(String[] args) {
/*
建立Map的不可變集合
細節1:
鍵是不能重複的
細節2:
Map裡面的of方法,引數是有上限的,最多隻能傳遞20個引數,10個鍵值對
細節3:
如果我們要傳遞多個鍵值對物件,數量大於10個,在Map介面中還有一個方法
*/
//一旦建立完畢之後,是無法進行修改的,在下面的程式碼中,只能進行查詢操作
Map<String, String> map = Map.of("張三", "南京", "張三", "北京", "王五", "上海",
"趙六", "廣州", "孫七", "深圳", "周八", "杭州",
"吳九", "寧波", "鄭十", "蘇州", "劉一", "無錫",
"陳二", "嘉興");
Set<String> keys = map.keySet();
for (String key : keys) {
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("--------------------------");
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println("--------------------------");
}
}
1.6.2:鍵值對個數大於10
public class ImmutableDemo4 {
public static void main(String[] args) {
/*
建立Map的不可變集合,鍵值對的數量超過10個
*/
//1.建立一個普通的Map集合
HashMap<String, String> hm = new HashMap<>();
hm.put("張三", "南京");
hm.put("李四", "北京");
hm.put("王五", "上海");
hm.put("趙六", "北京");
hm.put("孫七", "深圳");
hm.put("周八", "杭州");
hm.put("吳九", "寧波");
hm.put("鄭十", "蘇州");
hm.put("劉一", "無錫");
hm.put("陳二", "嘉興");
hm.put("aaa", "111");
//2.利用上面的資料來獲取一個不可變的集合
/*
//獲取到所有的鍵值對物件(Entry物件)
Set<Map.Entry<String, String>> entries = hm.entrySet();
//把entries變成一個陣列
Map.Entry[] arr1 = new Map.Entry[0];
//toArray方法在底層會比較集合的長度跟陣列的長度兩者的大小
//如果集合的長度 > 陣列的長度 :資料在陣列中放不下,此時會根據實際資料的個數,重新建立陣列
//如果集合的長度 <= 陣列的長度:資料在陣列中放的下,此時不會建立新的陣列,而是直接用
Map.Entry[] arr2 = entries.toArray(arr1);
//不可變的map集合
Map map = Map.ofEntries(arr2);
map.put("bbb","222");*/
//Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));
Map<String, String> map = Map.copyOf(hm);
map.put("bbb","222");
}
}
2.Stream流
2.1體驗Stream流
案例需求
按照下面的要求完成集合的建立和遍歷
- 建立一個集合,儲存多個字串元素
- 把集合中所有以"張"開頭的元素儲存到一個新的集合
- 把"張"開頭的集合中的長度為3的元素儲存到一個新的集合
- 遍歷上一步得到的集合
原始方式示例程式碼
public class MyStream1 { public static void main(String[] args) { //集合的批次新增 ArrayList<String> list1 = new ArrayList<>(List.of("張三丰","張無忌","張翠山","王二麻子","張良","謝廣坤")); //list.add() //遍歷list1把以張開頭的元素新增到list2中。 ArrayList<String> list2 = new ArrayList<>(); for (String s : list1) { if(s.startsWith("張")){ list2.add(s); } } //遍歷list2集合,把其中長度為3的元素,再新增到list3中。 ArrayList<String> list3 = new ArrayList<>(); for (String s : list2) { if(s.length() == 3){ list3.add(s); } } for (String s : list3) { System.out.println(s); } } }
使用Stream流示例程式碼
public class StreamDemo { public static void main(String[] args) { //集合的批次新增 ArrayList<String> list1 = new ArrayList<>(List.of("張三丰","張無忌","張翠山","王二麻子","張良","謝廣坤")); //Stream流 list1.stream().filter(s->s.startsWith("張")) .filter(s->s.length() == 3) .forEach(s-> System.out.println(s)); } }
Stream流的好處
- 直接閱讀程式碼的字面意思即可完美展示無關邏輯方式的語義:獲取流、過濾姓張、過濾長度為3、逐一列印
- Stream流把真正的函數語言程式設計風格引入到Java中
- 程式碼簡潔
2.2Stream流的常見生成方式
- Stream流的思想
Stream流的三類方法
獲取Stream流
- 建立一條流水線,並把資料放到流水線上準備進行操作
中間方法
- 流水線上的操作
- 一次操作完畢之後,還可以繼續進行其他操作
終結方法
- 一個Stream流只能有一個終結方法
- 是流水線上的最後一個操作
生成Stream流的方式
Collection體系集合
使用預設方法stream()生成流, default Stream<E> stream()
Map體系集合
把Map轉成Set集合,間接的生成流
陣列
透過Arrays中的靜態方法stream生成流
同種資料型別的多個資料
透過Stream介面的靜態方法of(T... values)生成流
程式碼演示
public class StreamDemo { public static void main(String[] args) { //Collection體系的集合可以使用預設方法stream()生成流 List<String> list = new ArrayList<String>(); Stream<String> listStream = list.stream(); Set<String> set = new HashSet<String>(); Stream<String> setStream = set.stream(); //Map體系的集合間接的生成流 Map<String,Integer> map = new HashMap<String, Integer>(); Stream<String> keyStream = map.keySet().stream(); Stream<Integer> valueStream = map.values().stream(); Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); //陣列可以透過Arrays中的靜態方法stream生成流 String[] strArray = {"hello","world","java"}; Stream<String> strArrayStream = Arrays.stream(strArray); //同種資料型別的多個資料可以透過Stream介面的靜態方法of(T... values)生成流 Stream<String> strArrayStream2 = Stream.of("hello", "world", "java"); Stream<Integer> intStream = Stream.of(10, 20, 30); } }
2.3Stream流中間操作方法
概念
中間操作的意思是,執行完此方法之後,Stream流依然可以繼續執行其他操作
常見方法
方法名 說明 Stream<T> filter(Predicate predicate) 用於對流中的資料進行過濾 Stream<T> limit(long maxSize) 返回此流中的元素組成的流,擷取前指定引數個數的資料 Stream<T> skip(long n) 跳過指定引數個數的資料,返回由該流的剩餘元素組成的流 static <T> Stream<T> concat(Stream a, Stream b) 合併a和b兩個流為一個流 Stream<T> distinct() 返回由該流的不同元素(根據Object.equals(Object) )組成的流 filter程式碼演示
public class MyStream3 { public static void main(String[] args) { // Stream<T> filter(Predicate predicate):過濾 // Predicate介面中的方法 boolean test(T t):對給定的引數進行判斷,返回一個布林值 ArrayList<String> list = new ArrayList<>(); list.add("張三丰"); list.add("張無忌"); list.add("張翠山"); list.add("王二麻子"); list.add("張良"); list.add("謝廣坤"); //filter方法獲取流中的 每一個資料. //而test方法中的s,就依次表示流中的每一個資料. //我們只要在test方法中對s進行判斷就可以了. //如果判斷的結果為true,則當前的資料留下 //如果判斷的結果為false,則當前資料就不要. // list.stream().filter( // new Predicate<String>() { // @Override // public boolean test(String s) { // boolean result = s.startsWith("張"); // return result; // } // } // ).forEach(s-> System.out.println(s)); //因為Predicate介面中只有一個抽象方法test //所以我們可以使用lambda表示式來簡化 // list.stream().filter( // (String s)->{ // boolean result = s.startsWith("張"); // return result; // } // ).forEach(s-> System.out.println(s)); list.stream().filter(s ->s.startsWith("張")).forEach(s-> System.out.println(s)); } }
limit&skip程式碼演示
public class StreamDemo02 { public static void main(String[] args) { //建立一個集合,儲存多個字串元素 ArrayList<String> list = new ArrayList<String>(); list.add("林青霞"); list.add("張曼玉"); list.add("王祖賢"); list.add("柳巖"); list.add("張敏"); list.add("張無忌"); //需求1:取前3個資料在控制檯輸出 list.stream().limit(3).forEach(s-> System.out.println(s)); System.out.println("--------"); //需求2:跳過3個元素,把剩下的元素在控制檯輸出 list.stream().skip(3).forEach(s-> System.out.println(s)); System.out.println("--------"); //需求3:跳過2個元素,把剩下的元素中前2個在控制檯輸出 list.stream().skip(2).limit(2).forEach(s-> System.out.println(s)); } }
concat&distinct程式碼演示
public class StreamDemo03 { public static void main(String[] args) { //建立一個集合,儲存多個字串元素 ArrayList<String> list = new ArrayList<String>(); list.add("林青霞"); list.add("張曼玉"); list.add("王祖賢"); list.add("柳巖"); list.add("張敏"); list.add("張無忌"); //需求1:取前4個資料組成一個流 Stream<String> s1 = list.stream().limit(4); //需求2:跳過2個資料組成一個流 Stream<String> s2 = list.stream().skip(2); //需求3:合併需求1和需求2得到的流,並把結果在控制檯輸出 // Stream.concat(s1,s2).forEach(s-> System.out.println(s)); //需求4:合併需求1和需求2得到的流,並把結果在控制檯輸出,要求字串元素不能重複 Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s)); } }
2.4Stream流終結操作方法
概念
終結操作的意思是,執行完此方法之後,Stream流將不能再執行其他操作
常見方法
方法名 說明 void forEach(Consumer action) 對此流的每個元素執行操作 long count() 返回此流中的元素數 程式碼演示
public class MyStream5 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("張三丰"); list.add("張無忌"); list.add("張翠山"); list.add("王二麻子"); list.add("張良"); list.add("謝廣坤"); //method1(list); // long count():返回此流中的元素數 long count = list.stream().count(); System.out.println(count); } private static void method1(ArrayList<String> list) { // void forEach(Consumer action):對此流的每個元素執行操作 // Consumer介面中的方法void accept(T t):對給定的引數執行此操作 //在forEach方法的底層,會迴圈獲取到流中的每一個資料. //並迴圈呼叫accept方法,並把每一個資料傳遞給accept方法 //s就依次表示了流中的每一個資料. //所以,我們只要在accept方法中,寫上處理的業務邏輯就可以了. list.stream().forEach( new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } } ); System.out.println("===================="); //lambda表示式的簡化格式 //是因為Consumer介面中,只有一個accept方法 list.stream().forEach( (String s)->{ System.out.println(s); } ); System.out.println("===================="); //lambda表示式還是可以進一步簡化的. list.stream().forEach(s->System.out.println(s)); } }
2.5Stream流的收集操作
概念
對資料使用Stream流的方式操作完畢後,可以把流中的資料收集到集合中
常用方法
方法名 說明 R collect(Collector collector) 把結果收集到集合中 工具類Collectors提供了具體的收集方式
方法名 說明 public static <T> Collector toList() 把元素收集到List集合中 public static <T> Collector toSet() 把元素收集到Set集合中 public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map集合中 程式碼演示
// toList和toSet方法演示 public class MyStream7 { public static void main(String[] args) { ArrayList<Integer> list1 = new ArrayList<>(); for (int i = 1; i <= 10; i++) { list1.add(i); } list1.add(10); list1.add(10); list1.add(10); list1.add(10); list1.add(10); //filter負責過濾資料的. //collect負責收集資料. //獲取流中剩餘的資料,但是他不負責建立容器,也不負責把資料新增到容器中. //Collectors.toList() : 在底層會建立一個List集合.並把所有的資料新增到List集合中. List<Integer> list = list1.stream().filter(number -> number % 2 == 0) .collect(Collectors.toList()); System.out.println(list); Set<Integer> set = list1.stream().filter(number -> number % 2 == 0) .collect(Collectors.toSet()); System.out.println(set); } } /** Stream流的收集方法 toMap方法演示 建立一個ArrayList集合,並新增以下字串。字串中前面是姓名,後面是年齡 "zhangsan,23" "lisi,24" "wangwu,25" 保留年齡大於等於24歲的人,並將結果收集到Map集合中,姓名為鍵,年齡為值 */ public class MyStream8 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("zhangsan,23"); list.add("lisi,24"); list.add("wangwu,25"); Map<String, Integer> map = list.stream().filter( s -> { String[] split = s.split(","); int age = Integer.parseInt(split[1]); return age >= 24; } // collect方法只能獲取到流中剩餘的每一個資料. //在底層不能建立容器,也不能把資料新增到容器當中 //Collectors.toMap 建立一個map集合並將資料新增到集合當中 // s 依次表示流中的每一個資料 //第一個lambda表示式就是如何獲取到Map中的鍵 //第二個lambda表示式就是如何獲取Map中的值 ).collect(Collectors.toMap( s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1]) )); System.out.println(map); } }
2.6Stream流綜合練習
案例需求
現在有兩個ArrayList集合,分別儲存6名男演員名稱和6名女演員名稱,要求完成如下的操作
- 男演員只要名字為3個字的前三人
- 女演員只要姓林的,並且不要第一個
- 把過濾後的男演員姓名和女演員姓名合併到一起
- 把上一步操作後的元素作為構造方法的引數建立演員物件,遍歷資料
演員類Actor已經提供,裡面有一個成員變數,一個帶參構造方法,以及成員變數對應的get/set方法
程式碼實現
演員類
public class Actor { private String name; public Actor(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
測試類
public class StreamTest { public static void main(String[] args) { //建立集合 ArrayList<String> manList = new ArrayList<String>(); manList.add("周潤發"); manList.add("成龍"); manList.add("劉德華"); manList.add("吳京"); manList.add("周星馳"); manList.add("李連杰"); ArrayList<String> womanList = new ArrayList<String>(); womanList.add("林心如"); womanList.add("張曼玉"); womanList.add("林青霞"); womanList.add("柳巖"); womanList.add("林志玲"); womanList.add("王祖賢"); //男演員只要名字為3個字的前三人 Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(3); //女演員只要姓林的,並且不要第一個 Stream<String> womanStream = womanList.stream().filter(s -> s.startsWith("林")).skip(1); //把過濾後的男演員姓名和女演員姓名合併到一起 Stream<String> stream = Stream.concat(manStream, womanStream); // 將流中的資料封裝成Actor物件之後列印 stream.forEach(name -> { Actor actor = new Actor(name); System.out.println(actor); }); } }
3.方法引用
3.1體驗方法引用
方法引用的出現原因
在使用Lambda表示式的時候,我們實際上傳遞進去的程式碼就是一種解決方案:拿引數做操作
那麼考慮一種情況:如果我們在Lambda中所指定的操作方案,已經有地方存在相同方案,那是否還有必要再寫重複邏輯呢?答案肯定是沒有必要
那我們又是如何使用已經存在的方案的呢?
這就是我們要講解的方法引用,我們是透過方法引用來使用已經存在的方案
程式碼演示
public interface Printable { void printString(String s); } public class PrintableDemo { public static void main(String[] args) { //在主方法中呼叫usePrintable方法 // usePrintable((String s) -> { // System.out.println(s); // }); //Lambda簡化寫法 usePrintable(s -> System.out.println(s)); //方法引用 usePrintable(System.out::println); } private static void usePrintable(Printable p) { p.printString("愛生活愛Java"); } }
3.2方法引用符
方法引用符
:: 該符號為引用運算子,而它所在的表示式被稱為方法引用
推導與省略
- 如果使用Lambda,那麼根據“可推導就是可省略”的原則,無需指定引數型別,也無需指定的過載形式,它們都將被自動推導
- 如果使用方法引用,也是同樣可以根據上下文進行推導
- 方法引用是Lambda的孿生兄弟
3.3引用類方法
引用類方法,其實就是引用類的靜態方法
格式
類名::靜態方法
範例
Integer::parseInt
Integer類的方法:public static int parseInt(String s) 將此String轉換為int型別資料
練習描述
- 定義一個介面(Converter),裡面定義一個抽象方法 int convert(String s);
定義一個測試類(ConverterDemo),在測試類中提供兩個方法
- 一個方法是:useConverter(Converter c)
- 一個方法是主方法,在主方法中呼叫useConverter方法
程式碼演示
public interface Converter { int convert(String s); } public class ConverterDemo { public static void main(String[] args) { //Lambda寫法 useConverter(s -> Integer.parseInt(s)); //引用類方法 useConverter(Integer::parseInt); } private static void useConverter(Converter c) { int number = c.convert("666"); System.out.println(number); } }
使用說明
Lambda表示式被類方法替代的時候,它的形式引數全部傳遞給靜態方法作為引數
3.4引用物件的例項方法
引用物件的例項方法,其實就引用類中的成員方法
格式
物件::成員方法
範例
"HelloWorld"::toUpperCase
String類中的方法:public String toUpperCase() 將此String所有字元轉換為大寫
練習描述
定義一個類(PrintString),裡面定義一個方法
public void printUpper(String s):把字串引數變成大寫的資料,然後在控制檯輸出
定義一個介面(Printer),裡面定義一個抽象方法
void printUpperCase(String s)
定義一個測試類(PrinterDemo),在測試類中提供兩個方法
- 一個方法是:usePrinter(Printer p)
- 一個方法是主方法,在主方法中呼叫usePrinter方法
程式碼演示
public class PrintString { //把字串引數變成大寫的資料,然後在控制檯輸出 public void printUpper(String s) { String result = s.toUpperCase(); System.out.println(result); } } public interface Printer { void printUpperCase(String s); } public class PrinterDemo { public static void main(String[] args) { //Lambda簡化寫法 usePrinter(s -> System.out.println(s.toUpperCase())); //引用物件的例項方法 PrintString ps = new PrintString(); usePrinter(ps::printUpper); } private static void usePrinter(Printer p) { p.printUpperCase("HelloWorld"); } }
使用說明
Lambda表示式被物件的例項方法替代的時候,它的形式引數全部傳遞給該方法作為引數
3.5引用類的例項方法
引用類的例項方法,其實就是引用類中的成員方法
格式
類名::成員方法
範例
String::substring
public String substring(int beginIndex,int endIndex)
從beginIndex開始到endIndex結束,擷取字串。返回一個子串,子串的長度為endIndex-beginIndex
練習描述
定義一個介面(MyString),裡面定義一個抽象方法:
String mySubString(String s,int x,int y);
定義一個測試類(MyStringDemo),在測試類中提供兩個方法
- 一個方法是:useMyString(MyString my)
- 一個方法是主方法,在主方法中呼叫useMyString方法
程式碼演示
public interface MyString { String mySubString(String s,int x,int y); } public class MyStringDemo { public static void main(String[] args) { //Lambda簡化寫法 useMyString((s,x,y) -> s.substring(x,y)); //引用類的例項方法 useMyString(String::substring); } private static void useMyString(MyString my) { String s = my.mySubString("HelloWorld", 2, 5); System.out.println(s); } }
使用說明
Lambda表示式被類的例項方法替代的時候
第一個引數作為呼叫者
後面的引數全部傳遞給該方法作為引數
3.6引用構造器
引用構造器,其實就是引用構造方法
l格式
類名::new
範例
Student::new
練習描述
定義一個類(Student),裡面有兩個成員變數(name,age)
並提供無參構造方法和帶參構造方法,以及成員變數對應的get和set方法
定義一個介面(StudentBuilder),裡面定義一個抽象方法
Student build(String name,int age);
定義一個測試類(StudentDemo),在測試類中提供兩個方法
- 一個方法是:useStudentBuilder(StudentBuilder s)
- 一個方法是主方法,在主方法中呼叫useStudentBuilder方法
程式碼演示
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; } } public interface StudentBuilder { Student build(String name,int age); } public class StudentDemo { public static void main(String[] args) { //Lambda簡化寫法 useStudentBuilder((name,age) -> new Student(name,age)); //引用構造器 useStudentBuilder(Student::new); } private static void useStudentBuilder(StudentBuilder sb) { Student s = sb.build("林青霞", 30); System.out.println(s.getName() + "," + s.getAge()); } }
使用說明
Lambda表示式被構造器替代的時候,它的形式引數全部傳遞給構造器作為引數