中级实训全面总结
阶段一
本阶段除了Gridworld的开启任务之外,比较重用的是使用java实现一个带有GUI的计算器小程序,并且使用Ant、Junit、Sonarqube等进行辅助开发。
Java的GUI
Java拥有比较完善的API,实现GUI可以直接调用,十分方便。实现GUI的是Java Swing包,在代码开头添加:
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
从而导入相关的GUI类和方法。
JFrame
JFrame是一个窗口对象,简单实现后就可以直接生成一个可拉动的视窗了:
JFrame frame = new JFrame("window");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
其中有两个几乎是必须运行的方法:
- setDefaultCloseOperation:只有设置后才能通过右上角或左上角点击关闭窗口;
- setVisible:只有设置后才能展现窗口在系统上,建议在实现GUI代码的最后设置。
JLabel
JLabel是一个标签对象,用于显示一块无法直接键盘更改的文本块。
在初始化的时候直接传入字符串参数作为文本:
JLabel label = new JLabel("Hello World");
或者使用setText改变显示的文本:
label.setText("Hello");
这个方法是控制改变JLabel的常用方法,基本是唯一途径。
JButton
按钮对象,实现一个简单的按钮,用于触发事件。
同样在初始化时传入字符串参数,作为显示的文本:
JButton plus = new JButton("+");
只是简单初始化一个按钮对象是不够的,它本身并不拥有任何功能,需要再点击后触发特定的事件需要我们自己设定:
plus.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
operator.setText("+");
}
});
实现ActionListener接口,并且重写actionPerformed方法,在方法内部编写在点击按钮后产生的事件。
JTextField
文本框对象,实现一个可以通过键盘更改的文本编辑框,当然我们也可以设置禁止键盘更改:
同样在初始化时传入字符串作为初始文本内容:
JTextField first = new JTextField("2");
有时我们可能会需要文本框中的字符居中或靠右显示,则需要通过特定的方法设置:
first.setHorizontalAlignment(JTextField.CENTER);
当然setHorizontalAlignment对JLabel对象同样使用,鉴于我们并不是每次都希望他们的文本都靠左。
Layout
GUI中窗口内组件的布局是一件令人头疼的事,这里我们可以使用一些常用的简单的布局对象:
frame.setLayout(new GridLayout(2, 5));
没错,在java中,布局策略本身也是一种对象。这里我们使用的是网格布局,它可以按照矩阵网格的模式依此排布组件。在设置后布局后,我们就可以添加组建了:
frame.add(first);
frame.add(operator);
frame.add(second);
frame.add(equal);
Ant
如果你知道用于构建C/C++程序的Make的话,那么可以很好的理解Ant就是Java的Make。Ant用于构建Java程序,它的主要功能是将所有程序操作自动化,包括创建文件,删除文件,进行编译,运行程序等等。只要写好一个Ant文件,那么所有事情都会有条不紊的按照你想要的顺序自动完成。
project
Ant生成文件的根元素。为了初始化整个构建程序,我们必须先定义project元素:
<project default="run" name="Helloworld">
</project>
可以看到除了元素标签之外,我们还需要定义元素的属性。在project中主要的属性有:
- default:默认运行的target,稍后解释
- name:project的名称
- basedir:指定基准目录,一般为.
property
有了根元素project声明整个项目,我们就可以添加property来定义属性了。可以理解它为一个变量,声明后可以被方便的使用其保存的值:
<property name="src" value="src/"></property>
<property name="classes" value="bin/"></property>
它的属性非常直观:
- name:属性名称
- value:保存的值
之后我们可以通过${src}
的形式取得它的值,稍后会有例子。在这里,我们定义了两个用于保存.java
文件目录的属性src,和编译后.class
所在目录的属性classes,以便后续使用。
target
project的子元素,表示一个单独执行的任务。可以理解为java中的一个函数,ant通过执行target来完成构建任务。看到下面的例子,我们知道target主要有以下属性:
- name:名称,用于被调用
- depends:依赖,在执行此target前会先执行的target
<target name="compile" depends="clean">
<mkdir dir="${classes}"/>
<javac srcdir="${src}" destdir="${classes}" excludes="TestRunner.java, TestHelloworld.java"></javac>
</target>
Task
task是一类元素,表示用于执行一项具体任务的语句。包括, , 等等,我们通过观察标签就能直观知道他们的作用。本项目中会用到的有:
-
mkdir:用于创建文件夹
<mkdir dir="${classes}"/>
dir为创建的目录路径,这里我们使用了一个property进行赋值
-
javac:用于java编译
<javac srcdir="${src}" destdir="${classes}"></javac>
其中srcdir和destdir分别指定了源代码和目标目录的路径。
-
java:运行java程序
<java classname="Helloworld" fork="yes"> <classpath path="${classes}"></classpath> </java>
classname指定了运行的Main class,而fork一般必须选为yes,表示会新创建一个进程运行java程序,防止当前build中断。并且还必须包括一个classpath子元素,用于指定运行的
.class
文件的目录。 -
delete:删除目录
<delete dir="${classes}"></delete>
Junit
Junit是用于对java程序进行单元测试的工具,它和正常的java代码一样,通过调用需要测试的函数,然后使用断言判断返回的结果是否正确,以进行测试。
格式规范
一个规范的测试函数和文件必须满足一下要求:
- 文件名和类名为:TestXXX.java,其中XXX为被测试的文件和类
- 函数名为:testXXXX,其中XXXX为被测试函数的名称
- 在测试函数前添加:@Test
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class TestHelloworld {
private String obj = "world";
private Helloworld hello = new Helloworld(obj);
@Test
public void testSayHello() {
assertEquals("Hello, world", hello.sayHello());
}
}
断言
我们可以看到代码中使用到了assertEquals函数,这是一个Junit提供的断言。断言判断参数是否满足条件,不满足则使测试失败,说明程序不达标。这里assertEquals表示后者sayHello函数返回值,必须与前者“Hello, world”相等,否则测试失败。常用的断言有:
//Check that two objects are equal
assertEquals(str1, str2);
//Check that a condition is true
assertTrue (val1 < val2);
//Check that a condition is false
assertFalse(val1 > val2);
等等。我们就使用这些断言,判断被测试函数的返回值是否与预期相符,以达到测试的目的。
阶段二
本阶段主要专注于Gridworld任务,并无新的技术学习,暂且略过。
阶段三
本阶段重点是学习实现了对bmp图片文件的读取工具类,并且学习了广度优先搜索和A*启发式算法。
读取bmp
由于bmp文件的复杂多样性,Java的api中被没有能很好的直接读取的方法。所以这里我们实现的方法是比较使用的。
FileInputStream
为了打开指定的bmp文件,我们需要使用此对象对特定的目录进行跟踪。此对象可以将文件构建成一个字节串管道,我们可以通过方法不断从管道中读取数据。
FileInputStream input = new FileInputStream(filePath);
在初始化时传入路径字符串参数,指定相应的文件。
当然这里我们为了方便,一次性从管道内读取了所有的字节:
byte[] bytes = input.readAllBytes();
由于bmp中不是所有字节都对读取图像信息有用,所以我们直接操作字节串而不是管道会更加合理便捷。
注意在开启使用完FileInputStream之后,需要及时关闭:
input.close();
ByteBuffer
ByteBuffer是一个封装字节数组的对象,通过此对象实现的方法,我们可以更加便捷的对字节串进行操作。
ByteBuffer buffer = ByteBuffer.wrap(bytes);
使用静态方法wrap,传入需要封装的字节串,返回封装好的对象。
createImage
为了通过像素数组创建一个Image对象,我们使用:
Toolkit.getDefaultToolkit().createImage((ImageProducer)new MemoryImageSource(width, height, pix, 0, width));
这里的createImage方法可以实现我们所需要的功能,当然中间我们需要先实现一个MemoryImageSource对象,能够将我们之间从bmp文件中读取到的包含像素数组,高宽数值的字节串信息封装。
其中前两个参数分别是宽和高,第三个参数是保存像素数值的数值,注意其中的排列顺序是有规定的,它是一个int数组,每个int代表一个像素值,其高8位需要设置为全1,也就是0xff,其余三个8位分别就是红蓝绿三个通道的数值。
写入bmp
BufferedImage
实际上Image只是一个抽象类,当我们需要创建一个实例时,必须创建其子类BufferedImage类对象:
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
在这里,它可以将Image的抽象类重新cast为一个可以操作的子类。
ImageIO
读写image的io对象,实现大部分格式图片的读取和写入。
ImageIO.write(bufferedImage, "bmp", file);
这里我们可以使用write方法将一个Image对象保存为一个特定的格式对象。
过滤通道
private static Image showChanel(Image sourceImage, int chanel) {
RGBImageFilter filter = new RGBImageFilter() {
@Override
public int filterRGB(int x, int y, int rgb) {
this.canFilterIndexColorModel = true;
return (rgb & chanel);
}
};
return Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(sourceImage.getSource(), filter));
}
有时候我们需要对一个图像进行特定形式的滤镜操作,这时候就需要api中的ImageFilter了。比如这里我们是对色彩通道进行过滤,使用的是:RGBImageFilter。
创建一个对象,并且重写其filterRGB方法,里面对每个像素进行数值处理,并返回最终的像素值。