Java学习笔记:高阶语法

文章目录

        • 一、多线程介绍:
          • ①进程与线程:
          • ②并发原理:
          • ③线程状态:
          • ④线程的创建方法:
          • ⑤线程相关方法及性质的介绍:
          • ⑥并发安全问题:
          • ⑦同步锁:
          • ⑧互斥锁:
        • 二、Collection 介绍:
          • ①List 和 Set:
          • ②集合的常见方法介绍:
          • ③集合中存放的是元素引用:
          • ④集合之间的操作:
          • ⑤迭代器:
          • ⑥增强循环:
          • ⑦泛型:
        • 三、List 集合介绍:
          • ①List 基本介绍:
          • ②List 常用方法介绍:
          • ③集合和数组之间的转换:
          • ④集合的排序:
          • ⑤重载 sort 方法:
        • 四、Queue 队列:
          • ①基本介绍:
          • ②常用方法以及代码举例:
          • ③双端队列:
          • ④栈的实现:
        • 五、集合并发安全问题:
          • ①集合并发安全的实现:
          • ②队列并发安全的实现:
          • 未完待续……

一、多线程介绍:

①进程与线程:
  • 什么是进程?

    • 进程是操作系统中运行的一个任务(一个应用程序运行在一个进程中)。
    • 进程(process)是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。
    • 进程所包含的一个或多个执行单元称之为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间能被它所包含的线程访问。
    • 线程只能归属于一个进程并且它只能访问该进程所拥有的资源。当操作系统创建一个进程之后,给进程会自动申请一个名为主线程或者首要线程的线程。
  • 什么是线程?

    • 一个线程是进程的一个顺序执行流。
    • 同类的多个线程共享一块内存地址和一组系统资源,线程本身有一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程,一个进程中可以包含多个线程
  • 进程与线程之间的区别

    • 一个进程至少包含一个线程
    • 线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
    • 线程在执行过程中与进程的区别在于每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程是不能独立执行的,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    • 从逻辑角度看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但是操作系统并没有将多个线程看做多个独立的应用来实现进程的调度和管理以及资源分配
  • 线程使用的场合

    • 线程通常用于在一个程序中需要同时完成多个任务的情况。我们可以将每一个任务定义为一个线程,使得他们可以一同工作。
    • 也可以用于单一线程中可以完成,但是使用多线程可以更快的情况;比如下载文件。
②并发原理:
  • 多个线程“同时”运行知识我们感官上的一种表现。事实上线程是并发运行的,OS将时间划分为很多时间片段(时间片),尽可能均匀分配给每一个线程,获取时间片段的线程被CPU运行,而其他线程全部等待。所以微观上是走走停停的,宏观上都在运行。这种现象叫并发,但是不是绝对意义上的“同时发生”。
③线程状态:

Java学习笔记:高阶语法_第1张图片

④线程的创建方法:
  • 创建方式一

    • 继承 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
    
  • 线程优先级

    Java学习笔记:高阶语法_第2张图片

    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 状态,等待分配时间片

      Java学习笔记:高阶语法_第3张图片

⑥并发安全问题:
  • 问题的产生:

    • 多个线程并发操作同一资源时,由于线程切换实际的不确定性,会导致执行操作资源的代码顺序未按照设计顺序执行,出现操作混乱的情况。严重时可能会导致系统瘫痪。
  • 问题的解决:

    • 将并发操作同一资源改为同步操作,即:有先后顺序的操作。
  • 问题代码举例

    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 比较进行判断的。

Java学习笔记:高阶语法_第4张图片

②集合的常见方法介绍:
  • 添加元素

    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}
    //报错……
    

Java学习笔记:高阶语法_第5张图片

  • 泛型在集合中的应用——约束集合中的元素类型

    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 ( ) XX<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 list1 = new ArrayList();
      //        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
    //    当返回值=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

  • 常用函数

    Java学习笔记:高阶语法_第6张图片

  • 代码举例

    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。

Java学习笔记:高阶语法_第7张图片

  • 代码举例

    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( ̄▽ ̄)ブ。

Java学习笔记:高阶语法_第8张图片

未完待续……

你可能感兴趣的:(#,Java学习笔记,java,学习笔记,多线程,集合,队列)