主頁 > 後端開發 > Java學習筆記:高階語法

Java學習筆記:高階語法

2021-09-10 09:04:03 後端開發

文章目錄

        • 一、多執行緒介紹:
          • ①行程與執行緒:
          • ②并發原理:
          • ③執行緒狀態:
          • ④執行緒的創建方法:
          • ⑤執行緒相關方法及性質的介紹:
          • ⑥并發安全問題:
          • ⑦同步鎖:
          • ⑧互斥鎖:
        • 二、Collection 介紹:
          • ①List 和 Set:
          • ②集合的常見方法介紹:
          • ③集合中存放的是元素參考:
          • ④集合之間的操作:
          • ⑤迭代器:
          • ⑥增強回圈:
          • ⑦泛型:
        • 三、List 集合介紹:
          • ①List 基本介紹:
          • ②List 常用方法介紹:
          • ③集合和陣列之間的轉換:
          • ④集合的排序:
          • ⑤多載 sort 方法:
        • 四、Queue 佇列:
          • ①基本介紹:
          • ②常用方法以及代碼舉例:
          • ③雙端佇列:
          • ④堆疊的實作:
        • 五、集合并發安全問題:
          • ①集合并發安全的實作:
          • ②佇列并發安全的實作:
          • 未完待續……

一、多執行緒介紹:

①行程與執行緒:
  • 什么是行程?

    • 行程是作業系統中運行的一個任務(一個應用程式運行在一個行程中),
    • 行程(process)是一塊包含了某些資源的記憶體區域,作業系統利用行程把它的作業劃分為一些功能單元,
    • 行程所包含的一個或多個執行單元稱之為執行緒(thread),行程還擁有一個私有的虛擬地址空間,該空間能被它所包含的執行緒訪問,
    • 執行緒只能歸屬于一個行程并且它只能訪問該行程所擁有的資源,當作業系統創建一個行程之后,給行程會自動申請一個名為主執行緒或者首要執行緒的執行緒,
  • 什么是執行緒?

    • 一個執行緒是行程的一個順序執行流,
    • 同類的多個執行緒共享一塊記憶體地址和一組系統資源,執行緒本身有一個供程式執行時的堆疊,執行緒在切換時負荷小,因此,執行緒也被稱為輕負荷行程,一個行程中可以包含多個執行緒
  • 行程與執行緒之間的區別

    • 一個行程至少包含一個執行緒
    • 執行緒的劃分尺度小于行程,使得多執行緒程式的并發性高,另外,行程在執行程序中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大地提高了程式的運行效率,
    • 執行緒在執行程序中與行程的區別在于每個獨立的執行緒有一個程式運行的入口、順序執行序列和程式的出口,但是執行緒是不能獨立執行的,必須依存在應用程式中,由應用程式提供多個執行緒執行控制,
    • 從邏輯角度看,多執行緒的意義在于一個應用程式中,有多個執行部分可以同時執行,但是作業系統并沒有將多個執行緒看做多個獨立的應用來實作行程的調度和管理以及資源分配
  • 執行緒使用的場合

    • 執行緒通常用于在一個程式中需要同時完成多個任務的情況,我們可以將每一個任務定義為一個執行緒,使得他們可以一同作業,
    • 也可以用于單一執行緒中可以完成,但是使用多執行緒可以更快的情況;比如下載檔案,
②并發原理:
  • 多個執行緒“同時”運行知識我們感官上的一種表現,事實上執行緒是并發運行的,OS將時間劃分為很多時間片段(時間片),盡可能均勻分配給每一個執行緒,獲取時間片段的執行緒被CPU運行,而其他執行緒全部等待,所以微觀上是走走停停的,宏觀上都在運行,這種現象叫并發,但是不是絕對意義上的“同時發生”,
③執行緒狀態:

image-20210806114207132

④執行緒的創建方法:
  • 創建方式一

    • 繼承 Thread 并重寫 run 方法,run 方法中就是希望執行緒執行的邏輯,
    public class Test {
        public static void main(String[] args) {
            Thread1 thread1 = new Thread1();
            Thread2 thread2 = new Thread2();
            /*
            啟動執行緒要呼叫 start 方法,而不是直接呼叫 run 方法;當 start 方法呼叫完畢后,run 方法很快會被執行緒自行呼叫,
             */
            thread1.start();
            thread2.start();
        }
    }
    
    class Thread1 extends Thread{
        @Override
        public void run() {
            for (int i=0;i<100;i++){
                System.out.println("你是誰啊?");
            }
        }
    }
    
    class Thread2 extends Thread{
        @Override
        public void run() {
            for (int i=0;i<100;i++){
                System.out.println("我是你dad!");
            }
        }
    }
    //output:
    //你是誰啊?
    //你是誰啊?
    //我是你dad!
    //我是你dad!
    //我是你dad!
    //你是誰啊?
    //你是誰啊?
    // ……
    
    • 第一種創建執行緒的方式比較簡單直接,但是缺點主要有兩個:
      • 由于需要繼承執行緒,這導致不能再繼承其他類,實際開發中經常需要復用某個超類的功能,那么在繼承執行緒之后不能再繼承其他類會有很多不便,
      • 定義執行緒類的同時重寫了 run 方法,這會導致執行緒與執行緒要執行的任務有一個必然的耦合關系,不利于執行緒的重用,
  • 創建方式二

    • 實作 Runnable 介面,單獨定義執行緒任務,
    public class Test {
        public static void main(String[] args) {
            //實體化兩個任務
            Runnable r1 = new Runnable1();
            Runnable r2 = new Runnable2();
            //創建兩個執行緒并指派任務
            Thread t1 = new Thread(r1);
            Thread t2 = new Thread(r2);
    
            t1.start();
            t2.start();
        }
    }
    
    class Runnable1 implements Runnable{
        @Override
        public void run() {
            for (int i=0;i<100;i++){
                System.out.println("你是誰啊?");
            }
        }
    }
    
    class Runnable2 implements Runnable{
        @Override
        public void run() {
            for (int i=0;i<100;i++){
                System.out.println("我是你dad!");
            }
        }
    }
    
⑤執行緒相關方法及性質的介紹:
  • currentThread

    • 執行緒提供了一個靜態方法:

      static Thread currentThread()
      
      • 該方法用來獲取運行這個方法的執行緒,main 方法也是靠一個執行緒運行的,當 JVM 啟動后會自動創建一個執行緒來執行 main 方法,而這個執行緒的名字叫做“main”,我們稱其為主執行緒,
    • 樣例一:

      public class Test {
          public static void main(String[] args) {
              Thread thread = Thread.currentThread();
              System.out.println("運行main方法的執行緒為:"+thread);
          }
      }
      //output:
      //運行main方法的執行緒為:Thread[main,5,main]
      //第一個main指執行緒名,5指的是執行緒的優先級,第二個main指的是執行緒所在的組
      
    • 樣例二:

      public class Test {
          public static void main(String[] args) {
              Thread thread = Thread.currentThread();
              System.out.println("運行main方法的執行緒為:"+thread);
              Something();
          }
          public static void Something(){
              Thread thread = Thread.currentThread();
              System.out.println("運行Something方法的執行緒為:"+thread);
          }
      }
      //output:
      //運行main方法的執行緒為:Thread[main,5,main]
      //運行Something方法的執行緒為:Thread[main,5,main]
      //第一個main指執行緒名,5指的是執行緒的優先級,第二個main指的是執行緒所在的組
      
    • 樣例三:

      public class Test {
          public static void main(String[] args) {
              Thread thread = Thread.currentThread();
              System.out.println("運行main方法的執行緒為:"+thread);
              Something();
              Thread t = new Thread(){
                  @Override
                  public void run() {
                      Thread t = Thread.currentThread();
                      System.out.println("自定義執行緒"+t);
                      Something();
                  }
              };
              t.start();
          }
          public static void Something(){
              Thread thread = Thread.currentThread();
              System.out.println("運行Something方法的執行緒為:"+thread);
          }
      }
      //output:
      //運行main方法的執行緒為:Thread[main,5,main]
      //運行Something方法的執行緒為:Thread[main,5,main]
      //自定義執行緒Thread[Thread-0,5,main]
      //運行Something方法的執行緒為:Thread[Thread-0,5,main]
      
  • 執行緒自身資訊的獲取

    public class Test {
        public static void main(String[] args) {
            Thread thread = Thread.currentThread();
            //獲取執行緒的名字
            String name = thread.getName();
            System.out.println("name:"+name);
            //獲取執行緒的唯一標識(id)
            long id = thread.getId();
            System.out.println("ID:"+id);
            //獲取執行緒的優先級(1~10),默認值是5
            int priority = thread.getPriority();
            System.out.println("優先級:"+priority);
            //執行緒是否還處于活動狀態
            boolean isAlive = thread.isAlive();
            System.out.println("isAlive?"+isAlive);
            //執行緒是否被中斷
            boolean isInterrupted = thread.isInterrupted();
            System.out.println("是否被中斷?"+isInterrupted);
            //執行緒是否為守護執行緒
            boolean isDaemon = thread.isDaemon();
            System.out.println("是否為守護執行緒?"+isDaemon);
        }
    }
    //output:
    //name:main
    //ID:1
    //優先級:5
    //isAlive?true
    //是否被中斷?false
    //是否為守護執行緒?false
    
  • 執行緒優先級

    image-20210806110424271

    public class Test {
        public static void main(String[] args) {
            Thread max = new Thread(){
                @Override
                public void run() {
                    for (int i=0;i<100;i++)
                        System.out.println("max");
                }
            };
            Thread min = new Thread(){
                @Override
                public void run() {
                    for (int i=0;i<100;i++)
                        System.out.println("min");
                }
            };
            Thread normal = new Thread(){
                @Override
                public void run() {
                    for (int i=0;i<100;i++){
                        System.out.println("normal");
                    }
                }
            };
    
            max.setPriority(Thread.MAX_PRIORITY);
            min.setPriority(Thread.MIN_PRIORITY);
            max.start();
            min.start();
            normal.start();
        }
    }
    
  • sleep阻塞

    • 執行緒提供了一個靜態方法:

      static void sleep(long ms)
      
      • 使得運行這個方法的執行緒阻塞指定毫秒超時后該執行緒會自動回到 Runnable 狀態,等待再次并發運行
      public class Test {
          public static void main(String[] args) {
              System.out.println("程式開始了!");
              try {
                  Thread.sleep(5000);//卡5秒
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("程式結束了!");
          }
      }
      //output:
      //程式開始了!
      //(期間等了5秒)
      //程式結束了!
      
    • sleep 方法要求必須處理中斷例外,原因在于當一個執行緒呼叫了 sleep 方法處于阻塞狀態的程序中若被呼叫了它的 interrupt 方法中斷時,它就會在 sleep 方法中拋出中斷例外,此時并非是將這個執行緒直接中斷,而是中斷了它的阻塞狀態

      public class Test {
          public static void main(String[] args) {
              Thread thread_rest = new Thread(){
                  @Override
                  public void run() {
                      System.out.println("開始休息!");
                      try {
                          Thread.sleep(100000);
                      } catch (InterruptedException e) {
                          System.out.println("休息打斷!");
                      }
                      System.out.println("休息結束!");
                  }
              };
      
              Thread thread_interrupt = new Thread(){
                  @Override
                  public void run() {
                      System.out.println("鬧鐘開啟!");
                      for (int i=0;i<3;i++){
                          System.out.println("鬧鐘響了!");
                          try {
                              Thread.sleep(1000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                      System.out.println("鬧鐘結束!");
                      //中斷thread_rest執行緒
                      thread_rest.interrupt();
                  }
              };
              thread_rest.start();
              thread_interrupt.start();
          }
      }
      //output:
      //開始休息!
      //鬧鐘開啟!
      //鬧鐘響了!
      //鬧鐘響了!
      //鬧鐘響了!
      //鬧鐘結束!
      //休息打斷!
      //休息結束!
      
      • JDK8 之前,由于 JVM 記憶體分配問題,當一個方法的區域變數想被這個方法的其他內部類所使用的的時候這個變數必須是final,此時上文中的 thread_rest 物件的宣告需要改為:
      final Thread thread_rest = new Thread(){...}
      
  • 守護執行緒

    • 又稱為后臺執行緒,默認創建的執行緒都是普通執行緒或稱之為前臺執行緒,執行緒提供了一個方法:

      void setDaemon(boolean on)
      
      • 只有呼叫該方法并傳入引數 true 時,該執行緒才會設定為守護執行緒
    • 守護執行緒在使用上與普通執行緒沒有差別,但是在結束時機上有一個區別:行程結束時,所有正在運行的守護執行緒都會被強制停止,(行程結束:當一個行程中所有的普通執行緒都結束時行程既結束,)

      public class Test {
          public static void main(String[] args) {
              Thread thread_sos = new Thread(){
                  @Override
                  public void run() {
                      for (int i=0;i<3;i++){
                          System.out.println("Help!");
                          try {
                              Thread.sleep(1000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                      System.out.println("Death!");
                  }
              };
      
              Thread thread_help = new Thread(){
                  @Override
                  public void run() {
                      while (true){
                          System.out.println("I'm coming!");
                          try {
                              Thread.sleep(1000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              };
              thread_sos.start();
              //將thread_help設定為守護執行緒,必須在啟動前進行設定,
              thread_help.setDaemon(true);
              thread_help.start();
              System.out.println("main執行緒結束了!");
          }
      }
      //output:
      //main執行緒結束了!
      //I'm coming!
      //Help!
      //I'm coming!
      //Help!
      //I'm coming!
      //Help!
      //I'm coming!
      //Death!
      
      • 程序分析:上述程式中,實際有三個執行緒執行,main執行緒是第一個結束運行的,在 thread_sos 和 thread_help 執行緒執行完 start 方法之后就結束了 main 執行緒的任務,然后就是 thread_sos 執行緒和 thread_help 執行緒的執行,直到 thread_sos 結束運行;即使 thread_help 中有一個無限回圈,但是它是一個守護執行緒,所以 thread_sos 一結束運行, thread_help 也就結束了運行;此時整個行程就此結束,
  • join阻塞

    • 執行緒提供了一個方法:

      void join()
      
      • 該方法可以協調執行緒之間的同步運行
    • 同步與異步:

      • 同步運行——運行有順序,
      • 異步運行——運行代碼無順序,多執行緒并發運行就是異步運行,
      public class Test {
          //標識圖片是否下載完畢
          private  static boolean isFinish = false;
          public static void main(String[] args) {
              Thread download = new Thread(){
                  @Override
                  public void run() {
                      System.out.println("開始下載圖片……");
                      for (int i = 1;i<=100;i++){
                          System.out.println("down:"+i+"%");
                          try {
                              Thread.sleep(20);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                      System.out.println("down:下載圖片完畢!");
                      isFinish = true;
                  }
              };
      
              Thread show = new Thread(){
                  @Override
                  public void run() {
                      System.out.println("show:開始顯示圖片!");
                      //加載圖片之前應當先等待下載執行緒將圖片下載完畢!
                      try {
                          /*
                          show 執行緒在呼叫 download.join() 方法之后就進入了阻塞狀態,直到 download 執行緒的 run 方法執行完畢才會解除阻塞,
                           */
                          download.join();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      if (!isFinish){
                          throw new RuntimeException("加載圖片失敗!");
                      }
                      System.out.println("顯示圖片完畢!");
                  }
              };
              download.start();
              show.start();
          }
      }
      //output:
      //開始下載圖片……
      //show:開始顯示圖片!
      //down:1%
      //down:2%
      //……
      //down:99%
      //down:100%
      //down:下載圖片完畢!
      //顯示圖片完畢!
      
  • yield方法

    • Thread 的靜態方法 yield:

      static void yield()
      
      • 該方法用于使當前執行緒主動讓出當次 CPU 時間片回到 Runnable 狀態,等待分配時間片

      image-20210807110645237

⑥并發安全問題:
  • 問題的產生:

    • 多個執行緒并發操作同一資源時,由于執行緒切換實際的不確定性,會導致執行操作資源的代碼順序未按照設計順序執行,出現操作混亂的情況,嚴重時可能會導致系統癱瘓,
  • 問題的解決:

    • 將并發操作同一資源改為同步操作,即:有先后順序的操作,
  • 問題代碼舉例

    public class Test {
        public static void main(String[] args) {
            Table table = new Table();
            Thread thread = new Thread(){
                @Override
                public void run() {
                    while (true){
                        int bean = table.getBean();
                        System.out.println("執行緒"+getName()+"的豆:"+bean+"個!");
                    }
                }
            };
    
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    while (true){
                        int bean = table.getBean();
                        Thread.yield();
                        System.out.println("執行緒"+getName()+"的豆:"+bean+"個!");
                    }
                }
            };
            thread.start();
            thread1.start();
        }
    }
    
    class Table{
        private int beans = 10;
    
        public int getBean(){
            if (beans==0){
                throw new RuntimeException("沒有豆子!");
            }
            //模擬執行緒執行到這里就沒有時間了,
            Thread.yield();
            return beans--;
        }
    }
    //output:
    //程式有陷入死回圈的風險,因為兩個執行緒對于beans屬性的操作的任意操作,會導致執行緒在運行getBean函式時可能會跳過下面的if陳述句塊,譬如beans=1時,兩個執行緒都取了一次beans,此時beans變成了一個負值就停不下來了!
    //if (beans==0){
    //      ……
    //}
    
  • 解決方案

    • 一個方法被 synchronized 修飾之后,該方法稱為“同步方法”,即:多個執行緒不能同時在方法的內部運行,強制讓多個執行緒在執行同一個方法時變為同步操作就解決了并發安全問題,
    //其他部分的代碼一樣
    class Table{
        private int beans = 10;
    
        public synchronized int getBean(){
            if (beans==0){
                throw new RuntimeException("沒有豆子!");
            }
            //模擬執行緒執行到這里就沒有時間了,
            Thread.yield();
            return beans--;
        }
    }
    //其他部分的代碼一樣
    
⑦同步鎖:
  • 同步塊

    synchronized(同步監視物件){
    	需要同步運行的代碼片段
    }
    
    • 同步塊可以更加精準的控制需要同步運行的代碼片段;有效的縮小同步范圍可以保證并發安全的前提下提高代碼并發運行的效率
    • 使用同步塊控制多執行緒同步運行必須要求這些執行緒看到的同步監視器物件同一個
    public class Test {
        public static void main(String[] args) {
            Shop shop = new Shop();
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    shop.buy();
                }
            };
            Thread thread2 = new Thread(){
                @Override
                public void run() {
                    shop.buy();
                }
            };
            thread1.start();
            thread2.start();
        }
    }
    
    class Shop{
        public void buy(){
            try {
                Thread thread = Thread.currentThread();
                System.out.println(thread.getName()+":正在挑衣服……");
                Thread.sleep(1000);
    
                synchronized (this){//this表示Shop物件,也就是main方法中的shop物件,多個執行緒指代的是同一個物件,因此可以實作“同步運行”的效果, 
                    System.out.println(thread.getName()+":正在試衣服……");
                    Thread.sleep(1000);
                }//顧客不能同時進入試衣間試衣服
    
                System.out.println(thread.getName()+":結賬離開……");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //output:
    //Thread-1:正在挑衣服……
    //Thread-0:正在挑衣服……
    //Thread-0:正在試衣服……
    //Thread-1:正在試衣服……
    //Thread-0:結賬離開……
    //Thread-1:結賬離開……
    
  • 方法上使用 synchronized,那么同步監視器物件就是當前方法所屬物件,即:方法內部看到的 this ,

    //以“并發安全問題”中的代碼為例:
    public synchronized int getBean(){
    	if (beans==0){
            throw new RuntimeException("沒有豆子!");
        }
        //模擬執行緒執行到這里就沒有時間了,
        Thread.yield();
    	return beans--;
    }
    
  • 靜態方法使用 synchronized 修飾,那么該方法一定具有同步效果;靜態方法對應的同步監視器物件為當前類的類物件(CLass的實體),類物件是后面反射的知識點,

    public class Test {
        public static void main(String[] args) {
            Demo d1 = new Demo();
            Demo d2 = new Demo();
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    d1.Something();
    //                Demo.Something();
                }
            };
    
            Thread thread2 = new Thread(){
                @Override
                public void run() {
                    d2.Something();
    //                Demo.Something();
                }
            };
            thread1.start();
            thread2.start();
        }
    }
    
    class Demo{
        public synchronized static void Something(){
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+"正在運行Something方法!");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(thread.getName()+"運行Something方法完畢!");
        }
    }
    
    //output:
    //Thread-0正在運行Something方法!
    //Thread-0運行Something方法完畢!
    //Thread-1正在運行Something方法!
    //Thread-1運行Something方法完畢!
    
    • 靜態方法是屬于類的,不屬于某一個物件;使用 synchronized 修飾之后,哪怕是新建兩個物件來分別執行執行緒,也會實作同步的效果(順序執行),(具體知識和類物件有關,反射那塊會講解)
⑧互斥鎖:
  • 多個代碼片段被 synchronized 塊修飾之后,這些同步塊的同步監聽器物件又是同一個時,這些代碼片段就是互斥的,多個執行緒不能同時在這些方法中運行,

    public class Test {
        public static void main(String[] args) {
            Demo demo = new Demo();
            Thread thread1 = new Thread(){
                @Override
                public void run() {
                    demo.methodA();
                }
            };
            Thread thread2 = new Thread(){
                @Override
                public void run() {
                    demo.methodB();
                }
            };
            thread1.start();
            thread2.start();
        }
    }
    
    class Demo{
        public synchronized void methodA(){
            Thread thread = Thread.currentThread();
            try {
                System.out.println(thread.getName()+"正在運行A方法!");
                Thread.sleep(1000);
                System.out.println(thread.getName()+"運行A方法完畢!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public synchronized void methodB(){
            Thread thread = Thread.currentThread();
            try {
                System.out.println(thread.getName()+"正在運行B方法!");
                Thread.sleep(1000);
                System.out.println(thread.getName()+"運行B方法完畢!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //output:
    //Thread-0正在運行A方法!
    //Thread-0運行A方法完畢!
    //Thread-1正在運行B方法!
    //Thread-1運行B方法完畢!
    
    • methodA 方法和 methodB 方法首先都是被 synchronized 修飾的,然后這兩個片段的同步監聽器物件又是同一個,即 Demo 物件 demo;所以實作了互斥的效果,

二、Collection 介紹:

①List 和 Set:
  • 在實際開發中,需要將使用的物件存盤于特定的資料結構的容器中,JDK 提供了這樣的容器——集合(Collection),集合與陣列相似,可以保存一組元素,并且提供了操作集合元素的相關方法,使用便捷,
  • Collection 是一個介面,定義了集合的相關操作方法,它有兩個子介面:List(可重復集)和 Set(不可重復集),
    • List:可重復集合,并且有序;可以通過下標操作元素,
    • Set:不可重復元素,元素是否重復是依據元素自身 equal 比較進行判斷的,

image-20210811213001431

②集合的常見方法介紹:
  • 添加元素

    boolean add(E e)
    
    • 向當前集合中添加給定元素,當該元素成功添加則回傳 true,
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Test {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add("Hello");
            collection.add("World");
            collection.add("!");
            System.out.println(collection);
        }
    }
    //output:
    // [Hello, World, !]
    
  • 洗掉元素

    boolean remove(Object o)
    
    • 依靠元素的 equals 方法進行比較判定是否洗掉,
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class CollectionTest {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add(new Point(1,1));
            collection.add(new Point(2,2));
            collection.add(new Point(3,3));
            collection.add(new Point(4,4));
            System.out.println("之前的:"+collection);
    
            Point point = new Point(1,1);
            collection.remove(point);
            System.out.println("之后的:"+collection);
        }
    }
    //outputs:
    //之前的:[Point{x=1, y=1}, Point{x=2, y=2}, Point{x=3, y=3}, Point{x=4, y=4}]
    //之后的:[Point{x=2, y=2}, Point{x=3, y=3}, Point{x=4, y=4}]
    
  • 獲取集合的元素個數

    int size()
    
    • 回傳當前集合的元素個數,
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Test {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add("Hello");
            collection.add("World");
            collection.add("!");
            System.out.println(collection);
            int size = collection.size();
            System.out.println("集合的大小為:"+size);
        }
    }
    //output:
    //[Hello, World, !]
    //集合的大小為:3
    
  • 判斷集合是否為空

    boolean isEmpty()
    
    • 用于判斷集合是否為空集(不含有任何元素),
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Test {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add("Hello");
            collection.add("World");
            collection.add("!");
            System.out.println(collection);
            System.out.println("isEmpty?"+collection.isEmpty());
        }
    }
    //output:
    //[Hello, World, !]
    //isEmpty?false
    
  • 清空集合

    void clear()
    
    • 清空當前集合,
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Test {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add("Hello");
            collection.add("World");
            collection.add("!");
            System.out.println(collection);
            collection.clear();
            System.out.println(collection);
        }
    }
    //output:
    //[Hello, World, !]
    //[]
    
  • 判斷集合是否包含指定元素

    boolean contains(Object o)
    
    • 該方法的執行是通過待必要元素的 equals 方法來實作的,
    //測驗元素類
    public class Point {
        private int x;
        private int y;
    
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        @Override
        public String toString() {
            return "Point{" + "x=" + x + ", y=" + y + '}';
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Point point = (Point) o;
            return x == point.x && y == point.y;
        }
    }
    
    //主體程式:
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class ContainsTest {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add(new Point(1,1));
            collection.add(new Point(2,2));
            collection.add(new Point(3,3));
            collection.add(new Point(4,4));
            //集合的toString會呼叫每個元素自身的toString方法體現出來,
            System.out.println(collection);
            Point point = new Point(3,3);
            System.out.println("point為:"+point);
            //contains方法是依靠元素自身equals方法比較的結果判斷集合是否包含該元素,
            System.out.println("是否包含point?"+collection.contains(point));
        }
    }
    //output:
    //[Point{x=1, y=1}, Point{x=2, y=2}, Point{x=3, y=3}, Point{x=4, y=4}]
    //point為:Point{x=3, y=3}
    //是否包含point?true
    
    • 對于集合的元素是我們自身定義的類時,我們需要對其的 toString 方法和 equals 方法進行重寫;要不然 contains 方法執行時使用的是 object 類的 equals 方法;無法做到真正的“比較”,
③集合中存放的是元素參考:
  • 代碼舉例

    //測驗元素類:
    public class Point {
        private int x;
        private int y;
    
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        @Override
        public String toString() {
            return "Point{" + "x=" + x + ", y=" + y + '}';
        }
        
        public void setX(int x) {
            this.x = x;
        }
    }
    
    //主體程式:
    
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Test {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            Point point = new Point(1,2);
            collection.add(point);
            System.out.println("collection:"+collection);
            System.out.println("point:"+point);
    
            point.setX(2);
            System.out.println("collection:"+collection);
            System.out.println("point:"+point);
        }
    }
    //output:
    //collection:[Point{x=1, y=2}]
    //point:Point{x=1, y=2}
    //collection:[Point{x=2, y=2}]
    //point:Point{x=2, y=2}
    
    • 正因為集合中存放的是元素的參考,因此當我們對于物件 point 的相關屬性值進行修改時,集合中該元素的對應屬性值也發生了相應的改變,
  • 一個經典的代碼題目

    import java.util.ArrayList;
    import java.util.Collection;
    
    public class CollectionTest {
        public static void main(String[] args) {
            int data = 1;
            String string = "Hello";
            Point point = new Point(1,2);
            Collection collection = new ArrayList();
            collection.add(point);
            test(data,string,point,collection);
            System.out.println(data);
            System.out.println(string);
            System.out.println(point);
            System.out.println(collection);
        }
    
        public static void test(int data,String string,Point point,Collection collection){
            data = 2;
            string+=" World!";
            point.setX(3);
            point = new Point(5,6);
            collection.clear();
            collection.add(point);
            point.setY(7);
            collection = new ArrayList();
            collection.add(point);
        }
    }
    //output:
    //1
    //Hello
    //Point{x=3, y=2}
    //[Point{x=5, y=7}]
    
    • 代碼分析
      • 因為 int 型別資料 data 對于 test 函式傳遞的是值,相當于是一份備份,所以在函式體中對于 data 資料的操作對 main 函式中的 data 沒有影響,
      • 對于 String 型別的資料 string,對于 test 函式傳遞的是參考,但是因為在 test 函式體中對于該字串進行了拼接操作,所以 Java 會新建一個 String 物件存盤新的字串“Hello World!”,對于原來的字串“Hello”,因為有 main 函式中 string 的指向,所以該 String 物件的存盤空間也不會釋放,故而在 main 函式中輸出 string 字串的內容時還是原來的樣子,
      • 對于 Point 和 Collection 的物件,對于 test 函式傳遞的是參考,所以在 test 函式體中對于這兩個變數的相關操作會真正影響到相關的值;所以在 main 函式中的相關輸出才會發生改變,
④集合之間的操作:
  • 并集

    boolean addAll(Collection<? extends E> c)
    
    • 將給定集合中的所有元素添加到當前的集合中;取并集的兩個集合不一定為同一型別元素,
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class CollectionTest {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add("C");
            collection.add("C#");
            collection.add("C++");
            System.out.println("collection:"+collection);
    
            Collection collection1 = new HashSet();
            collection1.add("Php");
            collection1.add("Java");
            System.out.println("collection1:"+collection1);
    
            collection.addAll(collection1);
            System.out.println("并集:"+collection);
        }
    }
    //outputs:
    //collection:[C, C#, C++]
    //collection1:[Java, Php]
    //并集:[C, C#, C++, Java, Php]
    
  • 全包含

    boolean containsAll(Collection<?> c)
    
    • 判斷當前集合是否包含給定集合中的所有元素,
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class CollectionTest {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add("C");
            collection.add("C#");
            collection.add("C++");
            System.out.println("collection:"+collection);
    
            Collection collection2 = new ArrayList();
            collection2.add("C");
            collection2.add("C#");
            System.out.println("collection2:"+collection2);
            System.out.println("是否全包含:"+collection.containsAll(collection2));
        }
    }
    //outputs:
    //collection:[C, C#, C++]
    //collection2:[C, C#]
    //是否全包含:true
    
  • 刪交集

    boolean removeAll(Collection<?> c)
    
    • 洗掉當前集合與給定集合的共有元素,
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class CollectionTest {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add("C");
            collection.add("C#");
            collection.add("C++");
            System.out.println("collection:"+collection);
    
            Collection collection2 = new ArrayList();
            collection2.add("C");
            collection2.add("C#");
            collection2.add("Java");
            System.out.println("collection2:"+collection2);
            collection.removeAll(collection2);//洗掉交集
            System.out.println("collection:"+collection);
        }
    }
    //outputs:
    //collection:[C, C#, C++]
    //collection2:[C, C#, Java]
    //collection:[C++]
    
⑤迭代器:
  • 集合提供了統一的遍歷元素方式——迭代器模式,

    Iterator iterator()
    
    • 該方法可以獲取一個用來遍歷當前集合的迭代器實作類,通過它遍歷元素,
    java.util.Iterator//介面
    
    • 迭代器介面,規定了迭代器遍歷集合的相關操作;不同集合都實作了一個用于遍歷自身元素的迭代器實作類
  • 迭代器遍歷集合元素遵循的程序——問、取、刪,其中洗掉元素不是必要操作,

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    public class CollectionTest {
        public static void main(String[] args) {
            Collection collection = new ArrayList();
            collection.add("one");
            collection.add("#");
            collection.add("two");
            collection.add("#");
            collection.add("three");
            collection.add("#");
            collection.add("four");
            System.out.println("之前:"+collection);
    
            //獲取迭代器
            Iterator iterator = collection.iterator();
            /*
            boolean hasNext()
            判斷集合是否還有元素可以迭代,
             */
            while (iterator.hasNext()){//問
                /*
                E next()
                迭代取出集合的下一個元素,默認回傳值為Object型別,
                 */
                String string = (String)iterator.next();//取
                if ("#".equals(string)){
                    iterator.remove();//刪
                    //迭代器的remove方法無需傳遞引數,洗掉的就是next取出的元素
                }
                System.out.println(string);
            }
            System.out.println("之后:"+collection);
        }
    }
    //outputs:
    //之前:[one, #, two, #, three, #, four]
    //one
    //#
    //two
    //#
    //three
    //#
    //four
    //之后:[one, two, three, four]
    
⑥增強回圈:
  • JDK 5推出時,推出了一個新的特性:增強型 for 回圈,也稱為新回圈;新回圈不取代傳統 for 回圈的作業,它專門設計是用來遍歷集合或陣列的,

    import java.util.ArrayList;
    import java.util.Collection;
    
    public class CollectionTest {
        public static void main(String[] args) {
            String[] array = {"Hello"," ","World","!"};
            for (String string: array) {
                System.out.print(string);
            }
            System.out.println();
    
            Collection collection = new ArrayList();
            collection.add("Hello");
            collection.add(" ");
            collection.add("World");
            collection.add("!");
            for (Object object: collection) {
                String string = (String)object;
                System.out.print(string);
            }
        }
    }
    //outputs:
    //Hello World!
    //Hello World!
    
    • 新回圈的語法也是編譯器認可,而非虛擬機認可,編譯器會在編譯源代碼時將新回圈遍歷陣列改為傳統for回圈遍歷的方式
    • 新回圈遍歷集合會被編譯器改為使用迭代器遍歷;所以在遍歷的程序中是不能通過集合的方法增、刪元素的,所以新回圈專門設計是用來遍歷集合或陣列的,
⑦泛型:
  • 泛型是 JDK 5 推出的特性,也稱為引數化型別;它允許將一個類中屬性的型別,方法引數的型別以及方法回傳值型別等的定義權移交給使用者,這使得實際應用中使用這個類更加靈活便捷,

    //主體類:
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class CollectionTest {
        public static void main(String[] args) {
            Location<Integer>location = new Location<Integer>(1,2);
            int locationInt = location.getX();
            System.out.println("X:"+locationInt);
            System.out.println(location);
    
            Location<Double>location1 = new Location<Double>(1.2,2.4);
            double locationDouble = location1.getX();
            System.out.println("X:"+locationDouble);
            System.out.println(location1);
    
            Location<String>location2 = new Location<String>("Hello ","World!");
            String locationString = location2.getX();
            System.out.println("X:"+locationString);
            System.out.println(location2);
        }
    }
    //outputs:
    //X:1
    //Location{x=1, y=2}
    //X:1.2
    //Location{x=1.2, y=2.4}
    //X:Hello
    //Location{x=Hello , y=World!}
    
    //測驗類
    public class Location <E>{
        private E x;
        private E y;
    
        //構造方法:
        public Location(E x,E y){
            this.x = x;
            this.y = y;
        }
    
        @Override
        public String toString() {
            return "Location{" + "x=" + x + ", y=" + y + '}';
        }
    
        public void setX(E x) {
            this.x = x;
        }
    
        public E getX() {
            return x;
        }
    
        public E getY() {
            return y;
        }
    
        public void setY(E y) {
            this.y = y;
        }
    }
    
    • 泛型也可以指定多個:
    public class Location <E,T,……>{
    	//……
    }
    
  • 泛型是編譯器認可的,并非虛擬機;編譯器會將泛型改為 Object,所以泛型的實際型別就是 Object,在使用泛型時,編譯器會輔助做兩個操作:

    • 對泛型設定值時,編譯器會檢查該值的型別是否與泛型一致,不一致則編譯不通過,
    • 在獲取泛型值時,編譯器會添加向下造型的代碼,
    //Location類借用上面宣告的類
    
    public class CollectionTest {
        public static void main(String[] args) {
            Location<Integer>location = new Location<Integer>(2,2);
            //編譯器會檢查實際賦值是否符合泛型型別要求,不符合則編譯不通過,
            location.setX(1);
            /* 編譯器會在編譯時補全向下造型的代碼為:
             * int data = (Integer)location.getX();
             * 然后還會觸發自動拆箱,改為:
             * int data = ((Integer)location.getX()).intValue();
             */
            int data = location.getX();
            System.out.println("location:"+location);
            System.out.println("location_x:"+data);
    
            //泛型可以不指定,不指定這按照默認的Object看待(下面的location1),
            Location location1 = location;
            System.out.println("location1:"+location1);
            //因為型別是Object,所以setX函式接收的是Object型別,
            location1.setX("Hello");
            System.out.println("location1:"+location1);
            //再次以location1的角度獲取x
            data = (int) location1.getX();
            System.out.println("location1_x:"+data);//報錯!
        }
    }
    //outputs:
    //location:Location{x=1, y=2}
    //location_x:1
    //location1:Location{x=1, y=2}
    //location1:Location{x=Hello, y=2}
    //報錯……
    

image-20210821205832485

  • 泛型在集合中的應用——約束集合中的元素型別

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    public class CollectionTest {
        public static void main(String[] args) {
            Collection<String> stringCollection = new ArrayList<String>();
            //指定之后add方法只能傳入泛型要求的元素
            stringCollection.add("A");
            stringCollection.add("B");
            stringCollection.add("C");
            System.out.println(stringCollection);
            //新回圈可以直接用實際型別接收元素,
            for (String string:stringCollection){
                System.out.print(string);
            }
            System.out.println();
            //迭代器也支持泛型,指定的型別與集合的泛型一致即可,
            Iterator<String> stringIterator = stringCollection.iterator();
            while(stringIterator.hasNext()){
                String string  = stringIterator.next();
                System.out.print(string);
            }
        }
    }
    //outputs:
    //[A, B, C]
    //ABC
    //ABC
    

三、List 集合介紹:

①List 基本介紹:
  • List 介面是 Collection 的子介面,用于定義線性表資料結構可重復,并且有序,提供了一組可以通過下標操作元素的方法;可以將 List 理解為存放物件的陣列,只不過其元素個數可以動態的增加或者減少,

  • ArrayList 和 LinkedList

    • List 介面的兩個常見實作類為 ArrayList 和 LinkedList:
      • ArrayList 內部由動態陣列實作,更適合于隨機訪問,查詢性能更好,
      • LinkedList 內部由鏈表實作,更適合于插入或洗掉,增刪元素的性能更好,
    • 在對于性能要求不是很高時,通常使用的是 ArrayList,
②List 常用方法介紹:
  • 獲取元素_get 函式

    E get(int index);
    
    • 獲取給定下標對應的元素
    import java.util.ArrayList;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("one");
            list.add("two");
            list.add("three");
            list.add("four");
            System.out.println(list);
    
            String string = list.get(1);
            System.out.println(string);
            //List 可以使用普通的回圈進行遍歷
            for (int i=0;i<list.size();i++)
                System.out.print(list.get(i)+" ");
    
            System.out.println();
            //增強型 for 回圈
            for (String s : list) System.out.print(s + " ");
        }
    }
    //output:
    // [one, two, three, four]
    // two
    // one two three four 
    // one two three four 
    
  • 替換元素_set 函式

    E set(int index, E element);
    
    • 給定元素設定到指定位置,回傳值為原位置對應元素;所以 set 方法的意義就是替換元素操作
    import java.util.ArrayList;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("one");
            list.add("two");
            list.add("three");
            list.add("four");
    
            System.out.println(list);
            System.out.println("替換之前的元素值:"+list.set(0,"1"));
            System.out.println("替換之后的元素值:"+list.get(0));
            System.out.println(list);
        }
    }
    //output:
    //[one, two, three, four]
    //替換之前的元素值:one
    //替換之后的元素值:1
    //[1, two, three, four]
    
  • List 還提供了一對多載的 add,remove 方法

    void add(int index, E element);
    
    • 將給定元素插入到指定位置,
    import java.util.ArrayList;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("one");
            list.add("two");
            list.add("three");
            list.add("four");
            System.out.println(list);
            //[one, two, three, four]
            list.add(1,"2");
            System.out.println(list);
            //[one, 2, two, three, four]
        }
    }
    //output:
    //[one, two, three, four]
    //[one, 2, two, three, four]
    
    E remove(int index);
    
    • 洗掉并回傳給定位置對應的元素,
    package Collection;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("one");
            list.add("two");
            list.add("three");
            list.add("four");
            System.out.println(list);
            //[one, two, three, four]
            System.out.println(list.remove(2));
            System.out.println(list);
            //[one, two, four]
        }
    }
    //output:
    //[one, two, three, four]
    //three
    //[one, two, four]
    
  • 獲取子集操作_subList 函式

    List<E> subList(int fromIndex, int toIndex);
    
    • 獲取當前集合指定下標對應范圍內的元素,
    import java.util.ArrayList;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("one");
            list.add("two");
            list.add("three");
            list.add("four");
            list.add("five");
            System.out.println(list);
            List<String> list1 = list.subList(0,3);
            System.out.println(list1);
        }
    }
    //output:
    //[one, two, three, four, five]
    //[one, two, three]
    
    • 一個有趣的現象
    import java.util.ArrayList;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<Integer>();
            list.add(1);
            list.add(2);
            list.add(3);
            list.add(4);
            list.add(5);
            System.out.println("全集為:" + list);
    
            List<Integer> subList = list.subList(0,4);
            System.out.println("子集為:" + subList);
            //將子集中的元素擴大10倍
            for (int i=0;i<subList.size();i++){
                subList.set(i, subList.get(i)*10);
            }
            System.out.println("子集為:" + subList);
            System.out.println("全集為:" + list);
        }
    }
    //output:
    //全集為:[1, 2, 3, 4, 5]
    //子集為:[1, 2, 3, 4]
    //子集為:[10, 20, 30, 40]
    //全集為:[10, 20, 30, 40, 5]
    
    • 所以操作子集會對原集合造成同樣的操作影響
    • 一個簡單的應用
    import java.util.ArrayList;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<Integer>();
            list.add(1);
            list.add(2);
            list.add(3);
            list.add(4);
            list.add(5);
            System.out.println("全集為:" + list);
    
            List<Integer> subList = list.subList(0,3);
            System.out.println("子集為:" + subList);
            //洗掉子集中的元素,進而查看原集合中的效果
            subList.clear();
            System.out.println("洗掉子集——————");
            System.out.println("子集為:" + subList);
            System.out.println("全集為:" + list);
        }
    }
    //output:
    //全集為:[1, 2, 3, 4, 5]
    //子集為:[1, 2, 3]
    //洗掉子集——————
    //子集為:[]
    //全集為:[4, 5]
    
    • 因此可以利用對于子集的洗掉操作,快速清理原集合中的部分元素
③集合和陣列之間的轉換:
  • 集合轉為陣列

    • 集合提供了一個方法—— toArray,可以將當前集合轉換為一個陣列,

      Object[] toArray();//不常用
      
      • 該方法回傳的是一個 Object 型別的陣列,這就帶來一個麻煩,當你接收該陣列之后,使用該陣列中的元素時,每一次都要型別轉換一下才能用,太麻煩了!但是 jdk5 之前只有這一個方法可以使用,
    • 在泛型推出之后,在 toArray 方法中可以傳入一個陣列以表示其陣列型別:

      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.Collection;
      
      public class CollectionTest {
          public static void main(String[] args) {
              Collection<String> collection = new ArrayList<String>();
              collection.add("one");
              collection.add("two");
              collection.add("three");
              collection.add("four");
              System.out.println(collection);
      
              String[] array = collection.toArray(new String[collection.size()]);
              System.out.println(array.length);
              System.out.println(Arrays.toString(array));
          }
      }
      //outputs:
      //[one, two, three, four]
      //4
      //[one, two, three, four]
      
      • 對于 toArray 方法中傳入的陣列大小 X 有以下的講究:
        • X < c o l l e c t i o n . s i z e ( ) X<collection.size() X<collection.size()時, t o A r r a y toArray toArray方法將會自己新建一個陣列來存放在集合中的資料,
        • X = c o l l e c t i o n . s i z e ( ) X=collection.size() X=collection.size()時, t o A r r a y toArray toArray方法將會直接使用傳入的陣列來存放在集合中的資料,
        • X > c o l l e c t i o n . s i z e ( ) X>collection.size() X>collection.size()時, t o A r r a y toArray toArray方法將會直接使用傳入的陣列來存放在集合中的資料,多余的空間將會是 n u l l null null
  • 陣列轉為集合

    • 陣列只能轉換為 List,不能轉換為 Set;這是因為 List 是可重復集合,Set 是不可重復集合;陣列里面可能會出現重復的元素,所以陣列不能轉換為 Set 集合,

      public static <T> List<T> asList(T... a) {}
      
    • 代碼演示

      import java.util.Arrays;
      import java.util.List;
      
      public class CollectionTest {
          public static void main(String[] args) {
              String[] strings = {"one","two","three","four"};
              System.out.println(Arrays.toString(strings));
      
              List<String> list =  Arrays.asList(strings);
              System.out.println(list);
          }
      }
      //outputs:
      //[one, two, three, four]
      //[one, two, three, four]
      
    • 一個有趣的現象

      import java.util.Arrays;
      import java.util.List;
      
      public class CollectionTest {
          public static void main(String[] args) {
              String[] strings = {"one","two","three","four"};
              System.out.println("陣列為:" + Arrays.toString(strings));
      
              List<String> list =  Arrays.asList(strings);
              System.out.println("集合為:" + list);
              
              list.set(1,"2");
              System.out.println("改變之后………………");
              System.out.println("集合為:" + list);
              System.out.println("陣列為:" + Arrays.toString(strings));
          }
      }
      //outputs:
      //陣列為:[one, two, three, four]
      //集合為:[one, two, three, four]
      //改變之后………………
      //集合為:[one, 2, three, four]
      //陣列為:[one, 2, three, four]
      
      • 如果 List 集合是由陣列轉變而來的,那么對于集合中元素的操作,會同步影響到陣列中元素,但是這種操作僅僅限修改元素操作不允許進行增刪操作,因為陣列是定長的,所以進行增刪操作會拋出例外報錯,
    • 破解增刪操作限制的方案——其實就是新建一個集合,再把資料復制過去,

      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.List;
      
      public class CollectionTest {
          public static void main(String[] args) {
              String[] strings = {"one","two","three","four"};
              System.out.println("陣列為:" + Arrays.toString(strings));
      
              List<String> list =  Arrays.asList(strings);
              System.out.println("集合為:" + list);
              //原方案:
      //        List<String> list1 = new ArrayList<String>();
      //        list1.addAll(list);
              //代碼優化:
              //所有的集合都提供了一個引數為 Collection 的構造方法,
              // 作用是在創建當前集合的同時包含給定集合中的所有元素,
              List<String> list1 = new ArrayList<String>(list);
              list1.add("five");
              System.out.println("新集合為:" + list1);
          }
      }
      //outputs:
      //陣列為:[one, two, three, four]
      //集合為:[one, two, three, four]
      //新集合為:[one, two, three, four, five]
      
④集合的排序:
  • 關鍵就是借助集合工具類:

    java.util.Collections;
    
    • 其提供了一個靜態方法——sort,可以對 List 集合進行自然排序(從小到大),
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<Integer>list = new ArrayList<Integer>();
            //生成亂數
            for (int i=0;i<10;i++){
                list.add((int)(Math.random()*100));
            }
            System.out.println(list);
            Collections.sort(list);
            System.out.println(list);
        }
    }
    //output:
    //[68, 28, 52, 42, 44, 15, 56, 44, 76, 37]
    //[15, 28, 37, 42, 44, 44, 52, 56, 68, 76]
    
  • 對于 Java 中內置的類,我們可以直接呼叫 sort 方法進行排序,對于自定義的類就不能直接呼叫 sort 進行排序了!

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<Point> list = new ArrayList<Point>();
            list.add(new Point(1,2));
            list.add(new Point(2,3));
            list.add(new Point(3,4));
            System.out.println(list);
            Collections.sort(list);//編譯直接不通過
            System.out.println(list);
        }
    }
    
    //Point類的宣告:
    public class Point{
        private int x;
        private int y;
    
        Point(int x,int y){
            this.x = x;
            this.y = y;
        }
    
        @Override
        public String toString() {
            return "{" + x + "," + y + '}';
        }
    }
    
    • Collections 的 sort 方法排序的集合要求元素必須實作 Comparable 介面
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<Point> list = new ArrayList<Point>();
            list.add(new Point(1,2));
            list.add(new Point(6,3));
            list.add(new Point(1,5));
            list.add(new Point(3,4));
            System.out.println(list);
            Collections.sort(list);
            System.out.println(list);
        }
    }
    //output:
    //[{1,2}, {6,3}, {1,5}, {3,4}]
    //[{1,2}, {3,4}, {1,5}, {6,3}]
    
    
    //Point類的宣告:
    public class Point implements Comparable<Point>{
        private int x;
        private int y;
    
        Point(int x,int y){
            this.x = x;
            this.y = y;
        }
    
        @Override
        public String toString() {
            return "{" + x + "," + y + '}';
        }
        //當一個類實作了 Comparable 介面之后必須重寫方法——compareTo.
        //該方法的作用是比較當前物件 this 與方法的引數物件 o 之間的大小,
        @Override
        public int compareTo(Point o) {
            int thisLength = this.x*this.x+this.y*this.y;
            int oLength = o.x*o.x+o.y*o.y;
            return thisLength-oLength;
        }
    //    回傳值不關心具體的取值,只關心取值范圍:
    //    當回傳值>0:當前物件大于引數物件(this>o)
    //    當回傳值<0:當前物件小于引數物件(this<o)
    //    當回傳值=0:當前物件等于引數物件(this=o)
    }
    
⑤多載 sort 方法:
  • 問題的產生

    • 如果是排序自定義型別資料,強烈建議不使用這一種方法,因為這個 sort 方法對我們的代碼有侵入性(它要求自定義類必須必須實作 Comparable 介面,并重寫方法),
    • 由于java API 中很多類實作了 Comparable 介面,譬如:包裝類、String類等,那么在排序這些元素的集合時可以直接呼叫 sort 函式,
  • 問題的解決:在實際開發中推薦使用該方式進行排序,

    • Collection 提供了一個多載的 sort 方法,該方法除了傳入要排序的集合外,還要求再傳入一個比較器(Comparator),該比較器可以定義一種比較規則,這個多載的 sort 方法會用這個比較規則對于集合元素比較后進行排序
  • 代碼舉例

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    
    public class ListTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            list.add("你好");
            list.add("明天見");
            list.add("我");
            list.add("下次一定");
            System.out.println(list);
            Collections.sort(list,new Comparator<String>(){
                @Override
                public int compare(String o1, String o2) {
                    return o1.length()-o2.length();//按照字符的多少比較大小!
                }
            });
            //還可以進一步化簡為lambda運算式
    //        Collections.sort(list, (o1, o2) -> {
    //            return o1.length()-o2.length();//按照字符的多少比較大小!
    //        });
            System.out.println(list);
        }
    }
    //output:
    //[你好, 明天見, 我, 下次一定]
    //[我, 你好, 明天見, 下次一定]
    
    • 這個排序方法不要求集合元素必須實作 Comparable 介面,對此在排序自定義元素時不對我們的代碼產生額外侵入,由于可以自定義比較規則,對于像 String 這樣已經實作類比較方法的也可以按照我們制定的規則進行排序,

四、Queue 佇列:

①基本介紹:
import java.util.Queue;
  • Queue 介面繼承于 Collection 介面;佇列也可以保存一組元素,但是存取元素必須遵循先進先出模式,
  • 常用實作類: L i n k e d L i s t LinkedList LinkedList
②常用方法以及代碼舉例:
import java.util.LinkedList;
import java.util.Queue;

public class QueueTest {
    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<String>();
        queue.offer("Hello");//入隊操作,向佇列末尾追加元素,
        queue.offer("World");
        queue.offer("!");
        System.out.println(queue);
//        queue.add("World");//和offer方法一樣的效果,但是建議使用offer,更加規范

        String string = queue.poll();//出隊操作
        System.out.println(string);
        System.out.println(queue);

        string = queue.peek();//參考隊首元素,元素不出隊
        System.out.println(string);
        System.out.println(queue);

        //遍歷操作
        for (String str : queue) {
            System.out.println(str);
        }
        System.out.println("_________");
        //使用poll方法遍歷佇列
        while (queue.size()>0){
            System.out.println(queue.poll());
        }
        System.out.println(queue);
    }
}
//output:
//[Hello, World, !]
//Hello
//[World, !]
//World
//[World, !]
//World
//!
//_________
//World
//!
//[]
③雙端佇列:
import java.util.Deque;
  • 繼承于 Queue 介面,

  • 雙端佇列是指佇列的兩端都可以進行進隊出隊操作

  • 常用實作類: L i n k e d L i s t LinkedList LinkedList

  • 常用函式

    image-20210907162137512

  • 代碼舉例

    import java.util.Deque;
    import java.util.LinkedList;
    
    public class DequeTest {
        public static void main(String[] args) {
            Deque<String> deque = new LinkedList<String>();
            //放置元素:
            deque.offer("one");
            deque.offer("two");
            deque.offerFirst("three");
            deque.offerLast("four");
            System.out.println(deque);
            //取出元素:
            System.out.println(deque.pollLast());
            System.out.println(deque);
            System.out.println(deque.pollFirst());
            System.out.println(deque);
            //參考元素:
            System.out.println(deque.peek());
            System.out.println(deque.peekLast());
            System.out.println(deque.peekFirst());
            System.out.println(deque);
        }
    }
    //output:
    //[three, one, two, four]
    //four
    //[three, one, two]
    //three
    //[one, two]
    //one
    //two
    //one
    //[one, two]
    
④堆疊的實作:
  • 基本介紹
    • 堆疊也可用于保存一組元素,但是存取元素必須遵循先進后出原則,Deque 雙端佇列可以用于實作堆疊,并且為堆疊專門提供了兩個方法:push、pop,

image-20210907163726594

  • 代碼舉例

    import java.util.Deque;
    import java.util.LinkedList;
    
    public class DequeTest {
        public static void main(String[] args) {
            Deque<String> stack = new LinkedList<String>();
            //資料入堆疊
            stack.push("one");
            stack.push("two");
            stack.push("three");
            System.out.println(stack);
            //資料出堆疊
            System.out.println(stack.pop());
            System.out.println(stack);
        }
    }
    //output:
    //[three, two, one]
    //three
    //[two, one]
    

五、集合并發安全問題:

①集合并發安全的實作:
  • 集合有執行緒安全的實作,我們可以借助 Collection 將現有的集合轉換為一個執行緒安全的,
import java.util.*;

public class DequeTest {
    public static void main(String[] args) {
        //List中的常用實作類ArrayList和LinkedList都不是執行緒安全的,
        List<String> list = new ArrayList<String>();
        list.add("one");
        list.add("two");
        list.add("three");
        System.out.println(list);
        //將給定集合轉換為一個執行緒安全的,
        list = Collections.synchronizedList(list);//看一下源代碼發現是新建了一個執行緒安全的list
        System.out.println(list);

        //HashSet同樣也不是執行緒安全的
        Set<String> set = new HashSet<String>(list);
        set = Collections.synchronizedSet(set);
        System.out.println(set);
    }
}
//output:
//[one, two, three]
//[one, two, three]
  • 注意:即使轉換為了一個執行緒安全的集合,它也不和迭代器遍歷做互斥,所以對于迭代器遍歷操作要求執行緒安全的話,要自己進行維護
②佇列并發安全的實作:
  • BlockingQueue、BlockingDeque(阻塞佇列)是一個雙緩沖佇列,在多執行緒并發時,若需要使用佇列,我們可以使用 Queue,但是要解決一個問題就是同步,但是同步操作會降低并發對 Queue 操作的效率,

  • BlockingQueue 內部使用兩條佇列,可以允許兩個執行緒同時向佇列一個做存盤,一個做取出操作;在保證安全的同時提供了佇列的存取效率,

  • 繼承阻塞佇列實作的子類

    • ArrayBlockingQueue規定大小的 BlockingQueue,其建構式必須帶一個 int 引數來指明其大小,其所包含的物件是以 FIFO(先入先出)順序進行排序的,
    • LinkedBlockingQueue大小不定的 BlockingQueue,若其建構式帶一個規定大小的引數,則生成的 BlockingQueue 有大小的限制;若不帶大小引數,則所生成的 BlockingQueue 的大小是由 Integer.MAX_VALUE 來決定的;其所包含的物件是以 FIFO(先進先出)順序進行排序的,
    • PriorityBlockingQueue:類似于LinkedBlockingQueue,但是其所包含的物件的排序不是 FIFO,而是根據物件的自然順序或者是建構式的 Comparator 決定的順序
    • SynchronousQueue:特殊的 BlockingQueue,對其操作必須是放和取交替進行的;所以里面最多只有一個元素,
    • 代碼舉例:
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.TimeUnit;
    
    public class DequeTest {
        public static void main(String[] args) {
            BlockingQueue<String> queue = new LinkedBlockingQueue<String>();
    
            queue.offer("one");//塞不下時,直接拋出例外了,
    
            try {//對于阻塞之后的處理手段,等待50ms,再看是否塞得下!
                queue.offer("two",50, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(queue);
        }
    }
    //output:
    //[one, two]
    

  • 在學習的Java的程序中,越到后面知識體系愈發龐大,筆記也越來越難整理,這篇筆記時隔這么久的原因:
    • 開學課務繁雜,
    • 自己還要學其他的很多東西,所以學習時間比假期削減了很多,
    • 不好整理了,每一個知識點都可以擴展開來,所以這次的筆記不是很全面,難免有紕漏,希望大家在評論區多多留言指出錯誤o( ̄▽ ̄)ブ,

表情包

未完待續……

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/298940.html

標籤:java

上一篇:??思維導圖整理大廠面試高頻陣列10: 3種方法徹底解決中位數問題, 力扣4??

下一篇:畢業之后所有面試總結

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more