栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java基础入门(下)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java基础入门(下)

二、Java语言基础(下) 2.1 多线程

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

  1. 什么是程序?

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

  2. 什么是进程?

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

  3. 什么是线程?

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

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

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

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

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

    • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
    • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
2.1.1 创建多线程的方式
  1. 继承Thread类

  2. 实现Runnable接口

  3. JDK5新增-实现Callable接口

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

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

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

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

    线程池的优点:

    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没任务时最多保持多长时间后会终止
2.1.2 Thread类中的常用的方法
  1. start():启动当前线程;调用当前线程的run()。

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

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

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

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

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

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

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

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

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

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

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

线程的优先级:

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

2.1.4 线程的同步机制
  1. 背景

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

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

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

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

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

    • 同步代码块

      synchronized(同步监视器){
          //需要被同步的代码
      }
      
      
    • 同步方法

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

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

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

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

    • Lock锁 — JDK5.0新增

      synchronized 与 Lock的异同?

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

使用的优先顺序:

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

线程安全的单例模式:

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

避免出现死锁

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

public static void main(String[] args) {

    StringBuffer s1 = new StringBuffer();
    StringBuffer s2 = new StringBuffer();

    new Thread(){
        @Override
        public void run() {
            synchronized (s1){
                s1.append("a");
                s2.append("1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (s2){
                    s1.append("b");
                    s2.append("2");
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }
    }.start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (s2){
                s1.append("c");
                s2.append("3");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (s1){
                    s1.append("d");
                    s2.append("4");
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }
    }).start();
}
2.1.5 线程通信

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

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

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

备注:

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

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

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

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

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

    • 不同点:

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

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

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

  5. yield 和 sleep 的异同

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

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

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

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

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

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

String的不可变性

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

String类实例化方式

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

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

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

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

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

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

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

  1. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  2. 只要其中一个是变量,结果就在堆中。
  3. 如果拼接的结果调用intern()方法,返回值就在常量池中
String s1 = "javaEE";
String s2 = "hadoop";

String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;

System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false

String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
****************************
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//false

final String s4 = "javaEE";//s4:常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true

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

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

StringBuffer

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

2.2.2 时间API 2.2.2.1 获取系统当前时间

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

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

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

  • 区别:

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

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

  • 联系:

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

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

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

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

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

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

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

补充说明(了解即可):

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

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

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

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

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

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

前两代存在的问题举例:

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

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

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

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

  • 常用方法:

  1. 时间点:Instant
  • 简介

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

  • 常用方法:

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

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

  • 实例化方式:

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

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

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

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

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

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

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

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

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

  1. Comparable接口的使用

先来看一段代码:

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

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

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

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

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

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

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

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

  1. Comparator接口的使用

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

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

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

示例代码如下:

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

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

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

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

注解是什么?

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

为什么要学习注解

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

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

首先说明一些概念:

  • 什么是元注解?

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

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

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

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

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

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

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

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

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

怎么使用注解

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

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

@HelloAnnotation
public void setAge() {
    
}

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

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

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

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

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

    代码示例如下:

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

    示例代码如下:

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

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

    示例代码如下:

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

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

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

​ |----ArrayList、linkedList、Vector

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

​ |----HashSet、linkedHashSet、TreeSet

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

遍历Collection的两种方式:

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

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

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

2.3.2 Collection子接口:List接口

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

2.3.2.1 List接口常用实现类

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

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

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

2.3.2.2 源码分析
  1. ArrayList

    • jdk 7情况下

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

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

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

    • jdk8情况下

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

      扩容机制:与jdk 7 相同。

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

  2. linkedList

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

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

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

2.3.3 Collection子接口:Set接口

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

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

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

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

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

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

在JDK7中,HashSet底层的数据结构是“数组+链表”;

在JDK8中,HashSet底层的数据结构仍然是“数组+链表”,不过,当链表长度大于8时,链表会转换为红黑树,即数据结构变为了“数组+红黑树”。

2.3.3.1 Set接口常用实现类

|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值

|----linkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历

​ 在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。

​ 对于频繁的遍历操作,linkedHashSet效率高于HashSet.

|----TreeSet:可以照添加对象的指定属性,进行排序。

2.3.3.2 存储对象所在类的要求
  • HashSet/linkedHashSet:

    要求向Set(主要指:HashSet、linkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()方法。
    重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。

    重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

  • TreeSet:

    • 在自然排序中,比较两个对象是否相同的标准为compareTo()返回0。(不再是equals())
    • 在定制排序中,比较两个对象是否相同的标准为compare()返回0。(不再是equals())
2.3.3.3 TreeSet的使用
//方式一:自然排序
@Test
    public void test1(){
        TreeSet set = new TreeSet();

        //失败:不能添加不同类的对象
//        set.add(123);
//        set.add(456);
//        set.add("AA");
//        set.add(new User("Tom",12));

            //举例一:
//        set.add(34);
//        set.add(-34);
//        set.add(43);
//        set.add(11);
//        set.add(8);

        //举例二:
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Jack",33));
        set.add(new User("Jack",56));


        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }

//方式二:定制排序
    @Test
    public void test2(){
        Comparator com = new Comparator() {
            //照年龄从小到大排列
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }else{
                    throw new RuntimeException("输入的数据类型不匹配");
                }
            }
        };

        TreeSet set = new TreeSet(com);
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Mary",33));
        set.add(new User("Jack",33));
        set.add(new User("Jack",56));


        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
2.3.4 Map接口 2.3.4.1 常用实现类

|----Map:双列数据,存储key-value对的数据

​ |----HashMap:作为Map的主要实现类;线程不安全的,效率高;可以存储null的key和value

​ |----linkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。

​ 原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。

​ |----TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。底层使用红黑树

​ |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value

​ |----Properties:常用来处理配置文件。key和value都是String类型

2.3.4.2 存储结构的理解

Map中的key:无序的、不可重复的,使用Set存储的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)

Map中的value:无序的、可重复的,使用Collection存储的value —>value所在的类要重写equals()

一个键值对:key-value构成了一个Entry对象。

Map中的entry:无序的、不可重复的,使用Set存储所的entry

2.3.4.3 HashMap的实现原理
  1. JDK7中:

    HashMap map = new HashMap()://在实例化以后,底层创建了长度是16的一维数组Entry[] table。
    //...可能已经执行过多次put...
    
    map.put(key1,value1);
    
    
    
    • 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置;

    • 如果此位置上的数据为空,此时的key1-value1添加成功。

    • 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:

      • 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。

      • 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:

        ​ 如果equals()返回false:此时key1-value1添加成功。

        ​ 如果equals()返回true:使用value1替换value2。

    ​ 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。

  2. JDK8中:

    • new HashMap():底层没创建一个长度为16的数组
    • jdk 8底层的数组是:Node[],而非Entry[]
    • 首次调用put()方法时,底层创建长度为16的数组
    • jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
      • 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
      • 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。

HashMap底层典型属性的属性的说明:

DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

2.3.5 linkedHashMap的底层实现原理

​ linkedHashMap底层使用的结构与HashMap相同,因为linkedHashMap继承于HashMap.
​ 区别在于:linkedHashMap内部提供了Entry,替换HashMap中的Node.

2.3.6 TreeMap的使用

​ 向TreeMap中添加key-value,要求key必须是由同一个类创建的对象。因为要根据key进行排序:自然排序 、定制排序

2.3.7 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 对象的所旧值

    ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以使用synchronizedList(List list) 和 synchronizedMap(Map map)将ArrayList、HashMap转换为线程安全的。

2.4 泛型
1.举例:
【Order.java】

public class Order {

    String orderName;
    int orderId;
    //类的内部结构就可以使用类的泛型
    T orderT;
    public Order(){
        //编译不通过
//        T[] arr = new T[10];
        //编译通过
        T[] arr = (T[]) new Object[10];
    }

    public Order(String orderName,int orderId,T orderT){
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }

    //如下的个方法都不是泛型方法
    public T getOrderT(){
        return orderT;
    }

    public void setOrderT(T orderT){
        this.orderT = orderT;
    }

    //静态方法中不能使用类的泛型。
//    public static void show(T orderT){
//        System.out.println(orderT);
//    }

    public void show(){
        //编译不通过
//        try{
//        }catch(T t){
//        }
    }

    //泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没任何关系。
    //换句话说,泛型方法所属的类是不是泛型类都没关系。
    //泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
    public static   List copyFromArrayToList(E[] arr){

        ArrayList list = new ArrayList<>();

        for(E e : arr){
            list.add(e);
        }
        return list;

    }
}

【SubOrder.java】
public class SubOrder extends Order {//SubOrder:不是泛型类

    public static  List copyFromArrayToList(E[] arr){
        ArrayList list = new ArrayList<>();
        for(E e : arr){
            list.add(e);
        }
        return list;
    }

}

//实例化时,如下的代码是错误的
SubOrder o = new SubOrder<>();

【SubOrder1.java】
public class SubOrder1 extends Order {//SubOrder1:仍然是泛型类

}

备注:

  1. 泛型类的构造方法是public Person() {}而不是public Person(){}
  2. 异常类不能是泛型的;
  3. 泛型不能使用new T[],但是可以T[] data = (T[]) Object[length]

​ 类A是类B的父类,则A 是 B 的父类。但G 和G二者不具备子父类关系,二者是并列关系。

2.4.1 通配符:?

​ 类A是类B的父类,G和G是没关系的,二者共同的父类是G

 @Test
    public void test3(){
        List list1 = null;
        List list2 = null;

        List list = null;

        list = list1;
        list = list2;
        //编译通过
//        print(list1);
//        print(list2);


        //
        List list3 = new ArrayList<>();
        list3.add("AA");
        list3.add("BB");
        list3.add("CC");
        list = list3;
        //添加(写入):对于List就不能向其内部添加数据。
        //除了添加null之外。
//        list.add("DD");
//        list.add('?');

        list.add(null);

        //获取(读取):允许读取数据,读取的数据类型为Object。
        Object o = list.get(0);
        System.out.println(o);


    }

    public void print(List list){
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            Object obj = iterator.next();
            System.out.println(obj);
        }
    }

 
2.4.2 有限制条件的通配符的使用 

​ ? extends A:
​ G 可以作为G和G的父类,其中B是A的子类.(G是A以及A的子类的父类)

	? super A:
        	G 可以作为G和G的父类,其中B是A的父类.(G是A以及A的父类的父类)
@Test
 public void test4(){

     List list1 = null;
     List list2 = null;

     List list3 = new ArrayList();
     List list4 = new ArrayList();
     List list5 = new ArrayList();

     list1 = list3;
     list1 = list4;
//        list1 = list5;

//        list2 = list3;
     list2 = list4;
     list2 = list5;

     //读取数据:
     list1 = list3;
     Person p = list1.get(0);
     //编译不通过
     //Student s = list1.get(0);

     list2 = list4;
     Object obj = list2.get(0);
     编译不通过
//        Person obj = list2.get(0);

     //写入数据:
     //编译不通过
//        list1.add(new Student());

     //编译通过
     list2.add(new Person());
     list2.add(new Student());

 }	 
 
2.5 IO流 
  1. 操作数据单位:字节流、字符流

  2. 数据的流向:输入流、输出流

  3. 流的角色:节点流、处理流

2.5.1 File类
  • 常用构造器

    File(String filePath)
    File(String parentPath,String childPath)
    File(File parentFile,String childPath)
    
  • 常用方法

    getPath():返回路径。若为绝对路径,则返回绝对路径;若为相对路径,则返回相对路径。

    getAbsolutePath():返回路径。若为绝对路径,则返回绝对路径;若为相对路径,则返回user.dir+相对路径。

    IDEA中:
    如果使用JUnit中的单元测试方法测试,相对路径即为当前Module下。
    如果使用main()测试,相对路径即为当前的Project下。
    Eclipse中:
    不管使用单元测试方法还是使用main()测试,相对路径都是当前的Project下。

2.5.2 节点流:(参数为File对象)
  • FileReader、FileWriter:字符流、节点流

    FileWriter(file,false) / FileWriter(file):对原文件的覆盖

    FileWriter(file,true):不会对原文件覆盖,而是在原文件基础上追加内容

        public void testFileReaderFileWriter() {
            FileReader fr = null;
            FileWriter fw = null;
            try {
                //1.创建File类的对象,指明读入和写出的文件
                File srcFile = new File("hello.txt");
                File destFile = new File("hello2.txt");
    
                //不能使用字符流来处理图片等字节数据
    //            File srcFile = new File("爱情与友情.jpg");
    //            File destFile = new File("爱情与友情1.jpg");
    
    
                //2.创建输入流和输出流的对象
                 fr = new FileReader(srcFile);
                fw = new FileWriter(destFile);
    
    
                //3.数据的读入和写出操作
                char[] cbuf = new char[5];
                int len;//记录每次读入到cbuf数组中的字符的个数
                while((len = fr.read(cbuf)) != -1){
                    //每次写出len个字符
                    fw.write(cbuf,0,len);
    
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //4.关闭流资源
                //方式一:
    //            try {
    //                if(fw != null)
    //                    fw.close();
    //            } catch (IOException e) {
    //                e.printStackTrace();
    //            }finally{
    //                try {
    //                    if(fr != null)
    //                        fr.close();
    //                } catch (IOException e) {
    //                    e.printStackTrace();
    //                }
    //            }
                //方式二:
                try {
                    if(fw != null)
                        fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                try {
                    if(fr != null)
                        fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
    
        }
    
  • FIleInputStream、FileOutputStream:字节流、节点流

    2.FileInputStream / FileOutputStream的使用:
    * 1. 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
    * 2. 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理
    
    @Test
    public void testFileInputOutputStream()  {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //1.造文件
            File srcFile = new File("爱情与友情.jpg");
            File destFile = new File("爱情与友情2.jpg");
    
            //2.造流
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
    
            //3.复制的过程
            byte[] buffer = new byte[5];
            int len;
            while((len = fis.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
    
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null){
                //4.关闭流
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    
    }
    
2.5.3 缓冲流:(参数为对应的节点流对象)
  • BufferedReader、BufferedWriter:字符流、缓冲流

    3.2 使用BufferedReader和BufferedWriter:处理文本文件
    @Test
        public void testBufferedReaderBufferedWriter(){
            BufferedReader br = null;
            BufferedWriter bw = null;
            try {
                //创建文件和相应的流
                br = new BufferedReader(new FileReader(new File("dbcp.txt")));
                bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
    
                //读写操作
                //方式一:使用char[]数组
    //            char[] cbuf = new char[1024];
    //            int len;
    //            while((len = br.read(cbuf)) != -1){
    //                bw.write(cbuf,0,len);
    //    //            bw.flush();
    //            }
    
                //方式二:使用String
                String data;
                while((data = br.readLine()) != null){
                    //方法一:
    //                bw.write(data + "n");//data中不包含换行符
                    //方法二:
                    bw.write(data);//data中不包含换行符
                    bw.newline();//提供换行的操作
    
                }
    
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //关闭资源
                if(bw != null){
    
                    try {
                        bw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(br != null){
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
    
                }
            }
    
        }
    
  • BufferedInputStream、BufferedOutputStream:字节流、缓冲流

    3.1 使用BufferedInputStream和BufferedOutputStream:处理非文本文件
    //实现文件复制的方法
        public void copyFileWithBuffered(String srcPath,String destPath){
            BufferedInputStream bis = null;
            BufferedOutputStream bos = null;
    
            try {
                //1.造文件
                File srcFile = new File(srcPath);
                File destFile = new File(destPath);
                //2.造流
                //2.1 造节点流
                FileInputStream fis = new FileInputStream((srcFile));
                FileOutputStream fos = new FileOutputStream(destFile);
                //2.2 造缓冲流
                bis = new BufferedInputStream(fis);
                bos = new BufferedOutputStream(fos);
    
                //3.复制的细节:读取、写入
                byte[] buffer = new byte[1024];
                int len;
                while((len = bis.read(buffer)) != -1){
                    bos.write(buffer,0,len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //4.资源关闭
                //要求:先关闭外层的流,再关闭内层的流
                if(bos != null){
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
    
                }
                if(bis != null){
                    try {
                        bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
    
                }
                //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
    //        fos.close();
    //        fis.close();
            }
        }
    
    
  • 缓冲流可以加快流的读取、写入的速度。因为其内部提供了一个缓冲区。默认情况下是8kb。

2.5.4 转换流(属于字符流)

​ 转换流提供字节流与字符流之间的转换。

  • InputStreamReader:将一个字节的输入流转换为字符的输入流
    解码:字节、字节数组 —>字符数组、字符串

  • OutputStreamWriter:将一个字符的输出流转换为字节的输出流
    编码:字符数组、字符串 —> 字节、字节数组

  • 实例:
@Test
public void test2() throws Exception {
    //1.造文件、造流
    File file1 = new File("dbcp.txt");
    File file2 = new File("dbcp_gbk.txt");

    FileInputStream fis = new FileInputStream(file1);
    FileOutputStream fos = new FileOutputStream(file2);

    InputStreamReader isr = new InputStreamReader(fis,"utf-8");
    OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");

    //2.读写过程
    char[] cbuf = new char[20];
    int len;
    while((len = isr.read(cbuf)) != -1){
        osw.write(cbuf,0,len);
    }

    //3.关闭资源
    isr.close();
    osw.close();

}

常见的编码表

ASCII:美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表。用一个字节的8位表示。
GB2312:中国的中文编码表。最多两个字节编码所有字符
GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode:国际标准码,融合了目前人类使用的所字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。

2.5.5 其他流
  1. 标准的输入输出流

    System.in:标准的输入流,默认从键盘输入
    System.out:标准的输出流,默认从控制台输出

    修改默认的输入和输出行为:
    System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。

  2. 打印流

    PrintStream 和PrintWriter

    提供了一系列重载的print()和println()方法,用于多种数据类型的输出。

    System.out返回的是PrintStream的实例

  3. 数据流

    DataInputStream 和 DataOutputStream用于读取或写出基本数据类型的变量或字符串

    @Test
    public void test3() throws IOException {
        //1.
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
        //2.
        dos.writeUTF("刘建辰");
        dos.flush();//刷新操作,将内存中的数据写入文件
        dos.writeInt(23);
        dos.flush();
        dos.writeBoolean(true);
        dos.flush();
        //3.
        dos.close();
    }
    
    @Test
    public void test4() throws IOException {
        //1.
        DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
        //2.
        String name = dis.readUTF();
        int age = dis.readInt();
        boolean isMale = dis.readBoolean();
    
        System.out.println("name = " + name);
        System.out.println("age = " + age);
        System.out.println("isMale = " + isMale);
    
        //3.
        dis.close();
    }
    
2.5.6 对象流

ObjectOutputStream:内存中的对象—>存储中的文件、通过网络传输出去(序列化过程)
ObjectInputStream:存储中的文件、通过网络接收过来 —>内存中的对象(反序列化过程)

  • 对象的序列化机制

    ​ 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

  • 实现序列化的对象所属的类需要满足以下条件

    • 实现接口:Serializable
    • 当前类提供一个全局常量:serialVersionUID
    • 除了当前类需要实现Serializable接口之外,还必须保证其内部所属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)

    ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量。

  • 序列化代码实现

    @Test
    public void testObjectOutputStream(){
        ObjectOutputStream oos = null;
    
        try {
            //1.
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            //2.
            oos.writeObject(new String("我爱北京天安门"));
            oos.flush();//刷新操作
    
             oos.writeObject(new Person("王铭",23));
            oos.flush();
    
            oos.writeObject(new Person("张学良",23,1001,new Account(5000)));
            oos.flush();
    
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //3.
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    }
    
  • 反序列化代码实现

    @Test
    public void testObjectInputStream(){
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("object.dat"));
    
            Object obj = ois.readObject();
            String str = (String) obj;
    
            Person p = (Person) ois.readObject();
            Person p1 = (Person) ois.readObject();
    
            System.out.println(str);
            System.out.println(p);
            System.out.println(p1);
    
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    }
    
2.5.7 随机存取文件流

​ RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口。

​ RandomAccessFile既可以作为一个输入流,又可以作为一个输出流。

​ 如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建;如果写出到的文件存在,则会对原文件内容进行覆盖。(默认情况下,从头覆盖)

​ 可以通过seek(int pos)操作,实现RandomAccessFile“插入”数据的效果。

实例1:

@Test
public void test1() {

    RandomAccessFile raf1 = null;
    RandomAccessFile raf2 = null;
    try {
        //1.
        raf1 = new RandomAccessFile(new File("爱情与友情.jpg"),"r");
        raf2 = new RandomAccessFile(new File("爱情与友情1.jpg"),"rw");
        //2.
        byte[] buffer = new byte[1024];
        int len;
        while((len = raf1.read(buffer)) != -1){
            raf2.write(buffer,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //3.
        if(raf1 != null){
            try {
                raf1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(raf2 != null){
            try {
                raf2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

实例2:(使用RandomAccessFile实现数据的插入效果)

@Test
public void test3() throws IOException {

    RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");

    raf1.seek(3);//将指针调到角标为3的位置
    //保存指针3后面的所数据到StringBuilder中
    StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
    byte[] buffer = new byte[20];
    int len;
    while((len = raf1.read(buffer)) != -1){
        builder.append(new String(buffer,0,len)) ;
    }
    //调回指针,写入“xyz”
    raf1.seek(3);
    raf1.write("xyz".getBytes());

    //将StringBuilder中的数据写入到文件中
    raf1.write(builder.toString().getBytes());
    raf1.close();
}

执行前:

执行后:

2.5.8 Path、Paths、Files
  1. Path

    替换原有的File类。

  2. Paths

    常用方法:

  3. Files

    工具类,常用方法:

2.6 网络编程 2.6.1 InetAddress类的使用

​ InetAddress类:此类的一个对象就代表着一个具体的IP地址。

创建InetAddress对象:

​ getByName(String host) 、 getLocalHost()

常用方法:

​ getHostName() / getHostAddress()

端口号与IP地址的组合得出一个网络套接字:Socket

端口号为一个 16 位的整数 0~65535。

2.6.2 TCP网络编程
//示例1:客户端发送信息给服务端,服务端将数据显示在控制台上
//客户端
    @Test
    public void client()  {
        Socket socket = null;
        OutputStream os = null;
        try {
            //1.创建Socket对象,指明服务器端的ip和端口号
            InetAddress inet = InetAddress.getByName("192.168.14.100");
            socket = new Socket(inet,8899);
            //2.获取一个输出流,用于输出数据
            os = socket.getOutputStream();
            //3.写出数据的操作
            os.write("你好,我是客户端mm".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源的关闭
            if(os != null){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }



    }
    //服务端
    @Test
    public void server()  {

        ServerSocket ss = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            //1.创建服务器端的ServerSocket,指明自己的端口号
            ss = new ServerSocket(8899);
            //2.调用accept()表示接收来自于客户端的socket
            socket = ss.accept();
            //3.获取输入流
            is = socket.getInputStream();

            //不建议这样写,可能会乱码
//        byte[] buffer = new byte[1024];
//        int len;
//        while((len = is.read(buffer)) != -1){
//            String str = new String(buffer,0,len);
//            System.out.print(str);
//        }
            //4.读取输入流中的数据
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[5];
            int len;
            while((len = is.read(buffer)) != -1){
                baos.write(buffer,0,len);
            }

            System.out.println(baos.toString());

            System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(baos != null){
                //5.关闭资源
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(ss != null){
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

    }

//示例2:客户端发送文件给服务端,服务端将文件保存在本地。

@Test
public void client() throws IOException {
    //1.
    Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
    //2.
    OutputStream os = socket.getOutputStream();
    //3.
    FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
    //4.
    byte[] buffer = new byte[1024];
    int len;
    while((len = fis.read(buffer)) != -1){
        os.write(buffer,0,len);
    }
    //5.
    fis.close();
    os.close();
    socket.close();
}


@Test
public void server() throws IOException {
    //1.
    ServerSocket ss = new ServerSocket(9090);
    //2.
    Socket socket = ss.accept();
    //3.
    InputStream is = socket.getInputStream();
    //4.
    FileOutputStream fos = new FileOutputStream(new File("beauty1.jpg"));
    //5.
    byte[] buffer = new byte[1024];
    int len;
    while((len = is.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }
    //6.
    fos.close();
    is.close();
    socket.close();
    ss.close();

}

代码示例3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。


@Test
public void client() throws IOException {
    //1.
    Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
    //2.
    OutputStream os = socket.getOutputStream();
    //3.
    FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
    //4.
    byte[] buffer = new byte[1024];
    int len;
    while((len = fis.read(buffer)) != -1){
        os.write(buffer,0,len);
    }
    //关闭数据的输出
    socket.shutdownOutput();

    //5.接收来自于服务器端的数据,并显示到控制台上
    InputStream is = socket.getInputStream();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] bufferr = new byte[20];
    int len1;
    while((len1 = is.read(buffer)) != -1){
        baos.write(buffer,0,len1);
    }

    System.out.println(baos.toString());

    //6.
    fis.close();
    os.close();
    socket.close();
    baos.close();
}


@Test
public void server() throws IOException {
    //1.
    ServerSocket ss = new ServerSocket(9090);
    //2.
    Socket socket = ss.accept();
    //3.
    InputStream is = socket.getInputStream();
    //4.
    FileOutputStream fos = new FileOutputStream(new File("beauty2.jpg"));
    //5.
    byte[] buffer = new byte[1024];
    int len;
    while((len = is.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }

    System.out.println("图片传输完成");

    //6.服务器端给予客户端反馈
    OutputStream os = socket.getOutputStream();
    os.write("你好,美女,照片我已收到,非常漂亮!".getBytes());

    //7.
    fos.close();
    is.close();
    socket.close();
    ss.close();
    os.close();

}
2.6.3 UDP网络编程
//发送端
@Test
public void sender() throws IOException {

    DatagramSocket socket = new DatagramSocket();



    String str = "我是UDP方式发送的导弹";
    byte[] data = str.getBytes();
    InetAddress inet = InetAddress.getLocalHost();
    DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);

    socket.send(packet);

    socket.close();

}
//接收端
@Test
public void receiver() throws IOException {

    DatagramSocket socket = new DatagramSocket(9090);

    byte[] buffer = new byte[100];
    DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);

    socket.receive(packet);

    System.out.println(new String(packet.getData(),0,packet.getLength()));

    socket.close();
}
2.6.4 URL编程
  1. URL(Uniform Resource Locator)的理解:
    统一资源定位符,对应着互联网的某一资源地址

  2. URL的五个结构

    http://localhost:8080/examples/beauty.jpg?username=Tom

    协议 //主机名 : 端口号 / 资源地址? 参数列表

  3. 实例化

    URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom");
    
  4. 常用方法

  5. 读取、下载对应的url资源:

    public static void main(String[] args) {
    
        HttpURLConnection urlConnection = null;
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            URL url = new URL("http://localhost:8080/examples/beauty.jpg");
    
            urlConnection = (HttpURLConnection) url.openConnection();
    
            urlConnection.connect();
    
            is = urlConnection.getInputStream();
            fos = new FileOutputStream("day10\beauty3.jpg");
    
            byte[] buffer = new byte[1024];
            int len;
            while((len = is.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
    
            System.out.println("下载完成");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(urlConnection != null){
                urlConnection.disconnect();
            }
        }
    }
    
2.7 Java反射

​ 反射被视为动态语言的关键。

  1. 获取Class实例的几种方式:

    //方式一:调用运行时类的属性:.class
            Class clazz1 = Person.class;
            System.out.println(clazz1);
            //方式二:通过运行时类的对象,调用getClass()
            Person p1 = new Person();
            Class clazz2 = p1.getClass();
            System.out.println(clazz2);
    
            //方式三:调用Class的静态方法:forName(String classPath)
            Class clazz3 = Class.forName("com.atguigu.java.Person");
    //        clazz3 = Class.forName("java.lang.String");
            System.out.println(clazz3);
    
            System.out.println(clazz1 == clazz2);
            System.out.println(clazz1 == clazz3);
    
            //方式四:使用类的加载器:ClassLoader  (了解)
            ClassLoader classLoader = ReflectionTest.class.getClassLoader();
            Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
            System.out.println(clazz4);
    
            System.out.println(clazz1 == clazz4);
    
  2. 使用Classloader加载src目录下的配置文件:

    @Test
    public void test2() throws Exception {
    
        Properties pros =  new Properties();
        //此时的文件默认在当前的module下。
        //读取配置文件的方式一:
        //        FileInputStream fis = new FileInputStream("jdbc.properties");
        //        FileInputStream fis = new FileInputStream("src\jdbc1.properties");
        //        pros.load(fis);
    
        //读取配置文件的方式二:使用ClassLoader
        //配置文件默认识别为:当前module的src下
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
        pros.load(is);
    
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user = " + user + ",password = " + password);
    }
    
2.7.1 创建运行时类的对象

要想newInstance()正常的创建运行时类的对象,要求:

  1. 运行时类必须提供空参的构造器。
  2. 具有空参的构造器的访问权限。通常,设置为public。
Class clazz = Person.class;

Person obj = clazz.newInstance();
System.out.println(obj);
2.7.2 获取运行时类的完整结构
@Test
public void test1(){

    Class clazz = Person.class;

    //获取属性结构
    //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
    Field[] fields = clazz.getFields();
    for(Field f : fields){
        System.out.println(f);
    }
    System.out.println();

    //getDeclaredFields():获取当前运行时类中声明的所属性。(不包含父类中声明的属性
    Field[] declaredFields = clazz.getDeclaredFields();
    for(Field f : declaredFields){
        System.out.println(f);
    }
}

@Test
public void test1(){

    Class clazz = Person.class;

    //getMethods():获取当前运行时类及其所父类中声明为public权限的方法
    Method[] methods = clazz.getMethods();
    for(Method m : methods){
        System.out.println(m);
    }
    System.out.println();
    //getDeclaredMethods():获取当前运行时类中声明的所方法。(不包含父类中声明的方法
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for(Method m : declaredMethods){
        System.out.println(m);
    }
}


    @Test
    public void test1(){

        Class clazz = Person.class;
        //getConstructors():获取当前运行时类中声明为public的构造器
        Constructor[] constructors = clazz.getConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }

        System.out.println();
        //getDeclaredConstructors():获取当前运行时类中声明的所的构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(Constructor c : declaredConstructors){
            System.out.println(c);
        }

    }

    
    @Test
    public void test2(){
        Class clazz = Person.class;

        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);
    }

    
    @Test
    public void test3(){
        Class clazz = Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
    }

    
    @Test
    public void test4(){
        Class clazz = Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        //获取泛型类型
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
//        System.out.println(actualTypeArguments[0].getTypeName());
        System.out.println(((Class)actualTypeArguments[0]).getName());
    }

    
    @Test
    public void test5(){
        Class clazz = Person.class;

        Class[] interfaces = clazz.getInterfaces();
        for(Class c : interfaces){
            System.out.println(c);
        }

        System.out.println();
        //获取运行时类的父类实现的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for(Class c : interfaces1){
            System.out.println(c);
        }

    }
    
    @Test
    public void test6(){
        Class clazz = Person.class;

        Package pack = clazz.getPackage();
        System.out.println(pack);
    }

    
    @Test
    public void test7(){
        Class clazz = Person.class;

        Annotation[] annotations = clazz.getAnnotations();
        for(Annotation annos : annotations){
            System.out.println(annos);
        }
    }

2.7.3 调用运行时类的指定结构
调用指定的属性:
@Test
public void testField1() throws Exception {
    Class clazz = Person.class;

    //创建运行时类的对象
    Person p = (Person) clazz.newInstance();

    //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
    Field name = clazz.getDeclaredField("name");

    //2.保证当前属性是可访问的
    name.setAccessible(true);
    //3.获取、设置指定对象的此属性值
    name.set(p,"Tom");

    System.out.println(name.get(p));
}
调用指定的方法:
 @Test
    public void testMethod() throws Exception {

        Class clazz = Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();

        
        Method show = clazz.getDeclaredMethod("show", String.class);
        //2.保证当前方法是可访问的
        show.setAccessible(true);

        
        Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
        System.out.println(returnValue);

        System.out.println("*************如何调用静态方法*****************");

        // private static void showDesc()

        Method showDesc = clazz.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
        //如果调用的运行时类中的方法没返回值,则此invoke()返回null
//        Object returnVal = showDesc.invoke(null);
        Object returnVal = showDesc.invoke(Person.class);
        System.out.println(returnVal);//null

    }

调用指定的构造器:
@Test
public void testConstructor() throws Exception {
    Class clazz = Person.class;

    //private Person(String name)
    

    Constructor constructor = clazz.getDeclaredConstructor(String.class);

    //2.保证此构造器是可访问的
    constructor.setAccessible(true);

    //3.调用此构造器创建运行时类的对象
    Person per = (Person) constructor.newInstance("Tom");
    System.out.println(per);

}

2.7.4 动态代理
  1. 如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象?
    通过Proxy.newProxyInstance()实现
  2. 当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a?
    通过InvocationHandler接口的实现类及其方法invoke()
  3. 实例:
  • MyActiveProxyFactory.java

    package cn.qujialin.activeproxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    //代理工厂
    public class MyActiveProxyFactory {
    
        public static Object getProxyInstance(Operator op){
            Object proxy = null;
            ClassLoader cl = op.getClass().getClassLoader();
            Class[] interfaces = op.getClass().getInterfaces();
            InvocationHandler invocationHandler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    String methodName = method.getName();
                    System.out.println("methodName:::" + methodName);
                    Object result = method.invoke(op, args);
                    System.out.println("methodName:::" + methodName);
                    return result;
                }
            };
            return Proxy.newProxyInstance(cl,interfaces,invocationHandler);
        }
    
    }
    
  • Operator.java

    package cn.qujialin.activeproxy;
    //接口
    public interface Operator {
        public int add(int i, int j);
        public int sub(int i, int j);
        public int mul(int i, int j);
        public int div(int i, int j);
    }
    
  • MyOperator.java

    package cn.qujialin.activeproxy;
    
    import cn.qujialin.activeproxy.Operator;
    
    //被代理类
    public class MyOperator implements Operator{
        @Override
        public int add(int i, int j){
            System.out.println("add......");
            return i + j;
        }
        @Override
        public int sub(int i, int j){
            System.out.println("sub......");
            return i - j;
        }
        @Override
        public int mul(int i, int j){
            System.out.println("mul......");
            return i * j;
        }
        @Override
        public int div(int i, int j){
            System.out.println("div......");
            return i / j;
        }
    
    }
    
    
  • TestMain.java

    package cn.qujialin.activeproxy;
    //测试
    public class TestMain {
        public static void main(String[] args) {
            Operator p = (Operator)MyActiveProxyFactory.getProxyInstance(new MyOperator());
            p.add(3,5);
        }
    }
    
    

你学会了吗?收藏点赞加关注,技术学习不迷路~

微信搜索“五维星空”关注我吧~

by 五维星空-分享前后端技术

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号