Java 黑马程序员(进阶篇1)
项目:拼图游戏
1. 主界面分析:
① GUI 图形化接口显示操作画面
- JFrame:最外层的窗口
- JMenuBar:最上层的菜单
- JLable:管理文字和图片的容器
② 创建主界面(框架)
public class App {public static void main(String[] args) {new GameFrame();new LoginFrame();new RegisterFrame();}
}
package com.itheima.ui;import javax.swing.*;public class GameJFrame extends JFrame {public GameJFrame() {this.setSize(603,680);this.setVisible(true);}
}
package com.itheima.ui;import javax.swing.*;public class LoginJFrame extends JFrame {public LoginJFrame() {this.setSize(480,430);this.setVisible(true);}
}
package com.itheima.ui;import javax.swing.*;public class RegisterJFrame extends JFrame {public RegisterJFrame() {this.setSize(488,500);this.setVisible(true);}
}
② 运行结果:
三张页面按照输入的长宽像素大下运行自动弹出
2. 创建主界面(用继承的方法)
① 为什么使用继承改写?
(1) 自定义窗口类(如 GameFrame
)继承 javax.swing.JFrame
,直接复用 JFrame
的功能,在子类构造方法中配置窗口属性(无需额外手动创建 JFrame
对象)。
(2) 不适用继承和使用继承的对比:
先看「不使用继承」的写法(手动创建 JFrame
)
// 第1个窗口
JFrame frame1 = new JFrame();
frame1.setSize(600, 500); // 重复写
frame1.setTitle("主窗口");
frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 重复写
frame1.setVisible(true);// 第2个窗口(设置窗口)
JFrame frame2 = new JFrame();
frame2.setSize(600, 500); // 再次重复写
frame2.setTitle("设置窗口");
frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 再次重复写
frame2.setVisible(true);// 第3个窗口(帮助窗口)
JFrame frame3 = new JFrame();
frame3.setSize(600, 500); // 第三次重复写
frame3.setTitle("帮助窗口");
frame3.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 第三次重复写
frame3.setVisible(true);
- 问题:
setSize
、setDefaultCloseOperation
等通用配置被重复写了 3 次,冗余且难维护(如果要改尺寸,3 处都要改)。
继承 JFrame
的子类方式:
// 子类中封装配置(只写一次)
public class MyFrame extends JFrame {public MyFrame(String title) { // 允许传入标题区分不同窗口this.setSize(600, 500); // 只写一次this.setTitle(title);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 只写一次this.setVisible(true);}
}// 创建3个窗口时,直接复用子类的配置:
new MyFrame("主窗口"); // 自动应用 setSize 等配置
new MyFrame("设置窗口"); // 自动应用,无需重复写
new MyFrame("帮助窗口"); // 自动应用,无需重复写
- 优势 1:
setSize
等通用配置只在子类中写一次,后续创建任何窗口都自动生效(如果要改尺寸,只需改子类中的setSize
一行代码),只需new 子类()
就能自动复用这些配置,避免手动创建时 “每次都重复写配置代码” 的冗余。 - 优势 2:主方法中只需
new GameFrame()
,就能得到一个 “已经配置好的窗口”,无需再关心 “如何初始化JFrame
” 的细节。
(3) 代码示例与关键语句分析(以 “拼图游戏主界面” 为例)
import javax.swing.*;public class GameFrame extends JFrame {public GameFrame() {// 1. 设置窗口尺寸this.setSize(603, 680); // 2. 设置窗口标题this.setTitle("拼图小游戏1.0"); // 3. 窗口置顶(始终显示在其他窗口上层)this.setAlwaysOnTop(true); // 4. 窗口在屏幕中居中this.setLocationRelativeTo(null); // 5. 点击关闭按钮时,终止整个程序this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // 6. 让窗口可见(Swing窗口默认不可见,必须显式设置)this.setVisible(true); }
}
3. 菜单制作
import javax.swing.*;public class GameFrame extends JFrame {public GameFrame(){initJFrame();//初始化菜单//创建整个菜单对象initJMenuBar();this.setVisible(true);}private void initJMenuBar() {JMenuBar jMenuBar = new JMenuBar();//创建菜单对象//创建菜单选项对象JMenu functinonMenu = new JMenu("功能");JMenu aboutMenu = new JMenu("关于我们");//创建下面的条目对象JMenuItem replayItem = new JMenuItem("重新游戏");JMenuItem reloginItem = new JMenuItem("重新登陆");JMenuItem closeItem = new JMenuItem("关闭游戏");JMenuItem accountItem = new JMenuItem("公众号");//将条目对象添加到选项对象当中//添加功能条目对象到功能菜单当中functinonMenu.add(replayItem);functinonMenu.add(reloginItem);functinonMenu.add(closeItem);//添加公众号条目对象到关于菜单当中aboutMenu.add(accountItem);jMenuBar.add(functinonMenu);jMenuBar.add(aboutMenu);//给整个界面设置菜单this.setJMenuBar(jMenuBar);//将菜单展示在屏幕上}private void initJFrame() {this.setSize(603,680);this.setTitle("拼图小游戏1.0");this.setAlwaysOnTop(true);this.setLocationRelativeTo(null);}
}
4. 添加图片
package com.itheima.ui;import javax.swing.*;
import java.util.Random;public class GameJFrame extends JFrame {int[][] data = new int[4][4];public GameJFrame() {initData();initJFrame();initJMenuBar();initImage();this.setVisible(true);}private void initData() {int[] tempArr = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};Random r = new Random();for (int i = 0; i < tempArr.length; i++) {int index = r.nextInt(tempArr.length);int temp = tempArr[i];tempArr[i] = tempArr[index];tempArr[index] = temp;}for (int i = 0; i < tempArr.length; i++) {data[i / 4][i % 4] = tempArr[i]; //不太理解}}private void initImage() {for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {int num = data[i][j];JLabel jLabel = new JLabel(new ImageIcon("D:\\张锐彬\\Java练习\\day14-test\\src\\image\\animal\\animal3\\"+ num +".jpg"));jLabel.setBounds(105 * j, 105 * i, 105, 105);this.getContentPane().add(jLabel); //不太理解}}}private void initJMenuBar() {JMenuBar jMenuBar = new JMenuBar();JMenu functionJMenu = new JMenu("功能");JMenu aboutJMenu = new JMenu("关于我们");JMenuItem replayItem = new JMenuItem("重新游戏");JMenuItem reLoginItem = new JMenuItem("重新登录");JMenuItem closeItem = new JMenuItem("关闭游戏");JMenuItem accountItem = new JMenuItem("公众号");functionJMenu.add(replayItem);functionJMenu.add(reLoginItem);functionJMenu.add(closeItem);aboutJMenu.add(accountItem);jMenuBar.add(functionJMenu);jMenuBar.add(aboutJMenu);this.setJMenuBar(jMenuBar);}private void initJFrame() {this.setSize(603,680);this.setTitle("拼图单机版 v1.0");this.setAlwaysOnTop(true);this.setLocationRelativeTo(null);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);this.setLayout(null);}
}
关键逻辑:
先执行 initData()
生成随机数据,再执行 initImage()
根据数据加载图片:
initData()
会创建一个包含0~15
的一维数组,打乱后存入4x4
的data
数组(data[i][j]
存储的是随机数字)。- 执行前:
data
是int[4][4]
类型的数组,int
类型数组的默认值是0
,所以此时data
中所有元素都是0
。 - 执行后:
data
中充满了0~15
的随机数字(比如data[0][0]=5
,data[0][1]=12
等)。 initImage()
必须知道data[i][j]
具体是什么数字,才能加载正确的图片。
5. 打乱图片
题目:数组打乱与二维数组转换
需求说明:
请编写一个 Java 程序,实现以下功能:
(1) 创建一个包含整数 0~15(共 16 个元素)的一维数组;
(2) 使用随机打乱算法(洗牌算法)将该一维数组的元素顺序打乱;
(3) 将打乱后的一维数组转换为 4 行 4 列的二维数组(int[4][4]
);
按以下格式输出结果:
- 第一行:打印打乱后的一维数组元素(元素间用空格分隔);
- 接下来 4 行:打印转换后的二维数组,每行元素用空格分隔,每行单独占一行。
package Test;import java.util.Random;public class test1 {public static void main(String[] args) {int[] tempArr = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};Random r = new Random();for (int i = 0; i < tempArr.length; i++) {int index = r.nextInt(tempArr.length);int temp = tempArr[i];tempArr[i] = tempArr[index];tempArr[index] = temp;}for (int i = 0; i < tempArr.length; i++) {System.out.print(tempArr[i] + " ");}System.out.println();int[][] data = new int[4][4];for (int i = 0; i < tempArr.length; i++) {data[i / 4][i % 4] = tempArr[i]; //不太理解}for (int i = 0; i < data.length; i++) {for (int j = 0; j < data.length; j++) {System.out.print(data[i][j] + " ");}System.out.println();}}
}
关键逻辑:
data[i / 4][i % 4] = tempArr[i];
① 用「整数除法 i / 4
」确定 “行索引”
- 当
i = 0,1,2,3
时,i / 4 = 0
→ 对应二维数组的第 0 行; - 当
i = 4,5,6,7
时,i / 4 = 1
→ 对应二维数组的第 1 行; - 当
i = 8,9,10,11
时,i / 4 = 2
→ 对应二维数组的第 2 行; - 当
i = 12,13,14,15
时,i / 4 = 3
→ 对应二维数组的第 3 行。
② 用「取余运算 i % 4
」确定 “列索引”
- 当
i = 0,4,8,12
时,i % 4 = 0
→ 对应每行的第 0 列; - 当
i = 1,5,9,13
时,i % 4 = 1
→ 对应每行的第 1 列; - 当
i = 2,6,10,14
时,i % 4 = 2
→ 对应每行的第 2 列; - 当
i = 3,7,11,15
时,i % 4 = 3
→ 对应每行的第 3 列。