目录
★枚举类 (需要定义一组常量时,选用枚举类)
枚举类---->类的对象只有有限个、确定的
jdk5.0后,用enum关键字定义枚举类
说明:enum定义的枚举类默认继承于java.lang.En
常量顺序:枚举常量的声明必须放在枚举体的开头,多个常量之间用", " 隔开, 末尾常量";"结束
toString 和 ordinal 方法:每个枚举都有默认的
toString()
和ordinal()
方法,分别返回枚举的名字和位置序号(从0开始)
以下关于枚举类型enum的叙述错误的是( C )
A. 枚举类型本质上也是类 B. 可以修改enum对象的成员变量
C. 枚举类型可以拥有public构造方法 D. enum构造方法也可以重载
【解析】
枚举的构造方法是私有的,不能声明为 public 或 protected。
枚举实例是在类加载时自动创建的,无法外部实例化。
枚举不能使用继承(因为编译器会自动为我们继承Enum抽象类而Java只支持单继承,因此枚举类是无法手动实现继承的)
B选项:不能修改对象,因为对象是常量,但对象内的属性是可以修改的
7. 已知枚举类型Seasons的定义如下:
enum Seasons{
SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");
private String desc = "";
public void setDesc(String desc){
this.desc = desc;
}
public Seasons(String desc){
setDesc(desc);
}
}判断上述代码段是否正确?如果不正确,请指出错误之处,并修改。
【解析】
上述代码不正确,枚举类中的构造方法必须是私有的,不能为public
代码应该修改为:
enum Seasons{ SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天"); private String desc = ""; public void setDesc(String desc){ this.desc = desc; } // 枚举类中构造方法必须是私有的 private Seasons(String desc){ setDesc(desc); } }
扩展:
//使用enum关键字来定义枚举类
enum Seasons{
/*初始自定义枚举类的格式应该为:
class Seasons{
private String desc;
private Seasons(String desc){
this.desc = desc;
}
public static final Seasons SPRING = new Seasons("春天");
public static final Seasons SUMMER = new Seasons("夏天");
public static final Seasons AUTUMN = new Seasons("秋天");
public static final Seasons WINTER = new Seasons("冬天");
}
*/
//默认情况下,枚举对象是public static final修饰的
SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");
//枚举对象是常量不可以修改,但对象内的属性是可以修改的
private String desc = "";
public void setDesc(String desc){
this.desc = desc;
}
// 枚举类中构造方法必须是私有的
private Seasons(String desc){
setDesc(desc);
}
public String getDesc(){
return desc;
}
public static void main(String[] args){
Seasons s = Seasons.SPRING;
s.setDesc("春天(副本)");
System.out.println(s.getDesc());
}
}
★异常处理
Exception:java.lang包下,称为异常类,表示程序本身可以处理的问题
12. 下列属于未检查异常的是( D )。
A. java.io.IOException B. java.net.SocketException
C. java.io.FileNotFoundException D. java.lang.NullPointerException(空指针的访问)
未检查异常---运行时异常
已检查异常---编译时异常
异常处理机制
异常的处理:抓抛模型
- 过程一:"抛":程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行
- 过程二:"抓”,可以理解为异常的处理方式 1)try-catach-finally 2)throws
1.捕获异常 try… catch…finally
try{
//可能发生异常的代码块
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型1 变量名1){
//处理异常的方式1
}finally{//可选
//无论是否发生异常,都会执行
}
2.throws+异常类型
在方法上使用throws关键字可以声明该方法可能会抛出的异常
调用改方法的"调用者",需要处理此异常,否则编译不通过
★比较器
java中,对集合对象和数组对象进行排序,有以下两种实现方式:
- 1)实现Comparable接口,实现compareTo()方法
- 若一个类实现了Comparable接口,就意味着该类支持排序。
- 实现了 Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。
- 2)自定义比较器,实现Comparator接口,实现compare()方法
Arrays.sort(数组类型,Comparator对象)
//Comparator 定义
public interface Comparator {
int compare(T o1, T o2);
boolean equals(Object obj); //可以不实现
}
8. 已知抽象类Shape定义如下:
public abstract class Shape{
public abstract double getArea(); //计算面积
}
为Shape添加一个默认比较器,实现对面积的降序排序。请写出修改后的完整Shape类代码。
【解析】
public abstract class Shape { //计算面积的抽象方法(抽象方法没有方法体) public abstract double getArea(); //默认比较器,按面积从大到小排序 public static final Comparator<Shape> areaComparator = new Comparator<Shape>() { @Override public int compare(Shape o1, Shape o2) { return Double.compare(o2.getArea(), o1.getArea()); } }; }
Staff, Teacher, SecurityGuard, Dean
(1)定义Staff类(职工),添加如下属性(name, address, age, sex, salary, dateHired),类型自定,其中salary为工资,dateHired为雇佣日期(java.util.Date类型)。生成相应的setter/getter方法。
(2)编写Teacher类(教师),继承自Staff类,包含属性:department(系), speciality(专业), postAllowance(岗位津贴)。
(3)编写SecurityGuard类(保安),继承自Staff类,包含属性:skills(专技), dangerousAllowance(高危津贴)。
(4)编写Teacher的一个子类Dean(院长),包含属性:adminAward(行政奖金)。
(5)定义上述各类的getter/setter方法,并添加合适的构造方法。
(6)编写一个测试类,在测试类中添加若干个Staff, Teacher, SecurityGuard, Dean实例(个数及内容自定),并在测试类中定义并测试如下方法:
①编写一个方法private static void printName(Staff[] persons)打印出每个人的名字;
②编写一个方法private static void printSalary(Staff[] staffs)打印出Staff类或者其子类对象的薪水(注意:Staff的薪水只有salary,Teacher的薪水为salary+postAllowance,SecurityGuard的薪水为salary+dangerousAllowance,而Dean的薪水则为salary+postAllowance+adminAward);
③编写一方法private static void sortBySalary(Staff[] staffs),支持对Staff类及其子类按照各自的薪水降序排序;
④编写一方法private static void sortByAge(Staff[] staffs),对Staff对象按照年龄升序排序,再编写一个方法按name升序进行排序;
1)Staff类
package B4; import java.util.Date; //职工类 public class Staff { private String name; private String address; private int age; private String sex; private double salary; private Date dateHired; //对应setter和getter方法 public Staff(String name,String address,int age,String sex,double salary,Date dateHired){ this.name=name; this.address=address; this.age=age; this.sex=sex; this.salary=salary; this.dateHired=dateHired; } public int getAge(){ return this.age; } public String getName(){ return this.name; } public double getSalary(){ return this.salary; } public double getResultSalary(){ return this.getSalary(); } }
2)Teacher类
package B4; import java.util.Date; public class Teacher extends Staff{ private String department; private String speciality; private double postAllowance; //对应setter和getter方法 public double getPostAllowance() { return this.postAllowance; } public Teacher(String name, String address, int age, String sex, double salary, Date dataHired,String department,String speciality,double postAllowance){ super(name,address,age,sex,salary,dataHired); this.department=department; this.speciality=speciality; this.postAllowance=postAllowance; } public double getResultSalary(){ return this.getSalary()+this.getPostAllowance(); } }
3)SecurityGuard类
package B4; import java.util.Date; //保安类 public class SecurityGuard extends Staff{ private String skills; private double dangerousAllowance; //对应setter和getter方法 public SecurityGuard(String name , String address, int age, String sex, double salary, Date dateHired,String skills,double dangerousAllowance){ super(name,address,age,sex,salary,dateHired); this.skills=skills; this.dangerousAllowance=dangerousAllowance; } public double getDangerousAllowance(){ return this.dangerousAllowance; } public double getResultSalary(){ return this.getSalary()+this.getDangerousAllowance(); } }
4)Dean类
package B4; import java.util.Date; //院长类 public class Dean extends Teacher{ private double adminAward; //对应setter和getter方法 public Dean(String name, String address, int age, String sex, double salary, Date dateHired, String department,String speciality,double postAllowance,double adminAward){ super(name,address,age,sex,salary,dateHired,department,speciality,postAllowance); this.adminAward=adminAward; } public double getResultSalary(){ return super.getResultSalary()+this.adminAward; } }
5)测试类
package B4; import java.util.Comparator; import java.util.Date; import java.util.Arrays; public class Test { public static void main(String[] args) { Staff[] staffs = new Staff[4]; staffs[0] = new Staff("Pan", "厦门",20,"女",100,new Date(2023,9,1)); staffs[1]=new Teacher ("Lin","泉州",20,"女",200,new Date(2022,9,1),"文学系","语文",100); staffs[2]=new SecurityGuard("Hu","南平",18,"男",50,new Date(2021,9,1),"拳击",10); staffs[3]=new Dean("Liu","福州",40,"女",300,new Date(2020,9,1),"理学院","数学",100,300); //打印每个人的名字 printName(staffs); System.out.println(); //打印薪水 printSalary(staffs); System.out.println(); //按薪水排序(降序) sortBySalary_2(staffs); printSalary(staffs); System.out.println(); //按年龄升序 sortByAge_2(staffs); printAge(staffs); System.out.println(); //按名字排序(升序) sortByName_2(staffs); printName(staffs); } //打印姓名 private static void printName(Staff[] persons){ for(Staff staff:persons){ String name=staff.getName(); System.out.println(name); } } //打印工资 private static void printSalary(Staff[] staffs){ for(Staff staff:staffs){ double salary=staff.getResultSalary(); System.out.println("Salay:"+salary); } } //按工资降序排序 //写法1 private static final Comparator<Staff> SalaryComptator= new Comparator<Staff>(){ public int compare(Staff s1,Staff s2){ return Double.compare(s2.getResultSalary(),s1.getResultSalary()); } }; private static void sortBySalary(Staff[] staffs){ Arrays.sort(staffs,SalaryComptator); } //写法2 private static void sortBySalary_1(Staff[] staffs){ Arrays.sort(staffs,new Comparator<Staff>(){ public int compare(Staff s1,Staff s2){ return Double.compare(s2.getResultSalary(), s1.getResultSalary()); } }); } //写法3 private static void sortBySalary_2(Staff[] staffs){ Arrays.sort(staffs,(s1,s2)->Double.compare(s2.getResultSalary(),s1.getResultSalary())); } //按年龄排序(升序) //写法1 private static void sortByAge(Staff[] staffs){ Arrays.sort(staffs,AgeComparator); } private static final Comparator<Staff> AgeComparator=new Comparator<Staff>(){ public int compare(Staff s1,Staff s2){ return s1.getAge()-s2.getAge(); } }; //写法2 private static void sortByAge_1(Staff[] staffs){ Arrays.sort(staffs,new Comparator<Staff>(){ public int compare(Staff s1,Staff s2){ return Integer.compare(s1.getAge(), s2.getAge()); } }); } //写法3 private static void sortByAge_2(Staff[] staffs){ Arrays.sort(staffs,(s1,s2)->Integer.compare(s1.getAge(),s2.getAge())); } private static void printAge(Staff[] staffs){ for(Staff staff:staffs){ int age=staff.getAge(); System.out.println("Age="+age); } } //对名字进行排序 //写法1 private static void sortByName(Staff[] staffs){ Arrays.sort(staffs,NameComparator); } private static final Comparator<Staff> NameComparator=new Comparator<Staff>(){ public int compare(Staff s1,Staff s2){ return s1.getName().compareTo(s2.getName()); } }; //写法2 private static void sortByName_1(Staff[] staffs){ Arrays.sort(staffs,new Comparator<Staff>(){ public int compare(Staff s1,Staff s2){ return s1.getName().compareTo(s2.getName()); } }); } //写法3 使用Lambda表达式 /* Lambda表达式的语法格式:(参数列表)->主体 1.只有一个参数,可省略括号 2.没有参数,需要空括号 */ private static void sortByName_2(Staff[] staffs){ Arrays.sort(staffs,(s1,s2)->s1.getName().compareTo(s2.getName())); } }
★多线程
5.试分析下列程序。
public class Test {
private static long total = 0L;
public static void main(String[] args) {
Thread t = new Thread(()->{
for(int i = 1;i <= 10000;i++) total += i;
});
t.start();
System.out.println("Total=" + total);
}
}当尝试使用线程计算1到10000的累加时,几乎每次都得不到正确的结果,试分析原因,并给出修改的方法。
【解析】
1)分析原因:total为共享变量,被多个线程访问,可能存在主线程比创建的新线程跑得快,在创建的新线程还没跑完,主线程就已经跑完并且输出了total的值,导致total值输出不正确
2)修改方法:使用join方法,让主线程在创建的线程跑完以后再输出total的值
public class Test { private static long total = 0L; public static void main(String[] args) { Thread t = new Thread(()->{ for(int i = 1;i <= 10000;i++) total += i; }); t.start(); try { t.join(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("Total=" + total); } }
6. 试分析下列程序。
public class Test {
private static long total = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for(int i = 1;i <= 50001;i++) total += i;
});
Thread t2 = new Thread(()->{
for(int i = 50001;i <= 100000;i++) total += i;
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("Total=" + total);
}
}当尝试使用2个线程协作计算1到100000的累加时,几乎每次都得不到正确的结果,试分析原因,并给出修改的方法。
【解析】
分析原因:存在竞争条件,当两个线程要同时访问并修改共享变量total的时候,程序中没有适当的同步机制来确保这种访问是安全的。具体来说就是可能出现两个线程几乎同时读取total的值,对其进行增加操作后再写回时,可能会覆盖彼此的操作,导致一些增加操作被丢失,从而导致最终结果错误
修改方法:使用同步块,确保每次访问total变量时都使用相同的锁对象进行同步,这样可以有效避免多个线程同时修改total的值导致程序结果错误的问题
public class Test { private static long total = 0; private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { // 线程1 Thread t1 = new Thread(() -> { for (int i = 1; i <= 50000; i++) { synchronized (lock) { total += i; } } }); // 线程2 Thread t2 = new Thread(() -> { for (int i = 50001; i <= 100000; i++) { synchronized (lock) { total += i; } } }); t1.start(); t2.start(); try { t1.join(); t2.join(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("Total: " + total); } }
进一步改进:
public class Test1{ private static long total = 0; private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { // 线程1 Thread t1 = new Thread(() -> { long localSum = 0; for (int i = 1; i <= 50000; i++) { localSum += i; } synchronized (lock) { total += localSum; } }); // 线程2 Thread t2 = new Thread(() -> { long localSum = 0; for (int i = 50001; i <= 100000; i++) { localSum += i; } synchronized (lock) { total += localSum; } }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Total: " + total); } }
实现线程的2种方式
-
继承Thread类
创建MyThread类(继承Thead类)->重写run()方法->创建MyTread类的实例化对象t1->调用t1.start()方法启动线程
每个线程对应一个MyThread的实例
public class 继承Thread类 {
//创建MyThread类继承Thread类
static class MyThread extends Thread{
//重写run方法
public void run(){
System.out.println("线程执行了");
}
}
public static void main(String[] args) {
//创建MyThread对象
MyThread t1=new MyThread();
//调用start方法
t1.start();
}
}
-
2.实现Runnable接口(常用)
- 创建MyThread类(实现Runnable接口)->重写run()方法->将Runnable对象传给Thread类的构造函数来创建线程
-
public class 实现Runnable接口 { static class MyThread implements Runnable{ @Override public void run() { System.out.println("实现Runnable接口"); } } public static void main(String[] args) { //创建Thread对象 Thread t1=new Thread(new MyThread()); //调用start方法 t1.start(); } }
注:只能调用start()方法启动线程
线程的几个重要方法
1.start() 启动线程并调用线程的run()方法。每个线程只能调用一次start()方法
注:直接调用run方法不会启动新线程,它只是会像普通方法一样去执行
2.join()
- 当前线程等待调用
join()
方法的线程执行完毕后再继续执行。 - 可以指定等待时间,如果超过指定时间,当前线程将继续执行。
- 如果不指定时间,当前线程将一直等待,直到调用
join()
方法的线程结束。 - 同样可能抛出
InterruptedException
。
3.Thread.sleep(long mills)
- 使当前线程暂停执行指定的时间,并交出CPU执行权。
- 不会释放对象锁,其他线程无法访问该线程持有的同步资源。
- 时间结束后,线程自动恢复到可运行状态,但不一定立即执行,需要等待CPU调度。
- 可能会抛出
InterruptedException
,如果线程在睡眠过程中被中断。
4.Thread.yield()
- 暂停当前正在执行的线程,让同等优先级或更高优先级的线程获得执行机会。
- 不会释放对象锁,与
sleep()
类似,但不会阻塞线程,而是让线程重回就绪状态。 - 使用
yield()
后,线程需要与其他线程重新争夺CPU资源。
下列( A )方法无法使线程从运行状态进入到非运行状态
A. notify B. wait C. sleep D. yield
补充:线程的生命周期
1、新建状态(new):新建一个线程对象
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行的线程池中,变得可运行,等待获取CPU的使用权
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
synchronized关键字
锁的类型
- 类锁 (synchronized(Main.class))
用于锁定整个类,适用于保护静态成员或当多个线程需要协调对相同资源的访问时
- 实例锁 (synchronized(this))
锁定当前对象,适用于保护非静态成员,确保同一时间只有一个线程可以访问该对象的方法或属性
- 对象锁 锁定某些特定的操作或者代码块
private final Object lock =new Object();
synchronized(lock){//同步代码块;}
public class Test {
private static long total=0;
private static final Object lock=new Object();
public static void main(String[] args) throws InterruptedException{
// 线程1
Thread t1=new Thread(()->{
synchronized (lock){
for(int i=1;i<50000;i++){
total+=i;
}
}
});
// 线程2
Thread t2=new Thread(()->{
synchronized(lock){
for(int i=50000;i<100000;i++){
total+=i;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Total"+total);
}
}
wait()和notify() 属于Object类
wait() 使当前线程等待,直到其他线程对象调用该对象的notify()或notifyAll()方法
notify()随机唤醒一个等待该对象锁的线程
notifyAll()唤醒所有等待的线程
注:这些方法都必须在synchronized代码块或者方法中调用,因为他们涉及到对象锁的操作
生产者-消费者模型
7. 生产者-消费者模型
使用多线程模拟生产者-消费者模型。已知物品容量为10,生产者每2秒生产5个产品,而消费者每秒消耗2个产品。假定生产者生产时,消费者不能消费;同样消费者消费时,生产者不能生产。请使用wait/notify模拟该模型。
//生产者-消费者模型 /*问题描述: 已知仓库容量为10 生产者每2秒生产5个产品,消费者每1秒消费2个产品 规定:生产者生产时,消费者不能消费;同理,消费者消费时,生产者不能生产 请用wait()/notify()描述该模型 */ public class Test { public static void main(String[] args){ Store store=new Store(); Thread producer=new Thread(()->{ while(true){ try{ store.produce(); }catch(InterruptedException e){ e.printStackTrace(); } } }); Thread consumer=new Thread(()->{ while(true){ try{ store.consume(); }catch(InterruptedException e){ e.printStackTrace(); } } }); producer.start(); consumer.start(); } }
public class Store { //数量 private int count; //最大数量 private static final int MAX=10; //设置起始状态 private boolean isProducing =true; //生产者方法 //使用synchronized关键字确保同一时间只有一个线程可以执行此方法 public synchronized void produce() throws InterruptedException { while(true){ if(!isProducing){//没有在生产 this.notify();//唤醒等待的线程(消费者) this.wait();//调用时,生产者线程进入waiting队列 continue; } count=count+5; System.out.println("生产者生产了5个产品,当前库存:"+count); Thread.sleep(2000); if(count>=MAX){ isProducing=false;//结束生产 } } } //消费者 public synchronized void consume() throws InterruptedException{ while(true){ if(isProducing){//没有消费(没有商品了,无法消费) this.notify();//唤醒等待的线程(生产者) this.wait();//调用时,消费者线程进入waiting队列 continue; } count=count-2; System.out.println("消费者消费了2个产品,当前库存:"+count); Thread.sleep(1000); if(count<=0){ isProducing=true;//结束消费 } } } }
哲学家就餐问题
6. 哲学家就餐问题(wait/notify)
哲学家就餐问题是1965年由Dijkstra提出的一种线程同步的问题。
一圆桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条。哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭。上述问题会产生死锁的情况,当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时产生死锁现象。
请尝试使用多线程模拟,确保不会出现“饿死”状态。
说明:本题有很多解法,但优先使用wait/notify模型,绘制类图。
【解析】
哲学家类 (Phy)
package 多线程.哲学家就餐问题; public class Phy implements Runnable{ private Servant servant; private int id; public Phy(int id,Servant servant){ this.id=id; this.servant=servant; } public void run(){ while(true){ try{ //System.out.println("哲学家:"+id+"在思考....."); //Math.random() 会生成一个范围在[0.0,1.0)的随机double数值 Thread.sleep((int) (1+Math.random()*5)*1000); servant.take(id); System.out.println("哲学家:"+id+"在吃饭....."); Thread.sleep((int)(1+Math.random()*5)*1000); servant.put(id); }catch(Exception e){ e.printStackTrace(); } } } }
服务类(Servant)
package 多线程.哲学家就餐问题; public class Servant { private boolean[] chops; private int n; public Servant(int n){ this.n=n; //n个筷子 chops =new boolean[n];//默认值是false(这里表示所有筷子都未被占用) } /*synchronized 作用 1.锁定对象:当一个线程调用take方法时,它会获取this对象的锁。 也就是说,同一个时刻只能有一个线程在执行take方法中的代码,其他线程必须等当前线程执行完成后才能执行 2.保证互斥:要是有多个线程同时调用take方法,Java会确保只有一个线程能在某个时刻执行方法的内容。 */ //要拿筷子 public synchronized void take(int pid)throws InterruptedException{ //如果筷子已经被占用,则进入等待 while(chops[pid]||chops[(pid+1)%this.n]){ /*唤醒其他线程:具体来说就是,当前线程的条件不满足要执行拿筷子这个操作的所需要的条件,这个线程就不执行了,一边等着去, 然后在这个线程里面调用notify(),去唤醒其他正在等待使用这个方法的线程(因为这个方法已经被整个锁起来了),让它们来调用这个方法 */ notify(); wait();//让当前的线程进入等待状态,直到其他线程通过notify()唤醒它 } //如果筷子没有被占用,那就赋值true,表示这个筷子被当前调用take方法的这个哲学家给拿了 chops[pid]=true; chops[(pid+1)%this.n]=true; } //放下筷子(前提是调用这个方法的哲学家拿到了筷子) //哲学家拿到筷子吃完之后就自然的放下了筷子 public synchronized void put(int pid){ chops[pid]=false; chops[(pid+1)%this.n]=false; notify();//唤醒其他也已经吃完饭了,但是还没有放下筷子,还在等待拿到该方法使用权的线程 } }
测试类
package 多线程.哲学家就餐问题; /*哲学家就餐问题 问题表述: 一圆桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条。--->圆桌 5个哲学家,5只筷子 哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭。 述问题会产生死锁的情况,当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时产生死锁现象。 请尝试使用多线程模拟,确保不会出现“饿死”状态。 wait/notify模型实现 */ public class Test { public static void main(String[] args){ final int N=5;//5个哲学家 Servant servant=new Servant(5); for(int i=0;i<N;i++){ new Thread(new Phy(i,servant)).start(); } } }
并发计算模拟
5. 并发计算模拟
计算从1到1亿整型数相加。要求使用并发程序处理,即采用多线程实现,在主线程中将计算结果累加(不能使用累加公式)。
(1)编写SumWorker类,实现Runnable接口,计算从m到n的和,其中m,n由构造方法传入;绘制类图;
(2)在主程序中每次开启若干个SumWorker线程(具体数量由程序指定或者用户输入),计算完成之后,将部分结果累加,然后再启动另一批线程,直到计算完成;注意并非所有线程都同时启动,允许分批执行。
【解析】
SumWorker类
package 多线程.并发计算模拟; public class SumWorker implements Runnable{ private final int from,to; //构造方法 public SumWorker(int from,int to){ this.from=from; this.to=to; } public void run(){ long localSum=0; for(int i=from;i<=to;i++){ localSum=localSum+i; } //使用类锁来保证线程安全 //sum 是静态变量,属于类本身 而不是类的对象 synchronized(Test.class){ Test.sum+=localSum; } } }
Test类
package 多线程.并发计算模拟; //计算从1到1亿的整数和 import java.util.ArrayList; import java.util.List; public class Test { //sum 属于Test类 //sum是线程共享的数据 //要确保同一时间只有一个线程在修改sum的值 public static long sum=0; //当我们知道某个方法可能会抛出异常的时候,但是不想在方法内部处理,就可以使用throws关键字来声明该方法可能抛出的异常 //t.join ---->InterruptedException public static void main(String[] args) throws InterruptedException{ int start=1; int end=100000000; //手动设置线程数量 int threadCount=10; //List 是一个接口(Collection集合里面的一个接口) 代表的是有序的集合(列表),允许重复元素 //ArrayList是实现List接口的具体类,提供了基于数组实现的动态列表 List<Thread> threads=new ArrayList<>(); //分配任务给线程 int step=(end-start+1)/threadCount; int remainder=(end-start+1)%threadCount;//剩下的 int s=start; for(int i=1;i<=threadCount;i++){ int e=s+step-1; if(i==threadCount){ //讲剩下的数分配给最后一个线程 end+=remainder; } //Thread t=new Thread(实现Runnable接口的实例) Thread t=new Thread(new SumWorker(s,e)); threads.add(t); t.start(); s=e+1; } //等待所有线程完成 for(Thread t:threads){ t.join();//主线程等待子线程结束 } //输出结果 System.out.println("Sum="+sum); } }
编程题
筛法求素数
1. 筛法求素数
编写一个程序,从键盘读取正整数n,使用筛法求不大于n的所有素数(或称质数,Prime Number),并逐个打印出来。输出结果为:
n=10
The prime numbers are 2,3,5,7
具体做法是:给出要筛数值的范围n,先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个素数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个素数5筛,把5留下,把5的倍数剔除掉;不断重复下去……
(1)编写函数
编写public static void printPrimeNumbers(int[] n)方法,将不大于n的每个素数输出;
编写public static int[] getPrimeNumbers(int n)方法,将每个不大于n的素数放入数组,并返回。
(2)编写测试代码
在main方法中输入n,调用getPrimeNumbers方法获取素数数组,再调用printPrimeNumbers方法输出这些素数。
【解析】
public class 筛法求素数 { public static void main(String[] args){ printPrimeNumbers(getPrimeNumbers(10)); } public static int[] getPrimeNumbers(int n){ int[] temp=new int[n+1]; temp[0]=-1; temp[1]=-1; for(int i=2;i<=n;i++){ temp[i]=0; } int count=0;//记录n以内的素数总数 for(int i=2;i<=n;i++){ if(temp[i]==0){ count++; for(int j=i*2;j<=n;j=j+i){ temp[j]=-1; } } } int[] result=new int[count]; int k=0; for(int i=0;i<temp.length;i++){ if(temp[i]!=-1){ result[k]=i; k++; } } return result; } public static void printPrimeNumbers(int[] result){ System.out.print("The prime numbers are "); int length=result.length; for(int i=0;i<length;i++){ System.out.print(result[i]+" "); } } }
二次方程求解
2. 二次方程式求解
为二次方程ax2+bx+c=0设计一个名为QuadraticEquation的类。这个类包括:
(1)代表三个系数的私有数据成员a,b和c;
(2)一个参数为a,b,c的构造方法;
(3)一个名为getDiscriminant()的方法,返回判别式:b2-4ac;
(4)名为getRoot1()和getRoot2()的方法,返回二次方程的两个根:r1=(-b+(b2-4ac)1/2)/2a,r2=(-b-(b2-4ac)1/2)/2a。这两个方法只有在判别式为非负数时才有用。
实现这个类。编写一个测试程序,提示用户输入a,b和c的值,然后显示判别式的值。如果判别式为正数,显示两个根;如果判别式为0,则显示一个根,否则显示"The equation has no roots."字样。
【解析】
同一个包下
QuadraticEquation类
public class QuadraticEquation { private double a,b,c; public QuadraticEquation(double a,double b,double c){ this.a=a; this.b=b; this.c=c; } public double getDiscriminant(){ return b*b-4*a*c; } public double getRoot1(){ // (-b+Math.sqrt(getDiscriminant()))/2*a; 不能这样写 这样写会先/2 在整体*a return (-b+Math.sqrt(getDiscriminant()))/(2*a); } public double getRoot2(){ return (-b-Math.sqrt(getDiscriminant()))/(2*a); } }
Test类
import java.util.Scanner; public class Test { public static void main(String[] args){ //获取a,b,c的值 Scanner scanner=new Scanner(System.in); double a,b,c; System.out.println("请输入a,b,c的值:"); a=scanner.nextDouble();//记得最后有个() b=scanner.nextDouble(); c=scanner.nextDouble(); //求解 QuadraticEquation q=new QuadraticEquation(a,b,c); double temp=q.getDiscriminant(); System.out.println("判别式的值为:"+temp); if(temp>0){ System.out.println("root1="+q.getRoot1()+","+"root2="+q.getRoot2()); } else if(temp==0){ System.out.println("root="+q.getRoot1()); } else{ System.out.println("The equation has no roots"); } //关闭Scanner对象 scanner.close(); } }
矩阵四则运算
3. 矩阵四则运算
定义矩阵类Matrix,包括:
(1)代表矩阵的行数rows(或m)、列数cols(或n),以及二维数组data;
(2)一个参数为rows,cols的构造方法,实现初始化操作,并将矩阵元素全部置为0;
(3)public void setElement(int row, int col, double value);方法,用于设置第row行,第col列元素的值;
(4)public Matrix add(Matrix m);方法,实现当前矩阵与m矩阵相加,并返回新的矩阵;若无法相加,则返回null;
(5)public Matrix minus(Matrix m);方法,实现当前矩阵减去m矩阵,并返回新的矩阵;若无法相减,则返回null;
(6)public Matrix multiply(Matrix m);方法,实现当前矩阵乘以m矩阵,并返回新的矩阵;若无法相乘,则返回null;
(7)public Matrix transpose();方法,实现矩阵转置,并返回新的矩阵;
(8)public void display();方法,打印当前矩阵。
实现该类。编写一个测试程序,随机生成矩阵元素或者由程序中用常量设置(可不必由键盘输入),测试上述四则运算,打印运算结果。
【解析】
同一个包下
Matrix类
//矩阵类 public class Matrix { private int rows; private int cols; private double[][] data; //构造方法 //无参 public Matrix(){}; //有参 public Matrix(int rows,int cols){ this.rows=rows; this.cols=cols; this.data=new double[rows][cols]; for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ data[i][j]=0; } } } //获取矩阵特定行,特定列的元素 public double getElement(int row,int col) { if (0 <= row && row < rows && 0 <= col && col < cols) { return data[row][col]; } return -1; } //设定矩阵特定行,特定列的元素的值 public void setElement(int row,int col,double value){ if(0<=row&&row<rows&&0<=col&&col<cols) { this.data[row][col] = value; } } //实现矩阵相加 //要记得先判定能不能加 //注意方法的返回值类型:Matrix public Matrix add(Matrix m){ if(m.rows==rows&&m.cols==cols){ Matrix result=new Matrix(rows,cols); for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ result.data[i][j]=data[i][j]+m.data[i][j]; } } return result; } return null; } //实现两矩阵相减 public Matrix minus(Matrix m){ if(m.cols==cols&&m.rows==rows){ Matrix result=new Matrix(rows,cols); for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ result.data[i][j]=data[i][j]-m.data[i][j]; } } return result; } return null; } //实现两矩阵相乘 public Matrix multiply(Matrix m){ if(cols==m.rows){ Matrix result=new Matrix(rows,m.cols); for(int i=0;i<rows;i++){ for(int j=0;j<m.cols;j++){ double sum=0; for(int k=0;k<cols;k++) { sum = sum + data[i][k] * m.data[k][j]; } result.data[i][j]=sum; } } return result; } return null; } //实现矩阵的转置 public Matrix transpose(){ Matrix result=new Matrix(cols,rows); for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ result.data[j][i]=data[i][j]; } } return result; } public void display(){ for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ System.out.print(data[i][j]+" "); } System.out.println(); } System.out.println(); } }
Test类
public class Test { public static void main(String[] args){ //矩阵1 Matrix m1=new Matrix(2,2); m1.setElement(0,0,1); m1.setElement(0,1,1); m1.setElement(1,0,2); m1.setElement(1,1,2); m1.display(); //矩阵2 Matrix m2=new Matrix(2,2); m2.setElement(0,0,2); m2.setElement(0,1,2); m2.setElement(1,0,1); m2.setElement(1,1,1); m2.display(); // m1.display(); // m2.display(); // (m1.add(m2)).display(); // (m1.minus(m2)).display(); // (m1.multiply(m2)).display(); // (m1.transpose()).display(); // (m2.transpose()).display(); //记得做非空检查 Matrix result=new Matrix(); //加法 result=m1.add(m2); if(result!=null){ result.display(); } else{ System.out.println("当前两矩阵无法实现相加操作!"); } //减法 result=m1.minus(m2); if(result!=null){ result.display(); }else{ System.out.println("当前两矩阵无法实现减法操作!"); } //乘法 result=m1.multiply(m2); if(result!=null){ result.display(); }else{ System.out.println("当前矩阵无法实现乘法操作!"); } //转置 (m1.transpose()).display(); (m2.transpose()).display(); }
Shape
9. Shape
(1)定义抽象类Shape(图形),定义常量PI,添加两个抽象方法: double getPerimeter()和double getArea(),分别用于求图形的周长和面积。
(2)编写Rectangle(长方形)、Triangle(三角形)和Circle(圆形),均继承自Shape类;为各个子类添加合适的成员、构造方法和getters/setters。
(3)实现3个子类的求周长、面积的方法。
(4)添加默认比较器,实现按面积的降序排序。
(5)编写一个测试类,在测试类中添加若干个Rectangle, Triangle, Circle实例(个数及内容自定),并在测试类中定义并测试如下方法:
①编写方法private static void printPerimeter(Shape[] shapes),打印出每个图形的周长;
②编写方法private static void sortByArea(Shape[] shapes),实现对面积的降序排序;
➂编写方法private static void sortByPerimeter(Shape[] shapes),实现对周长的升序排序。
附:海伦公式求三角形面积。已知边a,b,c的三角形,其面积公式如下:
其中,p=(a+b+c)/2
【解析】
抽象类Shape
package B9; import java.util.Comparator; //定义抽象类Shape public abstract class Shape { public static final double PI=3.14; public abstract double getPerimeter(); public abstract double getArea(); //默认比较器,实现按面积的降序排序 public static final Comparator<Shape> areaComparator=new Comparator<>(){ public int compare(Shape s1,Shape s2){ return Double.compare(s2.getArea(),s1.getArea()); } }; //默认比较器,实现按周长的升序排序 public static final Comparator<Shape> perimeterComparator=new Comparator<Shape>(){ public int compare(Shape s1,Shape s2){ return Double.compare(s1.getPerimeter(),s2.getPerimeter()); } }; }
长方形类Rectangle
package B9; public class Rectangle extends Shape{ private double x; private double y; private double h; public Rectangle(double x,double y,double h){ this.x=x; this.y=y; this.h=h; } //setter和getter方法 public void setx(double x){ this.x=x; } @Override public double getPerimeter() { return 4*(x+y+h); } @Override public double getArea() { return 2*(x*y+x*h*y*h); } }
三角形类Triangle
package B9; public class Triangle extends Shape{ private double a; private double b; private double c; public Triangle(double a,double b,double c){ this.a=a; this.b=b; this.c=c; } public double getPerimeter(){ return a+b+c; } public double getArea(){ double p=(a+b+c)/2; double result= Math.sqrt(p*(p-a)*(p-b)*(p-c)); return result; } }
圆形类Circle
package B9; public class Circle extends Shape { private double r; public Circle(double r){ this.r=r; } public double getPerimeter(){ return PI*r*2; } public double getArea(){ return PI*r*r; } }
Test类
package B9; import java.util.Arrays; public class Test { public static void main(String[] args){ //长方形 Rectangle r1=new Rectangle(1,1,1); Rectangle r2=new Rectangle(1,1,2); Rectangle r3=new Rectangle(1,2,2); //三角形 Triangle t1=new Triangle(1,1,1); Triangle t2=new Triangle(1,1,2); Triangle t3=new Triangle(1,2,2); //圆形 Circle c1=new Circle(1); Circle c2=new Circle(2); Circle c3=new Circle(3); Shape[] shapes=new Shape[9]; shapes[0]=r1;shapes[1]=r2;shapes[2]=r3; shapes[3]=t1;shapes[4]=t2;shapes[5]=t3; shapes[6]=c1;shapes[7]=c2;shapes[8]=c3; printPerimeter(shapes); System.out.println(); sortByArea(shapes); printPerimeter(shapes); System.out.println(); sortByPerimeter(shapes); printPerimeter(shapes); } private static void printPerimeter(Shape[] shapes){ for(Shape shape:shapes){ System.out.println("Perimeter:"+shape.getPerimeter()); } } private static void sortByArea(Shape[] shapes){ //Arrays.sort方法接受两个参数:一个是待排序的数组,另一个是用于定义排序规则的Comparator Arrays.sort(shapes,Shape.areaComparator); } private static void sortByPerimeter(Shape[] shapes){ Arrays.sort(shapes,Shape.perimeterComparator); } }
完全数(Perfect number)
10. 完全数(Perfect number)
完全数(Perfect number),又称完美数或完备数,是一些特殊的自然数。它所有的真因子(即除了自身以外的约数)的和(即因子函数),恰好等于它本身。
请编写程序,输出n以内所有完全数(n由控制台输入)。
【解析】
package B10; import java.util.Scanner; public class PerfectNumbers { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.print("请输入一个正整数 n: "); int n = scanner.nextInt(); System.out.println("n 以内的完全数有:"); for (int i = 1; i <= n; i++) { if (isPerfectNumber(i)) { System.out.println(i); } } } // 判断一个数是否为完全数 public static boolean isPerfectNumber(int number) { int sum = 0; for (int i = 1; i <= number / 2; i++) { if (number % i == 0) { sum += i; } } if( sum == number) return true; else return false; } }
约瑟夫环问题
8. 约瑟夫环(Josephus Ring)
编号为1到n的n个人围成一圈。从编号为1的人开始报数,报到m的人离开。下一个人继续从1开始报数。n-1轮结束后,只剩下一个人,问最后留下的人编号是多少?
package B8; import java.util.LinkedList; import java.util.Scanner; public class Test { public static int findLastPerson(int n, int m) { // 创建一个 LinkedList,存储编号为 1 到 n 的所有人 LinkedList<Integer> circle = new LinkedList<>(); for (int i = 1; i <= n; i++) { circle.add(i); } // 当前报数的索引 int index = 0; // 循环直到只剩一个人 while (circle.size() > 1) { // 计算报数到第 m 个人的索引 index = (index + m - 1) % circle.size(); // 删除该人 circle.remove(index); } // 返回最后剩下的人的编号 return circle.get(0); } public static void main(String[] args) { // 从键盘输入 n 和 m Scanner scanner = new Scanner(System.in); System.out.print("请输入人数 n: "); int n = scanner.nextInt(); System.out.print("请输入报数的间隔 m: "); int m = scanner.nextInt(); // 求解并输出最后剩下的人的编号 int result = findLastPerson(n, m); System.out.println("最后剩下的人的编号是: " + result); scanner.close(); } }