目录
单例设计模式
单例设计模式:
保证类在内存中只有一个对象。
如何保证类在内存中只有一个对象呢?
(1)控制类的创建,不让其他类来创建本类的对象。private
(2)在本类中定义一个本类的对象。Singleton s;
(3)提供公共的访问方式。 public static Singleton getInstance(){return s}
单例写法两种:
第一种格式 饿汉式 推荐开发用这种方式
演示:
/* * 饿汉式 更好,在开发的时候用 * 上来就想吃(上来就想new 对象) */ class Singleton{ //1.私有构造方法 ,其他类不能访问该构造方法了,即不能创建此类对象 private Singleton() {} //2.创建本类对象 private static Singleton s = new Singleton(); //s是成员变量,必须用对象去调用他,但现在没有对象,我们就只能用类名.调用 所以加上静态 //3.对外提供公共的访问方法 public static Singleton getInstance() { //getInstance 获取实例(对象) return s; } }
第二种格式 懒汉式 面试写这种方式
演示:
/* * 懒汉式 * 先声明引用,然后再做一个判断,啥时候需要再创建对象 * 为什么不好: * 如果是多线程访问,第一条线程进行判断s == null,条件满足进入if内部,刚准备执行内部代码块。 * 这时不小心线程的执行权被别的线程抢去了,结果线程一在s = new Singleton();语句前等待 ,这时线程二进入if内部(线程二 s == null) * 线程一突然抢回执行权,线程二在s = new Singleton();语句前等待,线程一创建一个对象,return s给返回了。线程二接着也抢到执行权,创建一个对象。 * 这样,一共创建两次对象,就不是单例设计模式了。因此,懒汉式在多线程访问的时候,他会有安全隐患————他有可能会创建多个对象出来。 * 所以,懒汉式只在面试的时候用。 面试时,他让你写一个单例的延迟加载模式,其实问的就是懒汉式。 */ class Singleton{ //1.私有构造方法 ,其他类不能访问该构造方法了,即不能创建此类对象 private Singleton() {} //2.声明一个引用 private static Singleton s ; //3.对外提供公共的访问方法 public static Singleton getInstance() { if(s == null) { //s == null 没有创建对象 s = new Singleton(); } return s; } }
第三种格式 没名字
演示:
class Singleton{ //1.私有构造方法 ,其他类不能访问该构造方法了,即不能创建此类对象 private Singleton() {} //2.声明一个引用 public static final Singleton s = new Singleton(); //加final,s不可改变 }
饿汉式和懒汉式的区别
1.饿汉式是空间换时间 上来就去创建一个对象,把空间给浪费了。但是节省了时间,因为你想要,我只接就返回给你。现在时间更重要
懒汉式是时间换空间 上来做个声明,不去创建,什么时候用什么时候给你创建。调用方法的时候做判断s == null,判断就浪费了我们的时间,每一次都要做判断,每一次都浪费时间,比较亏
2.在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象。
Runtime类(懒汉式单例设计模式的应用场景)
Runtime类是一个单例类,概述:
public class Runtime extends Object 每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。可以通过getRuntime方法获取当前运行时。
应用程序不能创建自己的Runtime类实例。
api中没有构造方法(一个类里没有构造方法,说明构造方法被私有了),但并不是所有方法都为静态。
Runtime类的成员方法:
public static Runtime getRuntime() 返回与当前Java应用程序相关的运行时对象。(说明这个类其实是私有构造。在本类创建对象,对外提供这么一个公共的访问方式。)
源代码:
private static Runtime currentRuntime = new Runtime(); //私有了一个本类对象 自己创建了一个本类对象 currentRuntime当前的一个运行时对象 public static Runtime getRuntime() { return currentRuntime; //返回这个currentRuntime } private Runtime() {} //私有构造方法
private Runtime() {} //私有构造方法
public Process exec(String command) 在单独的进程中执行指定的字符串命令。
演示:
public class Demo2_Runtime {
public static void main(String[] args) throws IOException {
Runtime r = Runtime.getRuntime(); //获取运行时对象
// r.exec("shutdown -s -t 300"); //设置系统在5分钟后关机 第一次把系统给修改了
r.exec("shutdown -a"); //取消注销计划(不会关机了) 第二次修改你修改后的结果,我们操作的是同一个对象 这就需要用到单例设计模式
}
}
Timer类
Timer类:计时器,概述:
public class Timer extends Object 一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
与每个Timer对象相对应的是单个后台线程,用于顺序地执行所有计时器任务。计时器任务应该迅速完成。如果完成某个计时器任务的时间太长,那么它会“独占“计时器的任务执行线程。因此,这就可能延迟后续任务的执行,而这些任务就可能”堆在一起”,并且在上述不友好的任务最终完成时才能够被快速连续地执行。
对Timer对象最后的引用完成后,并且所有未处理的任务都已执行完成后,计时器的任务执行线程会正常终止(并且成为垃圾回收的对象)。但是这可能要很长时间后才发生。默认情况下,任务执行线程并不作为守护线程来运行,所以他能够阻止应用程序终止。如果调用者想要快速终止计时器的任务执行线程,那么调用者应该调用计时器的cancel方法。
如果意外终止了计时器的任务执行线程,例如调用了它的stop方法。那么所有以后对该计时器安排的尝试都将导致IllegalStateException,就好像调用了计时器的cancel方法一样。
此类是线程安全的:多个线程可以共享单个Timer对象而无需进行外部同步。
此类不提供实时保证:它使用Object.wait(long)方法来安排任务。
实现注意事项:此类可扩展到大量同时安排的任务(存在数千个都没有问题)。在内部,它使用二进制堆来表示其任务队列,所以安排任务的开销是O(log n),其中n是同时安排的任务数。
实现注意事项:所有构造方法都启动计时器线程。
Timer类的成员方法:
public void schedule(TimerTask task, Date time) 安排在指定的时间执行指定的任务。
public void schedule(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定延迟执行。以近似固定的时间间隔(由指定的周期分隔)进行后续执行。
在固定延迟执行中,根据前一次执行的实际执行时间来安排每次执行。如果由于任何原因(如垃圾回收或其他后台活动)而延迟了某次执行,则后续执行也将被延迟。在长期运行中,执行的频率一般要稍慢于指定周期的倒数(假定 Object.wait(long) 所依靠的系统时钟是准确的)。
固定延迟执行适用于那些需要“平稳”运行的重复执行活动。换句话说,它适用于在短期运行中保持频率准确要比在长期运行中更为重要的活动。这包括大多数动画任务,如以固定时间间隔闪烁的光标。这还包括为响应人类活动所执行的固定活动,如在按住键时自动重复输入字符。
TimerTask类的概述:
public abstract class TimerTask extends Object implements Runnable 由Timer安排为一次执行或重复执行的任务。
TimerTask类的成员方法:
public abstract void run() 此计时器任务要执行的操作。父类实现了Runnable接口,所以run()来自于Runnable接口。该方法抽象,所以任务是交给子类去完成的。
Date类(util包下的)的成员方法:
public Date(int year, int month, int date, int hrs, int min, int sec) 已过时。 从 JDK 1.1 开始,由 Calendar.set(year + 1900, month, date, hrs, min, sec) 或 GregorianCalendar(year + 1900, month, date, hrs, min, sec) 取代。
分配 Date 对象,并初始化此对象,以表示本地时区中由 year、month、date、hrs、min 和 sec 参数指定的秒的开始瞬间。
参数:
year - 减1900的年份。
month - 0-11之间的月份。
date - 一月中1-31之间的某一天。
hrs - 0-23之间的小时数。
min - 0-59之间的分钟数。
sec - 0-59之间的秒数。
演示:
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Demo3_Timer {
public static void main(String[] args) throws InterruptedException {
//Timer计时器类
Timer t = new Timer();
//在指定时间安排指定任务,第一个参数是安排的任务,第二个参数是执行的时间,第三个参数是过多长时间再重复执行
t.schedule(new MyTimerTask(), new Date(118, 11, 23, 11, 49, 05), 3000); //3000为毫秒值,意思为过3秒使设定的任务再执行一次
//隔一秒钟打印一次时间
while(true) {
Thread.sleep(1000);
System.out.println(new Date());
}
}
}
class MyTimerTask extends TimerTask {
public void run() {
System.out.println("起床读英文报纸。");
}
}
两个线程间的通信
什么时候需要通信
多个线程并发执行时,在默认情况下CPU是随机切换线程的。
如果我们希望他们有规律的执行,就可以使用通信,例如每个线程执行一次打印。
怎么通信
如果希望线程等待,就调用wait()
如果希望唤醒等待的线程,就调用notify();
这两个方法必须在同步代码中执行,并且使用同步锁对象来调用。
Object类中的成员方法:
public final void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
public final void notify() 唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
一次只能有一个线程拥有对象的监视器。
演示:
/**
* 等待唤醒机制
*/
public class Demo1_Notify {
public static void main(String[] args) {
final Printer p = new Printer(); //局部内部类在使用局部变量的时候,必须用final修饰
new Thread() {
public void run() {
while(true) {
try {
p.print1();
} catch (InterruptedException e) { //run()中的异常只能自己处理,父类没有抛异常,子类当然不能抛异常
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
/*如果想要两条线程交替打印,就需要等待唤醒机制
* 两条线程同时全都开启,比如先执行第一个方法,做判断1 == 1不满足条件,然后输出了一次Hello! flag改成2.现在没有等待的线程,但也可以随机唤醒一下。
* 如果当前线程还有执行权,再进入方法体做判断2 != 1满足条件,当前线程等待。执行权让出,让另一条线程去执行。
* 另一条线程跑起来,调用Print2方法,做判断flag为2 == 2不满足条件,执行了一次World! flag改为1,this.notify();把等待的线程叫醒了。
* 又有可能他还拥有执行权,再进入方法体做判断1 != 2满足条件,这条线程也去等待了。此时活着的线程是之前刚被叫醒的那个执行print1()的线程。
* 然后在最先执行的那条线程再重复先前的步骤 判断,不符合条件 - 打印 - flag改变值 - 唤醒一个等待中的线程 - 判断,符合条件 - 等待
* 等待之前,他们都唤醒了对方,这样就形成了一个间隔输出
*/
class Printer {
private int flag = 1;
public void print1() throws InterruptedException {
synchronized(this) {
if(flag != 1) {
this.wait(); //当前线程等待
}
System.out.print("H");
System.out.print("e");
System.out.print("l");
System.out.print("l");
System.out.print("o");
System.out.print("!");
System.out.print("\r\n");
flag = 2;
this.notify(); //随机唤醒单个等待的线程
}
}
public void print2() throws InterruptedException {
synchronized(this) {
if(flag != 2) {
this.wait();
}
System.out.print("W");
System.out.print("o");
System.out.print("r");
System.out.print("l");
System.out.print("d");
System.out.print("!");
System.out.print("\r\n");
flag = 1;
this.notify();
}
}
}
三个或三个以上间的线程通信
多个线程通信的问题
JDK5之前无法唤醒指定的一个线程。
如果多个线程之间通信,需要使用notifyAll()通知所有线程,用while来反复判断条件。
Object类中的成员方法:
public final void notifyAll() 唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
演示:
public class Demo2_NotifyAll {
public static void main(String[] args) {
final Printer2 p = new Printer2();
new Thread() {
public void run() {
while(true) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
/*
* 极端情况:三条线程都启动,首先第二个线程执行,执行print2()做判断flag为1 != 2,所以第二条线程等待。
* 然后,第三条线程得到线程执行权,进入Print3()的方法体中,做判断flag为1 != 3条件满足,所以第三条线程等待。
* 活着的线程只剩线程1,执行print()做判断,1 == 1不满足条件。输出了一次Hello! flag改为2,再执行this.notify()
* 很有可能线程1仍还有执行权,再次进入方法体,2 != 1满足条件,线程1等待。但之前执行的this。notify()可是随机唤醒,不一定唤醒的是线程二还是线程三
* 假如唤醒的是线程三,线程三醒来后,不会再回去判断条件。if语句从上往下执行,线程三之前在if语句的方法体中等待,就在此醒来。
* 醒来后,紧接着就执行打印no bug! 并将flag改为1.但这不是我们想要的,我们想要他打印完Hello!后紧接着打印World!
* 很显然,问题出现了:他没有去判断标记(判断了标记,就是去执行该执行的)。我每一次都想让线程回去判断一下标记,if语句是不判断的,但while语句判断,因为while语句是循环的。
*
* 但如果将if语句都改成while语句, 程序仅执行有限次(随机)就会自己停住,所有线程都进入等待状态。
* 再次分析:三条线程都启动,首先第二个线程执行,执行print2()做判断flag为1 != 2条件满足,所以等待。
* 然后,第三条线程得到线程执行权,进入Print3()的方法体中,做判断flag为1 != 3条件满足,所以等待。
* 活着的线程只剩线程1,执行print()做判断,1 == 1不满足条件。输出了一次Hello! flag改为2,再执行this.notify()可能会唤醒线程2也可能会唤醒线程3
* 假如唤醒的是线程三.很有可能线程1仍还有执行权,进入Print1()方法体进行判断,2 != 1满足条件,线程1等待。
* 唤醒的3要做一个判断,while不像if,if在哪睡在哪起。while在哪等待,还需要回到条件再做一次判断。
* flag为2 != 3条件是满足的,满足条件线程3又等待了。结果没有活着的线程了!所有线程都在等待,程序就死掉了。
*
* 解决方法:this.notifyAll(); 唤醒所有的,谁满足条件,谁就来。
* 最后一次分析:三条线程都启动,首先第二个线程执行print2()做判断flag为1 != 2条件满足,所以等待。
* 然后,第三条线程得到线程执行权,进入Print3()的方法体中,做判断flag为1 != 3条件满足,所以等待。
* 活着的线程只剩线程1,执行print()做判断,1 == 1不满足条件。输出了一次Hello! flag改为2,紧接着执行this.notifyAll()就唤醒了所有等待的线程
* 很有可能线程1仍还有执行权,进入Print1()方法体进行判断,2 != 1满足条件,线程1等待。但另外两个线程现在都是活着的,
* 我们去执行线程3,做判断2 != 3条件满足,线程3等待了。但是活着的还有线程2
* 线程2执行,2 != 2,条件不满足,所以输出一次World! flag改为3,再执行this.notifyAll()唤醒了所有等待的线程(也就是说,不管符不符合规则、到没到时间,叫起来就行)
* 但这是咱们在1.5版本之前问题的解决方案,1.5版本后有更好的,叫互斥锁
*/
class Printer2 {
private int flag = 1;
public void print1() throws InterruptedException {
synchronized(this) {
/*if(flag != 1) {
this.wait();
}*/
while(flag != 1) {
this.wait();
}
System.out.print("H");
System.out.print("e");
System.out.print("l");
System.out.print("l");
System.out.print("o");
System.out.print("!");
System.out.print("\r\n");
flag = 2;
// this.notify();
this.notifyAll();
}
}
public void print2() throws InterruptedException {
synchronized(this) {
/*if(flag != 2) { //线程二在此等待
this.wait();
}*/
while(flag != 2) {
this.wait();
}
System.out.print("W");
System.out.print("o");
System.out.print("r");
System.out.print("l");
System.out.print("d");
System.out.print("!");
System.out.print("\r\n");
flag = 3;
// this.notify();
this.notifyAll();
}
}
public void print3() throws InterruptedException {
synchronized(this) {
/*if(flag != 3) { //线程三在此等待,if语句是在哪里等待,就在哪里起来
this.wait();
}*/
while(flag != 3) { //while循环是循环判断,每次都会判断标记,
this.wait();
}
System.out.print("n");
System.out.print("o");
System.out.print(" ");
System.out.print("b");
System.out.print("u");
System.out.print("g");
System.out.print("!");
System.out.print("\r\n");
flag = 1;
// this.notify();
this.notifyAll();
}
}
}
线程间通信注意的问题
在同步代码块中,用什么对象当作锁,就用什么对象去调用wait()和notify()。
为什么wait()和notify()定义在Object这个类中?
因为锁对象可以是任意对象,Object是所有类的基类(超类),所以wait()和notify()需要定义在Object这个类中。
面试题:Sleep()和wait()的区别?
区别一:
sleep方法必须传入参数,参数就是时间值,时间到了自动醒来。
wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待。
区别二:
sleep方法在同步函数或同步代码块中,不释放锁。睡着了也抱着锁睡,他在指定时间可以醒来。
wait方法在同步函数或者是同步代码块中,释放锁。如果不释放锁,不释放执行权,CPU一直在等待的地方耗着,意味着所有人都没有机会去执行了。
JDK1.5的新特性互斥锁
同步
使用ReentrantLock类的lock()和unlock()进行同步。
ReentrantLock类的概述:
public class ReentrantLock extends Object implements Lock, Serializable 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
ReentrantLock类的成员方法:
public void lock() 获取锁。
如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为1。
如果当前线程已经保持该锁,则将保持计数加1,并且该方法立即返回。
如果该锁被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态,此时锁保持计数被设置为1。
public void unlock() 试图释放此锁。
如果当前线程是此锁所有者,则将保持计数减1。如果保持计数现在为0,则释放该锁。如果当前线程不是此锁的持有者,则抛出IllegalMonitorStateException。
public Condition newCondition() 返回用来与此Lock实例一起使用的Condition实例。
Condition接口概述:
public interface Condition,Condition将Object监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了synchronized方法和语句的使用,Condition替代了Object 监视器方法的使用。
Condition接口的一般方法:
void await() throws InterruptedException 造成当前线程在接到信号或被中断之前一直处于等待状态。
void signal() 唤醒一个等待线程。
如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。
void signalAll() 唤醒所有等待线程。
如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。
通信
使用ReentrantLock类的newCondition()可以获取Condition对象。
需要等待的时候使用Condition的await(),唤醒的时候用signal()。
不同的线程使用不同的Condition,这样就能区分唤醒的时候找哪个线程了。
演示:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Demo3_ReentrantLock {
public static void main(String[] args) {
final Printer3 p = new Printer3();
new Thread() {
public void run() {
while(true) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer3 {
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
private int flag = 1;
public void print1() throws InterruptedException {
r.lock(); //获取锁
// while(flag != 1) { //可以唤醒指定的线程,就不需要循环判断了
if(flag != 1) {
c1.await();
}
System.out.print("H");
System.out.print("e");
System.out.print("l");
System.out.print("l");
System.out.print("o");
System.out.print("!");
System.out.print("\r\n");
flag = 2;
c2.signal();
r.unlock(); //释放锁
}
public void print2() throws InterruptedException {
r.lock();
// while(flag != 2) {
if(flag != 2) {
c2.await();
}
System.out.print("W");
System.out.print("o");
System.out.print("r");
System.out.print("l");
System.out.print("d");
System.out.print("!");
System.out.print("\r\n");
flag = 3;
c3.signal();
r.unlock();
}
public void print3() throws InterruptedException {
r.lock();
// while(flag != 3) {
if(flag != 3) {
c3.await();
}
System.out.print("n");
System.out.print("o");
System.out.print(" ");
System.out.print("b");
System.out.print("u");
System.out.print("g");
System.out.print("!");
System.out.print("\r\n");
flag = 1;
c1.signal();
r.unlock();
}
}
按照老师的意思,Condition理解为监视器,每个线程放一个。await等待一个线程,signal就再唤醒一个指定的线程。
线程组的概述和使用
线程组概述
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。
Thread类的构造方法:
public Thread(ThreadGroup group, Runnable target) 分配新的Thread对象。
public Thread(ThreadGroup group, Runnable target, String name) 分配新的 Thread对象,以便将target作为其运行对象,将指定的name作为其名称,并作为group所引用的线程组的一员。
第二个参数可以接收Runnable的子类对象,也可以接收Thread类的子类对象,Thread实现了Runnable接口,Thread子类对象也相当于实现了Runnable。Runnable拓展性更强一些。
Thread类的成员方法:
public final ThreadGroup getThreadGroup() 返回该线程所属的线程组。如果该线程已经终止(停止运行),该方法则返回null。
ThreadGroup类概述:
public class ThreadGroup extends Object implements Thread.UncaughtExceptionHandler
线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。
允许线程访问有关自己的线程组的信息,但是不允许它访问有关其线程组的父线程组或其他任何线程组的信息。
ThreadGroup类的成员方法:
public final String getName() 返回此线程组的名称。
我们也可以给线程设置分组:
1.ThreadGroup(String name) 创建线程组对象并给其赋值名字。
2.创建线程对象。
3.Thread(ThreadGroup?group, Runnable?target, String?name)。
4.设置整组的优先级或者守护线程。
演示:
/**
* 线程组
* public final ThreadGroup getThreadGroup() //通过线程对象获取他所属于的组
* public final String getName() //通过线程组对象获取他组的名字
*/
public class Demo4_ThreadGroup {
public static void main(String[] args) {
// defaultThreadGroup_Main();
ThreadGroup tg = new ThreadGroup("我是一个新的线程组"); //创建新的线程组
MyRunnable mr = new MyRunnable(); //创建Runnable的子类对象
//将两条线程全都放到tg这个线程组里
Thread t1 = new Thread(tg, mr, "张三"); //将线程t1放在组中
Thread t2 = new Thread(tg, mr, "李四"); //将线程t2放在组中
System.out.println(t1.getThreadGroup().getName()); //我是一个新的线程组 //获取组名
System.out.println(t2.getThreadGroup().getName()); //我是一个新的线程组
//编程组的好处是可以直接设置整个组
tg.setDaemon(true); //整个组都变成守护线程了
}
public static void defaultThreadGroup_Main() {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "张三");
Thread t2 = new Thread(mr, "李四");
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
System.out.println(tg1.getName()); //main 默认是主线程
System.out.println(tg2.getName());
}
}
class MyRunnable implements Runnable {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
}
线程的五种状态
新建,就绪,运行,阻塞,死亡。
Thread类的成员方法:
public final void stop() 强迫线程停止执行。
已过时。该方法具有固有的不安全性。用 Thread.stop来终止线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查ThreadDeath异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。stop的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示它要停止运行,则从其运行方法依次返回。如果目标线程等待很长时间(例如基于一个条件变量),则应使用interrupt方法来中断该等待。有关更多信息,请参阅api中为何不赞成使用 Thread.stop、Thread.suspend和Thread.resume?。
线程池的概述和使用
线程池概述
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。
内置线程池的使用概述
JDK5新增了一个Executors工厂类来产生线程池。
Executors类概述:
public class Executors extends Object 此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。此类支持以下各种方法:
创建并返回设置有常用配置字符串的ExecutorService的方法。
创建并返回设置有常用配置字符串的ScheduledExecutorService的方法。
创建并返回“包装的”ExecutorService方法,它通过使特定于实现的方法不可访问来禁用重新配置。
创建并返回ThreadFactory的方法,它可将新创建的线程设置为已知的状态。
创建并返回非闭包形式的Callable的方法,这样可将其用于需要Callable的执行方法中。
Executors类的成员方法:
public static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数nThreads线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。参数: nThreads - 池中的线程数。该方法用于创建拥有多个线程的线程池,线程的数量由参数进行设置。
public static ExecutorService newSingleThreadExecutor()
创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。 该方法用于创建拥有一个线程的线程池。
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。ExecutorService线程池服务器。
ExecutorService接口概述:
public interface ExecutorService extends Executor,Executor提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。
可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭ExecutorService。shutdown()在终止前允许执行以前提交的任务,而 shutdownNow()阻止等待任务启动并试图停止当前正在执行的任务。在终止时,执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。应该关闭未使用的ExecutorService以允许回收其资源。
ExecutorService接口的方法:
Future<?> submit(Runnable task) 提交一个Runnable任务用于执行,并返回一个表示该任务的Future。该Future的get方法在成功 完成时将会返回null。参数:task - 要提交的任务。返回:表示任务等待完成的Future。 submit提交的意思。参数可以是Runnable,就可以接收线程的子类对象(Thread类的子类对象、Runnable的子类对象)。
void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。
List<Runnable> shutdownNow() 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。返回:从未开始执行的任务的列表。
无法保证能够停止正在处理的活动执行任务,但是会尽力尝试。例如,通过 Thread.interrupt() 来取消典型的实现,所以任何任务无法响应中断都可能永远无法终止。
使用步骤:
创建线程池对象。
创建Runnable实例。
提交Runnable实例。
关闭线程池。
演示:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo5_Executors {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2); //创建一个线程数为2的线程池
pool.submit(new MyRunnable2()); //参数传入Runnable的子类对象,将线程放进池子里并执行
pool.submit(new MyRunnable2());
pool.shutdown(); //关闭线程池
}
}
class MyRunnable2 implements Runnable {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}
}
多线程程序实现的方式3
提交的是Callable
Callable<V>接口概述:
public interface Callable<V> 返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable不会返回结果,并且无法抛出经过检查的异常。
Executors类包含一些从其他普通形式转换成Callable类的实用方法。
类型参数:V - call方法的结果类型。
Callable<V>接口的方法:
V call() 计算结果,如果无法计算结果,则抛出一个异常。返回:计算的结果。
ExecutorService接口的方法:
<T> Future<T> submit(Callable<T> task) 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的Future。该Future的get方法在成功完成时将会返回该任务的结果。
Future<V>接口概述:
public interface Future<V>,Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用get方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由cancel方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用Future但又不提供可用的结果,则可以声明Future<?>形式类型、并返回null作为底层任务的结果。
Future<V>接口的方法:
V get() 如有必要,等待计算完成,然后获取其结果。返回:计算的结果。
演示:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 线程第三种实现方式:自定义类实现Callable接口,重写他里面的call(),call()的好处是他有返回值,他可以抛异常,原来的run()不可以抛异常。
* 然后,通过线程池将创建自定义类对象的线程放入池子中,算出一个结果,将结果返回给f1和f2.再通过f1、f2的get()将结果给获取出来。
*/
public class Demo6_Callable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f1 = pool.submit(new MyCallable(100)); //future的作用是,将pool.submit(new MyCallable(100));接收进来后,会获取到1至100运算的结果
Future<Integer> f2 = pool.submit(new MyCallable(50)); //将pool.submit(new MyCallable(50));接收进来后,会获取到1至50运算的结果
System.out.println(f1.get()); //5050
System.out.println(f2.get()); //1275
pool.shutdown();
}
}
class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
public Integer call() throws Exception {
int sum = 0;
for(int i = 1; i <= num; i++) {
sum += i;
}
return sum;
}
}
多线程程序实现的方式3的好处:
可以有返回值。
可以抛出异常。
多线程程序实现的方式3的弊端:
代码比较复杂,所以一般不用。
简单工厂模式概述和使用
简单工厂模式概述
又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例。
优点
客户端不需要在负责对象的创建,由工厂来创建,客户端只需要用工厂创建出来的对象即可,从而明确了各个类的职责。
缺点
这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护。
为什么会有简单工厂设计模式?
一开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就制造了一个专门的类来创建对象。
演示:
动物抽象类:
public abstract class Animal {
public abstract void eat();
}
具体狗类:
public class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
}
具体猫类:
public class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
动物工厂类:
public class AnimalFactory {
/*public static Dog createDog() { //加static,直接类名.即可调用
return new Dog();
}
public static Cat createCat() {
return new Cat();
}*/
//发现如果面向的对象过多,则要定义的方法会很多,方法复用性太差.改进
public static Animal createAnimal(String type) {
if("dog".equalsIgnoreCase(type)) {
return new Dog();
}else if("cat".equalsIgnoreCase(type)) {
return new Cat();
}else {
return null;
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
// Dog d = AnimalFactory.createDog(); //不需要自己创建狗对象,现在直接用工厂提供的方式就可以了
Dog d = (Dog) AnimalFactory.createAnimal("dog"); //父类引用指向子类对象,需要强转
d.eat(); //狗吃肉
Cat c = (Cat) AnimalFactory.createAnimal("cat"); //父类引用指向子类对象,需要强转
c.eat(); //猫吃鱼
}
}
工厂方法模式的概述和使用
工厂方法模式概述
工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
优点
客户端不需要再负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性。
缺点
需要额外的编写代码,增加了工作量。
为什么会有工厂方法模式?
一开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。发现每次修改代码太麻烦,用工厂方法改进,针对每一个具体的实现提供一个具体工厂。
演示:
动物抽象类:
public abstract class Animal {
public abstract void eat();
}
工厂接口:
public interface Factory {
public Animal createAnimal();
}
具体狗类:
public class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
}
具体猫类:
public class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
狗工厂:
public class DogFactory implements Factory {
public Animal createAnimal() {
return new Dog();
}
}
猫工厂:
public class CatFactory implements Factory {
public Animal createAnimal() {
return new Cat();
}
}
测试类:
public class Test {
public static void main(String[] args) {
DogFactory df = new DogFactory();
Dog d = (Dog) df.createAnimal();
d.eat();
CatFactory cf = new CatFactory();
Cat c = (Cat) cf.createAnimal();
c.eat();
}
}
GUI如何创建一个窗口并显示
Graphical User Interface(图形用户接口),做一个界面出来。
老师说:这玩意面试和开发都不用,java这个界面做的很垃圾。java的代码一处编译,到处运行;而GUI是一处编译,到处调试。
Frame类概述:
public class Frame extends Window implements MenuContainer,Frame 是带有标题和边框的顶层窗口。java.awt包下,使用需要导包。
窗体的大小包括为边框指定的所有区域。边框区域的尺寸可以使用 getInsets方法获得,但是,由于这些尺寸与平台相关,因此在通过调用pack或show将窗体设置为可显示之前,将无法获得有效的insets值。由于窗体的总大小包括了边框区,因此边框实际上遮掩了窗体的部分区域,并将可用于在矩形中呈现和/或显示子部件的区域限制在一个矩形内,该矩形左上角的位置为(insets.left, insets.top),宽度为 width - (insets.left + insets.right),长度为 height - (insets.top + insets.bottom)。
Frame类的构造方法:
public Frame() 构造一个最初不可见的Frame新实例()。Frame的标题为空。
public Frame(String title) 构造一个新的、最初不可见的、具有指定标题的 Frame 对象。
演示:
import java.awt.Frame;
import java.awt.Toolkit;
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗体。");
f.setSize(400, 600); //设置窗体大小
f.setLocation(300, 500);//设置窗体位置
f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png")); //设置图标
f.setVisible(true); //设置窗体可见
}
}
GUI 布局管理器
FlowLayout(流式布局管理器)
从左到右的顺序排列。水平居中
Panel默认的布局管理器。
BorderLayout(边界布局管理器)
东,南,西,北,中。适中
Frame默认的布局管理器。
GridLayout(网格布局管理器)
规则的矩阵。
CardLayout(卡片布局管理器)
选项卡。Eclipse开发页面就是卡片布局
GridBagLayout(网格包布局管理器)
非规则的矩阵。Windows自带的计算器布局
演示:
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Toolkit;
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗体。");
f.setSize(400, 600);
f.setLocation(500, 50);
f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
f.add(b1);
f.setLayout(new FlowLayout()); //设置布局管理器
f.setVisible(true);
}
}
GUI 窗体监听
WindowListener接口概述:
public interface WindowListener extends EventListener 用于接收窗口事件的侦听器接口。旨在处理窗口事件的类要么实现此接口(及其包含的所有方法),要么扩展抽象类 WindowAdapter(仅重写所需的方法)。然后使用窗口的addWindowListener方法将从该类所创建的侦听器对象向该Window注册。当通过打开、关闭、激活或停用、图标化或取消图标化而改变了窗口状态时,将调用该侦听器对象中的相关方法,并将WindowEvent传递给该方法。
WindowListener接口的方法:
void windowClosing(WindowEvent e) 用户试图从窗口的系统菜单中关闭窗口时调用。
WindowAdapter类实现了WindowListener,概述:
public abstract class WindowAdapter extends Object implements WindowListener, WindowStateListener, WindowFocusListener 接收窗口事件的抽象适配器类。此类中的方法为空。此类存在的目的是方便创建侦听器对象。
扩展此类可创建WindowEvent侦听器并为所需事件重写该方法。如果要实现WindowListener接口,则必须定义该接口内的所有方法。此抽象类将所有方法都定义为null,所以只需针对关心的事件定义方法。)
大白话:此类实现了windowClosing,重写了windowClosing里的所有抽象方法,方法都不是抽象的了。但这个类是个抽象类,如果想用里面的方法,需要先继承这个类,起到适配的作用。方法全部为空,目的就是提供给子类使用这个方法,而不是自己用。子类用哪个就重写哪个方法。定义成抽象是因为不想让你去创建他的类对象,因为创建这个类对象,调用这些空方法也是没有意义的。如果一个抽象类中,没有定义他的抽象方法,目的只有一个不允许其他类来创建这个类对象。
使用扩展的类可以创建侦听器对象,然后使用窗口的addWindowListener方法向该窗口注册侦听器。当通过打开、关闭、激活或停用、图标化或取消图标化而改变了窗口状态时,将调用该侦听器对象中的相关方法,并将WindowEvent传递给该方法。
这个类想当于一个中间类,特别给力。
演示:
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗体。");
f.setSize(400, 600);
f.setLocation(500, 50);
f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
f.add(b1);
f.setLayout(new FlowLayout());
// f.addWindowListener(new MyWindowListener()); //Closing 点叉号windowClosing方法执行了
// f.addWindowListener(new MyWindowAdapter());
f.addWindowListener(new WindowAdapter() { //匿名内部类
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setVisible(true);
}
}
/*class MyWindowListener implements WindowListener { //实现接口要重写所有方法太费劲,优化一下
public void windowOpened(WindowEvent e) {
}
public void windowClosing(WindowEvent e) {
// System.out.println("Closing");
System.exit(0);
}
public void windowClosed(WindowEvent e) {
}
public void windowIconified(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
public void windowActivated(WindowEvent e) {
}
public void windowDeactivated(WindowEvent e) {
}
}*/
/*class MyWindowAdapter extends WindowAdapter { //定义个类也费劲,再优化一下
public void windowClosing(WindowEvent e) {
System.exit(0);
super.windowClosing(e);
}
}*/
GUI 鼠标监听
MouseListener接口概述:
public interface MouseListener extends EventListener 用于接收组件上“感兴趣”的鼠标事件(按下、释放、单击、进入或离开)的侦听器接口。(要跟踪鼠标移动和鼠标拖动,请使用MouseMotionListener。)
旨在处理鼠标事件的类要么实现此接口(及其包含的所有方法),要么扩展抽象类 MouseAdapter(仅重写所需的方法)。
然后使用组件的addMouseListener方法将从该类所创建的侦听器对象向该组件注册。当按下、释放或单击(按下并释放)鼠标时会生成鼠标事件。鼠标光标进入或离开组件时也会生成鼠标事件。发生鼠标事件时,将调用该侦听器对象中的相应方法,并将MouseEvent传递给该方法。
MouseListener接口的方法:
void mouseClicked(MouseEvent e) 鼠标按键在组件上单击(按下并释放)时调用。
void mouseReleased(MouseEvent e) 鼠标按钮在组件上释放时调用。
MouseAdapter类概述:
public abstract class MouseAdapter extends Object implements MouseListener, MouseWheelListener, MouseMotionListener 接收鼠标事件的抽象适配器类。此类中的方法为空。此类存在的目的是方便创建侦听器对象。
鼠标事件使用户能够跟踪鼠标何时被按下、释放、单击、移动、拖动、何时进入一个组件、何时退出、何时滚动鼠标滚轮。
扩展此类可创建MouseEvent(包括拖动和移动事件)或/和MouseWheelEvent侦听器,并针对所需事件重写方法。(如果要实现MouseListener和MouseMotionListener接口,则必须定义该接口中的所有方法。此抽象类将所有方法都定义为null,所以只需针对关心的事件定义方法。)
演示:
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗体。");
f.setSize(400, 600);
f.setLocation(500, 50);
f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
f.add(b1);
f.setLayout(new FlowLayout());
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
b1.addMouseListener(new MouseAdapter() {
/*public void mouseClicked(MouseEvent e) { //监听鼠标单击
System.exit(0);
}*/
public void mouseReleased(MouseEvent e) { //监听鼠标释放
System.exit(0);
}
});
f.setVisible(true);
}
}
GUI 键盘监听和键盘事件
KeyListener接口概述:
public interface KeyListener extends EventListener 用于接收键盘事件(击键)的侦听器接口。旨在处理键盘事件的类要么实现此接口(及其包含的所有方法),要么扩展抽象 KeyAdapter 类(仅重写有用的方法)。
然后使用组件的addKeyListener方法将从该类所创建的侦听器对象向该组件注册。按下、释放或键入键时生成键盘事件。然后调用侦听器对象中的相关方法并将该KeyEvent传递给它。
KeyListener接口的方法:
void keyTyped(KeyEvent e) 键入某个键时调用此方法。有关键入键事件的定义,请参见 KeyEvent 的类描述。
void keyPressed(KeyEvent e) 按下某个键时调用此方法。有关按下键事件的定义,请参见 KeyEvent 的类描述。
void keyReleased(KeyEvent e) 释放某个键时调用此方法。有关释放键事件的定义,请参见 KeyEvent 的类描述。
KeyAdapter类概述:
public abstract class KeyAdapter extends Object implements KeyListener 接收键盘事件的抽象适配器类。此类中的方法为空。此类存在的目的是方便创建侦听器对象。
扩展此类即可创建KeyEvent侦听器并重写所需事件的方法。(如果要实现KeyListener接口,则必须定义该接口内的所有方法。此抽象类将所有方法都定义为null,所以只需针对关心的事件定义方法。)
使用扩展的类可创建侦听器对象,然后使用组件的addKeyListener方法向该组件注册此侦听器对象。当按下、释放或键入某个键时,将调用该侦听器对象中的相应方法,并将KeyEvent传递给相应的方法。
KeyEvent类概述:
public class KeyEvent extends InputEvent 表示组件中发生键击的事件。
当按下、释放或键入某个键时,组件对象(如文本字段)将生成此低级别事件。该事件被传递给每一个KeyListener或KeyAdapter对象,这些对象使用组件的addKeyListener方法注册,以接收此类事件。(KeyAdapter对象实现KeyListener接口。)发生事件时,所有此类侦听器对象都将获得此KeyEvent。
“键入键”事件 是高级别事件,通常不依赖于平台或键盘布局。输入Unicode字符时生成此类事件,它们被认为是发现字符输入的最佳方式。最简单的情况是,按下单个键(如"a")将产生键入键事件。但是,字符经常是通过一系列按键(如‘shift’+‘a’)产生的,按下键事件和键入键事件的映射关系可能是多对一或多对多的。键释放通常不需要生成键入键事件,但在某些情况下,只有释放了某个键后才能生成键入键事件(如在Windows中通过Alt-Numpad方法来输入ASCII序列)。对于不生成Unicode字符的键是不会生成键入键事件的(如动作键、修改键等等)。
“按下键”和“释放键”事件是低级别事件,依赖于平台和键盘布局。只要按下或释放键就生成这些事件,它们是发现不生成字符输入的键(如动作键、修改键等等)的惟一方式。通过getKeyCode方法可指出按下或释放的键,该方法返回一个虚拟键码。虚拟键码用于报告按下了键盘上的哪个键,而不是一次或多次键击组合生成的字符(如 "A" 是由 shift + "a" 生成的)。
KeyEvent类的成员方法:
public int getKeyCode() 返回与此事件中的键关联的整数keyCode。返回: 键盘上实际键的整数代码。
演示:
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗体。");
f.setSize(400, 600);
f.setLocation(500, 50);
f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
f.add(b1);
f.setLayout(new FlowLayout());
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
b1.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
System.exit(0);
}
});
b1.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent e) {
// System.exit(0);
// System.out.println(e.getKeyCode());
// if(e.getKeyCode() == 32) {
if(e.getKeyCode() == KeyEvent.VK_SPACE) { //VK_SPACE 空格
System.exit(0);
}
}
});
f.setVisible(true);
}
}
GUI 动作监听
默认指向空格和鼠标单击。
ActionListener接口概述:
public interface ActionListener extends EventListener 用于接收操作事件的侦听器接口。对处理操作事件感兴趣的类可以实现此接口,而使用该类创建的对象可使用组件的addActionListener方法向该组件注册。在发生操作事件时,调用该对象的actionPerformed方法。
ActionListener接口的方法:
void actionPerformed(ActionEvent e) 发生操作时调用。
演示:
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗体。");
f.setSize(400, 600);
f.setLocation(500, 50);
f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
Button b1 = new Button("按钮1");
Button b2 = new Button("按钮2");
f.add(b1);
f.add(b2);
f.setLayout(new FlowLayout());
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
b1.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
System.exit(0);
}
});
b1.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_SPACE) {
System.exit(0);
}
}
});
b2.addActionListener(new ActionListener() { //添加动作监听,应用场景就是暂停视频和播放视频
public void actionPerformed(ActionEvent e) {
System.exit(0); //无论是敲空格键还是敲鼠标左键都会退出java虚拟机
}
});
f.setVisible(true);
}
}
适配器设计模式
什么是适配器
在使用监听器的时候,需要定义一个类事件监听器接口。
通常接口中有多个方法,而程序中不一定所有的都用到,但又必须重写,这很繁琐。
适配器简化了这些操作,我们定义监听器时只要继承适配器,然后重写需要的方法即可。
适配器原理
适配器就是一个类,实现了监听器接口,所有抽象方法都重写了,但是方法全是空的。
适配器类需要定义成抽象的,因为创建该类对象,调用空方法是没有意义的。
目的就是为了简化程序员的操作,定义监听器时继承适配器,只重写需要的方法就可以了。
演示:
public class Demo1_Adapter {
}
interface 和尚 {
public void 打坐();
public void 念经();
public void 撞钟();
public void 习武();
}
//天罡星类就是一个适配器类
abstract class 天罡星 implements 和尚 { //声明成抽象的原因是,不想让其他类创建本类对象,因为创建也没有意义,方法都是空的
public void 打坐() {}
public void 念经() {}
public void 撞钟() {}
public void 习武() {}
}
class 鲁智深 extends 天罡星 {
public void 习武() {
System.out.println("倒拔垂杨柳");
System.out.println("拳打镇关西");
System.out.println("大闹野猪林");
System.out.println("......");
}
}
GUI 需要知道的
事件处理
事件:用户的一个操作(点鼠标,敲键盘等)。
事件源:被操作的组件(按钮等)。
监听器:一个自定义类的对象,实现了监听器接口,包含事件处理方法,把监听器添加在事件源上,当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法。