二、Java语言基础(下)

2.1 多线程

​ 关于多线程,首先需要理解三个基本概念:

  1. 什么是程序?

    程序是为完成特定任务、用某种语言编写的一组指令的集合。

  2. 什么是进程?

    进程是程序的一次执行过程。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

  3. 什么是线程?

    进程可进一步细化为线程,是一个程序内部的一条执行路径。线程是调度和执行的单位。

    每个线程拥独立的运行栈和程序计数器(pc);多个线程,共享同一个进程中的方法区、堆。

    线程切换的开销小,进程切换开销大,一个进程中可以有多个线程。

  4. 单核CPU与多核CPU的理解

    • 单核CPU:其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费。)但是因为CPU时间单元特别短,因此感觉不出来。
    • 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)。
    • 一个Java应用程序java.exe,其实至少三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
  5. 并行与并发的理解

    • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
    • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事

2.1.1 创建多线程的方式

  1. 继承Thread类

  2. 实现Runnable接口

  3. JDK5新增-实现Callable接口

    //1.创建一个实现Callable的实现类
    class NumThread implements Callable{//2.实现call方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {if(i % 2 == 0){System.out.println(i);sum += i;}}return sum;}
    }public class ThreadNew {public static void main(String[] args) {//3.创建Callable接口实现类的对象NumThread numThread = new NumThread();//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象FutureTask futureTask = new FutureTask(numThread);//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()new Thread(futureTask).start();try {//6.获取Callable中call方法的返回值//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。Object sum = futureTask.get();System.out.println("总和为:" + sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}
    

    相较于实现Runnable接口,实现Callable接口的方式更加强大,具体体现在:

    • call()可以返回值的。
    • call()可以抛出异常,被外面的操作捕获,获取异常的信息
    • Callable是支持泛型的
  4. JDK5新增-使用线程池

    class NumberThread implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
    }class NumberThread1 implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
    }public class ThreadPool {public static void main(String[] args) {//1. 提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//设置线程池的属性
    //        System.out.println(service.getClass());
    //        service1.setCorePoolSize(15);
    //        service1.setKeepAliveTime();//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());//适合适用于Runnableservice.execute(new NumberThread1());//适合适用于Runnable//        service.submit(Callable callable);//适合使用于Callable//3.关闭连接池service.shutdown();}}
    

    线程池的优点:

    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没任务时最多保持多长时间后会终止

2.1.2 Thread类中的常用的方法

  1. start():启动当前线程;调用当前线程的run()。

  2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。

  3. currentThread():静态方法,返回执行当前代码的线程。

  4. getName():获取当前线程的名字。

  5. setName():设置当前线程的名字。

  6. yield():释放当前cpu的执行权,不会释放锁。

  7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。

  8. stop():已过时。当执行此方法时,强制结束当前线程。

  9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。

  10. isAlive():判断当前线程是否存活。

  11. getPriority():获取线程的优先级。

  12. setPriority(int p):设置线程的优先级。

线程的优先级:

  • MAX_PRIORITY:10
  • MIN _PRIORITY:1
  • NORM_PRIORITY:5 -->默认优先级
  • 高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。

2.1.3 线程的生命周期

2.1.4 线程的同步机制

  1. 背景

    例子:创建个窗口卖票,总票数为100张.使用实现Runnable接口的方式

    • 问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题

    • 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。

    • 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

  2. Java解决方案:同步机制

    • 同步代码块

      synchronized(同步监视器){//需要被同步的代码
      }
      /*  说明:1.操作共享数据的代码,即为需要被同步的代码。  -->不能包含代码多了,也不能包含代码少了。
      *       2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
      *       3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
      *          要求:多个线程必须要共用同一把锁。
      *
      * 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。Runnable r = new MyRunnable();new Thread(r).start();new Thread(r).start();在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
      */
      
    • 同步方法

      如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明同步的。

      同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。

      非静态的同步方法,同步监视器是:this。

      静态的同步方法,同步监视器是:当前类本身。

    • Lock锁 — JDK5.0新增

      synchronized 与 Lock的异同?

      • 相同:二者都可以解决线程安全问题
      • 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器;Lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock())。

使用的优先顺序:

​ Lock —> 同步代码块(已经进入了方法体,分配了相应资源 ) —> 同步方法(在方法体之外)

线程安全的单例模式:

public class SingleInstance {private volatile static SingleInstance uniqueInstance = null;public static SingleInstance getInstance() {if (uniqueInstance == null) {synchronized (SingleInstance.class) {if (uniqueInstance == null) {uniqueInstance = new SingleInstance();}}}return uniqueInstance;}
}

避免出现死锁

死锁,即不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。如下:

public static void main(String[] args) {StringBuffer s1 = new StringBuffer();StringBuffer s2 = new StringBuffer();new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();new Thread(new Runnable() {@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}).start();
}

2.1.5 线程通信

wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。

notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

notifyAll():一旦执行此方法,就会唤醒所有被wait的线程

备注:

  1. wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

  2. wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常。

  3. wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。

  4. sleep() 和 wait()的异同?

    • 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。

    • 不同点:

      1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()

      2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中。

      3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

  5. yield 和 sleep 的异同

    1)yield, sleep 都能暂停当前线程,sleep 可以指定具体休眠的时间,而 yield 则依赖 CPU 的时间片划分。

    2)yield, sleep 两个在暂停过程中,如已经持有锁,则都不会释放锁资源。

    3)yield 不能被中断,而 sleep 则可以接受中断。

  6. 问题:yield()不会释放锁,只释放cpu的执行权,有啥意义,不是只有拿到锁的线程才能执行么?

    答:谁告诉你只有一把锁了???共用一把锁的其他线程不能执行,但是用别的锁的线程可以执行!

2.2 Java常用类

2.2.1 java.lang.String类的使用

  1. String声明为final的,不可被继承
  2. String实现了Serializable接口:表示字符串是支持序列化的。实现了Comparable接口:表示String可以比较大小。
  3. String内部定义了final char[] value用于存储字符串数据。
  4. 通过字面量的方式(区别于new给一个字符串赋值,此时的字符串值声明在字符串常量池中)。
  5. 字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。

String的不可变性

  1. 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
  2. 当对现的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
  3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

String类实例化方式

  1. 通过字面量定义的方式

    此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。

    String s1 = "javaEE";
    String s2 = "javaEE";
    
  2. 通过new + 构造器的方式

    通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。

    String s3 = new String("javaEE");
    String s4 = new String("javaEE");System.out.println(s1 == s2);//true
    System.out.println(s1 == s3);//false
    System.out.println(s1 == s4);//false
    System.out.println(s3 == s4);//false
    

问题:String s = new String("abc");方式创建对象,在内存中创建了几个对象?
两个;一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”。

字符串拼接方式赋值的对比

  1. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  2. 只要其中一个是变量,结果就在堆中。
  3. 如果拼接的结果调用intern()方法,返回值就在常量池中
String s1 = "javaEE";
String s2 = "hadoop";String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//falseString s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
****************************
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//falsefinal String s4 = "javaEE";//s4:常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true

JVM中字符串常量池存放位置

  • jdk 1.6 (jdk 6.0 ,java 6.0):方法区(永久区)
  • jdk 1.7:堆空间
  • jdk 1.8:方法区(元空间)

StringBuffer

初始化为一个长度为16的char[],扩容策略:2*原容量+2

2.2.2 时间API

2.2.2.1 获取系统当前时间

​ 想获取系统当前时间,System类中的静态方法currentTimeMillis()想必大家都不会陌生,它返回的是当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差,也被称为时间戳

  1. java.util.Date类与java.sql.Date类

关于这两个类,很多人可能不是特别清楚两者之间的区别与联系,接下来我们对此进行简单的梳理。

  • 区别:

​ 首先,从包名可以看出java.sql.Date类是与数据库相关的类,更准确地来说,它与数据库中的日期类型变量相对应。而java.util.Date类,就是我们平时使用的类了(事实上开发中也很少会用它,具体后面再说)。

​ 那么除了根据包名能判断出的这些外,两者之间有什么联系呢?

  • 联系:

​ 通过查阅jdk源码可知:java.sql.Date继承了java.util.Date类,也就是说,java.sql.Date类是java.util.Date类的子类。

  • 搞清楚了两者之间的区别和联系后,我们来回答两个问题:

    • 第一个问题:如何将java.sql.Date对象转换为java.util.Date对象呢?

      熟悉面向对象的读者肯定知道,子类对象可直接向上转型成父类对象(多态性)。据此,我们可以直接将java.sql.Date类的对象赋给java.util.Date对象,代码如下:

      java.util.Date date = new java.sql.Date(136513463156L); //向java.sql.Date的构造器中传入时间戳
      
    • 第二个问题:如何将java.util.Date对象转换为java.sql.Date对象呢?

      进行强制类型转换?当然不可行。事实上,如果我们将父类对象强制转化为子类对象,将会抛出Exception in thread "main" java.lang.ClassCastException: java.util.Date cannot be cast to java.sql.Date异常。那么就只能从两者之间的联系下手,代码如下:

      java.util.Date date = new java.util.Date();// 创建java.util.Date对象
      java.sql.Date sqlDate = new java.sql.Date(date.getTime());//getTime()方法会返回当前Date对象对应的毫秒数。(时间戳)
      

补充说明(了解即可):

  • java.text.SimpleDataFormat类:主要用来格式化日期时间。简单介绍一下用法:
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
String format = sdf.format(date);
System.out.println(format);//2019-02-18 11:48:27
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
//否则,抛异常
Date date1 = sdf1.parse("2020-02-18 11:48:27");
System.out.println(date1);
  • Calendar类:该类是日历类,也是一个抽象类。简单介绍一下用法:

    //1.创建Calendar类的对象有两种方式:
    //方式一:创建其子类(GregorianCalendar的对象)
    //方式二:调用其静态方法getInstance()
    Calendar calendar = Calendar.getInstance();//2.常用方法
    //get()
    System.out.println(calendar.get(Calendar.DAY_OF_MONTH));//获取本月内的第几天,从1开始
    System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//获取一年内的第几天,从1开始//set()
    //calendar可变性
    calendar.set(Calendar.DAY_OF_MONTH,22); // 设置日期为本月内的第22天
    System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); //输出22//add()
    calendar.add(Calendar.DAY_OF_MONTH,-3); // 设置日期为今天的日期-3天;
    System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); // 今天是11月8号,所以输出5//getTime():日历类---> Date
    Date date = calendar.getTime(); // 返回Date对象
    System.out.println(date);//输出Sun Nov 08 17:49:57 CST 2020//setTime():Date ---> 日历类
    Date date = new Date();
    calendar.setTime(date);
    System.out.println(calendar.get(Calendar.YEAR)); // 输出2020
    System.out.println(date.getYear()); // 输出120,此方法输出的是今年与1900年之差
    

    Calendar类本意是为了弥补了Date类的缺陷而生的,但是随着在开发中的使用,渐渐的暴露出它们的不足。比如糟糕的可变性、偏移性等等。下面,我们一起来看看jdk1.8之后,关于日期的处理。

2.2.2.2 jdk1.8引入的关于时间处理的API

回顾Java对日期时间的迭代:

  • 第一代:jdk 1.0 Date类
  • 第二代:jdk 1.1 Calendar类,一定程度上替换Date类
  • 第三代:jdk 1.8 提出了新的一套API

前两代存在的问题举例:

  1. 可变性:像日期和时间这样的类应该是不可变的。
  2. 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
  3. 格式化:格式化只对Date用,Calendar则不行。
  4. 此外,它们也不是线程安全的;不能处理闰秒等。

java 8 中新的日期时间API涉及到的包:

  1. 本地日期、本地时间、本地日期时间的使用:LocalDate / LocalTime / LocalDateTime
  • 简介

    ① 分别表示使用 ISO-8601日历系统的日期、时间、日期时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
    ② LocalDateTime相较于LocalDate、LocalTime,使用频率要高
    ③ 类似于Calendar

  • 常用方法:

  1. 时间点:Instant
  • 简介

    ① 时间线上的一个瞬时点。 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC开始的秒数。)
    ② 类似于 java.util.Date类

  • 常用方法:

  1. 日期时间格式化类:DateTimeFormatter
  • 简介

    ① 格式化或解析日期、时间
    ② 类似于SimpleDateFormat

  • 实例化方式:

    • 预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
    • 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
    • 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
  • 常用方法:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
    //格式化
    String str = formatter.format(LocalDateTime.now());
    System.out.println(str);//2020-11-08 06:48:13//解析
    TemporalAccessor accessor = formatter.parse("2020-11-08 06:48:13");
    System.out.println(accessor);
    //输出{SecondOfMinute=13, MinuteOfHour=48, MicroOfSecond=0, MilliOfSecond=0, NanoOfSecond=0, HourOfAmPm=6},ISO resolved to 2020-11-08
    
  1. 其他API
  • 带时区的日期时间:ZonedDateTime / ZoneId

    //获取“Asia/Tokyo”时区对应的时间
    LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
    System.out.println(localDateTime);//2020-11-08T19:57:38.479
    //now()/now(ZoneId id):获取当前时区/指定时区的ZonedDateTime对象
    ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
    System.out.println(zonedDateTime);//2020-11-08T19:58:45.084+09:00[Asia/Tokyo]
    
  • 时间间隔:Duration–用于计算两个“时间”间隔,以秒和纳秒为基准

    //设置起始日期时间,获取当前日期时间
    LocalDateTime localDateTime = LocalDateTime.of(2016, 11, 8, 19, 2, 00);
    LocalDateTime localDateTime1 = LocalDateTime.now();Duration duration = Duration.between(localDateTime, localDateTime1);//返回间隔对象
    System.out.println(duration.toDays()); //输出1461
    
  • 日期间隔:Period --用于计算两个“日期”间隔,以年、月、日衡量

    LocalDate localDate = LocalDate.now();
    LocalDate localDate1 = LocalDate.of(2030, 11, 8);Period period = Period.between(localDate, localDate1);
    System.out.println(period); //P10Y
    System.out.println(period.getYears()); // 10
    System.out.println(period.getMonths()); // 0
    System.out.println(period.getDays()); // 0
    
  • 日期时间校正器:TemporalAdjuster

    //获取当前日期的下一个周日是哪天?
    TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
    LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
    System.out.println(localDateTime); //2020-11-15T19:15:51.426
    //获取下一个工作日是哪天?今天是2020-11-08
    LocalDate localDate = LocalDate.now().with(new TemporalAdjuster(){@Overridepublic Temporal adjustInto(Temporal temporal) {LocalDate date = (LocalDate)temporal;if(date.getDayOfWeek().equals(DayOfWeek.FRIDAY)){return date.plusDays(3);//如果是周五,则+3}else if(date.getDayOfWeek().equals(DayOfWeek.SATURDAY)){return date.plusDays(2);//如果是周六,则+2}else{return date.plusDays(1);//其他情况,则+1}}
    });
    System.out.println("下一个工作日是:" + localDate); //下一个工作日是:2020-11-09
    

2.2.3 比较器

  1. 引子

​ 在比较Java对象时,正常情况下,我们只能使用==!=进行比较,而不能使用>或者<这样的运算符。

​ 那么问题来了,开发过程中我们经常要对多个对象排序,排序必然需要比较,那么如何实现呢?

​ 其实实现也很简单,使用Comparable或者Comparator两个接口中的一个即可。

  1. Comparable接口的使用

先来看一段代码:

public static void main(String[] args) {String[] arr = new String[] {"dd","aa","xx","cc"};Arrays.sort(arr);System.out.println(Arrays.toString(arr)); // 输出[aa, cc, dd, xx]
}

执行上面的代码,输出结果为:[aa, cc, dd, xx]

为什么呢?查阅String类的源码可知,String类实现了Comparable接口的compareTo(obj)方法。

在重写compareTo(obj)方法时,我们需要满足三个规则:

  1. 如果当前对象this大于形参对象obj,则返回正整数,
  2. 如果当前对象this小于形参对象obj,则返回负整数,
  3. 如果当前对象this等于形参对象obj,则返回零。

那么让我们自定义的类实现比较就很简单了,只需要按照上面三个规则实现Comparable接口中的compareTo(obj)方法,再调用Arrays.sort(arr)或者Collections.sort(list)即可。

那么什么情况下都能够以实现Comparable接口的方式来定义对象的比较方式吗?

当然不是!实际上,虽然Comparable接口可以解决一部分业务需求,但是当我们无法使某个类实现Comparable接口时(比如第三方Jar包中的类),我们通常会使用另一种方式—实现Comparator接口。

  1. Comparator接口的使用

与实现Comparable接口类似,使用Comparator接口同样需要我们实现一个方法—compare(obj1,obj2)。

在重写compare(Object o1,Object o2)方法时,我们同样需要满足三个规则

  1. 如果方法返回正整数,则表示o1大于o2;
  2. 如果返回0,表示相等;
  3. 返回负整数,表示o1小于o2。

示例代码如下:

public static void main(String[] args) {Comparator comparator = (o1, o2) -> {if (o1 instanceof Person && o2 instanceof Person) {Person preson1 = (Person) o1;Person preson2 = (Person) o1;return Integer.compare(preson1.getAge(),preson2.getAge());}throw new RuntimeException("输入的数据类型不一致");};Person p1 = new Person();p1.setAge(18);Person p2 = new Person();p2.setAge(20);Person[] people = new Person[]{p1, p2};Arrays.sort(people,comparator);System.out.println(Arrays.toString(people)); // 输出[Person{age=18}, Person{age=20}]
}

2.2.4 枚举类

  1. 使用

    //使用enum关键字枚举类
    enum Season1 {//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束SPRING("春天","春暖花开"),SUMMER("夏天","夏日炎炎"),AUTUMN("秋天","秋高气爽"),WINTER("冬天","冰天雪地");//2.声明Season对象的属性:private final修饰private final String seasonName;private final String seasonDesc;//2.私化类的构造器,并给对象属性赋值private Season1(String seasonName,String seasonDesc){this.seasonName = seasonName;this.seasonDesc = seasonDesc;}//4.其他诉求1:获取枚举类对象的属性public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}
    }
    
  2. 常用方法

    Season1 summer = Season1.SUMMER;
    //toString():返回枚举类对象的名称
    System.out.println(summer.toString());//System.out.println(Season1.class.getSuperclass());
    System.out.println("****************");
    //values():返回所的枚举类对象构成的数组
    Season1[] values = Season1.values();
    for(int i = 0;i < values.length;i++){System.out.println(values[i]);
    }
    System.out.println("****************");
    Thread.State[] values1 = Thread.State.values();
    for (int i = 0; i < values1.length; i++) {System.out.println(values1[i]);
    }//valueOf(String objName):返回枚举类中对象名是objName的对象。
    Season1 winter = Season1.valueOf("WINTER");
    //如果没objName的枚举类对象,则抛异常:IllegalArgumentException
    //        Season1 winter = Season1.valueOf("WINTER1");
    System.out.println(winter);
    
  3. 枚举类对象分别实现接口

    interface Info{void show();
    }//使用enum关键字枚举类
    enum Season1 implements Info{//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束SPRING("春天","春暖花开"){@Overridepublic void show() {System.out.println("春天在哪里?");}},SUMMER("夏天","夏日炎炎"){@Overridepublic void show() {System.out.println("宁夏");}},AUTUMN("秋天","秋高气爽"){@Overridepublic void show() {System.out.println("秋天不回来");}},WINTER("冬天","冰天雪地"){@Overridepublic void show() {System.out.println("大约在冬季");}};
    }
    

2.2.5 注解

注解是什么?

Java注解是附加在代码中的一些元信息 ,简单来说,注解就算一个辅助工具,可以让我们的代码更加简洁等。

为什么要学习注解

​ 首先,我们平时使用的框架,就是靠着注解才得以使我们开发更加方便。框架=注解+反射+设计模式

通过注解你可以将你的某个方法以注解的形式调用,就像Spring中的@Controller、@Component等注解一样。

首先说明一些概念:

  • 什么是元注解?

    元注解就是对现有注解进行说明的注解。

为此,我们接下来来了解一下JDK5.0提供的四个元注解:

  1. @Retention:指定所修饰的 Annotation 的生命周期。参数:

    • RetentionPolicy.SOURCE—注解只在源文件中有效

    • RetentionPolicy.CLASS—注解在class文件中有效,当运行Java程序时,该注解不会被保留。默认值。

    • RetentionPolicy.RUNTIME—运行时有效。程序可以通过反射来获取该注解。

  2. @Target:用于指定被修饰的 注解能用于修饰哪些程序元素

  3. @Documented:表示所修饰的注解在被javadoc解析时,保留下来。

  4. @Inherited:被它修饰的注解将具继承性。

怎么使用注解

了解了上面的概念后,我们可以来实现一个简单的注解,代码如下:

@Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取
@Target({ElementType.METHOD,ElementType.PARAMETER})  // 注解可用在方法和参数上。
@Inherited // 注解可继承
@Documented // javadoc解析时保留
@interface HelloAnnotation {String value() default "hello";
}@HelloAnnotation
public void setAge() {/*......*/
}

注解只是提供一个辅助信息,要跟反射结合在一起使用才可以实现某项功能!

补充知识:Java8中注解的新特性

  1. 可重复注解:(当需要在一个方法上使用多次注解时使用)

    ① 在HelloAnnotation上声明@Repeatable,成员值为HelloAnnotations.class

    ② HelloAnnotation的Target和Retention等元注解与HelloAnnotations相同。

    代码示例如下:

    @Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取
    @Target(ElementType.METHOD) // 注解可用在方法上
    @Inherited // 注解可继承
    @Repeatable(HelloAnnotations.class)//可重复
    @Documented // javadoc解析时保留
    @interface HelloAnnotation {String value() default "hello";
    }
    @Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取
    @Target(ElementType.METHOD) // 注解可用在方法上
    @Inherited // 注解可继承
    @Documented // javadoc解析时保留
    @interface HelloAnnotations {HelloAnnotation value()[];
    }//使用如下:
    @HelloAnnotation
    @HelloAnnotation
    public static void main(String[] args) {}
    
  2. 类型注解:
    ①ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中,如:泛型声明。

    示例代码如下:

    @Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取
    @Target({ElementType.TYPE_PARAMETER }) // 注解可用在方法上
    @interface HelloAnnotation {String value() default "hello";
    }class TestAnnotation<@HelloAnnotation T> {}
    

    ②ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

    示例代码如下:

    @Retention(RetentionPolicy.RUNTIME) // 注解在运行时有效,即可通过反射获取
    @Target({ElementType.TYPE_USE}) // 注解可用在方法上
    @interface HelloAnnotation {String value() default "hello";
    }public static void main(String[] args) throws @HelloAnnotation RuntimeException{@HelloAnnotation int i = 0;List<@HelloAnnotation String> list = null;
    }
    

2.3 Java集合

2.3.1 Collection接口

|----Collection接口:单列集合,用来存储一个一个的对象

​ |----List接口:存储有序的、可重复的数据。 -->“动态”数组

​ |----ArrayList、LinkedList、Vector

​ |----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”

​ |----HashSet、LinkedHashSet、TreeSet

向Collection接口的实现类的对象中添加对象obj时,要求obj所在类要重写equals()。

遍历Collection的两种方式:

  1. 使用迭代器Iterator
  2. foreach循环(或增强for循环)

使用集合的remove()方法时需要注意:

如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。因为“指针”还没有下移。

2.3.2 Collection子接口:List接口

List存储有序的、可重复的数据。

2.3.2.1 List接口常用实现类

|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储

|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储,线程不安全

|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储

2.3.2.2 源码分析

  1. ArrayList

    • jdk 7情况下

      ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementDatalist.add(123);//elementData[0] = new Integer(123);...list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
      

      扩容机制:扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

      建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)。

    • jdk8情况下

      ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
      

      扩容机制:与jdk 7 相同。

    • 总结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

  2. LinkedList

    LinkedList list = new LinkedList(); //内部声明了Node类型的first和last属性,默认值为null
    list.add(123);//将123封装到Node中,创建了Node对象。
    //Node:LinkedList的双向链表的一个节点
    private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}
    }
    
  3. Vector

    jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。

    扩容机制:默认扩容为原来的数组长度的2倍。

2.3.3 Collection子接口:Set接口

​ Set存储无序的、不可重复的元素。存储顺序是根据数据的哈希值确定的。

以HashSet为例,描述元素添加过程:

  1. 我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即索引位置);
  2. 判断数组此位置上是否已经元素:
    • 如果此位置上没其他元素,则元素a添加成功。
    • 如果此位置上其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
      • 如果hash值不相同,则元素a添加成功。
      • 如果hash值相同,进而需要调用元素a所在类的equals()方法:
        equals()返回true,元素a添加失败
        equals()返回false,则元素a添加成功。

​ 在此位置已有元素的情况下,元素添加成功:元素a 与已经存在指定索引位置上数据以链表的方式存储。

jdk 7 :元素a放到数组中,指向原来的元素。

jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下,如下图

Java基础入门(下)相关推荐

  1. 叮!您收到一份超值Java基础入门资料!

    摘要:Java语言有什么特点?如何最大效率的学习?深浅拷贝到底有何区别?阿里巴巴高级开发工程师为大家带来Java系统解读,带你掌握Java技术要领,突破重点难点,入门面向对象编程,以详细示例带领大家J ...

  2. java基础入门课后习题_《Java基础入门》课后习题及答案

    <Java基础入门>课后习题及答案Java基础入门,课后习题,答案 博学谷--让IT教学更简单,让IT学习更有效 <Java基础入门>课后习题 第1章Java开发入门 一.填空 ...

  3. 叮!您收到一份超值Java基础入门资料! 1

    Java语言有什么特点?如何最大效率的学习?深浅拷贝到底有何区别?阿里巴巴高级开发工程师为大家带来Java系统解读,带你掌握Java技术要领,突破重点难点,入门面向对象编程,以详细示例带领大家Java ...

  4. Qt学习之Qt基础入门(下)

    1. 前言 前两篇博客简单的阐述了一下Qt的入门用法,这篇博客继续跟着视频学习. Qt入门系列: Qt学习之C++基础 Qt学习之Qt安装 Qt学习之Qt基础入门(上) Qt学习之Qt基础入门(中) ...

  5. JAVA中整型常量的长度,Java基础入门篇(三)——Java常量、变量,

    Java基础入门篇(三)--Java常量.变量, 一.Java常量 (一)什么是常量 常量指的是在程序中固定不变的值,是不能改变的数据.在Java中,常量包括整型常量.浮点型常量.布尔常量.字符常量等 ...

  6. 大数据必学Java知识(一):Java基础入门语法和安装

    Java基础入门语法和安装 1. Java概述 1.1 Java语言背景介绍(了解) 1.2 Java语言跨平台原理(理解) 1.3 JRE和JDK(记忆) 1.4 JDK的下载和安装(应用) 2. ...

  7. Java基础入门:IDEA软件安装和软件初始化设置

    IDEA是一个专门针对Java的集成开发工具(IDE),由Java语言编写.所以,需要有JRE运行环境并配置好环境变量. 它可以极大地提升我们的开发效率.可以自动编译,检查错误.在公司中,使用的就是I ...

  8. Java基础入门--学习笔记

    Java基础入门教程 itheima–java基础小白教程 学习配套软件:eclipse 1.基础知识 (1)常用DOS命令:切换盘–>E:,cd–>进入文件,可多级,cd - --> ...

  9. Java基础入门(六)

    Java基础入门(六) 1 Debug调试 1.1 Debug概述 1.2 Debug操作流程 1.2.1 设置断点 1.2.2 运行加了断点的程序 1.2.3 Debugger窗口和Console窗 ...

  10. java基础入门了解

    java基础入门了解 Java发展简史 java语言的用处(数据存储,数据分析,数据处理) java被运用的程度 java语言的特点 java语言体系的结构 java语言运行机制 课后知识补充 Jav ...

最新文章

  1. DC-RC加固修补型砂浆
  2. 【转】从源码分析Handler的postDelayed为什么可以延时?
  3. 【Python】Python入门-字符串初相识
  4. Swift之数组去重(去除重复元素)
  5. 2021企业直播新观察——市场升温蕴藏机会,消费场景左右未来
  6. matlab simulink_简单五步实现 MATLAB/Simulink 锂电池建模
  7. 问题 D: 求圆的面积和周长 山东科技大学oj c 语言
  8. 从 Web1.0 到 3.0 你不知道的互联网的演进史!
  9. Tricks(二十二) —— zip(python) 的实现及使用
  10. int main():声明指定了两个以上的数据类型
  11. php写2048,原生js编写2048小游戏实例代码
  12. STM32F103_DMA控制器
  13. 移动客户端谈百度分享经验
  14. 【19C】logmnr参考
  15. Python 量化分析——基本面选股模型
  16. MarkDown高阶语法手册
  17. Mendix与JEECG对比
  18. 关于openfire支持视频聊天
  19. obs多推流地址_最热门直播工具OBS的下载和设置教程,值得一看
  20. 基于Tensorflow2.x低阶API搭建神经网络模型并训练及解决梯度爆炸与消失方法实践

热门文章

  1. Linux学习 分支(if-then fi, if then elif then fi, case in );;easc
  2. 物理信道重配置-异频切换
  3. LTE重选算法及相关参数
  4. OpenCV 单通道三通道理解
  5. 【Cycle-Interactive GAN:弱光图像增强】
  6. 大学解惑05 - 突然回忆起了自己临近毕业的那段时间
  7. HDU2677 DFS+模拟 Java版
  8. sync.Mutex 与 sync.WaitGroup 使用示例
  9. 让loadrunner走下神坛
  10. 绞尽脑汁,给代码取个好名字