当前位置:   article > 正文

尚硅谷JavaSE笔记(二)_尚硅谷 java日程管理项目笔记

尚硅谷 java日程管理项目笔记

系列文章目录

尚硅谷JavaSE笔记(一)

尚硅谷JavaSE笔记(二)

尚硅谷JavaSE笔记(三)


文章目录

前言

 十、异常处理

1、异常的概述和异常体系结构

2、异常的处理方式

十一、多线程

1、多线程的创建方式

2、多线程的生命周期

线程生命周期

 线程安全问题

死锁

 jdk5新特性:Lock(锁)

线程间通信

线程创建方式三:实现 Callable 接口

线程池

十二、常用类

1、字符串相关类之不可变字符序列:String

 2、String常用方法

3、StringBuffer 与 StringBuilder 的理解

4、日期时间

5、新的日期时间API

6、Java比较器

十三、枚举类与注解

1、枚举类的使用

2、注解


前言

本笔记为B站尚硅谷Java入门视频教程(在线答疑+Java面试真题)的笔记,是笔者对于已经学过的大二Java课程的查缺补漏及巩固。部分C语言学过的基本语法将跳过。


 十、异常处理

1、异常的概述和异常体系结构

异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常)        


Java程序在执行过程中所发生的异常事件可分为两类:
>Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。

  1. public class ErrorTest {
  2. public static void main(String[] args) {
  3. //栈溢出;java.lang.StackOverflowError
  4. main(args);
  5. }
  6. }

  1. public class ErrorTest {
  2. public static void main(String[] args) {
  3. //栈溢出;java.lang.StackOverflowError
  4. //main(args);
  5. //堆溢出;java.lang.OutOfMemoryError: Java heap space
  6. Integer[] arr = new Integer[1024*1024*1024];
  7. }
  8. }


Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
√空指针访问
√试图读取不存在的文件
√网络连接中断
√数组角标越界

2、异常的处理方式

为什么采用异常处理机制?

在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制。

异常的处理:抓抛模型

过程一:"抛":程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。


过程二:"抓",可以理解为异常的处理方式:① try-catch-finally  ② throws

‘抓’异常处理方式:

方式一:try-catch-finally

try{

        //可能出现异常的代码

}catch(异常类型1 变量名1){

        //处理异常的方式1       

}catch(异常类型2 变量名2){

        //处理异常的方式2     

}

...

finally{

    //一定会执行的代码

}

说明:

1、finally不一定非得写(可选的)

2、使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配

3、一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前try-catch的结构(在没有写finally的情况下),继续执行其后的代码

4、catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
      catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错

5、常用的异常对象处理的方式:String getMessage()                 printstackTrace()

6、在try结构中声明的变量,在出了try结构以后,就不能再被调用(所以尽量定义在外面,赋初值)

  1. package error;
  2. import org.junit.Test;
  3. public class ExceptionTest {
  4. @Test
  5. public void Test1(){
  6. String str = "123";
  7. str = "abc";
  8. try {
  9. int num = Integer.parseInt(str);
  10. System.out.println("hello ...1");
  11. }catch (NumberFormatException e){
  12. //System.out.println("出现数值转换异常。。。");
  13. //System.out.println(e.getMessage());
  14. e.printStackTrace();
  15. }catch(NullPointerException e){
  16. System.out.println("出现空指针异常。。。");
  17. }catch (Exception e){
  18. System.out.println("出现异常,,,");
  19. }
  20. System.out.println("hello ...2");
  21. }
  22. }

 

 

 

try-catch-finally中finally的使用:
1.finally是可选的
2.finally中声明的是一定会被执行的代码。即使出现catch中又出现异常了,try中有return语句,catch中有return语句等情况。
3.像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。

方式二:throws+异常类型

1."throws +异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!

2.体会: try-catch-finally:真正的将异常给处理掉了。throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉。

3.开发中如何选择使用try-catch-finally 还是使用throws ?
    3.1 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用 throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
    3.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throw的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理

  1. public class ExceptionTest2 {
  2. public static void main(String[] args) {
  3. try{
  4. method2();
  5. }catch (ArrayIndexOutOfBoundsException e){
  6. e.printStackTrace();
  7. }
  8. finally {
  9. System.out.println("2222");
  10. }
  11. }
  12. public static void method2() throws ArrayIndexOutOfBoundsException{
  13. method1();
  14. }
  15. public static void method1() throws ArrayIndexOutOfBoundsException{
  16. int[] a = new int[10];
  17. System.out.println(a[10]);
  18. System.out.println("111111");
  19. }
  20. }

注:

方法重写的规则之一:
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型

关于异常对象的产生:

系统自动生成的异常对象
手动的生成一个异常对象,并抛出(throw)

  1. public class StudentTest {
  2. public static void main(String[] args) {
  3. try {
  4. Student s = new Student();
  5. s.regist(-1001);
  6. System.out.println(s);
  7. } catch (Exception e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. }
  12. class Student{
  13. private int id;
  14. public void regist(int id) throws Exception {
  15. if(id > 0){
  16. this.id = id;
  17. }else {
  18. //System.out.println("您输入的数据非法!");
  19. //手动抛出异常
  20. //throw new RuntimeException("您输入的数据非法!");
  21. throw new Exception("您输入的数据非法!");
  22. }
  23. }
  24. @Override
  25. public String toString() {
  26. return "Student{" +
  27. "id=" + id +
  28. '}';
  29. }
  30. }

如何自定义异常类?
1.继承于现有的异常结构:RuntimeException . Exception

2.提供全局常量: serialVersionUID

3.提供重载的构造器

  1. public class MyException extends RuntimeException{
  2. static final long serialVersionUID = -7034897190745766939L;
  3. public MyException() {
  4. }
  5. public MyException(String msg){
  6. super(msg);
  7. }
  8. }

十一、多线程

1、多线程的创建方式

方式一:继承与Thread类

1.创建一个继承于Thread类的子类

2.重写Thread类的run() -->将此线程执行的操作声明在run( )中

3.创建Thread类的子类的对象

4.通过此对象调用start()

  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. MyThread t1 = new MyThread();
  4. t1.start();
  5. for(int i = 0; i < 100; i++){
  6. System.out.println("this is main " + i);
  7. }
  8. }
  9. }
  10. class MyThread extends Thread{
  11. @Override
  12. public void run() {
  13. for(int i = 0; i < 100; i++){
  14. if(i % 2 == 0){
  15. System.out.println(i);
  16. }
  17. }
  18. }
  19. }

注意点:

一:我们不能通过直接调用run()的方式启动线程。

二:如果想再启动一个线程,不能用已经start()的线程去执行,需要重新创建一个线程对象

  1. package thread;
  2. public class ThreadTest1 {
  3. public static void main(String[] args) {
  4. Threado t1 = new Threado();
  5. Threadj t2 = new Threadj();
  6. t1.start();
  7. t2.start();
  8. }
  9. }
  10. class Threado extends Thread{
  11. @Override
  12. public void run() {
  13. for(int i = 0; i < 100; i++){
  14. if(i % 2 == 0){
  15. System.out.println(i);
  16. }
  17. }
  18. }
  19. }
  20. class Threadj extends Thread{
  21. @Override
  22. public void run() {
  23. for(int i = 0; i < 100; i++){
  24. if(i % 2 == 1){
  25. System.out.println(i);
  26. }
  27. }
  28. }
  29. }

创建Thread类的匿名子类对象:

  1. new Thread(){
  2. @Override
  3. public void run() {
  4. for(int i = 0; i < 100; i++){
  5. if(i % 2 == 0){
  6. System.out.println(i);
  7. }
  8. }
  9. }
  10. }.start();

 Thread中常用方法:

• public void run() :此线程要执行的任务在此处定义代码。

•public void start() :导致此线程开始执行; Java 虚拟机调用此线程的 run 方法。

•public String getName() :获取当前线程名称。

• public void setName(String name):设置该线程名称。

•public static Thread currentThread() :返回对当前正在执行的线程对象的引用。在

Thread 子类中就是 this,通常用于主线程和 Runnable 实现类

•public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时

停止执行)。

•public static void yield():yield 只是让当前线程暂停一下,让系统的线程调度器重新

调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这

个不能保证,完全有可能的情况是,当某个线程调用了 yield 方法暂停之后,线程调

度器又将其调度出来重新执行。

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

线程

每个线程都有一定的优先级,同优先级线程组成先进先出队列(先到先服务),使用分时调度策略。优先级高的线程采用抢占式策略,获得较多的执行 机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。

• Thread 类的三个优先级常量:

– MAX_PRIORITY(10):最高优先级

– MIN _PRIORITY (1):最低优先级

– NORM_PRIORITY (5):普通优先级,默认情况下 main 线程具有普通优先级

• public final int getPriority() :返回线程优先级

• public final void setPriority(int newPriority) :改变线程的优先级,范围在[1,10]之间。

System.out.println(Thread.currentThread().getName() + " : " + Thread.currentThread().getPriority() )

方式二:实现 Runnable 接口

Java 有单继承的限制,当我们无法继承 Thread 类时,那么该如何做呢?在核心类库中提供了 Runnable 接口,我们可以实现 Runnable 接口,重写 run()方法, 然后再通过 Thread 类的对象代理启动和执行我们的线程体 run()方法。

实现步骤:

1.创建一个实现了Runnable接口的类

2.实现类去实现Runnable中的抽象方法: run( )

3.创建实现类的对象

4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

5.通过Thread类的对象调用start()

  1. public class RunnableTest {
  2. public static void main(String[] args) {
  3. MThread mThread = new MThread();
  4. Thread t1 = new Thread(mThread);
  5. t1.start();
  6. }
  7. }
  8. class MThread implements Runnable{
  9. @Override
  10. public void run() {
  11. for(int i = 0; i < 100; i++){
  12. if(i % 2 == 0){
  13. System.out.println(Thread.currentThread().getName() + " : " + Thread.currentThread().getPriority() + " : " + i);
  14. }
  15. }
  16. }
  17. }

如果要创建第二个线程,可共用同一个MThread mThread = new MThread(); 只需再创建一个

Thread t2 = new Thread(mThread);

比较创建线程的两种方式:
开发中:优先选择:实现Runnable接口的方式

原因:

1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况。

2、多线程的生命周期

线程生命周期

 线程安全问题

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

  1. public class WindowTest {
  2. public static void main(String[] args) {
  3. Window w1 = new Window();
  4. Thread t1 = new Thread(w1);
  5. Thread t2 = new Thread(w1);
  6. Thread t3 = new Thread(w1);
  7. t1.start();
  8. t2.start();
  9. t3.start();
  10. }
  11. }
  12. class Window implements Runnable{
  13. private int ticket = 100;
  14. @Override
  15. public void run() {
  16. while (true){
  17. if(ticket > 0){
  18. try {
  19. Thread.sleep(100);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. System.out.println(Thread.currentThread().getName() + ":买票,票根为:" + ticket);
  24. ticket--;
  25. }
  26. else {
  27. break;
  28. }
  29. }
  30. }
  31. }

1.问题:   卖票过程中,出现了重票、错票-->出现了线程的安全问题
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票

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

4.在Java中,我们通过同步机制,来解决线程的安全问题。
 

方式一:同步代码块
        synchronized(同步监视器){
                //需要被同步的代码
                }
说明:

1、操作共享数据的代码,即为需要被同步的代码

2、共享数据:多个线程共同操作的变量,比如ticket就是共享数据

3、同步监视器,俗称:锁。任何一个类的对象都可以充当锁
    要求:多个线程必须要共用同一把锁

  1. package thread;
  2. public class WindowTest {
  3. public static void main(String[] args) {
  4. Window w1 = new Window();
  5. Thread t1 = new Thread(w1);
  6. Thread t2 = new Thread(w1);
  7. Thread t3 = new Thread(w1);
  8. t1.start();
  9. t2.start();
  10. t3.start();
  11. }
  12. }
  13. class Window implements Runnable{
  14. private int ticket = 100;
  15. Object obj = new Object();
  16. @Override
  17. public void run() {
  18. while (true){
  19. synchronized(obj){
  20. if(ticket > 0){
  21. try {
  22. Thread.sleep(100);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. System.out.println(Thread.currentThread().getName() + ":买票,票根为:" + ticket);
  27. ticket--;
  28. }
  29. else {
  30. break;
  31. }
  32. }
  33. }
  34. }
  35. }

也可:

synchronized(this)

方式二:同步方法

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

同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。

        public synchronized void method(){

                可能会产生线程安全问题的代码

        }

改进懒汉式线程安全问题

懒汉式:延迟创建对象,第一次调用 getInstance 方法再创建对象

  1. public class LazyOne {
  2. private static LazyOne instance;
  3. private LazyOne(){}
  4. //方式 1:
  5. public static synchronized LazyOne getInstance1(){
  6. if(instance == null){
  7. instance = new LazyOne();
  8. }
  9. return instance;
  10. }
  11. //方式 2:
  12. public static LazyOne getInstance2(){
  13. synchronized(LazyOne.class) {
  14. if (instance == null) {
  15. instance = new LazyOne();
  16. }
  17. return instance;
  18. }
  19. }
  20. //方式 3:
  21. public static LazyOne getInstance3(){
  22. if(instance == null){
  23. synchronized (LazyOne.class) {
  24. try {
  25. Thread.sleep(10);//加这个代码,暴露问题
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. if(instance == null){
  30. instance = new LazyOne();
  31. }
  32. }
  33. }
  34. return instance;
  35. }
  36. }

注意:上述方式 3 中,有指令重排问题

死锁

1.死锁的理解:

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

2.说明:
1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

2)我们使用同步时,要避免出现死锁。

 jdk5新特性:Lock(锁)

JDK5.0 的新增功能,保证线程的安全。与采用 synchronized 相比,Lock 可提供多种锁方案,更灵活、更强大。Lock 通过显式定义同步锁对象来实现同步。同步锁使用 Lock 对象充当。

举例:

  1. import java.util.concurrent.locks.ReentrantLock;
  2. public class LockTest {
  3. public static void main(String[] args) {
  4. Window1 w1 = new Window1();
  5. Thread t1 = new Thread(w1);
  6. Thread t2 = new Thread(w1);
  7. Thread t3 = new Thread(w1);
  8. t1.setName("窗口1");
  9. t2.setName("窗口2");
  10. t3.setName("窗口3");
  11. t1.start();
  12. t2.start();
  13. t3.start();
  14. }
  15. }
  16. class Window1 implements Runnable{
  17. private int ticket = 100;
  18. //1.实例化ReentrantLock
  19. private ReentrantLock lock = new ReentrantLock();
  20. @Override
  21. public void run() {
  22. while (true){
  23. try{
  24. //2.调用锁定方法:lock()
  25. lock.lock();
  26. if (ticket > 0) {
  27. try {
  28. Thread.sleep(100);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. System.out.println(Thread.currentThread().getName() + ":买票,票根为:" + ticket);
  33. ticket--;
  34. } else {
  35. break;
  36. }
  37. }finally {
  38. //3.调用解锁方法:unlock()
  39. lock.unlock();
  40. }
  41. }
  42. }
  43. }

synchronized 与 Lock 的对比

1. Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized 是隐式锁,出了

作用域、遇到异常等自动解锁

2. Lock 只有代码块锁,synchronized 有代码块锁和方法锁

3. 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性

(提供更多的子类),更体现面向对象。

4.(了解)Lock 锁可以对读不加锁,对写加锁,synchronized 不可以

5.(了解)Lock 锁可以有多种获取锁的方式,可以从 sleep 的线程中抢到锁,

synchronized 不可以

开发建议中处理线程安全问题优先使用顺序为:Lock ----> 同步代码块 ----> 同步方法

线程间通信

为什么要处理线程间通信:

当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那 么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同 操作一份数据

等待唤醒机制

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race,比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。

在一个线程满足某个条件时,就进入等待状态(wait() / wait(time)),等待其他线程执行完他们的指定代码过后再将其唤醒(notify());

或可以指定 wait 的时间,等时间到了自动唤醒;

在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制

涉及到的三个方法:
wal():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait就唤醒优先级高的那个
notifyALL():一旦执行此方法,就会唤醒所有被wait的线程。

举例:使用两个线程打印 1-100。线程 1, 线程 2 交替打印

  1. public class Communication_test {
  2. public static void main(String[] args) {
  3. Communication c1 = new Communication();
  4. Thread t1 = new Thread(c1);
  5. Thread t2 = new Thread(c1);
  6. t1.setName("线程1");
  7. t2.setName("线程2");
  8. t1.start();
  9. t2.start();
  10. }
  11. }
  12. class Communication implements Runnable{
  13. private int number = 1;
  14. @Override
  15. public void run() {
  16. while (true){
  17. synchronized (this){
  18. notify();
  19. if(number <= 100){
  20. System.out.println(Thread.currentThread().getName() + ":" + number);
  21. number++;
  22. try {
  23. //使得调用如下wait()方法的线程进入阻塞状态
  24. wait();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. }else {
  29. break;
  30. }
  31. }
  32. }
  33. }
  34. }

说明:

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

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

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

sleep()和 wait()的异同?
1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
2.不同点:

1)两个方法声明的位置不同: Thread类中声明sleep() , object类中声明wait()
2)调用的要求不同: sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中

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

线程创建方式三:实现 Callable 接口

JDK5.0 新增线程创建方式:新增方式一:实现 Callable 接口

•与使用 Runnable 相比, Callable 功能更强大些

        –相比 run()方法,可以有返回值

        –方法可以抛出异常

        –支持泛型的返回值(需要借助 FutureTask 类,获取返回结果)

•Future 接口

        –可以对具体 Runnable、Callable 任务的执行结果进行取消、查询是否完 成、获取结果等。

        –FutureTask 是 Futrue 接口的唯一的实现类

        –FutureTask 同时实现了 Runnable, Future 接口。它既可以作为 Runnable 被 线程执行,又可以作为 Future 得到 Callable 的返回值

•缺点:在获取分线程执行结果的时候,当前线程(或是主线程)受阻塞,效率较低。

举例:计算一百以内的偶数和

  1. import java.util.concurrent.Callable;
  2. import java.util.concurrent.ExecutionException;
  3. import java.util.concurrent.FutureTask;
  4. //1.创建一个实现 Callable 的实现类
  5. class NumThread implements Callable{
  6. //2.实现 call 方法,将此线程需要执行的操作声明在 call()中
  7. @Override
  8. public Object call() throws Exception {
  9. int sum = 0;
  10. for(int i = 0; i <= 100; i++){
  11. if(i % 2 == 0){
  12. System.out.println(i);
  13. sum += i;
  14. }
  15. }
  16. return sum;
  17. }
  18. }
  19. public class CallableTest {
  20. public static void main(String[] args) {
  21. //3.创建 Callable 接口实现类的对象
  22. NumThread numThread = new NumThread();
  23. //4.将此 Callable 接口实现类的对象传递到 FutureTask 构造器中,创建 FutureTask 的对象
  24. FutureTask futureTask = new FutureTask(numThread);
  25. //5.将 FutureTask 的对象作为参数传递到 Thread 类的构造器中,创建 Thread 对象,并调用 start()
  26. new Thread(futureTask).start();
  27. try {
  28. //6.获取 Callable 中 call 方法的返回值
  29. //get()返回值即为 FutureTask 构造器参数 Callable 实现类重写的 call()的返回值
  30. Object sum = futureTask.get();
  31. System.out.println("总和为:" +sum);
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. } catch (ExecutionException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }

线程池

现有问题:

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线 程需要时间。

那么有没有一种办法使得线程可以复用,即执行完一个任务,并不被销毁,而 是可以继续执行其他的任务?

思路:

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具

好处:

•提高响应速度(减少了创建新线程的时间)

•降低资源消耗(重复利用线程池中线程,不需要每次都创建)

•便于线程管理

        –corePoolSize:核心池的大小

        –maximumPoolSize:最大线程数

        –keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池相关 API

•JDK5.0 之前,我们必须手动自定义线程池。从 JDK5.0 开始,Java 内置线程池相关的 API。在 java.util.concurrent 包下提供了线程池相关 API:ExecutorService Executors

ExecutorService:真正的线程池接口。常见子类 ThreadPoolExecutor

        –void execute(Runnable command) :执行任务/命令,没有返回值, 一般用来执行 Runnable

        –<T> Future<T> submit(Callable<T> task):执行任务,有返回 值,一般又来执行 Callable

        –void shutdown() :关闭连接池

Executors:一个线程池的工厂类,通过此类的静态工厂方法可以创建多种类型的线程池对象。

        –Executors.newCachedThreadPool():创建一个可根据需要创建新线 程的线程池

        – Executors.newFixedThreadPool(int nThreads); 创建一个可重用 固定线程数的线程池

        –Executors.newSingleThreadExecutor() :创建一个只有一个线程的 线程池

        –Executors.newScheduledThreadPool(int corePoolSize):创建 一个线程池,它可安排在给定延迟后运行命令或者定期地执行

代码举例:

  1. import java.util.concurrent.*;
  2. class NumberThread implements Runnable{
  3. @Override
  4. public void run() {
  5. for(int i = 0; i <+100; i++){
  6. if(i % 2 == 0){
  7. System.out.println(Thread.currentThread().getName() + ": " + i);
  8. }
  9. }
  10. }
  11. }
  12. class NumberThread1 implements Runnable{
  13. @Override
  14. public void run() {
  15. for(int i = 0; i <+100; i++){
  16. if(i % 2 != 0){
  17. System.out.println(Thread.currentThread().getName() + ": " + i);
  18. }
  19. }
  20. }
  21. }
  22. class NumberThread2 implements Callable{
  23. @Override
  24. public Object call() throws Exception {
  25. int sum = 0; //记录偶数的和
  26. for(int i = 0; i <+100; i++){
  27. if(i % 2 != 0){
  28. sum += i;
  29. }
  30. }
  31. return sum;
  32. }
  33. }
  34. public class PoolTest {
  35. public static void main(String[] args) {
  36. //1.提供指定线程数量的线程池
  37. ExecutorService service = Executors.newFixedThreadPool(10);
  38. ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
  39. //设置线程池的属性
  40. service1.setMaximumPoolSize(50);//设置线程池中线程数的上限
  41. //2.执行指定的线程的操作。需提供实现Runnable接口或Callable接口实现类的对象
  42. service.execute(new NumberThread());//适合使用于 Runnable
  43. service.execute(new NumberThread1());//适合使用于 Runnable
  44. try {
  45. Future future = service.submit(new NumberThread2());//适合使用于 Callable
  46. System.out.println("总和为:" + future.get());
  47. } catch (Exception e) {
  48. e.printStackTrace();
  49. }
  50. //3.关闭线程池
  51. service.shutdown();
  52. }
  53. }

十二、常用类

1、字符串相关类之不可变字符序列:String

String 的特性

java.lang.String 类代表字符串。Java 程序中所有的字符串文字(例如 "hello" )都可以看作是实现此类的实例。

•字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。

•字符串 String 类型本身是 final 声明的,意味着我们不能继承 String。

•String 对象的字符内容是存储在一个字符数组 value[]中的。

//jdk8 中的 String 源码:

public final class String

implements java.io.Serializable, Comparable<String>, CharSe

quence {

/** The value is used for character storage. */

        private final char value[]; //String 对象的字符内容是存储在此

数组中

/** Cache the hash code for the string */

        private int hash; // Default to 0

–private 意味着外面无法直接获取字符数组,而且 String 没有提供 value 的 get 和 set 方法。

–final 意味着字符数组的引用不可改变,而且 String 也没有提供方法来修改 value 数组某个元素值

–因此字符串的字符数组内容也不可变的,即 String 代表着不可变的字符序 列。即,一旦对字符串进行修改,就会产生新对象。

–JDK9 只有,底层使用 byte[]数组。

String 的内存结构

概述

因为字符串对象设计为不可变,那么所以字符串有常量池来保存很多常量对象。

JDK6 中,字符串常量池在方法区。JDK7 开始,就移到堆空间,直到目前 JDK17版本。

  1. String s1 = "hello";
  2. String s2 = "hello";
  3. System.out.println(s1 == s2);

以上结果为true。  内存中只有一个"hello"对象被创建,同时被 s1 和 s2 共享。

 进一步:

  1. Person p1 = new Person();
  2. p1.name = “Tom";
  3. Person p2 = new Person();
  4. p2.name = “Tom";
  5. System.out.println(p1.name == p2.name);

以上结果为true,原理如下图

String str1 = “abc”;       与    String str2 = new String(“abc”);   的区别?

str2 首先指向堆中的一个字符串对象,然后堆中字符串的 value 数组指向常量池中常量对象的 value 数组。

 练习:

  1. String s1 = "javaEE";
  2. String s2 = "javaEE";
  3. String s3 = new String("javaEE");
  4. String s4 = new String("javaEE");
  5. System.out.println(s1 == s2);//true
  6. System.out.println(s1 == s3);//false
  7. System.out.println(s1 == s4);//false
  8. System.out.println(s3 == s4);//false

 2、String常用方法

(1)boolean isEmpty():字符串是否为空

(2)int length():返回字符串的长 度

(3)String concat(xx):拼接

(4)boolean equals(Object obj):比较字符 串是否相等,区分大小写

(5)boolean equalsIgnoreCase(Object obj):比较字 符串是否相等,不区分大小写

(6)int compareTo(String other):比较字符串 大小,区分大小写,按照 Unicode 编码值比较大小

(7)int compareToIgnoreCase(String other):比较字符串大小,不区分大小写

(8) String toLowerCase():将字符串中大写字母转为小写

(9)String toUpperCase():将字符串中小写字母转为大写

(10)String trim():去掉字符 串前后空白符

(11)public String intern():结果在常量池中共享

...

3、StringBuffer 与 StringBuilder 的理解

因为 String 对象是不可变对象,虽然可以共享常量对象,但是对于频繁字符串的修改和拼接操作,效率极低,空间消耗也比较高。因此,JDK 又在 java.lang 包提供了可变字符序列 StringBuffer 和 StringBuilder 类型。

•java.lang.StringBuffer 代表可变的字符序列,JDK1.0 中声明,可以对字符串内容进行 增删,此时不会产生新的对象。比如:

  1. //情况 1:
  2. String s = new String("我喜欢学习");
  3. //情况 2:
  4. StringBuffer buffer = new StringBuffer("我喜欢学习");
  5. buffer.append("数学");

•StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能 的方法也一样。

•区分 String、StringBuffer、StringBuilder

         –String:不可变的字符序列; 底层使用 char[]数组存储(JDK8.0 中)

         –StringBuffer:可变的字符序列;线程安全(方法有 synchronized 修饰),效率低;底层使用 char[]数组存储 (JDK8.0 中)

        –StringBuilder:可变的字符序列; jdk1.5 引入,线程不安全的,效率高;底层使用 char[]数组存储(JDK8.0 中)

常用 API

(1)StringBuffer append(xx):提供了很多的 append()方法,用于进行字符串

追加的方式拼接

(2)StringBuffer delete(int start, int end):删除[start,end)之 间字符

(3)StringBuffer deleteCharAt(int index):删除[index]位置字符

(4) StringBuffer replace(int start, int end, String str):替换[start,end)范围的字符序列 为 str

(5)void setCharAt(int index, char c):替换[index]位置字符

(6)char charAt(int index):查找指定 index 位置上的字符

(7)StringBuffer insert(int index, xx):在[index]位置插入 xx

(8)int length():返回存储的字符数据的长 度

(9)StringBuffer reverse():反转

4、日期时间

java.lang.System 类的方法

•System 类提供的 public static long currentTimeMillis():用来返回当前时间与 1970 年 1 月 1 日 0 时 0 分 0 秒之间以毫秒为单位的时间差。

java.util.Date

表示特定的瞬间,精确到毫秒。

•构造器:

        –Date():使用无参构造器创建的对象可以获取本地当前时间。

        –Date(long 毫秒数):把该毫秒值换算成日期时间对象

•常用方法

        –getTime(): 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象 表示的毫秒数。

        –toString(): 把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz 是时间标准。

        –其它很多方法都过时了。

  1. public class DateTest {
  2. @Test
  3. public void test1(){
  4. Date d = new Date();
  5. System.out.println(d);
  6. }
  7. @Test
  8. public void test2(){
  9. long time = System.currentTimeMillis();
  10. System.out.println(time);
  11. }
  12. @Test
  13. public void test3(){
  14. Date d = new Date();
  15. long time = d.getTime();
  16. System.out.println(time);
  17. }
  18. @Test
  19. public void test4(){
  20. long time = 1679099981269L;
  21. Date d = new Date(time);
  22. System.out.println(d);
  23. }
  24. @Test
  25. public void test5(){
  26. long time = Long.MAX_VALUE;
  27. Date d = new Date(time);
  28. System.out.println(d);
  29. }
  30. }

java.text.SimpleDateFormat

java.text.SimpleDateFormat 类是一个不与语言环境有关的方式来格式化和解析日期的具体

类。

可以进行格式化:日期 --> 文本

可以进行解析:文本 --> 日期

构造器:

–SimpleDateFormat() :默认的模式和语言环境创建对象

–public SimpleDateFormat(String pattern):该构造方法可以用参数 pattern 指定的格式创建一个对象

格式化:

–public String format(Date date):方法格式化时间对象 date

解析:

–public Date parse(String source):从给定字符串的开始解析文本,以生成 一个日期

  1. public class SimpleTest {
  2. //格式化
  3. @Test
  4. public void test1(){
  5. Date d = new Date();
  6. SimpleDateFormat stf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 HH 时 mm 分 ss 秒 SSS 毫秒 E Z");
  7. //把 Date 日期转成字符串,按照指定的格式转
  8. String str = stf.format(d);
  9. System.out.println(str);
  10. }
  11. //解析
  12. @Test
  13. public void test2() throws ParseException {
  14. String str = "2023 年 03 月 19 日 10 时 07 分 01 秒 791 毫秒 星期日 +0800";
  15. SimpleDateFormat stf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 HH 时 mm 分 ss 秒 SSS 毫秒 E Z");
  16. Date d = stf.parse(str);
  17. System.out.println(d);
  18. }
  19. }

java.util.Calendar

•Date 类的 API 大部分被废弃了,替换为 Calendar。

Calendar 类是一个抽象类,主用用于完成日期字段之间相互操作的功能。

•获取 Calendar 实例的方法

        –使用 Calendar.getInstance()方法

• 一个 Calendar 的实例是系统时间的抽象表示,可以修改或获取 YEAR、MONTH、DAYOFWEEK、HOUROFDAY 、MINUTE、SECOND 等 日历字段对应的时间值。

        –public int get(int field):返回给定日历字段的值

        –public void set(int field,int value) :将给定的日历字段设置为指定的值

        –public void add(int field,int amount):根据日历的规则,为给定的日历字段 添加或者减去指定的时间量

        –public final Date getTime():将 Calendar 转成 Date 对象

        –public final void setTime(Date date):使用指定的 Date 对象重置 Calendar 的时间

•注意:

        –获取月份时:一月是 0,二月是 1,以此类推,12 月是 11

        –获取星期时:周日是 1,周二是 2 , 。。。。周六是 7

  1. public class TestCalendar {
  2. @Test
  3. public void test1(){
  4. Calendar c = Calendar.getInstance();
  5. System.out.println(c);
  6. int year = c.get(Calendar.YEAR);
  7. int month = c.get(Calendar.MONTH)+1;
  8. int day = c.get(Calendar.DATE);
  9. int hour = c.get(Calendar.HOUR_OF_DAY);
  10. int minute = c.get(Calendar.MINUTE);
  11. System.out.println(year + "-" + month + "-" + day + " " + hour + ":" + minute);
  12. }
  13. @Test
  14. public void test2(){
  15. Calendar c = Calendar.getInstance();
  16. // 从一个 Calendar 对象中获取 Date 对象
  17. Date date = c.getTime();
  18. // 使用给定的 Date 设置此 Calendar 的时间
  19. date = new Date(234234235235L);
  20. c.setTime(date);
  21. c.set(Calendar.DAY_OF_MONTH, 8);
  22. System.out.println("当前时间日设置为 8 后,时间是:" + c.getTime());
  23. c.add(Calendar.HOUR, 2);
  24. System.out.println("当前时间加 2 小时后,时间是:" + c.getTime());
  25. c.add(Calendar.MONTH, -2);
  26. System.out.println("当前日期减 2 个月后,时间是:" + c.getTime());
  27. }
  28. }

5、新的日期时间API

JDK1.0 中包含了一个 java.util.Date 类,但是它的大多数方法已经在 JDK 1.1 引入 Calendar 类之后被弃用了。而 Calendar 并不比 Date 好多少。它们面临的问题是:

•可变性:像日期和时间这样的类应该是不可变的。

•偏移性:Date 中的年份是从 1900 开始的,而月份都从 0 开始。

•格式化:格式化只对 Date 有用,Calendar 则不行。

•此外,它们也不是线程安全的;不能处理闰秒等。

第三次引入的 API 是成功的,并且 Java 8 中引入的 java.time API 已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务。 Java 8 以一个新的开始为 Java 创建优秀的 API。新的日期时间 API 包含:

java.time – 包含值对象的基础包

java.time.chrono – 提供对不同的日历系统的访问。

java.time.format – 格式化和解析时间和日期

java.time.temporal – 包括底层框架和扩展特性

java.time.zone – 包含时区支持的类

说明:新的 java.time 中包含了所有关于时钟(Clock),本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、 时区(ZonedDateTime)和持续时间(Duration)的类。

本地日期时间:LocalDate、LocalTime、LocalDateTime

方法

描述

now()/ now(ZoneId zone)

静态方法,根据当前时间创建对象/指定时区的对象

of(xx,xx,xx,xx,xx,xxx)

静态方法,根据指定日期/时间创建对象

getDayOfMonth()/getDayOfYear()

获得月份天数(1-31) /获得年份天数(1-366)

getDayOfWeek()

获得星期几(返回一个 DayOfWeek 枚举值)

getMonth()

获得月份, 返回一个 Month 枚举值

getMonthValue() / getYear()

获得月份(1-12) /获得年份

getHours()/getMinute()/getSecond()

获得当前对象对应的小时、分钟、秒

withDayOfMonth()/withDayOfYear()/withMonth()/withYear()

将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象

with(TemporalAdjuster t)

将当前日期时间设置为校对器指定的日期时间

plusDays(), plusWeeks(),plusMonths(), plusYears(),plusHours()

向当前对象添加几天、几周、几个月、 几年、几小时

minusMonths() /minusWeeks()/minusDays()/minusYe ars()/minusHours()

从当前对象减去几月、几周、几天、几

年、几小时

plus(TemporalAmountt)/minus(TemporalAmount t)

添加或减少一个 Duration 或 Period

isBefore()/isAfter()

比较两个 LocalDate

isLeapYear()

判断是否是闰年(在 LocalDate 类中声

明)

format(DateTimeFormatter t)

格式化本地日期、时间,返回一个字符

parse(Charsequence text)

将指定格式的字符串解析为日期、时间

  1. public class LocalTimeTest {
  2. @Test
  3. public void test1(){
  4. LocalDate now = LocalDate.now();
  5. System.out.println(now);
  6. }
  7. @Test
  8. public void test2(){
  9. LocalTime now = LocalTime.now();
  10. System.out.println(now);
  11. }
  12. @Test
  13. public void test3(){
  14. LocalDateTime now = LocalDateTime.now();
  15. System.out.println(now);
  16. }
  17. @Test
  18. public void test4(){
  19. LocalDate lai = LocalDate.of(2019, 5, 13);
  20. System.out.println(lai);
  21. }
  22. @Test
  23. public void test5(){
  24. LocalDate lai = LocalDate.of(2019, 12, 13);
  25. System.out.println(lai.getDayOfYear());
  26. }
  27. @Test
  28. public void test6(){
  29. LocalDate lai = LocalDate.of(2019, 5, 13);
  30. LocalDate go = lai.plusDays(160);
  31. System.out.println(go);
  32. }
  33. @Test
  34. public void test7(){
  35. LocalDate now = LocalDate.now();
  36. LocalDate before = now.minusDays(100);
  37. System.out.println(before);
  38. }
  39. }

瞬时:Instant

•Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。

        –时间戳是指格林威治时间 1970 年 01 月 01 日 00 时 00 分 00 秒(北京时间 1970 年 01 月 01 日 08 时 00 分 00 秒)起至现在的总秒数。

java.time.Instant 表示时间线上的一点,而不需要任何上下文信息,例如,时 区。概念上讲,它只是简单的表示自 1970 1 1 0 0 0 秒(UTC)开始 的秒数。

日期时间格式化:DateTimeFormatter

该类提供了三种格式化方法:

•预定义的标准格式。如:ISOLOCALDATETIME、ISOLOCALDATE、 ISOLOCAL_TIME

•本地化相关的格式。如:ofLocalizedDate(FormatStyle.LONG)

•自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

方法描述

ofPattern(String pattern)

静态方法,返回一个指定字符串格式的DateTimeFormatter

format(TemporalAccessort)

格式化一个日期、时间,返回字符串

parse(CharSequence text)

将指定格式的字符序列解析为一个日期、时间

  1. public class TestDateTimeFormatter {
  2. @Test
  3. public void test1() {
  4. // 方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
  5. DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
  6. // 格式化:日期-->字符串
  7. LocalDateTime localDateTime = LocalDateTime.now();
  8. String str1 = formatter.format(localDateTime);
  9. System.out.println(localDateTime);
  10. System.out.println(str1);
  11. // 解析:字符串 -->日期
  12. TemporalAccessor parse = formatter.parse("2023-03-19T16:06:53.743");
  13. LocalDateTime dateTime = LocalDateTime.from(parse);
  14. System.out.println(dateTime);
  15. }
  16. @Test
  17. public void test2(){
  18. LocalDateTime localDateTime = LocalDateTime.now();
  19. // 方式二:
  20. // 本地化相关的格式。如:ofLocalizedDateTime()
  21. // FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:适用于 LocalDateTime
  22. DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
  23. // 格式化
  24. String str2 = formatter1.format(localDateTime);
  25. System.out.println(str2);
  26. // 本地化相关的格式。如:ofLocalizedDate()
  27. // FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM /FormatStyle.SHORT : 适用于 LocalDate
  28. DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
  29. // 格式化
  30. String str3 = formatter2.format(LocalDate.now());
  31. System.out.println(str3);
  32. }
  33. @Test
  34. public void test3(){
  35. //方式三:自定义的方式(关注、重点)
  36. DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
  37. //格式化
  38. String strDateTime = dateTimeFormatter.format(LocalDateTime.now());
  39. System.out.println(strDateTime);
  40. //解析
  41. TemporalAccessor accessor = dateTimeFormatter.parse("2023/03/19 16:09:39");
  42. LocalDateTime localDateTime = LocalDateTime.from(accessor);
  43. System.out.println(localDateTime);
  44. }
  45. }

6、Java比较器

•在 Java 中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。

•Java 实现对象排序的方式有两种:

        –自然排序:java.lang.Comparable

        –定制排序:java.util.Comparator

自然排序:java.lang.Comparable

•Comparable 接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的

自然排序。

•实现 Comparable 的类必须实现 compareTo(Object obj)方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。如果当前对象 this 大于形参对象obj,则返回正整数,如果当前对象 this 小于形参对象 obj,则返回负整数,如果当前 对象 this 等于形参对象obj,则返回零。

•实现 Comparable 接口的对象列表(和数组)可以通过 Collections.sort 或 Arrays.sort 进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中 的元素,无需指定比较器。

代码举例:

  1. public class Student implements Comparable{
  2. private int id;
  3. private String name;
  4. private int score;
  5. private int age;
  6. public Student(int id, String name, int score, int age) {
  7. this.id = id;
  8. this.name = name;
  9. this.score = score;
  10. this.age = age;
  11. }
  12. public int getId() {
  13. return id;
  14. }
  15. public void setId(int id) {
  16. this.id = id;
  17. }
  18. public String getName() {
  19. return name;
  20. }
  21. public void setName(String name) {
  22. this.name = name;
  23. }
  24. public int getScore() {
  25. return score;
  26. }
  27. public void setScore(int score) {
  28. this.score = score;
  29. }
  30. public int getAge() {
  31. return age;
  32. }
  33. public void setAge(int age) {
  34. this.age = age;
  35. }
  36. @Override
  37. public String toString() {
  38. return "Student{" +
  39. "id=" + id +
  40. ", name='" + name + '\'' +
  41. ", score=" + score +
  42. ", age=" + age +
  43. '}';
  44. }
  45. @Override
  46. public int compareTo(Object o) {
  47. //这些需要强制,将 o 对象向下转型为 Student 类型的变量,才能调用 Student 类中的属性
  48. //默认按照学号比较大小
  49. Student stu = (Student) o;
  50. return this.id - stu.id;
  51. }
  52. }
  1. public class TestStudent {
  2. public static void main(String[] args) {
  3. Student[] arr = new Student[5];
  4. arr[0] = new Student(3,"张三",90,23);
  5. arr[1] = new Student(1,"熊大",100,22);
  6. arr[2] = new Student(5,"王五",75,25);
  7. arr[3] = new Student(4,"李四",85,24);
  8. arr[4] = new Student(2,"熊二",85,18);
  9. //单独比较两个对象
  10. System.out.println(arr[0].compareTo(arr[1]));
  11. System.out.println(arr[1].compareTo(arr[2]));
  12. System.out.println(arr[2].compareTo(arr[2]));
  13. System.out.println("所有学生:");
  14. for (int i = 0; i < arr.length; i++) {
  15. System.out.println(arr[i]);
  16. }
  17. System.out.println("按照学号排序:");
  18. for (int i = 1; i < arr.length; i++) {
  19. for (int j = 0; j < arr.length-i; j++) {
  20. if(arr[j].compareTo(arr[j+1])>0){
  21. Student temp = arr[j];
  22. arr[j] = arr[j+1];
  23. arr[j+1] = temp;
  24. }
  25. }
  26. }
  27. for (int i = 0; i < arr.length; i++) {
  28. System.out.println(arr[i]);
  29. }
  30. }
  31. }

再举例:

  1. class Goods implements Comparable {
  2. private String name;
  3. private double price;
  4. //按照价格,比较商品的大小
  5. @Override
  6. public int compareTo(Object o) {
  7. if(o instanceof Goods) {
  8. Goods other = (Goods) o;
  9. if (this.price > other.price) {
  10. return 1;
  11. } else if (this.price < other.price) {
  12. return -1;
  13. }
  14. return 0;
  15. }
  16. throw new RuntimeException("输入的数据类型不一致");
  17. }
  18. //构造器、getter、setter、toString()方法略
  19. }
  1. public class ComparableTest{
  2. public static void main(String[] args) {
  3. Goods[] all = new Goods[4];
  4. all[0] = new Goods("《红楼梦》", 100);
  5. all[1] = new Goods("《西游记》", 80);
  6. all[2] = new Goods("《三国演义》", 140);
  7. all[3] = new Goods("《水浒传》", 120);
  8. Arrays.sort(all);
  9. System.out.println(Arrays.toString(all));
  10. }
  11. }

定制排序:java.util.Comparator

•思考

        –当元素的类型没有实现 java.lang.Comparable 接口而又不方便修改代码 (例如:一些第三方的类,你只有.class 文件,没有源文件)

        –如果一个类,实现了 Comparable 接口,也指定了两个对象的比较大小的 规则,但是此时此刻我不想按照它预定义的方法比较大小,但是我又不能 随意修改,因为会影响其他地方的使用,怎么办?

•JDK 在设计类库之初,也考虑到这种情况,所以又增加了一个 java.util.Comparator 接 口。强行对多个对象进行整体排序的比较。

        –重写 compare(Object o1,Object o2)方法,比较 o1 和 o2 的大小:如果方法 返回正整数,则表示 o1 大于 o2;如果返回 0,表示相等;返回负整数, 表示 o1 小于 o2。

        –可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。

  1. public class StudentScoreComparator implements Comparator {
  2. @Override
  3. public int compare(Object o1, Object o2) {
  4. Student s1 = (Student) o1;
  5. Student s2 = (Student) o2;
  6. int result = s1.getScore() - s2.getScore();
  7. return result != 0 ? result : s1.getId() - s2.getId();
  8. }
  9. }
  1. public class TestStudent1 {
  2. public static void main(String[] args) {
  3. Student[] arr = new Student[5];
  4. arr[0] = new Student(3, "张三", 90, 23);
  5. arr[1] = new Student(1, "熊大", 100, 22);
  6. arr[2] = new Student(5, "王五", 75, 25);
  7. arr[3] = new Student(4, "李四", 85, 24);
  8. arr[4] = new Student(2, "熊二", 85, 18);
  9. System.out.println(Arrays.toString(arr));
  10. //定制排序
  11. StudentScoreComparator sc = new StudentScoreComparator();
  12. Arrays.sort(arr, sc);
  13. System.out.println("排序之后:");
  14. System.out.println(Arrays.toString(arr));
  15. }
  16. }

十三、枚举类与注解

1、枚举类的使用


1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类

2.当需要定义一组常量时,强烈建议使用枚举类

3.如果枚举类中只有一个对象,则可以作为单例模式的实现方式。|

如何定义枚举类
方式一: jdk5.0之前,自定义枚举类
方式二:jdk5.0,可以使用enum关键字定义枚举类

自定义枚举类

  1. public class SeasonTest {
  2. public static void main(String[] args) {
  3. Season spring = Season.SPRING;
  4. System.out.println(spring);
  5. }
  6. }
  7. //自定义枚举类
  8. class Season{
  9. //1.声明Season对象的属性:private final修饰
  10. private final String seasonName;
  11. private final String seasonDesc;
  12. //2.私有化类的构造器,并给对象属性赋值
  13. private Season(String seasonName, String seasonDesc) {
  14. this.seasonName = seasonName;
  15. this.seasonDesc = seasonDesc;
  16. }
  17. //3.提供当前枚举类的多个对象
  18. public static final Season SPRING = new Season("春天","春暖花开");
  19. public static final Season SUMMER = new Season("夏天","夏日炎炎");
  20. public static final Season AUTUMN = new Season("秋天","秋高气爽");
  21. public static final Season WINTER = new Season("冬天","冰天雪地");
  22. //4.其他诉求1:获取枚举类对象的属性
  23. public String getSeasonName() {
  24. return seasonName;
  25. }
  26. public String getSeasonDesc() {
  27. return seasonDesc;
  28. }
  29. //4.其他诉求1:提供toString()
  30. @Override
  31. public String toString() {
  32. return "Season{" +
  33. "seasonName='" + seasonName + '\'' +
  34. ", seasonDesc='" + seasonDesc + '\'' +
  35. '}';
  36. }
  37. }

使用enum枚举类

  1. package Test_2.season;
  2. public class SeasonTest {
  3. public static void main(String[] args) {
  4. Season summer = Season.SUMMER;
  5. System.out.println(summer);
  6. }
  7. }
  8. //使用enum枚举类
  9. enum Season{
  10. //1.提供当前枚举类的对象,多个对象之间用逗号隔开
  11. SPRING("春天","春暖花开"),
  12. SUMMER("夏天","夏日炎炎"),
  13. AUTUMN("秋天","秋高气爽"),
  14. WINTER("冬天","冰天雪地");
  15. //1.声明Season对象的属性:private final修饰
  16. private final String seasonName;
  17. private final String seasonDesc;
  18. //2.私有化类的构造器,并给对象属性赋值
  19. private Season(String seasonName, String seasonDesc) {
  20. this.seasonName = seasonName;
  21. this.seasonDesc = seasonDesc;
  22. }
  23. //3.其他诉求1:获取枚举类对象的属性
  24. public String getSeasonName() {
  25. return seasonName;
  26. }
  27. public String getSeasonDesc() {
  28. return seasonDesc;
  29. }
  30. //3.其他诉求1:提供toString()
  31. @Override
  32. public String toString() {
  33. return "Season{" +
  34. "seasonName='" + seasonName + '\'' +
  35. ", seasonDesc='" + seasonDesc + '\'' +
  36. '}';
  37. }
  38. }

Enum类中的常用方法:
values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
valueOf(string str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对像的“名字”。如果不是,会有运行时异常。

toString( ):返回当前枚举类对象常量的名称

使用enum关键字定义的枚举类实现接口的情况

情况一:实现接口,在enum类中实现抽象方法
情况二:让枚举类的对象分别实现接口中的抽象方法

  1. interface Info{
  2. void show();
  3. }
  4. //使用enum枚举类
  5. enum Season implements Info{
  6. //1.提供当前枚举类的对象,多个对象之间用逗号隔开
  7. SPRING("春天","春暖花开"){
  8. @Override
  9. public void show(){
  10. System.out.println("春天在哪里");
  11. }
  12. },
  13. SUMMER("夏天","夏日炎炎"){
  14. @Override
  15. public void show(){
  16. System.out.println("宁夏");
  17. }
  18. },
  19. AUTUMN("秋天","秋高气爽"){
  20. @Override
  21. public void show(){
  22. System.out.println("秋天的风");
  23. }
  24. },
  25. WINTER("冬天","冰天雪地"){
  26. @Override
  27. public void show(){
  28. System.out.println("大约在冬季");
  29. }
  30. };
  31. }

2、注解

什么是注解?

注解(Annotation)其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

Annotation可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的“name=value”对中。

常见的annotation示例

●在编译时进行格式检查(JDK内置的三个基本注解)
>@Override:限定重写父类方法,该注解只能用于方法
>@Deprecated:用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
>@SuppressWarnings:抑制编译器警告

●跟踪代码依赖性,实现替代配置文件功能
>Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。

如何自定义注解

①注解声明为:@interface

②内部定义成员,通常使用vaLlue表示

③可以指定成员的默认值,使用defauLt定义

④如果自定义注解没有成员,表明是一个标识作用

  1. public @interface MyAnnotation {
  2. String value() default "hello";
  3. }

如果注解有成员,在使用注解时,需要指明成员的值。

注:自定义注解必须配上注解的信息处理流程(使用反射)才有意义。

jdk提供的4种元注解

●JDK的元Annotation 用于修饰其他Annotation定义

●JDK5.0提供了4个标准的meta-annotation类型,分别是:

>Retention: 指定所修饰的Annotation的生命周期:SOURCE\CLASS(默认行为)\RUNTIME,只有声明为RUNTIME的注解,才能通过反射获取。

>Target:用于指定被修饰的Annotation能用于修饰哪些程序元素

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

>lnherited: 被它修饰的Annotation将具有继承性

自定义注解通常都会指明两个元注解:Retention,Target

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/157681
推荐阅读
相关标签
  

闽ICP备14008679号