第一章:多线程
1、基本概念:程序、进程、线程
基本概念:
- 程序:为了完成特定任务,用某种语言编写的一组指令的集合。
- 进程:程序的一次执行过程,或是正在运行的一个程序。
- 线程:一个程序内部的一条执行路径。
- 单核CPU:假的多线程,CPU处理的速度太快,用户无感知。
- 多核CPU:多核才能发挥多线程的效率(Java应用最少有三个线程:main( )、 gc( )、异常处理线程)
- 并行:多个CPU同时执行多个任务。
- 并发:一个CPU同时执行多个任务。
多线程的优点:
- 提高程序的响应
- 提高CPU的利用率
- 改善程序结构
2、线程的创建和使用
创建多线程的方式:
- 方式一:继承于Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类的run( )方法:将此线程执行的操作声明在run( )中
- 创建Thread类的子类的对象
- 通过此对象调用start( )方法 :
- 启动当前线程
- 调用当前线程的run( )
//继承Thread类实现多线程:
public class ThreadDemo {
public static void main(String[] args) {
Thread1 thread = new Thread1();
thread.start();
}
}
class Thread1 extends Thread {
@Override
public void run() {
System.out.println("继承Thread类,实现多线程...");
}
}
- 方式二:实现Runnable接口
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run( )
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start( )—> 1.启动线程 2.调用当前线程的run( )
//实现Runnable接口实现多线程
public class ThreadDemo2 {
public static void main(String[] args) {
Thread2 thread2 = new Thread2();
Thread t1 = new Thread(thread2);
t1.start();
}
}
class Thread2 implements Runnable {
@Override
public void run() {
System.out.println("实现Runnable接口,实现多线程...");
}
}
**创建多线程的两种方式的比较:**开发中优先选择实现Runnable接口的方式
- 实现的方式没有类的单继承性的局限性;
- 实现的方式更适合来处理多个线程有共享数据的情况;
- 联系:public class Thread implements Runnable;
- 相同点:两种方式都需要重写run方法,将线程要执行的逻辑声明在run( )中。
注意点:
- 我们不能直接通过调用run()的方式启动线程
- 不能够重复调用start()方法去启动线程,需要重新创建一个对象来执行start( )
Thread常用的方法:
- start( ):启动当前线程,调用当前线程的run( )方法
- run( ):通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法
- currentThread( ):静态方法,返回执行当前代码的线程
- getName( ):获取当前线程的名字
- setName( ):设置当前线程的名字
- yield( ):释放当前CPU的执行权
- join( ):在线程a中调用线程b的join( ),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a 才能结束阻塞状态
- stop( ):已过时,强制停止线程
- sleep( ):让当前线程“睡眠”
- isAlive( ):判断线程释放存活
线程的优先级:
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
获取和设置当前线程的优先级方法:
- getPriority( ):获取优先级
- setPriority(int priority):设置优先级
- 说明:
- 线程创建时继承父类线程的优先级。
- 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用。
线程的分类:
- Java中的线程分为:一种是守护线程,一种是用户线程。
- Java垃圾回收就是一个守护线程。
- 守护线程是用来服务用户线程的,在start( )方法前使用thread.setDaemon(true)可以使用户线程变成守护线程。
3、线程的生命周期
线程的状态:
- 新建: 当 一个 Thread 类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
- 就绪: 处于新建状态的线程被start( ) 后,将进入线程队列等待 CPU 时间片,此时它已具备了运行的条件,只是没分配到 CPU 资源。
- 运行: 当就绪的线程被调度并获得 CPU 资源时便进入运行状态, run( ) 方法定义了线程的操作和功能。
- 阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态。
- 死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束。
4、线程的同步
解决线程安全的方法:同步机制
- 方式一:同步代码块
- 操作共享数据的代码,即为需要被同步的代码。
- 共享数据:多个线程共同操作的变量。
- 同步监视器:俗称:锁。任何一个类的对象,都可以充当锁。(要求:多个线程必须要共用同一把锁)
- 补充1:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
- 补充2:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
synchronized(同步监视器) {
//需要被同步的代码
}
class Ticket implements Runnable {
private int ticket = 100;
@Override
public void run() {
while(true) {
//同步代码块
synchronized(this) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "出售车票,ticket号为:" + ticket--);
}else {
break;
}
}
}
}
}
- 方式二:同步方法
- 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的。
- 总结:
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是:this
- 静态的同步方法,同步监视器是:当前类本身
class Ticket implements Runnable {
private int ticket = 100;
boolean flag = true;
@Override
public void run() {
while(flag) {
showTicket();
}
}
public synchronized void showTicket() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "出售车票,ticket号为:" + ticket--);
}else {
flag = false;
}
}
}
- 方式三:Lock锁
- 实例化ReentrantLock
- 调用锁定方法:lock( )
- 调用解锁方法:unlock( )
- 实例化ReentrantLock
//使用锁的方式实现线程同步
class Ticket3 implements Runnable {
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
private int ticket = 100;
@Override
public void run() {
lock.lock(); //手动加锁
while(true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "出售车票,ticket号为:" + ticket);
ticket--;
}else {
break;
}
}
lock.unlock(); //手动解锁
}
synchronized与Lock的异同:
- 相同点:两者都可以解决线程安全问题
- 不同点:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
- Lock需要手动的启动同步(lock( )),同时结束同步后也需要手动的实现(unlock( ))
释放锁的操作:
- 当前线程的同步方法,同步代码块执行完成。
- 当前线程在同步方法,同步代码块中遇到了break,return终止了该方法,该代码块的继续执行。
- 当前线程在同步方法,同步代码块中遇到了未处理的Exception或Error,导致异常结束
- 当前线程在同步方法,同步代码块中执行了该线程对象的wait()方法,导致线程暂停并释放锁。
// 使用同步机制将单例模式中的懒汉式改成为线程安全的:
class Bank {
private Bank() {
}
private static Bank instance = null;
public static Bank getInstance() {
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
线程死锁:
- 不同的线程分别占用了对象需要的同步资源不放弃,都在等待对方发起自己需要的同步资源,就形成了线程的死锁。
5、线程的通信
线程通信涉及到的三个方法:
-
wait( ):一旦执行此方法,当前线程就进入到阻塞状态,并释放同步监视器
-
notify( ):一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级别高的。
-
notifyAll( ):一旦执行此方法,就会唤醒所有被wait的线程。
方法使用说明:
- wait( )、notify( )、notifyAll( )这三个方法必须使用在同步代码块或同步方法中。
- wait( )、notify( )、notifyAll( )这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常。
- wait( )、notify( )、notifyAll( )这三个方法是定义在java.lang.Object类中的。
拓展sleep( )和wait( )的异同:
- 相同点:一旦执行方法,都可以使得当前进程进入阻塞状态。
- 不同点:
- 两个方法声明的位置不同:Thread类中声明sleep( ),Object类中声明wait( )。
- 调用的要求不同:
- sleep( )可以在任何需要的场景下调用;
- wait( )必须使用在同步代码块或同步方法。
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep( )不会释放锁,wait( )会释放锁。
6、JDK5.0新增线程创建的方式
创建多线程的方法:JDK5.0后
- 方式三:实现Callable接口
- 创建一个实现Callable的实现类。
- 实现call方法,将此线程需要执行的操作声明在call( )中。
- 创建Callable接口实现类的对象。
- 将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象。
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start( )。
- 获取Callable中call方法的返回值
//1.创建一个实现Callable的实现类NumThread
class NumThread implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
//此线程需要执行的操作
int sum = 0;
for (int i = 0; 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();
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
try {
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
}catch (Exception e) {
e.printStackTrace();
}
}
}
-
拓展:Callable接口实现多线程比Runnable接口强大的理由:
- call( )方法可以有返回值
- call( )方法可以抛出异常
- Callable可以支持泛型
-
方式四:使用线程池
- 使用线程池的好处:
- 提高响应时间(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
- 使用线程池的好处:
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread1());
service.execute(new NumberThread2());
//3.关闭连接池
service.shutdown();
}
}
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);
}
}
}
}
class NumberThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
}
第二章:Java常用类
1、字符串相关的类
String的特性
- String:字符串,使用一对“ ”引起来表示
- String声明为final,不可被继承
- String实现了Serializable接口:
- 表示字符串是支持序列化的;
- 实现了Comparable接口:表示String可以比较大小。
- String内部定义了final char[ ] value 用于存储字符串数据
- String:代表不可变的字符序列。简称:不可变性
- 体现:当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
- 当使用String中的replace( )方法修改指定位置的字符或字符串时,也需要重新指定内存区域值,不能使用原有的value进行赋值
- 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串声明在字符串常量池中
- 字符串常量池中是不会存储相同内容的字符串的
String对象的创建:
方式1: String str = “hello”;
方式2: String str = new String( );
方式1和方式2的区别:方式1 是在方法区,方式2是new了一个对象。
String的拼接结论:
- 常量与常量的拼接结果在常量池,且常量池中不会存在相同内容的常量。
- 只要其中有一个是变量,结果就在堆中
- 如果拼接的结果调用intern()方法,返回值就在常量池中
String常用方法:
- 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(Stringprefix):测试此字符串是否以指定的前缀开始
- 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):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。注:indexOf和lastIndexOf方法如果未找到都是返回-1。
- 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[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
- String[] split(String regex,int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
String和包装类之间的转换:
-
字符串 转换成 基本数据类型、包装类
- Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。
- 类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。
-
基本数据类型、包装类 转换成 字符串
- 调用String类的 public String valueOf(int n) 可将int型转换为字符串。
- 相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b)可由参数的相应类型到字符串的转换。
String与字符数组之间的转换:
字符数组 转换成 字符串
- String 类的构造器:String(char[ ]) 和String(char[ ],int offset,int length) 分别用字符数组中的全部字符和部分字符创建字符串对象
字符串 转换成 字符数组
- public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。
- public void getChars(int srcBegin, int srcEnd, char[]dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的
StringBuilder和StringBuffer比较
-
String、StringBuffer、StringBuilder的异同:
异:
- String:不可变的字符序列
- StringBuffer:可变的字符序列;线程安全,效率低
- StringBuilder:可变的字符序列;JDK5.0新增,线程不安全,效率高
同:
- 底层都是使用char[ ]存储
StringBuffer、StringBuilder的常用方法:
- StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
- StringBuffer delete(intstart,intend):删除指定位置的内容
- StringBuffer replace(intstart,intend,String str):把[start,end)位置替换为str
- StringBuffer insert(int offset,xxx):在指定位置插入xxx
- StringBuffer reverse():把当前字符序列逆转
String、StringBuffer、StringBuilder之间的转换:
-
String =================> StringBuffer\StringBuilder:调用StringBuffer\StringBuilder的构造器
-
StringBuffer\StringBuilder =================> String:调用String的构造器
2、JDK8之前的日期时间API
获取系统当前时间:
- System类中的currentTimeMillis()
- long time = System.currentTimeMillis(); //返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
Date类:
-
java.util.Date类
-
java.sql.Date类
-
构造器:
- new Date( ):创建一个对应当前时间的Date对象
- new Date(long date):创建执行毫秒数的Date对象
-
方法的使用:
- toString( ):显示当前的年-月-日-时-分-秒
- getTime( ):获取当前Date对象对应的毫秒数
-
java.sql.Date对应着数据库中的日期类型的变量:
-
如何实例化:java.sql.Date date = new java.sql.Date( 123456789L);
-
将java.util.Date对象 ==========> java.sql.Date对象:
Date date = new Date( );
java.sql.Date date2 = new java.sql.Date(date.getTime( ));
-
SimpleDateFormat类:
-
SimpleDateFormat的使用
-
两个操作:
-
格式化:日期 —>字符串
-
SimpleDateFormat() :默认的模式和语言环境创建对象
-
public SimpleDateFormat(String pattern):该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用:
-
public String format(Date date):方法格式化时间对象date
-
-
解析:格式化的逆过程,字符串 —> 日期
-
public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期。
-
SimpleDateFormat的实例化:new + 构造器
-
-
-
Calendar类:日历类,抽象类
1、实例化
方式一:创建其子类GregorianCalendar的对象
方式二:调用其静态方法getInstance()
2、常用方法:
public void set(int field,int value)
public void add(int field,int amount)
public final Date getTime()
public final void setTime(Date date)
3、JDK8中新的日期时间API
日期时间API的迭代:
- 第一代:jdk 1.0 Date类
- 第二代:jdk 1.1 Calendar类,一定程度上替换Date类
- 第三代:jdk 1.8 提出了新的一套API
前两代存在的问题举例:
- 可变性:像日期和时间这样的类应该是不可变的。
- 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
- 格式化:格式化只对Date用,Calendar则不行。此外,它们也不是线程安全的;不能处理闰秒等。
Java 8 中新的日期时间API涉及到的包
- java.time
- java.time.chrono
- java.time.format
- java.time.temporal
- java.time.zone
本地日期、本地时间、本地日期时间的使用:LocalDate / LocalTime / LocalDateTime
- 说明:
- 分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
- LocalDateTime相较于LocalDate、LocalTime,使用频率要高
- 类似于Calendar
- 常用方法:
- now( ):静态方法,根据当前时间创建对象/指定时区的对象
- of( ):静态方法,根据指定日期/时间创建对象
- getDayOfMonth()/getDayOfYear():获得月份天数(1-31) ,获得年份天数(1-366)
- getDayOfWeek():获得星期几
- getMonth():获得月份
- getMonthValue()/getYear():获得月份(1-12)/获得年份
- getHour()/getMinute()/getSecond():获得当前对象对应的小时、 分钟 、 秒
- withDayOfMonth()/withDayOfYear()/withMonth()/withYear():将月份天数、年份天数 、月份 、年份修改为指定的值并返回新的对象
- plusDays(), plusWeeks(),plusMonths(), plusYears(),plusHours():向当前对象 添加几天 、 几周 、 几个月 、 几年 、 几小时
- minusMonths()/minusDays()/minusYears()/minusHours():从当前对象 减去 几 月 、 几周 、 几天 、 几年 、 几小时
时间点:Instant
- 说明:
- 时间线上的一个瞬时点。 概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC开始的秒数。)
- 类似于 java.util.Date类
- 常用方法:
- now( ):静态方法,返回默认UTC时区的Instant类的对象
- ofEpochMilli(long epochMilli):静态方法,返回在 1970-01-01 00:00:00 基础上加上指定毫秒数之后的 Instant 类的对象
- atOffset(ZoneOffset offset):结合即时的偏移来创建一个OffsetDateTime
- toEpochMilli():返回1970-01-01 00:00:00 到当前时间的毫秒数 即为时间戳
日期时间格式化类:DateTimeFormatter
- 说明:
- 格式化或解析日期、时间
- 类似于SimpleDateFormat
- 常用方法:
- 实例化方式:
- 预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
- 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
- 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
- 常用方法:
- ofPattern(String pattern): 静态方法,返回一个指定字符串格式的
- DateTimeFormatter format(TemporalAccessort): 格式化一个日期、 时间返回字符串
- parse(CharSequence text) :将指定格式的字符序列解析为一个日期、 时间
- 特别的:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
- 重点:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
- 实例化方式:
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09
//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);
4、Java比较器
Java比较器的使用背景:
Java中的对象,正常情况下,只能进行比较:== 或 != ;不能使用 > 或 < 的
但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小
如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
自然排序:使用Comparable接口
说明
1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象 大小的方式。
2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
3.重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
4.对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj) 方法。在compareTo(obj)方法中指明如何排序
自定义类代码举例:
//指明商品比较大小的方式:照价格从低到高排序,再照产品名称从高到低排序
@Override
public int compareTo(Object o) {
// System.out.println("**************");
if(o instanceof Goods){
Goods goods = (Goods)o;
//方式一:
if(this.price > goods.price){
return 1;
}else if(this.price < goods.price){
return -1;
}else{
// return 0;
return -this.name.compareTo(goods.name);
}
//方式二:
// return Double.compare(this.price,goods.price);
}
// return 0;
throw new RuntimeException("传入的数据类型不一致!");
}
// getter、setter、toString()、构造器:省略
}
定制排序:使用Comparator接口
说明
1.背景:
当元素的类型没实现java.lang.Comparable接口而又不方便修改代码,或者实现了 java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator 的对象来排序
2.重写compare(Object o1,Object on)方法,比较o1和o2的大小:
如果方法返回正整数,则表示o1大于o2;
如果返回0,表示相等;
返回负整数,表示o1小于o2。
代码举例:
Comparator com = new Comparator() {
//指明商品比较大小的方式:照产品名称从低到高排序,再照价格从高到低排序
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Goods && o2 instanceof Goods){
Goods g1 = (Goods)o1;
Goods g2 = (Goods)o2;
if(g1.getName().equals(g2.getName())){
return -Double.compare(g1.getPrice(),g2.getPrice());
}else{
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException("输入的数据类型不一致");
}
}
使用:
Arrays.sort(goods,com);
Collections.sort(coll,com);
new TreeSet(com);
两种排序方式对比
- Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
- Comparator接口属于临时性的比较。
5、System类
1、System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
2、由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量 和成员方法都是static的,所以也可以很方便的进行调用。
3、方法:
native long currentTimeMillis()
void exit(int status)
void gc()
String getProperty(String key)
6、Math类
java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。
7、BigInteger与BigDecimal
说明:
① java.math包的BigInteger可以表示不可变的任意精度的整数。
② 要求数字精度比较高,用到java.math.BigDecimal类
第三章:枚举类与注解
1、枚举类的使用
枚举类:
1、枚举类的理解:类的对象只有有限个,确定的。
2、当需要定义一组常量时,强烈建议使用枚举类
3、如果枚举类只有一个对象,则可以作为单例模式的实现方式
如何定义枚举类
方式一:JDK5.0之前,自定义枚举类
1、私有化类的构造器,保证不能在类的外部创建其对象。
2、在类的内部创建枚举类的实例。声明为:public static final
3、对象如果有实例变量,应该声明为:public final 并在构造器中初始化
/**
* 自定义枚举类
*/
public class Season {
private final String SEASONNAME;
private final String SEASONDESC;
private Season(String seasonName,String seasonDesc) {
this.SEASONDESC = seasonDesc;
this.SEASONNAME = seasonName;
}
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","白雪皑皑");
}
方式二:JDK5.0之后,使用关键字enum定义枚举类
说明:定义的枚举类默认继承于java.lang.Enum类
1、枚举类的构造器只能使用 private 权限修饰符
2、枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的实例系统会自动添加 public static final 修饰
3、必须在枚举类的第一行声明枚举类对象
/**
* 使用enum定义枚举类
*/
public enum SeasonEnum {
SPRING,
SUMMER,
AUTUMN,
WINTER;
}
Enum类的主要方法
1、values() 方法 :返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
2、valueOf (String str ):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名 字”。如不是,会有运行时异常:IllegalArgumentException。
3、toString():返回当前枚举类对象常量的名称。
实现接口的枚举类
1、和普通 Java 类一样,枚举类可以实现一个或多个接口。
2、若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
3、若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法。
2、注解的使用
注解(Annotation)概述
1、Annotation其实就是代码里的特殊标记, 这些标记可以在编译,类加载,运行时被读取 , 并执行相应的处理
2、Annotation可以像修饰符一样被使用,可用于修饰包类、构造器、方法、成员变量、参数、局部变量的声 明,这些信息被保存在 Annotation的 “name=value” 对中
3、注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式
常见的Annotation示例
1、使用Annotation 时要在其前面增加 @ 符号 , 并把该Annotation当成一个修饰符使用;用于修饰它支持的程 序元素
示例一:生成文档相关的注解
@author 标明开发该类模块的作者 多个作者之间使用 分割
@version 标明该类模块的版本
@see 参考转向 也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明 如果没有参数就不能写
@return 对方法返回值的说明 如果方法的返回值类型是 void 就不能写
@exception 对方法可能抛出的异常进行说明 如果方法没有用 throws 显式抛出的异常就不能写
示例二: 在编译时进行格式检查 (JDK 内置的三个基本注解)
@Override: 限定重写父类方法,该注解只能用于方法
@Deprecated : 用于表示所修饰的元素类,方法等已过时。通常是因为所修饰的结构危险或存在更好的选 择
@SuppressWarnings : 抑制编译器警告
示例三: 跟踪代码依赖性,实现替代配置文件功能
自定义Annotation
1、Annotation类型使用 @interface关键字
2、内部定义成员,通常使用value表示
3、可以指定成员的默认值,使用default
4、如果自定义注解没有成员,表明是一个标识作用
说明:
1、如果注解有成员,在使用注解时,需要指明成员的值
2、自定义注解必须配上注解的信息处理流程(使用反射)才有意义
3、自定义注解通常都会指明两个元注解:Retention、Target
@MyAnnotation(value = "测试")
public class MyAnnotationTest {
public static void main(String[] args) {
Class clazz = MyAnnotationTest.class;
Annotation a = clazz.getAnnotation(MyAnnotation.class);
MyAnnotation m = (MyAnnotation) a;
String info = m.value();
System.out.println(info);
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {
String value() default "test";
}
JDK中的元注解
1、元注解:对现有的注解进行解释说明的注解
2、JDK5.0提供了4个标准的meta annotation类型分别是:
@Retention:指明所修饰的注解的生命周期
A、Rentention包含一个RetentionPolicy类型的成员变量
B、RetentionPolicy.SOURCE:在源文件有效
C、RetentionPolicy.CLASS:在class文件中有效
D、RetentionPolicy.RUNTIME:运行时有效
@Target:用于修饰Annotation定义,用于指定被修饰的Annotation能用于修饰哪些程序元素;它也包含 一个名为value的成员变量。
@Documented:用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档
@Inherited:被它修饰的Annotation将具有继承性,如果某个类使用了被 @Inherited修饰的 Annotation,则其子类将自动具有该注解。
利用反射获取注解信息(在反射部分涉及)
当一个Annotation类型被定义为运行时Annotation后,该注解才是运行时可见,当class文件被载入时保存在class文件中的Annotation才会被虚拟机读取
前提:声明的注解中Retention中声明的生命周期:RUNTIME
JDK8中注解的新特性
1、可重复的注解
2、可用于类型的注解
第四章:Java集合
1、Java集合概述
集合框架概述
1、集合、数组都是对多个数据进行存储操作的结构,简称Java容器
说明:这里的存储指的是内存
2、数组的特点:
A、数组初始化以后,长度就确定了
B、数组声明的类型,就决定了进行元素初始化时的类型
3、数组在存储数据方面的弊端:
A、数组初始化以后,长度就不可变了,不便于扩展
B、数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高;同时无法直接获取存储 元素的个数
C、数组存储的数据是有序的、可以重复的
集合框架分类
Collection接口:单列集合,用来存储一个个的对象
List接口:存储有序的,可重复的数据。
实现类:ArrayList、LinkedList、Vector
Set接口:存储无序的,不可重复的数据。
实现类:HashSet、LinkedHashSet、TreeSet
Map接口:双列集合,存储一对对的数据(key-value)
实现类:HashMap、LinkedHashMap、TreeMap、HashTable、Properties
2、Collection接口方法
常用方法
1、添加:add(Object obj)/addAll(Collection coll)
2、获取有效元素的个数:int size( )
3、清空集合:void clear( )
4、是否是空集合:boolean isEmpty
5、是否包含某个元素:
boolean contains(Object obj) 是通过元素的equals方法来判断是否是同一个对象
boolean containsAll(Collection c) 也是调用元素的equals方法来比较的; 拿两个集合的元素挨个比较
6、删除:
boolean remove(Object obj)通过元素的equals方法判断是否是要删除的那个元素;只会删除找到的第一 个元素
boolean removeAll(Collection coll)取当前集合的差集
7、取两个集合的交集:boolean retainAll(Collection c) 把交集的结果存在当前集合中,不影响 c
8、集合是否相等:boolean equals(Object obj)
9、转成对象数组:Object[] toArray()
10、获取集合对象的哈希值:hashCode()
11、遍历:iterator( ) :返回迭代器对象,用于集合遍历
3、Iterator迭代器接口
Iterator接口介绍
1、Iterator 对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
2、其内部方法:next( ),hasNext( ),remove( )
3、集合对象每次调用iterator()方法都将得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
4、Iterator仅用于遍历集合
@Test
public void test() {
Collection coll = new ArrayList();
coll.add("123");
coll.add(123);
coll.add("helloword");
//迭代输出
Iterator it = coll.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
5、使用foreach遍历
@Test
public void test() {
Collection coll = new ArrayList();
coll.add("123");
coll.add(123);
coll.add("helloword");
for (Object c : coll) {
System.out.println(c);
}
}
6、Iterator接口的remove()方法
@Test
public void test() {
Collection coll = new ArrayList();
coll.add("123");
coll.add(123);
coll.add("helloword");
Iterator it = coll.iterator();
while(it.hasNext()) {
if (it.next().equals("helloword"));
it.remove();
}
}
4、Collection子接口之一:List接口
List接口的框架
1、List接口:存储有序的,可重复的数据。
2、实现类:
ArrayList: List接口的主要实现类;线程不安全的,效率高;底层使用的是Object[ ] elementData
LinkedList: 对应频繁的插入,删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
Vector: List接口的古老实现类;线程安全的,效率低;底层使用Object[ ] elementData
ArrayList源码分析
1、JDK 7版本:
1. ArrayList list = new ArrayList() ;//底层创建了长度是10的数组Object[ ] elementData
2. 当添加导致底层elementData数组容量不够时,则自动扩容
3. 默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中
2、JDK 8版本:
1. ArrayList list = new ArrayList() ;//底层Object[] elementData初始化为{ };并没有创建长度为10的数组
2. 当第一次调用add( )时,底层才创建了长度10的数组,并将数据添加到elementData
3. 后续的操作跟JDK7无异
3、小结:
JDK 7中的ArrayList的对象的创建类似于单例的饿汉式,8中ArrayList的对象的创建类似于单例的懒汉式
LinkedList源码分析
1、LinkedList list = new LinkedList( ); //内部声明了Node类型的first和last属性,默认值为null
2、list.add(“123”); //将123封装到Node中,创建了Node对象
3、其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector源码分析
1、JDK 7和 JDK 8中通过Vector( )构造器创建对象时,底层都是创建了长度为10的数组;在扩容方面,默认 扩容为原来数据的2倍。
List接口中的常用方法
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位置的子集合
总结:常用方法
增:add(Object obj)
删:remove(int index) / remove(object obj)
改:set(int index,Object element)
查:get(int index)
插:add(int index,Object element)
长度:size( )
遍历:a. Iterator迭代器 b. foreach循环 c. 普通的循环
5、Collection子接口之一:Set接口
Set接口的框架
1、Set接口:存储无序的,不可重复的数据。
2、实现类:
HashSet: Set接口的主要实现类;线程不安全的,可以存储null值
LinkedHashSet: 作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历
作为频繁的遍历操作,LinkedHashSet效率高于HashSet
TreeSet: 只能放同一个类的对象,可以按照添加的对象的指定数据进行排序
3、理解Set的无序性和不可重复性
a. 无序性:不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈 希值决定的。
b. 不可重复性:保证添加的元素按照equals( )判断时,不能返回true。即:相同的元素只能添加一个
说明:
Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法
要求1:向Set中添加的数据,其所在的类一定要重写hashCode( )和equals( )
要求2:重写的hashCode( )和equals( )要保持一致性->相等的对象必须具有相等的散列值
HashSet添加元素的过程:
以HashSet为例:
1、向HashSet中添加元素a,首先调用元素a所在类的hashCode( )方法,计算元素a的哈希值,此哈希值接 着通过某种算法计算出在HashSet底层数组中的存放位置(索引的位置),判断数组此位置上是否有元素:
如果此位置上没有其他的元素,则元素a添加成功 ---->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功 ----->情况2
如果hash值相同,则需要进一步的调用元素a所在类的equals( )方法:
equals( )返回true,则元素a添加失败
equals( )返回false,则元素a添加成功 ----->情况3
2、对于添加成功的情况2和情况3而言,元素a与已经存在指定索引位置上数据以链表的方式存储
3、HashSet底层:数组+链表
LinkedHashSet的使用
1、LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一 个数据和后一个数据。
TreeSet的使用
1、向TreeSet中添加的数据,要求是相同类的对象
2、两种排序方式:自然排序(实现Comparable接口) 和 定制排序(实现Comparator)
3、自然排序中,比较两个对象是否相同的标准为:compareTo( )返回0。不再是equals( )
4、定制排序中,比较两个对象是否相同的标准为:Compare( )返回0。不再是equals( )
6、Map接口
Map接口的框架
Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
1. HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
2. TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序底层使 用红黑树
3. Hashtable :作为古老的实现类;线程安全的,效率低;不能存储null的key和value
Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表 (jdk 7及之前)
数组+链表+红黑树 (jdk 8)
Map存储结构的理解
1、Map中的key:无序的、不可重复的,使用Set存储所的key;key所在的类要重写equals()和hashCode() (以HashMap为例)
2、Map中的value:无序的、可重复的,使用Collection存储value;value所在的类要重写equals()
3、一个键值对:key-value构成了一个Entry对象。
4、Map中的entry:无序的、不可重复的,使用Set存储所的entry
HashMap的底层实现原理
1、HashMap在jdk 7中实现原理:
1. HashMap map = new HashMap():在实例化以后,底层创建了长度是16的一维数组Entry[] table。
2. 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在 Entry数组中的存放位置。
3. 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
4. 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经 存在的一个或多个数据的哈希值:
4.1 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
4.2 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1 所在类的equals(key2)方法,比较:
4.2.1 如果equals()返回false:此时key1-value1添加成功。----情况3
4.2.2 如果equals()返回true:使用value1替换value2。
补充1:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
补充2:不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的 扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
2、HashMap在jdk 8中相较于jdk 7在底层实现方面的不同:
1. new HashMap():底层没创建一个长度为16的数组jdk 8底层的数组是:Node[],而非Entry[]
2. 首次调用put()方法时,底层创建长度为16的数组
3. jdk 7底层结构只:数组+链表。jdk 8中底层结构:数组+链表+红黑树。
4. 形成链表时,七上八下(jdk 7:新的元素指向旧的元素。jdk 8:旧的元素指向新的元素)
5.当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 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
LinkedHashMap的底层实现原理(了解)
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap.
区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node.
HashMap常用方法
添加:put(Object key,Object value) / putAll(Map map)
删除:remove(Object key):返回移除的value
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet() / values() / entrySet()
TreeMap的使用
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象;因为要照key进行排序:自然排序 、定 制排序。
Properties的使用
1、Properties 类是 Hashtable 的子类,该对象用于处理属性文件
2、由于属性文件里的key、value都是字符串类型,所以Properties里的key和value都是字符串类型
3、存取数据时,建议使用setProperty(String key,String value)方法和getProperty (String key)方法
Collections工具类
1、作用:操作Collection和Map的工具类
2、常用方法:
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 对象的所旧值
3、说明:ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、 HashMap转换为线程的。
使用synchronizedList(List list) 和 synchronizedMap(Map map)
数据结构简述
1、概述:数据结构(Data Structure是一门和计算机硬件与软件都密切相关的学科,它的研究重点是在计算 机的程序设计领域中探讨如何在计算机中组织和存储数据并进行高效率的运用,涉及的内容包含:数据的逻 辑关系、数据的存储结构、排序算法(Algorithm)、查找(或搜索)等。
2、数据结构与算法的理解
程序能否快速而高效地完成预定的任务,取决于是否选对了数据结构,而程序是否能清楚而正确地把问 题解决,则取决于算法。
所以大家认为:“Algorithms + Data Structures = Programs”(出自:Pascal之父Nicklaus Wirth)
总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体。
第五章:泛型
1、泛型的概念
泛型的概念
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值类型 及参数类型;这个类型参数将在使用时(例如继承或实现这个接口用这个类型声明变量、创建对象时)确定 (即传入实际的类型参数也称为类型实参)。
泛型的引入背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK 1.5之前只能 把元素类型设计为Object,JDK 1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分 是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这 个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。
2、集合中使用泛型
1、Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时, 代码更加简洁、健壮。
2、编译时,就会进行类型检查,保证数据的安全
3、避免强转操作
4、JDK 7 新特性:类型推断。例子如下:
Map<String,Integer> map = new HashMap<>();
总结:
1.集合接口或集合类在jdk 5.0时都修改为带泛型的结构。
2.在实例化集合类时,可以指明具体的泛型类型。
3.指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类 的泛型的位置,都指定为实例化的泛型类型。
比如:add(E e) —>实例化以后:add(Integer e)
4.注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
5.如果实例化时,没指明泛型的类型。默认类型为java.lang.Object类型。
3、自定义泛型结构
自定义泛型类
泛型类的声明:
1. interface List 和 class GenTest<K,V >
2. 其中T,K,V 不代表值,而是表示类型。 这里使用任意字母都可以 。
3. 常用T表示,是Type的缩写。
泛型类的实例化:
1. 一定要在类名后面指定类型参数的值。
List list = new ArrayList<>();
2. 使用泛型的主要优点是能够在编译时而不是在运行时检测错误
注意点:
1. 泛型类可能有多个参数,此时应该把多个参数一起放在尖括号内。例如:<E1 , E2 , E3>
2. 泛型类的构造器:Public GenericClass() { }
3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致 。
4. 泛型不同的引用不能相互赋值。
尽管在编译时 ArrayList 和 ArrayList 是两种类型,但是,在运行时只有
一个 ArrayList 被加载到 JVM 中 。
5. 泛型如果不指定,将被擦除,泛型对应的类型均按照 Object 处理,但不等价于 Object 。 经验: 泛型要使用一路都用。要不用,一路都不要用
6. 如果泛型结构是一个接口或者抽象类,则不可创建泛型类的对象
7. JDK 7 泛型的简化操作,ArrayList list = new ArrayList<>( );
8. 泛型参数不能使用基本数据类型,可以使用其对应的包装类替换
9. 静态方法中不能使用泛型
10. 异常类不能是泛型的
11. 不能使用:new E[ ],但是可以使用E[ ] elements = (E[ ]) Object[capacity]
12. 父类有泛型,子类可以选择保留泛型或者指定泛型的类型
自定义泛型方法
泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名 ([泛型标识 参数名称]) 抛出的异常
自定义泛型接口
4、泛型在继承上的体现
1、泛型在继承方面的体现
1. 虽然类A是类B的父类,但是G 和G二者不具备子父类关系,二者是并列关系。
2. 例如:String 是 Object的子类,但是List 并不是的子类。
5、通配符的使用
1、使用通配符: ?
1. 例如:List<?> 是 List、List等各种泛型List的父类。
2、涉及通配符的集合的数据的写入和读取:
1.读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它都包含的是 Object
2. 写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象;null除外。
3、有限制条件的通配符的使用
? extends A:
G<? extends A> 可以作为G和G的父类,其中B是A的子类
使用时指定的类型必须是继承某个类,或者实现某个接口
? super A:
G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类
使用时指定的类型不能小于操作的类
6、泛型应用
1、泛型嵌套
第六章:IO流
1、File类的使用
File类的理解:
-
File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
- File类声明在java.io包下
3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
4. 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点"。
- File类声明在java.io包下
File的实例化
-
常用构造器
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath) -
路径的分类
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径说明:
IDEA中: 如果大家开发使用JUnit中的单元测试方法测试,相对路径即为当前Module下。
如果大家使用main()测试,相对路径即为当前的Project下。
Eclipse中:
不管使用单元测试方法还是使用main()测试,相对路径都是当前的Project下。
-
路径分隔符
windows和DOS系统默认使用“\”来表示
UNIX和URL使用“/”来表示
因为Win和Unix中的分隔符不一致,所以File类提供了一个常量:public static final String sepatator;
File类的常用方法
-
File类的获取功能
public String getAbsolutePath( ):获取绝对路径
public String getPath( ):获取路径
public String getName( ):获取名称
public String getParent( ):获取上层文件目录路径
public long length( ):获取文件长度(即:字节数)
public long lastModified( ):获取最后一次修改时间
public String[ ] list( ):获取指定目录下所有文件或文件目录的名称数组
public File[ ] listFiles( ):获取指定目录下的所有文件或文件目录的File数组
-
File类的重命名
public boolean renameTo(File dest):把文件重命名为指定的文件路径
-
File类的创建
public boolean createNewFile( ):创建文件,若文件存在,则不创建,返回false
public boolean mkdir( ):创建文件目录,若存在则不创建,其上级目录不存在也不创建
public boolean mkdirs( ):创建文件目录,若上级不存在则一并创建
-
File类的删除
public boolean delete( ):删除文件或目录
2、IO流原理及流的分类
流的分类
- 操作数据单位:字节流、字符流
- 数据的流向:输入流、输出流
- 流的角色:节点流、处理流
流的体系结构
重点关注学习:
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream |
重点说明的几个结构
抽象基类 | 节点流(文件类) | 缓冲流 |
---|---|---|
InputStream | FileInputStream (read(byte[ ] buffer)) | BufferedInputStream (read(byte[ ] buffer)) |
OutputStream | FileOutputStream (write(byte[ ] buffer)) | BufferedOutputStream (write(byte[ ] buffer)/flush( )) |
Reader | FileReader (read(char[ ] cbuf)) | BufferedReader (read(char[ ] cbuf)/readLine()) |
Writer | FileWriter (write(char[ ] cbuf,0,len)) | BufferedWriter(write(char[ ] cbuf,0,len)) |
输入、输出的标准化过程
-
输入过程
- 创建File类的对象,指明读取的数据的来源;
- 创建相应的输入流,将File类的对象作为参数,传入流的构造器中;
- 具体的读取过程:创建相对应的byte[ ] 或 char[ ];
- 关闭资源。
-
输出过程
- 创建File类的对象,指明写出的数据的位置。(不要求此文件一定要存在);
- 创建相应的输出流,将File类的对象作为参数,传入流的构造器中;
- 具体的写出过程:write(char[]/byte[] buffer,0,len);
- 关闭流资源。
说明:程序中出现的异常需要使用try-catch-finally处理。
3、节点流
节点流的概念
节点流(文件流):直接从数据源或目的地读写数据。
FileReader/FileWriter的用法
1. FileReader的用法:文件内容读入程序中,并输出到控制台
1. 创建一个流对象,将已经存在的一个文件加载进流;
2. 创建一个临时存放数据的数组;
3. 调用流对象中的读取方法将流中的数据读取到数组中;
4. 关闭资源。
public class FileReaderTest {
@Test
public void test() {
FileReader fr = null;
try {
//1.创建一个流对象,将已经存在的一个文件加载进流
fr = new FileReader(new File("test.txt"));
//2.创建一个临时存放数据的数组
char[] ch = new char[1024];
//3.调用流对象中的读取方法将流中的数据读取到数组中
while ((fr.read(ch)) != -1) {
String str = new String(ch,0,ch.length);
System.out.println(str);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.关闭资源
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
说明点:
1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
3. 读入的文件一定要存在,否则就会报FileNotFoundException。
- FileWriter的用法:从内存中写出数据到硬盘文件中
- 创建对象流,建立数据存储文件;
- 调用流对象的写入方法,将数据写入流;
- 关闭流资源,并将流中的数据清空到文件中。
public class FileWriterTest {
@Test
public void test() {
FileWriter fw = null;
try {
//1.创建对象流,建立数据存储文件
fw = new FileWriter(new File("test2.txt"));
//2.调用流对象的写入方法,将数据写入流
fw.write("this is test");
} catch (IOException e) {
e.printStackTrace();
}finally {
//3.关闭流资源,并将流中的数据清空到文件中
if(fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
说明点:
1. 输出操作,对应的File可以不存在的;并不会报异常
2. File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原文件的覆盖
如果流使用的构造器是:FileWriter(file,true):不会对原文件覆盖,而是在原文件基础上追加内容
FileInputStream / FileOutputStream的使用
1. 对于文本文件(.txt, .java, .c, .cpp),使用字符流处理(FileReader、FileWriter)
2. 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,…),使用字节流处理(FileInputStream /、FileOutputStream)
@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();
}
}
}
}
4、缓冲流
缓冲流的概念
为了提高数据读写的速度,Java API提供了带缓冲功能的类,在使用这些类时,会创建一个内部缓冲数组, 默认使用8192个字节(8Kb)的缓冲区。
缓冲流的分类
1. BufferedInputStream/BufferedOutputStream
2. BufferedReader/BufferedWriter
典型代码
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();
}
}
}
}
说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略。
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();
}
}
}
}
5、转换流
转换流的概念
1. 转换流提供了字节流和字符流之间的转换。
2. Java API提供了两个转换流:
1. InputStreamReader:将InputStream转换为Reader;解码-->字节、字节数组 --->字符数组、字符串
2. OutputStreamWriter:将writer转换为OutputStream;编码-->字符数组、字符串 ---> 字节、字节数组
3. 1.
典型实现
InputStreamReader
1. 实现将字节的输入流按指定字符集转换为字符的输入流。
2. 构造器:
1. public InputStreamReader(InputStream in)
2. public InputStreamReader(InputStream in,String charsetName)
@Test
public void test1() throws IOException {
FileInputStream fis = new FileInputStream("dbcp.txt");
// InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集
// 参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");// 使用系统默认的字符集
char[] cbuf = new char[20];
int len;
while ((len = isr.read(cbuf)) != -1) {
String str = new String(cbuf, 0, len);
System.out.print(str);
}
isr.close();
}
OutputStreamWriter
1. 实现将字符的输出流按指定的字符集转换为字节的输出流。
2. 构造器:
1. public OutputStreamWriter(OutputStream out)
2. public OutputStreamWriter(OutputStream out,String charsetName)
@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();
}
字符编码集了解
1. ASCII:美国标准信息交换码;用一个字节的7位可以表示。
2. ISO8859-1:拉丁码表、欧洲码表;用一个字节的8位表示。
3. GB2312:中国的中文编码表;最多两个字节编码所有字符。
4. GBK:中国的中文编码表升级,融合了更多的中文文字符号;最多两个字节编码。
5. Unicode:国际标准码,融合了目前人类使用的所字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
6. UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
6、标准输入输出流
标注输入输出流的概念
1. System.in:标准的输入流,默认从键盘输入
2. System.out:标准的输出流,默认从控制台输出
输入输出重定向:
1. 修改默认的输入和输出行为,重新指定输入和输出的流。
1. System类的setIn(InputStream is)
2. setOut(PrintStream ps)
7、打印流
打印流的概念
-
实现基本数据类型的数据格式转化为字符串输出。
-
打印流:PrintStream、PrintWriter
- 提供了一系列重载的print()和println()方法,用于多种数据类型的输出。
- System.out返回的是PrintStream的实例。
- PrintStream、PrintWriter不会抛出IOException异常。
- PrintStream、PrintWriter有自动flush功能
8、数据流
数据流的概念
1. 为了方便地操作基本数据类型和String的数据,可以使用数据流。
2. 作用:用于读取或写出基本数据类型的变量或字符串
3. 数据流的分类:DataInputStream 和 DataOutputStream
4. 它们分别套接在InputStream 和 OutputStream子类的流上
/*
练习:将内存中的字符串、基本数据类型的变量写出到文件中。
注意:处理异常的话,仍然应该使用try-catch-finally.
*/
@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();
}
9、对象流
对象流的概念
- 用于存储和读取基本数据类型数据或对象的处理流;它可以把Java对象写入到数据源,也能把对象从数据源中还原回来。
- 分类:ObjectInputStream 和 OjbectOutputSteam
- 序列化:用 ObjectOutputStream类保存基本类型数据或对象的机制。
- 反序列化:用 ObjectInputStream类读取基本类型数据或对象的机制。
- 注意点:ObjectOutputStream 和 ObjectInputStream 不能序列化 static 和 transient 修饰的成员变量。
作用
-
ObjectOutputStream:内存中的对象—>存储中的文件、通过网络传输出去:序列化过程
-
ObjectInputStream:存储中的文件、通过网络接收过来 —>内存中的对象:反序列化过程
对象的序列化机制
- 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
- 当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
典型实现代码
1. 序列化实现
@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();
}
}
}
}
实现序列化的对象所属的类需要满足
- 需要实现接口:Serializable
- 当前类提供一个全局常量:serialVersionUID
- 除了当前Person类需要实现Serializable接口之外,还必须保证其内部所属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
10、随机存储文件流
随机存储文件流的概念
1. RandomAccessFile声明在Java.io包下,但直接继承于java.lang.Object类;并且它实现了DataInput、DataOutput这两个接口,意味着这个类既可以读也可以写。
2. RandomAccessFile类支持“随机访问”的方式,程序可以直接跳到文件的任意位置地方来读写文件
1. 支持只访问文件的部分内容
2. 可以向已存在的文件后追加内容
3. RandomAccessFile对象包含一个记录指针,用以标识当前读取处的位置;该对象可以自由移动记录指针
1. long getFilePoint( ):获取文件当前指针的位置
2. void seek(long pos):将文件记录指针定位到pos位置
RandomAccessFile类
- 构造器:
- public RandomAccessFile(File file,String mode)
- public RandomAccessFile(String name,String mode)
- mode参数说明:
- r: 以只读方式打开。
- rw :打开以便读取和写入。
- rwd 打开以便读取和 写入;同步文件内容的更新。
- r ws 打开以便读取和 写入; 同步文件内容和元数据的。
- 如果模式为只读 r 。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。
- 如果模式为 rw 读写。如果文件不存在则会去创建文件,如果存在则不会创建。
典型实现代码
@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();
}
}
}
}
/*
使用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();
//思考:将StringBuilder替换为ByteArrayOutputStream
}
11、NIO.2中Path、Paths、Files类的使用
NIO的使用说明
-
Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。
-
NIO与原来的IO同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于
通道的IO操作。 -
NIO将以更加高效的方式进行文件的读写操作。
-
随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。
java.nio.channels.Channel
-
FileChannel: 处理本地文件
-
SocketChannel TCP 网络编程的客户端的 Channel
-
ServerSocketChannel:TCP 网络编程的服务器端的 Channel
-
DatagramChannel UDP 网络编程中发送端和接收端的 Channel
Path接口的使用
说明:Path替换原有的File类
Paths实例化:
- Paths 类提供的静态 get() 方法用来获取 Path 对象:
- static Path get(String first, String … more) : 用于将多个字符串串连成路径
- static Path get(URI url): 返回指定 url 对应的 Path 路径
常用方法:
-
String toString() 返回调用 Path 对象的字符串表示形式
-
boolean startsWith(String path) : 判断是否以 path 路径开始
-
boolean endsWith(String path) : 判断是否以 path 路径结束
-
boolean isAbsolute() : 判断是否是绝对路径
-
Path getParent() :返回 Path 对象包含整个路径,不包含 Path 对象指定的文件路径
-
Path getRoot() :返回调用 Path 对象的根路径
-
Path getFileName() : 返回与调用 Path 对象关联的文件名
-
int getNameCount() : 返回 Path 根目录后面元素的数量
-
Path getName(int idx) : 返回指定索引位置 idx 的路径名称
-
Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
-
Path resolve(Path p) : 合并两个路径,返回合并后的路径对应的 Path 对象
-
File toFile(): 将 Path 转化为 File 类的对象
Files类的使用
常用方法:
-
Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
-
Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
-
Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
-
void delete(Path path) : 删除一个文件 目录,如果不存在,执行报错
-
void deleteIfExists(Path path) : Path 对应的文件 目录如果存在,执行删除
-
Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
-
long size(Path path) : 返回 path 指定文件的大小
Files 常用方法:用于判断
-
boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
-
boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
-
boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
-
boolean isHidden(Path path) : 判断是否是隐藏文件
-
boolean isReadable(Path path) : 判断文件是否可读
-
boolean isWritable(Path path) : 判断文件是否可写
-
boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
Files 常用方法:用于操作内容
-
SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连
接, how 指定打开方式。 -
DirectoryStream
newDirectoryStream(Path path) : 打开 path 指定的目录 -
InputStream newInputStream(Path path, OpenOption…how): 获取 InputStream 对象
-
OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
第七章:网络编程
1、网络编程概述
网络编程的目的
- 直接或间接地通过网络协议与其他计算机实现数据交换,进行通讯
实现网络通信需要解决的两个问题
-
如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
-
找到主机后如何可靠高效地进行数据传输
2、网络通信要素概述
网络通信的两个要素
- IP和端口号
- 网络通信协议
如何实现网络中的主机互相通信
-
通信双方的地址
- IP和端口号
-
一定的规则(网络通信协议)
- OSI 参考模型 :模型过于理想化,未能在因特网上进行广泛推广
- TCP/IP 参考模型 或 TCP/IP 协议 )):事实上的国际标准(应用层、传输层、网络层、物理+数据链路层)
3、通信要素1:IP和端口号
IP的理解
- IP:唯一的标识 Internet 上的计算机(通信实体)
- 在Java中使用InetAddress类代表IP
- IP分类:IPv4(4个字节) 和 IPv6(16个字节) ; 万维网 和 局域网
- 域名: www.baidu.com www.mi.com www.sina.com www.jd.com
- 域名解析:域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。
- 本地回路地址:127.0.0.1 对应着:localhost
InetAddress类的使用
- 此类的一个对象就代表着一个具体的IP地址。
- 实例化:
- getByName(String host)
- getLocalHost()
- 常用方法:
- String getHostName() :获取此IP地址的主机名
- String getHostAddress():返回IP地址字符串(以文本表现形式)
- boolean isReachable(int timeout):测试是否可以达到该 地址
- 实例化:
public class InetAddressTest {
@Test
public void test() {
try {
// 实例化
InetAddress inet = InetAddress.getByName("127.0.0.1");
// 常用方法
String name = inet.getHostName();
System.out.println(name);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
@Test
public void test2() {
try {
// 实例化
InetAddress inet2 = InetAddress.getLocalHost();
String addr = inet2.getHostAddress();
System.out.println(addr);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
端口号
-
端口号表示计算机上正在运行的进程。
- 不同的进程有不同的端口号。
- 被规定为一个16位的整数:0~65535.
- 端口号的分类
- 公认端口:0到1023,被预先定义的服务通信占用。
- 注册端口:1024到49151,分配给用户进程或者应用程序。
- 动态/私有端口:49152到65535
说明:端口号和IP地址的组合得出一个网络套接字:Socket
4、通信要素2:网络协议
网络通信协议
- 计算机网络中实现通信必须有一些约定,即通信协议, 对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
分层模型
OSI参考模型 | TCP/IP | TCP/IP参考模型各层对应的协议 |
---|---|---|
应用层 | 应用层 | HTTP、FTP、Telnet、DNS |
表示层 | ||
会话层 | ||
传输层 | 传输层 | TCP、UDP |
网络层 | 网络层 | IP、ICMP、ARP |
数据链路层 | ||
物理层 | 物理+数据链路层 | Link |
TCP/IP协议簇
- TCP/IP 以其两个主要协议:传输控制协议 TCP) 和网络互联协议 IP) 而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
- 传输层协议中有两个非常重要的的协议
- 传输控制协议(TCP)
- 用户数据报协议(UDP)
TCP和UDP的区别
TCP 协议:
- 使用 TCP 协议前,须先建立 TCP 连接,形成传输数据通道。
- 传输前,采用“ 三次握手 方式 ,点对点通信是可靠的。
- TCP 协议进行通信的两个应用进程:客户端、 服务端。
- 在连接中可进行大数据量的传输。
- 传输完毕,需释放已建立的连接效率低。
UDP 协议:
- 将数据、源、目的封装成数据包, 不需要建立连接。
- 每个数据报的大小限制在 64K 内。
- 发送不管对方是否准备好,接收方收到也不确认, 故是不可靠的
- 可以广播发送
- 发送数据结束时 无需释放 资源 ,开销小,速度 快
TCP三次握手:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OncHpjf4-1690386722425)(D:\zhanghaibi888\JavaNote\测试开发核心技术笔记\pic\TCP三次握手.png)]
TCP四次挥手:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z3hezlVY-1690386722426)(D:\zhanghaibi888\JavaNote\测试开发核心技术笔记\pic\TCP四次挥手.png)]
Socket套接字
-
网络具有唯一标识的 IP 地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
-
Socket 分类:
- 流套接字( stream socket ):使用 TCP 提供可依赖的字节流服务
- 数据报套接字( datagram socket ):使用 UDP 提供“尽力而为”的数据报服务
-
Socket 类的常用构造器
- public Socket(InetAddress address,int port) 创建一个流套接字并将其连接到指定 IP 地址的指定
- public Socket(String host,int port) 创建一个流套接字并将其连接到指定主机上的指定端口号
-
Socket 类的常用方法:
- public InputStream getInputStream() 返回此套接字的输入流 。 可以用于接收网络消息
- public OutputStream getOutputStream() 返回此套接字的输出流 。 可以用于发送网络消息
5、TCP网络编程
基于Socket的TCP编程
客户端 Socket 的工作过程包含以下四个基本的步骤:
- 创建 Socket 根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
- 打开连接到 Socket 的输入出流: 使用 getInputStream 方法获得输入流,使用getOutputStream 方法获得输出流,进行数据传输
- 按照一定的协议对 Socket 进行读写操作: 通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。
- 关闭 Socket 断开客户端到服务器的连接,释放线路
服务器 程序的工作过程包含以下四个基本的步骤:
- 调用 ServerSocket int port ) 创建一个服务器端套接字,并绑定到指定端口上 。用于监听客户端的请求。
- 调用 accept() 监听连接请求,如果客户端请求连接,则接受连接,返回通信
套接字对象 。 - 调用 该 Socket 类对象的 getOutputStream () 和 getInputStream 获取 输出流和输入流,开始网络数据的发送和接收。
- 关闭 ServerSocket 和 Socket 对象: 客户端访问结束,关闭 通信套接字
public class TCPTest {
// 客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
InetAddress inet;
try {
// 1.创建Socket对象,指明服务器端的ip和端口号
inet = InetAddress.getByName("192.168.0.104");
socket = new Socket(inet, 9999);
// 2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
// 3.写出数据的操作
os.write("你好,我是客户端".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(9999);
// 2.调用accept()表示接收来自于客户端的socket
socket = ss.accept();
// 3.获取输入流
is = socket.getInputStream();
// 4.读取输入流中的数据
baos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[5];
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
System.out.println(baos.toString());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (baos != null) {
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();
}
}
}
}
}
6、UDP网络编程
UDP网络通信
- 类 DatagramSocket 和 DatagramPacket 实现了基于UDP协议网络程序。
- UDP 数据报通过数据报套接字 DatagramSocket 发送和接收, 系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
- DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的 IP地址和端口号以及接收端的 IP 地址和端口号。
- UDP 协议中 每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。
public class UDPTest {
// 发送端
@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();
}
}
7、URL编程
URL类
-
URL(Uniform Resource Locator)的理解:统一资源定位符,对应着互联网的某一资源地址
-
URL 的基本结构由 5 部分组成 :传输协议 主机名 端口号文件名片段名参数列表
http://localhost:8080/examples/beauty.jpg?username=Tom
协议 主机名 端口号 资源地址 参数列表
URL类构造器
-
public URL(String spec):)):通过 一个表示 URL 地址的字符串可以构造一个 URL 对象。
例如:URL url = new URL(“http://www.localhost.com”)
-
public URL(URL context,String spec):)):通过基 URL 和相对URL构造一个URL 对象 。
URL downloadUrl = ( new url ,“download.html”)
-
实例化:URL url = new URL(“http://localhost:8080/examples/beauty.jpg?username=Tom”);
常用方法
一个URL对象生成之后,其属性是不能被改变的,但可以通过它给定的方法获得这些属性
- public String getProtocol ( ) 获取该 URL 的协议名
- public String getHost ( ) 获取 该 URL 的主机名
- public String getPort ( ) 获取 该 URL 的端口号
- public String getPath ( ) 获取 该 URL 的文件路径
- public String getFile ( ) 获取 该 URL 的文件名
- public String getQuery ( ) 获取 该 URL 的查询名
public class URLTest {
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();
}
}
}
}
第八章:Java反射机制
1、Java反射机制的概述
反射的理解
- Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何
类的内部信息,并能直接操作任意对象的内部属性及方法。
体会反射机制的动态性
@Test
public void test(){
//体会动态性
for(int i = 0;i < 100;i++){
int num = new Random().nextInt(3);//0,1,2
String classPath = "";
switch(num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.atguigu.java.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
创建一个指定类的对象。
classPath:指定类的全类名
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
反射机制能提供的功能
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时获取泛型信息。
- 在运行时调用任意一个对象的成员变量和方法。
- 在运行时处理注解。
- 生成动态代理。
相关API
- java.lang.Class:反射的源头,代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
2、理解Class类并获取Class实例
Class类的理解
- 在Object类中定义了一个public final Class getClass()方法,此方法被Object类所有的子类继承;
- 以上的方法返回值是一个Class类,此类是Java反射的源头,实际上反射从程序运行的结果来看也很好理解,即:可以通过对象反射求出类的名称;
- Class本身也是一个类;Class对象只能由系统建立对象;
- 一个加载的类在 JVM中只有一个Class实例;
- 一个Class对象对应的是一个加载到 JVM中的一个.class文件
类加载的过程
- 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
- 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
- 换句话说,Class的实例就对应着一个运行时类。
- 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
获取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);
//方式四:使用类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz4);
Class类的常用方法
- static Class forName(String name):返回指定类名name 的Class 对象;
- Object newInstance( ):调用缺省构造函数,返回该Class 对象的一个实例;
- getName( ):返回此Class 对象所表示的实体(类、接口、数组类、基本类型或 void )名称
- Class getSuperClass( ):返回当前Class 对象的父类的 Class 对象
- Class[] getInterfaces( ):获取当前Class 对象的接口
- ClassLoader getClassLoader( ):返回该类的类加载器
- Class getSuperclass( ):返回表示此Class 所表示的实体的超类的 Class
- Constructor[] getConstructors( ):返回一个包含某些Constructor 对象的数组
- Field[] getDeclaredFields( ):返回Field 对象的一个数组
- Method getMethod (String name,Class … paramTypes):返回一个Method 对象,此对象的形参类型为 paramType
总结:创建类的对象的方式
- 方式一:new + 构造器
- 方式二:要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在;可以调用其静态方法,创建Xxx对象。
- 方式三:通过反射
Class实例可以是哪些结构的说明
- class 外部类,成员(成员内部类、静态内部类),局部内部类,匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
3、类的加载和ClassLoader的理解
类的加载过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S6SDCQzn-1690386722427)(D:\zhanghaibi888\JavaNote\测试开发核心技术笔记\pic\类加载的过程.png)]
-
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
-
链接:将Java类的二进制代码合并到 JVM 的运行状态之中的过程。
- 验证:确保加载的类信息符合 JVM 规范,例如:以cafe开头,没有安全方面的问题;
- 准备:正式为类变量(static)分配内存并设置类变量默认初始化值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池的符号引用(常量名)替换为直接引用(地址)的过程。
-
初始化:
- 执行类构造器()方法的过程。类构造器 < 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。 (类构造器是构造类信息的,不是构造该类对象的构造器 )。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
类的加载器的作用
- 类加载的作用:将class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生产一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
- 类缓存: 标准的 JavaSE 类加载器可以按要求查找类但一旦某个类被加载到类加载器中它将维持加载缓存 一段时间 。 不过 JVM 垃圾回收机制可以回收这些 Class 对象。
类加载器的分类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VZhHHsj3-1690386722428)(D:\zhanghaibi888\JavaNote\测试开发核心技术笔记\pic\类加载器分类.png)]
- 引导类加载器:用 C++ 编写的,是 JVM 自带的类加载 器, 负责 Java 平台核心库 ,用来装载核心类库。该加载器无法直接获取。
- 扩展类加载器:负责jre / ext 目录下的 jar 包或D java.ext.dirs 指定目录下的 jar 包装入工作库。
- 系统类加载器:负责 java classpath 或 Djava.class.path 所指的目录下的类与 jar 包装入工作 ,是最常用的加载器。
Java类编译、运行的执行的流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kG55wK6Y-1690386722428)(D:\zhanghaibi888\JavaNote\测试开发核心技术笔记\pic\类编译运行图.png)]
-
获取一个系统类加载器:
ClassLoader classloader = ClassLoader.getSystemClassLoader();
-
获取系统类加载器的父类加载器,即扩展类加载器
classloader = classloader.getParent();
-
获取扩展类加载器的父类加载器,即引导类加载器
classloader = classloader.getParent();
-
测试当前类由哪个类加载器进行加载
classloader = Class.forName (“exer2. getClassLoader”).getClassRoader();
-
测试 JDK提供的Object类由哪个类加载器加载的
classloader = Class.forName(" java.lang.Object") getClassLoader();
4、创建运行时类的对象
创建对象
- 创建类的对象:调用Class对象的newInstance( )方法
- 类必须要有一个无参数的构造器
- 类的构造器的访问权限需要足够
代码举例
Class<Person> clazz = Person.class;
Person obj = clazz.newInstance();
说明
newInstance( ):调用此方法,创建对应的运行时类的对象;内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
-
运行时类必须提供空参的构造器
-
空参的构造器的访问权限得够。通常,设置为public。
在javabean中要求提供一个public的空参构造器。原因:
-
便于通过反射,创建运行时类的对象
-
便于子类继承此运行时类时,默认调用super()时,保证父类此构造器
5、获取运行时类的完整结构
通过反射获取运行时类的完整结构
- 实现的全部接口
/*
*获取运行时类实现的接口
*/
@Test
public void test1(){
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 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);
}
/*
* 获取运行时类的带泛型的父类的泛型
* 代码:逻辑性代码 vs 功能性代码
*/
@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;
// 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 test6(){
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);
}
}
5、全部的属性
/**
* 获取属性结构
*/
public void test7() {
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 test8() {
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annos : annotations) {
System.out.println(annos);
}
}
- 类所在的包
/*
*获取运行时类所在的包
*/
@Test
public void test6(){
Class clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
6、调用运行时类的指定结构
调用运行时类的指定结构有
- 调用指定方法:
- 通过反射,调用类的指定方法,通过Method类完成。
@Test
public void testMethod() throws Exception {
Class clazz = Person.class;
// 创建运行时类的对象
Person p = (Person) clazz.newInstance();
/*
* 1.获取指定的某个方法 getDeclaredMethod():
* 参数1:指明获取的方法的名称
* 参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
// 2.保证当前方法是可访问的
show.setAccessible(true);
/*
* 3. 调用方法的invoke():
* 参数1:方法的调用者
* 参数2:给方法形参赋值的实参 invoke()的返回值即为对应类中调用的方法的返回值。
*/
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
}
-
调用指定属性:
-
在反射机制中,可以直接通过Field 类操作类中的属性,通过 Field 类提供的 set() 和get() 方法就可以完成设置和取得属性内容的操作。
- public Field getField (String name) 返回此 Class 对象表示的类或接口的指定的public 的 Field 。
- public Field getDeclaredField (String 返回此 Class 对象表示的类或接口的指定的 Field。
-
在 Field 中:
- public Object get(Object obj ) 取得指定对象 obj 上此 Field 的属性内容。
- public void set(Object obj,Object value) 设置指定对象 obj 上此 Field 的属性内容。
-
@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 testConstructor() throws Exception {
Class clazz = Person.class;
//private Person(String name)
/*
1.获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器是可访问的
constructor.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);
}
关于setAccessible 方法的使用
-
Method、Field和Constructor对象都有setAccessible()方法。
-
setAccessible 启动和禁用访问安全检查的开关 。
-
参数 值 为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查 。
- 提高反射的效率 。 如果代码中必须用反射 而该句代码需要频繁的被调用 那么请设置为 true。
- 使得 原本无法访问的私有成员也可以访问。
-
参数值为 false 则指示反射的对象应该实施 Java 语言访问检查 。
7、反射的应用:动态代理
代理设计模式的原理
- 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
静态代理
- 静态代理举例
实现Runnable接口的方法创建多线程。
Class MyThread implements Runnable{} //相当于被代理类
Class Thread implements Runnable{} //相当于代理类
main(){
MyThread t = new MyThread();
Thread thread = new Thread(t);
thread.start();//启动线程;调用线程的run()
}
- 静态代理的缺点
- 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。
- 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
动态代理的特点
- 动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对
象。
动态代理的实现
-
需要解决的两个主要问题
- 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。(通过Proxy.newProxyInstance()实现)
- 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
(通过InvocationHandler接口的实现类及其方法invoke())
-
代码实现
/**
* 动态代理的举例
*/
interface Human {
String getBelief();
void eat(String food);
}
// 被代理类
class SuperMan implements Human {
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
class HumanUtil {
public void method1() {
System.out.println("====================通用方法一====================");
}
public void method2() {
System.out.println("====================通用方法二====================");
}
}
class ProxyFactory {
// 调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj) {// obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}
class MyInvocationHandler implements InvocationHandler {
private Object obj;// 需要使用被代理类的对象进行赋值
public void bind(Object obj) {
this.obj = obj;
}
// 当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
// 将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil util = new HumanUtil();
util.method1();
// method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
// obj:被代理类的对象
Object returnValue = method.invoke(obj, args);
util.method2();
// 上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
// proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
// 当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("四川麻辣烫");
System.out.println("*****************************");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}
第九章:Java 8 的新特性
1、Lambda表达式
Lambda表达式概念
- Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码。
- Lambda表达式的本质:作为函数式接口的实例。
- 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
- 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
Lambda表达式使用前后的对比
@Test
public void test() {
//使用Lambda表达前的实现方式
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门");
}
};
r1.run();
System.out.println("*********分隔符*********");
// 使用Lambda表达式实现
Runnable r2 = () -> System.out.println("我爱北京故宫");
r2.run();
}
例子2:
//比较两个整数的大小
@Test
public void test2() {
//使用Lambda前的实现方式
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
int compare1 = com1.compare(12, 23);
System.out.println(compare1);
System.out.println("*********分隔符*********");
// 使用Lambda表达式实现
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
int compare2 = com2.compare(32, 12);
System.out.println(compare2);
}
Lambda表达式的基本语法
- Lambda表达式:在Java 8中引入的一种新的语法元素和操作符;这个操作符为“ -> ”,它将Lambda表达式分为两部分:
- 左侧:指定了Lambda表达式需要的参数列表。
- 右侧:指定了Lambda体,抽象方法的实现逻辑,也即Lambda表达式要执行的功能。
Lambda表达式使用的六种方式
- 语法格式一:无参数,无返回值。
@Test
public void test() {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱大深圳");
}
};
r1.run();
System.out.println("*******************");
// 语法格式一:无参数,无返回值
Runnable r2 = () -> System.out.println("我爱大南山");
r2.run();
}
- 语法格式二:Lambda 需要一个参数,但是没有返回值。
@Test
public void test2() {
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("谎言和誓言的区别是什么?");
System.out.println("*******************");
// Lambda 需要一个参数,但是没有返回值。
Consumer<String> con2 = (String s) -> System.out.println(s);
con2.accept("一个是听得人当真了,一个是说的人当真了");
}
- 语法格式三:数据类型可以省略 ,因为可由编译器推断得出,称为“类型推断“
@Test
public void test3() {
Consumer<String> con = (String s) -> System.out.println(s);
con.accept("一个是听得人当真了,一个是说的人当真了");
System.out.println("*******************");
// 数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
Consumer<String> con2 = (s) -> System.out.println(s);
con2.accept("一个是听得人当真了,一个是说的人当真了");
}
- 语法格式四:Lambda 若只需要一个参数时, 参数的小括号可以省略。
@Test
public void test4() {
Consumer<String> con = (s) -> System.out.println(s);
con.accept("一个是听得人当真了,一个是说的人当真了");
System.out.println("*******************");
// Lambda 若只需要一个参数时, 参数的小括号可以省略
Consumer<String> con2 = s -> System.out.println(s);
con2.accept("一个是听得人当真了,一个是说的人当真了");
}
- 语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值。
@Test
public void test5() {
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
System.out.println(com.compare(22, 21));
System.out.println("*******************");
// Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
Comparator<Integer> com2 = (o1, o2) -> {
System.out.println(o1);
System.err.println(o2);
return o1.compareTo(o2);
};
System.out.println(com2.compare(32, 51));
}
- 语法格式六:当 Lambda 体只有一条语句时, return 与大括号若有,都可以省略。
@Test
public void test6() {
// 当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
Comparator<Integer> com = (o1, o2) -> o1.compareTo(o2);
System.out.println(com.compare(22, 24));
}
六种使用情况总结
- 左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只一个参数,其一对()也可以省略
- 右边:lambda体应该使用一对{ }包裹;如果lambda体只一条执行语句(可能是return语句,省略这一对{}和return关键字
2、函数式(Functional)接口
函数式(Functional)接口的定义
- 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。
- 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
- Lambda表达式的本质:作为函数式接口的实例。
4个Java核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 | T | void | 对类型为T 的对象应用操作,包含方法void accept(T t) |
Supplier 供给型接口 | 无 | T | 返回类型为 T 的对象,包含方法 T get |
Function<T,R> 函数型接口 | T | R | 对类型为 T 的对象应用操作,并返回结果。结果是 R 类型的对象。包含方法 R apply(T t) |
Predicate 断定型接口 | T | boolean | 确定类型为 T 的对象是否满足某约束,并返回boolean 值。包含方法 boolean test(T t) |
举例子
@Test
public void test() {
happyTime(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("学习太累了,去天上人间买可乐,价格为:" + aDouble);
}
});
System.out.println("******************");
//
happyTime(400, money -> System.out.println("学习太累了,去天上人间买可乐,价格为:" + money));
}
public void happyTime(double money, Consumer<Double> con) {
con.accept(money);
}
3、方法引用和构造器引用
方法引用
-
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法体。
-
方法引用可以看做是lambda表达式深层次的表达;方法引用就是Lambda表达式,也就是函数式接口的一个实例。
-
要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
-
格式: 类(或对象) :: 方法名
-
方法引用的三种使用情况:
- 对象 :: 非静态方法(实例方法)
- 类 :: 静态方法
- 类 :: 非静态方法
方法引用的代码实现
- 情况一:对象 :: 实例方法
@Test
public void test() {
Consumer<String> con = s -> System.out.println(s);
con.accept("我爱北京");
System.out.println("**************************");
// 情况一:对象 :: 实例方法
PrintStream ps = System.out;
Consumer<String> con2 = ps::println;
con2.accept("我爱深圳");
}
@Test
public void test2() {
Employee emp = new Employee(1001, "Tom", 20, 9000);
Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());
System.out.println("**************************");
// 对象 :: 实例方法
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}
- 情况二:类 :: 静态方法
@Test
public void test3() {
Comparator<Integer> com1 = (i1, i2) -> Integer.compare(i1, i2);
System.out.println(com1.compare(22, 33));
System.out.println("**************************");
// 情况二:类 :: 静态方法
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(45, 33));
}
@Test
public void test4() {
Function<Double, Long> func = new Function<Double, Long>() {
public Long apply(Double d) {
return Math.round(d);
}
};
System.out.println(func.apply(12.4));
System.out.println("**************************");
// Lambda表达式
Function<Double, Long> func2 = d -> Math.round(d);
System.out.println(func2.apply(12.6));
System.out.println("**************************");
// 类 :: 静态方法
Function<Double, Long> func3 = Math::round;
System.out.println(func3.apply(13.6));
}
- 情况三:类 :: 实例方法 (有难度)
@Test
public void test5() {
Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc", "abd"));
System.out.println("**************************");
// 类 :: 实例方法
Comparator<String> com2 = String::compareTo;
System.out.println(com2.compare("abc", "abc"));
}
构造器引用
- 和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
- 抽象方法的返回值类型即为构造器所属的类的类型
- 格式:ClassName ::new
构造器引用代码
- 空参构造器
//Supplier中的T get()
@Test
public void test() {
//常规
Supplier<Employee> sup1 = new Supplier<Employee>() {
@Override
public Employee get() {
return new Employee();
}
};
System.out.println(sup1.get());
System.out.println("**************************");
// Lambda表达式
Supplier<Employee> sup2 = () -> new Employee();
System.out.println(sup2.get());
System.out.println("**************************");
// 构造器引用
Supplier<Employee> sup3 = Employee::new;
System.out.println(sup3.get());
}
- 有参构造器
//Function中的R apply(T t)
@Test
public void test2() {
Function<Integer, Employee> func1 = id -> new Employee(id);
Employee emp1 = func1.apply(1001);
System.out.println(emp1.toString());
System.out.println("**************************");
//有参数构造器
Function<Integer, Employee> func2 = Employee::new;
Employee emp2 = func2.apply(1002);
System.out.println(emp2);
}
- 多参数构造器
//BiFunction中的R apply(T t,U u)
@Test
public void test3() {
BiFunction<Integer, String, Employee> biFunction = (id,name) -> new Employee(id,name);
System.out.println(biFunction.apply(1001, "Peter"));
System.out.println("**************************");
//多参数构造器
BiFunction<Integer, String, Employee> biFunction2 = Employee :: new;
System.out.println(biFunction2.apply(1002, "Tom"));
}
数组引用
//数组引用
//Function中的R apply(T t)
@Test
public void test4(){
Function<Integer,String[]> func1 = length -> new String[length];
String[] arr1 = func1.apply(5);
System.out.println(Arrays.toString(arr1));
System.out.println("*******************");
//数组引用
Function<Integer,String[]> func2 = String[] :: new;
String[] arr2 = func2.apply(10);
System.out.println(Arrays.toString(arr2));
}
4、强大的Stream API
Stream API的理解:
- Stream是 Java 8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
- java 8提供了一套API,使用这套API可以对内存中的数据进行过滤、排序、映射、归约等操作。类似于SQL对数据库中表的相关操作。
- Stream关注的是对数据的运算,与CPU打交道;集合关注的是数据的存储,与内存打交道。
Stream API的注意点:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream的使用流程:
- 创建Stream: Stream的实例化,获取一个流。
- 中间操作:一个中间操作链,对数据源的数据进行处理。
- 终止操作:一旦执行终止操作,就执行中间操作链 ,并产生结果 。之后,不会再被使用。
使用流程的注意点:
- 一个中间操作链,对数据源的数据进行处理
- 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
创建Stream的方式:
- 创建Stream 方式一:通过集合
- Java 8 中的Collection接口被扩展,提供了两个获取流的方法:
- default Stream stream( ):返回一个顺序流。
- default Stream parallelStream( ):返回一个并行流。
- Java 8 中的Collection接口被扩展,提供了两个获取流的方法:
//创建 Stream方式一:通过集合
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();
// default Stream<E> stream() : 返回一个顺序流
Stream<Employee> stream = employees.stream();
// default Stream<E> parallelStream() : 返回一个并行流
Stream<Employee> parallelStream = employees.parallelStream();
}
- 创建Stream 方式二:通过数组
- Java 8中的 Arrays 的静态方法 stream( ) 可以获取数组流:
- static Stream stream(T[ ] array): 返回一个流。
- 重载形式,能够处理对应基本类型的数组。
- Java 8中的 Arrays 的静态方法 stream( ) 可以获取数组流:
//创建 Stream方式二:通过数组
@Test
public void test2() {
int[] arr = {1,2,3,4,5,6,7};
//调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
IntStream intStream = Arrays.stream(arr);
//其他类型的数组:如对象数组
Employee emp1 = new Employee(1001,"Tom");
Employee emp2 = new Employee(1002,"Peter");
Employee[] employees = {emp1,emp2};
Stream<Employee> employeeStream = Arrays.stream(employees);
}
- 创建Stream 方式三:通过 Stream 的 of( )
- 可以调用Stream 类静态方法 of( ), 通过显示值创建一个流。它可以接收任意数量的参数。
- public static Stream of(T… values) : 返回一个流
//创建 Stream方式三:通过Stream的of()
@Test
public void test3() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7);
}
- 创建Stream 方式四:创建无限流
- 可以使用静态方法Stream.iterate( ) 和 Stream.generate( ) 创建无限流。
- 迭代:public static Stream iterate(final T seed, final UnaryOperator f)
- 生成:public static Stream generate(Supplier s)
//创建 Stream方式四:创建无限流
@Test
public void test4(){
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
Stream的中间操作
-
筛选与切片
- filter(Predicate p):接收Lambda 从流中排除某些元素。
- distinct( ):筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素。
- limit(long maxSize):截断流,使其元素不超过给定数量。
- skip(long n):跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足 n 个,则返回一个空流。与limit(n) 互补。
-
映射
- map(Function f ):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
- mapToDouble( ):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream
- mapToInt(ToIntFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream 。
- mapToLong(ToLongFunction f ):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
- flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
-
排序
- sorted( ):产生一个新流,其中按自然顺序排序。
- sorted( ):产生一个新流,其中按比较器顺序排序。
Stream的终止操作
-
匹配和查找
- allMatch(Predicate p):检查是否匹配所有元素。
- anyMatch(Predicate p):检查是否至少匹配一个元素。
- noneMatch(Predicate p ):检查是否没有匹配到元素。
- findFirst( ):返回第一个元素。
- findAny( ):返回当前流中的任意元素。
- count( ):返回流中元素总数。
- max(Comparator c):返回流中最大值。
- min(Comparator c):返回流中最小值。
- ForEach(Consumer c):内部迭代。
-
归约
- reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回 T
- reduce(BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回 Optional
-
收集
- collect(Collector c):将流转换为其他形式。接收一个Collector接口的实现,用于给 Stream 中元素做汇总的方法。
- Collector接口中方法的实现决定了如何对流执行收集的操作 (如收集到 List 、 Set 、Map)
5、Optional类
Optional类的理解:
- 理解:为了解决java中的空指针问题而生。
- Optional 类 (java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
常用方法
- Optional.of(T t) : 创建一个 Optional 实例, t 必须非空
- Optional.empty() : 创建一个空的 Optional 实例
- Optional.ofNullable(T t) t 可以为 null
- boolean isPresent() : 判断是否包含对象
- void ifPresent(Consumer<? super T> consumer) 如果有值,就执行 Consumer接口的实现代码,并且该值会作为参数传给它。
- T get(): 如果调用对象包含值,返回该值,否则抛异常
- T orElse(T other) 如果有值则将其返回,否则返回指定的 other 对象。
- T orElseGet(Supplier<? extends T> other) 如果有值则将其返回,否则返回由Supplier 接口实现提供的对象。
- T orElseThrow(Supplier<? extends X> exceptionSupplier) 如果有值则将其返
回,否则抛出由 Supplier 接口实现提供的异常