宋红康《30天搞定Java》学习笔记(B站视频)

多线程

程序&进程&线程的基本概念

程序(program):静态代码,为完成特定任务、用某种语言编写的一组指令的集合。
进程(process):正在运行的一个程序,有其生命周期,是一个动态的过程。进程是资源分配的单元。
线程(thread):进程可进一步细化为线程,它是程序内部的一条执行路径,若一个进程同一时间并行执行多个线程,就是支持多线程的;线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),线程切换的开销小; 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患

多线程运行在单核/多核CPU的差异:

  1. 单核CPU,其实是一种伪多线程,因为在一个时间单元内,也只能执行一个线程的任务
  2. 多核CPU,可以在同一时间单元内,可以执行多个线程的任务
  3. 一个Java应用程序至少具有三个线程,即主线程main、垃圾回收线程gc、异常处理线程

并行、并发的区别:

  1. 并行:同时执行多个任务(进程、线程)
  2. 并发:处理器采用时间分片的方式在不同的任务间切换执行,宏观上体现出并行的特征,但本质上一个时间点仅支持执行单一任务

多线程的优势:

  1. 提高应用程序的响应,对图形化界面更有意义,可增强用户体验
  2. 提高计算机系统CPU的利用率,即将等待IO、网络的时间用于执行其它任务。就改善程度而言,IO密集型的程序比计算密集型程序的效果要好
  3. 改善程序设计结构,将互不影响的部分设计为多个线程交由CPU执行,提高程序运行效率

线程的生命周期

4irnbjaiin.jpeg

一般把线程的生命周期分为五种状态:

  1. 新建:当一个Thread类或其子类的对象被声明并创建时,线程对象处于新建状态
  2. 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时已具备了运行的条件,只是没分配到CPU资源
  3. 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
  4. 阻塞:被人为挂起或执行输入输出操作时,让出 CPU 资源并临时中止执行,进入阻塞状态
  5. 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

线程的创建(java.lang.Thread)

Java创建多线程的方式:

  1. 创建继承Thread类的子类,重写run()方法;创建该类的子类,调用start()方法即可启动
  2. 实现Runnable接口,重写run()方法;将Runnable接口的实现类对象作为实际参数传递给Thread类的构造器中,创建对象,调用start()方法即可启动
  3. 实现Callable接口,重写call()方法;将Callable接口的实现类对象传递到FutureTask类构造器中,创建FutureTask的对象,将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,调用start()方法启动。此种形式可以调用FutureTask的对象的get()方法得到在call()方法中的返回值,且call()方法可抛出异常
  4. 使用线程池:经常创建和销毁、使用量特别大的资源,可以提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。相关的API有 ExecutorService 接口和 Executors 线程池工厂类
  1. import java.util.concurrent.*;
  2. ExecutorService service = Executors.newFixedThreadPool(10);//返回的是接口实现类
  3. System.out.println(service.getClass());//检视类名
  4. ThreadPoolExecutor tpool = (ThreadPoolExecutor)service;//按类名进行转换
  5. tpool.execute(new Runnable接口实现类());//基于Runnable接口实现的线程放入线程池
  6. //基于Callable接口实现的线程放入线程池,可用get()取出返回信息
  7. System.out.println(tpool.submit(new Callable接口实现类()).get());
  8. tpool.shutdown();
  1. //实现Callable接口,重写call()方法
  2. class Ttest implements Callable {
  3. @Override
  4. public Object call() {
  5. //多线程实现的代码部分
  6. }
  7. return "Call方法调用";//可返回值
  8. }
  9. //将Callable接口的实现类对象传递到FutureTask类构造器中,创建FutureTask的对象
  10. FutureTask futureTask = new FutureTask(new Ttest);
  11. //将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象
  12. Thread ttest = new Thread(futureTask);
  13. //调用start()方法启动
  14. ttest.start();
  15. //可通过FutureTask的对象取得call()返回值,但方法get()声明包含抛出异常,需进行处理
  16. try {
  17. System.out.println(futureTask.get());
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. } catch (ExecutionException e) {
  21. e.printStackTrace();
  22. }
  1. //实现Runnable接口,重写run()方法
  2. class Ttest implements Runnable {
  3. @Override
  4. public void run() {
  5. //多线程实现的代码部分
  6. }
  7. }
  8. //将Runnable接口的实现类对象作为实际参数传递给Thread类的构造器中,创建对象(代理)
  9. Thread ttest = new Thread(new Ttest());
  10. //调用start()方法启动
  11. ttest.start();
  1. class Ttest extends Thread {
  2. //可声明私有变量,在构造器中用传入对象的方式初始化,实现多个线程共享一个对象
  3. //创建继承Thread类的子类,重写run()方法
  4. @Override
  5. public void run() {
  6. //多线程实现的代码部分
  7. }
  8. }
  9. //调用start()方法启动
  10. new Ttest().start();

各种创建方式的对比: 【继承方式和实现方式的对比】

  1. 实现接口方式避免了单继承的局限性,且符合业务逻辑
  2. 实现接口方式因使用时仍需将实现类传入Thread类的构造器中创建Thread对象,因此使用时创建的对象引用的是同一份资源(实现类);继承方式可以通过在类中声明私有变量,在构造器中用传入对象的方式初始化,实现多个线程共享一份资源,操作较复杂

【Runnable和Callable接口实现的对比】

  1. call()方法相比run()方法可有返回值(实现时取得返回值需借助FutureTask类),可抛出异常
  2. Callable接口支持泛型

【线程池使用的优势】

  1. 响应速度快,直接从线程池中获取使用
  2. 降低资源消耗,避免频繁创建、销毁,实现重复利用
  3. 便于线程管理,提供线程池管理的工具方法

线程的同步

多线程的安全问题:多个线程在对共享数据进行操作时,可能导致共享数据的错误。

Java线程同步的实现方法:

  1. 使用同步代码块,将存在线程安全问题的代码块纳入同步代码块,提供保护
  2. 将存在线程安全问题的代码作为一个方法,并在方法声明中使用synchronized关键字指明为同步方法
  3. 利用Lock显式地加锁和释放锁来对共享资源进行保护

JDK5.0之后,Java提供了显式定义锁对象实现同步的机制。java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,ReentrantLock 类(可重入锁)实现了 Lock 接口,同步时使用声明的Lock对象充当锁。

各种同步方法的简单对比: 【synchronized与Lock的对比】

  1. synchronized机制在执行完同步代码块后,自动释放同步监视器;Lock需要手动启动同步(lock()),结束同步也需要手动实现(unlock())
  1. //使用任意Object对象作为同步监视器,要求多个线程必须共用同一个同步监视器
  2. //实现接口方式的多线程程序,一般可用this作为同步监视器
  3. //继承Thread类创建的多线程程序,一般使用当前类充当同步监视(类名.class)
  4. synchronized(Object对象){
  5. //需要被同步的代码块
  6. }
  1. //同步方法仍然需要使用同步监视器,只是不需要我们显式声明
  2. //非静态的同步方法,同步监视器是:this
  3. //静态(static)的同步方法,同步监视器是:当前类本身
  4. synchronized void method (){
  5. //需要被同步的代码
  6. }
  1. private final ReentrantLock lock = new ReenTrantLock();
  2. public void method(){
  3. lock.lock();
  4. try{
  5. //保证线程安全的代码;
  6. }
  7. finally{
  8. lock.unlock();
  9. }
  10. }

同步锁释放的情况:

  1. 同步代码块执行结束,进程正常释放同步锁。
  2. 占有锁的线程执行发生异常(Error或Exception),此时JVM会让线程自动释放锁。
  3. 占有锁的线程执行wait()方法,当前线程暂停(让出CPU时间片和其它资源),并释放锁。

    一些不会释放锁的操作:

    1. 程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
    2. 其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁

死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,只是线程都处于阻塞状态,无法继续

线程的通信

wait():当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待获得同步监视器的所有权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者。
notifyAll():唤醒正在排队等待资源的所有线程。

wait、notify、notifyAll使用注意点:

  1. 这三个方法只有在synchronized方法或代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常
  2. 这三个方法在Object类中声明,因为其必须在作为同步监视器的对象中调用,而任何对象都能作为同步监视器对象
  1. //使用两个线程打印 1-100,要求线程1, 线程2交替打印
  2. class Communication implements Runnable {
  3. int i = 1;
  4. public void run() {
  5. while (true) {
  6. synchronized (this) {
  7. notify();
  8. if (i <= 100) {
  9. System.out.println(Thread.currentThread().getName() +
  10. ":" + i++);
  11. } else
  12. break;
  13. try {
  14. wait();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. }
  21. }

线程的优先级

线程的调度是有优先级之分的,具体表现为对于高优先级的线程会使用优先调度的抢占式策略,获得更多的CPU执行时间片,对于同优先级的线程则组成FIFO队列的调度策略。Java中的线程分为两类:一种是守护线程,一种是用户线程,守护线程是用来服务用户线程,通过在start方法前调用thread.setDaemon(true)可以把线程变成一个守护线程,Java垃圾回收GC就是一个典型的守护线程。

java.lang.Thread中的优先级常量:

  1. MAX_PRIORITY:10
  2. MIN _PRIORITY:1
  3. NORM_PRIORITY:5

java.lang.Thread中的方法

void start(): 启动线程,执行对象的run()方法。
run():线程在被调度时执行的操作,单独调用并不会启动一个线程。
String getName():返回线程的名称。
void setName(String name):设置该线程名称。
static Thread currentThread():返回当前线程。
static void yield():线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级或更高优先级的线程,则忽略此方法。
join() :当某个程序执行中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的线程执行完为止,低优先级的线程也可以获得执行。
static void sleep(long millis):当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,之后重新排队等待执行。该方法会抛出InterruptedException异常。
stop(): 强制线程生命期结束,不推荐使用。
boolean isAlive():返回boolean,判断线程是否还活着。
getPriority():返回线程优先值。
setPriority(int newPriority):改变线程的优先级。

Java常用类

Java常用类.xmind

字符串相关的API

java.lang.String

String的特性:

  1. String是一个final类(不能被继承),是不可变的字符序列,字符内容存储在字符数组value中
  2. 字符串是常量,用双引号引起来表示。其值在创建后不能更改
  3. 实现java.io.Serializable接口,表示该类可以被序列化(写入磁盘、不同进程间数据传输、网络传输都需要传输数据的序列化)

String对象的内存解析:

  1. 通过字面量定义赋值的方式实例化String,此时的字符串值声明在字符串常量池中
  2. 字符串常量池中是不会存储相同内容(使用String类的equals()比较)的字符串
  3. 使用new + 构造器的方式实例化String,此时变量存储的是在堆中开辟空间以后对应的地址值,堆中的String对象属性字符数组value再指向(拷贝)字符串常量池中的字符串值。因此在内存中创建了两个对象,一个是堆空间中的String对象,一个是常量池中的char型数组
  4. 拼接赋值的内存情况:常量(包括以final修饰的常量、显式指定的字符串常量)与常量的拼接结果在常量池,且常量池中不会存在相同内容的常量;存在与变量拼接的情况,则结果在堆中;对拼接结果调用intern()方法,返回值在常量池中

Snipaste_2022-04-27_09-39-14.png
Snipaste_2022-04-27_09-39-47.png
Snipaste_2022-04-27_09-40-24.png
Snipaste_2022-04-27_09-41-01.png

String类常用方法(详见API文档): int length():返回字符串的长度: return value.length char charAt(int index): 返回某索引处的字符return value[index] boolean isEmpty():判断是否是空字符串:return value.length == 0 String toLowerCase():使用默认语言环境,将 String 中的所字符转换为小写 String toUpperCase():使用默认语言环境,将 String 中的所字符转换为大写 String trim():返回字符串的副本,忽略前导空白和尾部空白 boolean equals(Object obj):比较字符串的内容是否相同 boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写 String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+” int compareTo(String anotherString):比较两个字符串的大小 String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串 String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串 boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束 boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始 boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始 boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true int indexOf(String str):返回指定子串在此字符串中第一次出现处的索引 int indexOf(String str, int fromIndex):返回指定子串在此字符串中第一次出现处的索引,从指定的索引开始 int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引 int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索 String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的 String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串 String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串 String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串 boolean matches(String regex):告知此字符串是否匹配给定的正则表达式

【String与字符数组转换】 字符数组转换为字符串:使用String类的构造器String(char[]) 字符串转换为字符数组:使用String类的 toCharArray() 空参方法或getChars()带参方法 【String与字节数组转换】 字节数组转换为字符串:使用String类的构造器String(byte[]) 字符串转换为字节数组:使用String类的getBytes()方法

java.lang.StringBuffer/StringBuilder

StringBuffer是可变的字符序列,jdk1.0即有此API,其提供的方法都是线程安全的,底层使用char[]存储。
StringBuilder是可变字符序列,jdk5.0新增,方法是线程不安全的,但执行效率高,底层使用char[]存储。

StringBuffer/StringBuilder的底层存储(可查看源码):

  1. 调用空参构造器时,底层创建一个长度是16的数组进行存储,但重写了length()方法,所以调用该方法输出为实际数据长度
  2. 扩容问题:如果要添加的数据底层数组装不下,就需要扩容底层的数组。默认情况下,扩容为原来容量的2倍 + 2,同时将原数组中的元素复制到新的数组中

StringBuffer/StringBuilder类常用方法(详见API文档) append(xxx):提供了多个重载的append()方法,用于进行字符串拼接 delete(int start,int end):删除指定位置的内容 replace(int start, int end, String str):把[start,end)位置替换为str insert(int offset, xxx):在指定位置插入xxx reverse() :把当前字符序列逆转 public int indexOf(String str):查找子串位置 public String substring(int start,int end):按指定位置截取字符串生成子串 public int length() :字符串长度 public char charAt(int n ):返回字符串给定位置的字符 public void setCharAt(int n ,char ch) :设置字符串给定位置的字符

  1. String str = null;
  2. StringBuffer sb = new StringBuffer();
  3. sb.append(str);//append方法源码对str为null的情况进行了特殊处理,字符串写入"null"
  4. System.out.println(sb.length());//4
  5. System.out.println(sb);//"null"
  6. StringBuffer sb1 = new StringBuffer(str);//构造器调用str.length()报错
  7. System.out.println(sb1);

时间相关的API

java.util.Date类 |—-java.sql.Date类 java.text.SimpleDateFormat类 java.util.Calendar类 java.time.LocalDate类 java.time.LocalTime类 java.time.LocalDateTime类 java.time.format.DateTimeFormatter类

  1. //时间对象(java.util.Date)
  2. System.out.println(System.currentTimeMillis());//System
  3. Date date = new Date();//创建java.util.Date对象
  4. System.out.println(date.getTime());//获取毫秒数
  5. System.out.println(date);//默认格式输出
  6. //可用于时间格式化的对象(java.text.SimpleDateFormat)
  7. SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");//指定时间格式
  8. System.out.println(format.format(date));//将时间对象转换为给定格式的字符串
  9. //符合指定格式的时间字符串解析为java.util.Date对象
  10. try {
  11. System.out.println(format.parse("2022/5/1 7:00:00"));
  12. } catch (ParseException e) {
  13. e.printStackTrace();
  14. }
  15. //时间对象(java.sql.Date)
  16. //获取java.util.Date对象的毫秒数形式并转换为java.sql.Date对象
  17. java.sql.Date sqldate = new java.sql.Date(date.getTime());
  18. System.out.println(sqldate);
  19. //时间对象(java.util.Calendar)
  20. Calendar cal = Calendar.getInstance();
  21. //获取指定的时间信息get,设置set,修改add
  22. int week_of_year = cal.get(Calendar.WEEK_OF_YEAR);
  23. System.out.println(week_of_year);
  24. //获取java.util.Date类型的对象
  25. System.out.println(cal.getTime());
  26. //用java.util.Date类型的对象设置时间
  27. cal.setTime(date);
  28. //使用java.time.LocalDateTime
  29. LocalDateTime datetime = LocalDateTime.now();
  30. System.out.println(datetime);
  31. //指定日期创建LocalDateTime对象
  32. datetime = LocalDateTime.of(2022,5,1,7,0,0);
  33. System.out.println(datetime.plusDays(1));//修改时间,返回新的LocalDateTime对象
  34. //可用于时间格式化的对象(java.time.format.DateTimeFormatter)
  35. DateTimeFormatter dt_formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd hh:mm:ss");
  36. System.out.println(dt_formatter.format(datetime));

比较器

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

  1. 自然排序:java.lang.Comparable接口实现方式,实现Comparable接口的对象列表(和数组)可以通过Collections.sort或Arrays.sort进行自动排序,一般按从小到大排列。实现Comparable的类必须实现compareTo(Object obj)方法,两个对象即通过compareTo(Object obj)方法的返回值来比较大小。如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零。
  2. 定制排序:java.util.Comparator接口实现方式, 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator 的对象来排序,重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示 o1小于o2。 然后将 Comparator 传递给sort方法来实现排序。

    System类

    系统级的很多属性和控制方法都放置在System类,该类位于java.lang包。由于该类的构造器是private的,所以无法创建该类的对象,但其内部的成员变量和成员方法都是static的,所以也很方便调用。

    java.lang.System类常用方法与属性: native long currentTimeMillis():返回当前的计算机时间 void exit(int status):实现程序退出 void gc():请求系统进行垃圾回收 String getProperty(String key):获得系统中属性名为key的属性对应的值

Math类

java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。

BigInteger&BigDecimal类

java.math包下的BigInteger&BigDecimal类数字精度比较高,可用于做科学计算或商业、工程计算。

枚举类

枚举类特征:类的对象只有确定的有限多个,通常使用的是一组常量。

自定义枚举类的使用注意点:

  1. enum关键字声明枚举类适用jdk1.5之后的版本,在jdk1.5之前的版本需自定义枚举类
  2. 枚举类对象的属性不应允许被改动,,所以应该使用 private final 修饰
  3. 私有化类的构造器,保证不能在类的外部创建其对象,使用 private final 修饰的属性应该在构造器中为其赋值
  4. 在类的内部创建枚举类的实例。声明为:public static final
  1. class Season{
  2. private final String SEASONNAME;//季节的名称
  3. private final String SEASONDESC;//季节的描述
  4. private Season(String seasonName,String seasonDesc){
  5. this.SEASONNAME = seasonName;
  6. this.SEASONDESC = seasonDesc;
  7. }
  8. public static final Season SPRING = new Season("春天", "春暖花开");
  9. public static final Season SUMMER = new Season("夏天", "夏日炎炎");
  10. public static final Season AUTUMN = new Season("秋天", "秋高气爽");
  11. public static final Season WINTER = new Season("冬天", "白雪皑皑");
  12. }

enum定义枚举类的注意点:

  1. 使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其它类
  2. 枚举类的构造器只能使用 private 权限修饰符
  3. 必须在枚举类的第一行声明枚举类对象,枚举类的所有实例必须在枚举类中显式列出(以“,”分隔,以“;”结尾)。列出的实例系统会自动添加 public static final 修饰

Enum类的主要方法:

  1. values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
  2. valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常IllegalArgumentException。
    1. public enum Season {
    2. SPRING("春天","春风又绿江南岸"),
    3. SUMMER("夏天","映日荷花别样红"),
    4. AUTUMN("秋天","秋水共长天一色"),
    5. WINTER("冬天","窗含西岭千秋雪");
    6. private final String seasonName;
    7. private final String seasonDesc;
    8. private Season(String seasonName, String seasonDesc) {
    9. this.seasonName = seasonName;
    10. this.seasonDesc = seasonDesc;
    11. }
    12. public String getSeasonName() {
    13. return seasonName;
    14. }
    15. public String getSeasonDesc() {
    16. return seasonDesc;
    17. }
    18. }

    注解 (Annotation)

    Annotation是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或部署。
    使用 Annotation 时要在其前面增加 @ 符号,并把该 Annotation 当成 一个修饰符使用。用于修饰它支持的程序元素。注解必须配上注解的信息处理流程才有意义。

    JDK基本注解:

    1. @Override:限定重写父类方法,该注解只能用于方法
    2. @Deprecated:用于表示所修饰的元素(类/方法等)已过时,通常因为所修饰的结构存在隐患或有更优选择
    3. @SuppressWarnings:抑制编译器警告

自定义注解的注意点:

  1. 定义新的注解类型使用 @interface 关键字,定义时自动继承了java.lang.annotation.Annotation接口
  2. 注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型及以上所有类型的数组
  3. 可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字
  4. 如果只有一个参数成员,建议使用参数名value
  5. 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值” ,如果只有一个参数成员,且名称为value, 可以省略“value=”
  6. 没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元数据注解

JDK 的元 Annotation 用于修饰其他 Annotation 定义。有4个标准的meta-annotation类型,分别是:Retention、Target、Documented、Inherited。Java8对注解处理提供了可重复的注解(在同一处使用相同类型的多个注解)及可用于类型的注解。

@Retention:只能用于修饰一个Annotation定义,用于指定该Annotation的生命周期。@Retention包含一个 RetentionPolicy类型的成员变量,使用@Rentention时必须为该value成员变量指定值:

  1. RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
  2. RetentionPolicy.CLASS:在class文件中有效(即class保留),当运行Java程序时, JVM不会保留注解,这是默认值
  3. RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java程序时, JVM会保留注释,程序可以通过反射获取该注释

Snipaste_2022-04-28_10-50-26.png

@Target: 用于修饰Annotation定义,用于指定被修饰的Annotation能用于修饰哪些程序元素。@Target包含一个名为value的成员变量,JDK8新增的可用于类型的注解:

  1. ElementType.TYPE_PARAMETER表示该注解能写在类型变量的声明语句中(如:泛型声明)
  2. ElementType.TYPE_USE表示该注解能写在使用类型的任何语句中

Snipaste_2022-04-28_10-33-44.png

@Documented:用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档,定义为Documented的注解必须设置Retention值为RUNTIME @Inherited:被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation, 则其子类将自动具有该注解

  1. //Annotation1
  2. @Inherited
  3. @Repeatable(MyAnnotations.class)
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
  6. public @interface MyAnnotation {
  7. String value() default "MyAnnotation";
  8. }
  9. //Annotation2
  10. @Inherited
  11. @Retention(RetentionPolicy.RUNTIME)
  12. @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
  13. public @interface MyAnnotations {
  14. MyAnnotation[] value();
  15. }

集合

【集合与数组的对比】集合、数组都是对数据进行存储操作(内存层面)的结构,简称Java容器 数组的特点及使用缺点:

  1. 数组一旦初始化以后,其长度就确定了,不可修改数组长度
  2. 数组存储数据是有序、可重复的,对于存储数据要求不可重复、排序存储等需求没有很好的实现方案
  3. 数组提供的操作方法有限,增删操作的效率不高

集合的特点:

  1. 弥补了数组使用不便的缺点,提供多种数据结构的存储容器,适用各种特点的数据存储
  2. 集合框架下提供了大量操作数据的方法

集合框架

Java集合可分为Collection和Map两种体系 Collection接口:List、Set和Queue接口的父接口,存储单列数据,定义了存取一组对象的方法 Map接口:用于保存具有映射关系(key-value)的数据

1078540-20191115163627420-566269456.png
1078540-20191115163647011-1193506491.png
Snipaste_2022-04-28_23-36-30.png

常用方法

Java.util.Collection

Collection常用方法(abstract) boolean add(Object obj):添加元素 boolean addAll(Collection coll):添加集合 void clear() :清空集合 boolean remove(Object obj):移除元素 boolean removeAll(Collection coll):取集合差集 boolean isEmpty():判断是否为空 boolean contains(Object obj):判断是否包含元素obj boolean containsAll(Collection coll):判断是否包含此子集 boolean equals(Object obj):判断集合是否相等 int size():返回集合元素个数 boolean retainAll(Collection coll):取集合交集 Object[] toArray():转换为数组 int hashCode():获取集合对象的哈希值 ITerator iterator():返回迭代器对象用于集合遍历

使用Collection集合存储对象,要求对象所属的类重写equals()方法,因为使用类提供的操作数据的方法一般都会调用equals()方法进行比较 数组转化为集合方式:使用Arrays类的静态方法asList()

  1. //asList将整个基本数据类型元素的数组当作一个对象放入List
  2. List arr1 = Arrays.asList(new int[]{1, 2});
  3. System.out.println(arr1.size());//1
  4. //asList可以将对象数组中的每一个对象元素分别放入List
  5. List arr2 = Arrays.asList(new Integer[]{1, 2});
  6. System.out.println(arr2.size());//2

迭代器

迭代器模式:提供一种方法访问一个容器(container)对象中各个元素,而又不暴露该对象的内部细节。
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,所有实现Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象(仅用于遍历集合)。集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标在集合的第一个元素之前。

迭代器使用注意点:

  1. Iterator可以删除集合的元素,但是在遍历过程中调用迭代器对象的remove方法,不是调用集合对象的remove方法
  2. 如果还未调用next()或在调用next()方法之后已经调用了remove()方法,再调用remove()方法都会报IllegalStateException异常
  1. Iterator iter = coll.iterator();//返回一个迭代器对象用于遍历集合
  2. //迭代器的hasNext方法:检测是否有下一个元素
  3. //若不调用,且下一条记录无效,直接调用next()会抛出NoSuchElementException异常
  4. while(iter.hasNext()){
  5. //迭代器的next方法:指针下移,并将下移以后集合位置上的元素返回
  6. Object obj = iter.next();
  7. if(obj.equals("Tom")){
  8. //删除该位置上的元素
  9. iter.remove();
  10. }
  11. }

增强for循环

Java 5.0 提供了foreach循环访问Collection和数组,其底层仍是调用Iterator完成遍历操作。

  1. public class ForTest {
  2. public static void main(String[] args) {
  3. String[] str = new String[5];
  4. //使用foreach循环
  5. for (String temp : str) {
  6. //底层使用的是迭代器对象,将迭代器取出的对象赋给temp,相当于方法的值传递
  7. //因此下面的修改是无效的
  8. temp = "temp";
  9. }
  10. for (int i = 0; i < str.length; i++) {
  11. System.out.println(str[i]);//输出为null
  12. }
  13. }
  14. }

List接口

通常使用List替代数组,List集合的特点就是存取有序,可以存储重复的元素,可以用下标进行元素的操作。
JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。

List接口实现类:ArrayList

ArrayList是一个动态数组结构,支持随机存取,尾部插入删除方便,内部插入删除效率低(因为要移动数组元素);如果内部数组容量不足则自动扩容,因此当数组很大时,效率较低。

java.util.ArrayList新增与索引相关方法(详见API文档) void add(int index, Object ele):在index位置插入ele元素 boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来 Object get(int index):获取指定index位置的元素 int indexOf(Object obj):返回obj在集合中首次出现的位置 int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置 Object remove(int index):移除指定index位置的元素,并返回此元素 Object set(int index, Object ele):设置指定index位置的元素为ele List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex 位置的子集合

List接口实现类:LinkedList

LinkedList是一个双向链表结构,在任意位置插入删除都很方便,但是不支持随机取值,每次都只能从一端开始遍历,直到找到查询的对象,然后返回;不过,它不像 ArrayList 那样需要进行内存拷贝,因此相对来说效率较高,但是因为存在额外的前驱和后继节点指针,因此占用的内存比 ArrayList 多一些。

  1. private static class Node<E> {
  2. E item;
  3. Node<E> next;
  4. Node<E> prev;
  5. Node(Node<E> prev, E element, Node<E> next) {
  6. this.item = element;
  7. this.next = next;
  8. this.prev = prev;
  9. }
  10. }

java.util.LinkedList新增与链表操作相关方法(详见API文档) void addFirst(Object obj) void addLast(Object obj) Object getFirst() Object getLast() Object removeFirst() Object removeLast()

List接口实现类:Vector

Vector也是一个动态数组结构,一个古老实现类,早在jdk1.1就引入进来类,之后在jdk1.2里引进ArrayList,ArrayList大部分的方法和Vector比较相似,两者是不同的,Vector是允许同步访问的,Vector中的操作是线程安全的,但是效率低,而ArrayList所有的操作都是异步的,执行效率高,但不安全。

Set接口

Set集合的特点是元素不重复,存取无序,无下标。其主要实现类有HashSet、LinkedHashSet和TreeSet。

Set接口实现类:HashSet

HashSet底层是基于HashMap的k实现的,元素不可重复,特性同HashMap。

  1. HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等
  2. 对于存放在Set容器中的对象,对应的类要重写equals()和hashCode()方法,通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算,确保“相等的对象具有相等的散列码”
  3. 向HashSet集合中插入元素时,会调用该对象的hashCode()方法得到该对象的哈希值,通过散列函数决定该对象在HashSet底层数组中的存储位置
  4. 如果两个元素的哈希值相等(存储在数组同一位置),会继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,则保存该元素,通过链表的方式继续链接元素
  5. 底层数组初始容量为16,当如果使用率超过0.75,则扩容为原来的2倍
  1. HashSet set = new HashSet();
  2. Person p1 = new Person(1001,"xiaoming");//Person类含ID,名称
  3. Person p2 = new Person(1002,"xiaohong");//Person类重写了hashCode()和equal()方法
  4. set.add(p1);
  5. set.add(p2);
  6. p1.name = "Tony";//修改了p1的name属性,导致计算出的hashcode发生变化
  7. set.remove(p1);//按变化后的hashcode寻址去删除元素,无法删除p1
  8. System.out.println(set);//因为p1是在修改前计算出hashcode确定存储位置
  9. set.add(new Person(1001,"Tony"));//计算hashcode后确定存放位置发现没有元素,可插入
  10. System.out.println(set);//存在两个值相同的元素,但存放的位置不同
  11. set.add(new Person(1001,"xiaoming"));//可存放
  12. //存入HashSet的变量一般不再更改,若要更改则应先删除再重新插入修改后的变量

Set接口实现类:LinkedHashSet

LinkedHashSet是HashSet的子类,底层是基于LinkedHashMap的k实现的,元素不可重复,特性同LinkedHashMap。

  1. 根据元素的哈希值来决定元素的存储位置
  2. 使用双向链表维护元素的次序,使元素看起来是以插入顺序保存的

Set接口实现类:TreeSet

TreeSet是基于TreeMap的k实现的,同样元素不可重复,特性同TreeMap。

  1. 底层使用红黑树结构存储数据, 可以使集合元素处于排序状态
  2. 调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列(自然排序)
  3. 实现Comparator接口,重写compare方法,并在声明TreeSet对象时传入Comparator接口实现类对象(定制排序)

java.util.TreeSet新增与树操作相关方法(详见API文档) Comparator comparator():返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回 null Object first():返回此 set 中当前第一个(最低)元素 Object last():返回此 set 中当前最后一个(最高)元素 Object lower(Object e):返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null Object higher(Object e):返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null SortedSet subSet(fromElement, toElement):返回此集合部分的视图,其元素范围从fromElement(包括)到toElement(不包括) SortedSet headSet(toElement):返回此 set 的部分视图,其元素严格小于 toElement SortedSet tailSet(fromElement):返回此 set 的部分视图,其元素大于等于 fromElement

Map接口

Map是一个双列集合,其中保存的是键值对,键要求保持唯一性,值可以重复。

Map接口常用方法 【添加、修改、删除操作】 Object put(Object key,Object value):将指定的key-value添加到(或修改)当前map对象 void putAll(Map m):将m中的所有key-value对存放到当前map对象 Object remove(Object key):移除指定key的key-value对,并返回value值 void clear():清空当前map中的所有数据 【查询操作】 Object get(Object key):获取指定key对应的value boolean containsKey(Object key):是否包含指定的key boolean containsValue(Object value):是否包含指定的value int size():返回map中key-value对的个数 boolean isEmpty():判断当前map是否为空 boolean equals(Object obj):判断当前map和参数对象obj是否相等 【元视图操作】 Set keySet():返回所有key构成的Set集合 Collection values():返回所有value构成的Collection集合 Set entrySet():返回所有key-value对构成的Set集合

Map接口实现类:HashMap

HashMap继承自AbstractMap,key不可重复,因为使用的是哈希表存储元素,所以输入的数据与输出的数据顺序基本不一致,另外,HashMap最多只允许一条记录的key为null。

HashMap特征:

  1. 所有的key构成的集合是Set,是无序、不可重复的。key所在的类要重写equals()和hashCode()
  2. 所有的value构成的集合是Collection,是无序、可重复的。value所在的类要重写equals()
  3. 一个key-value对构成一个entry,所有的entry构成的集合是Set,是无序的、不可重复的
  4. HashMap判断两个key相等的标准是:两个key的hashCode值相等,通过equals()方法也返回true
  5. HashMap判断两个value相等的标准是:两个value通过equals()方法返回true

HashMap存储结构: JDK 7及以前版本:HashMap结构是数组+链表结构(即为链地址法) JDK 8版本以后:HashMap结构是数组+链表+红黑树

Map接口实现类:LinkedHashMap

HashMap 的子类,内部使用链表数据结构来记录插入的顺序,使得输入的记录顺序和输出的记录顺序是相同的。

Map接口实现类:TreeMap

TreeMap底层使用红黑树结构存储数据,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历时,得到的记录是排过序的。

Map接口实现类:Hashtable

Hashtable是一个古老实现类,键值不能为空,与HashMap不同的是,方法都加了同步锁,是线程安全的,但是效率没有HashMap快。

Map接口实现类:Properties

Properties 类是 Hashtable 的子类,该对象一般用于处理属性文件。属性文件里的key、value 都是字符串类型,所以Properties里的 key 和 value 都是字符串类型。

  1. Properties pros = new Properties();
  2. pros.load(new FileInputStream("jdbc.properties"));//加载属性文件
  3. String user = pros.getProperty("user");//获取属性value值
  4. System.out.println(user);

Collections工具类

Collections工具类中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、实现同步控制等方法

java.util.Collections常用方法 【排序相关】 reverse(List):反转List中元素的顺序 shuffle(List):对List集合元素进行随机排序 sort(List):根据元素的自然顺序对指定List集合元素按升序排序 sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序 swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换 【查找/替换】 Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素 Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素 Object min(Collection) Object min(Collection,Comparator) int frequency(Collection,Object):返回指定集合中指定元素的出现次数 void copy(List dest,List src):将src中的内容复制到dest中 boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值 【同步控制】 synchronizedList(List) synchronizedList(Set) 【设置不可变/只读】 unmodifiableList(List) unmodifiableMap(Map)

使用Arrays.aslist的注意点: 调用Arrays的asList()方法将数组转换成List时返回的是Arrays的静态内部类ArrayList,非日常使用的Java.util.ArrayList类,该内部类未重写add()和remove()方法,其父类AbstractList实现的add()方法和remove()方法只会抛出UnsupportedOperationException异常,导致调用Arrays的静态内部类ArrayList的这两个方法时,只会抛出异常 Arrays.asList返回的List是对Array的引用,数组长度不允许改变,因此不重写add()方法和remove()方法,避免出现通过返回的List修改原数组长度的非法操作

  1. public static void main(String[] args) {
  2. Integer[] datas = {1,2,3,4,5};
  3. List<Integer> list = Arrays.asList(datas);
  4. list.add(5);//运行报错:UnsupportedOperationException
  5. System.out.println(list.size());
  6. }

源码分析

泛型 (Generic)

把元素的类型设计成一个参数,这个类型参数叫做泛型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

泛型的作用:

  1. 解决元素存储的类型不安全问题
  2. 解决获取数据元素时,需要类型强制转换的问题

自定义泛型结构

泛型类/接口的声明

声明方式:interface test<T>class test<K,V>其中的T,K,V表示类型。这里可以使用任意字母,常用T表示,是Type的缩写。

泛型类的实例化

在类名后面指定类型参数的值(类型),T只能是类,不能用基本数据类型填充,但可以使用包装类填充。

自定义泛型结构注意点:

  1. 泛型类可能有多个参数,应将多个参数一起放在尖括号内,比如:<E1,E2,E3>
  2. 泛型类的构造器和普通类构造器结构相同:public GenericClass(){}而下面是错误的:public GenericClass<E>(){}
  3. 泛型不同的引用不能相互赋值,尽管在编译时ArrayList<String>ArrayList<Integer>是两种类型,但在运行时只有一个ArrayList被加载到JVM中
  4. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理
  5. JDK1.7及之后版本,泛型声明可以类型推断:ArrayList<Fruit> flist = new ArrayList<>();
  6. 泛型的指定中不能使用基本数据类型,可以使用包装类替换
  7. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、返回值类型。但在静态方法中不能使用类的泛型
  8. 异常类(Exception)不能声明为泛型
  9. 不能使用new E[]。但是可以使用E[] elements = (E[])new Object[capacity];因为ArrayList源码中声明为Object[] elementData,而非泛型参数类型数组
  10. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型
  1. class Father<T1, T2> {}
  2. // 子类不保留父类的泛型
  3. // 1)没有指明类型,则擦除泛型,等价于class Son extends Father<Object,Object>
  4. class Son1 extends Father {}
  5. // 2)没有指明父类类型,声明新的泛型,等价于class Son<A, B> extends Father<Object,Object>
  6. class Son2<A, B> extends Father{}
  7. // 3)指明具体类型
  8. class Son3 extends Father<Integer, String> {}
  9. // 4)指明父类具体类型,声明新的泛型
  10. class Son4<A, B> extends Father<Integer, String> {}
  11. // 子类保留父类的泛型
  12. // 1)全部保留
  13. class Son5<T1, T2> extends Father<T1, T2> {}
  14. // 2)部分保留
  15. class Son6<T2> extends Father<Integer, T2> {}
  16. // 3)全部保留,并声明新的泛型
  17. class Son7<T1, T2, A, B> extends Father<T1, T2> {}
  18. // 2)部分保留,并声明新的泛型
  19. class Son8<T2, A, B> extends Father<Integer, T2> {}
  1. public void testGenericAndSubClass() {
  2. Person[] persons = null;
  3. Man[] mans = null;//Man类是Person类的子类
  4. //Man[]数组可赋值给Person[]数组
  5. persons = mans;
  6. Person p = mans[0];
  7. //如果B是A的一个子类型(子类或者子接口)
  8. //而G是具有泛型声明的类或接口,G<B>并不是G<A>的子类型,两者不存在联系
  9. List<Person> personList = null;
  10. List<Man> manList = null;
  11. // personList = manList;(报错)
  12. }

泛型方法的声明

不管包含方法的类是不是泛型类。在方法声明时可以定义泛型参数,此时,参数的类型就是传入数据的类型。静态方法虽然不可使用泛型类的泛型参数,但也可声明为泛型方法,使用自己定义的泛型参数。

  1. //[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
  2. //DAO:database access operation
  3. public class DAO {
  4. //访问权限为public,必须在访问权限后加<泛型>,避免编译器将<泛型>识别为类
  5. public static <E> E get(int id, E e) {
  6. E result = null;
  7. return result;
  8. }
  9. }

类型通配符:?

泛型的类型通配符“?”一般用在方法的参数上,不能用在泛型类/方法声明、创建对象上。
使用泛型通配符的对象只能读取元素,不能写入,除了null属于所有类型的成员可以写入外。

  1. public static void read(List<?> list) {
  2. for (Object o : list) {
  3. System.out.println(o);
  4. }
  5. }

有限制的通配符(可以写入元素)

  1. <? extends Number>:只允许泛型为Number及Number子类的引用调用
  2. <? super Number>:只允许泛型为Number及Number父类的引用调用
  3. <? extends Comparable>:只允许泛型为实现Comparable接口的实现类的引用调用

IO流

IO流.xmind

文件的处理( java.io.File)

要在Java程序中表示一个真实存在的文件或目录,必须有一个File对象。java.io.File类正提供了这样一组处理文件的API,包括新建、删除、重命名文件和目录。File对象可以作为参数传递给流的构造器,用于处理IO流。

java.io.File类的使用 【构造器】 File(String filePath):以filePath为路径创建File对象,可以是绝对路径或者相对路径 File(String parentPath,String childPath):以parent为父路径,child为子路径创建File对象 File(File parentFile,String childPath):根据一个父File对象和子文件路径创建File对象 【获取文件信息】 public String getAbsolutePath():获取绝对路径 public String getPath() :获取路径 public String getName() :获取名称 public String getParent():获取上层文件目录路径。若无则返回null public long length() :获取文件长度(字节数),不能获取目录的长度 public long lastModified() :获取最后一次的修改时间,毫秒值 public String[] list() :获取指定目录下的所有文件或者目录的名称数组 public File[] listFiles() :获取指定目录下的所有文件或者目录的File数组 【判断】 public boolean isDirectory():判断是否是文件目录 public boolean isFile() :判断是否是文件 public boolean exists() :判断是否存在 public boolean canRead() :判断是否可读 public boolean canWrite() :判断是否可写 public boolean isHidden() :判断是否隐藏 【重命名/创建/删除】 public boolean renameTo(File dest):把文件重命名为指定的文件路径 public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false public boolean mkdir() :创建文件目录。如果此目录存在,就不创建。 如果此目录的上层目录不存在,也不创建 public boolean mkdirs() :创建文件目录。如果上层目录不存在,一并创建 public boolean delete():删除文件或者目录,目录内必须不含文件才能删除,删除后不会移动至回收站

新建的File对象可能没有一个与之对应真实存在的文件或目录,若真实存在则对象的各属性会显式赋值,否则除路径外其余属性取变量的默认值。

相对路径在IDEA和Eclipse中使用的区别: IDEA: 如果使用单元测试方法,相对路径基于当前Module的位置 如果使用main()方法,相对路径基于当前Project的位置 Eclipse: 无论是单元测试方法还是main()方法,相对路径都是基于当前Project的位置

Input/Output Stream

Java程序中,对于数据的输入/输出操作可用“流(stream)”的方式进行操作。

按操作数据单位不同分为:字节流(8 bit),字符流(16 bit) 按数据流的流向不同分为:输入流,输出流 按流处理的对象不同分为:节点流,处理流

  • 节点流:直接从数据源或目的地读写数据;
  • 处理流:不直接连接到数据源或目的地,而是套接在原有流(节点流或处理流)之上,提供额外的读写功能

Snipaste_2022-05-02_23-41-41.png

输入/输出流的抽象基类

InputStream是所有输入字节流的基类,Reader是所有输入字符流的基类。OnputStream是所有输出字节流的基类,Writer是所有输出字符流的基类。使用IO流时应注意:程序中打开的文件IO资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源。

【InputStream主要抽象方法】 int read():从输入流中读取数据的下一个字节。返回0到255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回-1 int read(byte[] b): 从输入流中将最多b.length个字节的数据读入一个byte数组中。如果因为已经到达流末尾而没有可用的字节,则返回-1,否则以整数形式返回实际读取的字节数 int read(byte[] b, int off,int len):将输入流中最多len个数据字节读入byte数组。尝试读取len个字节,但读取的字节也可能小于该值,以整数形式返回实际读取的字节数。如果流位于文件末尾没有可用的字节,则返回 -1 public void close() throws IOException:关闭此输入流并释放与该流关联的所有系统资源 【Reader主要抽象方法】 int read():读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x0000-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1 int read(char[] cbuf) :将字符读入数组,如果已到达流的末尾,则返回 -1,否则返回本次读取的字符数 int read(char[] cbuf,int off,int len) :将字符存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数 public void close() throws IOException:关闭此输入流并释放与该流关联的所有系统资源 【 OutputStream主要抽象方法】 void write(int b):将指定的字节写入此输出流,规定是:向输出流写入一个字节。要写入的字节是参数b的八个低位。b 的24个高位将被忽略, 即写入0~255范围的 void write(byte[] b): 将b.length个字节从指定的byte数组写入输出流 void write(byte[] b,int off,int len) :将指定byte数组中从偏移量off开始的len个字节写入输出流 public void flush()throws IOException :刷新此输出流并强制写出所有缓冲的输出字节 public void close() throws IOException:关闭此输出流并释放与该流关联的所有系统资源 【Writer主要抽象方法】 void write(int c) :写入单个字符,要写入的字符包含在给定整数值的16个低位中,16 个高位被忽略,即写入0 到 65535之间的Unicode码 void write(char[] cbuf) :写入字符数组 void write(char[] cbuf,int off,int len):写入字符数组的某一部分,从off开始,写入len个字符 void write(String str): 写入字符串 void write(String str,int off,int len): 写入字符串的某一部分 void flush():刷新该流的缓冲,则立即将它们写入预期目标 public void close() throws IOException:关闭此输出流并释放与该流关联的所有系统资源

流处理的主要步骤:

  1. 建立一个流对象,将已存在的一个文件加载进流
  2. 创建一个临时存放数据的数组(byte[] 或 char[])或其它容器
  3. 调用流对象的读取方法将流中的数据读入到数组中,或将数组中的数据写入流
  4. 关闭资源

    节点流

    1. //新建文件对象
    2. File file1 = new File("test1.txt");
    3. File file2 = new File("test2.txt");
    4. //新建字节流对象
    5. FileInputStream fis = null;
    6. FileOutputStream fos = null;
    7. //try-catch-finally捕获异常
    8. try {
    9. //将文件加载进流
    10. fis = new FileInputStream(file1);
    11. fos = new FileOutputStream(file2);
    12. //流信息操作
    13. byte[] bytes = new byte[1024];//字节流使用字节数组
    14. int len;
    15. while((len=fis.read(bytes))!=-1){
    16. fos.write(bytes,0,len);
    17. }
    18. } catch (IOException e) {
    19. e.printStackTrace();
    20. } finally {
    21. //关闭输入字节流
    22. try {
    23. if(fis != null){
    24. fis.close();
    25. }
    26. } catch (IOException e) {
    27. e.printStackTrace();
    28. }
    29. //关闭输出字节流
    30. try {
    31. if(fos!=null){
    32. fos.close();
    33. }
    34. } catch (IOException e) {
    35. e.printStackTrace();
    36. }
    37. }
    1. //新建文件对象
    2. File file1 = new File("test1.txt");
    3. File file2 = new File("test2.txt");
    4. //新建字符流对象
    5. FileReader fileReader = null;
    6. FileWriter fileWriter = null;
    7. StringBuffer str = new StringBuffer();
    8. try {
    9. //字符流
    10. fileReader = new FileReader(file1);
    11. fileWriter = new FileWriter(file2);
    12. //流信息操作
    13. char[] chars = new char[1024];//字符流使用字符数组
    14. int len;
    15. while ((len = fileReader.read(chars)) != -1) {
    16. fileWriter.write(chars, 0, len);
    17. str.append(chars,0,len);
    18. }
    19. } catch (IOException e) {
    20. e.printStackTrace();
    21. } finally {
    22. //关闭字符输入流
    23. try {
    24. if (fileReader != null) {
    25. fileReader.close();
    26. }
    27. } catch (IOException e) {
    28. e.printStackTrace();
    29. }
    30. //关闭字符输出流
    31. try {
    32. if (fileWriter != null) {
    33. fileWriter.close();
    34. }
    35. } catch (IOException e) {
    36. e.printStackTrace();
    37. }
    38. }

    缓冲流

    Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。读写数据时,数据按块读入缓冲区,其后的读写操作则直接访问缓冲区。

    如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,并关闭内层节点流 flush()方法可以强制将缓冲区的内容全部写入输出流,不必等缓存区写满

  1. BufferedInputStream bis = null;
  2. BufferedOutputStream bos = null;
  3. try {
  4. //创建流,缓冲流套接节点流,同时传入文件
  5. bis = new BufferedInputStream(new FileInputStream("test1.txt"));
  6. bos = new BufferedOutputStream(new FileOutputStream("test2.txt"));
  7. //流数据处理
  8. byte[] bytes = new byte[1024];
  9. int len;
  10. while((len=bis.read(bytes))!=-1){
  11. bos.write(bytes,0,len);
  12. }
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. } finally {
  16. //关闭流
  17. try {
  18. if(bis != null){
  19. bis.close();
  20. }
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. }
  24. try {
  25. if (bos!=null) {
  26. bos.close();
  27. }
  28. } catch (IOException e) {
  29. e.printStackTrace();
  30. }
  31. }
  1. //创建流
  2. BufferedReader bufferedReader = null;
  3. BufferedWriter bufferedWriter = null;
  4. try {
  5. bufferedReader = new BufferedReader(new FileReader("test1.txt"));
  6. bufferedWriter = new BufferedWriter(new FileWriter("test2.txt"));
  7. //处理流信息
  8. String str = "";
  9. //可以使用bufferedReader的readLine()方法一次读取一行进行处理
  10. while ((str = bufferedReader.readLine()) != null) {
  11. bufferedWriter.write(str);
  12. bufferedWriter.newLine();
  13. }
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. } finally {
  17. //关闭资源
  18. try {
  19. if (bufferedReader != null) {
  20. bufferedReader.close();
  21. }
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. try {
  26. if (bufferedWriter != null) {
  27. bufferedWriter.close();
  28. }
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }

转换流

转换流提供了在字节流和字符流之间的转换,可以实现解码和重新编码的功能,属于处理流的一种。
InputStreamReader将字节的输入流按指定字符集转换为字符的输入流,需要和InputStream套接使用;OutputStreamWriter将字符的输出流按指定字符集转换为字节的输出流,需要和OutputStream套接使用。
Snipaste_2022-05-03_01-29-33.png

  1. //转换流,字节读入转字符,字符输出转字节,可重新编码
  2. InputStreamReader isr = null;
  3. OutputStreamWriter osw = null;
  4. try {
  5. //创建转换流,
  6. isr = new InputStreamReader(new FileInputStream("test1.txt"), "utf8");
  7. osw = new OutputStreamWriter(new FileOutputStream("test_charset"), "gbk");
  8. //流信息处理
  9. char[] chars = new char[1024];
  10. int len;
  11. while ((len = isr.read(chars)) != -1) {
  12. osw.write(chars, 0, len);
  13. }
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. } finally {
  17. //关闭流
  18. try {
  19. if (isr != null) {
  20. isr.close();
  21. }
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. try {
  26. if (osw != null) {
  27. osw.close();
  28. }
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }

标准输入/输出

System.in和System.out分别代表了系统标准输入和输出,默认输入设备是:键盘,输出设备是:显示器 System.in的类型是InputStream;System.out的类型是PrintStream 重定向:通过System类的setIn,setOut方法对默认设备进行改变,相关方法为public static void setIn(InputStream in)public static void setOut(PrintStream out)

  1. System.out.println("请输入信息(退出输入e或exit):");
  2. // 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
  3. BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  4. String s = null;
  5. try {
  6. while ((s = br.readLine()) != null) { // 读取用户输入的一行数据,阻塞程序
  7. if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {
  8. System.out.println("安全退出!!");
  9. break;
  10. }
  11. // 将读取到的整行字符串转成大写输出
  12. System.out.println("转为大写:" + s.toUpperCase());
  13. System.out.println("继续输入信息");
  14. }
  15. } catch (IOException e) {
  16. e.printStackTrace();
  17. } finally {
  18. try {
  19. if (br != null) {
  20. br.close(); // 关闭外层流时,会自动关闭包装的内层节点流
  21. }
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. }

随机存取文件流

RandomAccessFile声明在java.io包下,但直接继承于java.lang.Object类。并且实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。RandomAccessFile类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写。

RandomAccessFile 类的构造方法有如下两种重载形式:

  1. RandomAccessFile(File file, String mode):访问参数 file 指定的文件,访问形式由参数 mode 指定,mode 参数有两个常用的可选值 r 和 rw,其中 r 表示只读,rw 表示读写
  2. RandomAccessFile(String name, String mode):访问参数 name 指定的文件

void seek(long pos):将文件记录指针定位到 pos 位置

NIO(Non-Blocking IO)

NIO与原来的IO有同样的作用,但是使用的方式完全不同。相比IO是面向流的操作、NIO支持面向缓冲区、基于通道的IO操作,以更加高效的方式进行文件的读写操作。

网络编程

InetAddress类

java.net.InetAddress类封装了与IP操作相关的方法,该类没有提供公共的构造器,而是提供了静态方法来获取 InetAddress实例。

  1. //在给定主机名的情况下,根据系统上配置的DNS服务返回其IP地址所组成的数组
  2. InetAddress[] addresses = InetAddress.getAllByName("www.bilibili.com");
  3. System.out.println(Arrays.toString(addresses));
  4. System.out.println(InetAddress.getByName("localhost"));//返回给定主机名的IP对象
  5. System.out.println(InetAddress.getLocalHost());//返回本主机IP对象
  6. //getHostAddress()以字符串形式返回IP对象的IP
  7. System.out.println(InetAddress.getByName("www.bilibili.com").getHostAddress());
  8. //getHostName()以字符串形式返回IP对象的主机名
  9. System.out.println(InetAddress.getByName("www.bilibili.com").getHostName());
  10. //测试网络是否可达
  11. System.out.println(InetAddress.getByName("www.bilibili.com").isReachable(100));

Socket

具有唯一标识的IP地址和端口号组合在一起构成唯一的标识符套接字,通信的两端都要有Socket,它是两台机器间通信的端点。Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。

流套接字(stream socket):使用TCP提供可靠的字节流服务 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务

1189312-20170916130334297-694676605.png

TCP网络编程

  1. //服务端程序
  2. @Test
  3. public void server() {
  4. ServerSocket serverSocket = null;
  5. Socket clientSocket = null;
  6. InputStreamReader isr = null;
  7. StringBuilder stringBuilder = null;
  8. try {
  9. //创建服务端套接字(指定端口12345)
  10. serverSocket = new ServerSocket(12345);
  11. //监听连接请求,创建通信套接字
  12. clientSocket = serverSocket.accept();
  13. //调用通信套接字的输入流,利用转换流包装为字符流
  14. InputStream inputStream = clientSocket.getInputStream();
  15. isr = new InputStreamReader(inputStream);
  16. //流信息处理
  17. char[] chars = new char[1024];
  18. int len;
  19. stringBuilder = new StringBuilder();
  20. while ((len = isr.read(chars)) != -1) {
  21. stringBuilder.append(chars, 0, len);
  22. }
  23. stringBuilder.append("---接收自:" + clientSocket.getInetAddress());
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. } finally {
  27. //关闭资源
  28. try {
  29. if (isr != null) {
  30. isr.close();
  31. }
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. try {
  36. if (clientSocket != null) {
  37. clientSocket.close();
  38. }
  39. } catch (IOException e) {
  40. e.printStackTrace();
  41. }
  42. try {
  43. if (serverSocket != null) {
  44. serverSocket.close();
  45. }
  46. } catch (IOException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. System.out.println(stringBuilder);
  51. }
  52. //客户端程序
  53. @Test
  54. public void client() {
  55. Socket client = null;
  56. OutputStreamWriter ops = null;
  57. try {
  58. //创建套接字,连接localhost的12345端口
  59. client = new Socket("localhost", 12345);
  60. //调用通信套接字的输出流,利用转换流包装为字符流
  61. OutputStream outputStream = client.getOutputStream();
  62. ops = new OutputStreamWriter(outputStream);
  63. ops.write("Hello,World");
  64. } catch (IOException e) {
  65. e.printStackTrace();
  66. } finally {
  67. //关闭资源
  68. try {
  69. if (ops != null) {
  70. ops.close();
  71. }
  72. } catch (IOException e) {
  73. e.printStackTrace();
  74. }
  75. try {
  76. if (client != null) {
  77. client.close();
  78. }
  79. } catch (IOException e) {
  80. e.printStackTrace();
  81. }
  82. }
  83. }
  1. //服务端程序
  2. @Test
  3. public void server() {
  4. ServerSocket serverSocket = null;
  5. Socket clientSocket = null;
  6. InputStream inputStream = null;
  7. FileOutputStream fos = null;
  8. OutputStream outputStream = null;
  9. try {
  10. //创建服务端套接字(指定端口12345)
  11. serverSocket = new ServerSocket(12345);
  12. //监听客户端请求,创建通信套接字
  13. clientSocket = serverSocket.accept();
  14. //获取通信套接字流对象
  15. inputStream = clientSocket.getInputStream();
  16. //创建输出字节流对象
  17. fos = new FileOutputStream("rev_client_file");
  18. //流信息处理
  19. byte[] bytes = new byte[1024];
  20. int len;
  21. while ((len = inputStream.read(bytes)) != -1) {
  22. fos.write(bytes, 0, len);
  23. }
  24. //获取通信套接字输出流对象,返回“接收成功”的信息
  25. outputStream = clientSocket.getOutputStream();
  26. outputStream.write("receive file success".getBytes());
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. } finally {
  30. //关闭资源
  31. try {
  32. if (outputStream != null) {
  33. outputStream.close();
  34. }
  35. } catch (IOException e) {
  36. e.printStackTrace();
  37. }
  38. try {
  39. if (fos != null) {
  40. fos.close();
  41. }
  42. } catch (IOException e) {
  43. e.printStackTrace();
  44. }
  45. try {
  46. if (inputStream != null) {
  47. inputStream.close();
  48. }
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. try {
  53. if (clientSocket != null) {
  54. clientSocket.close();
  55. }
  56. } catch (IOException e) {
  57. e.printStackTrace();
  58. }
  59. try {
  60. if (serverSocket != null) {
  61. serverSocket.close();
  62. }
  63. } catch (IOException e) {
  64. e.printStackTrace();
  65. }
  66. }
  67. }
  68. //客户端程序
  69. public void client() {
  70. Socket clientsocket = null;
  71. OutputStream ops = null;
  72. FileInputStream fis = null;
  73. InputStream ips = null;
  74. ByteArrayOutputStream baos = null;
  75. try {
  76. //创建套接字,连接localhost的12345端口
  77. clientsocket = new Socket("localhost", 12345);
  78. //获取socket输出流对象
  79. ops = clientsocket.getOutputStream();
  80. //创建文件输入字节流
  81. fis = new FileInputStream("test1.txt");
  82. //流信息处理
  83. byte[] bytes = new byte[1024];
  84. int len;
  85. while ((len = fis.read(bytes)) != -1) {
  86. ops.write(bytes, 0, len);
  87. }
  88. //写入流末尾标识,否则接收方的读取方法会一直阻塞
  89. clientsocket.shutdownOutput();
  90. //获取socket输入流对象
  91. ips = clientsocket.getInputStream();
  92. //利用字节数组输出流接收写入的字节流
  93. baos = new ByteArrayOutputStream();
  94. byte[] buffer = new byte[1024];
  95. int length;
  96. while ((length = ips.read(buffer)) != -1) {
  97. baos.write(buffer, 0, length);
  98. }
  99. System.out.println(baos.toString());
  100. } catch (IOException e) {
  101. e.printStackTrace();
  102. } finally {
  103. //关闭资源
  104. try {
  105. if (baos != null) {
  106. baos.close();
  107. }
  108. } catch (IOException e) {
  109. e.printStackTrace();
  110. }
  111. try {
  112. if (ips != null) {
  113. ips.close();
  114. }
  115. } catch (IOException e) {
  116. e.printStackTrace();
  117. }
  118. try {
  119. if (fis != null) {
  120. fis.close();
  121. }
  122. } catch (IOException e) {
  123. e.printStackTrace();
  124. }
  125. try {
  126. if (ops != null) {
  127. ops.close();
  128. }
  129. } catch (IOException e) {
  130. e.printStackTrace();
  131. }
  132. try {
  133. if (clientsocket != null) {
  134. clientsocket.close();
  135. }
  136. } catch (IOException e) {
  137. e.printStackTrace();
  138. }
  139. }
  140. }

UDP网络编程

DatagramSocket和DatagramPacket实现了基于UDP协议的网络传输。
UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送达目的地,也不能确定什么时候可以抵达。DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。

  1. //服务端程序
  2. @Test
  3. public void server() {
  4. DatagramSocket datagramSocket = null;
  5. try {
  6. //建立服务端套接字,指定监听端口
  7. datagramSocket = new DatagramSocket(12345);
  8. //建立数据报packet对象
  9. byte[] bytes = new byte[1024];
  10. DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
  11. //接收数据报
  12. datagramSocket.receive(packet);
  13. //解析数据报
  14. String str = new String(packet.getData(), 0, packet.getLength());
  15. System.out.println(str);
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. } finally {
  19. //关闭资源
  20. if (datagramSocket != null) {
  21. datagramSocket.close();
  22. }
  23. }
  24. }
  25. //客户端程序
  26. @Test
  27. public void client() {
  28. DatagramSocket clientsocket = null;
  29. try {
  30. //建立UDP套接字
  31. clientsocket = new DatagramSocket();
  32. //数据报
  33. byte[] data = "用户数据报消息".getBytes();
  34. InetAddress inet = InetAddress.getLocalHost();//获取地址
  35. DatagramPacket packet = new DatagramPacket(data, 0, data.length, inet, 12345);
  36. //发送数据报
  37. clientsocket.send(packet);
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. } finally {
  41. //关闭资源
  42. if (clientsocket != null) {
  43. clientsocket.close();
  44. }
  45. }
  46. }

URL

URL(Uniform Resource Locator):统一资源定位符,表示Internet上某一资源的地址。
URL的基本结构由5部分组成:<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表,参数列表格式为参数名=参数值&参数名=参数值
类URL和URLConnection提供了高级网络应用API,通过URL对象可以创建应用程序和URL表示的网络资源之间的连接,使程序可以读取或上传资源。

获取URL对象属性的方法: public String getProtocol( ):获取该URL的协议名 public String getHost( ):获取该URL的主机名 public String getPort( ):获取该URL的端口号,若URL的实例未申明(省略)端口号,则返回值为-1 public String getPath( ):获取该URL的文件路径 public String getFile( ):获取该URL的文件名 public String getQuery( ):获取该URL的查询名

  1. URL url = null;
  2. try {
  3. url = new URL("https://www.bilibili.com/video/BV1LT4y1r7XN");
  4. } catch (MalformedURLException e) {
  5. e.printStackTrace();
  6. }
  7. HttpsURLConnection urlConnection = null;
  8. InputStream inputStream = null;
  9. FileOutputStream fos = null;
  10. try {
  11. //创建URL连接对象
  12. urlConnection = (HttpsURLConnection) url.openConnection();
  13. urlConnection.connect();
  14. //获取URL连接对象的输入流
  15. inputStream = urlConnection.getInputStream();
  16. //创建文件输出字节流
  17. fos = new FileOutputStream("bilibili_vedio");
  18. //流消息处理
  19. byte[] bytes = new byte[1024];
  20. int len;
  21. while ((len = inputStream.read(bytes)) != -1) {
  22. fos.write(bytes, 0, len);
  23. }
  24. System.out.println("视频下载完成");//非静态对象可能下载的不是网页看到的内容
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. } finally {
  28. //关闭资源
  29. try {
  30. if (fos != null) {
  31. fos.close();
  32. }
  33. } catch (IOException e) {
  34. e.printStackTrace();
  35. }
  36. try {
  37. if (inputStream != null) {
  38. inputStream.close();
  39. }
  40. } catch (IOException e) {
  41. e.printStackTrace();
  42. }
  43. if (urlConnection != null) {
  44. urlConnection.disconnect();
  45. }
  46. }

反射(java.lang.reflect)

反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作类的内部属性及方法。JVM加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息。这个对象就像一面镜子,透过这个镜子可以看到类的结构,所以,我们形象的称之为:反射。

动态语言&静态语言

  • 动态语言:是一类在运行时可以改变元素结构的语言:例如新的函数、对象、甚至代码被引进,已有的函数可以被删除或是做一些结构上的调整。主要代表有JavaScript、Python
  • 静态语言:运行时结构不可变的语言就是静态语言,如Java、C、C++

Java虽然不是动态语言,但利用反射机制、字节码操作可以具备一定的动态性

Class类

在Object类中定义了获取Class类对象的方法public final Class getClass(),此方法被所有子类继承。

  • Class类也是类的一种,只是名字和class关键字高度相似,注意Java是大小写敏感的语言
  • Class类的对象内容是你创建的类的类型信息,比如你创建一个shapes类,那么,Java会生成一个内容是shapes的Class类的对象,且只能存在一个
  • Class类的对象不能像普通类一样,以 new shapes() 的方式创建,它的对象只能由JVM创建,因为这个类没有public构造函数
  • Class类的作用是运行时提供或获得某个对象的类型信息

Class类实例的获取

获取Class实例的方法:

  1. 若已知具体的类,通过类的class属性获取,该方法不会使JVM自动初始化该类,而以下的其它办法会使JVM初始化该类
  2. 已知类的实例,调用该实例的getClass()方法获取Class对象
  3. 已知类的全类名,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException异常
  4. 调用类的加载器生成Class实例
  1. //1.通过类的class属性获取
  2. Class obj0 = String.class;
  3. //2.通过类实例的getclass方法获取
  4. Class obj1 = "".getClass();
  5. //3.调用Class类的forName方法,传入全类名获取
  6. Class obj2 = Class.forName("java.lang.String");
  7. //4.使用类的加载器生成,test_class为一个测试类,com.java.test_class为全类名
  8. ClassLoader classLoader = test_class.class.getClassLoader();
  9. Class obj3 = classLoader.loadClass("com.java.test_class");

Class类的实例基本包含了所有的Java类型:

  1. class:外部类、成员内部类、局部内部类、匿名内部类
  2. interface:接口
  3. Object []/[][]:数组(数组元素类型和维度一样,就是同一个Class实例)
  4. enum:枚举类
  5. annotation:注解
  6. primitive type:基本数据类型
  7. void:空类型

类的加载

当程序主动使用某个类时,如果该类未被加载到内存中,则系统会进行类的加载、链接、初始化。
Snipaste_2022-05-04_15-52-06.png

加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程 验证:确保加载的类信息符合JVM规范 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程 初始化:执行类构造器方法的过程。类构造器方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。JVM会保证类构造方法在多线程环境中被正确加锁和同步

【类初始化发生的时机】 主动引用(一定会发生初始化):

  1. main方法所在的类
  2. 声明一个类的对象
  3. 调用类的静态成员(非final常量)和静态方法
  4. 对类进行反射调用
  5. 初始化一个类时,如果其父类没有被初始化,则会先初始化父类

被动引用(不会发生初始化):

  1. 通过子类引用父类的静态变量,不会导致子类初始化
  2. 数组定义类引用,不会触发此类的初始化
  3. 引用常量不会触发此类的初始化

类的加载器(ClassLoader)的作用是把class文件字节码内容加载到内存。

引导类加载器:用C++编写的,JVM自带的类加载器,负责Java平台核心类库的装载。该加载器无法直接获取 扩展类加载器:负责将jre/lib/ext目录下的jar包装入工作库 系统类加载器:负责其它目录下的类与jar包装入工作库,是最常用的加载器

  1. //系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
  2. ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
  3. System.out.println(systemClassLoader);
  4. //扩展类加载器:sun.misc.Launcher$ExtClassLoader@54bedef2
  5. ClassLoader extensionClassLoader = systemClassLoader.getParent();
  6. System.out.println(extensionClassLoader);
  7. //引导类加载器:无法显式获取null
  8. ClassLoader bootstapClassLoader = extensionClassLoader.getParent();
  9. System.out.println(bootstapClassLoader);

反射创建运行时类的对象

使用对应的Class类实例的newInstance方法可以创建运行时类的对象。使用时须注意:

  1. 运行时类必须提供空参的构造器
  2. 空参构造器的访问权限通常设置为public,确保可被调用
    1. Class obj = null;
    2. try {
    3. obj = Class.forName("java.util.Date");//通过Class类的forName方法获取Class类实例
    4. } catch (ClassNotFoundException e) {
    5. e.printStackTrace();
    6. }
    7. Date date = null;
    8. try {
    9. date = (Date) obj.newInstance();//使用newInstance方法创建运行时类对象
    10. } catch (InstantiationException e) {
    11. e.printStackTrace();
    12. } catch (IllegalAccessException e) {
    13. e.printStackTrace();
    14. }
    15. System.out.println(date);

    反射获取运行时类结构

    getFields():获取当前运行时类及其父类中声明为public访问权限的属性 getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中声明的属性) getMethods():获取当前运行时类及其所父类中声明为public权限的方法 getDeclaredMethods():获取当前运行时类中声明的所有方法(不包含父类中声明的方法) getConstructors():获取当前运行时类中声明为public的构造器 getDeclaredConstructors():获取当前运行时类中声明的所有构造器 getSuperclass():获取运行时类的父类 getGenericSuperclass():获取运行时类的带泛型的父类 getInterfaces():获取运行时类实现的接口 getPackage():获取运行时类所在的包 getAnnotations():获取运行时类声明的注解

反射调用运行时类结构

通过反射可以调用运行时类的结构,包括属性、方法、构造器。

  1. //创建Class类实例
  2. Class obj = null;
  3. try {
  4. obj = Class.forName("com.java.Person");//创建Class类实例
  5. } catch (ClassNotFoundException e) {
  6. e.printStackTrace();
  7. }
  8. //利用Class类实例的方法创建对象
  9. Person person = null;
  10. try {
  11. person = (Person) obj.newInstance();//调用Class类实例的newInstance方法生成对象
  12. } catch (InstantiationException e) {
  13. e.printStackTrace();
  14. } catch (IllegalAccessException e) {
  15. e.printStackTrace();
  16. }
  17. //调用运行时类的属性,测试类Person中有属性name
  18. try {
  19. Field name = obj.getDeclaredField("name");//获取指定属性
  20. name.setAccessible(true);//设置访问权限为可访问,避免private等权限约束
  21. name.set(person,"xiaoming");//设置指定属性
  22. System.out.println(name.get(person));
  23. } catch (NoSuchFieldException e) {
  24. e.printStackTrace();
  25. } catch (IllegalAccessException e) {
  26. e.printStackTrace();
  27. }
  28. //调用运行时类的方法,测试类Person中有方法display
  29. try {
  30. Method display = obj.getDeclaredMethod("display");//获取Person类的私有方法
  31. display.setAccessible(true);//将访问权限设置为可访问
  32. display.invoke(person);//invoke方法可调用指定对象的该方法
  33. } catch (NoSuchMethodException e) {
  34. e.printStackTrace();
  35. } catch (IllegalAccessException e) {
  36. e.printStackTrace();
  37. } catch (InvocationTargetException e) {
  38. e.printStackTrace();
  39. }
  40. //调用运行时类的静态方法,测试类Person中有静态方法staticmethod
  41. String str = null;
  42. try {
  43. //获取Person类中的私有静态方法,注意写明参数类型
  44. Method staticmethod = obj.getDeclaredMethod("staticmethod",String.class);
  45. staticmethod.setAccessible(true);//设置访问权限为可访问
  46. //invoke调用方法,并接收返回值,因为是静态方法,第一个参数可写参数类本身或null
  47. str = (String) staticmethod.invoke(null, "Hello");
  48. } catch (NoSuchMethodException e) {
  49. e.printStackTrace();
  50. } catch (IllegalAccessException e) {
  51. e.printStackTrace();
  52. } catch (InvocationTargetException e) {
  53. e.printStackTrace();
  54. }
  55. System.out.println(str);
  56. //调用运行时类的构造器
  57. Person newperson = null;
  58. try {
  59. //获取Person类的构造器,指明参数类型
  60. Constructor constructor = obj.getDeclaredConstructor(String.class,int.class);
  61. constructor.setAccessible(true);//设置访问权限为可访问
  62. //使用该构造器创建对象
  63. newperson = (Person) constructor.newInstance("xiaohong",27);
  64. } catch (NoSuchMethodException e) {
  65. e.printStackTrace();
  66. } catch (InstantiationException e) {
  67. e.printStackTrace();
  68. } catch (IllegalAccessException e) {
  69. e.printStackTrace();
  70. } catch (InvocationTargetException e) {
  71. e.printStackTrace();
  72. }
  73. System.out.println(newperson);

代理模式-动态代理

动态代理:在程序运行时根据需要动态创建代理对象的代理方式。也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。应用场景:AOP为Aspect Oriented Programming的缩写,意为面向切面编程,一般使用动态代理给程序动态统一添加某种特定功能。
在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现调用处理器InvocationHandler接口。当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,我们可以在invoke方法中添加统一的处理逻辑。

【动态代理需要解决的两个主要问题】 问题一:如何根据加载到内存中的被代理类,动态创建一个代理类及其对象 通过Proxy.newProxyInstance()方法实现 问题二:当通过代理类的对象调用方法时,如何动态去调用被代理类中的同名方法 通过InvocationHandler接口的实现类及其方法invoke()

  1. public interface InvocationHandler {
  2. Object invoke(Object proxy, Method method, Object[] args);
  3. }
  1. public class DynamicProxy_test {
  2. //动态代理
  3. public static void main(String[] args) {
  4. //被代理对象
  5. nikeclothfactory nike = new nikeclothfactory();
  6. //生成动态代理对象
  7. Object proxyInstance = ProxyFactory.getProxyInstance(nike);
  8. clothfactory sellproxy = (clothfactory) proxyInstance;
  9. sellproxy.method();
  10. }
  11. }
  12. //接口:功能
  13. interface clothfactory{
  14. void method();
  15. }
  16. //被代理类
  17. class nikeclothfactory implements clothfactory{
  18. @Override
  19. public void method() {
  20. System.out.println("nikeclothfactory method");
  21. }
  22. }
  23. //代理增加的功能
  24. class Sellmethod {
  25. //可以将要通过代理增加的功能定义在一个类中
  26. public void before() {
  27. System.out.println("before");
  28. }
  29. public void after() {
  30. System.out.println("after");
  31. }
  32. }
  33. //动态代理类
  34. class ProxyFactory{
  35. public static Object getProxyInstance(Object obj){
  36. //获取Class类实例
  37. Class clazz = obj.getClass();
  38. //实现调用处理器接口,创建中介类
  39. Handler handler = new Handler();
  40. handler.bind(obj);
  41. //获取代理类对象,并返回
  42. Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), handler);
  43. return proxy;
  44. }
  45. }
  46. //中介类,实现调用处理器接口
  47. class Handler implements InvocationHandler {
  48. //绑定被代理类
  49. private Object obj;
  50. public void bind(Object obj) {
  51. this.obj = obj;
  52. }
  53. @Override
  54. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  55. //在invoke方法中添加统一的处理逻辑,此处将所有功能另外定义在一个类中
  56. Sellmethod sellmethod = new Sellmethod();
  57. sellmethod.before();//调用增加的处理逻辑
  58. //传入使用反射获得的运行时类结构,并调用获取返回值
  59. Object returnvalue = method.invoke(obj,args);
  60. sellmethod.after();//调用增加的处理逻辑
  61. return returnvalue;
  62. }
  63. }

Lambda表达式

Lambda表达式:在Java 8语言中引入的一种新的语法元素和操作符。这个操作符为 “->”,该操作符被称为 Lambda 操作符或箭头操作符。它将Lambda分为两个部分, 左侧:指定了Lambda表达式需要的参数列表;右侧:指定了Lambda体,是抽象方法的实现逻辑,也即Lambda表达式要执行的功能。Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略 ->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句,可能是return语句,省略这一对{}和return关键字

函数式接口

  • 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口
  • 可以在一个接口上使用 @FunctionalInterface 注解,这样做可以在编译时检查它是否是一个函数式接口
  • Lambda表达式的本质:作为函数式接口的实例

Snipaste_2022-05-05_00-38-03.png
Snipaste_2022-05-05_13-14-14.png

  1. @Test
  2. public void functiontest1() {
  3. //使用Lambda表达式之前
  4. method1("Hello World", new Consumer<String>() {
  5. @Override
  6. public void accept(String s) {
  7. System.out.println(s);
  8. }
  9. });
  10. //使用Lambda表达式之后
  11. method1("Hello World", str -> System.out.println(str));
  12. }
  13. //消费型函数接口
  14. public void method1(String str, Consumer<String> consumer) {
  15. consumer.accept(str);
  16. }
  17. @Test
  18. public void functiontest2() {
  19. //使用Lambda表达式之前
  20. String val1 = method2(new Supplier<String>() {
  21. @Override
  22. public String get() {
  23. return "Hello World";
  24. }
  25. });
  26. System.out.println(val1);
  27. //使用Lambda表达式之后
  28. String val2 = method2(() -> "Hello World");
  29. System.out.println(val2);
  30. }
  31. //供给型接口
  32. public String method2(Supplier<String> supplier) {
  33. return supplier.get();
  34. }
  35. @Test
  36. public void functiontest3() {
  37. int val = 3894;
  38. //使用Lambda表达式之前
  39. int num1 = method3(val, new Function<Integer, Integer>() {
  40. @Override
  41. public Integer apply(Integer integer) {
  42. int i = integer/2;
  43. for (; i > 0; i--) {
  44. if (integer % i == 0) {
  45. return i;
  46. }
  47. }
  48. return -1;
  49. }
  50. });
  51. System.out.println(val + "的最大因子为:" + num1);
  52. //使用Lambda表达式之后
  53. int num2 = method3(val, a -> {
  54. int i = a/2;
  55. for (; i > 0; i--) {
  56. if (a % i == 0) {
  57. return i;
  58. }
  59. }
  60. return -1;
  61. });
  62. System.out.println(val + "的最大因子为:" + num2);
  63. }
  64. //函数型接口
  65. public int method3(int a, Function<Integer, Integer> function) {
  66. return function.apply(a);
  67. }
  68. @Test
  69. public void functiontest4() {
  70. // int num = 73939133;
  71. int num = 7393;
  72. //使用Lambda表达式之前
  73. boolean tag1 = method4(num, new Predicate<Integer>() {
  74. @Override
  75. public boolean test(Integer integer) {
  76. //费马素性测试:测试的数字较大可能发生溢出,应采用特殊的数据类型
  77. //(n^p) mod p = n
  78. BigInteger testnum = new BigInteger(String.valueOf(integer));
  79. for (int i = 3; i > 0; i--) {//测试三次
  80. int temp = (int) (Math.random() * num);
  81. BigInteger bigInteger = new BigInteger(String.valueOf(temp));
  82. BigInteger remain = bigInteger.pow(integer).remainder(testnum);
  83. if (!bigInteger.equals(remain)) return false;
  84. }
  85. return true;
  86. }
  87. });
  88. System.out.println(num + "是否可能是一个素数:" + tag1);
  89. //使用Lambda表达式之后
  90. boolean tag2 = method4(num, a -> {
  91. int temp = (int) (Math.sqrt(a));
  92. for (int i = 2; i <= temp; i++) {
  93. if (a % i == 0) return false;
  94. }
  95. return true;
  96. });
  97. System.out.println(num + "是否是一个素数:" + tag2);
  98. }
  99. //断定型接口
  100. public Boolean method4(int a, Predicate<Integer> predicate) {
  101. return predicate.test(a);
  102. }

方法引用

当要传递给Lambda体的操作,如果已经有实现的方法了,就可以使用方法引用。方法引用可以看做是Lambda表达式深层次的表达,也是函数式接口的一个实例,通过方法的名字来指向另一个方法,可以认为是Lambda表达式的一个语法糖。

要求:实现接口抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致 格式:使用操作符 “::” 将类(或对象)与方法名分隔开来 主要的使用情况:对象::实例方法名类::静态方法名类::实例方法名

  1. @Test
  2. public void test1(){
  3. //Lambda表达式是对象
  4. Consumer<String> consumer1 = str -> System.out.println(str);
  5. //调用方法
  6. consumer1.accept("Hello World");
  7. //改写为方法引用,对象::实例方法名
  8. PrintStream printout = System.out;
  9. Consumer<String> consumer2 = printout::println;
  10. consumer2.accept("Hello World");
  11. }
  12. @Test
  13. public void test2(){
  14. //Lambda表达式
  15. BiFunction<Integer,Integer,Integer> biFunction1 = (a,b)->{
  16. return Integer.compare(a,b);
  17. };
  18. System.out.println(biFunction1.apply(1, 2));
  19. //方法调用,类::静态方法名
  20. BiFunction<Integer,Integer,Integer> biFunction2 = Integer::compare;
  21. System.out.println(biFunction2.apply(1, 2));
  22. }
  23. @Test
  24. public void test3(){
  25. String str = "Hello World";
  26. System.out.println(str.length());
  27. //方法调用,类::实例方法名
  28. Function<String,Integer> function = String::length;
  29. System.out.println(function.apply(str));
  30. }
  1. @Test
  2. public void test1(){
  3. //Lambda表达式方式
  4. Function<char[],String> function1 = (chars)->new String(chars);
  5. String str1 = function1.apply(new char[]{'H','e','l','l','o'});
  6. System.out.println(str1);
  7. //方法引用
  8. Function<char[],String> function2 = String::new;
  9. String str2 = function2.apply(new char[]{'H','e','l','l','o'});
  10. System.out.println(str2);
  11. }
  12. @Test
  13. public void test2(){
  14. Function<Integer,String[]> function = String[]::new;
  15. String[] strarray = function.apply(5);
  16. System.out.println(Arrays.toString(strarray));
  17. }

JDK8其它语言特性

Stream API

Collection是一种静态的内存数据结构,而Stream是有关计算的。前者主要提供数据存储的结构,后者提供了一种高效且易于使用的处理数据的方式。 Stream的操作包含三个步骤 :创建、中间计算、终止,中间计算是惰性执行的,一旦执行终止操作,就执行中间操作链并产生结果。

  1. Integer[] array = new Integer[]{0,1,2,3,4,5,6,7,8,9};
  2. //Arrays新增的stream方法可以生成stream对象
  3. Stream<Integer> stream1 = Arrays.stream(array);
  4. ArrayList list = new ArrayList(Arrays.asList(array));
  5. Iterator<Integer> iterator = list.iterator();
  6. while(iterator.hasNext()){
  7. System.out.println(iterator.next());
  8. }
  9. //Collection对象提供了创建stream的方法
  10. Stream stream2 = list.stream();//创建顺序流
  11. Stream parallelStream2 = list.parallelStream();//创建并行流,可加快计算速度
  12. //Stream提供了of静态方法,可传入可变多个参数或数组构成stream
  13. Stream<Integer> stream3 = Stream.of(array);
  14. //Stream提供的iterate方法可用于生成无限流
  15. Stream.iterate(0,t->t+1).limit(10).map(t->2*t).forEach(System.out::println);
  16. //Stream提供的generate方法也可以生成无限流
  17. Stream.generate(Math::random).limit(10).forEach(System.out::println);

Stream的使用: 【中间计算】

  1. 筛选与切片:过滤filter(Predicate p);去重distinct();截断limit(long maxSize);丢弃skip(long n)
  2. 映射方法:map(Function f)mapToDouble(ToDoubleFunction f)mapToInt(ToIntFunction f)mapToLong(ToLongFunction f)flatMap(Function f)
  3. 排序:自然排序sorted();定制排序sorted(Comparator com)

【终止操作】

  1. 匹配查找:全部匹配allMatch(Predicate p);至少匹配一处anyMatch(Predicate p);不匹配noneMatch(Predicate p);返回第一个元素findFirst();返回任意的一个元素findAny()
  2. 统计:总数count();最大值max(Comparator com);最小值min(Comparator com);迭代forEach(Consumer c)
  3. 规约:归集处理后的元素,返回T类型(泛型)的数据reduce(T identity, BinaryOperator<T> accumulator);归集处理后的元素,返回Optional的数据Optional<T> reduce(BinaryOperator<T> accumulator)
  4. 收集:将流转换为其他形式collect(Collector c),Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例
  1. // Creating a list of Prime Numbers
  2. List<Integer> PrimeNumbers = Arrays.asList(5, 7, 11, 13);
  3. // Creating a list of Odd Numbers
  4. List<Integer> OddNumbers = Arrays.asList(1, 3, 5);
  5. // Creating a list of Even Numbers
  6. List<Integer> EvenNumbers = Arrays.asList(2, 4, 6, 8);
  7. List<List<Integer>> listOfListofInts = Arrays.asList(PrimeNumbers, OddNumbers, EvenNumbers);
  8. System.out.println("The Structure before flattening is : " + listOfListofInts);
  9. // Using flatMap for transformating and flattening.
  10. List<Integer> listofInts = listOfListofInts.stream()
  11. .flatMap(list -> list.stream())
  12. .collect(Collectors.toList());
  13. System.out.println("The Structure after flattening is : " +
  14. listofInts);

Optional类(java.util.Optional)

Optional类是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅保存null,表示这个值不存在。

Optional类常用方法 【创建Optional类对象】

  1. Optional.of(T t) : 创建一个Optional实例,t必须非空
  2. Optional.empty(): 创建一个空的Optional实例
  3. Optional.ofNullable(T t):创建一个Optional实例,t可以为null

【判断Optional容器中是否包含对象】

  1. boolean isPresent(): 判断是否包含对象
  2. void ifPresent(Consumer consumer):如果有值就执行Consumer接口的实现代码,并将该值作为参数传递

【获取Optional容器中的对象】

  1. T get():如果调用对象包含值,返回该值,否则抛异常
  2. T orElse(T other) :如果有值则将其返回,否则返回指定的other对象
  3. T orElseGet(Supplier other):如果有值则将其返回,否则返回由Supplier接口提供的对象
  4. T orElseThrow(Supplier exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实提供的异常