八、多线程
8.1 程序、进程、线程的理解
8.1.1 程序(program)
概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
8.1.2 进程(process)
概念: 是程序的一次执行过程,或是正在运行的一个程序。
说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
8.1.3 线程(thread)
概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。
说明: 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
【进程可以细化为多个线程。每个线程,拥有自己独立的:栈、程序计数器多个线程,共享同一个进程中的结构:方法区、堆。】
8.2 并行与并发
8.2.1 单核CPU和多核CPU的理解
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
8.2.2 并行与并发的理解
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
8.3 创建多线程的两种方式
8.3.1 方式一:继承Thread类的方式
说明的两个问题:
问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象调用start()。
package nianwuyin.java;
/**
* 多线程的创建,方式一:继承于Thread类
* 1. 创建一个继承于Thread的子类
* 2. 重写Thread类的run() ----> 将此线程执行的操作声明在run()中
* 3. 创建Thread类的子类的对象
* 4. 通过此对象调用start()
* <p>
* 例子:遍历100以内的所有偶数
*
* @author nianwuyin
* @create 2021-11-04 11:22 AM
*/
//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
//2. 重写Thread类的run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建子类的对象
MyThread t1 = new MyThread();
//4. 通过此对象调用start():① 启动当前线程 ② 调用当前线程的run()
t1.start();
//问题一:我们不能通过直接调用run()的方式启动线程。
// t1.run();
//问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start的线程去执行。会报IllegalThreadStateException
// t1.start();
//需要重新创建一个线程的对象
MyThread t2 = new MyThread();
t2.start();
//如下的操作仍然是在main线程中执行的
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i + "*****main******");
}
}
}
}
8.3.2 方式二:实现Runnable接口的方式
package nianwuyin.java;
/**
* 创建多线程的方式二:实现Runnable接口
* 1. 创建一个实现了Runnable接口的类
* 2. 实现类去实现Runnable中的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
* @author nianwuyin
* @create 2021-11-05 9:41 AM
*/
//1. 创建一个实现了Runnable接口的类
class MThread implements Runnable {
//2. 实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3. 创建实现类的对象
MThread mThread = new MThread();
//4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
Thread t2 = new Thread(mThread);
//5. 通过Thread类的对象调用start():①启动线程 ② 调用当前线程的run() --->调用了Runable类型的target的run()方法
t1.setName("线程一");
t1.start();
//再启动一个线程,遍历100以内的偶数
t2.setName("线程二");
t2.start();
}
}
8.3.3 两种方式的对比
开发中优先选择:实现Runnable接口的方式
原因:
1. 实现的方式没有类的单继承性的局限性
2. 实现的方式更适合于多个线程有共享数据的情况
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中
8.4 Thread类中的常用方法:
package nianwuyin.java;
/**
* 测试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();判断当前线程是否存活
*
* 线程的优先级:
* 1.
* MAX_PRIORITY:10
* MIN _PRIORITY:1
* NORM_PRIORITY:5 --->默认优先级
* 2.如何获取和设置当前线程的优先级:
* getPriority():
* setPriority(int p):
*
* 说明:高优先级的线程要抢占低优先级cpu的执行权。但是只是从概率上来讲,高概率的线程高概率的情况下被执行
* 并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
*
* @author nianwuyin
* @create 2021-11-04 12:37 PM
*/
class HelloThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
// try {
// sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
// if(i%20==0){
// yield();
// this.yield();
// Thread.currentThread().yield();
}
}
public HelloThread(String name) {
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
HelloThread h1 = new HelloThread("Thread: 1");
// h1.setName("线程一");
//设置分线程的优先级
h1.setPriority(Thread.MAX_PRIORITY);
h1.start();
//给主线程命名
Thread.currentThread().setName("主线程");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
if (i == 20) {
try {
h1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// System.out.println(h1.isAlive());
}
}
【补充】线程的分类:守护线程,用户线程
8.5 Thread的生命周期
说明:
1.生命周期关注两个概念:状态、方法
2.关注:状态a--->状态b:哪些方法执行了(回调方法)
某个方法主动调用:状态a--->状态b
3.阻塞:临时状态,不可以作为最终状态
死亡:最终状态
8.6 线程的同步机制
8.6.1 背景
* 例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
*
* 1. 问题:买票过程中,出现了重票、错票 --- >出现了线程的安全问题
* 2. 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与近来,也操作车票。
* 3. 如何解决:当一个线程a在操作ticket的时候,其他线不能参与进来。直到线程a操作完ticket时,其他线程才可以参与进来。
* 这种情况即使线程a出现了阻塞,也不能被改变。
8.6.2 Java解决方案:同步机制
* 方式一:同步代码块
*
* synchronized(同步监视器){
* //需要被同步的代码
*
* }
* 说明:1.操作共享数据的代码,即为需要被同步的代码 ---> 不能多包含代码,也不能少包含代码
* 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
* 3.同步监视器:俗称:锁。任何一个类的对象,都可以充当锁。
* 要求:多个线程必须共用同一把锁。
*
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑this充当同步监视器。
* 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器。考虑使用当前类当同步监视器
*
* 方式二:同步方法
* 如果操作共享数据的代码完整地声明在一个方法中,不妨将此方法声明为同步的。
* 关于同步方法的总结:
* 1. 同步方法仍然设计到同步监视器,只是不需要我们显示地声明。
* 2.
* 非静态的同步方法,同步监视器是:this
* 静态的同步方法,同步监视器是:当前类本身
*
*
* 方式三:Lock锁 --- JDK 5.0 新增
*
* 优先使用顺序
* Lock--->同步代码块(已经进入了方法体,分配了相应资源)--->同步方法(在方法体之外)
*
* 同步的方式,解决了线程的安全问题。----好处
* 操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。---局限性
package nianwuyin.java1;
import java.util.concurrent.locks.ReentrantLock;
/**
* 解决线程安全问题的方式三:Lock锁 --- JDK 5.0 新增
* @author nianwuyin
* @create 2021-11-05 3:31 PM
*/
class Window implements Runnable {
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try{
//2.调用锁定方法:lock()
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁方法:unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("Window 1");
t2.setName("Window 2");
t3.setName("Window 3");
t1.start();
t2.start();
t3.start();
}
}
8.6.3 面试题
1. Java是如何解决线程安全的问题的,有几种方式?并对比几种方式的不同
2.synchronized和Lock方式解决线程安全问题的对比
同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的代码逻辑以后,自动地释放同步监视器
Lock需要手动地启动同步(lock()),同时结束同步也需要手动实现(unlock())
8.6.4 线程安全的单例模式(懒汉式)
package nianwuyin.java1;
/**
*
* 使用同步机制将单例模式中的懒汉式改写为线程安全的
*
* @author nianwuyin
* @create 2021-11-05 2:54 PM
*/
public class BnakTest {
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
//方式一:效率稍差
// synchronized (Bank.class) {
// if(instance == null){
//
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率稍高
if(instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
8.6.5 死锁问题
package nianwuyin.java1;
/**
*
* 演示线程的死锁问题
*
* 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
*
* 2.说明:
* 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
* 2)我们使用同步时,要避免死锁。
*
* @author nianwuyin
* @create 2021-11-05 3:11 PM
*/
public class ThreadTest {
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();
}
}
package nianwuyin.java1;
//死锁的演示
class A {
public synchronized void foo(B b) {//同步监视器:a
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了A实例的foo方法"); // ①
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用B实例的last方法"); // ③
b.last();
}
public synchronized void last() {
System.out.println("进入了A类的last方法内部");
}//同步监视器:a
}
class B {
public synchronized void bar(A a) {//同步监视器:b
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了B实例的bar方法"); // ②
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用A实例的last方法"); // ④
a.last();
}
public synchronized void last() {
System.out.println("进入了B类的last方法内部");
}//同步监视器:b
}
public class DeadLock implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("主线程");
// 调用a对象的foo方法
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run() {
Thread.currentThread().setName("副线程");
// 调用b对象的bar方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
DeadLock dl = new DeadLock();
new Thread(dl).start();
dl.init();
}
}
8.7 线程通信
8.7.1 线程通信设计到的三个方法
* 涉及到的三个方法:
* wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器。
* notify():一旦执行此方法,唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个
* notifyAll():一旦执行此方法,就会唤醒所有的线程
8.7.2 说明
* 注意点:
* 1.wait()、 notify()、notifyAll()这三个方法必须使用在同步代码块或同步方法中。
* 2.wait()、 notify()、notifyAll()这三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
* 否则会出现IllegalMonitorStateException异常
* 3.wait()、 notify()、notifyAll()这三个方法是定义在java.lang.Object类中。
8.7.3 面试题
* 面试题:sleep()和wait()的异同?
* 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
* 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
* 2)调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中
* 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep不会释放锁,wait会释放锁
8.7.4 释放锁的操作和不会释放锁的操作:
8.8 JDK 5.0新增的创建方式
8.8.1 新增的方式一:实现Callable接口
package nianwuyin.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增
*
* 如何理解Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大
* 1. call方法可以有返回值的。
* 2. call方法可以抛出异常,被外面的操作捕获,获取异常的信息
* 3. Callable是支持泛型的
* @author nianwuyin
* @create 2021-11-05 5:24 PM
*/
//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方法返回值即为FutureTas构造器参数Callable实现类重写call方法的返回值
Object sum = futureTask.get();
System.out.println("Total:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
8.8.2 新增方式二:使用线程池
package nianwuyin.java2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
*
* 创建线程的方式四:使用线程池
*
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
* @author nianwuyin
* @create 2021-11-05 6:39 PM
*/
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
//关闭连接池
service.shutdown();
}
}
8.8.3 面试题:创建多线程有几种方式?
九、Java常用类
9.1 String类
9.1.1 概述
String:字符串,使用一对""引起来表示。
1.String声明为final的,不可以被继承
2.String实现了Serializable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示String可以比较大小
3.String内部定义了final char[] value用于储存字符串数据
4.通过字面量的方式(区别于new方式)给一个字符串赋值,此时的字符串值声明在字符串的常量池中。
5.字符串常量池中是不会存储相同内容的字符串的。
9.1.2 String的不可变性
String代表一个不可变的字符序列。简称:不可变性。体现:
1.当对字符串重新赋值时,需要重新指定内存区域赋值,不能在原有的value进行赋值。
2.当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value赋值。
3.当调用String的replace方法修改指定字符或字符串时,也必须重新指定内存区域赋值,不能使用原有的value赋值。
@Test
public void test1(){
String s1 = "abc";//字面量的定义方式
String s2 = "abc";
s1 = "hello";
System.out.println(s1 == s2);//比较s1和s2的地址值
System.out.println(s1);//hello
System.out.println(s2);//abc
System.out.println("**************");
String s3 = "abc";
s3 += "def";
System.out.println(s3);//abcdef
System.out.println(s2);//abc
System.out.println("**************");
String s4 = "abc";
String s5 = s4.replace('a', 'm');
System.out.println(s4);//abc
System.out.println(s5);//mbc
}
9.1.3 String实例化的不同方式
/*
String的实例化方式:
方式一:通过字面量定义的方式
方式二:通过new+构造器的方式
面试题:String s = new String("abc");方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"
*/
@Test
public void test2(){
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过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
System.out.println("**************");
Person p1 = new Person("Tom",12);
Person p2 = new Person("Tom",12);
System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.name == p2.name);//true
p1.name = "Jerry";
System.out.println(p2.name);//Tom
}
9.1.4 字符串拼接方式赋值的对比
/*
结论:
1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
2.只要其中有一个是变量,结果就在堆中
3.如果拼接的结果调用intern()方法,返回值就在常量池中
*/
@Test
public void test4(){
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//false
final String s4 = "javaEE";
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true
}
@Test
public void test3(){
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 = s5.intern();//返回得到的s8使用的是常量池中已经存在的"javaEEhadoop"
System.out.println(s3 == s8);//true
}
9.1.5 String常用方法
public class StringMethodTest {
/*
替换
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个,
如果超过了,剩下的全部都放到最后一个元素中。
*/
@Test
public void test4() {
String s1 = "北京尚硅谷教育北京";
String s2 = s1.replace("北", "南");
System.out.println(s1);
System.out.println(s2);
String s3 = s1.replace("北京", "上海");
System.out.println(s3);
String str = "12hello34world5java7891mysql456";
//把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
System.out.println(string);
str = "12345";
//判断str字符串中是否全部有数字组成,即有1-n个数字组成boolean matches = str.matches("\\d+");
boolean matches = str.matches("\\d+");
System.out.println(matches);
String tel = "0571-4534289";
//判断这是否是一个杭州的固定电话
boolean result = tel.matches("0571-\\d{7,8}");
System.out.println(result);
str = "hello|world|java";
String[] strs = str.split("\\|");
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
System.out.println();
String str2 = "hello.world.java";
String[] strs2 = str2.split("\\.");
for (int i = 0; i < strs2.length; i++) {
System.out.println(strs2[i]);
}
}
/*
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
*/
@Test
public void test3() {
String str1 = "helloword";
boolean b1 = str1.endsWith("ld");
System.out.println(b1);
boolean b2 = str1.startsWith("he");
System.out.println(b2);
boolean b3 = str1.startsWith("ll", 2);
System.out.println(b3);
String str2 = "wo";
boolean b4 = str1.contains(str2);
System.out.println(b4);
System.out.println(str1.indexOf("lo"));
System.out.println(str1.indexOf("lo", 5));
String str3 = "hellorworld";
System.out.println(str1.lastIndexOf("or"));
System.out.println(str1.lastIndexOf("or", 6));
//什么情况下,indexOf()和lastIndexOf()返回值相同
//情况一:存在唯一的str。情况二:不存在str
}
/*
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(不包含)的一个子字符串。
*/
@Test
public void test2() {
String s1 = "HelloWorld";
String s2 = "helloworld";
System.out.println(s1.equals(s2));
System.out.println(s1.equalsIgnoreCase(s2));
String s3 = "abc";
String s4 = s3.concat("def");
System.out.println(s4);
String s5 = "abc";
String s6 = new String("cba");
System.out.println(s5.compareTo(s6));//涉及到字符串排序
String s7 = "北京尚硅谷教育";
String s8 = s7.substring(2);
System.out.println(s7);
System.out.println(s8);
String s9 = s7.substring(2, 5);
System.out.println(s9);
}
@Test
public void test1() {
String s1 = "HelloWorld";
System.out.println(s1.length());
System.out.println(s1.charAt(0));
System.out.println(s1.charAt(9));
// System.out.println(s1.charAt(10));
System.out.println(s1.isEmpty());
String s2 = s1.toLowerCase();
System.out.println(s1);//s1是不可变的,仍然为原来的字符串
System.out.println(s2);//改成小写以后的字符串
String s3 = " he llo world ";
String s4 = s3.trim();
System.out.println("---------" + s3 + "---------");
System.out.println("---------" + s4 + "---------");
}
}
9.1.6 String和其他数据类型的转换
package nianwuyin.java;
import org.junit.Test;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
/**
* 涉及到String类与其他结构之间的转换
* @author nianwuyin
* @create 2021-11-07 7:23 PM
*/
public class StringTest1 {
/*
String ---> StringBuffer、StringBuilder:调用StringBuffer、StringBuilder的构造器
StringBuffer、StringBuilder ---> String:调用String的构造器 / StringBuffer、
StringBuilder的toString方法
*/
/*
String 与 byte[]之间的转换
String ---> byte[]:调用String的getBytes()
byte[] ---> String:调用String的构造器
编码:字符串 ---> 字节
解码:字节 ---> 字符串
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
*/
@Test
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();//使用默认的字符集,进行转换
System.out.println(Arrays.toString(bytes));
byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码
System.out.println(Arrays.toString(gbks));
String str2 = new String(bytes);//使用默认的字符集,进行解码。
System.out.println(str2);
String str3 = new String(gbks);
System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!
String str4 = new String(gbks, "gbk");
System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!
}
/*
String 与 char[]之间的转换
String ---> char[]:调用String的toCharArray()
char[] ---> String:调用String的构造器
*/
@Test
public void test2(){
String str1 = "abc123";// 题目:a21cb3
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.println(charArray[i]);
}
char[] arr = new char[]{'h','e','l','l','o'};
String str2 = new String(arr);
System.out.println(str2);
}
/*
复习
String与基本数据类型、包装类之间的转换。
String --> 基本数据类型,包装类:调用包装类的静态方法:parseXxx(str)
基本数据类型,包装类 --> String:调用String重载的ValueOf(xxx)
*/
@Test
public void test1(){
String str1 = "123";
// int num = (int)str1;错误的
int num = Integer.parseInt(str1);
String str2 = String.valueOf(num);
String str3 = num + "";
System.out.println(str1 == str3);//false
}
}
9.1.7 JVM字符中常量池存放位置说明:
jdk 1.6:字符串常量池存储在方法区(永久区)
jdk 1.7:字符串常量池存储在堆空间
jdk 1.8:字符串常量池存储在方法区(元空间)
9.2 String、String Buffer和StringBuilder
package nianwuyin.java;
import org.junit.Test;
/**
* 关于StringBuffer和StringBuilder的使用
*
* @author nianwuyin
* @create 2021-11-07 9:14 PM
*/
public class StringBufferBuilderTest {
/*
对比String、StringBuffer、StringBuilder三者的效率
从高到低排列:StringBuilder > StringBuffer > String
*/
@Test
public void test3() {
// 初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));
}
/*
StringBuffer的常用方法
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的字符串
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
总结:
增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length()
*遍历:for + charAt() / toString()
*/
@Test
public void test2() {
StringBuffer s1 = new StringBuffer("abc");
s1.append(1);
s1.append('1');
System.out.println(s1);
// s1.delete(2,4);
// s1.replace(2,4,"hello");
// s1.insert(2,false);
// s1.reverse();
String s2 = s1.substring(1, 3);
System.out.println(s1);
System.out.println(s2);
}
/*
String、StringBuffer、StringBuilder三者的异同?
String:不可变的字符序列;底层使用char[]存储
StringBuffer:可变的字符序列;线程安全的,效率偏低;底层使用char[]存储
StringBuilder:可变的字符序列;jdk5.0新增,线程不安全的,效率高;底层使用char[]存储
源码分析:
String str = new String();//new char[0];
String str1 = new String("abc");//new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();//new char[16];底层创建了一个长度是16的数组
.System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';
StringBuffer sb2 = StringBuffer("abc");//char value = new char["abc".length() + 16];
//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容地底层的数组。
默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。
指导意义:开放当中使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
*/
@Test
public void test1() {
StringBuffer sb1 = new StringBuffer("abc");
sb1.setCharAt(0, 'm');
System.out.println(sb1);
StringBuffer sb2 = new StringBuffer();
System.out.println(sb2.length());//0
}
}
9.3 JDK 8之前的日期时间API
9.3.1 java.util.Date类和java.sql.Date类
package nianwuyin.java;
import org.junit.Test;
import java.util.Date;
/**
* JDK之前日期和时间的API测试
*
* @author nianwuyin
* @create 2021-11-07 10:04 PM
*/
public class DateTimeTest {
/*
java.util.Date类
|---java.sql.Date类
1. 两个构造器的使用
2. 两个方法的使用
> toString():显示当前的时间年、月、日、时、分、秒
> getTime():获取当前Date对象对应的时间戳
3. java.sql.Date类对应数据库中日期类型的变量
> 如何实例化
> util.Date ---> sql.Date对象
*/
@Test
public void test2(){
//构造器一:Date():创建了一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//Sun Nov 07 22:13:58 CST 2021
System.out.println(date1.getTime());//1636294438317
//构造器二:创建了一个指定毫秒数的Date对象
Date date2 = new Date(1636238317);
System.out.println(date2.toString());
//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(942054547);
System.out.println(date3);//1970-01-12
//util.Date ---> sql.Date对象
//情况一
// Date date4 = new java.sql.Date(942054547);
// java.sql.Date date5 = (java.sql.Date) date4;
//情况二
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
}
//1. System类中currentTimeMillis()
@Test
public void test1() {
long time = System.currentTimeMillis();
//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
//时间戳
System.out.println(time);
}
}
9.3.2 SimpleDateFormat类的使用
/*
* SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析
* 1. 两个操作:
* 1.1 格式化:日期--->字符串
* 1.2 解析:格式化的逆过程,字符串--->日期
*
* 2. SimpleDateFormat的实例化
*/
@Test
public void test() throws ParseException {
//实例化SimpleDateFormat:使用默认的构造器
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化
Date date = new Date();
// System.out.println(date);
String format = sdf.format(date);
System.out.println(format);
//解析
String str = "11/12/21 2:32 PM";
Date date1 = sdf.parse(str);
System.out.println(date1);
//**************指定方式格式化:调用带参的构造器*******************
// SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
String format1 = sdf1.format(date);
System.out.println(format1);
//解析:要求字符串必须符合SimpleDateFormat识别的格式(通过构造器参数体现),否则抛异常
Date date2 = sdf1.parse("2021-11-12 02:37:15");
System.out.println(date2);
}
/*
练习一:字符串"2020-09-08"转换为java.sql.Date
练习二:“三天打鱼两天晒网” 1990-01-01 xxx-xx-xx 打渔?晒网
距离:2020-09-08 ? 总天数
总天数 % 5 == 1,2,3:打渔
总天数 % 5 == 4,0:晒网
总天数的计算?
方式一:(date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24) + 1
方式二:1990-01-01 ---> 2019-12-31 + 2020-01-01 ---> 2020-09-08
*/
@Test
public void testExer() throws ParseException {
String birth = "2020-09-08";
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf1.parse(birth);
// System.out.println(date);
java.sql.Date birthDate = new java.sql.Date(date.getTime());
System.out.println(birthDate);
}
/*
9.3.3 Calendar类的使用
/*
Calender日历类(抽象类)的使用
*/
@Test
public void testCalendar(){
//1.实例化
//方式一:创建其子类(GregorianCalendar)的对象
//方式二:调用其子类的方法getInstance()
Calendar calendar = Calendar.getInstance();
// System.out.println(calendar.getClass());
//2. 常用方法
//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
//set()
calendar.set(Calendar.DAY_OF_MONTH,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add
calendar.add(Calendar.DAY_OF_MONTH,3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//getTime:日历类 ---> Date
Date date = calendar.getTime();
System.out.println(date);
//setTime:Date ---> 日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
}
9.4 JDK8中新的时间日期API
9.4.1 日期时间API的迭代
第一代:jdk1.0 Date类
第二代:jdk1.1 Calendar类,一定程度上替换Date类
第三代:jdk1.8 提出了新的一套API
9.4.2 前两代存在的问题举例
可变性:像日期和时间这样的类应该是不可变的。
偏移性:Date中的年份是从1900开始的,而月份都从0开始。
格式化:格式化只对Date有用,Calendar则不行。
此外,它们也不是线程安全的;不能处理闰秒等。
9.4.3 java 8中新的日期时间API涉及到的包
9.4.4 LocalDate/LocalTime/LocalDateTime的使用:
【说明】
① 分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
② LocalDateTime的使用相较于LocalDate、LocalTime频率高
③ 类似于Calendar类
【常用方法】
9.4.5 Instant
【说明】
① 时间线上的一个瞬时点,概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。
② 类似于java.util.Date类
【常用方法】
9.4.6 时间日期格式化类:DateTimeFormatter
【说明】
① 格式化或解析日期、时间
② 类似于SimpleDateFormat类
【常用方法】
9.5 Java比较器
9.5.1 Java比较器的使用背景
Java的对象,正常情况下,只能进行比较:== 或 !=。不能使用< 或 >的,但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
9.5.2 使用Comparable接口——自然排序
【说明】
1. 像String、包装类等实现类Comparable接口,进而重写了compareTo(obj),给出了比较两个对象大小的方式
2. 像String、包装类重写了compareTo(obj)方法以后,进行了从小到大的排列
3. 重写compareTo(obj)规则: 如果当前对象this大于形参对象obj则返回正整数, 如果当前对象this小于形参对象obj则返回负整数, 如果当前对象this等于形参对象obj则返回零
4. 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj) 在compareTo(obj)方法中指明如何排序
【自定义类代码举例】
public class Goods implements Comparable {
private String name;
private double price;
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
//指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从高到低排序
@Override
public int compareTo(Object o) {
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);
}
throw new RuntimeException("传入的数据类型不一致!");
}
}
@Test
public void test2() {
Goods[] arr = new Goods[5];
arr[0] = new Goods("leneoMouse", 34);
arr[1] = new Goods("dellMouse", 43);
arr[2] = new Goods("xiaomiMouse", 12);
arr[3] = new Goods("huaweiMouse", 65);
arr[4] = new Goods("microsoftMouse", 43);
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
9.5.3 使用Comparator接口——定制排序
【说明】
1. 背景 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码, 或者实现了java.lang.Comparable接口的排序规则不适合当前的操作, 那么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排序的比较。
2. 重写compare(Object o1,Object o2)方法,比较o1和o2的大小: 如果方法返回正整数,则表示o1大于o2; 如果返回0,表示相等; 返回负整数,表示o1小于o2
【代码举例】
@Test
public void test3() {
String[] arr = new String[]{"AA", "CC", "kk", "MM", "GG", "JJ", "DD"};
Arrays.sort(arr, new Comparator() {
//按照字符串从大到小的顺序排列
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String) {
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
throw new RuntimeException("输入数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
@Test
public void test4() {
Goods[] arr = new Goods[6];
arr[0] = new Goods("leneoMouse", 34);
arr[1] = new Goods("dellMouse", 43);
arr[2] = new Goods("xiaomiMouse", 12);
arr[3] = new Goods("huaweiMouse", 65);
arr[4] = new Goods("huaweiMouse", 224);
arr[5] = new Goods("microsoftMouse", 43);
Arrays.sort(arr, 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("输入数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
9.5.4 两种接口方式的对比
Comparable接口的方式一旦确定,保证Comparable接口实现类的在任何位置都可以比较大小
Comparator接口属于临时性的比较。
9.6 其他类
9.6.1 System类
9.6.2 Math类
9.6.3 BigInteger和BigDecimal类
【说明】
① BigInteger 可以表示不可变的任意精度的整数
② 要求数字精度比较高,故用到 java.math.BigDecimal 类
【代码举例】
@Test
public void test2() {
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
// System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));
}
十、枚举类和注解
10.1 枚举类的使用
10.1.1 枚举类的说明
① 枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
② 当需要定义一组常量时,强烈建议使用枚举类
③ 如果枚举类中只有一个 对象,则可以作为单例模式的实现方式。
10.1.2 自定义枚举类步骤
//自定义枚举类
class Season {
//1. 声明Season对象的属性
private final String seasonName;
private final String seasonDesc;
//2. 私有化类的构造器,并给对象属性赋值
private Season(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3. 提供当前枚举类的多个对象;public static final
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("冬天", "冰天雪地");
//4. 其它诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//4. 其它诉求2:提供toString方法
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
10.1.3 jdk 5.0新增enum定义枚举类步骤
//使用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. 声明Season对象的属性
private final String seasonName;
private final String seasonDesc;
//3. 私有化类的构造器,并给对象属性赋值
private Season1(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4. 其它诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
10.1.4 Enum类的常用方法
public static void main(String[] args) {
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,返回枚举类中对象名是objName的对象。
//如果没有objName的枚举类对象,则抛异常:IllegalArgumentException
Season1 winter = Season1.valueOf("WINTER");
System.out.println(winter);
}
10.2 注解的使用
10.2.1 注解的理解
① jdk 5.0新增
② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
③ 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。 * 在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
框架 = 注解 + 反射机制 + 设计模式
10.2.2 注解的使用示例
示例一:生成文档相关的注解
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能
10.2.3 如何自定义注解:参照@SuppressWarinings去定义
① 注解声明为@interface
② 内部定义成员,通常使用value表示
③ 可以指定成员的默认值,使用default定义
④ 如果自定义注解没有成员,表明是一个标识作用。
【说明】
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通常都会注明两个元注解:Retention、Target
【代码举例】
@Inherited
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "hello";
}
10.2.4 元注解:对现有的注解进行解释说明的注解
Retention:指定该Annotation的生命周期:SOURCE、CLASS(默认行为)、RUNTIME(只有声明为RUNTIME生命周期的注解,才能通过反射获取)
Target:用于指定被修饰的Annotation用于修饰哪些程序元素
Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的Annotation将具有继承性
10.2.5 如何获取注解信息:通过反射获取注解信息
【前提】要求此注解的元注解Retention中声明的生命周期状态为:RUNTIME
10.2.6 JDK8中注解的新特性:
可重复注解:
① 在MyAnnotation上声明一个@Repeatabe,成员值为MyAnnotations.class
② MyAnnotation的Target和Retention等元注解与MyAnnotation相同。
类型注解:
① ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)
② ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
十一、Java集合
11.0 本章要求
① 选择合适的集合类去实现数据的保存,调用其内部的相关方法
② 不同的集合类底层的数据结构为何?如何实现数据的操作的:增删改查等
11.1 数组与集合
11.1.1 集合与数组存储数据的概述
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。 说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
11.1.2 数组存储的特点
① 一旦初始化以后,其长度就确定了。
② 数组一旦定义好,其元素的类型也就确定了。我们只能操作指定类型的数据了。
比如:String[] arr 、 int[] arr1
11.1.3 数组存储的弊端
① 一旦初始化以后,其长度就不可以修改
② 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
③ 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
④ 数组存储数据的特点:有序、可重复。对于无序,不可重复的需求,不能满足。
11.1.4 集合存储的优点
解决数组存储数据方面的弊端。
11.2 Collection接口
11.2.1 单列集合框架结构
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。-->“动态”数组
|----ArrayList、LinkedList、Vector
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet、LinkedHashSet、TreeSet
11.2.2 Collection接口常用方法
add(Object obj) / addAll(Collection coll) / size() / isEmpty() / clear()
contains(Object obj) / containsAll(Collection coll) / removeObject obj) / removeAll(Collection coll) / retainsAll(Collection coll) / equals(Object obj)
hashCode() / toArray / iterator()
11.2.3 Collection集合与数组间的转换
//集合--->数组:toArray
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//扩展:数组 ---> 集合?调用Arrays类的的静态方法asList(T ... t)
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);
List<int[]> arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2
11.2.4 使用Collection集合存储对象,对对象所属的类的要求
向Collection接口中实现类的对象中添加数据obj时,要求obj所在类要重写equals()。
11.3 Iterator接口与foreach循环
11.3.1 遍历Collection的两种方式
① 使用迭代器
② foreach循环(增强for循环)
11.3.2 java.utils包下定义的迭代器接口:Iterator
【说明】
terator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
【作用】遍历集合Collection元素
【如何获取实例】coll.iterator()返回一个迭代器的实例
【遍历代码的实现】
Iterator iterator = coll.iterator();
//hasNext():判断是否还有下一个元素
while (iterator.hasNext()){
//next():① 指针下移 ② 将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
【图示说明】
【remove()的使用】内部定义了remove()方法,可以在遍历的时候删除集合中的元素。此方法不同于集合直接调用remove()
//测试Iterator中的remove()
//如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法
//再调用remove都会报IllegalStateException。
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
//删除集合中的“Tom”
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
Object o = iterator.next();
if ("Tom".equals(o)){
iterator.remove();
}
}
//遍历
iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
11.3.3 jdk 5.0新特性---增强for循环(foreach循环)
【遍历集合举例】
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
//for(集合中元素的类型 局部变量 :集合对象)
//内部仍然调用了迭代器
for (Object obj : coll){
System.out.println(obj);
}
}
【遍历数组举例】
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//for(数组元素的类型 局部变量 :数组对象)
for (int i : arr){
System.out.println(i);
}
}
11.4 Collection子接口:List接口
11.4.1 存储的数据特点
存储有序的、可重复的数据
11.4.2 常用方法(记住)
增:add(Object obj)
删:remove(int index) remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:① Iterator迭代器方式 ② 增强for循环 ③ 普通的循环
11.4.3 常用实现类
|----Collection接口:单列集合,用来存储一个一个的对象
|----List接口:存储有序的、可重复的数据。-->“动态”数组,替换原有的数组
|----ArrayList:作为List的主要实现类,线程不安全的,效率高;底层使用 Object[] elementData存储
|----LinkedList:对于频繁插入和删除操作,使用此类效率比ArraysList高;底 层使用双向链表存储
|----Vector:作为List接口的古老实现类,线程安全的,效率低;底层使用 Object[]存储
11.4.4 源码分析(难点)
* 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);
* 【jdk 8】ArrayList的变化:
* ArrayList list = new ArrayList();//底层Object[]数组elementData初始化为{},并没有创建长度为10的数组
* list.add(123);//第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到elementData[0]
* ...
* 后续的添加和扩容操作与jdk 7无异。
* 【小结】:jdk7中ArrayList的对象的创建类似于单例的饿汉式,而jdk8的ArrayList的对象的创建类似于单例的懒汉式,
* 延迟了数组的创建,节省了内存。
*
* LinkedList的源码分析
* LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
* list.add(123);//将123封装到Node中,创建了Node对象。
*
* 其中,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的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组
* 在扩容方面,默认扩容为原来的数组长度的2倍。
11.4.5 存储的元素的要求
添加的对象所在的类要重写equals方法
11.4.6 面试题 ArrayList、LinkedList、Vector三者的异同?
同:三个类都实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
不同:见11.4.3和11.4.4
11.5 Collection子接口:Set接口
11.5.1 存储的数据特点:无序的、不可重复的元素
以HashSet为例说明:
① 无序性:不等于随机性。存储的数据在数组中并非按照数组索引的顺序添加。而是数据的哈希值决定的
② 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
11.5.2 元素添加过程(以HashSet为例)
我们向HashSet中添加元素a,首先调用元素a所在类的HashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组的存放位置(即为:相应的索引位置),判断数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。 --->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的哈希值:
如果哈希值不相同,则元素a添加成功。--->情况2
如果哈希值相同,进而调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。--->情况3
【说明】
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式进行存储。
jdk7:元素a放到数组中,指向原来的元素
jdk8:原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数据+链表的结构(前提:jdk 7)
11.5.3 常用方法
Set接口没有额外定义新的方法,使用的都是Collection中声明过的方法。
11.5.4 常用实现类
|----Collection接口:单列集合,用来存储一个一个的对象
|----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
|----HashSet:作为Set接口的主要实现类:线程不安全;可以存储null值
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加 的顺序去遍历; 在添加数据的同时,每个数据还维护了两个引用,记录此 数据前一个数据和后一个数据; 对于频繁的遍历操作,LinkedHashSet效率高于HashSet
|----TreeSet:可以按照添加对象的指定属性,进行排序。
11.5.5 存储对象所在类的要求
① 向Set(主要指:HashSet、LinkedHashSet)添加的数据,其所在的类一定要重写hashCode方法和equals方法
② 重写的HashCode方法和equals方法尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值
【TreeSet】
① 自然排序中,比较两个对象是否相同的标准为:compareTo方法返回0,不再是equals方法。
② 定制排序中,比较两个对象是否相同的标准为:compare方法返回0,不再是equals方法。
11.5.6 TreeSet的使用
【使用】
① 向TreeSet当中添加的数据,要求是相同类的对象。
② 两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator接口)
【代码举例】
@Test
public void test(){
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("Error");
}
}
};
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());
}
}
11.6 Map接口
11.6.1 常用实现类结构
|----Map接口:双列集合,用来存储一对(key-value)一对的数据 -->高中函数:y = f(x)
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;可以存储null的key或 value
|----LinkedHashMap:保证在遍历map元素时,按照添加的顺序实现遍历,原因:在原 有的HashMap底层结构基础上,添加了一对指针,指向前一个和 后一个元素。对于频繁地遍历操作,执行效率高于HashMap。
|----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时使用考虑key 的自然排序或者定制排序 ,底层使用的是红黑树
|----Hashtable:作为古老的实现类;线程安全的,效率低;不可以存储null的key或value
|----Properties:常用来处理配置文件。key和value都是String类型
【HashMap的底层】数组+链表 (jdk 7.0之前) / 数组+链表+红黑树 (jdk 8.0)
【面试题】
1. HashMap的底层实现原理?
2. HashMap和Hashtable的异同?
3. CurrentHashMap 与 Hashtable的异同?
11.6.2 存储结构的理解
① Map中的Key:无序的、不可重复的,使用Set存储所有key ---> key所在的类要重写equals方法和HashCode方法(以HashMap为例)
② Map中的value:无序的、可重复的,使用Collection存储所有的value ---> value所在的类要重写equals方法:一个键值对:key-value构成了一个Entry对象。
③ Map中的Entry:无序的、不可重复的,使用set存储所有的entry
11.6.3 常用方法
添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet()、values()、entrySet()
11.6.4 内存结构说明(难点)
【HashMap在jdk 7中的底层实现原理】
* Hash map = new HashMap():
* 在实例化以后,低层创建了长度是16的一维数组Entry[] table。
* ...可能已经执行过多次put...
* map.put(key1,value1):
* 首先,调用key1所在类的HashCode方法计算key1哈希值,此哈希值经过某种算法计算以后,得到Entry数组中的存放位置。
* 如果此位置上的数据为空,key1-value1添加成功。--- 情况1
* 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
* 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。---情况2
* 如果key1的哈希值与已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals方法,比较:
* 如果equals返回的是false:此时key1-value1添加成功。---情况3
* 如果equals返回的是true:使用value1替换value2。
*
* 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
*
* 在不断地添加的过程中,会涉及到扩容的问题,当超出临界值(且要存放位置非空)时,扩容。
* 默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
【jdk 8相较于jdk 7在底层实现方面的不同】
* 1. new HashMap():底层没有创建一个长度为16的数组
* 2. jdk 8底层的数组是:Node[],而非Entry[]
* 3. 首次调用put()方法时,底层创建长度为16的数组
* 4. jdk 7底层结构只有:数组+链表, jdk8中底层结构:数组+链表+红黑树。
* 当数组的某一个索引位置上的元素以链表形式存在的数据个数大于8且当前数组的长度大于64,
* 此时此索引位置上的所有数据改为使用红黑树存储。
【HashMap底层典型属性的属性说明】
* DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
* DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
* threshold:扩容的临界值,=容量*填充因子: 16 * 0.75 = 12
* REEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树: 8
* MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
【LinkedHashMap的底层实现原理】(了解)
LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap。区别就在于:LinkedHashMap提供了Entry,替换HashMap中的Node。
11.6.5 TreeMap的使用
public class TreeMapTest {
//向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
//因为要按照key进行排序:自然排序、定制排序
@Test
public void test(){
TreeMap map = new TreeMap();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object o = iterator1.next();
Map.Entry entry = (Map.Entry) o;
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
}
@Test
public void test1(){
TreeMap map = new TreeMap(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());
}
throw new RuntimeException("输入的类型不匹配");
}
});
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object o = iterator1.next();
Map.Entry entry = (Map.Entry) o;
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
}
}
11.6.6 Properties的使用
public class PropertiesTest {
//常用来处理配置文件。key和value都是String类型
public static void main(String[] args) {
FileInputStream fis = null;
try {
Properties pros = new Properties();
fis = new FileInputStream("jdbc.properties");
pros.load(fis);//加载流对应的文件
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println(name + "---" + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
11.7 Collections工具类
11.7.1 作用
Collections 是一个操作 Collection 和 Map 等集合的工具类
11.7.2 常用方法
11.7.3 面试题(Collection 和 Collections的区别)
十二、泛型
12.1 泛型的理解
12.1.1 概念
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
12.1.2 引入背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E> 这个<E>就是类型参数,即泛型。
12.2 泛型在集合上的使用
12.2.1 在集合使用泛型之前的例子
//在集合中使用泛型之前的情况
@Test
public void test1(){
ArrayList list = new ArrayList();
//需求:存放学生的成绩
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//问题一:类型不安全
list.add("Tom");
for (Object score : list){
//问题二:强转时,可能出现ClassCastException
int stuScore = (Integer) score;
System.out.println(stuScore);
}
}
【图示】
12.2.2 在集合中使用泛型的例子1
//在集合中使用泛型的情况:以ArrayList为例
@Test
public void test2(){
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//编译时,就会进行类型检查,保证数据的安全
// list.add("Tom");
//方式一:
// for (Integer score : list){
// //避免了强转操作
// int stuScore = score;
//
// System.out.println(stuScore);
// }
//方式二
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
Integer stuScore = iterator.next();
System.out.println(stuScore);
}
}
【图示】
12.2.3 在集合中使用泛型的例子2
//在集合中使用泛型的情况:以HashMap为例
@Test
public void test3(){
// Map<String, Integer> map = new HashMap<String, Integer>();
//jdk 7新特性:类型推断
Map<String, Integer> map = new HashMap<>();
map.put("Tom",87);
map.put("Jerry",87);
map.put("Too",67);
// map.put(123,"ABC");
//泛型嵌套
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entrySet.iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + value);
}
}
12.2.4 在集合中使用泛型总结
* ① 集合接口或集合类jdk 5.0时都修改为带泛型的结构
* ② 在实例化集合类时,可以指明的泛型类型
* ③ 指明完以后,在集合类或接口中,凡是定义类或接口时,
* 内部结构使用到类的泛型的位置时,都指定为实例化化时的泛型类型
* 比如:add(E e) ---> 实例化以后:add(Integer e)
* ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到时,拿包装类替换
* ⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.。 Object类型
12.3 自定义泛型类、泛型接口、泛型方法
12.3.1 举例
【Order.java】
public class Order<T> {
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;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
//静态方法中不能使用类的泛型
// public static void show(T orderT){
// System.out.println(orderT);
// }
public void show(){
//编译不通过
// try{
//
// }catch(T t){
//
// }
}
//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系
//换句话说,泛型的方法所属的类是不是泛型类都没有关系
//泛型方法可以设置为静态的。原因:泛型参数是在调用方法时确定的,并非实例化时确定的
public <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e : arr){
list.add(e);
}
return list;
}
}
【SubOrder.java】
实例化时,如下代码是错误的:
SubOrder<Integer> o = new SubOrder<>();
public class SubOrder extends Order<Integer> {//SubOrder:不再是泛型类
}
【SubOrder1.java】
public class SubOrder1<T> extends Order<T>{//SubOrder1<T>:仍然是泛型类
}
【测试】
public class GenericTest1 {
@Test
public void test1(){
//如果定义了泛型类,实例化没有指明类的泛型,则认为泛型类型为Object类型
//要去,如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
Order order = new Order();
order.setOrderT(123);
order.setOrderT("ABC");
//建议:实例化时指明类的泛型
Order<String> order1 = new Order<String>("AA",1001,"order:AA");
order1.setOrderT("AA:Hello");
}
@Test
public void test2(){
SubOrder sub1 = new SubOrder();
//由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
sub1.setOrderT(1122);
SubOrder1<String> sub2 = new SubOrder1<>();
sub2.setOrderT("order2");
}
@Test
public void test3(){
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<Integer>();
//泛型不同的引用不能相互赋值
// lis1 = list2;
Person p1 = null;
Person p2 = null;
p1 = p2;
}
//测试泛型方法
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4};
//泛型方法在调用时,指明泛型参数的类型
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
}
}
12.3.2 注意点
12.3.3 应用场景举例
【DAO.java】
public class DAO <T>{//表的共性操作的DAO
//添加一条记录
public void add(T t){
}
//删除一条记录
public boolean remove(int index){
return false;
}
//修改一条记录
public void update(int index,T t){
}
//查询一条记录
public T getIndex(int index){
return null;
}
//查询多条记录
public List<T> getForList(int index){
return null;
}
//泛型方法
//举例:获取表中一共有多少条记录?获取最大的员工入职时间?
public <E> E getValue(){
return null;
}
}
【CustomerDAO.java】
public class CustomerDAO extends DAO<Customer>{//只能操作某一个表的DAO
}
【StudentDAO.java】
public class StudentDAO extends DAO<Student> {//只能操作某一个表的DAO
}
12.4 泛型在继承上的体现
/*1. 泛型在继承方面的体现
虽然类A是类B的父类,但是G<A>和G<B>不具备子父类关系,二者是并列关系。
补充:类A是类B的父类,A<G>是B<G>的父类
*/
@Test
public void test1() {
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
//编译不通过
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//此时list1和list2不具有子父类关系
// list1 = list2;
//编译不通过
// Date date = new Date();
// str = date;
/*
反证法:
假设list1 = list2;
list1.add(123);导致混入非String的数据。
*/
show(list1);
show1(list2);
}
public void show1(List<String> list) {
}
public void show(List<Object> list) {
}
@Test
public void test2() {
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
List<String> list4 = new ArrayList<>();
}
12.5 通配符
/*
2. 通配符的使用
通配符:?
类A是类B的父类,G<A>是G<B>是没有关系的,二者共同的父类是G<?>
*/
@Test
public void test3() {
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
// print(list1);
// print(list2);
List<String> 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 next = iterator.next();
System.out.println(next);
}
}
/*
3.有限制条件的通配符的使用
? extends A:
G<? extends A> 是可以作为G<A>和G<B>的父类,其中B是A的子类
? super A:
G<? super A> 是可以作为G<A>和G<B>的父类,其中B是A的父类
*/
@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
list1 = list3;
list1 = list4;
// list1 = list5;
// list2 = list3;
list2 = list4;
list2 = list5;
//读取数据
list1 = list3;
Person person = list1.get(0);
//编译失败
//Student s = list1.get(0);
list2 = list4;
Object object = list2.get(0);
//写入数据
//编译不通过
// list1.add(new Student())
//编译通过
list2.add(new Student());
list2.add(new Person());
}
十三、IO流
13.1 File类的使用
13.1.1 File类的理解
* 1. File类的一个对象,代表一个文件或一个文件目录(文件夹)
* 2. File类声明在java.io包下
* 3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,
* 并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
* 4. 后续File类的对象常会作为参数传递到流的构造器中,指明读取或者写入的“终点”。
13.1.2 File类的实例化
/*
1. 如何创建File类的实例
public File(String pathname)
public File(String parent,String child)
public File(File parent,String child)
2.
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径
说明:IDEA中JUnit单元测试,相对路径即为当前Module,main()测试即为当前Project下。
Eclipse中相对路径都为当前的Project下。
3.路径分隔符
windows:\\
unix:/
*/
@Test
public void test1() {
//构造器1
File file1 = new File("hello.txt");//相对于当前module
File file2 = new File("D:\\CS\\code\\workplace_idea\\JavaSenior\\day08\\he.txt");
System.out.println(file1);
System.out.println(file2);
//构造器2
File file3 = new File("D:\\CS\\code\\workplace_idea", "JavaSenior");
System.out.println(file3);
//构造器3
File file4 = new File(file3, "hi.txt");
System.out.println(file4);
}
13.1.3 File类的常用方法
13.2 IO流的概述
13.2.1 流的分类
1.操作数据单位:字符流、字节流
2.数据流向:输入流、输出流
3.流角色:节点流、处理流
13.2.2 流的体系结构
13.2.3 重点说明的几个流结构
* 抽象基类 节点流(文件流) 缓冲流(处理流的一种)
* InputStream FileInputStream(read(byte[] buufer)) BufferedInputStream(read(byte[] buufer))
* OutputStream FileOutputStream(byte[] buffer, 0, len) BufferedOutputStream(byte[] buffer, 0, len) / flush()
* Reader FileReader(read(char[] cbuf)) BufferedReader(read(char[] cbuf)) / readline
* Writer FileWriter(write(char[] cbuf, 0, len) BufferedWriter(write(char[] cbuf, 0, len) / flush()
12.2.4 输入、输出的标准过程
【输入过程】
① 创建File类的对象,指明读取的数据来源。(要求此文件一定要存在)
② 创建相应的输入流,将File类的对象作为参数,传入流的构造器中
③ 具体的读入过程:
创建相应的byte[ ]或char[ ]
④ 关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理
【输出过程】
① 创建File类的对象,指明写出的数据位置。(不要求此文件一定要存在)
② 创建相应的输出流,将File类的对象作为参数,传入流的构造器中
③ 具体的写出过程:
write(char[ ] / byte[ ] buffer, 0, len)
④ 关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理
13.3 节点流(或文件流)
13.3.1 FileReader / FIleWriter的使用
【FileReader】
/*
将day09下的hello.txt文件内容读入程序中,并输出到控制台
说明点:
1. read()的理解:返回读入的一个字符,如果达到文件末尾,返回-1
2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
3. 读入的文件一定要存在,否则就会报FileNotFoundException
*/
@Test
public void testFileReader1() {
FileReader fr = null;
try {
File file = new File("hello.txt");
fr = new FileReader(file);
//read(char[] cubf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1){
//方式一
// for (int i = 0; i < len; i++) {
// System.out.print(cbuf[i]);
// }
//方式二
String s = new String(cbuf,0,len);
System.out.print(s);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fr != null){
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
【FIleWriter】
/*
从内存中写出数据到硬盘的文件里。
说明:
1. 输出的操作,对应的File可以不存在。并不会报异常
2. File中对应的文件如果不存在:在输出的过程中,会自动创建此文件。
File中对应的文件如果存在:
如果流使用的构造器是:FileWriter(file,false) / FileWriter(file),对原有文件的覆盖
如果流使用的构造器是:FileWriter(file,true),不会对原有文件覆盖,而是堆加内容
*/
@Test
public void testFileWriter() {
FileWriter fileWriter = null;
try {
//1.提供File类的对象,指明写出到的文件
File file = new File("hello1.txt");
//2.提供FileWriter对象,用于数据的写出
fileWriter = new FileWriter(file,false);
//3.写出的操作
fileWriter.write("I have a dream!\n");
fileWriter.write("you need to have a dream");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流资源的关闭
try {
if (fileWriter != null) {
fileWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
【文件的复制】
@Test
public void testFileReaderFileWriter() {
FileReader fileReader = null;
FileWriter fileWriter = null;
try {
//1.创建File类的对象,指明读入和写出的文件
File srcFile = new File("hello.txt");
File destFile = new File("hello2.txt");
//字符流不能处理图片
// File srcFile = new File("10.JPG");
// File destFile = new File("101.JPG");
//2.创建输入流和输出流的对象
fileReader = new FileReader(srcFile);
fileWriter = new FileWriter(destFile);
//3.数据的读入和写出操作
char[] cbuf = new char[5];
int len;//记录每次读入到cbuf数组中的字符个数
while ((len = fileReader.read(cbuf)) != -1){
//每次写入len个字符
fileWriter.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流资源
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fileWriter != null) {
fileWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
13.3.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 {
File srcFile = new File("10.JPG");
File destFile = new File("101.JPG");
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
byte[] buffer = new byte[5];
int len;
while ((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
13.4 缓冲流的使用
13.4.1 缓冲流设计到的类
* BufferedInputStream
* BufferedOutputStream
* BufferedReader
* BufferedWriter
13.4.2 作用
提高流的读取、写入的速度 。 提高速度的原因:内部提供了一个缓冲区
13.4.3 典型代码
【BufferedInputStream和BufferedOutputStream】:处理非文本文件
@Test
public void testBufferedStreamTest() throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1. 造文件
File srcFile = new File("10.JPG");
File destFile = new File("102.JPG");
//2. 造流
//2.1 造节点流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//2.2 造处理流(缓冲流)
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3. 复制细节:读取、写入
byte[] buffer = new byte[10];
int len;
while ((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
// bos.flush();//刷新缓存区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层流,再关闭内层流
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
//说明:关闭外层流的同时,内层流也会自动地进行关闭,关于内层流的关闭,我们可以省略
// fos.close();
// fis.close();
}
}
【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);
// }
//方式二: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 {
try {
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
13.5 转换流的使用
13.5.1 转换流涉及到的类
InputStreamReader:将一个字节的输入流,转换为字符的输入流
解码:字节、字节数组 ---> 字符数组、字符串
OutputStreamWriter:将一个字符的输出流,转换为字节的输出流
编码:字符数组、字符串 ---> 字节、字节数组
说明:编码决定了解码的方式
13.5.2 作用
提供字节流与字符流之间的转换
13.5.3 图示
13.5.4 典型实现
@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();
}
/*
综合使用InputStreamReader和OutputStreamWriter
*/
@Test
public void test2() throws IOException {
FileInputStream fis = new FileInputStream("dbcp.txt");
FileOutputStream fos = new FileOutputStream("dbcp_gbk.txt");
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
char[] cbuf = new char[20];
int len;
while ((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
osw.close();
isr.close();
}
13.5.5 编码表
客户端/浏览器端 <---> 后台(java,GO,Python,Node,js,php)<---> 数据库
要求前前后后使用的字符集都要统一:UTF-8
13.6 其他流的使用
13.6.1 标准的输入流输出流
System.in:标准的输入流,默认从键盘输入
System.out:标准的输出流,默认从控制台输出
System类的senIn(InputStream in) / setOut(PrintStream ps)方式重新指定输入输出的流。
13.6.2 打印流(PrintStream和PrintWriter)
作用:提供了一系列重载 的 print() 和 println() 方法 ,用于多种数据类型的输出
System.out 返回的是 PrintStream 的实例
13.6.3 数据流(DataInputStream 和 DataOutputStream)
作用:用于读取和写出 基本 数据类型、 String 类的数据
【示例代码】
/*
练习:将内存中的字符串、基本数据类型的变量写出到文件中。
注意:处理异常的话,仍然应该使用try-catch-finally
*/
@Test
public void test3() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("廿五寅");
dos.flush();//刷新操作,将内存中的数据写入文件
dos.writeInt(23);
dos.flush();
dos.writeBoolean(true);
dos.flush();
dos.close();
}
/*
将文件中储存的基本数据类型和字符串读取到内存中,保存在变量中
注意点:读取不同类型的顺序要与当初写入文件时保存的数据的顺序一致
*/
@Test
public void test4() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
String name = dis.readUTF();
int age = dis.readInt();
boolean isMale = dis.readBoolean();
System.out.println("name:" + name + ", age: " + age + ",isMale:"+ isMale);
dis.close();
}
13.7 对象流(ObjectInputStream 和 OjbectOutputSteam)
13.7.1 作用
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
ObjectInputStream:将磁盘文件中的对象还原为内存中的一个java对象
OjbectOutputSteam:将内存中的java对象保存到磁盘中或通过网络传输出去
13.7.2 对象的序列化机制
允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上, 或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
13.7.3 序列化及反序列化过程
/*
序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
使用ObjectOutputStream实现
*/
@Test
public void testObjectOutputStream(){
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("I love beijing!"));
oos.flush();//刷新操作
oos.writeObject(new Person("nianwuyin",23));
oos.flush();
oos.writeObject(new Person("hedaoyi",23,1011,new Account(5000)));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (oos != null){
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
反序列化:将磁盘文件中的对象还原为内存中的一个java对象
使用ObjectInputStream
*/
@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 {
try {
if (ois != null){
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
13.7.4 实现序列化的对象所需的类需要满足
* Person类需要满足如下要求方可序列化
* 1. 需要实现接口Serializable
* 2. 当前类提供一个全局常量:static final long serialVersionUID = xxxxxxxL;
* 3. 除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性也必须
* 是可序列化的。(默认情况下,基本数据类型可序列化)
*
* 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
13.8 RandomAccessFile的使用
13.8.1使用说明
* RandomAccessFile的使用
* 1. RandomAccessFile直接继承于java.lang.Object类,实现DataInput和DataOutput接口
* 2. RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
*
* 3. 如果RandomAccessFile作为输出流出现时,写出到的文件如果不存在,则在执行过程中自动创建
* 如果写出到的文件存在,则会对原有文件内容进行覆盖,默认情况下从头覆盖
* 4. 可以通过相关的操作,实现RandomAccessFile“插入数据”的效果
13.8.2 典型代码
@Test
public void test1() {
RandomAccessFile raf1 = null;
RandomAccessFile raf2 = null;
try {
raf1 = new RandomAccessFile(new File("10.JPG"), "r");
raf2 = new RandomAccessFile(new File("101.JPG"), "rw");
byte[] buffer = new byte[1024];
int len;
while ((len = raf1.read(buffer)) != -1) {
raf2.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (raf1 != null) {
raf1.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (raf2 != null) {
raf2.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test2() throws IOException {
RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw");
raf1.seek(3);//将指针调到角标为3的位置
raf1.write("xyz".getBytes());//覆盖数据的操作
raf1.close();
}
/*
使用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替换为ByteArrByteArrayOutputStream
}
13.9 Path、Paths、FIles的使用
13.9.1 NIO的使用说明
Java NIO (New IO Non Blocking IO) 是 从 Java 1.4 版本开始引入的 一 套 新的 IO API ,可以替代标准的 Java IO API 。 NIO 与原来的 IO 有同样的作用和目的,但是使用的方式完全不同, NIO 支持面向缓冲区的 (IO 是面向流的 、基于通道的 IO 操作。 NIO 将以更加高效的方式进行文件的读写操作。
13.9.2 Path的使用---jdk 7
【Path的说明】
Path替换原有的FIle类。
【如何实例化】
【常用方法】
13.9.3 Files工具类---jdk 7提供
【作用】操作文件或文件目录的工具类
【常用方法】
十四、网络编程
14.1 InetAddress类的使用
14.1.1 实现网络通信需要解决的两个问题
① 如何准确地定位网络上的一台或多台主机:定位主机上的特定的应用
② 找到主机后如何可靠高效地进行数据传输
14.1.2 网络通信的两个要素
① 对应问题一:提供IP和端口号
② 对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
14.1.3 通信要素之一:IP和端口号
【IP】
* 1. IP:唯一的标识Internet上的计算机(通信实体)
* 2. 在java中使用InetAddress类代表IP
* 3. IP分类:IPv4和IPv6;万维网 和 局域网
* 4. 域名:www.baidu.com www.mi.com www.sina.com www.jd.com
* www.vip.com
* 5. 本地回路地址:127.0.0.1 对应着localhost
【InetAddress类】此类的对象代表一个具体的IP地址
* 6. 如何实例化InetAddress:getByname(String host) / getLocoalHost
* 两个常用方法:getHostName() / getHostAddress
* 7. 端口号:正在计算机上运行的进程。
* 不同的进程有不同的端口号:0-655535
* 8. 端口号与IP地址的组合得出一个网络套接字:Socket
14.1.4 通信要素之二:网络通信协议
14.2 TCP网络编程
【代码示例1】
/**
* 实现TCP的网络编程
* 例子1:客户端发送信息给服务端,服务端将数据显示在控制台上
* @author nianwuyin
* @create 2021-11-22 9:15 PM
*/
public class TCPTest1 {
//客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
//1. 创建Socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet,8899);
//2. 获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3. 写出数据的操作
os.write("你好,我是客户端mm".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 资源的关闭
if (socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null){
try {
os.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[20];
// 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());
} catch (IOException e) {
e.printStackTrace();
} finally {
//5. 资源的关闭
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();
}
}
}
}
}
【代码示例2】
/**
* 2. 客户端发送文件给服务端,服务端将文件保存在本地
* @author nianwuyin
* @create 2021-11-22 9:43 PM
*/
public class TCPTest2 {
@Test
public void client() throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("10.JPG"));
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
os.close();
fis.close();
socket.close();
}
@Test
public void server() throws IOException {
ServerSocket serverSocket = new ServerSocket(9090);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("103.JPG"));
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
【代码示例3】
/**
* 例题3. 从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。
* @author nianwuyin
* @create 2021-11-22 9:58 PM
*/
public class TCPTest3 {
@Test
public void client() throws IOException {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("10.JPG"));
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//关闭数据的输出
socket.shutdownOutput();
//接收来自服务器端的反馈,显示到控制台上
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer1 = new byte[1024];
int len1;
while ((len1 = is.read(buffer1)) != -1){
baos.write(buffer1,0,len1);
}
System.out.println(baos.toString());
os.close();
fis.close();
socket.close();
baos.close();
is.close();
}
@Test
public void server() throws IOException {
ServerSocket serverSocket = new ServerSocket(9090);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("104.JPG"));
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
System.out.println("图片传输完成");
//服务器端给予客户端反馈
OutputStream os = socket.getOutputStream();
os.write("你好,图片已保存在本地".getBytes());
fos.close();
is.close();
socket.close();
serverSocket.close();
os.close();
}
}
14.3 UDP网络编程
/**
*
* UDPd协议的网络编程
* @author nianwuyin
* @create 2021-11-22 10:26 PM
*/
public class UDPTest {
@Test
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();
String str = "我是UDP发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getByName("127.0.0.1");
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();
}
}
14.4 URL编程
14.4.1 URL(Uniform Resource Locator)的理解
统一资源定位符,对应着互联网的某一资源地址
14.4.2 URL的5个基本结构
http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
协议 主机名 端口号 资源地址 参数列表
14.4.3 常用方法
public class URLTest {
public static void main(String[] args) {
try {
URL url = new URL("http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123");
// public String getProtocol()获取该URL的协议名
System.out.println(url.getProtocol());
// public String getHost()取该URL的主机名
System.out.println(url.getHost());
// public String getPort()取该URL的端口号
System.out.println(url.getPort());
// public String getPath()取该URL的文件路径
System.out.println(url.getPath());
// public String getFile()取该URL的文件名
System.out.println(url.getFile());
// public String getQuery()取该URL的查询名
System.out.println(url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
14.4.3 可以读取、下载对应的url资源
public class URLTest1 {
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();
}
}
}
}
十五、反射
15.1 反射的概述
15.1.1 本章内容
15.1.2 关于反射的理解
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
框架 = 反射 + 注解 + 设计模式
15.1.3 体会反射机制的“动态性”
//体会反射的动态性
@Test
public void test2() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
for (int i = 0; i < 99; i++) {
int num = new Random().nextInt(3);//0,1,3
String classPath = "";
switch (num) {
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "nianwuyin.java.Person";
break;
}
Object obj = getInstance(classPath);
System.out.println(obj);
}
}
//创建一个指定类的对象
//classPath:指定类的全类名
public Object getInstance(String classPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> clazz = Class.forName(classPath);
return clazz.newInstance();
}
15.1.4 反射机制能提供的功能
15.1.5 相关API
java.lang.Class:反射的源头
java.lang.reflect.Method
java.lang.reflect.Field
java.lang.reflect.Constructor
......
15.2 Class类的理解与获取Class的实例
15.2.1 Class类的理解
1. 类的加载过程:
程序在javac.exe命令后,会生成一个或多个字节码文件(.class结尾),接着我们使用java.exe命令对某个字节码文件进行解释运行。
相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的
一个实例。
2. 换句话说,Class的实例就对应着一个运行时类。
3. 加载到内存中的运行时类,会缓存一定的时间,在此时间内,我们可以通过不同的方式来获取此运行时类。
15.2.2 获取Class实例的几种方式
//获取Class实例的方式(前三种方式需要掌握)
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性: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("nianwuyin.java.Person");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
//方式四:使用类的加载器:ClassLoader(了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class<?> clazz4 = classLoader.loadClass("nianwuyin.java.Person");
System.out.println(clazz4);
System.out.println(clazz1 == clazz4);
}
15.2.3 总结:创建类的对象的方式
方式一: new + 构造器
方式二:要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类查看是否有相应的静态方法存在,可以调用其静态方法,创建Xxx的对象。
方式三:通过反射
15.2.4 Class实例可以是哪些结构的说明 ![](https://i-blog.csdnimg.cn/blog_migrate/2eaa8d1f80e88fdcad55045dd249bd9d.png)
15.3 了解ClassLoader
15.3.1 类的加载过程---了解
15.3.2 类的加载器的作用
15.3.3 类的加载器的分类
15.3.4 Java类编译、运行的执行的流程
15.3.5 使用ClassLoader加载src目录下的配置文件
/*
Properties:用来读取配置文件
*/
@Test
public void test2() throws IOException {
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);
}
15.4 反射应用一:创建运行时类的对象(newInstance( ))
@Test
public void test1() throws IllegalAccessException, InstantiationException {
Class clazz = Person.class;
/*
newInstance():调用此方法,创建对应的运行时类的对象,内部调用运行时类的空参构造器
要想此方法正常创建运行时类的对象,要求:
1. 运行时类必须提供空参的构造器
2. 空参的构造器的访问权限得够。通常,设置为public。
在javabean中要求提供一个public的空参构造器。原因:
1. 便于通过反射,创建运行时类的对象
2. 便于子类继承此运行类时,默认调用super()时,保证父类有此构造器
*/
Person obj = (Person) clazz.newInstance();
System.out.println(obj);
}
15.5 反射应用二:获取运行时类的完整结构
15.5.1 属性
@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 test2(){
Class clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields){
//1. 权限修饰符
int modifiers = f.getModifiers();
System.out.print(Modifier.toString(modifiers) + "\t\t");
//2. 数据类型
Class<?> type = f.getType();
System.out.print(type.getName() + "\t\t");
//3. 变量名
String name = f.getName();
System.out.print(name + "\t\t");
System.out.println();
}
}
15.5.2 方法
@Test
public void test1() {
Class<Person> 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);
}
}
/*
@Xxxx
权限修饰符 返回值类型 方法名(参数类型1 形参名1,。。。) throws XxxException{}
*/
@Test
public void test2() {
Class clazz = Person.class;
Method[] declaredFields = clazz.getDeclaredMethods();
for (Method m : declaredFields) {
//1. 注解
Annotation[] annotations = m.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a + "\t\t");
}
//2. 权限修饰符
int modifiers = m.getModifiers();
System.out.print(Modifier.toString(modifiers) + "\t\t");
//3. 返回值类型
Class<?> type = m.getReturnType();
System.out.print(type.getName() + "\t\t");
//4. 方法名
String name = m.getName();
System.out.print(name);
System.out.print("(");
//5. 形参列表
Class<?>[] parameterTypes = m.getParameterTypes();
if (!(parameterTypes == null && parameterTypes.length == 0)){
for (int i = 0; i < parameterTypes.length; i++) {
if (i == parameterTypes.length - 1) {
System.out.print(parameterTypes[i].getName() + " args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
}
}
System.out.print(")");
//6. 抛出的异常
Class<?>[] exceptionTypes = m.getExceptionTypes();
if (exceptionTypes.length > 0) {
System.out.print(" throws ");
for (int i = 0; i < exceptionTypes.length - 1; i++) {
if (i == exceptionTypes.length -1){
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ",");
}
}
System.out.println();
}
}
15.5.3 其他结构
/*
获取构造器结构
*/
@Test
public void test1() {
Class<Person> clazz = Person.class;
//getConstructor();获取当前运行时类中声明为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<Person> clazz = Person.class;
Class<? super Person> superclass = clazz.getSuperclass();
System.out.println(superclass);
}
/*
获取运行时类的带泛型的父类
*/
@Test
public void test3(){
Class<Person> clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
/*
获取运行时类的带泛型的父类的泛型
代码: 逻辑性代码 vs 功能性代码
*/
@Test
public void test4(){
Class<Person> 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<Person> 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<Person> clazz = Person.class;
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
}
/*
获取运行时类声明的注解
*/
@Test
public void test7(){
Class<Person> clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation a : annotations){
System.out.println(a);
}
}
15.6 反射应用三:调用运行时类的指定结构(掌握)
15.6.1 属性
/*
如何操作运行时类中指定的属性 -- 需要掌握
*/
@Test
public void testField1() throws IllegalAccessException, InstantiationException, NoSuchFieldException {
Class<Person> clazz = Person.class;
//创建运行时类的对象
Person p = 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));
}
15.6.2 方法
/*
如何操作运行时类中指定的方法 -- 需要掌握
*/
@Test
public void testMethod() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//创建运行时类的对象
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
/*
1. 获取指定的某个方法
getDeclaredMethod():参数1:指明获取方法的名称 参数2:形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2. 保证当前方法可访问
show.setAccessible(true);
//3. 调用方法的invoke():参数1:方法调用者 参数2:给方法形参赋值的实参
//invoke()的返回值即为对应类中调用方法的返回值
Object chn = show.invoke(p, "CHN");
System.out.println(chn);
System.out.println("*****************如何调用静态方法*************************");
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
Object returnVal = showDesc.invoke(null);
System.out.println(returnVal);//null
}
15.6.3 构造器
/*
如何调用运行时类中的指定的构造器
*/
@Test
public void testConstructor() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Person> clazz = Person.class;
//1. 获取指定的构造器
//getDeclaredConstructor():参数:指明构造器的参数列表
Constructor<Person> declaredConstructor = clazz.getDeclaredConstructor(String.class);
//2. 保证当前构造器可访问
declaredConstructor.setAccessible(true);
//3. 调用此构造器创建运行时类的对象
Person person = declaredConstructor.newInstance("Tom");
System.out.println(person);
}
15.7 反射应用四:动态代理
15.7.1 代理模式原理
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
15.7.2 静态代理
【举例】
实现Runnable接口的方法创建多线程
Class MyThread implements Runnable{}//相当于被代理类
Class Thread implements Runnable{}//相当于代理类
main(){
MyThread t = new Mythread();
Thread thread = new Thread(t)
thread.start();//启动线程,调用线程的run()
}
【缺点】
① 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展
② 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理
15.7.3 动态代理的特点
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
15.7.4 动态代理的实现(体现反射的动态性)
【需要解决的两个主要问题】
问题一:如何根据加载到内存中的被代理类,动态地创建一个代理类及其对象(通过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("==============通用方法1================");
}
public void method2(){
System.out.println("==============通用方法2================");
}
}
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时,就会自动地调用如下的方法
//将被代理类要执行的方法a功能声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil humanUtil = new HumanUtil();
humanUtil.method1();
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj, args);
humanUtil.method2();
//上述方法的返回值就作为当前类中的invoke()返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动地调用被代理类中同名的方法
System.out.println(proxyInstance.getBelief());
proxyInstance.eat("四川麻辣烫");
System.out.println("********************************************");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory procyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
procyClothFactory.produceCloth();
}
}
十六、Java 8的其他新特性
16.1 Java 8新特性概述
16.2 Lambda表达式
16.2.1 Lambda表达式使用前后对比
【举例一】
@Test
public void test1() {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("i love Beijing.");
}
};
r1.run();
System.out.println("***************************");
Runnable r2 = () -> System.out.println("i love Fujian.");
r2.run();
}
【举例二】
@Test
public void test2() {
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
int compare1 = com1.compare(12, 21);
System.out.println(compare1);
System.out.println("***************************");
//Lambda表达式的写法
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
int compare2 = com2.compare(32, 21);
System.out.println(compare2);
System.out.println("***************************");
//方法引用的写法
Comparator<Integer> com3 = Integer :: compare;
int compare3 = com3.compare(32, 21);
System.out.println(compare3);
}
16.2.2 Lambda表达式的基本语法
* Lambda表达式的使用
*
* 1. 举例:(o1,o2) -> Integer.compare(o1,o2);
* 2. 格式:
* ->:Lambda操作符 或箭头操作符
* ->左边:Lambda形参列表(其实就是接口中的抽象方法的形参列表)
* ->右边:Lambda体(其实就是重写的抽象方法的方法体)
16.2.3 六种使用情况
【总结】
->左边:Lambda形参列表的参数类型可以省略;如果Lambda形参列表只有一个参数,()可以省略
->右边:Lambda体应该使用一对{}包裹;如果Lambda体只有一条执行语句(可能是return语句), * 可以省略{ }以及return关键字
16.3 函数式接口
16.3.1 使用说明
> 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。
> 我们可以在一个接口上使用 FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口
> Lambda表达式的本质:作为函数式接口的一个实例
16.3.2 Java 8中关于Lambada表达式的4个基本的函数式接口
16.3.3 总结
【何时使用Lambda表达式】
当需要对一个函数式接口实例化的时候,可以使用lambda表达式
【何时使用给定的函数式接口】
如果我们开发中需要定义一个函数式接口,首先看看在已有的jdk提供的函数式接口是否提供了能满足需求的函数式接口。如果有,则直接调用即可,不需要自己再调用
16.4 方法引用
16.4.1 理解
方法引用可以看做是 Lambda 表达式深层次的表达。换句话说,方法 引用就是Lambda 表达式 ,也就是 函数式接口的一个实例,通过方法的名字来指向一个方法。
16.4.2 使用情境
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
16.4.3 格式
类(或对象) :: 方法名
16.4.4 分为如下三种情况
① 对象 :: 非静态方法
② 类 :: 静态方法
③ 类 :: 非静态方法
16.4.5 要求
要求接口中的抽象方法的形参列表和返回值类型与方法引用的形参列表和返回值类型相同!(针对情况1和情况2)
注意:当函数式接口方法的 第一个参数 是 需要引用方法的调用 者 ,并且第二个参数是需要引用方法的参数 或无参数 时: ClassName :: methodName(针对情况3)
16.4.6 使用建议
如果给函数式接口提供实例,恰好满足方法引用的使用情境,就可以考虑使用方法引用给函数式接口提供实例。不熟悉方法引用则可以使用Lambda表达式。
16.4.7 使用举例
// 情况一:对象 :: 实例方法
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1() {
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("北京");
System.out.println("*************************");
PrintStream ps = System.out;
Consumer<String> con2 = ps :: println;
con2.accept("beijing");
}
//Supplier中的T get()
//Employee中的String getName()
@Test
public void test2() {
Employee emp = new Employee(1001, "Tom", 23, 5600);
Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());
System.out.println("*************************");
Supplier<String> sup2 = emp :: getName;
System.out.println(sup2.get());
}
// 情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3() {
Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
System.out.println(com1.compare(12, 21));
System.out.println("*************************");
Comparator<Integer> com2 = Integer :: compare;
System.out.println(com2.compare(22, 12));
}
//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test4() {
Function<Double,Long> func1 = d -> Math.round(d);
System.out.println(func1.apply(21.1));
System.out.println("*************************");
Function<Double,Long> func2 = Math :: round;
System.out.println(func2.apply(12.3));
}
// 情况三:类 :: 实例方法 (有难度)
// Comparator中的int comapre(T t1,T t2)
// String中的int t1.compareTo(t2)
@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", "abm"));
}
//BiPredicate中的boolean test(T t1, T t2);
//String中的boolean t1.equals(t2)
@Test
public void test6() {
BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
System.out.println(pre1.test("abc", "abc"));
System.out.println("*************************");
BiPredicate<String,String> pre2 = String :: equals;
System.out.println(pre2.test("abc", "abc"));
}
// Function中的R apply(T t)
// Employee中的String getName();
@Test
public void test7() {
Employee emp = new Employee(1001, "Tom", 23, 5600);
Function<Employee,String> func1 = e -> e.getName();
System.out.println(func1.apply(emp));
System.out.println("*************************");
Function<Employee,String> func2 = Employee::getName;
System.out.println(func2.apply(emp));
}
16.5 构造器引用与数组引用
16.5.1 构造器引用
【格式】类名 :: new
【使用要求】和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型
【引用举例】
//构造器引用
//Supplier中的T get()
//Employee 空参构造器:Employee()
@Test
public void test1(){
Supplier<Employee> sup1 = () -> new Employee();
System.out.println(sup1.get());
System.out.println("***************************");
Supplier<Employee> sup2 = Employee::new;
System.out.println(sup2.get());
}
//Function中的R apply(T t)
@Test
public void test2(){
Function<Integer,Employee> func1 = id -> new Employee(id);
Employee e = func1.apply(1001);
System.out.println(e);
System.out.println("***************************");
Function<Integer,Employee> func2 = Employee :: new;
System.out.println(func2.apply(1002));
}
//BiFunction中的R apply(T t,U u)
@Test
public void test3(){
BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
System.out.println(func1.apply(1001, "nianwuyin"));
System.out.println("***************************");
BiFunction<Integer,String,Employee> func2 = Employee :: new;
System.out.println(func2.apply(1002, "hedaoyi"));
}
16.5.2 数组引用[ ]
【格式】数组类型[ ] :: new
【引用举例】
//数组引用
//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 = func1.apply(10);
System.out.println(Arrays.toString(arr2));
}
16.6 Stream API
16.6.1 Stream API的理解
Stream关注的是对数据的运算,与CPU打交道;集合关注的是数据的存储,与内存打交道;Java 8提供一套API可以对内存中的数据进行过滤、排序、映射、归约操作。类似于sql对数据库中表的操作。
【注意点】
① Stream 自己不会存储元素。
② Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream
③ Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
16.6.2 Stream API使用流程
① Stream的实例化 ② 一系列的中间操作(过滤、映射) ③ 终止操作
注意点: 一个中间操作链,对数据源的数据进行处理; 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
【Stream实例化】
//创建 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> employeeStream = employees.parallelStream();
}
//创建 Stream方式二:通过数组
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
// static <T> Stream<T> stream(T[] array): 返回一个流
IntStream stream = Arrays.stream(arr);
Employee e1 = new Employee(1001,"Tom");
Employee e2 = new Employee(1002,"Jerry");
Employee[] arr1 = new Employee[]{e1,e2};
Stream<Employee> stream1 = Arrays.stream(arr1);
}
//创建 Stream方式三:通过Stream的of()
@Test
public void tes3(){
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
}
//创建 Stream方式四:创建无限流
@Test
public void test4(){
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//遍历前十个偶数
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);
}
【中间操作】
//1 筛选与切片
@Test
public void test1() {
List<Employee> list = EmployeeData.getEmployees();
//filter(Predicate p):接收Lambda,从流中排除某些元素
Stream<Employee> stream = list.stream();
//练习:查询员工表中薪资大于7000的员工信息
stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);
System.out.println();
//limit(long maxSize):截断流,使元素不超过给定数量
Stream<Employee> stream1 = list.stream();
stream1.limit(3).forEach(System.out::println);
System.out.println();
//skip(long n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
list.stream().skip(3).forEach(System.out::println);
System.out.println();
//distinct():筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
list.add(new Employee(1010, "刘强东", 40, 8000));
list.add(new Employee(1010, "刘强东", 40, 8000));
list.add(new Employee(1010, "刘强东", 40, 8000));
list.add(new Employee(1010, "刘强东", 40, 8000));
list.add(new Employee(1010, "刘强东", 40, 8000));
list.stream().distinct().forEach(System.out::println);
}
//2 映射
@Test
public void test2() {
//map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
//练习1:获取员工姓名长度大于3的员工的姓名。
List<Employee> list1 = EmployeeData.getEmployees();
list1.stream().map(Employee::getName).filter(name -> name.length() > 3).forEach(System.out::println);
System.out.println();
//练习2:
Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
streamStream.forEach(s -> {
s.forEach(System.out::println);
});
System.out.println();
//flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
characterStream.forEach(System.out::println);
}
//将字符串中的多个字符构成的集合转换为对应的Stream的实例
public static Stream<Character> fromStringToStream(String str) {
ArrayList<Character> list = new ArrayList<>();
for (Character c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
@Test
public void test3() {
ArrayList list1 = new ArrayList();
list1.add(1);
list1.add(2);
list1.add(3);
ArrayList list2 = new ArrayList();
list2.add(4);
list2.add(5);
list2.add(6);
// list1.add(list2);
list1.addAll(list2);
System.out.println(list1);
}
//3 排序
@Test
public void test4() {
//sorted():自然排序
List<Integer> list = Arrays.asList(12, 45, 74, 34, 773);
list.stream().sorted().forEach(System.out::println);
//抛异常:原因:Employee类没有实现Comparable接口
// List<Employee> employees = EmployeeData.getEmployees();
// employees.stream().sorted().forEach(System.out :: println);
//sorted(Comparator com):定制排序
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted((e1, e2) -> {
int value = Integer.compare(e1.getAge(), e2.getAge());
if (value != 0) {
return value;
} else {
return Double.compare(e1.getSalary(),e2.getSalary());
}
}).forEach(System.out::println);
}
【终止操作】
//1 匹配与查找
@Test
public void test1() {
List<Employee> list = EmployeeData.getEmployees();
//allMatch(Predicate p)——检查是否匹配所有元素。
//练习:是否所有的员工的年龄都大于18
boolean allMatch = list.stream().allMatch(e -> e.getAge() > 18);
System.out.println(allMatch);
//anyMatch(Predicate p)——检查是否至少匹配一个元素。
//练习:是否存在员工的工资大于 10000
System.out.println(list.stream().anyMatch(e -> e.getSalary() > 10000));
//noneMatch(Predicate p)——检查是否没有匹配的元素。
//练习:是否存在员工姓“雷”
System.out.println(list.stream().noneMatch(e -> e.getName().startsWith("雷")));
//findFirst——返回第一个元素
System.out.println(list.parallelStream().findFirst());
//findAny——返回当前流中的任意元素
System.out.println(list.stream().findAny());
}
@Test
public void test2(){
List<Employee> list = EmployeeData.getEmployees();
//count——返回流中元素的总个数
long count = list.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);
//max(Comparator c)——返回流中最大值
//练习:返回最高的工资:
Stream<Double> salaryStream = list.stream().map(e -> e.getSalary());
System.out.println(salaryStream.max((Double::compare)));
//min(Comparator c)——返回流中最小值
//练习:返回最低工资的员工
Optional<Employee> employee = list.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(employee);
//forEach(Consumer c)——内部迭代
list.stream().forEach(System.out :: println);
//使用集合的遍历操作
list.forEach(System.out :: println);
}
//2 归约
@Test
public void tets3(){
//reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回 T
//练习1 计算1-10的自然数的和
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, Integer::sum);
System.out.println(sum);
//reduce(BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
//练习2 计算公司所有员工工资的总和
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> salaryStream = employees.stream().map(Employee :: getSalary);
// System.out.println(salaryStream.reduce(Double::sum));
System.out.println(salaryStream.reduce((d1, d2) -> d1 + d2));
}
//3 收集
@Test
public void test4(){
//collect(Collector c):将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
//练习:查找工资大于6000的员工,结果返回为一个List或Set
List<Employee> list = EmployeeData.getEmployees();
List<Employee> employeeList = list.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
employeeList.forEach(System.out :: println);
System.out.println();
Set<Employee> employeeSet = list.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
employeeList.forEach(System.out :: println);
}
16.7 Optional类的使用
16.7.1 理解
为了在程序中避免出现空指针异常而创建的
16.7.2 常用方法
@Test
public void test1(){
//empty():创建的Optional对象内部的value = null;
Optional<Object> op1 = Optional.empty();
if (!op1.isPresent()){//Optional封装的数据是否包含数据
System.out.println("数据为空!");
}
System.out.println(op1);
System.out.println(op1.isPresent());
//如果Optional封装的数据value为空,则get()报错。否则,返回value
// System.out.println(op1.get());
}
@Test
public void test2(){
String str = "hello";
// str = null;
//of(T t):封装数据t生成Optional对象。要求t非空,否则报错
Optional<String> op1 = Optional.of(str);
//get()通常于of()搭配使用,用于获取内部封装的数据
String str1 = op1.get();
System.out.println(str1);
}
@Test
public void test3(){
String str = "beijing";
str = null;
//ofNullable(T t):封装数据t赋给Optional内部的value。不要求t非空
Optional<String> op1 = Optional.ofNullable(str);
//orElse(T t1):如果Optional内部的value非空,则返回此value值。如果value非空,则返回t1
String str2 = op1.orElse("shanghai");
System.out.println(str2);
}
16.7.3 典型练习
//使用Optional类的getGirlName():
public String getGirlName2(Boy boy){
Optional<Boy> boyOptional = Optional.ofNullable(boy);
//此时的boy1一定非空
Boy boy1 = boyOptional.orElse(new Boy(new Girl("hedaoyi")));
Girl girl = boy1.getGirl();
Optional<Girl> girlOptional = Optional.ofNullable(girl);
//girl1一定非空
Girl girl1 = girlOptional.orElse(new Girl("xjp"));
return girl1.getName();
}
@Test
public void test5(){
Boy boy = null;
boy = new Boy();
boy = new Boy(new Girl("廿五寅"));
String girlName2 = getGirlName2(boy);
System.out.println(girlName2);
}