# java-study **Repository Path**: ChengSongyun/java-study ## Basic Information - **Project Name**: java-study - **Description**: java高级课堂案例 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-02-27 - **Last Updated**: 2025-04-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Java高级学习 ## 包装类和普通类的区别 包装类当没有赋值时,默认的数据为null,而普通类没有赋值时,会有默认值 - 如:int i = 0;Integer i = null; ## 一、线程 ### 概念 #### 1、程序 程序是一段静态代码,如没有启动的qq,微信,以及java中没有运行的main方法 #### 2、进程 正在运行的程序就是一个进程,进程是程序的一次动态执行过程,经历从代码加载、代码执行到执行完毕的一个完整过程(生命周期) #### 3、线程 线程是进程中的一条执行线路(路径),每个java程序有三条基础线程:main主线程、GC()垃圾回收机制的运行线程、异常处理线程 各线程是异步并行执行 - CPU分时机制 - 分配时间片 - 交叉执行 ### 创建线程* #### 1、继承Thread 第一:创建一个类,继承Thread类 ```java public class Xxx extends Thread{ } ``` 第二:重写run 方法 ```java public class Xxx extends Thread{ public void run(){ // ... } } ``` 第三:创建线程对象 ```java Xxx t = new Xxx(); ``` 第四:启动线程 ```java t.start(); ``` #### 2、实现Runnable 第一:实现Runnable接口 ```java public class Runner implements Runnable{ //重写run方法 - 实现所需代码逻辑 public void run(){ // ... } } ``` 第二:创建Runnable对象 ```java Xxx x = new Xxx(); ``` 第三: 实例化Thread对象 ```java //应该传入Runnable对象参数,否则实例化没有意义 Thread t = new Thread(Runnable对象); ``` 第四:启动线程 ```java t.start(); ``` #### 3、实现Callable接口 第一:实现Callable接口+重写call()方法 ```java //指定泛型,否则默认为Object public class MyCallable implements Callable { @Override public T call() throws Exception { // 此线程执行需要执行的操作声明在call方法中 } } ``` 第二:创建实现Callable接口的对象 ```java MyCallable mc = new MyCallable() ; ``` 第三:创建FutureTask对象 ```java FtureTask ft = new FutureTask<>(mc); ``` 第四:创建线程对象 ```java Thread t = new Thread(ft); ``` 第五:启动线程 ```java t.start(); ``` 第六:获取结果(主线程阻塞,同步等待task执行完毕结果) ```java //获取的将是call()方法的返回值 ft.get(); ``` 特点: 1. 异步执行任务:可以使用FutrreTask在一个线程中执行一个耗时的任务,而不会阻塞主线程 2. 获取任务结果:可以在需要的时候获取任务的执行结果,通过get()方法获取,如果任务还没有执行完成,**get()方法**将会**阻塞**直到任务完成 3. 取消任务:可以通过cancel()方法取消任务的执行,取消之后,get()方法将会返回CancellationException 4. 判断任务状态:可以通过isDone()、isCancelled()等方法判断任务的执行状态 #### 4、线程池 - JDK5.0新特性 常见线程池类型: - **FixedThreadPool**: 该线程池包含固定数量的线程,当有新的任务提交时,如果当前线程池中有空闲线程,则立即执行任务,否则任务将被放入队列中等待执行。 - **CachedThreadPool**: 该线程池会根据需要创建新的线程,但如果有空闲线程可用,则会重用它们。当线程处于空闲状态一段时间后,它们会被回收。 - **SingleThreadPool**: 只包含单个工作线程的线程池,所有任务按照先进先出的顺序执行。 - **ScheduledThreadPool**: 用于执行定时任务和周期性任务,可以指定延迟时间或固定的周期来执行任务。 第一:实现Callable接口+重写call()方法 || 实现Runnable接口+重写run()方法 略 第二:借助执行调度服务ExecutorService,获取Future对象 ```java // 第二:工具类Executors创建线程池并返回ExecutorService对象 //ExecutorService executorService = Executors.newFixedThreadPool(线程数) ; ExecutorService executorService = Executors.newFixedThreadPool(10) ; ``` 第三:创建Future对象 ```java //Future f = executorService.submit(Callable对象 || Runnable对象); Future f = executorService.submit(new MyCallable()); ``` 第四:获取值 ```java f.get(); ``` 第五:停止服务 ```java executorService.shutdownNow(); ``` ### 线程控制方法* #### 1、start():线程启动 注意:只能调用一次,并且该方法只是让线程进行就绪状态,不一定马上运行线程体,需要CPU分配时间片 ```java MyThread t = new MyThread(); //执行线程中的重写run()方法 t.start(); ``` #### 2、run():线程体 重写父类中的run()方法,写入自己需要的内容 #### 3、currentThread():获取当前正在运行的线程 ```java Thread.currentThread();//在run()方法中使用 ``` #### 4、getName():获取线程名称 线程名称默认为Thread-N ```java MyThread mt = new MyThread(); mt.start(); mt.getName(); //在run方法中使用 Thread.currentThread().getName(); ``` #### 5、setName(String):设置线程名称 ```java MyThread mt = new MyThread(); mt.start(); mt.setName("线程一"); ``` #### 6、getState():获取线程状态 线程状态通过Enum表示,分别为:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED ```java MyThread mt = new MyThread(); System.out.println(mt.getState()); mt.start(); ``` #### 7、setDaemon(true):设置守护线程 守护线程,也叫后台线程。相对后台线程而言称之为前台线程。当所有的前台线程都执行完毕(死亡),那么后台线程也自然死亡 ```java MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); //设置守护线程 - 当mt2执行完毕,不管mt1有没有执行完毕都停止执行 mt1.setDaemon(true); mt1.start(); mt2.start(); ``` #### 8、sleep(long ms):线程休眠 睡眠情况不会放锁 在哪个线程使用就代表让哪个线程休眠 ```java MyThread mt = new MyThread(); //当前线程休眠1秒 Thread.sleep(1000); mt.start(); ``` #### 9、yield():线程让步 - 静态 当前线程放弃cpu控制权,回到准备状态,还有可能重新抢到控制权 例子一:当进入某种条件后给予线程让步 ```java public void run(){ if(条件){ Thread.yield(); } //其余代码块 } ``` #### 10、线程优先级控制 优先级越高,cpu的执行时间就越长 设置/获取优先级 ```java MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); //取值范围是1-10,值越大,优先级越高 mt1.setPriority(10); //获取优先级 mt1.getPriority(); mt1.start(); mt2.start(); ``` 优先级的三个参数: ```java public final static int MIN_PRIORITY = 1 ; //最低优先级 public final static int NORM_PRIORITY = 5 ; //缺省优先级 public final static int MAX_PRIORITY = 10 ; //最高优先级 //设置为最高优先级 mt1.setPriority(MAX_PRIORITY); ``` 注意: - 高优先级并不意味着先执行,它只是提示任务调度器优先调度此线程,但任务调度器很有可能会忽略它。 - 只是原则上具有更高概率先抢占CPU资源执行。 - 如果CPU比较忙,则优先级高的线程会获得更多的时间片;如果CPU闲时,优先级几乎无作用。 #### 11、join():停止当前线程 等待当前线程运行结束,当有参数时,就是等待当前线程运行n毫秒 在某个线程中使用 线程.join() 那么该线程就会停止(进入阻塞状态),等待指定线程运行 ```java public static void main(String[] args){ MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); mt1.start(); //等待mt1线程运行完才会往下运行 - 主线程停止 mt1.join(); mt2.start(); } ``` #### 12、isAlive:是否处于活动状态 判断线程是否存活,也就是有没有执行完毕 ```java MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); mt1.isAlive();//false mt1.start(); mt2.start(); mt1.isAlive();//true ``` #### 13、interrupt():中断线程 用于打断阻塞状态的线程,如:sleep、wait、join 注意: - 如果被中断的线程正在sleep、wait、join会导致被中断的线程抛出InterruptedException异常,并清楚中断标记,中断标记(状态)为**false** - 如果中断的是正在运行的线程(非阻塞状态),则会设置中断标记。中断标记(状态)为**true** ```java MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); mt1.start(); mt2.start(); Thread.sleep(5000); //中断线程(设置中断标记为true) - 5s后中断 mt1.interrupt(); ``` #### 14、isInterrupted():判断这个线程是否被中断 特点:不会清楚中断标记,可以多次使用 ```java //假设为mt1 - 在非阻塞状态中断线程 public void run(){ //循环输出中断标记 while(true){ //获取中断标记 - 在五秒后中断标记为true boolean flag = Thread.currentThread().isInterrupted(); System.out.println(flag); if(flag){ //在中断标记为true时停止循环 - 线程死亡 break; } } } ``` ```java //假设为mt1 - 在阻塞状态中断线程 public void run(){ //循环输出中断标记 while(true){ //获取中断标记 - 在五秒后中断标记为true boolean flag = Thread.currentThread().isInterrupted(); System.out.println(flag); if(flag){ //在中断标记为true时停止循环 - 线程死亡 break; } //将会在5s后进入并进入catch代码块 - 清楚中断标记false try{ //测试线程在阻塞状态中断线程 Thread.sleep(1000); }catch(Exception e){ //常理来说会抛出异常并报错 - 注释执行自己设定的代码 //throw new RuntimeException(e); System.out.println(flag);//false //再次中断 - 重新设置中断标记为true Thread.currentThread.interrupt(); } } } ``` #### 15、interrupted():判断当前线程是否被中断 特点:会清除中断标记 - 无法多次使用 ### 生命周期 #### 1、新建状态(New) 当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread(); #### 2、就绪状态(Runnable) 当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行; #### 3、运行状态(Running) 当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中; #### 4、阻塞状态(Blocked) 处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种: 1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态; 2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态; 3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 #### 5、死亡状态(Dead) 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。![img](http://8.134.185.56/java/images/008eGmZEgy1gp6pfm95htj30s00kwk1s.jpg) ### 线程同步* 当多个线程操作(修改、写)相同资源时,就有可能发出线程安全问题。 解决方案:同步机制 - 锁 #### 1、实现一:同步块 ```java //注意:object参数就是锁对象 //object参数可以是任意对象,但必须保证object是唯一(相同)的 //唯一的对象可以有一下几种情况: //1)字符串常量 - String key = "aa"; //2)静态变量/常量 - static Object obj = new Object(); //3)Class.类名 类名.class - 所有对象的Class对象相同 //4)this 慎用 - 不理解就不要用 synchronized(object){ //代码段(临界区);--可能发生线程安全问题的代码区域 } ``` #### 2、实现二:同步方法 - 使用synchronized关键字声明的方法,称之为同步方法 - 同步方法的锁对象为:当前对象(this) - 同步静态方法的锁对象是:类对象(当前类名.class) - 在一个类中,存在多个同步方法时,各同步方法之间是互斥的(多个锁对象相同类型(静态和普通)的方法,其中一个方法被使用,那么其他的方法其他线程无法使用),这是因为它们的锁对象是同一个 ```java [修饰符] synchronized 数据类型 方法名() { // 代码段 ; } ``` 等同于以下的同步块 ```java synchronized(this){ } ``` #### 3、实现三:lock(JDK5.0新增) JDK5.0新特性,同步锁由`Lock`对象充当。`ReentrantLock`类实现Lock接口。 `java.util.concurrent.locks.Lock`接口:控制多个线程对共享数据资源进行访问的工具。锁提供了对共享数据资源的独占访问,每次只有一个线程对Lock对象加锁,线程开始访问共享数据资源之前需要先获取Lock对象。 同样,使用方式一创建线程需要主要lock对象的静态问题。 ```java ReentrantLock reentrantLock = new ReentrantLock(); try { //加锁 reentrantLock.lock(); //需要同步的代码块 }finally { //释放锁 reentrantLock.unlock(); } ``` 两大类解决线程安全问题方法的不同之处(synchronized和lock(ReentrantLock)) - `synchronized`机制属于在执行完同步代码后,会自动释放锁(隐式锁); - `Lock`机制需要手动的启动和释放锁(显示锁),且只有代码块锁,没有方法锁; ### 线程死锁问题 ​ 某些情况下会出现该问题 ,尽量避免...... ### 线程通信* #### 1、概念 线程通信是指**多个线程之间**通过共享内存或消息传递等方式**进行信息交换和协作的过程**,以完成特定的任务或解决特定的问题。 #### 2、线程通信常用方法 1)wait() > 线程进入阻塞状态,并释放锁(和sleep不同) 2)notify() > 唤醒正在等待锁的线程,进入就绪状态(有优先级按优先级,没有随机唤醒一个); 3)notifyAll() > 唤醒所有正在等待锁的线程; 注意: > - 这三种方法只能出现在同步代码块或同步方法中,且不能用在lock中 > - 这三个方发是Object类定义的,所以所有对象都可以访问这三个方法,但是,**一般通过锁对象访问** > - 这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,默认情况下是this或者类.class(当前类的对象) **sleep()和wait()的异同** - 同 - 一旦使用,均可使当前线程进入阻塞状态; - 异 - 调用要求不同:sleep()声明在Thread类中,wait()声明在Object类中; - 声明位置不同:sleep()可以使用在各种需要的地方,而wait()只能用在同步代码块或同步方法里; - 使用 sleep() 不会释放对象锁,而使用 wait() 会释放对象锁。 ## 二、反射 ### 一、概念 反射:Java的一种特性,可以使用Java程序**在运行时,动态地执行一些操作**!使用Java语言具有动态语言的灵活性、扩展性 反射相关的API - java.lang.reflect.* - java.lang.reflect.Array:数组集合 - java.lang.reflect.Constructor:构造方法 - java.lang.reflect.Field:字段(属性) - java.lang.reflect.Method:方法 - java.lang.reflect.Parameter:方法参数 - java.lang.reflect.Modifier:访问修饰符 - java.lang.reflect.Package:包 ### 二、Class 每个类都有**一个** Class 对象,它用来创建这个类的所有对象,反过来说,每个类的所有对象都会关联同一个 Class 对象 Class对象是一面透射镜,可透射类中的所有信息,构造方法、属性(字段[Field])、方法、修饰符等 在开发中,我们就是通过Class对象获取某个类的所有信息,从而在运行时动态的执行相关的操作! 一切反射操作从Class对象开始 注意:我们不能实例化类的Class对象(JVM),但是我们可以通过一下途径获取类的Class对象 ### 三、Constructor - 构造方法对象 > 构造方法对象 1、获取构造方法对象 ```java 1、获取具体某个构造方法对象 - 基于参数区分 Constructor con = Class对象.getConstructor(参数类型的Class对象); 2、获取类中所有的构造方法 - 返回数组 Constructor[] arr = Class对象.getConstructors(); 3、获取具体某个私有的构造方法对象 - 基于参数区分 Constructor con = clazz.getDeclaredConstructor(参数类型的Class对象); ``` 2、基于构造方法对象,实例化具体的对象 ```java 1、默认构造方法 - 默认返回Object对象(泛型未指定) Object o = 构造方法对象.newInstance(); 2、重载构造方法 Object o = 构造方法对象.newInstance(实参列表); 3、私有构造方法 私有构造方法对象.setXxx(true); Object o = 私有构造方法对象.newInstance([实参列表]); ``` ### 四、Field - 字段(属性)对象 > 字段(属性)对象 1.获取字段 ```java 1)获取所有公共的字段 Field[] fs = Class对象.getFields() ; 2)获取所有的字段 Field[] fs = Class对象.getDeclaredFields() ; 3)获取属性名称 field对象.getName() 4)获取某个公共字段 Field field = clazz.getField("字段名称"); 5)获取某个字段 (包括私有) Field field = clazz.getDeclaredField("字段名称"); ``` 2.设置私有字段可访问 ```java Field对象.setAccessible(true); ``` 3.设置属性 ```java field对象.set(对象,值) ``` 4.获取属性 ```java field对象.get(对象) ``` 5.获取字段类型 ```java //获取字段类型的Class对象 Class fieldTypeClazz = field对象.getType() ; //获取数据类型 System.out.println("数据类型(包名.类名):" + fieldTypeClazz.getName()); System.out.println("数据类型(类名):" + fieldTypeClazz.getSimpleName()); System.out.println("判断数据类型是否为基本类型:" + fieldTypeClazz.isPrimitive()); ``` ### 五、Mthod - 方法对象 #### 获取类中的方法 - 所有方法 || 指定方法 注意:在获取指定方法参数名称是默认为argN,需要配置参数来获取参数名称 获取到方法之后,如果是私有方法,在使用之前必须先权限公开 ```java // 1)获取类中所有的公共方法,返回Method数组 Method[] methods = Class对象.getMethods() ; // 2)获取为中所有的方法(包括私有),返回Method数组 Method[] methods = Class对象.getDeclaredMethods() ; // 3)返回某个指定的公共方法 Method method = Class对象.getMethod("方法名"[,"参数列表的Class对象",...]) ; // 4)返回某个指定的方法(包括私有) Method method = Class对象.getDeclaredMethod("方法名"[,"参数列表的Class对象",...]) ; ``` #### 获取方法返回值类型的类类型 ```java // Class typeClass = Method对象.getReturnType() ; // 获取方法返回值类型名称 // 如果是对象,则返回包类.类名,否则只返回名称 String name = typeClass.getName() ; // 不管是对象类型还是基本类型,都只返回名称 String simpleName = typeClass.getSimpleName() ; ``` #### 获取方法参数类型 ```java //获取所有参数的类型的Class对象 Class[] paramTypesClass = Method对象.getParameterTypes() ; //循环遍历输出参数的类型 for (Class param : paramTypesClass) { System.out.println(param.getName()); } ``` #### 设置私有方法可访问 ```java Method对象.setAccessible(true) ; ``` #### 调用方法 ```java //获取到指定方法后调用指定方法 Method对象.invoke("方法所在的对象"[,"实参列表"]) ; ``` #### 遍历所有的方法 ```java // 第一:获取Student类的Class对象 Class clazz = Student.class ; // 第二:获取Student类中的所有方法 Method[] methods = clazz.getMethods() ; // 第三:打印输出所有方法的名称 for (Method m : methods) { System.out.println("方法名:"+m.getName()); System.out.println("访问修饰符:"+m.getModifiers()); System.out.println("访问修饰符:"+Modifier.toString(m.getModifiers())); // Class typeClass = m.getReturnType() ; // String typeName = typeClass.getName() ; // String typeName = typeClass.getSimpleName() ; String typeName = m.getReturnType().getSimpleName() ; System.out.println("返回值类型:"+typeName); // 获取方法参数类型的Class对象 Class[] clz = m.getParameterTypes() ; System.out.println("参数个数:"+clz.length); // 遍历方法中的参数类型 for (Class param : clz) { // System.out.println(param.getName()); System.out.println(param.getSimpleName()); } System.out.println(); } ``` #### 获取某个方法并调用 ```java // 第一:获取Student类的Class对象 Class clazz = Student.class; //第二:创建Student类的对象 Constructor constructor = clazz.getConstructor(String.class,Float.class) ; Object obj = constructor.newInstance("王八",200.0F) ; // 第三:获取Student类中"公有的"sum方法 Method m = clazz.getMethod("sum", int.class,int.class) ; // 第四:调用方法 Object oj = m.invoke(obj, 1,1) ; // 输出:2 System.out.println(oj.toString()); ``` #### 调用私有方法 ```java // 第一:获取Student类的Class对象 Class clazz = Student.class; // 第二:创建Student类的对象 Constructor constructor = clazz.getConstructor(String.class,Float.class) ; Object obj = constructor.newInstance("王八",200.0F) ; // 第三:获取Student类中"私有的"sleep方法 // Method m = clazz.getMethod("sleep") ; //获取失败 Method m = clazz.getDeclaredMethod("sleep") ; // 第四:设置私有方法可访问,否则访问失败 m.setAccessible(true) ; // 第五:调用方法 m.invoke(obj) ; ``` ### 六、Modifier - 修饰符对象 这个类存放了所有修饰符的常量值,以及通过这些常量值判断是哪种修饰符。 - getModifiers():返回修改符的常量值 - Modifier.toString(修饰符常量值) :返回修饰符常量值对应的字符串表示 - isXxx(修饰符常量值):判断修饰符是否为Xxx ```java int modifier = Class对象.getModifiers() ; int modifider = Field对象. getModifiers() ; int modifider = Method对象. getModifiers() ; int modifider = Constructor对象. getModifiers() ; // ... ``` 以上返回的是修饰符对应的整型常数,可以通过Modifier.toString(修饰符常量值)转为字符串表示。 ```java Class clazz = Student.class ; Constructor ct = clazz.getConstructor() ; int mod = ct.getModifiers() ; // 输出:1 System.out.println(mod); // 输出:true - 判断指定修饰符是否是public System.out.println(Modifier.isPublic(mod)); // 输出:public - 转为字符串表示 System.out.println(Modifier.toString(mod)); ``` ### 七、获取实现的接口 ```java Class[] interfaces = ArrayList.class.getInterfaces(); Arrays.stream(interfaces).forEach(c -> System.out.println(c.getSimpleName())); // 输出: List, RandomAccess, Cloneable, Serializable ``` ### 八、获取注解 ```java Deprecated deprecated = MyClass.class.getAnnotation(Deprecated.class); if (deprecated != null) { System.out.println("Class is deprecated"); } ``` ### 九、获取内部类 ```java Class[] innerClasses = Map.class.getDeclaredClasses(); // 输出 Map 的内部类(如 Entry) Arrays.stream(innerClasses).map(Class::getSimpleName).forEach(System.out::println); ``` ### 十、类型检查 #### 1)检查是否为接口 ```java // 输出: true System.out.println(List.class.isInterface()); ``` #### 2)检查是否为数组 ```java int[] arr = new int[5]; // 输出: true System.out.println(arr.getClass().isArray()); ``` #### 3)检查是否为基本类型 ```java // 输出: true System.out.println(int.class.isPrimitive()); ``` #### 4)类型兼容性检查 ```java // 作用:检查 cls 参数表示的类或接口是否与当前类或接口相同,或者是其子类/实现类。 // 返回值:true 表示兼容,false 表示不兼容。 public native boolean isAssignableFrom(Class cls); ``` ```java // 1、类继承关系 // 子类可赋值给父类,输出: true System.out.println(Number.class.isAssignableFrom(Integer.class)); // 父类不可赋值给子类,输出: false System.out.println(Integer.class.isAssignableFrom(Number.class)); // 2、接口实现 // 实现类可赋值给接口,ArrayList 实现了 List,输出:true List.class.isAssignableFrom(ArrayList.class); // 接口不可赋值给实现类,输出:false ArrayList.class.isAssignableFrom(List.class); // 3.自动装箱是编译器行为,反射不处理。 // 基本类型和包装类型不兼容,以下两行代码都输出:false Integer.class.isAssignableFrom(int.class); int.class.isAssignableFrom(Integer.class); // 4.数组类型兼容 // String 数组是 Object 数组的子类型,输出:true Object[].class.isAssignableFrom(String[].class); // 输出:false String[].class.isAssignableFrom(Object[].class); // 5、泛型类型擦除后兼容性 // 类型擦除后只看原始类型,输出:true List.class.isAssignableFrom(ArrayList.class); ``` ### 十一、Array - 数组反射 > 这个类提供了动态创建和访问数组的能力 #### 1)创建数组 ```java Object obj = Array.newInstance("数组数据类型的Class对象", "长度") ; ``` #### 2)初始化数组 ```java Array.set("数组对象" , "下标索引" , "值") ; Array.setXxx("数组对象" , "下标索引" , "值") ; ``` #### 3)获取数组中的元素 ```java Array.get("数组对象", "下标索引") ; Array.getXxx("数组对象", "下标索引") ; ``` #### 一维数组 ```java // Array.newInstance("数组数据类型的Class对象", "长度") Object obj = Array.newInstance(int.class, 3) ; // 注:以上代码等价于:int[] obj = new int[3] ; // 初始化 // 强制类型转换 // int[] arr = (int[])obj ; // 给数组元素设值 // Array.setXxx("数组对象", "下标索引", "值") ; // Array.set("数组对象", "下标索引", "值") ; Array.set(obj, 0, 100) ; Array.set(obj, 1, 200) ; Array.setInt(obj, 2, 300) ; // 获取数组元素的值 // Array.getXxx("数组对象", "下标索引") ; // Array.get("数组对象", "下标索引") ; Object val1 = Array.get(obj, 0) ; int val2 = (Integer)Array.get(obj, 1) ; int val3 = Array.getInt(obj, 2) ; System.out.println(val1); //输出:100 System.out.println(val2); //输出:200 System.out.println(val3); //输出:300 ``` #### 多维数组 ```java // 创建一个三维数组:[3][4][7] // obj:三维数组的引用 // Array.newInstance(数组数据类型的Class对象, [维数,...]) ; Object objSan = Array.newInstance(int.class, 3, 4, 7); // 以上代码等同于:int[][][] objSan = new int[3][4][7] ; // 给三维数组赋值 int[][][] arr1 = (int[][][])objSan ; arr1[0][1][2] = 100 ; System.out.println(arr1[0][1][2]); //输出:100 // 给三维数组:[1][3][6] = 200 // 1.把三维数组的引用转换为二维数组的引用 Object objEr = Array.get(objSan, 1); // 2.把二维数组的引用转换为一维数组的引用 Object objOne = Array.get(objEr, 3); // 3.给一维数组的引用赋值 Array.set(objOne, 6, 200); // 4.取值 Object objValue = Array.get(objOne, 6) ; int[][][] arr2 = (int[][][])objSan ; //输出:200 System.out.println(objValue) ; //输出:200 System.out.println(arr2[1][3][6]) ; ``` #### 获取数组信息 ```java float[] arr = new float[5] ; // 获取数组的Class对象 Class arrclass = arr.getClass() ; // 获取数组的数据类型的Class对象 Class arrTypeClass = arrclass.getComponentType() ; // 获取数组数据类型的名称 String name = arrTypeClass.getName() ; System.out.println(name); // 获取数组的长度:Array.getLength("数组的引用") ; int len = Array.getLength(arr) ; System.out.println(len); ``` ### switch语句新特性与MySQL日期类型问题 #### 一、switch语句的新特性 Java 从 **Java 12** 开始逐步增强 `switch` 语法,并在后续版本中(如 Java 14、Java 17)进一步优化和标准化。具体的新特性如下所示: 1. **箭头语法 `->`** 替代传统的 `:` 和 `break`,简化代码并避免 `fall-through`(穿透)问题。 2. **多值匹配** `case` 允许多值匹配,逗号分隔。 3. **表达式返回值** `switch` 可以作为表达式直接返回值(类似 `return`)。 4. **类型增强** 支持更复杂的类型匹配(如枚举、字符串、数字)。 5. **`yield` 关键字** `case` 和 `default` 支持代码块,在代码块中通过 `yield` 关键字返回值。(Java 13+)。 6. **类型模式匹配** ```java switch(value) { case 数据类型 变量 -> ... } ``` 检查 switch 的 value 值是否与 case 的类型匹配,若匹配则将其绑定到变量,执行case箭头后的相关代码。 7. **类型覆盖检查** 如果 `switch` 表达式未覆盖所有可能值,会编译报错 ##### 1、传统用法 ```java package test00.lx.test08; import java.util.Scanner; /** * 传统 switch 语句的使用 - 特点:存在 fall-through(穿透)问题,需要使用 break 避免穿透问题 *

* 每个case都需要使用break语句退出 switch,从而避免穿透问题 * * @author zqx * @date 2025-03-20 */ public class SwitchTest01 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int day = sc.nextInt(); switch (day) { case 1: System.out.println("Monday"); break; case 2: System.out.println("Tuesday"); break; // ... 其他 case default: System.out.println("Unknown"); } } } ``` ##### 2、箭头语法 ```java package test00.lx.test08; import java.util.Scanner; /** * switch 箭头语法 - 替代传统的 : 和 break ,简化了代码并避免 fall-through(穿透)问题。 * * @author zqx * @date 2025-03-20 */ public class SwitchTest02 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int day = sc.nextInt(); switch (day) { // 支持多值匹配,逗号分隔;箭头语法,替代传统的 : 和 break case 1, 2, 3, 4, 5 -> System.out.println("Workday"); case 6, 7 -> System.out.println("Weekend"); // 支持代码块 default -> { if (day < 1) { System.out.println("Invalid"); } else { System.out.println("Unknown"); } } } } } ``` ##### 3、语句块,使用 yield 指定返回值 ```java package test00.lx.test08; import java.util.Scanner; /** * switch语句可以作为表达式直接返回值 *

* case 和 default 支持代码块,在代码块中通过 yield 关键字返回值; * 另外,在各代码中也可以定义变量,变量为代码块局部变量,各代码块中定义相同变量不冲突 * * @author zqx * @date 2025-03-20 */ public class SwitchTest03 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int day = sc.nextInt(); // switch 语句可以作为表达式直接返回值,使用变量接收结果,后面使用分号结束 String dayType = switch (day) { case 1, 2, 3, 4, 5 -> "Workday"; case 6, 7 -> "Weekend"; // 支持代码块 default -> { // 在代码块中,返回值需要使用 yield 关键字 if (day < 1) { yield "Invalid"; } else { yield "Unknown"; } } }; System.out.println(dayType); } } ``` ##### 4、语法块,定义变量 ```java package test00.lx.test08; /** * case 和 default 支持代码块,在代码块中通过 yield 关键字返回值; * 另外,在各代码中也可以定义变量,变量为代码块局部变量,各代码块中定义相同变量不冲突 * * @author zqx * @date 2025-03-20 */ public class SwitchTest04 { public static void main(String[] args) { String fruit = "apple"; int price = switch (fruit) { case "apple" -> { // 局部变量 int discount = 2; // 苹果价格减 2 yield 10 - discount; } case "banana" -> { // 在各代码块中定义相同名称的局部变量 - 独立作用域,不冲突 int discount = 1; // 香蕉价格减 1 yield 8 - discount; } default -> 5; }; System.out.println(price); } } ``` ##### 5、 类型模式匹配 ```java package test00.lx.test08; import java.util.Date; import java.util.Random; /** * case支持类型模式匹配(Type Pattern Matching), * 语法:case 数据类型 变量 -> ... * 作用:检查 switch 的 value 值是否与 case 的类型匹配,若匹配则将其绑定到变量,执行case箭头后的相关代码 * * @author zqx * @date 2025-03-20 */ public class SwitchTest05 { public static void main(String[] args) { System.out.println(test("hello")); System.out.println(test(new Date())); System.out.println(test(new Random())); } /** * 类型模式匹配 * * @param value 数据 * @return 匹配类型的处理结果 */ private static String test(Object value) { return switch (value) { case String str -> str.toUpperCase(); case Date date -> Long.toString(date.getTime()); case Random random -> Integer.toString(random.nextInt(10)); default -> null; }; } } ``` ##### 6、类型覆盖检查 ```java package test00.lx.test08; /** * * @author zqx * @date 2025-03-20 */ public class SwitchTest06 { public static void main(String[] args) { // 创建枚举 enum Color {RED, GREEN, BLUE} // 定义枚举 Color color = Color.RED; // 错误:缺少 BLUE 的 case String colorName = switch (color) { case RED -> "Red"; case GREEN -> "Green"; // 没有处理 BLUE,需要添加 default 或明确处理 //default -> "Blue"; }; System.out.println(colorName); } } ``` #### 二、类型转换 ##### 1、背景 MySQL 8.x+ 驱动,DateTime 类型的字段默认映射为 `java.time.LocalDateTime` - 解决一:实体对象日期时间字段使用 java.time.LocalDateTime 声明。 - 解决二:实体对象日期时间字段使用 java.util.Date 声明,通过在 JDBC 连接 URL 中添加 treatMysqlDatetimeAsTimestamp=true 参数,从而使 DateTime 类型的字段映射为 java.sql.Timestamp类型。而 java.util.Date 类型是 java.sql.Timestamp 类型的父类。 - 解决三:编写程序实现自动转换 - 先判断是否兼容,兼容转换,否则抛异常。 ##### 2、定义类型转换接口 ```java /** * 类型转换 - 实现数据表字段类型与实体对象类型的转换 * * @author zqx * @date 2025-03-20 */ public interface TypeConverter { /** * 判断是否需要把数据库查询到的数据,转换为实体对象映射字段的类型 * * @param fieldType 数据转换的目标类型 * @return true支持,否则不支持 */ boolean supports(Class fieldType); /** * 执行转换 * * @param fieldType 数据转换的目标类型 * @param value 转换数据 * @return 转换结果 */ Object apply(Class fieldType, Object value); } ``` **单一职责原则(Single Responsibility Principle)** - **核心体现**: `TypeConverter` 接口及其实现类(如 `DateConverter`)的每个实现 **仅负责一种特定类型转换**(如 `Date` → `LocalDate`),不涉及其他类型的转换逻辑。 - 优势 : - 每个转换器职责明确,修改或扩展时不会影响其他转换逻辑。 - 新增类型转换时,只需添加新类(如 `ZonedDateTimeConverter`),无需修改现有代码。 **开闭原则(Open-Closed Principle)** - 核心体现 : - **对扩展开放**:通过新增 `TypeConverter` 实现类支持新类型转换(如未来支持 `BigInteger` → `Long`)。 - **对修改关闭**:已有的转换逻辑(如 `DateConverter`)无需因新增类型而修改。 - **优势**: 系统可灵活扩展,避免因需求变化导致代码频繁修改。 **接口隔离原则(Interface Segregation Principle)** - **核心体现**: `TypeConverter` 接口仅定义类型转换的最小协议(`supports` 和 `apply`),不包含无关方法。 - 优势 : - 实现类无需实现冗余方法,降低接口污染风险。 - 客户端代码仅依赖所需接口,无需关心具体实现。 **策略模式(Strategy Pattern)** - 核心体现 : - **策略接口**:`TypeConverter` 定义类型转换的通用协议。 - **具体策略**:每个实现类(如 `DateConverter`)封装一种具体的转换策略。 - **动态选择策略**:根据目标字段类型(`fieldType`)选择对应的转换器。 - 优势 : - 解耦类型判断(`supports`)与转换逻辑(`apply`)。 - 支持运行时动态切换转换策略(如根据配置选择不同的日期格式)。 ##### 3、实现 ###### 1)DateConverter ```java import test00.lx.test08.TypeConverter; import java.sql.Time; import java.sql.Timestamp; import java.time.*; import java.util.Date; /** * 实现把查询数据转换为以下传统的日期类型: * java.util.Date * java.sql.Date * java.sql.Time、 * java.sql.Timestamp * * @author zqx * @date 2025-03-20 */ public class DateConverter implements TypeConverter { @Override public boolean supports(Class fieldType) { String targetType = fieldType.getName(); return switch (targetType) { case "java.util.Date", "java.sql.Timestamp", "java.sql.Date", "java.sql.Time" -> true; default -> false; }; } @Override public Object apply(Class fieldType, Object value) { String targetType = fieldType.getName(); long time = getTime(value); return switch (targetType) { case "java.util.Date" -> new Date(time); case "java.sql.Date" -> new java.sql.Date(time); case "java.sql.Time" -> new Time(time); case "java.sql.Timestamp" -> new Timestamp(time); default -> null; }; } /** * 把各种日期时间类型转换为毫秒数 * * @param value 日期时间 * @return 毫秒数 */ private long getTime(Object value) { return switch (value) { case Date date -> date.getTime(); case LocalDateTime localDateTime -> localDateTimeToDate(localDateTime).getTime(); case LocalDate localDate -> localDateToDate(localDate).getTime(); case LocalTime localTime -> localTimeToDate(localTime).getTime(); default -> throw new RuntimeException("Unable to convert " + value.getClass() + "."); }; } private Date localDateTimeToDate(LocalDateTime localDateTime) { ZoneId zoneId = ZoneId.systemDefault(); Instant instant = localDateTime.atZone(zoneId).toInstant(); return Date.from(instant); } private Date localDateToDate(LocalDate localDate) { ZoneId zoneId = ZoneId.systemDefault(); Instant instant = localDate.atStartOfDay().atZone(zoneId).toInstant(); return Date.from(instant); } private Date localTimeToDate(LocalTime localTime) { ZoneId zoneId = ZoneId.systemDefault(); Instant instant = localTime.atDate(LocalDate.now()).atZone(zoneId).toInstant(); return Date.from(instant); } } ``` ###### 2)LocalDateConverter ```java import test00.lx.test08.TypeConverter; import java.sql.Date; import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; /** * 实现把查询数据转换为:java.time.LocalDate * * @author zqx * @date 2025-03-20 */ public class LocalDateConverter implements TypeConverter { @Override public boolean supports(Class fieldType) { return fieldType.equals(LocalDate.class); } @Override public Object apply(Class fieldType, Object value) { return switch (value) { case LocalDate localDate -> localDate; case LocalDateTime localDateTime -> localDateTime.toLocalDate(); case Date sqlDate -> sqlDate.toLocalDate(); case Timestamp timestamp -> timestamp.toLocalDateTime().toLocalDate(); default -> null; }; } } ``` ###### 3)LocalDateTimeConverter ```java import test00.lx.test08.TypeConverter; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; /** * 实现把查询数据转换为:java.time.LocalDateTime; * * @author zqx * @date 2025-03-20 */ public class LocalDateTimeConverter implements TypeConverter { @Override public boolean supports(Class fieldType) { return fieldType.equals(LocalDateTime.class); } @Override public Object apply(Class fieldType, Object value) { return switch (value) { case LocalDateTime localDateTime -> localDateTime; case LocalDate localDate -> localDate.atStartOfDay(); case LocalTime localTime -> localTime.atDate(LocalDate.now()); case Date sqlDate -> sqlDate.toLocalDate().atStartOfDay(); case Timestamp timestamp -> timestamp.toLocalDateTime(); case Time time -> time.toLocalTime().atDate(LocalDate.now()); default -> null; }; } } ``` ###### 4)LocalTimeConverter ```java import test00.lx.test08.TypeConverter; import java.sql.Time; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.LocalTime; /** * 实现把查询数据转换为:java.time.LocalTime; * * @author zqx * @date 2025-03-20 */ public class LocalTimeConverter implements TypeConverter { @Override public boolean supports(Class fieldType) { return fieldType.equals(LocalTime.class); } @Override public Object apply(Class fieldType, Object value) { return switch (value) { case LocalTime localTime -> localTime; case LocalDateTime localDateTime -> localDateTime.toLocalTime(); case Time time -> time.toLocalTime(); case Timestamp timestamp -> timestamp.toLocalDateTime().toLocalTime(); default -> null; }; } } ``` ## 三、内省 ### 1、概念 内省是反射的一个延伸或应用,实现**运行时**获取和设置Java对象的属性。 方便、灵活的实现对象属性的读和写操作 JavaBean对象:是一个符合特定规范的Java对象 - 公共类:必须声明为public,类名采用大驼峰命名法 ```java public class XxxXxx{ } ``` - 无参构造器:必须提供默认的公共无参构造方法 - 私有属性:属性使用private修饰,通过公共的getter和setter方法访问 - 序列化支持:通常实现java.io.Serializable接口以支持持久化或网络传输 ```java public class Preson[implements Serializable]{ //1.字段 - Field private String name; private int age; //2.缺省构造方法 //public Person(){} //3.属性 - 基于setter/getter方法实现 - 只有其中的某个方法即可 //属性是对字段进行了封装 //setter:针对属性封装的字段进行写操作 //getter:针对属性封装的字段进行读操作 } ``` ### 2、Java内省的核心类 - Introspector - BeanInfo - PropertyDescriptor - MethodDescriptor - IntrospectionException #### Introspector类 Introspector是一个工具类,它提供了一种标准方法来了解目标JavaBean支持的属性、事件和方法。可以通过调用**Introspector.getBeanInfo()**方法**获取**封装JavaBean相关信息的**BeanInfo对象** 1. static BeanInfo getBeanInfo(Class beanClass) 获取beanClass对应的JavaBean对象的BeanInfo的封装 ```java //在获取属性、方法、事件时会也能获取到其父类的属性、方法、事件 BeanInfo beanInfo = introspector.getBeanInfo(Student.class); ``` 2. static BeanInfo getBeanInfo(Class beanClass, Class stopClass) 获取 beanClass 对应的 Javabean 对象的 BeanInfo 的封装。其中,stopClass 指定停止分析(解析)的基类。分析中将忽略 stopClass 或其基类中的任何方法、属性、事件。 ```java //将会忽略Object中的属性、方法、事件 BeanInfo beanInfo = introspector.getBeanInfo(Student.class,Object.class); ``` #### BeanInfo接口 BeanInfo对象是Java内省的核心对象之一,用于描述JavaBean的所有属性和方法信息。它包含了一个或多个PropertyDescriptor对象和MethodDescriptor对象,用于描述JavaBean的所有属性和方法信息。 1. getPropertyDescriptors() 返回的是一个PropertyDescriptor[]数组,用于存储JavaBean的所有属性的信息每个PropertyDescriptor对象包含了一个属性的名称、类型和读写方法等信息。 ```java //获取到指定对象的BeanInfo对象 BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass()); //根据BeanInfo对象获取到JavaBean所有属性对象 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); ``` 2. getMethodDescriptors() 返回一个MethodDescriptor数组,用于描述JavaBean的所有方法信息。每个MethodDescriptor对象包含了一个方法的名称、参数类型和返回值类型等信息。 ```java //获取到指定对象的BeanInfo对象 BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass()); //根据BeanInfo对象获取到JavaBean所有方法对象 MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors(); ``` #### PropertyDescriptor类 > 注意:内省是根据标准的 setter 或 getter 方法来获取属性信息。 > > 如果在类中定义了属性,当时没有提供标准的getter/setter方法,那么在使用此对象获取属性时将获取不到指定属性信息;但是没有定义属性,但是提供了标准的getter/setter方法,是能够获取到指定属性信息的 PropertyDescriptor类是用于描述 JavaBean 的一个属性信息。它包含了一个属性的名称、类型和读写方法等信息。 ```java //获取PropertyDescriptor类 - 单个 //语法:PropertyDescriptor propertyDescriptor = new PropertyDescriptor(属性名, 指定对象的Class对象); PropertyDescriptor propertyDescriptor = new PropertyDescriptor("name", Dog.class); ``` - getName():该方法返回属性的名称 ```java PropertyDescriptor propertyDescriptor = new PropertyDescriptor("name", Dog.class); propertyDescriptor.getName(); // name ``` - getPropertyType():返回属性的类型 ```java PropertyDescriptor propertyDescriptor = new PropertyDescriptor("name", Dog.class); Class typeClazz = propertyDescriptor.getPropertyType(); // String.class ``` - isPrimitive():判断数据类型是否为基本数据类型 ```java PropertyDescriptor propertyDescriptor = new PropertyDescriptor("name", Dog.class); Class typeClazz = propertyDescriptor.getPropertyType(); // String.class typeClazz.isPrimitive();//false ``` - getReadMethod():返回属性的读方法,即getter方法。 ```java PropertyDescriptor propertyDescriptor = new PropertyDescriptor("name", Dog.class); Method getter = propertyDescriptor.getReadMethod();//getName() //调用getter方法 getter.invoke(dog); ``` - getWriteMethod():返回属性的写方法,即setter方法。 ```java PropertyDescriptor propertyDescriptor = new PropertyDescriptor("name", Dog.class); Method setter = propertyDescriptor.getWriteMethod();//getName() //调用getter方法 getter.invoke(dog,参数列表); ``` #### MethodDescriptor类 MethodDescriptor类是用于描述JavaBean的一个方法信息。它包含了一个方法的名称、参数类型和返回值类型等信息 - getName():返回方法的名称。 - getParameterTypes():返回方法的参数类型数组。 - getReturnType():返回方法的返回值类型。 调用 getMethod() 方法可以获得指定方法的 Method 类型对象,然后就可以利用反射执行指定的方法。 #### IntrospectionException类 IntrospectionException类是 Java 内省过程中可能会抛出的异常类。它表示 Java 内省过程中发生的异常情况,例如 JavaBean 的属性或方法不存在、不合法的属性或方法等。 ## 四、泛型 ### 1、概念 **泛型就是变量类型的参数化**。也就是说,所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别称为**泛型类**、**泛型接口**和**泛型方法**。 ### 2、优点 1. 类型安全指定具体类型后,Java编译器会对错误的类型在编译时被捕获,而不是在运行时当作ClassCastException展示出来。从而提高程序的安全性和可靠性。 ```java List list = new ArrayList() ; //list对象只能存放String对象,否则会编译出错 ``` 2. 消除强制类型转换 ```java //需要强制类型转换 List list = new ArrayList() ; String str = (String)list.get(0) ; //不需要类型转换 List list = new ArrayList() ; String str = list.get(0) ; ``` 3. 潜在的性能收益 ### 3、分类 #### 一、泛型类 1. 语法 ```java [访问修饰符] class 类名<泛型1,泛型2,...> { 泛型1 泛型成员1 ; 泛型2 泛型成员2; ... } class Dog{ T t; E e; ... } ``` 2. 要点 2.1)泛型相当于类中一种特殊的类型。这种类型的特点是:在实例化该类时可指定为某种具体的实际类型。(不能是基本类型) 2.2)泛型1、泛型2…可以是JAVA的任意合法标识符(大写),如:T、E、K、V 2.3)`class People`声明了一个泛型类,这个A没有任何限制,实际上相当于Object类型,实际上相当于 `class People`。 ```java class People == class People ``` 2.4)与Object泛型类相比,使用泛型所定义的类在声明和构造实例的时候,可以使用“<实际类型>”来一并指定泛型类型持有者的真实类型。如: ```java People p1= new People() ; People p2 = new People() ; People p3 = new People() ; ``` 2.5)当然,也可以在构造对象的时候不使用尖括号指定泛型类型的真实类型,但是你在使用该对象的时候,就需要强制转换了。如: ```java People p = new People("张三","男",new Cat("小花猫", 3, "雌")) ; Cat c = (Cat)p.getPet() ; ``` 实际上,当构造对象时不指定类型信息的时候,默认会使用Object类型,这也是要强制转换的原因。 #### 二、泛型类继承 1)父类 ```java /** * 泛型类 * * @author zing */ public class Animal { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } ``` 2)子类 ```java /** * 泛型类继承 -> 同时指定父类的泛型 * * @author zing */ public class Dog extends Animal{ } /** * 泛型类继承 -> 同时指定父类的泛型,以及子类也可以定义为泛型类 * * @author zing */ public class Cat extends Animal { private T1 data1; private T2 data2; public T1 getData1() { return data1; } public void setData1(T1 data1) { this.data1 = data1; } public T2 getData2() { return data2; } public void setData2(T2 data2) { this.data2 = data2; } } ``` 3)测试 ```java public class MainTest { public static void main(String[] args) { Animal xb = new Cat() ; Animal xh = new Dog() ; // 以下代码报错,因为Dog继承动物时,指定父类的泛型为Integer // Animal xl = new Dog() ; } } ``` #### 三、泛型接口 1)语法 ```java [访问修饰符] interface 接口名<泛型1,泛型2,...> { 泛型1 泛型成员1 ; 泛型2 泛型成员2 ; ... } ``` 2)案例 ```java /** * 第一:定义泛型接口 */ public interface GenericTest { // 1. 常量 public static final String name = "AAA"; // 2.抽象方法 public void sayHello(T sth); } /** * 第二:实现接口 * 当实现泛型接口时,必须指定具体的数据类型 */ public class GenericTestIntegerImpl implements GenericTest { @Override public void sayHello(Integer sth) { System.out.println("你好,"+sth); } } /** * 第二:实现接口 * 当实现泛型接口时,必须指定具体的数据类型 */ public class GenericTestStringImpl implements GenericTest { @Override public void sayHello(String sth) { System.out.println("你好,"+sth); } } /** * 测试 * 第三:使用泛型接口 */ public class MainTest { public static void main(String[] args) { GenericTest gt1 = new GenericTestIntegerImpl() ; gt1.sayHello(100) ; GenericTest gt2 = new GenericTestStringImpl() ; gt2.sayHello("张三") ; } } ``` #### 四、泛型方法 1)语法 ```java [访问修饰符] <泛型列表> 返回值 方法名(参数列表) { } ``` 2)要点 2.1)在泛型列表中声明的泛型,可用于该方法的返回值类型声明、参数类型声明和方法代码中的局部变量的类型声明。 ```java public interface ProxyHandler { T createProxy(Class targetClass); } ``` 2.2)类中其他方法不能使用当前方法声明的泛型。 3)案例 ```java /** * 第一:定义泛型方法 */ public class GenericTest { // 定义一个泛型 // 注:泛型方法中的泛型只作用于本方法 public void sayHello(T sth) { // 泛型定义局部变量 T a = sth; System.out.println(a.getClass().getSimpleName() + ":" + sth); } // 定义多个泛型 public void sayGoodBye(T1 s1, T2 s2, T3 s3) { System.out.println(s1.getClass().getName()+":"+s1); System.out.println(s2.getClass().getName()+":"+s2); System.out.println(s3.getClass().getName()+":"+s3); } } /** * 第二:调用泛型方法 * 直接传递具体数据即可 */ public class MainTest { public static void main(String[] args) { GenericTest gt = new GenericTest() ; gt.sayHello(100) ; gt.sayGoodBye("AA", 100, true) ; } } ``` #### 五、泛型数组 > 可以声明泛型数组,但不能直接实例化泛型数组。 > > ```java > // 错误 > T[] arr = new T[3] ; > ``` 解决方法一:通过setter方法初始化 ```java public class GenericTest { T[] arr; public T[] getArr() { return arr; } /** * 方法一:通过setter方法实例化泛型数组 * * @param arr */ public void setArr(T[] arr) { this.arr = arr; } public GenericTest() { } } public class MainTest01 { public static void main(String[] args) { GenericTest gt = new GenericTest<>() ; // 在这里实例化数组对象,传递给setter方法 String[] str = {"aa","bb","cc"} ; gt.setArr(str) ; String[] array = gt.getArr() ; for (String s : array) { System.out.println(s); } } } ``` 解决方法二:通过`java.lang.reflect.Array`的`newInstance(Class,int)`,创建指定长度T类型的数组 ```java public class GenericTest { T[] arr; /** * 方法二:通过java.lang.reflect.Array的newInstance(Class,int),创建指定长度T类型的数组 * 构造方法或使用自定义工具类 * @param clazz * @param length */ public GenericTest(Class clazz, int length) { arr = (T[]) Array.newInstance(clazz, length); } } public class MainTest01 { public static void main(String[] args) { GenericTest gt = new GenericTest<>(Student.class,3) ; } } ``` #### 六、嵌套泛型 > 在某泛型类中引用一个泛型类(包含)。 1)泛型类:宠物 ```java /** * 定义泛型类 - 宠物 * * @author zing */ public class Pet { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } ``` 2)泛型类:主人 ```java /** * 定义一个泛型类 - 主人 * * @author zing */ public class People { /** * 嵌套泛型:在泛型类中使用另一个泛型类 -- 泛型类People嵌套使用泛型类Pet * * 注意:在这里,泛型类Pet指定的泛型为String */ private Pet pet; public Pet getPet() { return pet; } public void setPet(Pet pet) { this.pet = pet; } } ``` 3)测试 ```java /** * 嵌套泛型:在某泛型类中引用一个泛型类(包含) * * @author zing * */ public class MainTest { public static void main(String[] args) { People zs = new People(); Pet xh = new Pet(); zs.setPet(xh); // 以下代码报错:因为People类中的Pet指定的泛型为String Pet animal = new Pet() ; // zs.setAnimal(animal) ; } } ``` ### 4、边界 #### 一、使用边界的原因 当在使用泛型参数时,我们想要使用泛型参数中的某个方法,但是此时的泛型参数的值是不确定的,将无法使用其中的方法,当我们指定边界后,就可以使用了指定的边界的方法 ```java public static int compare(T t,T y){ //这将报错 return t.compareTo(y); } ``` #### 二、语法 1. 上边界 ```java // 语法一:指定一个边界 // 语法二:指定多个边界 ``` 2. 下边界 ```java //语法:指定下边界 ``` - Xxx是边界类型,它可以是类也可以是接口,这个边界类型称之为**上边界** - 类型实参(T)必须是这个边界类型或者是其子类型(包括子接口)或实现类,其它类型则报错 - extends关键字在这里不是继承的含义,应该理解为: - T是Xxx接口或是Xxx接口的子接口 - T是实现Xxx接口的实现类 - T是Xxx类 - T是Xxx类的子类 - 如果不限制边界,则默认是Object - `` - `` ```java // 限定T是实现Comparable接口的任意实现类,则就可以识别出compareTo方法,编译才通过 // 否则,默认为Object对象,并不存在compareTo方法,因此编译失败 public static int compare(T x, T y){ return x.compareTo(y); } ``` ### 5、通配符 > 由于Java泛型的**不变性**,父类泛型不能接收子类泛型,因此需要通配符实现**协变**、**逆变**。 ```java // 多态 - 协变性 - 父类对象可以接收所有的子类对象 Animal obj = new Dog() ; // 结果以上代码分析,下面的语句是否正确? List list = new ArrayList() ; ``` #### 一、型变 概念:表示泛型类型的父子关系。 ##### 1、不变 赋值运行符左右两边的泛型必须一致 ```java // 泛型是不变的 - 必须指定相同类型的泛型 ArrayList list1 = new ArrayList(); List list2 = new ArrayList(); // 错误 List list2 = new ArrayList(); ArrayList list2 = new ArrayList(); ArrayList list = new ArrayList(); ``` ##### 2、协变 子类型的泛型可以赋值给父类型的泛型 ```java // 语法:通配符上边界 - // 子类型的泛型Cat可以赋值给父类型的泛型Aniaml List list = new ArrayList() ; ``` 说明:`?`代表未知类型,`extends`关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。 注意:为了确保类型安全,**无法调用**协变后的对象中**含泛型参数的方法**,协变集合不能添加元素 ```java // 实现协变 List list = new ArrayList() ; // 添加元素 -- 错误 - add 是一个含泛型参数的方法 - 为了确保类型安全,无法调用协变后的对象中含泛型参数的方法 list.add(new Cat("小花")) ; list.add(new Dog("小朵")) ; ``` 解决: ```java // 创建泛型集合,并初始化数据 List list = new ArrayList(); list.add(new Cat("小花")) ; list.add(new Cat("小朵")) ; // 协变 List list = list ; // 查询 - 读取集合中的元素 Cat cat1 = list.get(0); Cat cat2 = list.get(1); ``` 应用场景:泛型的集合作为生产者(即**只从中读取数据,而不写入数据**)时,用 extends 关键字 ##### 3、逆变 父类型的泛型可以赋值给子类型的泛型 ```java // 语法:通配符下边界 - // 父类型的泛型Animal可以赋值给子类型的泛型Cat List list = new ArrayList() ; ``` 说明:`?`代表未知类型,`super` 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类。 注意:逆变集合可以添加T类型及其子类型的数据,但不能取集合中的元素 ```java // 本来,Pig 是 Animal的子类型 - 通过逆变操作后,Animal 变成了 ? super Pig 的子类型 List list = new ArrayList(); // 逆变集合可以添加Pig类及其子类,因为正式接收的是包含Pig或其父类的集合, // 如上面的 ArrayList ,当然也可以是 ArrayList // 当添加CartoonPig、Pig类型对象时,Pig类型或Animal类型都可以接收 list.add(new CartoonPig("乔治")); list.add(new Pig()); // 错误:逆变可以添加元素,但不能获取元素 - 获取的元素类型为 Animal 类型,向下转型为Pig类型失败 // Pig object = list.get(0); // 解决一:强制类型转换 Pig obj1 = (Pig)list.get(0); // 解决二:使用Object接收 Object obj2 = list.get(0) ; System.out.println(obj1); System.out.println(obj2); ``` - 应用场景:当集合作为消费者(即只向其中写入数据,而不读取数据)时,用super关键字 ## 五、注解 ### 1、概念 1)注释 标记源码,便于程序员阅读和理解代码 - 标记位置 - 任何位置 - 标记方式 - 单行注释 // 内容 - 多行注释 /* 内容 */ - 文档注释 /** 内容 */ 2)注解 标记源码,并对其进行解析,从而增强或拓展程序的功能 - 标记位置 - 包、**接口、类、字段、方法、方法参数**、局部变量 - 标记方式 - @注解名称 - @Override - @注解名称(成员1=值,...成员N=值) - WebServlet(value="/online") - 如果成员为value可以省略不写 - WebServlet("/online") - 解析方式 - 反射 - 运行时 - 注解的生命周期必须保留在运行时 ### 2、常见注解 - @Override:用于修饰此方法覆盖了父类的方法(而非重载) - @Deprecated:用于修饰已经过时的方法 - 为什么要修饰为过时的而不直接删除 - 为了jdk的向下兼容 - 高版本兼容低版本 - @SuppressWarnings:用于通知java编译器禁止特定的编译警告 第三方注解 - **Spring** - @Autowired - @Service - @Repository - **MyBatis** - @InsertProvider - @UpdateProvider - @Options ### 3、元注解 - 原始 > 自定义注解时,所使用的注解,称之为元注解 > > 我们是通过元注解来自定义注解 元注解主要有以下四个: - @Target - 设置自定义注解标记在哪里 - CONSTRUCTOR:构造方法声明 - FIELD:字段声明 - LOCAL_VARIABLE:局部变量声明 - Method:方法声明 - PACKAGE:包声明 - PARAMETER:参数声明 - TYPE:类、接口 ```java @Target(ElementType.TYPE) @Target({ElementType.TYPE,ElementType.FIELD}) ``` - @Retention - 设置注解的生命周期 - SOURCE:只在源码显示,编译时会丢弃 - CLASS:编译时会记录到class中,运行时忽略 - RUNTIME:运行时存在,可以通过反射读取 ```java @Retention(RetentionPolicy.SOURCE) ``` - @Inherited - 设置允许子类继承父类的注解,默认不会被子类继承 - @Documented - 设置注解是否生成文档 ### 4、自定义注解 第一:定义注解 ```java @元注解 public @interface 注解名称 { 数据类型 成员名称1() [default 默认值]; ... 数据类型 成员名称N() [default 默认值]; } ``` 第二:解析注解 - 反射 - 获取要操作类的Class对象 ```java Class clazz = 类.class; ``` - 基于Class对象进行相关的操作 - 获取字段(Field)、方法(Method)等的对象 - 如果注解是位于类上 则省略 ```java //Field f = clazz.getDeclaredField("字段名"); || Method m = clazz.getDeclaredMethod("方法名"); Field f = clazz.getDeclaredField("name"); ``` - 判断对象中是否存在注解 ```java boolean bl = 对象.isAnnotationPresent(注解.class); ``` - 获取字段、方法等的注解对象(Annotation) ```java 注解对象 annotation = 对象.getAnnotation(注解对象.class); ``` - 获取注解的值 ```java String 变量 = 注解对象.成员名称(); ``` - 基于注解的值进行相关的处理,从而实现相关功能的增强 第三:使用注解 ```java //语法一 - 标识注解 @注解名 //语法二 - 只有一个成员,且成员名为value @注解名("值") //语法三 - 多个成员 @注解名(成员名1=值1,...成员名N=值N) ``` ## 六、Lambda表达式&流(Stream) ### 一、内部类 #### 1、概念 在某个类中,嵌套定义的类 - 类中类 - 在类中定义类 - 内部类是指在一个外部类的内部再定义一个类 - 内部类作为外部类的一个成员,并且依附于外部类而存在的 - 内部类可以是静态的,可以用 protected 和 private 修饰。而外部类不可以,外部类只能使用public或default修饰 #### 2、作用 - 使用内部类可以使程序更加简洁,便于命名规范和划分层次结构,提供了更好的封装性,除了所在的外部类,其他类都不能访问 - 使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,一个子类只能继承一个父类,但是使用了内部类之后,内部类可以同时继承到多个父类的属性与发放 ```java public interface Father { } public interface Mother { } public class Son implements Father, Mother { } public class Daughter implements Father{ class Mother_ implements Mother{ } } ``` 在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整 ```java public class Father{ public void f() { } } public class Mother{ public void m() { } } public class Son extends Father{ class Daughter extends Mother{ public void aa() { m(); // 调用 Mother 类的方法 f(); // 调用 Father 类的方法 } } public void bb() { super.f(); // 调用 Father 类的方法 } } ``` #### 3、分类 - 成员内部类 - 静态内部类 - 局部内部类 - 匿名内部类 #### 4、实现 ##### 1)成员内部类 与成员变量、成员方法类似,作为某个类的成员定义的类,叫成员内部类 ```java //外部类 - 访问修饰符只能是public/default(缺省) [访问修饰符] class 类名{ //成员变量 private int age; //成员内部类 - 访问修饰符与成员变量和成员方法一样可以使用四个中的任意某个 [访问修饰符] class 内部类名{ //内部类成员 - 可以和外部类一样正常声明成员 } //成员方法 [访问修饰符] 数据类型 方法名([参数列表]) { 内部类名 对象 = new 内部类名() ; 对象.成员... } } ``` 成员内部类实例化 ```java //语法一 外部类 外部类对象 = new 外部类(); 外部类对象.内部类名称 内部类对象 = 外部类对象.new 内部类名称(); //语法二 外部类名称.内部类名称 内部类对象 = new 外部类对象().new 内部类对象(); ``` ##### 2)静态内部类 与静态变量、静态方法类似,作为某个类的静态成员定义的类,叫成员内部类 ```java //定义外部类 class 外部类名称{ //外部类成员 [访问修饰符] static class 内部类名称{ //内部类成员 - 静态内部类不能访问非静态成员 } } ``` 静态内部类实例化 ```java 外部类名称.内部类名称 内部类对象 = new 外部类名称.内部类名称(); ``` ##### 3)局部内部类 与局部变量类似,在方法内定义的类称为局部内部类 只能在方法内使用,想要在外部使用需要返回 - 不建议 ```java class 外部类名称{ [访问修饰符] 数据类型 方法名([参数列表]){ class 内部类名称{ //内部类成员 } } // 5.创建局部内部类对象: // 局部内部类名 对象 = new 局部内部类名() ; // 6.调用属性或方法 } ``` ##### 4)匿名内部类 匿名内部类是一种特殊的局部内部类,它是通过匿名类实现接口 - 为Lambda表达式做铺垫 ```java //作为方法参数来使用 new 外部类.方法(new 接口|抽象类|父类(){ 接口方法的实现(); 或 重写抽象类或父类的方法(); }); 接口名称 对象名 = new 接口名称(){ 重写抽象方法 } ``` ### 二、Lambda表达式 #### 1、概念 Lambda表达式是函数式接口中抽象方法的实现 注意:如果一个接口中,存在多个抽象方法,Lambda表达式无法实现,可以考虑匿名内部类 #### 2、函数式接口 有四种方法实现函数式接口: 1. 传统方法 - 接口实现类 2. 匿名内部类 3. Lambda表达式 4. 方法引用 **必须有且只有一个抽象方法的接口,称之为函数式接口** 一般使用FunctionalInterface注解来显示声明函数式接口 注意:函数式接口除了有一个抽象方法外,也可以包含以下内容: ```java @FunctionalInterface public interface 接口名称{ //抽象方法(有且只有一个) //常量(0~N) //默认方法(0~N) //静态方法(0~N) //重新定义object类的方法(0~N) } ``` #### 3、语法 ```java //Lambda表达式 - 方法 - 函数式接口抽象方法的 实现 函数式接口 对象 = ([参数列表]) -> { 方法体 [返回值] }; ``` 要点: 1. 使用空()表示没有参数 ```java //void test(); public class MainTest { public static void main(String[] args) { InterfaceTest it = () -> { System.out.println("你好"); }; it.test(); } } ``` 2. 方法体有且只有一行代码的情况可以省略花括号,如果有return,return必须省略 ```java //int test() public class MainTest { public static void main(String[] args) { InterfaceTest it = () -> System.out.println("你好"); it.test(); } } ``` ```java //int test(int a,int b) public class MainTest { public static void main(String[] args) { //return省略 InterfaceTest it = (int a,int b) -> a + b; it.test(1,1); } } ``` 3. 方法中的参数类型可以省略不写,参数类型可以有编译器根据上下文推断得出(参数名称也可不同(不建议)) ```java //int test(int a,int b) public class MainTest { public static void main(String[] args) { //参数类型省略 InterfaceTest it = (a,b) -> a + b; it.test(1,1); } } ``` 4. 只有一个参数的情况下,可以省略参数的括号 ```java //void test(String a) public class MainTest { public static void main(String[] args) { //参数类型省略 InterfaceTest it = a -> System.out.println("你好"+a); it.test("张三"); } } ``` #### 4、常用的函数式接口 - java.lang.Runnable - java.util.concurrent.Callable - java.security.PrivilegedAction - **java.util.Comparator** - java.io.FileFilter - java.beans.PropertyChangeListener ```java /*java.util.Comparator - 比较器 int compare(T o1,T o2):比较两个数据的大小或顺序 - 比较条件可自定义 o1 < o2:负整数 o1 = o2:0 o1 > o2:正整数*/ //可以在Arrays.sort()作为第二个参数使用 //Arrays.sort(数组,Comparator) Integer[] arr = {5,3,4,9,1,7,2,6}; //对整型数组进行升序排序 Arrays.sort(arr,new Comparator(){ @Override public int compare(Integer o1,Integer o2){ return o1-o2; } }); //使用Lambda表达式对数组进行降序排序 Arrays.sort(arr,(o1,o2) -> o2-o2); ``` #### 1)`Supplier`接口 ```java @FunctionalInterface public interface Supplier { T get(); } ``` Supplier是一个**供给型接口**(生产型接口)。用来获取一个泛型参数指定类型的对象数据。也就是说,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据返回。 > 课堂案例:定义一个参数为`Supplier`接口的print方法,分别使用匿名内部类和Lambda表达式,打印输出:好好学习,天天向上 > > 课堂案例:定义一个参数为`Supplier`接口的getMax方法,分别使用匿名内部类和Lambda表达式,实现计算一个整型数组中最大的值。 > > 课堂练习:定义一个学生类(姓名、年龄),使用`Supplier`接口,打印输出年龄最小的学生,格式为:姓名-年龄。 #### 2)`Consumer`接口 ```java @FunctionalInterface public interface Consumer { void accept(T t); default Consumer andThen(Consumer after) { // ... } } ``` Consumer是一个**消费型接口**。与Supplier接口相反,它不是用于生产一个数据,而是用于消费一个数据。当泛型指定什么类型,就可以使用accept方法消费什么类型的数据。至于具体怎么消费,根据需求实现。 > 课堂案例:定义一个方法:`change(String content, Consumer consumer)`,实现把某字符串转换为大写并打印输出。 > > 课堂练习:定义一个方法:实现判断一个分数是否合格,并打印输出结果。 `andThen`,Consumer接口中定义的默认方法,把两个Consumer接口组合到一起,对数据按顺序依次进行消费。 > 课堂案例:定义两个Consumer,分别用于把某字符串转换为大写输出和小写输出。 > > 课堂练习:定义一个学生类(姓名、年龄);定义一个数组,存储若干学生;定义三个Consumer,分别用于打印输出姓李的学生信息、名字有三个字的学生信息、年龄大于18岁的学生。 #### 3)`Predicate`接口 ```java @FunctionalInterface public interface Predicate { boolean test(T t); // ... } ``` Predicate是一个**断定型接口**。用于对某种数据类型的数据进行判断,结果返回一个boolean值。符合判断,返回True,否则为False。 > 课堂案例:定义一个方法:`check(String sex, Predicate predicate)`,实现对性别进行合法性的判断,性别要求要么是男,要么是女。 Predicate接口中,定义了以下四个默认方法 - and:与 - or:或 - negate:非 - isEqual:匹配是否相等 #### 4)`Function`接口 ```java @FunctionalInterface public interface Function { R apply(T t); // ... } ``` Function是一个**函数型接口**。用于根据一个T类型的数据得到另一个R类型的数据;前者称为前置条件,后者称为后置条件。 > 课堂案例:把一个字符串型的数据转换为整型数据。 > > 课堂练习:把字符串”张三,18”转换为学生对象(学生类同上) ```java public static Integer toInteger(String s, Function function){ return function.apply(s) ; } ``` 默认方法:`andThen`,用于组合操作(同上) > 课堂案例:把字符串”张三,18”转换为学生对象,获取学生对象的年龄,加1岁后转换为整数。 ```java public static Integer change(String s, Function function1,Function function2){ return function1.andThen(function2).apply(s) ; } ``` ### 三、方法引用 #### 1、概念 Lambda表达式本质上是一个方法,是一个实现函数式接口的方法 **函数式接口抽象方法的实现** 如果已经存在了一个方法,可以实现函数式接口,则不需要定义Lambda表达式,而是直接引用已经存在的方法,这种函数式接口的实现称之为方法引用 使用一个已存在的方法来实现函数式接口,而不需要定义Lambda表达式 #### 2、使用场景 在Lambda表达式中,如果Lambda体的操作,已经**有实现的方法**了,那么就可以使用**方法引用** #### 3、要求 函数式接口的抽象方法的参数列表,必须与方法引用的方法的参数列表保持一致,而返回值类型原则上也需要保持一致 - 如果函数式接口的抽象方法的返回值为void,而方法引用所引用的方法有返回值,则忽略返回值 - 函数式接口的抽象方法的返回值类型(如:double)必须能兼容方法引用的方法返回值类型(如:int) #### 4、语法 - 使用操作符 “::” 将类(或对象)与方法名分割开来(是已经实现好的方法名及其所在的类或对象); - 可以看成左边的类或对象是调用者,右边的方法是被调用者。 以下是六种不同类型的方法引用: | 类型 | 例子 | | :------------------------- | :---------------------------------- | | 静态方法的引用 | `类名::静态方法` | | 成员方法的引用 | `对象::成员方法` | | 特定类型方法的引用 | `类名::成员方法` | | 构造器引用 | `类名::new` | | 数组引用 | `数据类型[]::new` | | 父类方法引用、本类方法引用 | `super::成员方法`、`this::成员方法` | ### 四、Optional #### 1、概念 Optional是一个特殊的包装类,当一个数据可能为null时,可以使用Option包装,然后通过一些方法来判断是否存在值,或者在不存在值的情况下返回一个默认值 #### 2、创建Optional对象 - Optional.empty() - 创建一个空的Optional对象 ```java Optional emptyOptional = Optional.empty(); ``` - Optional.of() - 创建一个包含非null值的Optional实例 - 不能为null ```java // 正确,创建一个值为 Hello 的 Optional 对象 Optional nonEmptyOptional = Optional.of("Hello"); // 错误,of 方法不允许为 null Optional op = Optional.of(null); ``` - Optional.ofNullable() - 创建一个可以包含null值的Optional实例 ```java // 正确,创建一个值为 Hello 的 Optional 对象 Optional nonEmptyOptional = Optional.ofNullable("Hello"); // 正确,ofNullable 方法允许为 null Optional emptyOptional = Optional.ofNullable(null); ``` #### 3、常用API方法 ##### 1、isPresent() 判断Optional对象是否存在值,有值返回true,为null返回false ```java Optional optional = Optional.ofNullable("Hello"); boolean flag = optional.isPresent();//true ``` ##### 2、get() 获取Optional实例中的值,如果不存在则会抛出异常,所以,通常配合isPresent使用 ```java Optional optional = Optional.ofNullable("Hello"); if(optional.isPresent()){ //Hello System.out.println(optional.get()); } //抛出 NoSuchElementException 异常 Optional emptyOptional = Optional.empty(); System.out.println(emptyOptional.get()); ``` ##### 3、ifPresent(Consumer action) 如果Optional实例中存在值,则执行指定操作,否者什么也不做 ```java Optional optional = Optional.ofNullable("Hello"); optional.ifPresent((t) -> { System.out.println(t); }); optional.ifPresent(System.out::println); ``` ##### 4、orElse(默认值) 如果存在值,则返回对应的值,不存在则返回默认值 ```java Optional optional = Optional.ofNullable("Hello"); String str = optional.orElse("你好");///Hello //Optional optional = Optional.ofNullable(null); //String str = optional.orElse("你好");///你好 ``` ##### 5、orElseGet 如果 Optional 实例存在值,则返回相应的值,否则执行指定的Supplier函数并返回其结果 ```java // Optional optional = Optional.of("好好学习"); Optional optional = Optional.ofNullable(null); // 如果 optional 对象不存在值,则输出:天天向上 String str = optional.orElseGet(() -> "天天向上"); System.out.println(str); ``` ##### 6、orElseThrow 如果 Optional 实例中不存在值,则抛出指定的异常。 ```java Optional optional = Optional.empty(); optional.orElseThrow(() -> new RuntimeException("Value not found!")); ``` ##### 7、map(Function mapper) 如果存在一个值,则将提供的映射函数应用于它,如果结果为非空,则返回一个描述结果的 Optional。否则返回一个空的 Optional,也可以用于类型的转换 ```java // Optional optional = Optional.empty(); Optional optional = Optional.of("Hello"); // 将optional中的字符串转换为大写,如果optional不存在值,则返回DEFAULT String upperCase = optional.map(String::toUpperCase).orElse("DEFAULT"); // 输出:HELLO System.out.println(upperCase); ``` ## 五、流(Stream) > List list = Arrays.asList(参数列表); > > 使用Arrays.asList()方法直接初始化一个list集合,不需要使用add()方法慢慢添加数据 ### 1、概念 简单方便的对**集合或数组**进行处理 Stream(流)是一个来自数据源的元素队列 - 数据源:流的来源,指的是集合或数组 - 元素队列:元素是**特定类型**的对象,形成一个队列 - Stream 并不会存储元素,而是根据需要进行处理,如filter、map、skip等 - Stream流有以下两个操作,都提供了相关的 API 方法 - 延迟操作:对 Stream 进行一系列的中间操作,但不会被马上执行,等待终结操作才会一次性被执行 - 终结操作:终结操作后,则一系列的延迟操作被执行,并产生相应的结果,流就被关闭了 ### 2、操作步骤 第一:定义数据源(集合、数组) 第二:把数据源转换为流(Stream) - 集合对象 - 顺序流:集合对象.stram() - 并行流:集合对象.parallelStream() - 数组对象 - Arrays.stream(数组) - Stream.of(元素集合|数组) - 创建无限流 - 迭代:Stream.iterate(final T seed,final UnaryOperator f) - 生成:Stream.generate(Supplier s) 第三:操作 - 进行一个或多个中间操作 - 使用终止操作产生一个结果,Stream就不会再被使用了 ### 3、获取流 #### 1)集合对象 在java.util.Collection接口中,加入了默认方法stream()用于获取流,因此所有实现类都可以通过此方法获取流 - 语法一:顺序流 Stream<数据类型> 流对象 = 集合对象.stream(); - 语法二:并行流 Stream<数据类型> 流对象 = 集合对象.parallelStream(); ```java // 第一:定义数据源(集合、数组) List list = Arrays.asList(1, 2, 3, 4, 5); // 第二:把数组或集合转换为流对象 // 1)获取顺序流 Stream stream1 = list.stream(); // 2)获取并行流 Stream stream2 = list.parallelStream(); // 其它集合,获取流对象 // 1、Set集合 Set c2 = new HashSet() ; Stream s2 = c2.stream() ; // 2、Map合集 Map c3 = new HashMap<>() ; // 2.1、键的集合 Stream s3 = c3.keySet().stream(); // 2.2、值的集合 Stream s4 = c3.values().stream(); // 2.3、键值对的集合 Set> entries = c3.entrySet(); Stream> s5 = entries.stream(); ``` #### 2)数组对象 在java.util.stream.Stream中,提供了静态方法of可以获取数组对应的流。 - 语法一 Stream<数据类型> 流对象 = Arrays.stream(元素集合|数组); - 语法二: Stream<数据类型> 流对象 = Stream.of(元素集合|数组); ```java String[] arr = {"aa","bb","cc"} ; // 1.使用 Arrays 获取流对象 Stream stream1 = Arrays.stream(arr) ; // 2.使用 Stream 获取流对象 Stream stream2 = Stream.of(arr); // 3.通过元素集合,获取流对象 Stream stream3 = Stream.of(1, 2, 3, 4, 5); ``` #### 3)创建无限流 - 迭代 public static Stream iterate(final T seed,final UnaryOperator f) - 生成 public static Stream generate(Supplier s) ```java // 1.获取流对象,流对象的元素从0开始,每迭代一次加2 - T apply(T t); Stream stream = Stream.iterate(0,t -> t + 2); // 2.获取流的前面 10 个元素 - limit方法对流进行截取,截取前面 N 个元素 stream.limit(10).forEach(System.out::println); // 生成一个产生10以内随机数的流对象 - T get(); Stream stream = Stream.generate(()->new Random().nextInt(10)); // 获取流的前面 10 个元素 - limit方法对流进行截取,截取前面 N 个元素 stream.limit(10).forEach(System.out::println); ``` ### 4、常用API方法 #### 1、延迟方法 > 在没有使用终止方法之前延迟方法不会执行 返回值类型仍然为Stream接口自身类型的方法,因此支持链式调用 ##### 1)筛选与切片 - filter:过滤满足条件的元素 ```java List list = Arrays.toList("张三","李四","王五","赵六","张安","张全"); //创建流对象 Stream stream = list.stream(); //使用过滤方法 //stream.filter(Predicate predicate) //boolean test(T t); //过滤出姓别为张的 - 在没有使用终止方法之前延迟方法不会执行 stream.filter(t -> {t.charAt(0) == '张'}); ``` - limit(N):获取Stream中前N个元素 ```java List list = Arrays.toList("张三","李四","王五","赵六","张安","张全"); //创建流对象 Stream stream = list.stream(); //获取集合中前两项元素 stream.limit(2); //使用链式调用 - 过滤后取前两项 stream.filter(t -> {t.charAt(0) == '张'}).limit(2); ``` - skip(N):跳过Stream中前面的N个元素 ```java List list = Arrays.toList("张三","李四","王五","赵六","张安","张全"); //创建流对象 Stream stream = list.stream(); //跳过集合中前两项元素 - "张三","李四" stream.skip(2); ``` - distinct:去重 ```java List list = Arrays.toList("张三","李四","王五","赵六","张安","张全","张全"); //创建流对象 Stream stream = list.stream(); //去掉重复数据 - 张全 stream.distinct(); ``` ##### 2)映射 - *map:把一个 T 类型转换(映射)为一个 R 类型的流 ```java List list = Arrays.toList("张三","李四","王五","赵六","张安","张全","张全"); //创建流对象 Stream stream = list.stream(); //使用map将String字符串数据转换为Student对象 stream.map((t) -> { Student stu = new Student(); stu.setName(t); return stu; }); ``` - mapToDouble:把一个 T 类型的流 转换为 另一个 Double 类型流 - mapToInt:把一个 T 类型的流 转换为 另一个 int 类型流 - mapToLong:把一个 T 类型的流 转换为 另一个 Long 类型流 - flatMap:将流中的每个元素转换为另一个流,然后将这些流合并成一个流 ##### 3)排序 - sorted ```java List list = Arrays.toList(5,3,9,1,6,4,2); //创建流对象 Stream stream = list.stream(); //默认升序 - 从小到大 stream.sorted(); ``` - sorted(Comparator comparator) ```java List list = Arrays.toList(5,3,9,1,6,4,2); //创建流对象 Stream stream = list.stream(); //支持一个参数 - Comparator comparator //使用Lambda表达式实现函数式接口实现降序 - 从大到小 stream.sorted((o1,o2) -> o2-o1); ``` - 组合:concat:把两个流合并为一个流 - `Stream.concat(stream1, stream2);` #### 2、终结方法 返回值类型不再是Stream接口自身类型的方法 ##### 1)遍历 forEach:逐一处理 - void forEach(Consumer action); ```java List list = Arrays.toList(5,3,9,1,6,4,2); //创建流对象 Stream stream = list.stream(); //排序后进行输出 stream.sorted().forEach((t) -> { System.out.println(t); }); ``` ##### 2)匹配与查找 - allMatch:检查是否匹配所有元素 ```java //验证集合中的所有元素是否都符合某个规则 //boolean allMatch(Predicate predicate) List list = Arrays.toList(5,3,9,1,6,4,2); //创建流对象 Stream stream = list.stream(); //集合中的所有元素都匹配 boolean flag = stream.sorted().allMatch((t) -> {t > 2 && t < 10}); ``` - anyMatch:检查是否至少匹配一个元素 - noneMath:检查是否没有匹配所有元素 ##### 3)查找 - findFirst:返回第一个元素 ```java //Optional findFirst() List list = Arrays.toList(5,3,9,1,6,4,2); //创建流对象 Stream stream = list.stream(); Optional firstInt = stream.sorted().findFirst(); ``` - findAny:返回当前流中的任意元素 ##### 4)统计 - count:返回流中元素的总数 ```java //Optional findFirst() List list = Arrays.toList(5,3,9,1,6,4,2); //创建流对象 Stream stream = list.stream(); Integer num = stream.sorted().count(); ``` - max:返回流中最大值 - min:返回流中最小值 ##### 5)归约 归约是指将流中的元素按照指定的规则逐个进行操作,最终将它们归约(或者合并)为一个最终结果的过程。 归约操作可以是求和、求积、找出最大值或最小值等。 ```java // 返回 T T reduce(T identity, BinaryOperator accumulator); // 返回 Optional Optional reduce(BinaryOperator accumulator); ``` ```java //求和参数identity的值应为0、求积参数identity的值应为1 //T reduce(T identity, BinaryOperator accumulator); List numbers = Arrays.asList(1, 2, 3, 4, 5); // 使用 reduce 方法计算总和 int sum = numbers.stream().reduce(0, (a, b) -> a + b); // 初始值为 0,归并操作为加法 //// 使用 reduce 方法计算乘积 int product = numbers.stream().reduce(1, (a, b) -> a * b);//// 初始值为 1,归并操作为乘法 //求最大值、最小值 //Optional reduce(BinaryOperator accumulator); // 使用 reduce 方法找到最大值 Optional max = numbers.stream().reduce((a, b) -> a > b ? a : b); // 归并操作为比较大小 List emptyList = Arrays.asList(); // 使用 reduce 方法处理空流 Optional result = emptyList.stream().reduce((a, b) -> a + b); // 归并操作为加法 ``` ##### 6)收集 收集器(Collector)是一种用于将流中的元素收集到一个结果容器中,以便后续的处理和操作。 > collect(Collectors.xxx) ###### 6.1)收集 Stream 流中的数据到集合中 > Stream.collect(Collectors.toList());返回的集合是可变的 - 可以使用add()和remove() > Stream.toList();返回的集合是不可变的 - 不可以使用add()和remove() ```java toList`、`toSet`、`toCollection // 第一:定义数据源,并获取对应的流对象 Stream stream = Stream.of("AA","BB","AA","CC") ; // 第二:操作 - 收集流中的数据 // 1.收集流中的数据到List集合中 List list = stream.collect(Collectors.toList()); // 2.收集流中的数据到Set集合中 Set set = stream.collect(Collectors.toSet()); // 3.收集流中的数据到指定的集合(ArrayList)中 ArrayList arrayList = stream.collect(Collectors.toCollection(ArrayList::new)); // 4.收集流中的数据到指定的集合(HashSet)中 HashSet hashSet = stream.collect(Collectors.toCollection(HashSet::new)); ``` ###### 6.2)收集 Stream 流中的数据到数组中 - ```java Object[] toArray(); ``` - 使用无参,收集到数组,返回值为 Object[](Object类型将不好操作) - ```java A[] toArray(IntFunction generator); ``` - 使用有参,可以指定将数据收集到指定类型数组,方便后续对数组的操作 ```java // 第一:定义数据源,并获取对应的流对象 List list = Arrays.asList("AA", "BB", "AA", "CC"); // 第二:操作 - 收集流中的数据 Object[] objects = list.stream().toArray(); String[] strings = list.stream().toArray(String[]::new); ``` ###### 6.3)聚合操作 类似于数据库的聚合函数。 - 最大值: Collectors.maxBy(); - 最小值: Collectors.minBy(); - 总和 - Collectors.summingInt(); - Collectors.summingDouble(); - Collectors.summingLong(); - 平均值 - Collectors.averagingInt(); - Collectors.averagingDouble(); - Collectors.averagingLong(); - 总个数:Collectors.counting(); ```java // 第一:定义数据源,并获取对应的流对象 List list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); // 第二:操作 - 收集流中的数据 // 1.最大值 Optional maxOptional1 = list.stream() .collect(Collectors.maxBy((o1, o2) -> o1 - o2)); System.out.println("最大值:"+maxOptional1.get()); // Stream流的max方法也可以实现 Optional maxOptional2 = list.stream().max((o1, o2) -> o1 - o2); System.out.println("最大值:"+maxOptional2.get()); // 2.最小值 Optional minOptional1 = list.stream() .collect(Collectors.minBy((o1, o2) -> o1 - o2)); System.out.println("最小值:"+minOptional1.get()); // Stream流的min方法也可以实现 Optional minOptional2 = list.stream().min((o1, o2) -> o1 - o2); System.out.println("最小值:"+minOptional2.get()); // 3.总数 Integer sum1 = list.stream().collect(Collectors.summingInt(d -> d)); System.out.println("总数:"+sum1); Integer sum2 = list.stream().reduce(0, (a, b) -> a + b); System.out.println("总数:"+sum2); // 4.平均值 Double avg = list.stream().collect(Collectors.averagingInt(d -> d)); System.out.println("平均数:"+avg); // 5.统计数量 Long count = list.stream().collect(Collectors.counting()); System.out.println("数量为:"+count); ``` ###### 6.4)数据分组操作 类似SQL Server中的group by,根据需要可以根据某个属性来将数据进行分组。 - 接收一个 Function 参数 ```java groupingBy(Function classifier) ``` - 多级分组操作,接收两个参数: - Function 参数 - Collector多级分组 ```java groupingBy(Function classifier,Collector downstream) // 第一:定义数据源,并获取对应的流对象 Student s1 = new Student("张三","男",18,55) ; Student s2 = new Student("李四","女",28,63) ; Student s3 = new Student("王五","男",38,50) ; Student s4 = new Student("赵六","女",48,99) ; List list = Arrays.asList(s1,s2,s3,s4); // 第二:操作 - 收集流中的数据 // 1.根据性别分组 Map> map = list.stream() .collect(Collectors.groupingBy(s -> s.getSex())); // key:分组字段,value:分组结果 map.forEach((key,value)->{ System.out.println(key+":"+value); }); // 2.根据分数分组 list.stream().collect(Collectors.groupingBy(stu-> { if (stu.getScore() >= 60) { return "及格"; } return "不及格"; })).forEach((key,value)-> System.out.println(key+":"+value)); ``` ###### 6.5)数据分区操作 将集合其分解成两个子集合。我们通过使用 `Collectors.partitioningBy()` ,根据返回值是否为 true,把集合分为两个列表,一个 true 列表,一个 false 列表。 分组和分区的区别就在:分组可以有多个组。分区只会有两个区(true 和 false) - .一个参数 ```java partitioningBy(Predicate predicate) ``` - 两个参数(多级分区) ```java partitioningBy(Predicate predicate, Collector downstream) ``` ```java // 第一:定义数据源,并获取对应的流对象 Student s1 = new Student("张三","男",18,55) ; Student s2 = new Student("李四","女",28,63) ; Student s3 = new Student("王五","男",38,50) ; Student s4 = new Student("赵六","女",48,99) ; List list = Arrays.asList(s1,s2,s3,s4); // 第二:操作 - 分区操作 Map> map = list.stream().collect(Collectors.partitioningBy(s -> s.getScore() >= 60) ); map.forEach((k,v)->{ System.out.println(k + ":"+v); }); ``` ###### 6.6)数据拼接操作 - 无参数 ```java // 等价于 joining(""); Collectors.joining() ``` - 一个参数 ```java Collectors.joining(CharSequence delimiter) ``` - 三个参数(前缀 + 后缀) ```java Collectors.joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix) ``` ```java // 第一:定义数据源,并获取对应的流对象 Student s1 = new Student("张三","男",18,55) ; Student s2 = new Student("李四","女",28,63) ; Student s3 = new Student("王五","男",38,50) ; Student s4 = new Student("赵六","女",48,99) ; List list = Arrays.asList(s1,s2,s3,s4); // 第二:操作 - 拼接操作 // 1.无参:join() String str1 = list.stream().map(s -> s.getName()).collect(Collectors.joining()); System.out.println(str1); // 2.一个参数:joining(CharSequence delimiter) String str2 = list.stream().map(s -> s.getName()).collect(Collectors.joining(",")); System.out.println(str2); // 3.三个参数:joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix) String str3 = list.stream().map(s -> s.getName()).collect(Collectors.joining(",","[","]")); System.out.println(str3); ``` ## 七、枚举 > 枚举类默认继承java.lang.Enum ### 一、概念 枚举类型是一种特殊数据类型,能够为一个变量定义一组**预定义**的**有意义**的常量。变量必须等于为其预定义的值之一。 **枚举本质上是一个特殊的类** 目的&作用:提高代码的**可读性、可维护性和可重用性**。 ### 二、推导 #### 1、数据合法性验证–正则 第一:封装学生 ```java public class Student { //姓名 private String name ; //等级:必须只能是 A B C D E 五种级别 private String grade ; //ABCDE public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGrade() { return grade; } public void setGrade(String grade) { if(!grade.matches("[ABCDE]")) { throw new RuntimeException("等级只能是A B C D E") ; } this.grade = grade; } @Override public String toString() { return "姓名:"+this.name + ",等级:"+this.grade; } } ``` 第二:测试 ```java public class MainTest { public static void main(String[] args) { Student stu = new Student() ; stu.setName("张三"); stu.setGrade("G"); System.out.println(stu); } } ``` #### 2、改进,对象实现 **把等级封装为一个类** 第一:封装等级 ```java public class Grade { private String value ; // 私有化构造方法,不允许在类外实例化对象 private Grade() {} private Grade(String value) { this.value = value ; } // 实现化五个对象 public static final Grade A = new Grade("优秀"); public static final Grade B = new Grade("良好"); public static final Grade C = new Grade("中等"); public static final Grade D = new Grade("合格"); public static final Grade E = new Grade("不合格"); @Override public String toString() { return this.value; } } ``` 第二:重新封装学生对象,引用Grade对象 ```java public class Student { // 姓名 private String name ; // 等级:必须只能是 A B C D E 五种级别 private Grade grade ; public String getName() { return name; } public void setName(String name) { this.name = name; } public Grade getGrade() { return grade; } public void setGrade(Grade grade) { this.grade = grade; } @Override public String toString() { return "姓名:"+this.name + ",等级:"+this.grade; } } ``` 第三:测试 ```java public class MainTest { public static void main(String[] args) { Student stu = new Student() ; stu.setName("张三"); stu.setGrade(Grade.B); System.out.println(stu); } } ``` #### 3、改进,枚举实现 **把等级封装为一个枚举类,JDK5新增enum关键字,用于定义一个枚举类,枚举类也可以有构造方法(私有)、字段和方法。** 第一:定义枚举类 **1)简单定义** ```java // 定义Grade枚举 enum Grade { //public static final Grede A = new Grade(); // 枚举值:各枚举值表示枚举的实例对象(默认是静态的) A,B,C,D,E; } ``` 说明:此处的Grade枚举与上面的Grade类功能一样,枚举值A,B,C,D,E就是此枚举的实例对象(而且是**静态的**),也就是说:**枚举类中声明的每一个枚举值代表枚举类的一个实例对象。** **2)改进Grade枚举—指定A、B、C、D、E五个等级对应的中文表示** ```java public enum Grade { // 定义枚举列表值,同时使用构造方法初始化属性 A("优秀"),B("良好"),C("中等"),D("合格"),E("不合格"); //当有了带参构造方法后,并且重新给了无参构造方法,那么预设值就必须带参,否则在外部使用时值为null // A,B,C,D,E // 定义属性:表示等级ABCDE对应的中文表示 private String value ; private Grade(){} // 定义构造方法 private Grade(String value) { this.value = value ; } // 重写 toString 方法 @Override public String toString() { return this.value; } } ``` **3)改进Grade枚举—指定A、B、C、D、E五个等级的分数范围** > 创建枚举列表值,并且使用构造方法初始化属性; > 同时,由于枚举类中**定义了抽象方法**,不能直接创建枚举值, > 因此,创建枚举值时,必须以**匿名内部类方式创建** > 而且在匿名内部类中**重写方法**,实现返回该等级对应的分数范围 ```java public enum Grade { A("优秀"){ @Override public String getScoreRange() { return "分数>=90 && 分数<=100"; } },B("良好"){ @Override public String getScoreRange() { return "分数>=80 && 分数<90"; } },C("中等"){ @Override public String getScoreRange() { return "分数>=70 && 分数<80"; } },D("合格"){ @Override public String getScoreRange() { return "分数>=60 && 分数<70"; } },E("不合格"){ @Override public String getScoreRange() { return "分数>=0 && 分数<60"; } }; // 定义属性:表示等级ABCDE对应的中文表示 private String value ; /** * 构造方法 * @param value */ private Grade(String value) { this.value = value ; } /** * 定义抽象方法 * @return 返回不同等级对应的分数范围 */ public abstract String getScoreRange() ; // 重写 toString()方法,输出等级的中文表示 @Override public String toString() { return this.value; } /** * 定义成员方法 */ public void sayHello() { System.out.println("成员方法"); } /** * 定义静态方法 */ public static void testStatic() { System.out.println("静态方法"); } } ``` **以下是使用抽象类来实现和以上枚举类同样的功能** ```java public abstract class Grade { //定义属性:表示等级ABCDE对应的中文表示 private String value ; /** * 构造方法:设置各等级的中文表示 * @param value */ private Grade(String value) { this.value = value ; } //创建抽象类实现对象,通过匿名类实现抽象类,并实现方法 public static Grade A = new Grade("优秀") { @Override public String getScoreRange() { return "分数>=90 && 分数<=100"; } }; public static Grade B = new Grade("良好") { @Override public String getScoreRange() { return "分数>=80 && 分数<90"; } }; public static Grade C = new Grade("中等") { @Override public String getScoreRange() { return "分数>=70 && 分数<80"; } }; public static Grade D = new Grade("合格") { @Override public String getScoreRange() { return "分数>=60 && 分数<70"; } }; public static Grade E = new Grade("不合格") { @Override public String getScoreRange() { return "分数>=0 && 分数<60"; } }; /** * 抽象方法 * @return 返回不同等级对应的分数范围 */ public abstract String getScoreRange() ; //重写toString()方法,输出等级的中文表示 @Override public String toString() { return this.value; } } ``` 第二:封装学生对象,引用Grade枚举 与上例中的Student类一样,不需要做任何改动。验证了Grade枚举类与上例的Grade类功能一样。 第三:测试 ```java public class MainTest { public static void main(String[] args) { // 创建学生对象并初始化数据 Student stu = new Student() ; stu.setName("张三"); stu.setGrade(Grade.B); // 输出学生信息 System.out.println(stu); // 获取学生的等级,并打印输出该等级的分数范围 Grade g = stu.getGrade(); System.out.println("分数范围:" + g.getScoreRange()); // 调用枚举成员方法 g.sayHello(); // 调用枚举静态方法 Grade.testStatic(); } } ``` ### 三、语法 ```java [访问修饰符] enum 枚举类名 { // 枚举常量 - 枚举实例(枚举值) // 成员变量、常量 // 成员方法、抽象方法、静态方法、重载方法、重写方法 // 构造方法 } ``` #### 1、要点: 1)枚举是特殊的类(常量类),在声明枚举类时,可以定义成员变量、方法和构造方法 - 枚举常量是枚举类型的实例,它们是不可变的、公共的、静态的和最终的。 - 构造方法必须私有的(默认)。 2)枚举实例必须要求先定义,也就说,枚举值必须定义在枚举类的第一行代码中; 3)规范 - 命名规范:全大写命名,多个单词间使用下划线分隔 - 注释规范:所有的枚举类型字段必须要有注释,说明每个数据项的用途 4)各个枚举值逗号分隔,枚举类中仅仅只有枚举值,则可以省略分号,否则必须显式写分号 5)不能泛型化 ```java // 错误 public enum Color { ... } ``` 说明:Java 枚举类型并不支持泛型。这是因为枚举类型是在编译时确定的,而泛型是在运行时确定的。 6)使用枚举常量 ```java 语法:枚举类名.枚举常量 ``` #### 2、switch的支持 JDK5中扩展了switch语句,它除了可以接收int,byte,char,short外,还可以接收一个枚举类型。 ```java //1)定义枚举 public enum Color { RED,GREEN,BLUE } //2)测试 -- switch对枚举的支持 public class MainTest { public static void main(String[] args) { Color week = Color.RED; switch(week) { case RED: System.out.println("红");break; case GREEN: System.out.println("绿");break; case BLUE: System.out.println("蓝");break; } } } ``` #### 3、Enum常用方法 > 枚举类型都隐式继承了java.lang.Enum类,因此不能继承其他类,但可以实现接口 > > Java中声明的枚举类,均是java.lang.Enum类的子类,它继承了Enum类的所有方法。 > > ```java > public enum Color { > RED("红"),GREEN("绿"),BLUE("蓝"); > private String value ; > private Color(String value) { > this.value = value ; > } > public String getValue() { > return value; > } > } > ``` ##### 1)name() 返回枚举常量的名称,正是因为在其枚举声明中声明。 ```java Color color = Color.RED; String name = color.name(); System.out.println(name); ``` ##### 2)valueOf(String name) 根据枚举常量名称,返回枚举实例 ```java Color color = Color.valueOf("RED"); System.out.println(color.getValue()); // 输出:红 ``` ##### 3)values() 遍历枚举类所有的枚举值 ```java Color[] colors = Color.values(); for (Color color : colors) { System.out.println(color.name()); } /* 输出: RED GREEN BLUE */ ``` 注意:神秘的values()方法和valueOf(String)方法,这两个方法在其父类Enum中并不存在,那么它们哪里来的呢?答案是:它们是在创建枚举实例时,由编译器添加的static方法。 ##### 4)valueOf(Class enumType, String name) 此方法返回具有指定名称的指定枚举类型的枚举常量 ```java Color color = Enum.valueOf(Color.class,"RED"); System.out.println(color.getValue()); ``` **区别valueOf(String name)与valueOf(Class enumType, String name)。前者是编译器添加的静态方法,后者是继承自Enum类的静态方法。** ##### 5)ordinal() 返回此枚举常量的序数(其枚举声明中的位置,其中初始常量被分配一个序号零) ```java Color color = Color.RED; int ordinal = color.ordinal(); System.out.println(ordinal); //输出:0 ``` ##### 6)compareTo() 比较两个枚举值常量序数之差 ```java Color color = Color.RED; int n = color.compareTo(Color.BLUE) ; //0 减 2 System.out.println(n); ```