1 Day16–多线程01
1.1 程序概念
程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
1.2 进程
1.2.1 概念
进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态
的过程:有它自身的产生、存在和消亡的过程。——生命周期
- 如:运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
注意:一个程序执行可以有多个进程。一个软件的运行都需要依赖进程执行.可能会依赖一个进程(IDEA),也可以依赖多个进程(chrome).软件运行时就加载对应的进程,软件关闭时对应的进程会消失,具有动态性.。
1.3 线程
1.3.1 特点
- 独立性:进程是系统中独立存在的实体,它可以拥有自己的独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
- 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。
- 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
1.3.2 概念
线程(thread):是操作系统能够进行运算调度的最小单位,是一个程序内部的一条执行路径。它被包含在进程之中,是进程中的实际运作单位。一个进程可以开启多个线程。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的。
- 如果一个进程只有一个线程,这种程序被称为单线程。
- 如果一个进程中有多条执行路径被称为多线程程序(如360:可以同时清理垃圾,杀毒,电脑体检等操作。)。
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间
- 它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患
- 简而言之,一个程序运行后至少一个进程,一个进程里包含多个线程。
1.4 进程和线程的关系
图1
从图1中可以看出:一个操作系统中可以有多个进程,一个进程中可以有多个线程,每个进程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存。(记清这个关系,非常重要!)
所以想使用线程技术,得先有进程,进程的创建是OS创建的,你能实现吗?不能,一般都是c或者c++语言完成的。
关系:
- 进程的范围大一些,可以包含多个线程.线程的范围小一些,是每个软件运行时能调度的最小单位.
- 进程有自己的存储空间,可以存放自己的数据,可以包含多个线程.
- 线程也有自己的存储空间,每个线程间是独立的.
图2
由图2可以看出:
- 虚拟机栈和程序计数器,它们2个每个线程各有一份。
- 方法区和堆,它们2个每个进程程各有一份。
- 即:假如一个程序有2个进程,每个进程都要一份方法区和堆。每个进程又有2个线程,每个线程又有一份虚拟机栈和程序计数器。所以每个线程共享同一个进程的方法区和堆,以至于多个线程操作共享的系统资源可能就会带来安全的隐患。
1.5 单核/多核CPU,并行/并发的概念
1.5.1 CPU分时调度
时间片:即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片。即该进程允许运行的时间,使各个程序从表面上看是同时进行的。
如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程,将当前进程挂起。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换,而不会造成CPU资源浪费。当又切换到之前执行的进程,把现场恢复,继续执行。
在宏观上:我们可以同时打开多个应用程序,每个程序并行,同时运行。1/3000ns
在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。多核提高了并发能力。
1.6 多线程
1.6.1 多线程的优点
单核cpu按照先后顺序执行多个任务:因为多线程还需要来回切换需要花费时间,反而单线程比多线程速度更快。
多核cpu执行多个任务:单线程不管几个cpu还是只能一个一个的执行,多线程因为cpu是多个可以来会切换,一个cpu控制一部分线程,多个cpu控制多个线程同时执行任务速度远快于单线程。
1.6.2 何时用多线程
1.6.3 如何区分单线程,多线程
1.7 多线程的特性
1.7.1 随机性
1.7.2 线程状态
线程生命周期,总共有五种状态:
- 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
- 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
- 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
- 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
a)等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
b)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
c)其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 - 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1.8 多线程创建1:继承Thread类
1.8.1 概述
Thread类(lang包)本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。Start()方法是一个native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
模拟开启多个线程,每个线程调用run()方法
线程是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
1.8.2 创建对象
//自己定义一个子类继承Thread,可以是多态的的形式创建
Thread()
分配新的 Thread 对象。
Thread(Runnable target)
分配新的 Thread 对象。
Thread(Runnable target, String name)
分配新的 Thread 对象。
Thread(String name) //创建对象时指定的线程名称。
分配新的 Thread 对象。
......
1.8.3 Thread常用方法和线程优先级
package atguigu.java;
/**
* 测试Thread中的常用方法:
* 1. void start():启动当前线程;调用当前线程的run()
* 2. void run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
* 3. static Thread currentThread():静态方法,返回执行当前代码的线程 通过类名调用
* 4. String getName():获取当前线程的名字
* 5. void setName():设置当前线程的名字
* 6. static void yield():释放当前cpu的执行权
* 7. void join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才
* 结束阻塞状态。
* 8. void stop():已过时。当执行此方法时,强制结束当前线程。
* 9. static void sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前
* 线程是阻塞状态。
* 10. boolean isAlive():判断当前线程是否存活
* 11.long getId() 返回该线程的标识符。
*
*
* 线程的优先级:
* 1.
* MAX_PRIORITY:10 最大
* MIN _PRIORITY:1 最小
* NORM_PRIORITY:5 -->默认优先级
* 2.如何获取和设置当前线程的优先级:
* int getPriority():获取线程的优先级
* void setPriority(int p):设置线程的优先级
*
* 说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下
* 被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
*
*
* @author shkstart
* @create 2019-02-13 下午 2:26
*/
class HelloThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
// try {
//sleep方法会抛异常,因为这是重写后的方法,重写之前的方法没有抛异常所以不能用throws只能是try-catch
// sleep(10);//单位:毫秒 1000毫秒=1秒
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
// if(i % 20 == 0){
// yield();
// }
}
}
public HelloThread(String name){
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
HelloThread h1 = new HelloThread("Thread:1");//通过构造器给线程命名
// h1.setName("线程一"); 写在start()方法之前 通过setName方法给线程命名
//设置分线程的优先级 可以写数字 1 2 3...,也可以写这几个值 MAX_PRIORITY MIN _PRIORITY 但只能代表10 1
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());
}
}
1.8.4 案例测试1:2个线程(t1,t2)干相同的事情
只需要定义一个子类继承Thread即可。
package atguigu.java;
/**
* 多线程的创建,方式一:继承于Thread类
* 1. 创建一个继承于Thread类的子类
* 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
* 3. 创建Thread类的子类的对象
* 4. 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
* <p>
* 例子:遍历100以内的所有的偶数
*
* @author shkstart
* @create 2019-02-13 上午 11:46
*/
//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. 创建Thread类的子类的对象
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()************");
}
}
}
}
根据线程名可以看出执行结果是随机的
注意:从上面结果可以确认,start()方法只是通知操作系统线程就绪,具体什么时间执行,操作系统来决定,我们JVM已经控制不了了。
1.8.5 案例测试2:2个线程(t1,t2)干不同的事情
需要定义2个子类继承Thread。
package atguigu.exer;
/**
* 练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
*
*
* @author shkstart
* @create 2019-02-13 下午 2:16
*/
public class ThreadDemo {
public static void main(String[] args) {
// MyThread1 m1 = new MyThread1();
// MyThread2 m2 = new MyThread2();
//
// m1.start();
// m2.start();
//如果一个线程只用一次,可以简写为创建Thread类的匿名子类的方式
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
1.9 多线程创建2:实现Runnable接口
1.9.1 概述
如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口。 (接口可以多实现,更灵活)
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。(因为接口中有一个抽象方法run(),所以子类必须重写父类的抽象方法)
1.9.2 常用方法
//只有一个方法
void run()
使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。
1.9.3 测试
package atguigu.java;
/**
* 创建多线程的方式二:实现Runnable接口
* 1. 创建一个实现了Runnable接口的类
* 2. 实现类去实现Runnable中的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
*
*
* 比较创建线程的两种方式。
* 开发中:优先选择:实现Runnable接口的方式
* 原因:1. 实现的方式没有类的单继承性的局限性
* 2. 实现的方式更适合来处理多个线程有共享数据的情况。
*
* 联系:public class Thread implements Runnable
* 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
*
* @author shkstart
* @create 2019-02-13 下午 4:34
*/
//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);
t1.setName("线程1");
//5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
注意:可以看到执行顺序是乱的,我们已经知道start()方法只是通知操作系统线程就绪,具体什么时间执行,操作系统来决定,我们JVM已经控制不了了。这就是乱序的原因,也是正常的。
1.10 售票案例的线程安全问题
设计4个售票窗口,总计售票100张。
用多线程的程序设计并写出代码。
1.10.1 方案1:继承Thread
package cn.teud.threaddemo;
//测试 卖票:设计4个售票窗口,总计售票100张。
public class Test4_Tickets {
public static void main(String[] args) {
//4,测试
//问题1: 现在4个线程,卖了400张票 ?? --
//原因:tickets是成员变量也叫实例变量,只要创建了实例,每个实例就都会拥有tickets资源,创建了4个实例,就会拥有了4个tickets
//解决:如果能够把tickets作为一个全局唯一的全局共享的资源,从头到尾只会存在一次的资源,那么需要被static修饰.
MyTickets t = new MyTickets();
MyTickets t2 = new MyTickets();
MyTickets t3 = new MyTickets();
MyTickets t4 = new MyTickets();
t.setName("1号窗口");
t2.setName("2号窗口");
t3.setName("3号窗口");
t4.setName("4号窗口");
//5,开启线程
t.start();
t2.start();
t3.start();
t4.start();
}
}
//模拟多线程售票:extends Thread
class MyTickets extends Thread {
//1,定义变量,记录票数
static int tickets = 100;//因为tickets资源会被每个对象拥有一次,不符合需求.想要在多个对象间共享一个tickets资源,所以static
//2,开始卖票 -- 业务放在重写的run()里
@Override
public void run() {
//super.run();
//3,一直卖票
while (true) {
//1.假设tickets=1 t1 t2 t3 t4四个人都瞒住了判断条件,进去卖票。(谁睡醒没法控制,产生原因及一个人干活后数据还没来得及恢复就被另一个让人拿到了。)
if (tickets > 0) {//有票就可以卖票
//6,让程序休息一下,如果数据仍然是正确的,那么这个多线程程序才是完美的!!88888888888888(即:静态方法 sleep())
try {
//问题2: 超卖: 程序卖出了 -1 0 -2
//问题3: 重卖: 程序把一张票卖给了多个人
//2.原因??? t1 t2 t3 t4四个人都睡着了。
Thread.sleep(10);//让程序休眠10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
// 问题2超卖产生的原因?
//假t1醒了,现在tickets=1,开始执行tickets--,输出1,tickets自减变为0
//t1刚变完,t3醒了,现在tickets=0,开始执行tickets--,输出0,tickets自减变为-1.
// t3刚变完,t4醒了,现在tickets=-1,开始执行tickets--,输出-1,tickets自减变为-2.
// t4刚变完,t2醒了,现在tickets=2,开始执行tickets--,输出-2,tickets自减变为-3.
//问题三重买产生的原因? (与超卖的原因几乎一样,因为过程不可控,不知道谁先醒,回去抢占资源)
//假t1醒了,现在tickets=73,开始执行tickets--,输出73,tickets自减变为72,还没来得及改变。
// t3醒了,现在tickets=73,开始执行tickets--,输出73,tickets自减变为-72. 还没来得及改变。
//t4醒了,现在tickets=-73,开始执行tickets--,输出-73,tickets自减变为72. 还没来得及改变。
// t4刚变完,t2醒了,现在tickets=72,开始执行tickets--,输出72,tickets自减变为71.
System.out.println( getName() + "=" + tickets--);
} else { //没票就结束
break; //死循环的出口!!
}
}
}
}
1.10.2 方案2:实现Runnable
package cn.teud.threaddemo;
//测试 卖票:设计4个售票窗口,总计售票100张。
public class Test5_Tickets2 {
public static void main(String[] args) {
MyTickets2 target = new MyTickets2();
//怎么把目标对象和Thread对象绑定关系
Thread t = new Thread(target) ;
Thread t2 = new Thread(target) ;
Thread t3 = new Thread(target) ;
Thread t4 = new Thread(target) ;
//启动线程
t.start();
t2.start();
t3.start();
t4.start();
}
}
//模拟多线程售票:implements Runnable
class MyTickets2 implements Runnable {
//1,定义变量,记录票数
/*这个地方没有用static为啥卖的还是100张票而不是400张票。因为实现接口,虽然是开了四个窗口,
但是执行的任务都是同一个target对象,只new了一次所以从始至终卖的是100张票。不用修饰static
修饰也是一个对象.*/
int tickets = 100;
//2, 把卖票业务放入重写的run()
@Override
public void run() {
while (true) {//一直卖票
if (tickets>0) {
//一定要让程序睡一会儿,来检查数据是否安全 !!!
try {
//问题1:超卖:程序卖出了0 -1 -2号票
//问题2:重卖:程序把一张票卖给了多个人
//原因??? 解决方案????
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3,获取线程名Thread.currentThread().getName()
System.out.println(Thread.currentThread().getName()+"="+tickets--);
}else{
break ;//死循环的出口!!!
}
}
}
}
1.10.3 线程安全问题
- 每次创建线程对象,都会生成一个tickets变量值是100,创建4次对象就生成了400张票了。不符合需求,怎么解决呢?能不能把tickets变量在每个对象间共享,就保证多少个对象都是卖这100张票。
解决:用静态修饰或者用第二种方式创建线程。 - 产生超卖,-1张、-2张。- -多线程安全问题
- 产生重卖,同一张票卖给多人。- -多线程安全问题
- 多线程安全问题是如何出现的?
当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
常见情况是由于线程的随机性+访问延迟(加上延迟出错概率变高了)。 - 以后如何判断程序有没有线程安全问题?- - 在多线程程序中+有共享数据+多条语句操作共享数据。
- 如何解决线程安全问题? - - 同步锁, Lock锁(jdk1.5新特性)
解释:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
1.10.4 扩展
1 JVM启动是单线程还是多线程?
多线程,最少要启动main线程和GC线程。
2 守护线程
略。
2 Day17–多线程02+同步锁
2.1 同步锁解决线程安全问题:01
把有可能出现问题的代码包起来,一次只让一个线程执行。通过sychronized关键字实现同步。
当多个对象操作共享数据时,可以使用同步锁解决线程安全问题。
- 目前的多线程编程中,出现了资源抢占现象,引发的数据安全隐患。
- 如果能给资源上把锁就好了。谁有钥匙,拿着钥匙开锁使用资源。
- 同步锁就是用来把共享资源给锁起来。保证共享的资源,在同一时刻资源是独占的没人抢。
- 同步锁,本质上是牺牲了效率,保证了共享资源的安全性。
- 同步和异步的区别:
同步:是指同一时刻只能有一个人操作数据,别人只能排队等待。牺牲了效率,提高了安全。(StringBuffer就是加了锁,所以说效率降低,安全性提高了。)
异步:是指同一时刻没人排队,大家一起上一起抢。提高了效率,牺牲了安全。 - 使用synchronized关键字实现同步锁的效果。
- 并发和并行的区别:
并发:一个cpu同时执行多个程序。
并行:有多个cpu(现在电脑都是多核的:多个cpu),一个cpu执行一个程序。
2.1.1 Synchronized语法
同步的方式优缺点:
- 好处:解决了线程的安全问题。
- 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
//方式一:同步代码块
synchronized(同步监视器/锁对象){//锁的代码块,需要指定锁对象,可以是任意对象,但是必须是同一个对象。
* //需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求1:多个线程必须要共用同一把锁。
要求2:同步需要两个或者两个以上的线程。
//方式二:同步方法
synchronized public void eat(){
//需要被同步的代码
}
说明:1.如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
2.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
3.要求和方式一几乎相同,锁对象只有一个。
2.1.2 在开发中如何判断是否有多线程并发的安全隐患?
– 在多线程的场景下 + 共享资源,被多条语句操作 >=2
2.1.3 改造售票案例01(同步代码块- ->Thread)
package com.thread;
/**
* 使用同步代码块解决继承Thread类的方式的线程安全问题
*
* 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
*
*/
public class Test1 {
public static void main(String[] args) {
//4,测试
//问题1: 现在4个线程,卖了400张票 ?? --
//原因:tickets是成员变量也叫实例变量,只要创建了实例,每个实例就都会拥有tickets资源,创建了4个实例,就会拥有了4个tickets
//解决:如果能够把tickets作为一个全局唯一的全局共享的资源,从头到尾只会存在一次的资源,那么需要被static修饰.
MyTickets t = new MyTickets();
MyTickets t2 = new MyTickets();
MyTickets t3 = new MyTickets();
MyTickets t4 = new MyTickets();
t.setName("1号窗口");
t2.setName("2号窗口");
t3.setName("3号窗口");
t4.setName("4号窗口");
//5,开启线程
t.start();
t2.start();
t3.start();
t4.start();
}
}
//模拟多线程售票:extends Thread
class MyTickets extends Thread {
//1,定义变量,记录票数
static int tickets = 100;//因为tickets资源会被每个对象拥有一次,不符合需求.想要在多个对象间共享一个tickets资源,所以static
//private static Object obj = new Object();//方式一的锁对象:加上static是因为,Thread方式造了多个对象,所以加上static保证锁对象只有一个
//2,开始卖票 -- 业务放在重写的run()里
@Override
public void run() {
//super.run();
//3,一直卖票
while (true) {
/*锁位置找对合理的位置 + 锁对象是同一个对象 !!
* 位置合适:如果while (true) {放在同步锁里面,则程序执行相当于每次执行一个线程,把票卖完后,值执行别的线程,
* 即:一个人把票买完之后其它人才能买票,显然不合适。
* */
/*同步代码块--->extends Thread方式的锁对象:同步代码块锁对象任意
* 方式一:private static Object obj = new Object();
* 随便创建一个对象当做锁对象,因为以继承Thread的方式创建多个线程对象,所以把锁对象设置为静态的保证多个线程之间的锁对象是同一个。
* 方式二(容易错误):因为每次自己new一个对象作为锁对象太麻烦,直接用this代表本类对象作为锁对象更简洁,但是因为继承Thread的方式是创建了多个
* 线程对象,此时this的对象有:t,t2,t3,t4.不满足多个线程之间锁对象是同一个,所以这种方式错误。当然如果只有一个线程对象可以用。
* 方式三:Class clazz = MyTickets.class,MyTickets.class只会加载一次,使用类对象的方式,类对象只会加载一次。
* */
// synchronized (obj){ 方式一的锁对象
// synchronized (this){方式二的锁对象
synchronized (MyTickets.class){
if (tickets > 0) {//有票就可以卖票
//6,让程序休息一下,如果数据仍然是正确的,那么这个多线程程序才是完美的!!
try {
Thread.sleep(10);//让程序休眠10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( getName() + "=" + tickets--);
} else { //没票就结束
break; //死循环的出口!!
}
}
}
}
}
2.1.4 改造售票案例02(同步代码块- ->Runnable)
package com.thread;
/**
* 使用同步代码块解决实现 Runnable接口的方式的线程安全问题
*
*在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
*
*/
public class Test2 {
public static void main(String[] args) {
MyTickets2 target = new MyTickets2();
//怎么把目标对象和Thread对象绑定关系
Thread t1 = new Thread(target) ;
Thread t2 = new Thread(target) ;
Thread t3 = new Thread(target) ;
Thread t4 = new Thread(target) ;
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//模拟多线程售票:implements Runnable
//3,目前程序中有安全隐患 -- 加锁来解决 -- 锁的位置(发生点到问题结束) -- 锁的对象(同一个)
class MyTickets2 implements Runnable {
//1,定义变量,记录票数
int tickets = 100; //不加static 是因为Runnable天然只造了一个对象
//Object obj = new Object();//方式一的锁对象:不加static 是因为Runnable天然只造了一个对象,注意这个锁对象要放在方法外面。
//2, 把卖票业务放入重写的run()
@Override
public void run() {
// Object obj = new Object();方式一的锁对象的错误方式:不能写在里面,使用的每次都是new出来的新对象,可没统一!!在里面创建对象相当于new了4次对象。
while (true) {//一直卖票
//4,锁位置:从问题起点开始--同步代码块,需要同时指定锁对象,可以是任意对象,但是必须是同一个。
//5,加锁后,多个线程来访问这段资源,都得排队访问不再抢着干了。提高了安全,牺牲了效率。
/*
* 同步代码块--->implements Runnable方式的锁对象:同步代码块锁对象任意
* 方式一:随便创建一个对象当做锁对象,因为以implements Runnable的方式创建天然只有一个线程对象,四个线程来了,使用的都是new出来的Object同一个对象
* 所以锁对象不用设置为static静态的,但是要注意不能写在run()方法里面,在里面写相当于每次调用方法使用的每次都是new出来的新对象,不符合多个线程锁对象共享。
* 方式二:因为每次自己new一个对象作为锁对象太麻烦,直接用this代表本类对象作为锁对象更简洁,因为implements Runnable的方式是创建了一个线程对象,所以
* this只代表一个本类对象的引用target,符合规范。
* */
//synchronized (obj){方式一的锁对象 synchronized (new Object())直接new也是方式一的锁对象的错误方式
synchronized (this){//方式二的锁对象:四个线程来了,使用的都本类的对象this
if (tickets>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"="+tickets--);
}else{
break ;//死循环的出口!!!
}
}
}
}
}
2.1.5 改造售票案例03(同步方法- ->Thread)
package com.thread;
/**
* 使用同步方法解决继承Thread类的方式的线程安全问题
*
*关于同步方法的总结:
* 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
* 2. 非静态的同步方法,同步监视器是:this
* 静态的同步方法,同步监视器是:当前类本身
*/
public class Test3 {
public static void main(String[] args) {
//4,测试
//问题1: 现在4个线程,卖了400张票 ?? --
//原因:tickets是成员变量也叫实例变量,只要创建了实例,每个实例就都会拥有tickets资源,创建了4个实例,就会拥有了4个tickets
//解决:如果能够把tickets作为一个全局唯一的全局共享的资源,从头到尾只会存在一次的资源,那么需要被static修饰.
MyTickets3 t = new MyTickets3();
MyTickets3 t2 = new MyTickets3();
MyTickets3 t3 = new MyTickets3();
MyTickets3 t4 = new MyTickets3();
t.setName("1号窗口");
t2.setName("2号窗口");
t3.setName("3号窗口");
t4.setName("4号窗口");
//5,开启线程
t.start();
t2.start();
t3.start();
t4.start();
}
}
//模拟多线程售票:extends Thread
class MyTickets3 extends Thread {
//1,定义变量,记录票数
static int tickets = 100;//因为tickets资源会被每个对象拥有一次,不符合需求.想要在多个对象间共享一个tickets资源,所以static
//2,开始卖票 -- 业务放在重写的run()里
@Override
public void run() {
//super.run();
//3,一直卖票
while (true) {
show();
}
}
//private synchronized void show(){ //同步监视器:t,t2,t3,t4。此种解决方式是错误的,有线程安全问题,锁对象不共享。
private static synchronized void show(){同步监视器:MyTickets3.class 改为静态的方法,锁对象共享。
if (tickets > 0) {//有票就可以卖票
//6,让程序休息一下,如果数据仍然是正确的,那么这个多线程程序才是完美的!!
try {
Thread.sleep(10);//让程序休眠10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() + "=" + tickets--);
}
}
}
2.1.6 改造售票案例04(同步方法- ->Runnable)
package com.thread;
/**
* 使用同步方法解决实现 Runnable接口的方式的线程安全问题
*
*说明:如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
* 此案例需要修改代码。
*
*/
public class Test4 {
public static void main(String[] args) {
MyTickets4 target = new MyTickets4();
//怎么把目标对象和Thread对象绑定关系
Thread t1 = new Thread(target) ;
Thread t2 = new Thread(target) ;
Thread t3 = new Thread(target) ;
Thread t4 = new Thread(target) ;
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//模拟多线程售票:implements Runnable
//3,目前程序中有安全隐患 -- 加锁来解决 -- 锁的位置(发生点到问题结束) -- 锁的对象(同一个)
class MyTickets4 implements Runnable {
//1,定义变量,记录票数
int tickets = 100; //不加static 是因为Runnable天然只造了一个对象
//2, 把卖票业务放入重写的run()
@Override
public void run() {
while (true) {//一直卖票
show();
}
}
public synchronized void show(){//同步监视器:this
//synchronized (this){还是同步代码块的方式
if (tickets>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"="+tickets--);
}
//}
}
}
2.2 单例设计模式
2.2.1 懒汉式线程安全问题的解决
package com.thread;
class Single{
// 1、私有化构造方法,不让外界直接new
private Single() {}
// 2、在类的内部,创建好对象
//static :静态只能调用静态
static private Single s = null;
// 3、对外界提供一个公共的get(),返回一个已经准备好的对象
//static是为了外界不通过对象访问而是通过类名直接方法
static public Single get(){
// static synchronized public Single get(){//1.同步方法
/* 方式一:效率低,因为要求只创建一个对象,现在执行顺序是:第一次第一个线程进来,判断对象为null后创建好对象再返回对象。
*之后别的线程也要一个个的进来判断不为空后再返回,每次线程来都要进行判断,效率低。正确做法应该是第一个线程首次创建好对象后
* 挂个牌子已经创建好一个对象了,别的线程不用在进行判断是否有对象而是直接返回对象即可。
*
* */
// synchronized (Single.class) {//2.同步代码块
// if(s==null){
// /*会有线程安全问题:假如现在多个线程,每个线程调用run方法,各个run方法有调用这个get方法。
// 当第一个线程进入到这个get方法后,第一次进入对象为空,此时可能发生阻塞第二个线程进来判断为空
// 也需要创建对象。这样就创建了2个对象不合理。s相当于共享数据。
// */
// s = new Single();
// }
// return s;//是null说明还没有创建对象,不是null说明创建好了对象直接返回,可以保证只创建一次对象。
// }
//方式二:效率稍高
if(s == null){
synchronized (Single.class) {
if(s==null){
s = new Single();
}
}
}
return s;
}
}
2.3 线程的死锁问题
2.3.1 案例测试
package com.atguigu.java1;
/**
* 演示线程的死锁问题
*
* 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
* 都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
*
* 2.说明:
* 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
* 2)我们使用同步时,要避免出现死锁。
*
* @author shkstart
* @create 2019-02-15 下午 3:20
*/
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();
}
//线程安全问题:拿着s1等着执行s2
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();
}
//线程安全问题:拿着s2等着执行s1
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
2.4 Lock锁解决线程安全问题:02
JDK5.0新增
2.4.1 Lock锁改造售票案例
package com.atguigu.java1;
import java.util.concurrent.locks.ReentrantLock;
/**
* 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增
*
* 1. 面试题:synchronized 与 Lock的异同?
* 相同:二者都可以解决线程安全问题
* 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
* Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
*
* 2.优先使用顺序:
* Lock 同步代码块(已经进入了方法体,分配了相应资源) 同步方法(在方法体之外)
*
*
* 面试题:如何解决线程安全问题?有几种方式
* @author shkstart
* @create 2019-02-15 下午 3:38
*/
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2.调用锁定方法lock(),把代码放在try-catch-finally中,每页异常所以不用写catch.
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} 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("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
2.5 线程的通信问题
2.5.1 入门案例:wait(),notify(),notifyAll
package com.atguigu.java2;
/**
* 线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印
*
* 涉及到的三个方法:
* wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
* notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
*
* 说明:
* 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
* 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
* 否则,会出现IllegalMonitorStateException异常
* 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
*
* 面试题:sleep() 和 wait()的异同?
* 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
* 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
* 2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
* 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
*
* @author shkstart
* @create 2019-02-15 下午 4:21
*/
class Number implements Runnable{
private int number = 1;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
/*
* 程序执行过程:
* 假如线程1先执行进入方法中,因为没有wait阻塞的方法,此时obj.notify();只是执行下而已,之后打印线程1并进入到阻塞。
* 线程2进来首先唤醒线程1,因为此时同步锁对象是线程2拿着的即便唤醒线程1,线程1也进来,之后打印线程2,线程2进入到阻塞。
* 之后线程1在进来唤醒线程2,此时线程1拿着锁对象线程2唤醒也进不来,打印线程1,线程1进入到阻塞状态。
* 之后线程2进来唤醒线程1.......直到打印完成。
*
* */
obj.notify();
if(number <= 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
2.5.2 经典例题:生产者/消费者问题
package com.atguigu.java2;
/**
* 线程通信的应用:经典例题:生产者/消费者问题
*
* 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
* 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员
* 会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品
* 了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
*
* 分析:
* 1. 是否是多线程问题?是,生产者线程,消费者线程
* 2. 是否有共享数据?是,店员(或产品)
* 3. 如何解决线程的安全问题?同步机制,有三种方法
* 4. 是否涉及线程的通信?是
*
* @author shkstart
* @create 2019-02-15 下午 4:48
*/
class Clerk{
private int productCount = 0;
//生产产品
public synchronized void produceProduct() {
if(productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if(productCount > 0){
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
productCount--;
notify();
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//生产者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始生产产品.....");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{//消费者
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始消费产品.....");
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者1");
Consumer c2 = new Consumer(clerk);
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
2.6 多线程创建3:实现Callable接口
JDK5.0新增
package com.atguigu.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 shkstart
* @create 2019-02-15 下午 6:01
*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
//get方法只是获取call方法的返回值,如果你不感兴趣或者下面执行的代码中也用不到,则不用写这一步。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
2.7 多线程创建4:使用线程池(开发中最常用)
JDK5.0新增
开发中大多数写线程池是通过框架来实现的,一般不用自己写线程池
package com.atguigu.java2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 创建线程的方式四:使用线程池
*
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*
*
* 面试题:创建多线程有几种方式?四种!
* @author shkstart
* @create 2019-02-15 下午 6:30
*/
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是接口,Executors.newFixedThreadPool(10)这个工具类是
//为了创建对象,因为接口不能实例化,所以这个工具类的返回值一定是接口的子类对象,多态形式。
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性:
/*直接通过service无法调用:
*因为接口中的属性比较少,而且变量都是常量不允许修改,所以先要设置属性一定是ExecutorService接口的子类中设置。
*而Executors.newFixedThreadPool(10);的返回值恰好是子类对象,因为接收直接用的是多态形式看不到这个子类
*对象是谁,那么这个实现类对象如何看到呢????
* service.getClass():获取这个对象到底是那个类造的。此工具类返回的对象是ThreadPoolExecutor类造的,
* 因为是通过多态父类接收的,想调用子类的属性,可以通过强转实现。即service1调用。
*/
// System.out.println(service.getClass()); //ThreadPoolExecutor
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//...... 查Api即可
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
2.8 四种线程创建方式比较
方式 | 优点 | 缺点 |
---|---|---|
Thread | 编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。 | 线程类已经继承了Thread类,所以不能再继承其他父类 |
Runnable | 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。 | 编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。 |
Callable | Runnable规定(重写的方法是run(),Callable规定(重写)的方法是call()。Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。Call方法可以抛出异常,run方法不可以。运行Callable任务可以拿到一个Future对象,表示异步计算的结果。 | 存取其他项慢 |
Pool | 线程池可以创建固定大小,这样无需反复创建线程对象,线程是比较耗费资源的资源同时线程不会一直无界的创建下去,拖慢系统 | 编程繁琐,难以理解 |
3 枚举类+注解
3.1 枚举类概念
3.2 二种创建枚举类的方式
3.2.1 自定义枚举类
package com.atguigu.java;
/**
* 一、枚举类的使用
* 1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
* 2.当需要定义一组常量时,强烈建议使用枚举类(枚举类里面是有限个对象,对象是常量)
* 3.如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
*
* 二、如何定义枚举类
* 方式一:jdk5.0之前,自定义枚举类
* 方式二:jdk5.0,可以使用enum关键字定义枚举类
* @author shkstart
* @create 2019 上午 10:17
*/
public class SeasonTest {
public static void main(String[] args) {
Season spring = Season.SPRING;//通过类名调用
System.out.println(spring);//Season{seasonName='春天', seasonDesc='春暖花开'},默认继承Object 不重写toString()输出的是地址值
}
}
//方式一:自定义枚举类
class Season{
//1.声明Season对象的属性:private final修饰
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:获取枚举类对象的属性 get,set快捷键:Alt+insert
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//5.其他诉求2:提供toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
3.2.2 使用enum关键字定义枚举类
package com.atguigu.java;
/**
* 方式二:使用enum关键字定义枚举类
* 说明:定义的枚举类默认继承于java.lang.Enum类,如果不重写toString()打印的是对象名而不是地址值。
* 继承Object里才是地址值。
*
* Enum类中的常用方法:
* values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
* valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异 * 常:IllegalArgumentException。
* toString():返回当前枚举类对象常量的名称
*
* @author shkstart
* @create 2019 上午 10:35
*/
public class SeasonTest1 {
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());//SUMMER,因为继承的是父类是Enum,如果没重写toString输出的是对象名,如果父类是Object没重写则是地址值
System.out.println(Season1.class.getSuperclass());//查看当前类的父类,class java.lang.Enum
System.out.println("****************");
//values():返回所有的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);//SPRING,SUMMER,AUTUMNWINTER
}
System.out.println("****************");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);//查看当前线程的状态
}
System.out.println("****************");
//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf("WINTER");
//如果没有objName的枚举类对象,则抛异常:IllegalArgumentException
// Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);
}
}
//使用enum关键字枚举类
enum Season1{
/*1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
* 把前面的修饰符,对象名,等号,new对象 如果一样需要删除,否则报错:
* public static final Season1 SPRING = new Season1("春天","春暖花开");---》SPRING ("春天","春暖花开"),
* public static final Season SUMMER = new Season("夏天","夏日炎炎");---> SUMMER ("夏天","夏日炎炎"),
* public static final Season AUTUMN = new Season("秋天","秋高气爽");--> AUTUMN ("秋天","秋高气爽"),
* public static final Season WINTER = new Season("冬天","冰天雪地");--->WINTER ("冬天","冰天雪地");
*
*/
SPRING ("春天","春暖花开"),
SUMMER ("夏天","夏日炎炎"),
AUTUMN ("秋天","秋高气爽"),
WINTER ("冬天","冰天雪地");
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//3.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){//常量在构造器中赋值,是可以改变的。在直接量和构造代码块中不能修改。
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性 get,set快捷键:Alt+insert
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//5.其他诉求2:提供toString()
/* @Override
public String toString() {
return "Season1{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}*/
}
3.3 自定义的枚举类实现接口的情况
3.3.1 自定义枚举类实现接口
略,实现接口如果有抽象方法重写即可。
3.3.2 使用enum关键字定义枚举类实现接口
package com.atguigu.java;
/**
* 使用enum关键字定义的枚举类实现接口的情况:
* 情况一:实现接口,在enum类中实现抽象方法,每个对象调这个方法时输出的内容一样。
* 情况二:让枚举类的对象分别实现接口中的抽象方法,每个对象调这个方法时输出的内容不一样。
* @author shkstart
* @create 2019 上午 10:35
*/
public class SeasonTest1 {
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());//SUMMER,因为继承的是父类是Enum,如果没重写toString输出的是对象名,如果父类是Object没重写则是地址值
System.out.println(Season1.class.getSuperclass());//查看当前类的父类,class java.lang.Enum
System.out.println("****************");
//values():返回所有的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);//SPRING,SUMMER,AUTUMNWINTER
values[i].show();
}
System.out.println("****************");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);//查看当前线程的状态
}
System.out.println("****************");
//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf("WINTER");
//如果没有objName的枚举类对象,则抛异常:IllegalArgumentException
// Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);
winter.show();
}
}
//定义接口
interface Info{
void show();
}
//使用enum关键字枚举类
enum Season1 implements Info{
/*1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
* 把前面的修饰符,对象名,等号,new对象 如果一样需要删除,否则报错:
* public static final Season1 SPRING = new Season1("春天","春暖花开");---》SPRING ("春天","春暖花开"),
* public static final Season SUMMER = new Season("夏天","夏日炎炎");---> SUMMER ("夏天","夏日炎炎"),
* public static final Season AUTUMN = new Season("秋天","秋高气爽");--> AUTUMN ("秋天","秋高气爽"),
* public static final Season WINTER = new Season("冬天","冰天雪地");--->WINTER ("冬天","冰天雪地");
*
*/
/*情况一:
SPRING ("春天","春暖花开"),
SUMMER ("夏天","夏日炎炎"),
AUTUMN ("秋天","秋高气爽"),
WINTER ("冬天","冰天雪地");*/
//情况二:每个对象都重写show方法
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修饰
private final String seasonName;
private final String seasonDesc;
//3.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){//常量在构造器中赋值,是可以改变的。在直接量和构造代码块中不能修改。
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性 get,set快捷键:Alt+insert
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//5.其他诉求2:提供toString()
/* @Override
public String toString() {
return "Season1{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}*/
// @Override//情况一:实现接口,在enum类中实现抽象方法,每个对象调这个方法时输出的内容一样
// public void show() {
//
// }
}
3.4 注解
总结:为了优化以前的开发模式,提出注解开发。减少了代码量,提高了注解的复用。标志是@ Annotation
3.5 注解的使用示例
3.5.1 生成文档相关的注解
3.5.2 JDK内置的注解
package com.atguigu.java1;
import java.util.ArrayList;
import java.util.Date;
/**
* 注解的使用
*
* 1. 理解Annotation:
* ① jdk 5.0 新增的功能
*
* ② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,
* 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
*
* ③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android
* 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗
* 代码和XML配置等。
*
* 2. Annocation的使用示例
* 示例一:生成文档相关的注解
* 示例二:在编译时进行格式检查(JDK内置的三个基本注解)
* @Override: 限定重写父类方法, 该注解只能用于方法
* @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
* @SuppressWarnings: 抑制编译器警告
* @SafeVarargs jdk1.7出现,堆污染,不常用
* @FunctionallInterface jdk1.8出现,配合函数式编程拉姆达表达式,不常用
*
* 示例三:跟踪代码依赖性,实现替代配置文件功能
* @author shkstart
* @create 2019 上午 11:37
*/
public class AnnotationTest {
public static void main(String[] args) {
Person p = new Student();
p.walk();
Date date = new Date(2020, 10, 11);//已过时,但仍然能用,源码中用@Deprecated注解修饰了
System.out.println(date);
@SuppressWarnings("unused")//加上注解警告会消失,unused属性:没有用。 里面的值可以看成是成员变量
int num = 10;//定义了一个变量没有用,在eclipse中会有一个警告:定义了变量没有用。在idea中变量名变为灰色。
@SuppressWarnings({ "unused", "rawtypes" }) //变量没有用,没有添加泛型
ArrayList list = new ArrayList();
}
}
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void walk(){
System.out.println("人走路");
}
public void eat(){
System.out.println("人吃饭");
}
}
interface Info{
void show();
}
class Student extends Person implements Info{
@Override//不加上注解不一定不是重写,加上注解会在编译期检查是否为重写父类的方法 如果不是则报错。
public void walk() {
System.out.println("学生走路");
}
public void show() {
}
}
3.5.3 跟踪代码依赖性,实现替代配置文件功能
3.6 元注解
3.6.1 概念
3.6.2 测试
自定义注解中加上元注解:
package com.atguigu.java1;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
/**
* 1.如何自定义注解:参照@SuppressWarnings定义
* ① 注解声明为:@interface
* ② 内部定义成员,通常使用value表示
* ③ 可以指定成员的默认值,使用default定义
* ④ 如果自定义注解没有成员,表明是一个标识作用。public @interface MyAnnotation {},使用时也不需要写属性值
*
* 注意:
* 如果注解有成员,在使用注解时,需要指明成员的值。
* 自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
* 自定义注解通过都会指明两个元注解:Retention、Target
*
* 2.jdk 提供的4种元注解
* 元注解:对现有的注解进行解释说明的注解
* Retention:指定所修饰的 Annotation 的生命周期:SOURCE(在源文件中有效)\CLASS(默认行为, 在class文件中有效)\RUNTIME(在运行时有效)
* 只有声明为RUNTIME生命周期的注解,才能通过反射获取。
* Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素:如果不指定则代表那都可以用。
* ElementType.ANNOTATION_TYPE 应用于注释类型
* ElementType.CONSTRUCTOR 应用于构造函数
* ElementType.FIELD 应用于字段或属性
* ElementType.LOCAL_VARIABLE 应用于局部变量
* ElementType.METHOD 应用于方法级
* ElementType.PACKAGE 应用于包声明
* ElementType.PARAMETER 应用于方法的参数
* ElementType.TYPE 应用于类的元素
* *******出现的频率较低*******
* Documented:表示所修饰的注解在被javadoc解析时,保留下来。默认情况下,javadoc是不包括注解的。定义为Documented的注解必须设置Retention值为RUNTIME。
*
* Inherited:被它修饰的 Annotation 将具有继承性。如果某个类使用了被@Inherited 修饰的 Annotation, 则其子类将自动具有该注解。
* 如何证明子类继承了注解--->通过反射获取注解信息 ---到反射内容时系统讲解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
@Inherited
public @interface MyAnnotation {
//如果只有一个参数成员,建议使用参数名为value。注意这是一个属性不是方法,虽然有()。
//String value() ;//形式一:可以是任意类型的,如果value值可以指定多个可以定义为数组如:String[] value() ;
String value() default "hello";//形式二:通过default关键字指定一个默认值
}
使用注解:
package com.atguigu.java1;
public class AnnotationTest {
public static void main(String[] args) {
Person p = new Student();
p.walk();
}
}
//@MyAnnotation(value="hello")//形式一:使用注解,因为自定义注解@MyAnnotation有变量value,所以这里需要指定一个值 否则会报错。暂时随便指定个值,没有特殊含义.只有一个值可以简写为:@MyAnnotation("hello")
@MyAnnotation()//形式二:因为使用的value属性有默认值,此时不用指定,也不会报错。不想要默认值,可以进行修改如:@MyAnnotation(value="hi")
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void walk(){
System.out.println("人走路");
}
public void eat(){
System.out.println("人吃饭");
}
}
interface Info{
void show();
}
//student继承了person类,person添加了自定义注解@MyAnnotation(),又因为自定义注解设置了元注解@Inherited具有可继承性,所以
//student也有@MyAnnotation()注解。如何证明student继承了注解??---->通过反射
class Student extends Person implements Info{
@Override
public void walk() {
System.out.println("学生走路");
}
public void show() {
}
}
3.7 自定义注解
3,7,1 概述
3.7.2 测试
定义注解:
package com.atguigu.java1;
/**
* 如何自定义注解:参照@SuppressWarnings定义
* ① 注解声明为:@interface
* ② 内部定义成员,通常使用value表示
* ③ 可以指定成员的默认值,使用default定义
* ④ 如果自定义注解没有成员,表明是一个标识作用。public @interface MyAnnotation {},使用时也不需要写属性值
*
* 注意:
*1. 如果注解有成员,在使用注解时,需要指明成员的值。
*2. 自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
*3. 自定义注解通过都会指明两个元注解:Retention、Target---详情查看3.6元注解
*
*/
public @interface MyAnnotation {
//如果只有一个参数成员,建议使用参数名为value。注意这是一个属性不是方法,虽然有()。
//String value() ;//形式一:可以是任意类型的,如果value值可以指定多个可以定义为数组如:String[] value() ;
String value() default "hello";//形式二:通过default关键字指定一个默认值
}
使用注解:
package com.atguigu.java1;
public class AnnotationTest {
public static void main(String[] args) {
Person p = new Student();
p.walk();
}
}
//@MyAnnotation(value="hello")//形式一:使用注解,因为自定义注解@MyAnnotation有变量value,所以这里需要指定一个值 否则会报错。暂时随便指定个值,没有特殊含义。只有一个值可以简写为:@MyAnnotation("hello")
@MyAnnotation()//形式二:因为使用的value属性有默认值,此时不用指定,也不会报错。不想要默认值,可以进行修改如:@MyAnnotation(value="hi")
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void walk(){
System.out.println("人走路");
}
public void eat(){
System.out.println("人吃饭");
}
}
interface Info{
void show();
}
class Student extends Person implements Info{
@Override
public void walk() {
System.out.println("学生走路");
}
public void show() {
}
}
3.8 jdk1.8注解新特性
3.8.1 可重复注解 和 类型注解
类AnnotationTest:
package com.atguigu.java1;
import java.util.ArrayList;
public class AnnotationTest {
public static void main(String[] args) {
Person p = new Student();
p.walk();
}
}
//@MyAnnotation(value="hello")//形式一:使用注解,因为自定义注解@MyAnnotation有变量value,所以这里需要指定一个值 否则会报错。暂时随便指定个值,没有特殊含义.只有一个值可以简写为:@MyAnnotation("hello")
//@MyAnnotation()//形式二:因为使用的value属性有默认值,此时不用指定,也不会报错。不想要默认值,可以进行修改如:@MyAnnotation(value="hi")
//jdk 8之前的写法想要使用重复的注解:想要写多个注解,在自定义注解MyAnnotations中声明为MyAnnotation类型的数组:MyAnnotation[] value();
//@MyAnnotations({@MyAnnotation(value="hi"),@MyAnnotation(value="hi")})
//jdk 8开始想要这样使用重复的注解:
@MyAnnotation(value="hi")
@MyAnnotation(value="abc")
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void walk(){
System.out.println("人走路");
}
public void eat(){
System.out.println("人吃饭");
}
}
interface Info{
void show();
}
//student继承了person类,person添加了自定义注解@MyAnnotation(),又因为自定义注解设置了元注解@Inherited具有可继承性,所以
//student也有@MyAnnotation()注解。如何证明student继承了注解??---->通过反射
class Student extends Person implements Info{
@Override
public void walk() {
System.out.println("学生走路");
}
public void show() {
}
}
//类型注解的使用:
class Generic<@MyAnnotation T>{
public void show() throws @MyAnnotation RuntimeException{
ArrayList<@MyAnnotation String> list = new ArrayList<>();
int num = (@MyAnnotation int) 10L;
}
}
注解MyAnnotation:
package com.atguigu.java1;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
/**
* 1.如何自定义注解:参照@SuppressWarnings定义
* ① 注解声明为:@interface
* ② 内部定义成员,通常使用value表示
* ③ 可以指定成员的默认值,使用default定义
* ④ 如果自定义注解没有成员,表明是一个标识作用。public @interface MyAnnotation {},使用时也不需要写属性值
*
* 注意:
* 如果注解有成员,在使用注解时,需要指明成员的值。
* 自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
* 自定义注解通过都会指明两个元注解:Retention、Target
*
* 2.jdk 提供的4种元注解
* 元注解:对现有的注解进行解释说明的注解
* Retention:指定所修饰的 Annotation 的生命周期:SOURCE(在源文件中有效)\CLASS(默认行为, 在class文件中有效)\RUNTIME(在运行时有效)
* 只有声明为RUNTIME生命周期的注解,才能通过反射获取。
* Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素:如果不指定则代表那都可以用。
* ElementType.ANNOTATION_TYPE 应用于注释类型
* ElementType.CONSTRUCTOR 应用于构造函数
* ElementType.FIELD 应用于字段或属性
* ElementType.LOCAL_VARIABLE 应用于局部变量
* ElementType.METHOD 应用于方法级
* ElementType.PACKAGE 应用于包声明
* ElementType.PARAMETER 应用于方法的参数
* ElementType.TYPE 应用于类的元素
* *******出现的频率较低*******
* Documented:表示所修饰的注解在被javadoc解析时,保留下来。默认情况下,javadoc是不包括注解的。定义为Documented的注解必须设置Retention值为RUNTIME。
*
* Inherited:被它修饰的 Annotation 将具有继承性。如果某个类使用了被@Inherited 修饰的 Annotation, 则其子类将自动具有该注解。
* 如何证明子类继承了注解--->通过反射获取注解信息 ---到反射内容时系统讲解
* 3.可重复注解:① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
* ② MyAnnotation的Target和Retention等元注解(如:@Inherited)与MyAnnotations相同。
* 4. 类型注解写在@Target注解中:
* ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
* ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
@Inherited
@Repeatable(MyAnnotations.class)//jdk 8开始想要这样使用重复的注解
public @interface MyAnnotation {
//如果只有一个参数成员,建议使用参数名为value。注意这是一个属性不是方法,虽然有()。
//String value() ;//形式一:可以是任意类型的,如果value值可以指定多个可以定义为数组如:String[] value() ;
String value() default "hello";//形式二:通过default关键字指定一个默认值
}
注解MyAnnotations:
package com.atguigu.java1;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Inherited
public @interface MyAnnotations {
MyAnnotation[] value();// jdk 8之前的写法:想要写多个注解声明为数组
}
4 Day18–反射
4.1 概念
4.2 反射的应用
4.3 反射相关的主要API
注意:之前定义类的class是关键字首字母小写。这个Class首字母是大写 它代表的是一个类,用来表示一个通用的类。
4.4 综合测试:(获取反射对象)
此案例测试:
- 使用反射之前创建对象和使用反射之后创建对象获取公有私有资源对比
- 通过直接new的方式或反射的方式都可以调用公共的结构 开发中如何选择
- 反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
- 关于java.lang.Class类的理解,获取Class的实例的4种方式
- Class实例可以是哪些结构的说明
ReflectionTest类:
package com.atguigu.java;
import org.junit.Test;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author shkstart
* @create 2019 上午 10:38
*/
public class ReflectionTest {
//反射之前,对于Person的操作
@Test
public void test1() {
//1.创建Person类的对象
Person p1 = new Person("Tom", 12);
//2.通过对象,调用其内部的属性、方法 (一般属性私有化,通过public公共的方法间接调用私有属性。public方法可以直接调用)
p1.age = 10;
System.out.println(p1.toString());
p1.show();
//在Person类外部,不可以通过Person类的对象调用其内部私有结构。
//比如:name私有属性、showNation()私有方法 以及私有的构造器
}
//反射之后,对于Person的操作
@Test
public void test2() throws Exception{
Class clazz = Person.class;
//1.通过反射,创建Person类的对象
Constructor cons = clazz.getConstructor(String.class,int.class);//获取指定的构造器
Object obj = cons.newInstance("Tom", 12);//通过构造器创建对象
Person p = (Person) obj;//本质上obj是Person类型
System.out.println(p.toString());//Person{name='Tom', age=12}
//2.通过反射,调用对象指定的属性、方法
//调用属性 age此时为public
Field age = clazz.getDeclaredField("age");//获取属性age,里面是属性的名字
age.set(p,10);//修改name属性的值
System.out.println(p.toString());//Person{name='Tom', age=10}
//调用方法 show此时为public
Method show = clazz.getDeclaredMethod("show");//获取方法show,里面是方法的名字。注意同名的方法使用重载的方式区别。
show.invoke(p);//你好,我是一个人
System.out.println("*******************************");
//通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
//调用私有的构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p1 = (Person) cons1.newInstance("Jerry");
System.out.println(p1);//Person{name='Jerry', age=0}
//调用私有的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"HanMeimei");
System.out.println(p1);//Person{name='HanMeimei', age=0}
//调用私有的方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
//相当于String nation = p1.showNation("中国")
String nation = (String) showNation.invoke(p1,"中国");//我的国籍是:中国
System.out.println(nation);//中国
}
/*疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
建议:直接new的方式。
那么什么时候会使用:反射的方式。 反射的特征:动态性 解释动态性:一开始在编译器不能够确定要new的对象是谁,举例:在后面的
开发中项目部署到服务器上,先启动服务器才能通过前端访问后台,通过浏览器访问后台 有可能是登录或者注册,url后面
分别有参数/login登录或者rgist注册,发送到服务器端解析这个参数 发现是登录,此时服务器已经运行了,之后造login对应的对象
调对应的方法。 总结:在运行期间不知道改造那个类的对象,因为不知道你是想注册还是想登录,你发过来是什么就造相关类
的对象 进而调用相关的方法,这就叫做动态性需要用到反射来做。之前是可以确定造什么对象,比如创建Person类的对象,直接new Person()即可。*/
//疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
//不矛盾。封装是指的是私有的资源不能直接调用,反射是私有的也能直接调用 那不是白封装了吗,看似矛盾其实不冲突。解释:封装性指的是私有的资源调用
//可能有风险,可以通过公有的方法调用私有的资源这种方式更好。而反射调用指的是你能不能调用的问题,你通过公有的方法调用私有的可能比直接调用私有的
// 写法更好 建议使用这个,但是非要调用私有的那就调只不过通过公用的方法可能更好。总结:封装性解决的是建议用什么方式调的问题,反射解决的是你能不能调的问题。
/*
关于java.lang.Class类的理解
1.类的加载过程:
编译器:程序(.java源文件)经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
运行期:接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此
运行时类,就作为Class的一个实例。
2.换句话说,Class的实例就对应着加载到内存中的一个运行时类。(所以Class用new的方式赋值不对,new的是编译期对应的类)
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式
来获取此运行时类。
*/
//获取Class的实例的方式(前三种方式需要掌握)。方式三使用频率最多。
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性:.class
// Class本身带了泛型,可以写为:Class<Person> clazz1 = Person.class;指定泛型类型,如果不加泛型像之前学的一样如:用到的时候可能会需要强转。
Class<Person> clazz1 = Person.class;
System.out.println(clazz1);//class com.atguigu.java.Person
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();//Person()
Class clazz2 = p1.getClass();
System.out.println(clazz2);//class com.atguigu.java.Person
//方式三:调用Class的静态方法:forName(String classPath)
//这种方式需要处理异常,因为担心你找不到指定路径的文件 类似于io流要抛出异常
Class clazz3 = Class.forName("com.atguigu.java.Person");//类的全类名:包名+类名 (因为同一个Module的不同的包中类名可能相同)
System.out.println(clazz3);//class com.atguigu.java.Person
//clazz3 = Class.forName("java.lang.String");//可以进行修改,说明不论是系统Api提供的类还是自定义的类都可以作为Class的一个实例
//System.out.println(clazz3);//class java.lang.String
System.out.println(clazz1 == clazz2);//true 说明地址值相同,前三种方式获取的类是同一个
System.out.println(clazz1 == clazz3);//true
//方式四:使用类的加载器:ClassLoader (了解)
// ReflectionTest是当前类
ClassLoader classLoader = ReflectionTest.class.getClassLoader();//获取当前自定义类的类加载器是谁
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");//通过类加载器调用方法获取Class实例。
System.out.println(clazz4);//class com.atguigu.java.Person
System.out.println(clazz1 == clazz4);//true,也是同一个类
}
//万事万物皆对象?对象.xxx,File,URL,反射,前端、数据库操作
//Class实例可以是哪些结构的说明:不止是运行期的类,也可以是如下结构:
/*(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void*/
@Test
public void test4(){
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要数组的元素类型与维度(都是一维数组)一样,就是同一个Class
System.out.println(c10 == c11);//true
}
}
Person类:
package com.atguigu.java;
/**
* @author shkstart
* @create 2019 上午 10:38
*/
public class Person {
private String name;
//为了测试反射把属性改为public
public int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//为了测试反射把构造方法改为private
private Person(String name) {
this.name = name;
}
public Person() {
System.out.println("Person()");
}
public void show(){
System.out.println("你好,我是一个人");
}
//为了测试反射把普通方法改为private
private String showNation(String nation){
System.out.println("我的国籍是:" + nation);
return nation;
}
}
4.5 类的加载过程详解
4.6 类的加载器ClassLoader的理解
4.6.1 类加载器常用方法
4.6.2 ClassLoaderTest测试
ClassLoaderTest类:
package com.atguigu.java;
import org.junit.Test;
import java.io.InputStream;
import java.util.Properties;
/**
* 了解类的加载器
* @author shkstart
* @create 2019 下午 2:16
*/
public class ClassLoaderTest {
//测试1:获取各种类加载器
@Test
public void test1(){
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();//获取当前自定义类的类加载器是谁
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 (系统类加载器)
//调用系统类加载器的getParent():获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@279f2327(扩展类加载器)
//调用扩展类加载器的getParent():无法获取引导类加载器
//引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);//null
//String类的加载器是引导类加载器,不能直接拿到 所以输出位null
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);//null
}
//测试2:Properties(集合):用来读取配置文件。它是Map接口集合的子类Hashtable的子类Properties集合
@Test
public void test2() throws Exception {
Properties pros = new Properties();//集合对象
//在idea中,使用相对路径 此时的文件默认在当前的module下。
//读取配置文件的方式一:使用Map接口集合的子类Hashtable的子类Properties集合读取配置文件 详情见day05 -- 7.6
// FileInputStream fis = new FileInputStream("jdbc.properties");
// FileInputStream fis = new FileInputStream("src\\jdbc1.properties");//这种方式如何读取到当前Module src目录下的properties文件
//一般配置文件都是放在src目录下,如果是Module下部署到Tomact服务器中会缺失
// pros.load(fis);
//读取配置文件的方式二:使用ClassLoader
//在idea中,使用相对路径时,配置文件默认识别为:当前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);//user = 吴飞,password = abc123
}
}
jdbc1.properties配置文件:
user=吴飞
password=abc123
4.7 通过反射,创建运行时类的对象
NewInstanceTest:
package com.atguigu.java;
import org.junit.Test;
import java.util.Random;
/**
* 通过发射创建对应的运行时类的对象
*
* @author shkstart
* @create 2019 下午 2:32
*/
public class NewInstanceTest {
//另一种方式查看 4.11
@Test
public void test1() throws IllegalAccessException, InstantiationException {
Class<Person> clazz = Person.class;//获取Class的实例
/*
newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
1.运行时类必须提供空参的构造器
2.空参的构造器的访问权限得够。通常,设置为public。(>=private)
在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
*/
//需要处理异常
Person obj = clazz.newInstance();// 创建此 Class 对象所表示的类的一个新实例。
System.out.println(obj);//Person{name='null', age=0}
}
}
Person:
package com.atguigu.java;
/**
* @author shkstart
* @create 2019 上午 10:38
*/
public class Person {
private String name;
//为了测试反射把属性改为public
public int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//为了测试反射把构造方法改为private
private Person(String name) {
this.name = name;
}
public Person() {
System.out.println("Person()");
}
public void show(){
System.out.println("你好,我是一个人");
}
//为了测试反射把普通方法改为private
private String showNation(String nation){
System.out.println("我的国籍是:" + nation);
return nation;
}
}
4.8 体会反射的动态性
package com.atguigu.java;
import org.junit.Test;
import java.util.Random;
public class NewInstanceTest {
//体会反射的动态性:编译时不确定创建什么对象,在运行期才能确定造的是什么对象
//举例:使用随机数获取不同的对象 编译期不确定,只能是通过反射来做。
@Test
public void test2(){
for(int i = 0;i < 100;i++){
int num = new Random().nextInt(3);//0,1,2 Random:Api中的一个类 随机数
String classPath = "";
switch(num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.atguigu.java.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
创建一个指定类的对象。
classPath:指定类的全类名
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
}
4.10 获取运行时类的完整结构
4.10.1 提供结构丰富的Person类
Person子类:
package com.atguigu.java1;
/**
* @author shkstart
* @create 2019 下午 3:12
*/
@MyAnnotation(value="hi")
public class Person extends Creature<String> implements Comparable<String>,MyInterface{
private static final long serialVersionUID = 3974356206692991220L;
private String name;
int age;
public int id;
public Person(){}
@MyAnnotation(value="abc")
private Person(String name){
this.name = name;
}
Person(String name,int age){
this.name = name;
this.age = age;
}
@MyAnnotation
private String show(String nation){
System.out.println("我的国籍是:" + nation);
return nation;
}
public String display(String interests,int age) throws NullPointerException,ClassCastException{
return interests + age;
}
@Override
public void info() {
System.out.println("我是一个人");
}
@Override
public int compareTo(String o) {
return 0;
}
private static void showDesc(){
System.out.println("我是一个可爱的人");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
}
Creature父类:
package com.atguigu.java1;
import java.io.Serializable;
/**
* @author shkstart
* @create 2019 下午 3:12
*/
public class Creature<T> implements Serializable {
private static final long serialVersionUID = 3539774587585249102L;
private char gender;
public double weight;
private void breath(){
System.out.println("生物呼吸");
}
public void eat(){
System.out.println("生物吃东西");
}
}
MyInterface接口:
package com.atguigu.java1;
/**
* @author shkstart
* @create 2019 下午 3:15
*/
public interface MyInterface {
void info();
}
MyAnnotation注解:
package com.atguigu.java1;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
/**
* @author shkstart
* @create 2019 下午 3:19
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)//只有声明为RUNTIME生命周期的注解,才能通过反射获取。
public @interface MyAnnotation {
String value() default "hello";
}
4.10.2 获取当前运行时类的属性结构(成员变量)
package com.atguigu.java2;
import com.atguigu.java1.Person;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/**
* 获取当前运行时类的属性结构
*
* @author shkstart
* @create 2019 下午 3:23
*/
public class FieldTest {
//获取类里面的所有属性:
@Test
public void test1(){
Class clazz = Person.class;
//获取属性结构
//getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for(Field f : fields){
/*输出结果:
public int com.atguigu.java1.Person.id
public double com.atguigu.java1.Creature.weight*/
System.out.println(f);
}
System.out.println();
//getDeclaredFields():获取当前运行时类中声明的所有属性,包括私有的。(不包含父类中声明的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
/*输出结果:
private static final long com.atguigu.java1.Person.serialVersionUID
private java.lang.String com.atguigu.java1.Person.name
int com.atguigu.java1.Person.age
public int com.atguigu.java1.Person.id*/
System.out.println(f);
}
}
//获取类里面属性的具体结构:权限修饰符 数据类型 变量名 = 变量值
//非静态:只能拿到:权限修饰符 数据类型 变量名 至于变量值暂时拿不到,想要拿到需要有对象,这里只测试非静态
//静态:权限修饰符 数据类型 变量名 变量值都可以拿到
@Test
public void test2(){
Class clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
//1.权限修饰符
int modifier = f.getModifiers();
//输出结果:直接输出返回的是一个一个的数字,反射下的Modifier类中 每个数字对应的有权限修饰符
//注意,如果输出的数字是0代表默认的权限,所以输出啥也没有。
System.out.print(modifier+"\t" );//输出数字
System.out.print(Modifier.toString(modifier)+"\t" );//输出数字对应的权限修饰符
//2.数据类型
Class type = f.getType();
System.out.print(type.getName()+"\t" );
//3.变量名
String fName = f.getName();
System.out.print(fName);
System.out.println();//打印一个变量的所有信息后再换行
}
/*输出结果:
26 private static final long serialVersionUID
2 private java.lang.String name
0 int age
1 public int id */
//注意:String权限修饰符会保留包名为private static final,因为自己也可以定义一个类叫作String,为了和lang包下的String进行区分
// 所以会以全类名的方式显示。
}
}
4.10.3 获取运行时类的方法结构
package com.atguigu.java2;
import com.atguigu.java1.Person;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 获取运行时类的方法结构
*
* @author shkstart
* @create 2019 下午 3:37
*/
public class MethodTest {
//获取类里面的所有方法:
@Test
public void test1(){
Class clazz = Person.class;
//getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
for(Method m : methods){
System.out.println(m);
}
System.out.println();
//还有很多方法,具体查看Api
//getDeclaredMethod(String name, 类<?>... parameterTypes) :获取一个指定的方法对象
// 参数1:方法的名称 参数1:方法的名称参数数组
//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[] declaredMethods = clazz.getDeclaredMethods();//获取当前运行时类中声明的所有方法,包括私有的。
for(Method m : declaredMethods){
//1.获取方法声明的注解 只有声明为RUNTIME生命周期的注解,才能通过反射获取。
Annotation[] annos = m.getAnnotations();//方法上可以写多个注解,所以获取的是注解数组 需要进一步的遍历
for(Annotation a : annos){
System.out.println(a);
}
//2.权限修饰符 类似于获取属性的权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + "\t");
//3.返回值类型
System.out.print(m.getReturnType().getName() + "\t");
//4.方法名
System.out.print(m.getName());
System.out.print("(");
//5.形参列表
Class[] parameterTypes = m.getParameterTypes();//获取参数类型数组,如果没有参数那么返回的类型数组长度为0
//parameterTypes == null && parameterTypes.length == 0代表数组里面没有参数。
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;i++){
if(i == exceptionTypes.length - 1){
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ",");
}
}
System.out.println();
}
}
}
4.10.4 获取运行时类的其它结构
获取的结构有:
- 获取构造器结构
- 获取运行时类的父类
- 获取运行时类的带泛型的父类
- 获取运行时类的带泛型的父类的泛型
- 获取运行时类实现的接口
- 获取运行时类所在的包
- 获取运行时类声明的注解
package com.atguigu.java2;
import com.atguigu.java1.Person;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* @author shkstart
* @create 2019 下午 4:19
*/
public class OtherTest {
/*
获取构造器结构
*/
@Test
public void test1(){
Class clazz = Person.class;
//getConstructors():获取当前运行时类中声明为public的构造器,不包括父类的
Constructor[] constructors = clazz.getConstructors();
for(Constructor c : constructors){
System.out.println(c);
}
System.out.println();
//getDeclaredConstructors():获取当前运行时类中声明的所有的构造器,包括私有的
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor c : declaredConstructors){
System.out.println(c);
}
//至于构造器方法内部的结构和方法类似也能做。
}
/*
获取运行时类的父类
*/
@Test
public void test2(){
Class clazz = Person.class;
Class superclass = clazz.getSuperclass();
System.out.println(superclass);//class com.atguigu.java1.Creature 输出的类不带泛型
}
/*
获取运行时类的带泛型的父类
*/
@Test
public void test3(){
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);//com.atguigu.java1.Creature<java.lang.String> 输出的类带泛型
}
/*
获取运行时类的带泛型的父类的泛型
代码:逻辑性代码 vs 功能性代码
*/
@Test
public void test4(){
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//获取泛型类型 为什么是数组:比如Map的泛型类上就有2个,所以为数组。因为这里测试只有一个就不在遍历了。
Type[] actualTypeArguments = paramType.getActualTypeArguments();
//不加getTypeName()或者getName()输出为:class java.lang.String,这个方法可以去掉class
// System.out.println(actualTypeArguments[0].getTypeName()); 方式一 输出:java.lang.String
System.out.println(((Class)actualTypeArguments[0]).getName()); //方式二 输出:java.lang.String
}
/*
获取运行时类实现的接口 (即Person的父接口)
*/
@Test
public void test5(){
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for(Class c : interfaces){
System.out.println(c);
}
System.out.println();
//获取运行时类的父类实现的接口 (Person父接口的父接口)
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for(Class c : interfaces1){
System.out.println(c);
}
}
/*
获取运行时类所在的包
*/
@Test
public void test6(){
Class clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
/*
获取运行时类声明的注解
*/
@Test
public void test7(){
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for(Annotation annos : annotations){
System.out.println(annos);
}
}
}
4.11 调用运行时类的指定结构
调用运行时类中指定的结构:属性、方法、构造器
ReflectionTest类:
package com.atguigu.java2;
import com.atguigu.java1.Person;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 调用运行时类中指定的结构:属性、方法、构造器
*
* @author shkstart
* @create 2019 下午 4:46
*/
public class ReflectionTest {
/*
不需要掌握,因为开发中变量都是私有的
*/
//测试:获取指定的属性,要求运行时类中属性声明为public
@Test
public void testField() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
//获取指定的属性:要求运行时类中属性声明为public
//通常不采用此方法 抛异常
Field id = clazz.getField("id");
/*
设置当前属性的值 因为这里的属性的非静态的 所以需要创建对象
set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少
*/
id.set(p,1001);
/*
获取当前属性的值
get():参数1:获取哪个对象的当前属性值
*/
int pId = (int) id.get(p);
System.out.println(pId);
}
/*
如何操作运行时类中的指定的属性 -- 需要掌握
*/
//测试:获取运行时类中指定变量名的属性,包括私有的
@Test
public void testField1() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
//1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");
//2.保证当前属性是可访问的,如果不进行设置 ,私有的 默认的 protected的属性虽然可以拿到但是不能够赋值和取值。不设置想要使用只能是public的属性。
name.setAccessible(true);
//3.获取、设置指定对象的此属性值
name.set(p,"Tom");
System.out.println(name.get(p));
//获取静态属性和获取静态方法方式差不多 set时只写一个参数 当前类.class 或者 只写一个Null,get时写一个当前类.class
}
/*
如何操作运行时类中的指定的方法 -- 需要掌握
*/
@Test
public void testMethod() throws Exception {
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
/*
1.获取指定的某个方法
getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表(因为方法有可能是重名重载的)
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);
/*
3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
*/
Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
System.out.println(returnValue);
System.out.println("*************如何调用静态方法*****************");
// private static void showDesc(){
// System.out.println("我是一个可爱的人");
// }
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值 void,则此invoke()返回null
// Object returnVal = showDesc.invoke(null); 方式一 2种方式:只写一个参数 当前类.class 或者 只写一个Null
Object returnVal = showDesc.invoke(Person.class);//方式二
System.out.println(returnVal);//null
}
/*
如何调用运行时类中的指定的构造器
*/
@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;
//private Person(String name)
/*
1.获取指定的构造器
getDeclaredConstructor():参数:指明构造器的参数列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器是可访问的
constructor.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");//形参的值
System.out.println(per);//Person{name='Tom', age=0, id=0}
}
}
Person类:
package com.atguigu.java1;
/**
* @author shkstart
* @create 2019 下午 3:12
*/
@MyAnnotation(value="hi")
public class Person extends Creature<String> implements Comparable<String>,MyInterface{
private static final long serialVersionUID = 3974356206692991220L;
private String name;
int age;
public int id;
public Person(){}
@MyAnnotation(value="abc")
private Person(String name){
this.name = name;
}
Person(String name,int age){
this.name = name;
this.age = age;
}
@MyAnnotation
private String show(String nation){
System.out.println("我的国籍是:" + nation);
return nation;
}
public String display(String interests,int age) throws NullPointerException,ClassCastException{
return interests + age;
}
@Override
public void info() {
System.out.println("我是一个人");
}
@Override
public int compareTo(String o) {
return 0;
}
private static void showDesc(){
System.out.println("我是一个可爱的人");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id +
'}';
}
}
4.12 总结创建java创建对象的方式(3种)
说明:创建类的对象的方式?
- 方式一:new +构造器
- 方式二:要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在。可以调用其静态方法,创建Xxx对象。(本质上还是new +构造器,只不过帮你封装起来了,不建议你去直接new的方式创建,涉及到封装性的体现。)
- 方式三:通过反射
4.13 反射的应用- -动态代理(655集)
4.13.1 概述
4.13.2 测试静态代理
package com.atguigu.java;
/**
* 静态代理举例
*
* 特点:代理类和被代理类在编译期间,就确定下来了。
*
* @author shkstart
* @create 2019 上午 10:11
*/
interface ClothFactory{
void produceCloth();
}
//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike工厂生产一批运动服");
}
}
//代理类
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些后续的收尾工作");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
//创建被代理类的对象
ClothFactory nike = new NikeClothFactory();
//创建代理类的对象
ClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
/* 输出:
代理工厂做一些准备工作
Nike工厂生产一批运动服
代理工厂做一些后续的收尾工作 */
}
}
4.13.3 测试动态代理
package com.atguigu.java;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*
* 动态代理的举例
*
* @author shkstart
* @create 2019 上午 10:18
*/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
class HumanUtil{
public void method1(){
System.out.println("====================通用方法一====================");
}
public void method2(){
System.out.println("====================通用方法二====================");
}
}
/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
*/
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
//Proxy :反射下的一个类
// Proxy.newProxyInstance(xx xx,xx xx ,xx xx):创建代理类的对象
//参数1:对象的类加载器 参数2:被代理类实现的接口 参数3:
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil util = new HumanUtil();
util.method1();
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj,args);
util.method2();
//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("四川麻辣烫");
System.out.println("*****************************");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}
4.13.4 动态代理与AOP
5 Day19–网络通信(Socket通信)
说明:io流是在本地进行的数据传输,这是在网络中进行的数据传输,这里简单讲下重点放在web阶段讲解。
5.1 网络通信概述
概述:网络通信其实就是Socket间的通信 ,数据在两个Socket间通过IO传输。本质上,网络编程就是把数据抽象成IO流的形式 在网络中传输。
5.2 网络通信要素概述
说明:网络编程通讯的2个问题分别通过:ip 端口号和协议进行解决。
5.3 二个通信要素详解
5.3.1 ip和端口号
1,IP地址介绍:
1.1 说明:类似于File类, 进行本地文件的读入写出操作,需要一个端点File类,File就对应本地硬盘中的一个文件。而ip地址就是网络传输中的一个节点,在java中万事万物皆对象,用InetAddress类表示。最终ip+端口号组成一个网络中的节点Socket 在考虑网络通讯协议,这样不同的主机就可以进行实现数据传输了。
1.2 概述:
1.3 继承结构:
1.4 创建对象:没有提供构造方法,可以根据静态方法返回这个对象实例:
1.5 主机地址的2种表示方式:
1.6 测试:
package com.java;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 一、网络编程中有两个主要的问题:
* 1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
* 2.找到主机后如何可靠高效地进行数据传输
*
* 二、网络编程中的两个要素:
* 1.对应问题一:IP和端口号
* 2.对应问题二:提供网络通信协议:TCP/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(可以看成是域名)
*
* 6. 如何实例化InetAddress:两个方法:getByName(String host) 、 getLocalHost()
* 两个常用方法:getHostName() / getHostAddress()
*
* 7. 端口号:正在计算机上运行的进程。
* 要求:不同的进程有不同的端口号
* 范围:被规定为一个 16 位的整数 0~65535。
*
* 8. 端口号与IP地址的组合得出一个网络套接字:Socket
* @author shkstart
* @create 2019 下午 2:30
*/
public class InetAddressTest {
public static void main(String[] args) {
try {
//类似于:File file = new File("hello.txt"); 参数:域名或ip地址
InetAddress inet1 = InetAddress.getByName("192.168.10.14");//ip地址
System.out.println(inet1);//192.168.10.14
InetAddress inet2 = InetAddress.getByName("www.atguigu.com");//域名
System.out.println(inet2);//www.atguigu.com/111.7.163.158
InetAddress inet3 = InetAddress.getByName("127.0.0.1");
System.out.println(inet3);///127.0.0.1
//获取本地ip
InetAddress inet4 = InetAddress.getLocalHost();
System.out.println(inet4);//DESKTOP-4323HLC/169.254.173.57(这个获取的是局域网内的本机ip地址,实际上就是127.0.0.1)
//getHostName() 获取域名
System.out.println(inet2.getHostName());//www.atguigu.com
//getHostAddress() 获取ip地址
System.out.println(inet2.getHostAddress());//111.7.163.158
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
2 ,端口号介绍:
2.1 概述:
5.3.2 协议
概述:
TCP/IP协议簇:
TCP (如:传输文件,发送邮件,浏览网页)和 UDP(如:域名查询,语音通话,视频直播,隧道网络等)对比:
隧道网络:比如VPN。
三次握手,四次挥手:
解释:TCP在建立连接时需要使用3次握手,在断开连接时需要四次挥手。
现实生活举例:客户端:小明 服务端:马云
注意: 只能是客户端主动进行握手。
第一次握手:小明对马云说:你好我是小明。
第二次握手:马云对小明说:你好我知道你是小明,我叫马云
第三次握手:小明对马云说:我知道 你知道我是小明你是马云。
三次握手基本上可以保证双方都在,比如1次握手50%,2次握手80%,三次握手99%,超过三次握手双方都在的概率提升的比较少,握手次数哦太多反而会浪费时间。
现实生活举例:客户端, 服务端
注意:客户端 和服务端都能主动释放连接,但一般是客户端释放连接而服务端不会关闭连接 如:你到百度上看新闻 不想看了就关闭电脑,随时看随时打开电脑,要是服务端也关闭相当于百度就打不了。
第一次挥手:客户端对服务端说:我想断开连接了。
第二次挥手:服务端对客户端说:我知道你想断开连接了。
第三次挥手:服务端断开连接后对客户端说:我已经断开连接了
第四次挥手:客户端发送数据到服务端验证还能不能收到信息,收不到说明断开连接。
5.4 TCP网络编程案例测试
5.4.1 客户端发送信息给服务端,服务端将数据显示在控制台上
package com.java;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 实现TCP的网络编程
* 例子1:客户端发送信息给服务端,服务端将数据显示在控制台上
* 注意:需要先启动服务器,在启动客户端。如果先启动客户端建立连接 服务器没有启动,则不会连接成功。
* @author shkstart
* @create 2019 下午 3:30
*/
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(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();//socket类似于流 也需要自己关闭。
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//服务端
@Test
public void server() {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
//为什么不指定服务器的ip,在哪个服务器跑的当然知道自己这个服务器的ip。
ss = new ServerSocket(8899);
//2.调用accept()表示接收来自于客户端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();//返回此套接字的输入流。
//不建议这样写,可能会有乱码。因为用的是字节流读取汉字如果存不下会有乱码,UTF-8一个汉字占3个字节,new byte[5],第二个汉字就存不下了。
// 解决: 1.可以扩大字节数组new byte[1000],但是如果写出的数据多了同样不行。2.使用ByteArrayOutputStream
// byte[] buffer = new byte[1024];
// int len;
// while((len = is.read(buffer)) != -1){
// String str = new String(buffer,0,len);//还原为字符串可能会出现乱码。
// System.out.print(str);
// }
//4.读取输入流中的数据
//这个对象有没有指定文件路径,那么数据写到哪了???这个类本身提供了一个数组,不够的话会自动扩容。
//来几个数据依次拼接到数组中,最后统一还原为字符串
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = is.read(buffer)) != -1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());//把里面的所有字节数组转化为字符串
//Socket提供的方法: InetAddress getInetAddress() 返回套接字连接的地址。
//InetAddress(ip类): String getHostAddress() 返回IP地址字符串(以文本表现形式)。
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(baos != null){
//5.关闭资源 先关闭外层,ByteArrayOutputStream ,InputStream, socket ,ServerSocket
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();
}
}
}
}
}
5.4.2 客户端发送文件给服务端,服务端将文件保存在本地。
package com.java;
import org.junit.Test;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
*
* 实现TCP的网络编程
* 例题2:客户端发送文件给服务端,服务端将文件保存在本地。
*
* @author shkstart
* @create 2019 下午 3:53
*/
public class TCPTest2 {
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void client() throws IOException {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
OutputStream os = socket.getOutputStream();
//3. 这里的数据来自于文件所以先把数据读进来。
FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);//把读进来的数据写出去
}
//5.
fis.close();
os.close();
socket.close();
}
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void server() throws IOException {
//1.
ServerSocket ss = new ServerSocket(9090);
//2.
Socket socket = ss.accept();
//3.
InputStream is = socket.getInputStream();
//4.
FileOutputStream fos = new FileOutputStream(new File("beauty1.jpg"));//保存到本地
//5.
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
//6.
fos.close();
is.close();
socket.close();
ss.close();
}
}
5.4.3 客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端
package com.java;
import org.junit.Test;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 实现TCP的网络编程
* 例题3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。
* 并关闭相应的连接。
* @author shkstart
* @create 2019 下午 4:13
*/
public class TCPTest3 {
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void client() throws IOException {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
OutputStream os = socket.getOutputStream();
//3.
FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//关闭数据的输出
socket.shutdownOutput();
//5.接收来自于服务器端的数据,并显示到控制台上
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bufferra = new byte[20];
int len1;
while((len1 = is.read(bufferra)) != -1){
baos.write(bufferra,0,len1);
}
System.out.println(baos.toString());
//6.
fis.close();
os.close();
socket.close();
baos.close();
}
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void server() throws IOException {
//1.
ServerSocket ss = new ServerSocket(9090);
//2.
Socket socket = ss.accept();
//3.
InputStream is = socket.getInputStream();
//4.
FileOutputStream fos = new FileOutputStream(new File("beauty4.jpg"));
//5.
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
/*如果客户端不调用socket.shutdownOutput()这个方法,"图片传输完成"在控制台输出不来 图片也没有复制不成功,
说明是客户端发送数据 和服务端接收数据之间出现的问题。为什么会产生这个原因呢????
因为read方法是个阻塞式的方法,在服务端读取客户端发送的数据时 没有明确告诉的话这个方法不会退出循环,
写在while中的方法是 判断文件没有的话返回为-1,在客户端传输据没有给个明确的指示什么时候传完数据,所以在服务端
接收数据时一直等着客户端发送数据 没有给个明确的指示 客户端数据何时传完。它会一直停留在while循环中出不去,也就不会执行下面的代码了。*/
System.out.println("图片传输完成");
//6.服务器端给予客户端反馈
OutputStream os = socket.getOutputStream();
os.write("你好,美女,照片我已收到,非常漂亮!".getBytes());
//7.
fos.close();
is.close();
socket.close();
ss.close();
os.close();
}
}
5.4.4 客户端和服务端理解
5.5 UDP网络编程
5.5.1 概述
5.5.2 测试
package com.java;
import org.junit.Test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDPd协议的网络编程
*
* 先启动接收端,在启动发送端
* @author shkstart
* @create 2019 下午 4:34
*/
public class UDPTest {
//发送端
@Test
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();
String str = "我是UDP方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
//指定服务端的ip和端口号 封装为数据报
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);//把数据接收到数据报packet中,本质上是在buffer中
//packet.getData()获取packet中的字节数组,从0开始看你写进去几个
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
}
5.6 URL编程
说明:不在写客户端和服务端了,如:直接在浏览器输入地址访问服务器里的资源。
5.6.1 测试1:URL常用方法测试
package com.java;
import java.net.MalformedURLException;
import java.net.URL;
/**
* URL网络编程
* 1.URL:统一资源定位符,对应着互联网的某一资源地址
* 2.格式:
* http://localhost:8080/examples/beauty.jpg?username=Tom
* 协议 主机名 端口号 资源地址 参数列表
*
* @author shkstart
* @create 2019 下午 4:47
*/
public class URLTest {
public static void main(String[] args) {
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom");
// 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();
}
}
}
5.6.2 测试2:从tomact服务器下载文件
package com.java;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author shkstart
* @create 2019 下午 4:54
*/
public class URLTest1 {
//说明:beauty.jpg 为本机tomact服务器下的一个资源
//需求:把服务器的资源文件 beauty.jpg下载下来。
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 urlConnection = url.openConnection():获取服务器的一个连接对象
//http协议获取的是 HttpURLConnection的一个连接对象,又是URLConnection的子类,所以可以强转。
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();
}
}
}
}