构造有限个数的有效三角形
/*创建一个三角形类,包含属性:private int a,b,c;分别代表三角形的三条边。设计是需要满足如下需求:
a. 在创建对象输入三条边,三边取值必须合法(三边均为正值,且任意两边之和大于第三边),否则将无法创建对象;
b.为使类更易于维护,必须单独设计一个boolean limit(…)函数,实现对三条边的限制检查,符合创建条件则返回真,否则返回假。
c.该类有一个public boolean setEdges(int x, int y, int z)方法(注:此方法为非静态方法),将a/b/c的值替换成x/y/z,返回true。
但当x,y,z的值不满足三角形限制条件时,将不予替换,并返回false;
d. 三角形至多能创建3个。
设计该三角形类,并验证上述需求。 */
public class Triangle {
private int a,b,c;
static int total = 3,count = 0;
private Triangle(int x,int y,int z){
a = x;b = y;c = z;
count++;
}
static boolean limit(int x,int y,int z) {
if(x>0 && y>0 && z>0 && x+y>z && x+z>y && y+z>x) {
return true;
}
return false;
}
public static Triangle setTri(int x,int y,int z) {
if(limit(x,y,z) && count<total) {
return new Triangle(x,y,z);
}
return null;
}
public boolean setEdges(int x,int y,int z) {
if(limit(x,y,z)) {
a = x;b = y;c = z;
return true;
}
return false;
}
public String toString() {
return "(" + a + "," + b + "," + c + ")";
}
}
class App1{
public static void main(String[] args) {
Triangle[] t = new Triangle[5];
t[0] = Triangle.setTri(2, 2, 1);
t[1] = Triangle.setTri(1, 2, 1);
t[2] = Triangle.setTri(3, 4, 5);
t[3] = Triangle.setTri(2, 4, 4);
t[4] = Triangle.setTri(6, 8, 10);
for(int i = 0; i<5; i++)
System.out.println(t[i]);
}
}
注:
- count++要放在构造函数中,只要调用了构造函数就会创建对象
- 类中的toString()方法必须用public修饰,不能改小权限,且该方法会被对象自动调用
- 静态方法只能使用类中的静态成员,所以limit()方法和total,count变量要为静态的
形状识别器
使用抽象类实现
/*实现智能识别器,可针对圆形、矩形、三角形、梯形不同形状,提供如下服务:
a. 识别形状的面积;
b. 输出形状属性信息:类型和各种参数,如:梯形,上底:20,下底:30,高:10)--- 考虑toString()*/
import static java.lang.Math.PI;
abstract class Shape {
private String type;
Shape(String t){
type = t;
}
String getType() {
return type;
}
abstract double getArea();
}
class Circle extends Shape{
private int r;
Circle(String s,int R){
super(s);
r = R;
}
double getArea() {
return PI*r*r;
}
public String toString() {
return "半径:" + r;
}
}
class Rectangle extends Shape{
private int w,h;
Rectangle(String s,int x,int y){
super(s);
w = x;h = y;
}
double getArea() {
return w*h;
}
public String toString() {
return "宽:" + w + ",高:" + h;
}
}
class Trigon extends Shape{
private int s,h;
Trigon(String t,int x,int y){
super(t);
s = x;h = y;
}
double getArea() {
return s*h/2;
}
public String toString() {
return "底:" + s + ",高:" + h;
}
}
class Trapezoidal extends Shape{
private int a,b,h;
Trapezoidal(String s,int x,int y,int z){
super(s);
a = x;b = y;h = z;
}
double getArea() {
return (a+b)*h/2;
}
public String toString() {
return "上底:" + a + ",下底:" + b + ",高:" + h;
}
}
class Recognizer{
public static void identify(Shape s) {
System.out.println(s.getType() + "," + s.toString() + ",面积:" + s.getArea());
}
}
class App2{
public static void main(String[] args) {
Shape[] s = {new Circle("圆形", 2),
new Rectangle("矩形", 3, 4),
new Trigon("三角形", 4, 2),
new Trapezoidal("梯形", 2, 4, 3)
};
for(int i = 0;i<4;i++)
Recognizer.identify(s[i]);
}
}
注:
- 私有属性可以通过类中的方法返回得到
- abstract类中不一定要有抽象方法,但抽象方法所属类一定是抽象类
- 抽象方法不能有方法体
- 该识别器基于超类编程,实现即插即用的功能,添加子类很方便
使用接口实现
/*【接口实现】实现智能识别器,可针对圆形、矩形、三角形、梯形不同形状,提供如下服务:
a. 识别形状的面积;
b. 输出形状属性信息:类型和各种参数,如:梯形,上底:20,下底:30,高:10)--- 考虑toString()*/
import static java.lang.Math.PI;
interface Shapein {
public abstract String getType();
public abstract double getArea();
}
class yx implements Shapein{
private int r;
private String type;
yx(String s,int R){
r = R;
type = s;
}
public String getType() {
return type;
}
public double getArea() {
return PI*r*r;
}
public String toString() {
return "半径:" + r;
}
}
class jx implements Shapein{
private int w,h;
private String type;
jx(String s,int x,int y){
w = x;h = y;
type = s;
}
public String getType() {
return type;
}
public double getArea() {
return w*h;
}
public String toString() {
return "宽:" + w + ",高:" + h;
}
}
class sj implements Shapein{
private int w,h;
private String type;
sj(String s,int x,int y){
w = x;h = y;
type = s;
}
public String getType() {
return type;
}
public double getArea() {
return w*h/2;
}
public String toString() {
return "底:" + w + ",高:" + h;
}
}
class tx implements Shapein{
private int a,b,h;
private String type;
tx(String s,int x,int y,int z){
a = x;b = y;h = z;
type = s;
}
public String getType() {
return type;
}
public double getArea() {
return (a+b)*h/2;
}
public String toString() {
return "上底:" + a + ",下底:" + b + ",高:" + h;
}
}
class Identifier{
public static void identify(Shapein s) {
System.out.println(s.getType() + "," + s.toString() + ",面积:" + s.getArea());
}
}
class App3{
public static void main(String[] args) {
Shapein[] s = {new yx("圆形", 2),
new jx("矩形", 3, 4),
new sj("三角形", 4, 2),
new tx("梯形", 2, 4, 3)
};
for(int i = 0;i<4;i++)
Identifier.identify(s[i]);
}
}
注:
- 接口中只能定义常量,且常量都有public static final修饰,接口中的方法都有public abstract修饰
- 在main函数中,用的是接口引用指向实现类的对象,Java在利用接口变量调用其实现类的对象的方法时,该方法必须已经在接口中被声明,而且实现类的对象对该方法进行了重写。
兵种游戏设计
/*兵种包括轰炸机、直升机、重型坦克、轻型坦克、音波坦克、步兵、飞行兵
b.轰炸机、直升机、飞行兵属于空军;步兵、轻型坦克、重型坦克属于陆军,音波坦克属于水陆两栖;
c.轻型坦克、步兵只能攻击陆军,音波坦克只能攻击空军,轰炸机可攻击陆军、海军;重型坦克可攻击陆军、空军,直升机、飞行兵可攻击海军、陆军、空军;
并验证设计效果。(即向兵种变量填入海/陆/空军兵种,检测a.attack(b)的输出)*/
interface 陆军{}
interface 空军{}
interface 海军{}
interface 可攻击陆军{
default void attack(陆军 x) {System.out.println("可攻击陆军");}
}
interface 可攻击空军{
default void attack(空军 x) {System.out.println("可攻击空军");}
}
interface 可攻击海军{
default void attack(海军 x) {System.out.println("可攻击海军");}
}
interface 可攻击陆军海军 extends 可攻击陆军,可攻击海军{}
interface 可攻击陆军空军 extends 可攻击陆军,可攻击空军{}
interface 可攻击陆军空军海军 extends 可攻击陆军,可攻击空军,可攻击海军{}
abstract class 兵种{
private String type;
public String getType() {return type;}
兵种(String x){
type = x;
}
public abstract void attack(兵种 x);
public String attackinfo(兵种 x) {
return getType() + "遇见" + x.getType() +":";
}
}
class 轰炸机 extends 兵种 implements 空军,可攻击陆军海军{
轰炸机(){
super("轰炸机");
}
public void attack(兵种 x) {
if(x instanceof 陆军) {
System.out.print(attackinfo(x));
attack((陆军)x);
}else if(x instanceof 海军) {
System.out.print(attackinfo(x));
attack((海军)x);
}else {
System.out.println(attackinfo(x) + "不能攻击!");
}
}
}
class 直升机 extends 兵种 implements 空军,可攻击陆军空军海军{
直升机(){
super("直升机");
}
public void attack(兵种 x) {
if(x instanceof 陆军) {
System.out.print(attackinfo(x));
attack((陆军)x);
}else if(x instanceof 海军) {
System.out.print(attackinfo(x));
attack((海军)x);
}else {
System.out.print(attackinfo(x));
attack((空军)x);
}
}
}
class 飞行兵 extends 兵种 implements 空军,可攻击陆军空军海军{
飞行兵(){
super("飞行兵");
}
public void attack(兵种 x) {
if(x instanceof 陆军) {
System.out.print(attackinfo(x));
attack((陆军)x);
}else if(x instanceof 海军) {
System.out.print(attackinfo(x));
attack((海军)x);
}else {
System.out.print(attackinfo(x));
attack((空军)x);
}
}
}
class 重型坦克 extends 兵种 implements 陆军,可攻击陆军空军{
重型坦克(){
super("重型坦克");
}
public void attack(兵种 x) {
if(x instanceof 陆军) {
System.out.print(attackinfo(x));
attack((陆军)x);
}else if(x instanceof 空军) {
System.out.print(attackinfo(x));
attack((空军)x);
}else {
System.out.println(attackinfo(x) + "不能攻击!");
}
}
}
class 音波坦克 extends 兵种 implements 陆军,海军,可攻击空军{
音波坦克(){
super("音波坦克");
}
public void attack(兵种 x) {
if(x instanceof 空军) {
System.out.print(attackinfo(x));
attack((空军)x);
}else {
System.out.println(attackinfo(x) + "不能攻击!");
}
}
}
class 轻型坦克 extends 兵种 implements 陆军,可攻击陆军{
轻型坦克(){
super("轻型坦克");
}
public void attack(兵种 x) {
if(x instanceof 陆军) {
System.out.print(attackinfo(x));
attack((陆军)x);
}else {
System.out.println(attackinfo(x) + "不能攻击!");
}
}
}
class 步兵 extends 兵种 implements 陆军,可攻击陆军{
步兵(){
super("步兵");
}
public void attack(兵种 x) {
if(x instanceof 陆军) {
System.out.print(attackinfo(x));
attack((陆军)x);
}else {
System.out.println(attackinfo(x) + "不能攻击!");
}
}
}
class App4{
public static void main(String[] args) {
兵种[] x = {
new 轰炸机(),new 直升机(),new 重型坦克(),new 轻型坦克(),new 音波坦克(),new 飞行兵(),new 步兵()
};
for(int i = 0;i<x.length;i++) {
x[i].attack(x[x.length-1-i]);
}
}
}
注:
- 陆军、海军和空军都是空接口,仅起到标签作用
- 接口中的attack()方法不是抽象方法,有缺省实现,子类可以不重写
- 通过instanceof来判断兵种的具体类型,并在attack()中进行强制类型转换
PCI
/*展示接口实现“功能扩展”
【需求】主板预留PCI插槽。不可能知道该插槽将插装什么硬件。
主板做的事情只能是:加电、启动、run、停止。主板类预留5个PCI插槽,如何实现?
提示:5个插槽,就是5个变量。
* */
interface PCI {
void start();
void run();
void stop();
}
class NetCard implements PCI{
public void start() {System.out.print("\n网卡启动 ");}
public void run() {System.out.print("网卡运行");}
public void stop() {System.out.print("\n网卡停止");}
}
class SoundCard implements PCI{
public void start() {System.out.print("\n声卡启动 ");}
public void run() {System.out.print("声卡运行");}
public void stop() {System.out.print("\n声卡停止");}
}
class DisplayCard implements PCI{
public void start() {System.out.print("\n显卡启动 ");}
public void run() {System.out.print("显卡运行");}
public void stop() {System.out.print("\n显卡停止");}
}
class MainBoard{
PCI[] pci = new PCI[5];
public void add(PCI x) {
for(int i = 0;i<pci.length;i++)
if(pci[i] == null){
pci[i] = x;
return;
}
System.out.print("插槽已满!");
}
public void start() {
for(int i = 0;i<pci.length;i++)
if(pci[i] != null) {
pci[i].start();
pci[i].run();
}
}
public void stop() {
for(int i = 0;i<pci.length;i++)
if(pci[i] != null) pci[i].stop();
}
}
class Computer{
MainBoard mb = new MainBoard();//电脑中有一块主板,主板插装何种板卡通过参数传入
Computer(PCI[] x){
for(int i = 0;i<x.length;i++) {
mb.add(x[i]);//依次将PCI板卡插入主板
}
}
void start() {mb.start();}
void stop() {mb.stop();}
}
class App5{
public static void main(String[] args) {
PCI[] pci = {new NetCard(),new SoundCard(),new DisplayCard()};
Computer c = new Computer(pci);
c.start();c.stop();
}
}
互斥
/*线程类T定义方式为:class T implements Runnable{ int data; ...}
线程体实现依次输出data的1~10倍数据。
在main中,构造和启动线程的方式形如: T t=new T(2); t.start();
构造data分别为2、3、5的线程,线程名分别为t2、t3、t5,要求:t2、t3、t5分3行输出,且输出结果不得交叉。
* */
class D{
int x;
}
class T implements Runnable {
D d;
int data;
Thread t;
T(int x,D y){
data = x;
d = y;
t = new Thread(this);
}
public void run() {
synchronized(d) {
for(int i = 1;i<=10;i++) {
System.out.print(data*i + " ");
}
System.out.println();
}
}
void join() {
try {t.join();}catch(Exception e) {;}
}
void start() {
t.start();
}
}
class App6{
public static void main(String[] args) {
System.out.println("main线程开始");
D d = new D();
T t2 = new T(2,d);
T t3 = new T(3,d);
T t5 = new T(5,d);
t2.start();t3.start();t5.start();
t2.join();t3.join();t5.join();
System.out.println("main线程结束");
}
}
注:
- 类D仅用来充当对象锁和共享资源,所以不同线程传入的D要相同
- 该程序中join()用于让main线程在t2、t3和t5线程结束后结束
- t2、t3和t5并不是线程对象,但其内部包含线程对象
同步
/*线程类T定义方式为:class T implements Runnable{ int data; ...}
线程体实现依次输出data的1~10倍数据。
在main中,构造和启动线程的方式形如: T t=new T(2); t.start();
构造data分别为2、3、5的线程,线程名分别为t2、t3、t5,要求:t2、t3、t5输出结果按2 3 5 4 6 10...特定顺序输出。
* */
class Flag{
private int flag = 0;
void ChangeFlag(int x,int y) {
while(flag!=x) try {wait();}catch(Exception e) {;}
flag = y;notifyAll();
}
}
class Th implements Runnable{
private Flag flag;
int data,x,y;
Thread t;
Th(int h,int m,int n,Flag f){
t = new Thread(this);
data = h;flag = f;x = m;y = n;
}
public void run() {
synchronized(flag) {
for(int i = 1;i<=10;i++) {
try {Thread.sleep(1000);}catch(Exception e) {}
flag.ChangeFlag(x, y);
System.out.print(data*i + " ");
}
}
}
void start() {t.start();}
}
class App7{
public static void main(String[] args) {
Flag f = new Flag();
Th t2 = new Th(2,0,1,f);
Th t3 = new Th(3,1,2,f);
Th t5 = new Th(5,2,0,f);
t2.start();t3.start();t5.start();
}
}
注:
- Flag类用于状态标志不同线程的执行顺序,如果轮到t2执行,则将状态改为下一个执行线程的状态,并唤醒所有线程,否则等待
- 判断标志时不能用if,因为可能会存在伪唤醒,放在循环体中可避免这种情形
- 线程定义中的构造函数需要传入四个值:要进行操作的数据、当前状态、下一个状态、共享资源
委托事件处理模型
/*做一个简单的计算器,[1][+][2]=[3],[]为文本框,点击=按钮计算出结果,最后一个文本框不可点击(enable(false)),并借助标签显示出错信息
* a.当操作数文本框/运算符文本框中的字符串,在剔除首尾空格后,若字符串长度为0,则抛出自定义异常NoneException,提示“操作数为空”/“运算符为空”
* b.当运算符框中的字符串s剔除首尾空格(s.trim())后,若包含多个字符/不是+-* /等字符时抛出自定义异常OpCharException,提示“运算符过多”/“无法识别的运算符”
* c.对Double.parseDouble(s),如果无法将s转换成正确的double值,若s是“1.2.3”、“a.b”、“a3b”或是转换后的数值越界时,将抛出非检查型异常NumberFormatException,
* 提示“数据格式有错”;产生除零错时将抛出非检查型异常ArithmeticException,提示“除零错”
*/
import java.awt.*;import java.awt.event.*;import javax.swing.*;
class NoneException extends Exception{
public NoneException(String msg){super(msg);}
}
class OpCharException extends Exception{
public OpCharException(String msg){super(msg);}
}
public class GUI extends JFrame implements ActionListener {
private JTextField op1,op2,opchar,result;//对应操作数1、2、操作符、运算结果
private JButton bt_eq;//=按钮
private JLabel t_la;//显示错误信息
public GUI(){
super("计算器");
setSize(500,100);
setLocation(500,500);
setLayout(new FlowLayout());
op1 = new JTextField(5); add(op1);
opchar = new JTextField(2); add(opchar);
op2 = new JTextField(5); add(op2);
bt_eq = new JButton("="); add(bt_eq);
result = new JTextField(5);
result.setEnabled(false);
add(result);
t_la = new JLabel(" ");
add(t_la);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置点击关闭按钮
bt_eq.addActionListener(this);
}
private double getDouble(JTextField jf,int num) throws NoneException{
//检测jf是否为合法的操作数
String s = jf.getText().trim();double val;
if(s.length() == 0) throw new NoneException("第"+ num + "个操作数为空");
try {
val = Double.parseDouble(s);
}catch(NumberFormatException e) {
throw new NumberFormatException("第"+ num + "个操作数格式有错");
}
return val;
}
private char getOpChar(JTextField jf) throws NoneException,OpCharException{
//检测jf是否为合法的运算符
String s = jf.getText().trim();
if(s.length()==0) throw new NoneException("运算符为空");
if(s.length()>1) throw new OpCharException("运算符过多");
if("+-*/".indexOf(s)<0) throw new OpCharException("无法识别的运算符");
return s.charAt(0);//返回字符串第0位置的字符
}
private double compute(double a,double b,char op) {
//进行运算
if(b==0 && op == '/') throw new ArithmeticException("除零错");
if(op == '+') return a+b;
if(op == '-') return a-b;
if(op == '*') return a*b;
if(op == '/') return a/b;
return -1;//实际上为通过编译而设置
}
public void actionPerformed(ActionEvent e) {
double x,y;
char z;
// if(e.getCommand() == bt_eq) {
if(e.getActionCommand().equals("=")) {//确定事件源的两种写法
try {
x = getDouble(op1,1);y = getDouble(op2,2);z = getOpChar(opchar);
result.setText(compute(x,y,z)+"");
}
catch(Exception c) {
result.setText(" ");
t_la.setText(c.getMessage());
}
}
}
}
class App8{
public static void main(String[] args) {new GUI();}
}
注:
- 自定义异常类必须是Throwable类的子类,一般以Exception作为超类,且可以通过super()将出错信息传给超类,Exception类也可以通过getMessage()方法显示出错信息
- 委托事件处理模型:
- 处理者(GUI对象)要实现ActionListener接口
- 类中要重写接口中的actionPerformed()方法,方法中的参数为ActionEvent,在该方法中执行处理过程
- 将事件源(bt_eq按钮)与处理者相关联(通过addActionListener()方法)
- 将处理过程分成多个部分,使处理过程更加清晰
- “±* /”.indexOf(s)返回的是指定子字符串s在字符串"±*/"中第一次出现处的索引
复制文件
逐个字符读/写文件
public class FileCopy {
public static void main(String[] args) {
File inF = new File("a.txt");//关联当前文件夹中的文件a.txt,作为读出源头
File outF = new File("b.txt");//关联当前文件夹中的文件b.txt,作为写入目标
try {
int ch;
FileReader in = new FileReader(inF);//建立阅读器对象in
FileWriter out = new FileWriter(outF);
System.out.println("文件开始复制");
while((ch = in.read())!=-1)
out.write(ch);//从流in中读取字符ch并写入流out
in.close();out.close();//关闭流,释放相关资源
System.out.println("文件复制结束");
}catch(FileNotFoundException e) {
System.out.println("文件未找到!\n" + e);
}catch(IOException e) {
System.out.println("File read error!\n" + e);
}
}
}
注:
- a.txt要和src放在同一目录下,否则会找不到文件,当然也可以用绝对路径
按字符流整体读/写文件
public class FileCopy {
public static void main(String[] args) {
File inF = new File("a.txt");
File outF = new File("c.txt");
char[] ca = new char[(int)inF.length()];
try {
FileReader in = new FileReader(inF);
FileWriter out = new FileWriter(outF);
System.out.println("文件开始复制");
in.read(ca);out.write(ca);
in.close();out.close();
System.out.println("文件复制结束");
}catch(FileNotFoundException e) {
System.out.println("文件未找到!\n" + e);
}catch(IOException e) {
System.out.println("File read error!\n" + e);
}
}
}
基于缓冲区读/写文件
public class FileCopy {
public static void main(String[] args) {
String s;
File inF = new File("a.txt");
File outF = new File("d.txt");
try {
FileReader in = new FileReader(inF);
FileWriter out = new FileWriter(outF);
BufferedReader br = new BufferedReader(in);
BufferedWriter bw = new BufferedWriter(out);
System.out.println("文件开始复制");
while((s = br.readLine()) !=null) {//读取一行,并赋值给字符串s
bw.write(s,0,s.length());//将s中从0至s.length()的所有字符串写入bw
bw.newLine();//添加行分隔符
}
br.close();bw.close();
System.out.println("文件复制结束");
}catch(FileNotFoundException e) {
System.out.println("文件未找到!\n" + e);
}catch(IOException e) {
System.out.println("File read error!\n" + e);
}
}
}
防止出现乱码
class Utf_8_to_GBK {
public static void main(String args[] ){
BufferedReader br=null;
BufferedWriter bw=null;
String s;
try{ //按UTF-8格式读,按GBK格式写
br=new BufferedReader(new InputStreamReader(new FileInputStream("a.txt"), "UTF-8"));
bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("e.txt"), "GBK"));
System.out.println("文件开始复制");
while((s=br.readLine())!=null){
bw.write(s,0,s.length());
bw.newLine();
}
br.close(); bw.close();
System.out.println("文件复制结束");
}
catch(UnsupportedEncodingException e){ System.out.println("不支持的编码格式!\n"+e); }
catch(FileNotFoundException e){ System.out.println("文件没找到!\n"+e); }
catch(IOException e){ System.out.println("File read error!\n"+e); }
}
}
序列化与反序列化
import java.io.*;
class C{
public int x;
}
class B{
public int x;
public C c;
public B(int n,C c1) {x = n;c = c1;}
}
class A implements Serializable{
int x;
transient B b;//b引用对象将不被序列化
}
class ReadObject{
public static void main(String[] args) {
C c = new C();A a =new A();a.x = 10;a.b = new B(5,c);
System.out.println("序列化前 a.b.x=" + a.b.x);
A new_a = null;
try {
FileOutputStream fo = new FileOutputStream("serialObj.bat");
ObjectOutputStream obj_o = new ObjectOutputStream(fo);
obj_o.writeObject(a);obj_o.close();
FileInputStream fi = new FileInputStream("serialObj.bat");
ObjectInputStream obj_i = new ObjectInputStream(fi);
new_a = (A)obj_i.readObject();obj_i.close();
}catch(Exception e) {e.printStackTrace();}
System.out.print("恢复对象 new_a.b.x=" + new_a.b.x);
}
}
注:
- 本例将产生空指针异常,因为未对B对象序列化,在恢复时就没有创建相应的对象,当引用该属性域时会出错
- transient是成员修饰符,不能修饰类