不可否认,Java是一门体系庞大的语言,更需要不断练习实践熟悉,对于初学者来说,如何顺利实验很关键,这有助于新手增强学习信心和兴趣。相比较其它语言如python等,想顺利完成Java编程实验不太容易。本文对一般教程或网上常见的内容不再过多介绍,但将对初学者易造成疑惑误解的地方进行详尽的介绍说明,以帮助初学者少走弯路顺利完成Java程序实验。
要完成Java程序实验,需要先安装Java开发工具包(Java Development kit,JDK),可到官网
https://www.oracle.com/java/technologies/downloads/下载
Java历史版本官方下载地址
https://www.oracle.com/java/technologies/downloads/archive/
选择合适的版本,点击后面相应的Download按钮,即可来到版本选择页面,官网提供了包括Linux、Windows、MacOS在内的多个系统,可以选择自己所需要的系统对应版本进行下载。下载之前,官网有可能弹出需要登录的提示,可以按照提示注册帐号并登录,然后再进行下载。若不想注册账号,可以尝试使用https://repo.huaweicloud.com/java/jdk/或 http://www.javathinker.net/download.jsp下载
JDK现在发布节奏有点快,如何取舍呢?
对于Java SE 8之后的产品版本,Oracle将每三年指定一个版本作为长期支持(LTS,Long Term Support)版本,2017年起Oracle就将Java SE的发布频率提高到每六个月一次,多在每年的三月和九月,且非LTS的每一版的支持时间也只有六个月。参见Oracle Java SE支持路线图https://www.oracle.com/java/technologies/java-se-support-roadmap.html
建议下载使用LTS版本,目前有8 (2014年3月发布)、11 (2018年9月发布)、17(2021年9月发布)。
具体安装过程在此略过,具体可参考https://blog.csdn.net/cnds123/article/details/80446598
关于JAVA、JDK、JRE和JVM 可参见 https://blog.csdn.net/cnds123/article/details/122647541
Java程序的实验步骤
1) 编写源文件:扩展名必须是 .java。
2) 编译Java源程序:用Java编译器(javac.exe)编译源文件,得到字节码文件。
3) 运行Java程序:使用Java解释器(java.exe)来解释执行字节码文件。
简单的Java应用程序的源文件是由一个或若干个互相独立的类组成,例如下面Java源文件DemoA.java是由两个名字分别为DemoA和Student的类组成。
public class DemoA {
public static void main (String args[]) {
System.out.println("大家好!");
System.out.println("Nice to meet you");
Student stu = new Student();
stu.speak("We are students");
}
}
class Student {
public void speak(String s) {
System.out.println(s);
}
}
Java是区分大小写的。一定要注意,Java的关键字是小写的;变量名也要注意大小写,如mon和Mon是不一样的。
可以使用一个文本编辑器,如记事本编写上述例子的源文件。
Java源程序中语句所涉及到的小括号及标点符号都是英文状态下输入的括号和标点符号,比如“大家好!”中的引号必须是英文状态下的引号,而字符串里面的符号不受汉字符或英文字符的限制。
使用Windows自带的记事本编辑代码
事实上.java、.bat、.ini等,都是文本文件,只不过扩展名不同而已。那么,如何更改文本文档文件扩展名呢?
方法1、编辑完记事本文件内容后,单击“文件”菜单,在出现的下拉菜单中,选择“另存为”命令
在记事本中保存文件时,用英文双引号(英文半角)把文件名括起来,如:"autoexec.bat",再保存就可以了。
方法2、在出现的“另存为”对话框界面,单击“保存类型”后面的向下小箭头,在出现的下拉选项中。单击选中“所有文件(*.*)”,是不是扩展名可以修改了,改成你需要的即可。
参见下图:
如果代码中含有中文字符,建议编码选用ANSI,原因可见:用javac编译时报错:“错误:编码GBK出现不可映射字符”与windows记事本几种编码格式
https://blog.csdn.net/cnds123/article/details/122723394
保存Java源文件名为DemoA.java,我将其保存到D:\JavaTest中。
如果源文件中有多个类,那么只能有一个类是public类;如果有一个类是public类,那么源文件的名字必须与这个类的名字完全相同,扩展名是java;如果源文件没有public类,那么源文件的名字只要和某个类的名字相同,并且扩展名是java就可以了。
当编译一个.java文件(即一个编译单元)时,在.java文件中的每个类都会有一个输出文件,而该输出文件的名称与.java文件中每个类的名称相同,只是多了一个后缀名.class。因此在编译少量.java文件之后,会得到大量的.class文件。每个.java文件都是一个构件,如果希望许许多多的这样的构件从属于同一个群组,就可以在每一个.java文件中使用关键字package。而这个群组就是一个类库。
【关于java程序的组织结构,详见 https://blog.csdn.net/cnds123/article/details/80446981 】
javac编译命令和java运行命令的说明
javac命令是用来编译.java文件的。常用格式如下:
javac -d destdir srcFile
说明
1、-d destdir 指定存放编译生成的 .class 文件的路径。
若此选项省略,那么默认在当前目录下生成 .class 文件,并且不会生成包文件夹;如 javac JavacTest.java ,便在当前路径下生成了一个JavacTest.class的文件。
若使用-d destdir ,作用(i)在destdir指定的目录中生成class文件,(ii)且,若源代码中含有包语句将按包结构生成对应的目录结构,例如源代码中含有包语句package packA.packB; 将在destdir指定的目录中建立子目录\packA\packB,生成的class文件放到此处。
当前目录可以用“.”来表示。如javac -d . srcFile。【思考:javac -d . srcFile和不写 -d .即javac srcFile效果一样吗?请注意源代码中含有包(package)语句的情况。】
2、srcFile 是要编译的源代码文件名,必须有扩展名,可以带路径:路径\文件名.java,如 D:\TestDir\ Demo.java
3、使用选项 -encoding utf-8 指明源文件的编码格式,如:
javac -encoding utf-8 -d . SoftReferenceTest.java
4、不需要指定目标文件名,编译后以.class为扩展名 以源文件中所定义的类名为其文件名。
5、若Javac编译时提示 某某某使用或覆盖了已过时的 API。比较笼统,可是使用参数-Xlint:deprecation 用来提供详细原因,定位那个语句造成的,类似如下做,将
javac -d D:\JavaTestDir\ch04 D:\JavaTestDir\ch04\DemoA.java,改为
javac -Xlint:deprecation -d D:\JavaTestDir\ch04 D:\JavaTestDir\ch03\ DemoA.java
6、-classpath或-cp参数,设定查找被引用类的位置,可以是目录、jar文件、zip文件(里面都是class文件),jar文件、zip文件需要全路径到jar文件、zip文件。此参数会覆盖掉所有在CLASSPATH 环境变量中的设定。当你要编译的类引用了其它的类,但被引用类的.class文件不在当前目录下时,就需要通过-classpath来引入。
7、-sourcepath参数,指定查找被引用类源文件(.java 文件)的位置,注意不要误认为是指定srcFile的路径。
Java命令可以运行class文件字节码。常用格式如下:
java ClassFile
说明
1、常用选项-classpath ,可简写为-cp,用来指定位置:可以是要运行的类的路径,要运行的类所依赖的其他类的路径、jar文件、zip文件之类,jar文件、zip文件需要全路径到jar文件。它将覆盖CLASSPATH 环境变量中的设定。例如 java -cp c:\dir1\lib.jar Test
2、ClassFile 要运行的类的名,是含有入口方法的字节代码文件名。是“纯”名称——不能含路径且不能含扩展名.class。代码中未使用package(包)语句,ClassFile就是类名,若使用了package语句,类名前应带包名,格式为:包名.类名。
程序可以通过编译,但Java命令运行class文件,报“找不到或无法加载主类”错误的原因及处理
有如下两种情况:
1)、可用cmd的cd命令跳转到class文件所在的路径,再运行。javac 编译命令支持带路径的文件名,但java运行命令不支持带路径的文件名而只能是类文件名且不带扩展名,如运行 D:\TestDir\ch03 目录中的DemoA.class 文件,
对此可以这样处理,方法较多:
▲用cmd的cd命令切换到.class 文件所在的目录 cd /d D:\JavaTestDir\ch03,再这样运行java DemoA
▲使用参数 -cp 或 -classpath,可这样执行java -classpath D:\JavaTestDir\ch03 DemoA 或 java -cp D:\JavaTestDir\ch03 DemoA
▲在CLASSPATH环境变量中,加入路径D:\JavaTestDir\ch03,再这样运行java DemoA
▲将.class 文件放到在java命令所在目录下,再这样运行java DemoA ,但一般不用。
2)、若java 源文件使用了package(包)语句,这种情况下使用java命令运行一个.class文件,需要按照package语句指定的包层次转化为的文件路径找到.class文件,因此,用Javac命令来编译.java文件时可以使用-d选项,用来把把编译出的类文件(.class文件)放入包层次对应的目录中,否则需要用户自己来做。这种情况下用Java运行命令中ClassFile的格式为:包名.类名。
特别提醒:javac 编译命令支持带路径的文件名,但java运行命令不支持带路径的文件名而只能是[类]文件名且不带扩展名。
关于javac、java等命令更多信息参见
Java SE 8
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/s1-create-build-tools.html#sthref31
Java SE 11 Tools and Commands Reference
https://docs.oracle.com/en/java/javase/11/tools/tools-and-command-reference.html
Java® Development Kit Version 17 Tool Specifications
https://docs.oracle.com/en/java/javase/17/docs/specs/man/index.html
cmd的打开与使用
按下win键+R键(先按下键,再按下R键)或 右击“开始”->单击“运行”,弹出“运行”窗口,参见下图:
cmd窗口中的“>”是命令提示符,你可以在其后面输入命令。
关于cmd中cd命令 可参见https://blog.csdn.net/cnds123/article/details/107042760
先用cd命令(CMD的目录操作命令,cd是change directory[改变目录]的缩写,该命令也可以写成chdir,用来改变当前目录)切换到D:\JavaTest中,再用javac DemoA.java编译之,参见下图:
编译后,出现扩展名(后缀)为.class的文件
现在可以用java DemoA运行之,参见下图:
多文件编译运行
如果应用程序的源文件和其他的源文件在同一目录中,也可以只编译应用程序的源文件,Java 系统会自动地先编译应用程序需要的其他源文件。例如
有如下 4 个源文件保存到同一目录中,例如“D:\JavaTest” ,然后编译 MainClass.java。
MainClass.java 文件源码:
public class MainClass {
public static void main(String args[]) {
System.out.println("只需编译我");
A a=new A();
a.fA();
B b=new B();
b.fB();
}
}
A.java 文件源码:
public class A {
void fA() {
System.out.println("I am A");
}
}
B.java 文件源码:
public class B {
void fB() {
System.out.println("I am B");
}
}
C.java 文件源码:
public class C {
void fC() {
System.out.println("I am C");
}
}
在编译 MainClass.java的过程中,会自动地先编译 A.java、B.java,但不编译C.java ,因为应用程序并没有使用 C.java源文件产生的字节码类文件。编译通过后,在
“D:\JavaTest”中将会有MainClass.class、A.class 和B.class 几个字节码文件。
然后,可以使用java MainClass执行,参见下图:
提示:
(1)MainClass.java 编译通过以后,若修改了 A.java 源文件 ,比如在命令行窗口输出 Nice to meet you ,修改 A.java 源文件后单独编译A.java,然后直接运行应用程序 MainClass,也是可以的。
(2)如果需要编译某个目录下的全部 Java 源文件,比如“D:\JavaTest”目录,可以使用如下命令:
javac *.java
也是可以的。
使用包(package)的编译运行
本例涉及 2 个Java 文件,都在“D:\JavaTest”目录中,主类文件Say.java 包含另一个文件He.java 的test方法。
Say.java 文件源码:
package xzy;
import xzy.He;
public class Say {
public static void main(String args[]) {
He h=new He();
h.test();
System.out.println("hello");
}
}
He.java 文件源码:
package xzy;
public class He {
void test(){
System.out.println("test");
}
}
特别提示:建议使用-d 参数指定 编译的class路径, 会自动创建包名,并将 class 文件生成至此目录。【若不指定 -d,只会在当前文件夹下生成class文件,但不会创建包名,若要执行成功,需手动创建包】
编译:javac -d . Say.java He.java
自动创建包名结构,参见下图:
执行:java xzy.Say
参见下图:
关于java项目程序目录结构的建议
特别说明:前面的示例并没有按照这些建议进行,但按照这些建议,层次分明,可以减少混乱,项目中文件越多就越能体现出其好处。
建议将源码文件(即扩展名为.java的文件)和编译成字节代码文件(即扩展名为.class的文件)分开,如将源码文件放在scr目录中,将编译成字节代码放到classes或bin目录中.
建议将使用包(package)的源码文件,放在和包层次相同的目录中,目的是把不同的 java 程序分类保存,但这不是必须的。
例、有一个使用包的程序文件Something.java源码如下
package lee.java;
public class Something{
//主要执行区块
public static void main(String[ ] args){
//程序语句
System.out.println("我的第一个Java程序");
}
}
使用带 -d参数javac 编译成字节代码:
javac -d D:\JavaTest2\classes D:\JavaTest2\src\Something.java
现在,目录结构示意图如下:
运行命令:
java -classpath D:\JavaTest2\classes lee.java.Something
下面,给出一个复杂点的例:
小猪走迷宫游戏(本例取自网络)
我这个小项目的目录结构示意图如下:
两个源码文件放在D:\JavaPractice\src\com\zzk中
PigWalkMazeFrame.java源码文件内容如下:
package com.zzk;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
@SuppressWarnings("serial")
public class PigWalkMazeFrame extends JFrame implements KeyListener, Runnable {
Rectangle rect1, rect2, rect3, rect4;
int gobuttonX = 0, gobuttonY = 0;
final JButton goButton = new JButton();
URL url = getClass().getResource("/images/pig.png");
ImageIcon imageIcon = new ImageIcon(url);
final JLabel label = new JLabel();
/**
* Launch the application
*
* @param args
*/
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
PigWalkMazeFrame frame = new PigWalkMazeFrame();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame
*/
public PigWalkMazeFrame() {
super();
addWindowListener(new WindowAdapter() {
public void windowOpened(final WindowEvent e) {
goButton.requestFocus(); // 使小猪获取焦点
}
});
getContentPane().setLayout(null);
setBounds(100, 100, 488, 375);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("小猪走迷宫");
BakcgroundPanel panel = new BakcgroundPanel();
rect1 = new Rectangle(0, 55, 190, 40);
rect2 = new Rectangle(190, 40, 40, 240);
rect3 = new Rectangle(190, 180, 230, 40);
rect4 = new Rectangle(300, 180, 40, 140);
setResizable(false);
panel.setLayout(null);
panel.setBounds(0, 0, 482, 341);
getContentPane().add(panel);
final JButton button = new JButton();
button.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
buttonAction(e);
}
});
URL url = getClass().getResource("/images/button.png");
ImageIcon imageIcons = new ImageIcon(url);
button.setIcon(imageIcons);
button.setBounds(27, 100, 106, 28);
goButton.setBounds(0, 40, imageIcon.getIconWidth(), imageIcon
.getIconHeight());
panel.add(goButton);
panel.add(button);
gobuttonX = goButton.getBounds().x; // 定义小猪按钮的横坐标
gobuttonY = goButton.getBounds().y; // 定义小猪按钮的纵坐标
goButton.addKeyListener(this);
goButton.setIcon(imageIcon);
goButton.setContentAreaFilled(false); // 取消填充区域
goButton.setBorder(null); // 取消边框
url = getClass().getResource("/images/exit.png");
imageIcons = new ImageIcon(url);
label.setIcon(imageIcons);
label.setBounds(300, 315, imageIcons.getIconWidth(), imageIcons
.getIconHeight());
panel.add(label);
}
@Override
public void keyPressed(KeyEvent e) {
if ((gobuttonY == 286)) { // 如果小猪的纵坐标等于286
Thread thread = new Thread(this);
thread.start(); // 启动线程
}
if (e.getKeyCode() == KeyEvent.VK_UP) { // 如果用户按下了向上键
Rectangle rectAngle = new Rectangle(gobuttonX, gobuttonY, 20, 20); // 创建Rectangle对象
if (rectAngle.intersects(rect1)
|| rectAngle.intersects(rect2)
|| rectAngle.intersects(rect3)
|| rectAngle.intersects(rect4)) { // 判断小猪是否走出了迷宫
gobuttonY = gobuttonY - 2; // 设置变量坐标
goButton.setLocation(gobuttonX, gobuttonY); // 设置按钮坐标
} else { // 如果小猪走出了迷宫
JOptionPane.showMessageDialog(this, "撞墙了吧!重新开始吧!", "撞墙啦!",
JOptionPane.DEFAULT_OPTION);
}
} else if (e.getKeyCode() == KeyEvent.VK_DOWN) { // 判断用户是否按向下键
Rectangle rectAngle = new Rectangle(gobuttonX, gobuttonY, 20, 20);
if (rectAngle.intersects(rect1)
|| rectAngle.intersects(rect2)
|| rectAngle.intersects(rect3)
|| rectAngle.intersects(rect4)) {
gobuttonY = gobuttonY + 2;
goButton.setLocation(gobuttonX, gobuttonY);
} else {
JOptionPane.showMessageDialog(this, "撞墙了吧!重新开始吧!", "撞墙啦!",
JOptionPane.DEFAULT_OPTION);
}
} else if (e.getKeyCode() == KeyEvent.VK_LEFT) { // 如果用户按向左键
Rectangle rectAngle = new Rectangle(gobuttonX, gobuttonY, 20, 20);
if (rectAngle.intersects(rect1)
|| rectAngle.intersects(rect2)
|| rectAngle.intersects(rect3)
|| rectAngle.intersects(rect4)) {
gobuttonX = gobuttonX - 2;
goButton.setLocation(gobuttonX, gobuttonY);
} else {
JOptionPane.showMessageDialog(this, "撞墙了吧!重新开始吧!", "撞墙啦!",
JOptionPane.DEFAULT_OPTION);
}
} else if (e.getKeyCode() == KeyEvent.VK_RIGHT) { // 如果用户按向右键
Rectangle rectAngle = new Rectangle(gobuttonX, gobuttonY, 20, 20);
if (rectAngle.intersects(rect1)
|| rectAngle.intersects(rect2)
|| rectAngle.intersects(rect3)
|| rectAngle.intersects(rect4)) {
gobuttonX = gobuttonX + 2;
goButton.setLocation(gobuttonX, gobuttonY);
} else {
JOptionPane.showMessageDialog(this, "撞墙了吧!重新开始吧!", "撞墙啦!",
JOptionPane.DEFAULT_OPTION);
}
}
}
public void buttonAction(ActionEvent e) {
goButton.setIcon(imageIcon); // 重新设置按钮的显示图片
goButton.addKeyListener(this); // 为按钮添加键盘事件
goButton.setBounds(0, 40, imageIcon.getIconWidth(), imageIcon
.getIconHeight()); // 设置小猪位置
gobuttonX = goButton.getBounds().x; // 获得小猪当前位置的X坐标
gobuttonY = goButton.getBounds().y;// 获得小猪当前位置的Y坐标
goButton.requestFocus(); // 设置按钮获取焦点
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void run() {
URL out = getClass().getResource("/images/pigOut.png"); // 获取图片URL
ImageIcon imageout = new ImageIcon(out); // 创建图片对象
goButton.setIcon(imageout); // 设置小猪按钮显示图片
goButton.setBounds(gobuttonX,
gobuttonY - imageout.getIconHeight() + 50, imageout
.getIconWidth(), imageout.getIconHeight()); // 重新设置按钮位置
goButton.removeKeyListener(this); // 按钮移除键盘事件
}
}
BakcgroundPanel.java源码文件内容如下:
package com.zzk;
import java.awt.Color;
import java.awt.Graphics;
import java.net.URL;
import javax.swing.*;
@SuppressWarnings("serial")
public class BakcgroundPanel extends JPanel {
public BakcgroundPanel() {
super();
}
public void paintComponent(Graphics g) {
try {
URL url = getClass().getResource("/images/back.jpg"); // 指定图片路径
ImageIcon image = new ImageIcon(url); // 创建ImageIcon对象
g.drawImage(image.getImage(), 0, 0, this); // 将图片绘制到面板上
g.setColor(Color.RED); // 绘制颜色
g.fillRect(0, 40, 190, 40); // 在面板上绘制长方形
g.setColor(Color.yellow);
g.fillRect(190, 40, 40, 240);
g.setColor(Color.pink);
g.fillRect(190, 180, 230, 40);
g.setColor(Color.cyan);
g.fillRect(300, 180, 40, 140);
} catch (Exception e) {
}
}
}
五个图片文件放在D:\JavaPractice\src\images中
使用带 -d参数javac 编译成字节代码参见下图:
现在,目录结构变为:
运行参见下图:
顺便指出,用命令行方式——用javac编译源程序,用java运行class文件——因为java语言条条框框比其它语言多,还是比较麻烦的。通常,在实际开发应用时,会采用集成开发环境(IDE,Integrated Development Environment),使用IDE,其实是IDE帮你调用了javac和java。 关于java初学者需不需要立马学习使用IDE,可参见
https://zhuanlan.zhihu.com/p/93720794
附录
进一步学习:Java入门与提高学习讲座系列文章_cnds123的专栏-CSDN博客