类与对象
用类制造对象
建立程序环境(shapes)
MyPic(主程序)
package shapes;
public class MyPic {
public static void main(String[] args)
{
Picture pic = new Picture(420,300);
Circle c1 = new Circle(320,40,80);
Rectangle r1 = new Rectangle(100, 100, 100, 100);
Triangle t1 = new Triangle(100, 100, 200, 100, 150, 50);
Line l1 = new Line(0,205,400,205);
Circle c2 = new Circle(200,200,50);
pic.add(c1);
pic.add(r1);
pic.add(t1);
pic.add(l1);
pic.add(c2);
pic.draw();
}
}
Picture(窗口)
package shapes;
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Picture extends JFrame {
private static final long serialVersionUID = 1L;
private int width;
private int height;
private ArrayList<Shape> listShape = new ArrayList<Shape>();
private class ShapesPanel extends JPanel {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for ( Shape s : listShape )
{
s.draw(g);
}
}
}
public void add(Shape s)
{
listShape.add(s);
}
public Picture(int width, int height)
{
add(new ShapesPanel());
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.width = width;
this.height = height;
}
public void draw()
{
setLocationRelativeTo(null);
setSize(width, height);
setVisible(true);
}
}
Circle(圆)
package shapes;
import java.awt.Graphics;
public class Circle extends Shape {
private int x;
private int y;
private int radius;
public Circle(int x, int y, int radius)
{
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw(Graphics g) {
g.drawOval(x-radius, y-radius, radius*2, radius*2);
}
}
Line(直线)
package shapes;
import java.awt.Graphics;
public class Line extends Shape {
private int x1;
private int y1;
private int x2;
private int y2;
public Line(int x1, int y1, int x2, int y2)
{
this.x1 = x1; this.y1 = y1;
this.x2 = x2; this.y2 = y2;
}
@Override
public void draw(Graphics g) {
g.drawLine(x1, y1, x2, y2);
}
}
Rectangle(矩形)
package shapes;
import java.awt.Graphics;
public class Rectangle extends Shape {
private int x;
private int y;
private int width;
private int height;
public Rectangle(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void draw(Graphics g) {
g.drawRect(x, y, width, height);
}
}
Triangle(三角形)
package shapes;
import java.awt.Graphics;
public class Triangle extends Shape {
private int[] x = new int[3];
private int[] y = new int[3];
public Triangle(int x1, int y1, int x2, int y2, int x3, int y3)
{
x[0] = x1; x[1] = x2; x[2] = x3;
y[0] = y1; y[1] = y2; y[2] = y3;
}
@Override
public void draw(Graphics g) {
g.drawPolygon(x, y, x.length);
}
}
Shape(抽象类)
package shapes;
import java.awt.Graphics;
public abstract class Shape {
public abstract void draw(Graphics g);
}
类和对象的关系
- 对象是实体,需要被创建
- 类是规范,根据类的定义来创建对象
封装
将数据和对数据的操作放在一起 --> 封装
对象 = 属性 + 服务
- 数据:属性或状态
- 操作:函数
定义类
类的设计和对象的创建
package vendingMachine;
public class VendingMachine {
int price = 80;
int balance;
int total;
void setPrice(int price){
this.price = price;
}
void showPrompt(){
System.out.println("Welcome");
}
void insertMoney(int amount){
balance = balance + amount;
}
void showBalance(){
System.out.println(balance);
}
void getFood(){
if (balance >= price){
System.out.println("Here you are");
balance = balance - price;
total = total + price;
}
}
public static void main(String[] args) {
VendingMachine vm = new VendingMachine();
vm.showPrompt();
vm.showBalance();
vm.insertMoney(100);
vm.getFood();
vm.showBalance();
}
}
- .是运算符
- vm.insertMoney(100);
- vm.getFood();
成员变量与成员函数
成员变量
- 类定义了对象中所具有的变量,这些变量称作成员变量
- 每个对象有自己的变量,和同一个类的其他对象是分开的
函数与成员变量
- 在函数中可以直接写成员变量的名字来访问成员变量
- 那么究竞访问的是哪个对象的呢?
- 函数是通过对象来调用的
- vm.insertMoney()
- 这次调用临时建立了insertMoney()和v,之间的关系让insertMoney()内部的成员变量指的是vm的成员变量
成员函数
- 成员函数的定义是在类中声明和实现的函数,用于执行特定的操作或提供特定的功能。
语法
访问修饰符 返回类型 函数名(参数列表) {
// 函数体
}
this关键字
this是成员函数的一个特殊的固有的本地变量,它表达了调用这个函数的那个对象
常用于(构造函数,区分成员变量和参数):
this.可以原来调用函数
本地变量
- 定义在函数内部的变量是本地变量
- 本地变量的生存期和作用域都是函数内部
- 成员变量的生存期是对象的生存期,作用域是类内部的成员函数
对象初始化
成员变量:去new 这个对象时会自动赋值
构造函数
构造函数(Constructor)是一种特殊的成员函数,用于创建和初始化对象。构造函数的名称与类名相同,并且没有返回类型。它在创建对象时自动调用,负责初始化对象的成员变量。
访问修饰符 类名(参数列表) {
// 构造函数体
}
重载
在Java中,方法重载(Method Overloading)指的是在一个类中有多个方法具有相同的名称但参数列表不同的情况。通过方法重载,我们可以在一个类中定义多个方法,它们执行类似但参数不同的操作。
方法重载的规则如下:
- 方法名称必须相同。
- 参数列表必须不同(参数数量或参数类型不同)。
- 方法的返回类型可以相同也可以不同。
- 方法的修饰符可以相同也可以不同。
- 方法的异常列表可以相同也可以不同。
- 方法的重载与方法的返回值类型无关,即不能通过返回值类型来区分重载。
举个例子:
public class Calculation {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
public class Main {
public static void main(String[] args) {
Calculation calc = new Calculation();
System.out.println(calc.add(1, 2)); // 输出 3
System.out.println(calc.add(1.5, 2.5)); // 输出 4.0
System.out.println(calc.add(1, 2, 3)); // 输出 6
}
}
在上面的例子中,Calculation类中有三个名为add的方法,它们的参数列表不同。通过传入不同的参数,我们可以调用不同的方法,实现不同的功能。这就是重载方法的作用。
类型的自动转化
显式类型转换
在Java中,显式类型转换(Explicit Type Casting)是指强制将一个数据类型转换为另一个数据类型。显式类型转换需要使用括号将需要转换的值括起来,并在括号前加上目标类型。
显式类型转换在以下情况下通常会用到:
- 当将一个较大的数值类型转换为较小的数值类型时,可能会发生溢出或精度损失的情况。通过显式类型转换,可以将数值截断到目标类型能够容纳的范围内。例如,将一个double类型的值转换为int类型。
double d = 3.14;
int i = (int) d; // 将3.14强制转换为整数类型,i的值为3
- 在进行混合操作时,需要将不同类型的值进行计算。例如,将一个整数和一个浮点数相加,结果的数据类型取决于操作数之间的类型。可以使用显式类型转换将数值数据类型统一进行计算。
int num1 = 5;
double num2 = 2.5;
double result = num1 + (int) num2; // 将2.5转换为整数类型,结果为7.0
隐式类型转换
- 兼容的数据类型间的赋值:如果变量的数据类型为兼容的数据类型,Java会自动将值赋给变量,无需进行显式类型转换。例如,将一个整数赋给一个较大范围的整数类型变量,或将一个子类对象赋给一个父类类型变量。
- 数值类型之间的转换:如果两个数值类型是兼容的,且目标类型的范围足够大,Java会自动进行转换。转换规则如下:
-
- byte → short → int → long → float → double
- char → int
- 任何整数类型 → 浮点数类型
- 字符串类型与数值类型的转换:Java提供了字符串到数值类型的自动转换,以及数值类型到字符串的自动转换。例如,可以使用Integer.parseInt()方法将一个字符串转换为整数类型(重要),或使用整数类型的toString()方法将一个整数转换为字符串类型。
- 布尔类型与数值类型的转换:Java允许将布尔类型(boolean)隐式地转换为数值类型(int),其中false转换为0,true转换为1;反之亦然的隐式转换是不允许的。
需要注意的是,当进行类型自动转换时,可能会发生精度损失或溢出的情况。特别是在将较大的数据类型赋给较小的数据类型时,可能需要使用显式类型转换(强制类型转换)来避免编译器警告或错误。可以使用括号将需要转换的数值包围,并在括号前加上目标类型。例如,(int) 3.14将3.14转换为整数类型。
示例代码如下:
double d = 3.14;
int i = (int) d; // 强制类型转换,将double类型转换为int类型,i的值为3
int j = 10;
double result = j / 3; // 整数相除,自动将结果转换为double类型,result的值为3.0
String numString = "42";
int num = Integer.parseInt(numString); // 将字符串转换为整数类型,num的值为42
int num2 = 100;
String numString2 = Integer.toString(num2); // 将整数转换为字符串类型,numString2的值为"100"
总之,在Java中,类型的自动转换是根据类型之间的兼容性进行的。这使得编程更加便捷,同时也需要注意数据类型之间的转换规则和可能产生的结果。如果遇到类型不兼容的情况,需要使用显式类型转换来进行转换。
第一周作业
package homework;
import java.util.Scanner;
public class FirstWeek {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Fraction a = new Fraction(in.nextInt(), in.nextInt());
Fraction b = new Fraction(in.nextInt(),in.nextInt());
a.print();
b.print();
a.plus(b).print();
a.multiply(b).plus(new Fraction(5,6)).print();
a.print();
b.print();
in.close();
}
}
class Fraction{
int a;
int b;
Fraction(int a, int b){
this.a = a;
this.b = b;
}
double toDouble(){
return (double) a/b;
}
Fraction plus(Fraction r){
//相加 1/2 + 2/3
int b = this.b * r.b;
int a = this.a * r.b + this.b * r.a;
Fraction temp = new Fraction(a, b);
return temp;
}
Fraction multiply(Fraction r){
int a = this.a * r.a;
int b = this.b * r.b;
Fraction temp = new Fraction(a, b);
return temp;
}
private int gcd(int a, int b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
private void simplify() {
int gcd = gcd(a, b);
a /= gcd;
b /= gcd;
}
public void print() {
simplify();
if (a == b) {
System.out.println("1");
} else if (a > b) {
System.out.println(a + "/" + b);
} else {
System.out.println(a + "/" + b);
}
}
}
对象交互
对象交互(设计一个时钟)
对象的识别与交互(Display类)
设计思路
代码实现
package clock;
public class Clock {
private Display hour = new Display(24);
private Display minute = new Display(60);
public void start() {
while (true) {
minute.increase();
if (minute.getValue() == 0) {
hour.increase();
}
System.out.printf("%02d:%02d\n", hour.getValue(), minute.getValue());
}
}
public static void main(String[] args) {
Clock clock = new Clock();
clock.start();
}
}
package clock;
public class Display {
private int value = 0;
private int limit = 0;
public Display(int limit) {
this.limit = limit;
}
public void increase(){
value++;
if (value == limit){
value = 0;
}
}
public int getValue(){
return value;
}
public static void main(String[] args) {
Display a = new Display(24);
while (true){
System.out.println(a.getValue());
a.increase();
}
}
}
访问属性
回顾封装
- public:公共访问级别,可以在任何位置被访问。被public修饰的类、方法、变量和构造函数可以从任何其他类中访问。
- private:私有访问级别,只能在声明它们的类内部访问。被private修饰的方法、变量和构造函数只能被同一个类内的其他方法访问,外部无法直接访问。
- protected:受保护访问级别,可以在同一包内的其他类和继承关系的子类中被访问。被protected修饰的方法、变量和构造函数可以在同一包内的其他类中访问,并且可以在继承关系的子类中访问。
- 默认(没有显式修饰符):在同一包内可以被访问,但在不同包的情况下不能被访问。被默认修饰的类、方法、变量和构造函数(没有任何访问修饰符)只能在同一包内的其他类中访问。
包(package)
包的导入(import)
类变量和类函数
类变量
static
在Java中,static是一个关键字,用于修饰类的成员变量(字段)和成员方法。当一个成员被声明为static时,它会与类本身关联,而不是与类的每个实例(对象)关联。
以下是一些static关键字的特点和用法:
- 静态变量(静态字段):被声明为static的成员变量是类级别的变量,它在内存中只有一份拷贝,所有实例(对象)共享该变量。静态变量可以通过类名直接访问,无需创建类的实例。(类变量与成员变量的区别)
例如:
public class MyClass {
static int count; // 静态变量
public void increment() {
count++; // 访问静态变量
}
}
- 静态方法:被声明为static的方法属于类,而不是实例。静态方法可以通过类名直接调用,无需创建类的实例。(类函数与成员函数的区别)
例如:
public class MyClass {
static void printMessage() {
System.out.println("Hello, World!"); // 静态方法
}
public static void main(String[] args) {
printMessage(); // 直接调用静态方法
}
}
- 静态代码块:静态代码块是一种特殊的代码块,用于在类加载时执行一次初始化操作。静态代码块在类的生命周期中只执行一次,用于初始化静态变量或执行其他一次性的操作。
例如
public class MyClass {
static {
System.out.println("Static block executed"); // 静态代码块
}
}
需要注意的是,静态成员可以直接使用静态成员,但不能直接使用非静态(实例级别)的成员。非静态成员需要通过类的实例来访问。
另外,静态成员属于类级别,会一直存在于内存中,因此需要慎重使用静态成员,避免造成内存浪费或不必要的共享状态。静态成员一般适用于需要在类的多个实例之间共享的变量或方法。
继承与多态
继承(extends)
实例(媒体资料库的设计)
想法:保存CD的资料
代码
package dome;
import java.util.ArrayList;
public class Database {
private ArrayList<CD> listCD = new ArrayList<CD>();
private ArrayList<DVD> listDVD = new ArrayList<DVD>();
public void add(CD cd){
listCD.add(cd);
}
public void add(DVD dvd){
listDVD.add(dvd);
}
public void list(){
for (CD cd : listCD){
cd.print();
}
for (DVD dvd : listDVD){
dvd.print();
}
}
public static void main(String[] args) {
Database db = new Database();
db.add(new CD("ab", "ab", 4, 60, "..."));
db.add(new CD("abc", "abc", 4, 65, "..."));
db.add(new DVD("abc", "abc", 4, "..."));
db.list();
}
}
package dome;
public class CD {
private String title;
private String artist;
private int numofTracks;
private int playTime;
private boolean gotit = false;
private String comment;
public CD(String title, String artist, int numofTracks, int playTime, String comment) {
this.title = title;
this.artist = artist;
this.numofTracks = numofTracks;
this.playTime = playTime;
this.comment = comment;
}
public void print(){
System.out.println(title + ":" + artist);
}
}
package dome;
public class DVD {
private String title;
private String director;
private int playTime;
private boolean gotit = false;
private String comment;
public DVD(String title, String director, int playTime, String comment) {
this.title = title;
this.director = director;
this.playTime = playTime;
this.comment = comment;
}
public void print(){
System.out.println(title + ":" + director);
}
}
问题
CD与DVD有着大量的代码复制的问题
继承的概念
问题思路
子类父类关系
修改代码(一般不使用protected)
package dome;
import java.util.ArrayList;
public class Database {
private ArrayList<Item> listItem = new ArrayList<Item>();
public void add(Item item){
listItem.add(item);
}
public void list(){
for (Item item : listItem){
item.print();
}
}
public static void main(String[] args) {
Database db = new Database();
db.add(new CD("ab", 4, false, "ab", "...", 60));
db.add(new CD("abc", 4, false, "abc", "...", 65));
db.add(new DVD("abc", 4, false, "abc", "..."));
db.list();
}
}
package dome;
public class Item {
private String title;
private int playTime;
private boolean gotit = false;
private String comment;
public Item(String title, int playTime, boolean gotit, String comment) {
this.title = title;
this.playTime = playTime;
this.gotit = gotit;
this.comment = comment;
}
public void print() {
System.out.println(title);
}
}
package dome;
public class DVD extends Item {
private String director;
public DVD(String title, int playTime, boolean gotit, String comment, String director) {
super(title, playTime, gotit, comment);
this.director = director;
}
public void print() {
System.out.println("DVD:");
super.print();
System.out.println(":" + director);
}
}
package dome;
public class CD extends Item {
private String artist;
private int numofTracks;
public CD(String title, int playTime, boolean gotit, String comment, String artist, int numofTracks) {
super(title, playTime, gotit, comment);
this.artist = artist;
this.numofTracks = numofTracks;
}
public void print() {
System.out.println("CD:");
super.print();
System.out.println(":" + artist);
}
}
多态变量和向上造型
多态变量
- java的对象变量是多态的,它们能保存不止一种类型的对象
- 它们可以保存的是声明类型的对象,或声明类型的子类的对象
- 当把子类的对象赋给父类的变量的时候就发生了向上造型
向上造型
Item item = new CD("ab", 4, false, "ab", "...", 60);
CD cd = new CD("ab", 4, false, "ab", "...", 60);
item = cd; // 不是类型转换 子类可以赋给父类 父类不能赋给子类
cd = (CD) item;//不加(CD)是错误的
父类对象赋给子类是不安全的
- 用括号围起来放在值的前面
- 对象本身并没有发生任何变化 所以不是“类型转化”(向上造型与类型转换是有区别的)
- 运行时有机制检查这样的转化是否合理 ClassCastException
多态
基础:绑定
- 当通过对象变量调用函数的时候,调用哪个函数这件事情叫做绑定
- 静态绑定:根据变量的声明类型来决定
- 动态绑定:根据变量的动态类型来决定
- 在成员函数中调用其他成员函数也是通过this这个对象变量来调用的
覆盖
- 子类和父类中存在名称和参数表完全相同的函数,这一对函数构成覆盖关系
- 通过父类的变量调用存在覆盖关系的函数时,会调用变当时所管理的对象所属的类和函数
print()将父类里面的print()给覆盖
类型系统
Object类
JAVA实现了一个单根结构
Object类的函数
- toString()
- equals()等
重写equals()
@Override
public boolean equals(Object o) {
if (this == o) return true; //指的是调用当前该方法的对象
if (!(o instanceof Item)) return false;
Item item = (Item) o;
return playTime == item.playTime && gotit == item.gotit && Objects.equals(title, item.title) && Objects.equals(comment, item.comment);
}
@Override的作用
如果没有@Override那么调用的equals()就是Object()的equals()而不是自己写的equals()
DoME的新媒体类型
获得一个新的类是相当简单的,只要新建一个类,继承自Item
package dome;
public class VideoGame extends Item {
private int numberOfPlayers;
public VideoGame(String title, int playTime, boolean gotit, String comment, int numberOfPlayers) {
super(title, playTime, gotit, comment);
this.numberOfPlayers = numberOfPlayers;
}
@Override
public void print() {
System.out.println("VideoGame:");
super.print();
}
}
设计原则
城堡游戏(castle)
Game
package castle;
import java.util.Scanner;
/**
* @author ASUS
*/
public class Game {
private Room currentRoom;
public Game() {
createRooms();
}
private void createRooms() {
Room outside, lobby, pub, study, bedroom;
// 制造房间
outside = new Room("城堡外");
lobby = new Room("大堂");
pub = new Room("小酒吧");
study = new Room("书房");
bedroom = new Room("卧室");
// 初始化房间的出口
outside.setExits(null, lobby, study, pub);
lobby.setExits(null, null, null, outside);
pub.setExits(null, outside, null, null);
study.setExits(outside, bedroom, null, null);
bedroom.setExits(null, null, null, study);
// 从城堡门外开始
currentRoom = outside;
}
private void printWelcome() {
System.out.println();
System.out.println("欢迎来到城堡!");
System.out.println("这是一个超级无聊的游戏。");
System.out.println("如果需要帮助,请输入 'help' 。");
System.out.println();
System.out.println("现在你在" + currentRoom);
System.out.print("出口有:");
if (currentRoom.northExit != null) {
System.out.print("north ");
}
if (currentRoom.eastExit != null) {
System.out.print("east ");
}
if (currentRoom.southExit != null) {
System.out.print("south ");
}
if (currentRoom.westExit != null) {
System.out.print("west ");
}
System.out.println();
}
// 以下为用户命令
private void printHelp() {
System.out.print("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如:\tgo east");
}
private void goRoom(String direction) {
Room nextRoom = null;
if ("north".equals(direction)) {
nextRoom = currentRoom.northExit;
}
if ("east".equals(direction)) {
nextRoom = currentRoom.eastExit;
}
if ("south".equals(direction)) {
nextRoom = currentRoom.southExit;
}
if ("west".equals(direction)) {
nextRoom = currentRoom.westExit;
}
if (nextRoom == null) {
System.out.println("那里没有门!");
} else {
currentRoom = nextRoom;
System.out.println("你在" + currentRoom);
System.out.print("出口有: ");
if (currentRoom.northExit != null) {
System.out.print("north ");
}
if (currentRoom.eastExit != null) {
System.out.print("east ");
}
if (currentRoom.southExit != null) {
System.out.print("south ");
}
if (currentRoom.westExit != null) {
System.out.print("west ");
}
System.out.println();
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Game game = new Game();
game.printWelcome();
while (true) {
String line = in.nextLine();
String[] words = line.split(" ");
if ("help".equals(words[0])) {
game.printHelp();
} else if ("go".equals(words[0])) {
game.goRoom(words[1]);
} else if ("bye".equals(words[0])) {
break;
}
}
System.out.println("感谢您的光临。再见!");
in.close();
}
}
Room
package castle;
public class Room {
//房间名称
public String description;
//房间四个方向连接的房间
public Room northExit;
public Room southExit;
public Room eastExit;
public Room westExit;
public Room(String description)//初始化房间名
{
this.description = description;
}
public void setExits(Room north, Room east, Room south, Room west)//设置房间的四个方向的连接
{
if (north != null)
northExit = north;
if (east != null)
eastExit = east;
if (south != null)
southExit = south;
if (west != null)
westExit = west;
}
@Override
public String toString() {
return description;//输出房间名
}
}
封装
聚合和耦合
封装
利用封装降低耦合
- Room类和Game类都有大量的代码和出口相关
- 尤其是Game类中大量使用了Room类的成员变量
- 类和类之间的关系称作耦合
- 耦合越低越好,保持距离是形成良好代码的关键
首先江Room里面的成员变量设置为私有的,这是封装的第一步
接下来定义方法 getExit() 和 getExitDesc() 来代替Game里面的方法,减少了Game与Room之间的关系
package castle;
/**
* @author ASUS
*/
public class Room {
private String description;
private Room northExit;
private Room southExit;
private Room eastExit;
private Room westExit;
public Room(String description) {
this.description = description;
}
public void setExits(Room north, Room east, Room south, Room west) {
if (north != null) {
northExit = north;
}
if (east != null) {
eastExit = east;
}
if (south != null) {
southExit = south;
}
if (west != null) {
westExit = west;
}
}
@Override
public String toString() {
return description;
}
public String getExitDesc() {
StringBuffer sb = new StringBuffer();
if (northExit != null) {
sb.append("north");
}
if (eastExit != null) {
sb.append("east");
}
if (westExit != null) {
sb.append("west");
}
if (southExit != null) {
sb.append("south");
}
return sb.toString();
}
public Room getExit(String direction) {
Room ret = null;
if ("north".equals(direction)) {
ret = northExit;
}
if ("east".equals(direction)) {
ret = eastExit;
}
if ("south".equals(direction)) {
ret = southExit;
}
if ("west".equals(direction)) {
ret = westExit;
}
return ret;
}
}
package castle;
import java.util.Scanner;
/**
* @author ASUS
*/
public class Game {
private Room currentRoom;
public Game() {
createRooms();
}
private void createRooms() {
Room outside, lobby, pub, study, bedroom;
// 制造房间
outside = new Room("城堡外");
lobby = new Room("大堂");
pub = new Room("小酒吧");
study = new Room("书房");
bedroom = new Room("卧室");
// 初始化房间的出口
outside.setExits(null, lobby, study, pub);
lobby.setExits(null, null, null, outside);
pub.setExits(null, outside, null, null);
study.setExits(outside, bedroom, null, null);
bedroom.setExits(null, null, null, study);
// 从城堡门外开始
currentRoom = outside;
}
private void printWelcome() {
System.out.println();
System.out.println("欢迎来到城堡!");
System.out.println("这是一个超级无聊的游戏。");
System.out.println("如果需要帮助,请输入 'help' 。");
System.out.println();
System.out.println("现在你在" + currentRoom);
System.out.print("出口有:");
System.out.println(currentRoom.getExitDesc());
System.out.println();
}
// 以下为用户命令
private void printHelp() {
System.out.print("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如:\tgo east");
}
private void goRoom(String direction) {
Room nextRoom = currentRoom.getExit(direction);
if (nextRoom == null) {
System.out.println("那里没有门!");
} else {
currentRoom = nextRoom;
showPrompt();
}
}
public void showPrompt() {
System.out.println("你在" + currentRoom);
System.out.print("出口有:");
System.out.print(currentRoom.getExitDesc());
System.out.println();
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Game game = new Game();
game.printWelcome();
while (true) {
String line = in.nextLine();
String[] words = line.split(" ");
if ("help".equals(words[0])) {
game.printHelp();
} else if ("go".equals(words[0])) {
game.goRoom(words[1]);
} else if ("bye".equals(words[0])) {
break;
}
}
System.out.println("感谢您的光临。再见!");
in.close();
}
}
可扩展性
扩展
可扩展性
利用接口实现聚合
- 给Room类的新方法,把方向的细节隐藏彻底隐藏在Room类内部
- 今后的方向就和外部无关了
利用容器来实现灵活性
- Room的方向是用成员变量来表示的,增加或减少就要改变代码
- 如果用Hash表示方向,那么方向就不是硬编码的了
设计一个HashMap容器,用来保存房间的方向,通过该改变使得代码具有了可扩展性
在未来如果我想要添加新的方向的话,那么就简单了许多
package castle;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
* @author ASUS
*/
public class Room {
private final String description;
private Map<String, Room> exits = new HashMap<>();
public Room(String description) {
this.description = description;
}
public void setExit(String dir, Room room){
exits.put(dir, room);
}
@Override
public String toString() {
return description;
}
public String getExitDesc() {
StringBuffer sb = new StringBuffer();
for (String dir : exits.keySet()) {
sb.append(dir).append(" ");
}
return sb.toString();
}
public Room getExit(String direction) {
Room ret = exits.get(direction);
return ret;
}
}
package castle;
import java.util.Scanner;
/**
* @author ASUS
*/
public class Game {
private Room currentRoom;
public Game() {
createRooms();
}
private void createRooms() {
Room outside, lobby, pub, study, bedroom;
// 制造房间
outside = new Room("城堡外");
lobby = new Room("大堂");
pub = new Room("小酒吧");
study = new Room("书房");
bedroom = new Room("卧室");
// 初始化房间的出口
outside.setExit("east", lobby);
outside.setExit("south", study);
outside.setExit("west", pub);
lobby.setExit("west", outside);
pub.setExit("east", outside);
study.setExit("north", outside);
study.setExit("east", bedroom);
bedroom.setExit("west", study);
lobby.setExit("up", pub);
pub.setExit("down", lobby);
// 从城堡门外开始
currentRoom = outside;
}
private void printWelcome() {
System.out.println();
System.out.println("欢迎来到城堡!");
System.out.println("这是一个超级无聊的游戏。");
System.out.println("如果需要帮助,请输入 'help' 。");
System.out.println();
System.out.println("现在你在" + currentRoom);
System.out.print("出口有:");
System.out.println(currentRoom.getExitDesc());
System.out.println();
}
// 以下为用户命令
private void printHelp() {
System.out.print("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如:\tgo east");
}
private void goRoom(String direction) {
Room nextRoom = currentRoom.getExit(direction);
if (nextRoom == null) {
System.out.println("那里没有门!");
} else {
currentRoom = nextRoom;
showPrompt();
}
}
public void showPrompt() {
System.out.println("你在" + currentRoom);
System.out.print("出口有:");
System.out.print(currentRoom.getExitDesc());
System.out.println();
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Game game = new Game();
game.printWelcome();
while (true) {
String line = in.nextLine();
String[] words = line.split(" ");
if ("help".equals(words[0])) {
game.printHelp();
} else if ("go".equals(words[0])) {
game.goRoom(words[1]);
} else if ("bye".equals(words[0])) {
break;
}
}
System.out.println("感谢您的光临。再见!");
in.close();
}
}
框架加数据
框架和数据
从程序中识别出框架和数据,以代码实现框架,将部分功能以数据的方式加载,这样能在很大程度上实现可扩展性
框架加数据
以框架+数据来提高可扩展性
- 命令的解析是否可以脱离 if - else
- 定义一个HashMap来处理命令
- 用Hash来表示保存命令与Handler之间的关系
现在有 help,go,bye三个命令,将命令也进行框架化
创造 HandlerHelp,HandlerGo,HandlerBye 三个类(作为Handler的子类),进行框架化
package castle;
import java.util.HashMap;
import java.util.Scanner;
/**
* @author ASUS
*/
public class Game {
private Room currentRoom;
private HashMap<String, Handler> handlers = new HashMap<>();
public Game() {
handlers.put("go", new HandlerGo(this));
handlers.put("bye", new HandlerBye(this));
handlers.put("help", new HandlerHelp(this));
createRooms();
}
private void createRooms() {
Room outside, lobby, pub, study, bedroom;
// 制造房间
outside = new Room("城堡外");
lobby = new Room("大堂");
pub = new Room("小酒吧");
study = new Room("书房");
bedroom = new Room("卧室");
// 初始化房间的出口
outside.setExit("east", lobby);
outside.setExit("south", study);
outside.setExit("west", pub);
lobby.setExit("west", outside);
pub.setExit("east", outside);
study.setExit("north", outside);
study.setExit("east", bedroom);
bedroom.setExit("west", study);
lobby.setExit("up", pub);
pub.setExit("down", lobby);
// 从城堡门外开始
currentRoom = outside;
}
private void printWelcome() {
System.out.println();
System.out.println("欢迎来到城堡!");
System.out.println("这是一个超级无聊的游戏。");
System.out.println("如果需要帮助,请输入 'help' 。");
System.out.println();
System.out.println("现在你在" + currentRoom);
System.out.print("出口有:");
System.out.println(currentRoom.getExitDesc());
System.out.println();
}
// 以下为用户命令
public void goRoom(String direction) {
Room nextRoom = currentRoom.getExit(direction);
if (nextRoom == null) {
System.out.println("那里没有门!");
} else {
currentRoom = nextRoom;
showPrompt();
}
}
public void showPrompt() {
System.out.println("你在" + currentRoom);
System.out.print("出口有:");
System.out.print(currentRoom.getExitDesc());
System.out.println();
}
public void play(){
Scanner in = new Scanner(System.in);
while (true) {
String line = in.nextLine();
String[] words = line.split(" ");
Handler handler = handlers.get(words[0]);
String value = "";
if (words.length > 1) {
value = words[1];
}
if (handler != null) {
handler.doCmd(line);
if (handler.isBye()) {
break;
}
}
}
}
public static void main(String[] args) {
Game game = new Game();
game.printWelcome();
System.out.println("感谢您的光临。再见!");
}
}
package castle;
import java.util.HashMap;
import java.util.Map;
/**
* @author ASUS
*/
public class Room {
private final String description;
private Map<String, Room> exits = new HashMap<>();
public Room(String description) {
this.description = description;
}
public void setExit(String dir, Room room) {
exits.put(dir, room);
}
@Override
public String toString() {
return description;
}
public String getExitDesc() {
StringBuffer sb = new StringBuffer();
for (String dir : exits.keySet()) {
sb.append(dir).append(" ");
}
return sb.toString();
}
public Room getExit(String direction) {
Room ret = exits.get(direction);
return ret;
}
}
package castle;
/**
* @author ASUS
*/
public class Handler {
protected Game game;
public Handler(Game game) {
this.game = game;
}
public void doCmd(String word) {
}
public boolean isBye() { return false; }
}
package castle;
/**
* @author ASUS
*/
public class HandlerGo extends Handler {
public HandlerGo(Game game) {
super(game);
}
@Override
public void doCmd(String word) {
game.goRoom(word);
}
}
package castle;
/**
* @author ASUS
*/
public class HandlerBye extends Handler {
public HandlerBye(Game game) {
super(game);
}
@Override
public boolean isBye() {
return true;
}
}
package castle;
/**
* @author ASUS
*/
public class HandlerHelp extends Handler {
public HandlerHelp(Game game) {
super(game);
}
@Override
public void doCmd(String word) {
System.out.print("迷路了吗?你可以做的命令有:go bye help");
System.out.println("如:\tgo east");
}
}
抽象与接口
抽象(abstract)
作用
在Java编程语言中,abstract关键字主要用于定义抽象类和抽象方法,其作用如下:
修饰类:使用abstract关键字修饰的类被称为抽象类(Abstract Class)。抽象类不能直接实例化,也就是说你不能创建一个抽象类的对象。抽象类通常包含抽象方法,但也可以包含非抽象(具体实现)的方法。抽象类的主要目的是为了被其他类继承,并且作为这些子类的一个通用模板。
修饰方法:一个方法如果使用了abstract关键字修饰,则称该方法为抽象方法。抽象方法只有声明而没有实现(即方法体为空,以分号结束),它的具体实现由继承抽象类的子类来完成。子类在继承抽象类时必须提供对所有抽象方法的具体实现,除非子类自身也是一个抽象类。
抽象类和抽象方法的主要作用是:
- 提高代码的可扩展性和灵活性:通过定义抽象类及其抽象方法,可以规范子类必须遵循的基本结构和接口,而不需要指定具体的实现细节。
- 面向对象设计原则:实现接口隔离、依赖倒置等原则,降低耦合度,使得代码更加模块化。
- 模板设计模式:抽象类可以用作模板,规定一些基本操作,而将变化的部分推迟到子类中去实现,从而实现了特定行为的复用和定制化。
Shape是什么形状
抽象函数/抽象类
- 抽象函数 -- 表达概念而无法实现具体代码的函数
- 抽象类 -- 表达概念而无法构造出实体的类
- 带有abstract修饰符的函数
- 有抽象函数的类一定是抽象类
- 抽象类不能制造对象
- 但是可以定义变量
- 任何继承了抽象类的非抽象类的对象可以赋给这个变量
实现抽象函数
- 继承自抽象类的子类必须覆盖父类中的抽象函数
- 否则自己成为抽象类
两种抽象
- 与具体相对
- 表示一种概念而非实体
- 与细节相对
- 表示在一定程度上忽略细节而着眼大局
数据与表现分离:细胞自动机
细胞自动机代码
包与包,包与类,类与类之间的关系
类Field
package cell_separator.field;
import java.util.ArrayList;
import cell_separator.cell.Cell;
public class Field {
private int width;
private int height;
private Cell[][] field;//一个二维数组field,存放每个格子(细胞)
//构造器,初始化棋盘
public Field(int width, int height) {
this.width = width;
this.height = height;
field = new Cell[height][width];
}
public int getWidth() { return width; }
public int getHeight() { return height; }
//添加细胞
public Cell place(int row, int col, Cell o) {
Cell ret = field[row][col];
field[row][col] = o;
return ret;
}
//获取某个格子
public Cell get(int row, int col) {
return field[row][col];
}
//获取周围细胞
public Cell[] getNeighbour(int row, int col) {
ArrayList<Cell> list = new ArrayList<Cell>();
for ( int i=-1; i<2; i++ ) {
for ( int j=-1; j<2; j++ ) {
int r = row+i;
int c = col+j;
if ( r >-1 && r<height && c>-1 && c<width && !(r== row && c == col) ) {
list.add(field[r][c]);
}
}
}
//toArray会自动把一个Cell[]数组装好,让返回这个数组
return list.toArray(new Cell[list.size()]);
}
//清空所有格子
public void clear() {
for ( int i=0; i<height; i++ ) {
for ( int j=0; j<width; j++ ) {
field[i][j] = null;
}
}
}
}
类Cell
package cell_separator.cell;
import java.awt.Graphics;
public class Cell {
private boolean alive = false;//默认为死亡状态
public void die() { alive = false; }//让细胞死亡
public void reborn() { alive = true; }//让细胞重生
public boolean isAlive() { return alive; }//返回细胞状态,判断细胞是否存活
//绘制细胞
public void draw(Graphics g, int x, int y, int size) {
g.drawRect(x, y, size, size);
//画个空心矩形
if ( alive ) {//如果细胞存活
g.fillRect(x, y, size, size);//画个实心矩形
}
}
}
类View
package cell_separator.view;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import cell_separator.cell.Cell;
import cell_separator.field.Field;
public class View extends JPanel {
private static final long serialVersionUID = -5258995676212660595L;
private static final int GRID_SIZE = 16;
private Field theField;
//构造器
public View(Field field) {
theField = field;
}
//每次当窗口被重绘时调用,被调用时会得到一个Graphics对象,即当前要画的对象
@Override
public void paint(Graphics g) {
super.paint(g);//调用父类的paint方法
//绘制网格
for ( int row = 0; row < theField.getHeight(); row++ ) {//行
for ( int col = 0; col<theField.getWidth(); col++ ) {//列
Cell cell = theField.get(row, col);//得到每个格子的对象
if ( cell != null ) {//如果该格子有对象
cell.draw(g, col*GRID_SIZE, row*GRID_SIZE, GRID_SIZE);//绘制该格子
}
}
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(theField.getWidth()*GRID_SIZE+1, theField.getHeight()*GRID_SIZE+1);
}
public static void main(String[] args) {
Field field = new Field(10,10);
for ( int row = 0; row<field.getHeight(); row++ ) {
for ( int col = 0; col<field.getWidth(); col++ ) {
field.place(row, col, new Cell());
}
}
View view = new View(field);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setTitle("Cells");
frame.add(view);
frame.pack();
frame.setVisible(true);
}
}
类CellMachine
package cell_separator.cellmachine;
import javax.swing.JFrame;
import cell_separator.cell.Cell;
import cell_separator.field.Field;
import cell_separator.view.View;
public class CellMachine {
public static void main(String[] args) {
//数据准备阶段,画出一个细胞网格
Field field = new Field(30,30);//一个30*30的网格
//遍历网格,每个网格都有一个细胞
for ( int row = 0; row<field.getHeight(); row++ ) {//遍历每一行
for ( int col = 0; col<field.getWidth(); col++ ) {//遍历每一列
field.place(row, col, new Cell());//每个细胞都有一个状态,初始状态为死亡
}
}
//遍历每个细胞,设置初始细胞的生存状态
for ( int row = 0; row<field.getHeight(); row++ ) {//遍历每一行
for ( int col = 0; col<field.getWidth(); col++ ) {//遍历每一列
Cell cell = field.get(row, col);//获取(row,col)位置上的细胞
//利用随机数让整个网格的五分之一的细胞生存
if ( Math.random() < 0.2 ) { //Math.random()返回0~1之间的随机数
cell.reborn();//reborn()方法设置细胞的生存状态为true
}
}
}
//在窗口中显示细胞网格
View view = new View(field);//将网格传入View类中
JFrame frame = new JFrame();//创建一个JFrame对象,是java当中的窗口,用来显示细胞网格
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置窗口关闭时的操作
frame.setResizable(false);//设置窗口不可改变大小
frame.setTitle("Cells");//设置窗口的标题
frame.add(view);//将网格添加到窗口中
frame.pack();//设置窗口的大小
frame.setVisible(true);//设置窗口可见,即显示出来
//模拟细胞网格的运行
//遍历整个网格,取出每个细胞
while (true){
for ( int row = 0; row<field.getHeight(); row++ ) {//遍历每一行
for ( int col = 0; col<field.getWidth(); col++ ) {//遍历每一列
Cell cell = field.get(row, col);//获取(row,col)位置上的细胞
Cell[] neighbour = field.getNeighbour(row, col);//获取(row,col)位置上的细胞的邻居
int numOfLive = 0;//计数器,记录邻居中活细胞的个数
//遍历邻居,计算邻居中有多少个细胞是活的
for ( Cell c : neighbour ) {
if ( c.isAlive() ) {//如果邻居中有一个细胞是活的
numOfLive++;//让计数器加一
}
}
//控制台输出当前细胞的状态
System.out.print("["+row+"]["+col+"]:");//打印细胞的位置
System.out.print(cell.isAlive()?"live":"dead");//打印细胞的状态
System.out.print(":"+numOfLive+"-->");//打印细胞的邻居中有多少个细胞是活的
//写入下一次的细胞状态
if ( cell.isAlive() ) {//如果当前细胞是活的
if ( numOfLive <2 || numOfLive >3 ) {//判断是否满足让细胞死的条件条件
cell.die();//让细胞死
System.out.print("die");
}
} else if ( numOfLive == 3 ) {//如果当前细胞是死的,判断是否满足让细胞活的条件条件
cell.reborn();//让细胞活
System.out.print("reborn");
}
System.out.println();
}
}
System.out.println("UPDATE");
frame.repaint();//整个Field都更新好后,画出棋盘
//延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
细胞自动机
程序规则
黑色代表活着,白色代表死亡
- 死亡:如果活着的邻居的数量 <2 或 >3 ,则死亡
- 新生:如果正好有三个邻居活着,则新生
- 其他情况保持原状
程序运行图
阅读代码的方法
- 找到main,然后一点点看
- 找到最小的类,然后一步步找父类,然后将细节拼接起来
数据与表现分离
数据与表现分离
- 程序的业务逻辑与表现无关
- 表现可以是图形的也可以是文本的
- 表现可以是当地的也可以是远程的
所有的数据全部更新完了,再进行操作,重新画一个
View与Field的关系
- 表现与数据的关系,View只管根据Field画出图形,Field只管数据的存放
- 一旦数据更新异或,通知View重新画出整个画面
责任驱动的设计
将程序要实现的功能分配到合适的类/对象中去是设计中非常重要的一环
网格化
- 图形界面本身具有更高的解析度
- 但是将画面网格化以后,数据就更容易处理了
狐狸与兔子
狐狸与兔子
规则
结构图
思维导图
Animal
package foxandrabbit.animal;
import java.util.ArrayList;
import foxandrabbit.field.Location;
public abstract class Animal {
private int ageLimit;
private int breakableAge;
private int age;
private boolean isAlive = true;
public Animal(int ageLimit, int breakableAge) {
this.ageLimit = ageLimit;
this.breakableAge = breakableAge;
}
protected int getAge() {
return age;
}
protected double getAgePercent() {
return (double) age / ageLimit;
}
public abstract Animal breed();
public void grow() {
age++;
if (age >= ageLimit) {
die();
}
}
public void die() {
isAlive = false;
}
public boolean isAlive() {
return isAlive;
}
public boolean isBreakable() {
return age >= breakableAge;
}
public Location move(Location[] freeAdj) {
Location ret = null; // 创建一个名为 ret 的 Location 变量并初始化为 null
// 检查 freeAdj 数组的长度是否大于 0,并且以 0.02 的概率进行随机选择
if (freeAdj.length > 0 && Math.random() < 0.02) {
// 从 freeAdj 数组中随机选择一个位置并将其赋值给 ret
ret = freeAdj[(int) (Math.random() * freeAdj.length)];
}
return ret; // 返回 ret 变量,即随机选择的位置或 null(如果条件不满足)
}
@Override
public String toString() {
return "" + age + ":" + (isAlive ? "live" : "dead");
}
public Animal feed(ArrayList<Animal> neighbour) {
return null;
}
protected void longerLife(int inc) {
ageLimit += inc;
}
}
Rabbit
package foxandrabbit.animal;
import java.awt.Color;
import java.awt.Graphics;
import foxandrabbit.cell.Cell;
public class Rabbit extends Animal implements Cell {
public Rabbit() {
super(10, 2);
}
@Override
/**
* 绘制一个透明度随年龄百分比变化而变化的方块
* @param g 用于绘制方块的 Graphics 对象
* @param x 方块的左上角 x 坐标
* @param y 方块的左上角 y 坐标
* @param size 方块的大小
*/
public void draw(Graphics g, int x, int y, int size) {
// 计算方块的透明度(0~255)
int alpha = (int) ((1 - getAgePercent()) * 255); // 透明度随年龄百分比变化而变化
// 设置方块的颜色
g.setColor(new Color(255, 0, 0, alpha));// (int)((20-getAge())/20.0*255)));
// 在给定位置绘制方块
g.fillRect(x, y, size, size); // 填充颜色的方块
}
@Override
/**
* 繁殖动物
* @return 返回一个新的动物实例
*/
public Animal breed() {
Animal ret = null;
// 判断是否到达生育年龄,并且随机数小于0.12,则进行繁殖
if (isBreakable() && Math.random() < 0.12) {
ret = new Rabbit(); // 创建一个兔子实例
}
return ret; // 返回繁殖后的动物实例,如果没有繁殖则返回null
}
@Override
public String toString() {
return "Rabbit:" + super.toString();
}
}
Fox
package foxandrabbit.animal;
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import foxandrabbit.cell.Cell;
public class Fox extends Animal implements Cell {
public Fox() {
super(20, 4);
}
@Override
/**
* 绘制一个透明度随年龄百分比变化而变化的方块
* @param g 用于绘制方块的 Graphics 对象
* @param x 方块的左上角 x 坐标
* @param y 方块的左上角 y 坐标
* @param size 方块的大小
*/
public void draw(Graphics g, int x, int y, int size) {
// 计算方块的透明度(0~255)
int alpha = (int) ((1 - getAgePercent()) * 255); // 透明度随年龄百分比变化而变化
// 设置方块的颜色
g.setColor(new Color(0, 0, 0, alpha));// (int)((20-getAge())/20.0*255)));
// 在给定位置绘制方块
g.fillRect(x, y, size, size); // 填充颜色的方块
}
@Override
/**
* 繁殖动物
* @return 返回一个新的动物实例
*/
public Animal breed() {
Animal ret = null;
// 判断是否到达生育年龄,并且随机数小于0.05,则进行繁殖
if (isBreakable() && Math.random() < 0.05) {
ret = new Fox(); // 创建一个狐狸实例
}
return ret; // 返回繁殖后的动物实例,如果没有繁殖则返回null
}
@Override
public String toString() {
return "Fox:" + super.toString();
}
@Override
/**
* 喂养动物
* @param neighbour 邻居动物的列表
* @return 返回被喂养的动物实例
*/
public Animal feed(ArrayList<Animal> neighbour) {
Animal ret = null;
// 如果随机数小于0.2,则进行喂养
if (Math.random() < 0.2) {
// 随机选择一个邻居动物
ret = neighbour.get((int) (Math.random() * neighbour.size()));
// 增加寿命2个单位
longerLife(2);
}
return ret; // 返回被喂养的动物实例,如果没有被喂养则返回null
}
}
Cell(格子)
package foxandrabbit.cell;
import java.awt.Graphics;
public interface Cell {
void draw(Graphics g, int x, int y, int size);
}
View
package foxandrabbit.field;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
import foxandrabbit.cell.Cell;
public class View extends JPanel {
private static final long serialVersionUID = -2417015700213488315L;
private static final int GRID_SIZE = 16;
private Field theField;
public View(Field field) {
theField = field;
}
@Override
public void paint(Graphics g) {
super.paint(g); // 调用父类的绘制方法
// 设置画笔颜色为灰色
g.setColor(Color.GRAY);
// 绘制水平网格线
for (int row = 0; row < theField.getHeight(); row++) {
g.drawLine(0, row * GRID_SIZE, theField.getWidth() * GRID_SIZE, row * GRID_SIZE);
}
// 绘制垂直网格线
for (int col = 0; col < theField.getWidth(); col++) {
g.drawLine(col * GRID_SIZE, 0, col * GRID_SIZE, theField.getHeight() * GRID_SIZE);
}
// 遍历所有的单元格,绘制每个格子
for (int row = 0; row < theField.getHeight(); row++) {
for (int col = 0; col < theField.getWidth(); col++) {
Cell cell = theField.get(row, col);
// 如果单元格不为空,则绘制棋子
if (cell != null) {
// 调用单元格的绘制方法,传入当前列和行的偏移量,以及格子大小
cell.draw(g, col * GRID_SIZE, row * GRID_SIZE, GRID_SIZE);
}
}
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(theField.getWidth() * GRID_SIZE + 1, theField.getHeight() * GRID_SIZE + 1);
}
}
Field
package foxandrabbit.field;
import java.util.ArrayList;
import foxandrabbit.cell.Cell;
public class Field {
private static final Location[] adjacent = {
new Location(-1, -1), new Location(-1, 0), new Location(-1, 1),
new Location(0, -1), new Location(0, 0), new Location(0, 1),
new Location(1, -1), new Location(1, 0), new Location(1, 1)
};
private int width; // 场地的宽度
private int height; // 场地的高度
private Cell[][] field; // 场地上的格子
public Field(int width, int height) {
this.width = width;
this.height = height;
field = new Cell[height][width];
}
// 获取场地宽度
public int getWidth() {
return width;
}
// 获取场地高度
public int getHeight() {
return height;
}
// 在指定的位置放置动物,并且返回这个动物
public Cell place(int row, int col, Cell o) {
Cell ret = field[row][col];
field[row][col] = o;
return ret;
}
// 根据行列坐标获取对应的格子
public Cell get(int row, int col) {
return field[row][col];
}
// 获取指定位置周围的格子数组
public Cell[] getNeighbour(int row, int col) {
ArrayList<Cell> list = new ArrayList<Cell>();
for (int i = -1; i < 2; i++) {
for (int j = -1; j < 2; j++) {
int r = row + i;
int c = col + j;
if (r > -1 && r < height && c > -1 && c < width && !(r == row && c == col)) {
list.add(field[r][c]);
}
}
}
return list.toArray(new Cell[list.size()]);
}
// 获取指定位置周围的空闲位置数组
public Location[] getFreeNeighbour(int row, int col) {
ArrayList<Location> list = new ArrayList<Location>();
for (Location loc : adjacent) {
int r = row + loc.getRow();
int c = col + loc.getCol();
if (r > -1 && r < height && c > -1 && c < width && field[r][c] == null) {
list.add(new Location(r, c));
}
}
return list.toArray(new Location[list.size()]);
}
// 在指定位置的周围随机放置一个格子,并返回是否成功放置
public boolean placeRandomAdj(int row, int col, Cell cell) {
boolean ret = false;
Location[] freeAdj = getFreeNeighbour(row, col);
if (freeAdj.length > 0) {
int idx = (int) (Math.random() * freeAdj.length);
field[freeAdj[idx].getRow()][freeAdj[idx].getCol()] = cell;
ret = true;
}
return ret;
}
// 移除指定位置的格子,并返回被移除的格子
public Cell remove(int row, int col) {
Cell ret = field[row][col];
field[row][col] = null;
return ret;
}
// 移除指定格子
public void remove(Cell cell) {
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (field[row][col] == cell) {
field[row][col] = null;
break;
}
}
}
}
// 清空整个场地,即将所有格子设置为null
public void clear() {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
field[i][j] = null;
}
}
}
//将指定位置的格子移动到目标位置
public void move(int row, int col, Location loc) {
field[loc.getRow()][loc.getCol()] = field[row][col];
remove(row, col);
}
}
FoxAnadRabbit
package foxandrabbit;
import java.util.ArrayList;
import javax.swing.JFrame;
import foxandrabbit.animal.Animal;
import foxandrabbit.animal.Fox;
import foxandrabbit.animal.Rabbit;
import foxandrabbit.cell.Cell;
import foxandrabbit.field.Field;
import foxandrabbit.field.Location;
import foxandrabbit.field.View;
public class FoxAndRabbit {
private Field theField;
private View theView;
public FoxAndRabbit(int size) {
theField = new Field(size, size); // 创建大小为 size*size 的 Field 对象
for (int row = 0; row < theField.getHeight(); row++) {
for (int col = 0; col < theField.getWidth(); col++) {
double probability = Math.random();
if (probability < 0.05) { // 如果概率小于 0.05,则在该位置放置一只 Fox 对象
theField.place(row, col, new Fox());
} else if (probability < 0.15) { // 如果概率小于 0.15,则在该位置放置一只 Rabbit 对象
theField.place(row, col, new Rabbit());
}
}
}
theView = new View(theField); // 创建一个新的 View 对象,并传入 Field 对象作为参数
JFrame frame = new JFrame(); // 创建一个新的 JFrame 对象
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置 JFrame 窗口的关闭操作为退出应用程序
frame.setResizable(false); // 禁止调整 JFrame 窗口的大小
frame.setTitle("Cells"); // 设置 JFrame 窗口的标题为 "Cells"
frame.add(theView); // 将 View 对象添加到 JFrame 窗口中
frame.pack(); // 计算并设置 JFrame 窗口的最合适大小
frame.setVisible(true); // 将 JFrame 窗口设置为可见状态,显示在屏幕上
}
public void start(int steps) {
for (int i = 0; i < steps; i++) {
step(); // 进行一步模拟
theView.repaint(); // 重绘窗口,更新显示
try {
Thread.sleep(200); // 线程休眠 200 毫秒,用于控制模拟速度
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void step() {
for (int row = 0; row < theField.getHeight(); row++) {
for (int col = 0; col < theField.getWidth(); col++) {
Cell cell = theField.get(row, col);
if (cell != null) {
Animal animal = (Animal) cell;
animal.grow(); // 动物成长
if (animal.isAlive()) { // 如果动物存活
Location loc = animal.move(theField.getFreeNeighbour(row, col)); // 获取动物移动到的位置
if (loc != null) {
theField.move(row, col, loc); // 将动物移动到新位置
}
// 吃
Cell[] neighbour = theField.getNeighbour(row, col);
ArrayList<Animal> listRabbit = new ArrayList<Animal>();
for (Cell an : neighbour) { // 遍历周围的细胞
if (an instanceof Rabbit) { // 如果细胞是 Rabbit 类型
listRabbit.add((Rabbit) an); // 将细胞添加到待吃列表中
}
}
if (!listRabbit.isEmpty()) { // 如果待吃列表不为空
Animal fed = animal.feed(listRabbit); // 动物进行吃的行为
if (fed != null) {
theField.remove((Cell) fed); // 从地图上移除被吃的细胞
}
}
// 繁殖
Animal baby = animal.breed(); // 动物进行繁殖
if (baby != null) {
theField.placeRandomAdj(row, col, (Cell) baby); // 在周围的随机位置放置新生动物
}
} else { // 如果动物死亡
theField.remove(row, col); // 从地图上移除动物
}
}
}
}
}
public static void main(String[] args) {
// 创建 FoxAndRabbit 对象,设置地图大小为 50x50
FoxAndRabbit fab = new FoxAndRabbit(50);
// 开始模拟,模拟 500 步
fab.start(500);
}
}
Location
package foxandrabbit.field;
public class Location {
private int row;
private int col;
public Location(int row, int col) {
this.row = row;
this.col = col;
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
}
接口(implements)(表达概念)
- 接口是纯抽象类
- 所有的成员函数都是抽象函数
- 所有的成员变量都是public static final
- 接口规定了长什么样,但是不管里面有什么
接口设计模式
interface(创造接口)
Java接口是一个完全抽象的类型,它定义了一组方法签名(包括常量和默认方法),但不提供方法的具体实现。
接口主要用于规范和强制一组行为。任何类如果要遵循这个规范,就需要实现该接口。
在接口中声明的所有方法默认都是公开且抽象的(从Java 8开始,接口可以包含静态方法和默认方法,它们具有具体实现)。
implements(实现接口)
implements 关键字用来声明一个类实现了指定的接口。
当一个非抽象类使用 implements 关键字来实现一个或多个接口时,它必须为接口中声明的所有抽象方法提供具体的实现。
通过实现接口,类可以继承接口中的所有抽象方法,并可以选择性地覆盖接口的默认方法。
总结来说,在Java中,接口用 interface 定义并描述了一种标准或契约,而 implements 则是让一个类承诺实现接口中规定的所有行为的一种机制。此外,Java并不支持多继承(即一个类不能直接继承多个类),但它允许一个类通过 implements 关键字实现多个接口。
实现接口:
- 类用extends,接口用implements
- 类可以实现很多接口
- 接口可以继承接口,但不能继承类
- 接口不能实现接口
面向接口的编程方式
- 设计程序时先定义接口,再实现类
- 任何类需要在函数传入传出的一定是接口而不是具体的类
Cell和Field的关系
- Cell在Field中,但是Cell的很多操作需要Field的数据
- 方法一:让每个Cell有一个Field的管理者(Cell知道Field)
- 方法二:由外部第三方来建立两者至今啊的联系(Cell不知道Field)
控制反转与MVC模式
控制反转
图形用户界面GUI
布局管理器
// 创建一个新的 View 对象,并传入 Field 对象作为参数
theView = new View(theField);
// 设置 JFrame 窗口的关闭操作为退出应用程序
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 禁止调整 JFrame 窗口的大小
frame.setResizable(false);
// 设置 JFrame 窗口的标题为 "Cells"
frame.setTitle("Cells");
// 将 View 对象添加到 JFrame 窗口中
frame.add(theView);
//相当于一个部件
JButton btnStep = new JButton("单步");
//frame是一个容器,BorderLayout划分为5个区域
frame.add(btnStep, BorderLayout.NORTH);
// btnStep.addActionListener(new StepListener());
btnStep.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按下了");
step();
frame.repaint();
}
});
// 计算并设置 JFrame 窗口的最合适大小
frame.pack();
// 将 JFrame 窗口设置为可见状态,显示在屏幕上
frame.setVisible(true);
Swing
Swing是Java中用于构建图形用户界面(GUI)的工具包,由Sun Microsystems开发,现在作为Java Foundation Classes (JFC)的一部分由Oracle公司维护。它建立在Abstract Window Toolkit (AWT)之上,但与使用原生组件的AWT不同,Swing提供了一套跨平台的GUI组件集合。
关于Swing的关键要点:
- 平台独立性:Swing的所有组件完全用Java编写,因此具有高度的可移植性。这意味着使用Swing构建的应用程序在Windows、macOS和Linux等不同操作系统上看起来和表现都具有一致性。
- 丰富的组件集:Swing提供了多种高级UI组件,如JFrame(窗口)、JPanel(面板)、JButton(按钮)、JTable(表格)、JTree(树)、JTextArea(多行文本框)、JTextField(单行文本框)、JScrollPane(滚动面板)、JMenuBar(菜单栏)、JToolBar(工具栏)等。这些组件相比AWT提供的组件功能更丰富。
- 可插拔外观与感觉:Swing支持多种内置外观风格,包括默认的跨平台Metal外观,以及模拟Windows环境外观的Windows L&F,还有Nimbus外观等。开发者还可以创建自定义外观以满足特定设计需求。
- 事件驱动编程模型:Swing遵循事件驱动编程模型,用户的操作行为(例如按钮点击或文本更改)会触发事件处理器(如注解了@Override的方法actionPerformed()等),使得开发者可以根据用户交互来定义应用程序的行为。
- 模块化设计:Swing采用模块化设计,允许开发者轻松地构建复杂且可扩展的用户界面。他们可以将组件组合到面板中,将面板添加到框架中,并按层次结构嵌套组件。
BorderLayout
BorderLayout是Java Swing库中的一种布局管理器,它将容器划分为五个预定义的区域:NORTH(北)、SOUTH(南)、EAST(东)、WEST(西)和CENTER(中)。在上述代码片段中:
当调用 frame.add(btnStep, BorderLayout.NORTH); 时,表示将按钮btnStep放置在 JFrame 窗口的顶部(北侧)。
这种布局的特点是每个区域只能添加一个组件,且默认情况下,未指定区域的组件会被添加到CENTER区域。当窗口大小改变时,不同区域的组件会根据布局规则自动调整其大小和位置。
其他的布局管理器
Java Swing库中提供了多种布局管理器以适应不同的界面布局需求。除了上述BorderLayout外,还有以下常见的布局管理器:
- FlowLayout:默认布局管理器,将组件从左到右、从上到下依次排列。当容器宽度不够时,会在下一行继续排列。
- GridLayout:网格布局,将容器划分为相等大小的单元格,所有添加到容器的组件会按行和列填充这些单元格。
- BoxLayout:单行或单列布局,按照指定的方向(水平或垂直)依次放置组件。
- GridBagLayout:灵活的网格布局,允许组件占据不规则大小的单元格,并可以对每个组件进行精细的位置和大小控制。
- CardLayout:卡片布局,可以像翻阅卡片一样切换显示多个面板(Panel),同一时间仅显示一个面板。
- GroupLayout:Swing 1.6引入的新布局管理器,旨在简化复杂布局的设计,它通过约束规则来定位和调整组件大小
控制反转
btnStep.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按下了");
step();
frame.repaint();
}
});
这是一个Java函数,它将一个按钮(btnStep)与一个动作监听器(ActionListener)关联起来。当按钮被点击时,会触发一个动作事件(ActionEvent),并执行监听器中的方法。
具体来说,该监听器是一个匿名内部类,继承自AbstractAction。在监听器的actionPerformed方法中,首先打印出"按下了",然后调用step()方法,最后调用frame.repaint()方法来刷新界面。
这个函数的作用是将按钮与一个动作监听器关联起来,当按钮被点击时,执行监听器中的方法,实现相应的功能
内部类
概念
示例
//匿名类
btnStep.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按下了");
step();
frame.repaint();
}
});
将上面的代码改成下面的代码
//内部类
private class StepListener implements ActionListener {
//使用关键字"implements"来实现接口
@Override
public void actionPerformed(ActionEvent e) {
step();
frame.repaint();
}
}
匿名类
- 将new对象的时候给出的类的定义形成了匿名类
- 匿名类可以继承某类,也可以实现某接口
- Swing的消息机制广泛使用匿名类
内部类
- 定义在别的类内部,函数内部的类
- 内部类能直接访问外部的全部资源
- 包括任何私有的成员
- 外部是函数时,只能访问哪个函数里final的变量
注入反转
- 由按钮公布一个守听者接口和一堆注册/注销函数
- 你的代码实现着个接口,将守听者对象注册在按钮上
- 一旦按钮被按下,就会反过来调用你的守听者对象的某个函数
MVC模式
JTable
用JTable类可以以表格的形式显示和编辑数据。JTable类的对象并不存储数据,它只是数据的表现
课程表程序
package kcb;
import javax.swing.*;
/**
* @author ASUS
*/
public class KCB {
public static void main(String[] args) {
//声明一个窗口
JFrame frame = new JFrame();
//声明一个表格
JTable table = new JTable(new KCBData());
//声明一个滚动面板
JScrollPane pane = new JScrollPane(table);
//将滚动面板添加到窗口
frame.add(pane);
//自动调整窗口大小
frame.pack();
// 设置窗口关闭方式
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//显示窗口
frame.setVisible(true);
}
}
package kcb;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import java.util.Arrays;
/**
* @author ASUS
*/
public class KCBData implements TableModel {
private final String[] title = {
"周一","周二","周三","周四","周五","周六","周日"
};
private final String[][] data = new String[8][7];
public KCBData() {
for (String[] datum : data) {
Arrays.fill(datum, "");
}
}
@Override
public int getRowCount() {
// 返回表格中的行数
return 8;
}
@Override
public int getColumnCount() {
// 返回表格中的列数
return 7;
}
@Override
public String getColumnName(int columnIndex) {
// 返回表格指定列的名称
return title[columnIndex];
}
@Override
public Class<?> getColumnClass(int columnIndex) {
// 返回表格指定列的数据类型
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
// 返回单元格是否可编辑
return true;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
// 返回表格指定单元格的值
return data[rowIndex][columnIndex];
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
// 设置表格指定单元格的值
data[rowIndex][columnIndex] = (String) aValue;
}
@Override
public void addTableModelListener(TableModelListener l) {
// 添加表格模型监听器
}
@Override
public void removeTableModelListener(TableModelListener l) {
// 移除表格模型监听器
}
}
MVC设计模式
JTable
只管表现,不管数据
Table Model
数据是由自己写的 Table Model 来维护的
MVC
控制与表现没有任何联系,不与表现直接打交道
异常处理与输入输出
异常
捕捉异常
int[] a = new int[10];
try {
int tr = a[10];
System.out.println("Hello world");
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界");
}
运行结果:数组越界
异常捕捉机制
捕捉机制
public static void f(){
int[] a = new int[10];
a[10] = 10;
System.out.println("hello");
}
public static void main(String[] args) {
try {
f();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("caught");
}
System.out.println("main");
}
运行结果:caugth
main
逻辑
package exception;
public class ArrayIndex {
public static void f() {
int[] a = new int[10];
a[10] = 10;
System.out.println(a[10]);
}
public static void g() {
f();
}
public static void h() {
int i = 10;
if (i < 100) {
g();
}
}
public static void k() {
try {
h();
} catch (NullPointerException e) {
System.out.println("K()");
}
}
public static void main(String[] args) {
try {
k();
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("caught");
}
System.out.println("main()");
}
}
捕捉到的异常
第一次测试
package exception;
public class ArrayIndex {
public static void f() {
int[] a = new int[10];
a[10] = 10;
System.out.println(a[10]);
}
public static void g() {
f();
}
public static void h() {
int i = 10;
if (i < 100) {
g();
}
}
public static void k() {
try {
h();
} catch (NullPointerException e) {
System.out.println("K()");
}
}
public static void main(String[] args) {
try {
k();
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("caught");
// 打印异常信息 10
System.out.println(e.getMessage());
// 打印异常对象,包含异常类型和详细信息
// java.lang.ArrayIndexOutOfBoundsException: 10
System.out.println(e);
// 打印异常的堆栈跟踪信息
// java.lang.ArrayIndexOutOfBoundsException: 10
// at exception.ArrayIndex.f(ArrayIndex.java:6)
// at exception.ArrayIndex.g(ArrayIndex.java:10)
// at exception.ArrayIndex.h(ArrayIndex.java:15)
// at exception.ArrayIndex.k(ArrayIndex.java:20)
// at exception.ArrayIndex.main(ArrayIndex.java:27)
e.printStackTrace();
}
System.out.println("main()");
}
}
第二次测试
public static void k() {
try {
h();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("K()");
throw e;
}
}
两种不同的情况
public static void k() {
try {
h();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("K()");
// throw e;
}
}
异常机制
异常
如果要读文件
- 打开文件
- 判断文件大小
- 分配足够的内存空间
- 把内存读入内存
- 关闭文件
例子
修改后的代码
抛出异常
异常声明
自定义异常
class OpenException extends Throwable {
}
如果你的函数可能抛出异常,就必须在函数的头部加以声明
抛出子类异常的话,会被捕捉到父类异常
什么能扔
- 任何继承Throwable类的对象
- Exception类继承Throwable
- throw new Exception();
- throw new Exception("HELP");
异常捕捉时的匹配机制
机制
- Is-A的关系
- 抛出子类异常的话,会被捕捉到父类异常
运行时刻异常
- 像ArrayIndexOutOfBoundsException这样的异常是不需要声明的
- 但是如果没有适当的机制来捕捉,就会最终程序终止
异常遇到继承
异常声明
- 如果你的函数可能抛出异常,就必须在韩式的头部加以声明
- 你可以声明并不会真的抛出异常
- 如果你调用一个声明会抛出异常的函数,那么你必须:
- 把函数的调用放在try块,并设置catch来捕捉所有可能抛出的异常或声明自己会抛出无法处理的异常
异常声明遇到继承关系
- 当覆盖一个函数的时候,子类不能声明抛出比父类的版本更多的异常
- 在子类的构造函数中,必须声明父类可能抛出的全部异常