Java 编译全解

Java 很诱人,但对于刚跨入 Java 门槛的初学者来说,编译并运行一个无比简单的 Java 程序简直就是一个恶梦。明明程序没错,但各种各样让人摸不着头脑的错误信息真的让你百思不得其解,许多在 Java 门口徘徊了很久的初学者就这样放弃了学习 Java 的机会,很是可惜。笔者也经历过这个无比痛苦的阶段,感觉到编译难的问题就出在 classpath 的设置及对 package 的理解之上。本文以实例的方式,逐一解决在编译过程中所出现的各种 classpath 的设置问题。本文实例运行的环境是在 Windows XP + JDK 1.5.0 。对其他的环境,读者应该很容易进行相应的转换。

1. 
下载并安装 JDK1.5.0 ,并按默认路径,安装到 C:/Program Files/Java/jdk1.5.0 中。

2. 
用鼠标单击 WindowsXP 的“开始” -> “运行”,在弹出的运行窗口中输入 cmd ,按确定或回车,打开一个命令行窗口。

3. 
在命令行中输入:

java

有一列长长的洋文滚了出来,这是 JDK 告诉我们 java 这个命令的使用方法。其中隐含了一个重要信息,即 JDK 安装成功,可以在命令行中使用 java 此命令了。

4. 
在命令行中输入

javac

屏幕显示:

'javac' 
不是内部或外部命令,也不是可运行的程序或批处理文件。

这是由于 windows 找不到 javac 这个命令的原因。这就不明白了, java javac 都是 JDK 在同一个子目录里面的两个文件,为什么可以直接运行 java 而不能直接运行 javac 呢?原来, Sun 公司为了方便大家在安装完 JDK 后马上就可以运行 Java 类文件,在后台悄悄地将 java 命令加入了 Path 的搜索路径中,因此我们可以直接运行 java 命令 ( 但我们是看不到它到底是在哪设置的,无论是在用户的 Path 或系统的 Path 设置中均找不到这个 java 存放的路径 ) 。但 Sun 所做的到此为止,其他 JDK 的命令,一概不管,需要由用户自己添加到搜索路径中。

5. 
既然如此,那我们自己添加 Path 的搜索路径吧。对“我的电脑”按右键,选“属性”,在“系统属性”窗口中选“高级”标签,再按“环境变量”按钮,弹出一个“环境变量”的窗口,在用户变量中新建一个变量,变量名为“ Path ”,变量值为 "C:/Program Files/Java/jdk1.5.0/bin;%PATH%" 。最后的 %PATH% 的意思是说,保留原有的 Path 设置,且将目前的 Path 设置新加到其前面。一路按“确定”退出 ( 共有 3 ) 。关掉原来的命令行窗口,依照第 2 步,重新打开一个新的命令行窗口。在此窗口中输入

javac

长长的洋文又出现了,这回是介绍 javac 的用法。设置成功。

6. So far so good. 
到目前为止,我们已经可以编程了。但是,这不是一个好办法。因为随着以后我们深入学习 Java ,我们就会用到 JUnit Ant NetBeans 等应用工具,这些工具在安装时,都需要一个名为指向 JDK 路径的“ JAVA_HOME ”的环境变量,否则就安装不了。因此,我们需要改进第 5 步,为以后作好准备。依照第 5 步,弹出“环境变量”的窗口,在用户变量中新建一个变量,变量名为“ JAVA_HOME ”,变量值为 "C:/Program Files/Java/jdk1.5.0" 。注意,这里的变量值只到 jdk1.5.0 ,不能延伸到 bin 中。确定后,返回“环境变量”的窗口,双击我们原先设定的 Path 变量,将其值修改为“ %JAVA_HOME%/bin;%PATH% ”。这种效果与第 5 步是完全一样的,只不过多了一个 JAVA_HOME 的变量。这样,以后当我们需要指向 JDK 的路径时,只需要加入“ %JAVA_HOME% ”就行了。至此, Path 路径全部设置完毕。一路确定退出,打开新的命令行窗口,输入

javac

如果长长的洋文出现, Path 已经设置正确,一切正常。如果不是,请仔细检查本步骤是否完全设置正确。

7. 
开始编程。在 C 盘的根目录中新建一个子目录,名为“ JavaTest ”,以作为存放 Java 源代码的地方。打开 XP 中的记事本,先将其保存到 JavaTest 文件夹中,在“文件名”文本框中输入 "Hello.java" 。注意,在文件名的前后各加上一个双引号,否则,记事本就会将其存为 "Hello.java.txt" 的文本文件。然后输入以下代码:

public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello, world");
  }
}

再次保存文件。

8. 
在命令行窗口中输入

cd C:/JavaTest

将当前路径转入 JavaTest 中。然后,输入

javac Hello.java

JDK
就在 JavaTest 文件夹中编译生成一个 Hello.class 的类文件。如果出现“ 1 error ”或“ XX errors ”的字样,说明是源代码的输入有误,请根据出错提示,仔细地按第 7 步的代码找出并修正错误。请读者注意甄别代码输入有误的问题与 classpath 设置有误的问题。因为本文是关于如何正确设置 classpath package 的,因此,这里假设读者输入的代码准确无误。到目前为此,由于我们是在源代码的当前路径下编译,因此,不会出现 classpath 设置有误的问题。

9. 
在命令行窗口中输入

java Hello

屏幕出现了

Hello world

成功了,我们已经顺利地编译及运行了第一个 Java 程序。
但是,第 8 步及第 9 步是不完美的,因为我们是在 JavaTest 这个存放源码的文件夹中进行编译及运行的,因此,一些非常重要的问题并没有暴露出来。实际上,第 8 步的“ javac Hello.java ”及第 9 步的“ java Hello ”涉及到两个问题,一是操作系统如何寻找“ javac ”及“ java ”等命令,二是操作系统如何寻找“ Hello.java ”及“ Hello.class ”这些用户自己创建的文件。对于“ javac ”及“ java ”等命令,由于它们均是可执行文件,操作系统就会依据我们在第 6 步中设置好的 Path 路径中去寻找。而对于“ Hello.java ”及“ Hello.class ”这些文件, Path 的设置不起作用。由于我们是在当前工作路径中工作, java javac 会在当前工作路径中寻找相应的 java 文件 (class 文件的寻找比较特殊,详见第 11 )
因此一切正常。下面我们开始人为地将问题复杂化,在非当前工作路径中编译及运行,看看结果如何。

10. 
在命令行窗口中输入

cd C:
转入到 C 盘根目录上,当前路径离开了存放源码的工作区。输入

javac Hello.java

屏幕出现:

error: cannot read: Hello.java
1 error

找不到 Hello.java 了。我们要给它指定一个路径,告诉它到 C:/JavaTest 去找 Hello.java 文件。输入

javac C:/JavaTest/Hello.java

OK
,这回不报错了,编译成功。

11. 
输入

java C:/JavaTest/Hello

这回屏幕出现:

Exception in thread "main" java.lang.NoClassDefFoundError: C:/JavaTest/Hello

意思为在“ C:/JavaTest/Hello ”找不到类的定义。明明 C:/JavaTest/Hello 是一个 .class 文件,为什么就找不到呢?原来, Java 对待 .java 文件与 .class 文件是有区别的。对 .java 文件可以直接指定路径给它,而 java 命令所需的 .class 文件不能出现扩展名,也不能指定额外的路径给它。

那么,如何指定路径呢?对于 Java 所需的 .class 文件,必须通过 classpath 来指定。

12. 
依照第 5 步,弹出“环境变量”窗口,在用户变量中新建一个变量,变量名为“ classpath ”,变量值为 "C:/JavaTest" 。一路按“确定”退出。关闭原命令行窗口,打开新的命令行窗口,输入

java Hello

Hello world ”出来了。由此可见,在“环境变量”窗口中设置 classpath 的目的就是告诉 JDK ,到哪里去寻找 .class 文件。这种方法一旦设置好,以后每次运行 java javac 时,在需要调用 .class 文件时, JDK 都会自动地来到这里寻找。因此,这是一个全局性的设置。

13. 
除了这种在环境变量”窗口中设置 classpath 的方法之外,还有另一种方法,即在 java 命令后面加上一个选项 classpath ,紧跟着不带扩展名的 class 文件名。例如,

java -classpath C:/JavaTest Hello

JDK
遇到这种情况时,先根据命令行中的 classpath 选项中指定的路径去寻找 .class 文件,找不到时再到全局的 classpath 环境变量中去寻找。这种情况下,即使是没有设置全局的 classpath 环境变量,由于已经在命令行中正确地指定类路径,也可以运行。

为了在下面的例子中更好地演示 classpath 的问题,我们先将全局的 classpath 环境变量删除,而在必要时代之以命令行选项 -classpath 。弹出“环境变量”窗口,选中“ classpath ”的变量名,按“删除”键。

此外, java 命令中还可以用 cp ,即 classpath 的缩写来代替 classpath ,如 java -cp C:/JavaTest Hello 。特别注意的是, JDK 1.5.0 之前, javac 命令不能用 cp 来代替 classpath ,而只能用 classpath 。而在 JDK 1.5.0 中, java javac 都可以使用 cp classpath 。因此,为保持一致,建议一概使用 classpath 作为选项名称。

14. 
我们再次人为地复杂化问题。关闭正在编辑 Hello.java 的记事本,然后将 JavaTest 文件夹名称改为带空格的“ Java Test ”。在命令行中输入

javac C:/Java Test/Hello.java

长长的洋文又出来了,但这回却是报错了:

javac: invalid flag: C:/Java

JDK
将带有空格的 C:/Java Test 分隔为两部分 "C:/Java" "Test/Hello.java" ,并将 C:/Java 视作为一个无效的选项了。这种情况下,我们需要将整个路径都加上双引号,即

javac "C:/Java Test/Hello.java"

这回 JDK 知道,引号里面的是一个完整的路径,因此就不会报错了。同样,对 java 命令也需要如此,即

java -classpath "C:/Java Test" Hello

对于长文件名及中文的文件夹, XP 下面可以不加双引号。但一般来说,加双引号不容易出错,也容易理解,因此,建议在 classpath 选项中使用双引号。

15. 
我们再来看 .java 文件使用了其他类的情况。在 C:/Java Test 中新建一个 Person.java 文件,内容如下:

public class Person {
  private String name;

  public Person(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }
}

然后,修改 Hello.java ,内容如下:

public class Hello {
  public static void main(String[] args) {
    Person person = new Person("Mike");
    System.out.println(person.getName());
  }
}

在命令行输入

javac "C:/Java Test/Hello.java"

错误来了:

C:/Java Test/Hello.java:3: cannot find symbol
symbol: class Person

JDK
提示找不到 Person 类。为什么 javac "C:/Java Test/Hello.java" 在第 14 步中可行,而在这里却不行了呢?第 14 步中的 Hello.java 文件并没有用来其他类,因此, JDK 不需要去寻找其他类,而到了这里,我们修改了 Hello.java ,让其使用了一个 Person 类。根据第 11 步,我们需要告诉 JDK ,到哪里去找所用到的类,即使这个被使用的类就与 Hello.java 一起,同在 C:/Java Test 下面!输入

javac -classpath "C:/Java Test" "C:/Java Test/Hello.java"

编译通过, JDK C:/Java Test 文件夹下同时生成了 Hello.class Person.class 两个文件。实际上,由于 Hello.java 使用了 Person.java 类, JDK 先编译生成了 Person.class ,然后再编译生成 Hello.class 。因此,不管 Hello.java 这个主类使用了多少个其他类,只要编译这个类, JDK 就会自动编译其他类,很方便。输入

java -classpath "C:/Java Test" Hello

屏幕出现了

Mike

成功。

16. 
15 步说明了在 Hello.java 中如何使用一个我们自己创建的 Person.java ,而且这个类与 Hello.java 是同在一个文件夹下。在这一步中,我们将考查 Person.java 如果放在不同文件夹下面的情况。

先将 C:/Java Test 文件夹下的 Person.class 文件删除,然后在 C:/Java Test 文件夹下新建一个名为 DF 的文件夹,并将 C:/Java Test 文件夹下的 Person.java 移动到其下面。在命令行输入

javac -classpath "C:/Java Test/DF" "C:/Java Test/Hello.java"

编译通过。这时 javac 命令没有什么不同,只需将 classpath 改成 C:/Java Test/DF 就行了。

在命令行输入

java -classpath "C:/Java Test" Hello

这时由于 Java 需要找在不同文件夹下的两个 .class 文件,而命令行中只告诉 JDK 一个路径,即 C:/Java Test ,在此文件夹下,只能找到 Hello.class ,找不到 Person.class 文件,因此,错误是可以预料得到的:

Exception in thread "main" java.lang.NoClassDefFoundError: Person
        at Hello.main(Hello.java:3)

果真找不到 Person.class 。在设置两个以上的 classpath 时,先将每个路径以双引号引起来,再将这些路径以“ ; ”号隔开,并且每个路径与“ ; ”之间不能带有空格。因此,我们在命令行重新输入:

java -classpath "C:/Java Test";"C:/Java Test/DF" Hello

编译成功。但也暴露出一个问题,如果我们需要用到许多分处于不同文件夹下的类,那这个 classpath 的设置岂不是很长!有没有办法,对于一个文件夹下的所有 .class 文件,只指定这个文件夹的 classpath ,然后让 JDK 自动搜索此文件夹下面所有相应的路径?有,只要使用 package

17. package
简介。 Java 中引入 package 的概念,主要是为了解决命名冲突的问题。比如说,在我们的例子中,我们设计了一个很简单的 Person 类,如果某人开发了一个类库,其中恰巧也有一个 Person 类,当我们使用这个类库时,两个 Person 类出现了命名冲突, JDK 不知道我们到底要使用哪个 Person 类。更有甚者,当我们也开发了一个很庞大的类库,无可避免地,我们的类库中与其他人开发的类库中命名冲突的情况就会越来越多。总不能为了避免自己的类名与其他人开发的类名相同,而让每个编程人员都绞尽脑汁地将一个本应叫 Writer 的类强行改名为 SarkuyaWriter MikeWriter, SmithWriter 吧?

现实生活中也是如此。假如你名叫张三,又假如与你同一单位的人中有好几个都叫张三,那你的问题就来了。某天单位领导在会上宣布,张三被任命为办公室主任,你简直不知道是该哭还是该笑。但如果你的单位中只有你叫张三,你才不会在乎全国叫张三的人有多少个,因为其他张三都分布在全国各地、其他城市,你看不见他们,摸不着他们,自然不会担心。

Sun
从这个“张三问题”受到了很大的启发,为解决命名冲突问题,就采取了“眼不见心不烦”的策略:将每个类都归属到一个特定的区域中,在同一个区域中的所有类,都不允许同名;而不同区域的类,由于相互看不到,则允许有同名的类存在。这样,就解决了命名冲突的问题,正如北京的张三与上海的张三毕竟不是同一人。这个区域在 Java 中就叫 package 。由于 package Java 中非常重要,如果你没有定义自己的 package JDK 将会你的类都归到一个默认的无名 package 中。

自定义 package 的名称可以由各个程序员自由创建。作为避免命名冲突的手段, package 的名称最好足以与其他程序员的区别开来。在互联网上,每个域名都是唯一的,因此, Sun 推荐将你自己的域名倒写后作为 package 的名称。如果你没有自己的域名,很可能只是因为囊中羞涩而不去申请罢了,并不见得你假想的域名与其他域名发生冲突。例如,笔者假想的域名是 sarkuya.com ,目前就是唯一的,因此我的 package 就可以定名为 com.sarkuya 。谢谢 Java 给了我们一个免费使用我们自己域名的机会,唯一的前提是倒着写。当然,每个 package 下面还可以带有不同的子 package ,如 com.sarkuya.util com.sarkuya.swing ,等等。

定义 package 的方式是在相应的 .java 文件的第一行加上“ package packagename; ”的字样,而且每个 .java 文件只能有一个 package 。实际上, Java 中的 package 的实现是与计算机文件系统相结合的,即你有什么样的 package ,在硬盘上就有什么样的存放路径。例如,某个类的 package 名为 com.sarkuya.util ,那么,这个类就应该必须存放在 com/sarkuya/util 的路径下面。至于这个 com/sarkuya/util 又是哪个文件夹的子路径,第 18 步会谈到。

package
除了有避免命名冲突的问题外,还引申出一个保护当前 package 下所有类文件的功能,主要通过为类定义几种可视度不同的修饰符来实现: public, protected, private,  另外加上一个并不真实存在的 friendly 类型。

对于冠以 public 的类、类属变量及方法,包内及包外的任何类均可以访问;
protected
的类、类属变量及方法,包内的任何类,及包外的那些继承了此类的子类才能访问;
private
的类、类属变量及方法,包内包外的任何类均不能访问;
如果一个类、类属变量及方法不以这三种修饰符来修饰,它就是 friendly 类型的,那么包内的任何类都可以访问它,而包外的任何类都不能访问它 ( 包括包外继承了此类的子类 ) ,因此,这种类、类属变量及方法对包内的其他类是友好的,开放的,而对包外的其他类是关闭的。

前面说过, package 主要是为了解决命名冲突的问题,因此,处在不同的包里面的类根本不用担心与其他包的类名发生冲突,因为 JDK 在默认情况下只使用本包下面的类,对于其他包, JDK 一概视而不见:“眼不见心不烦”。如果要引用其他包的类,就必须通过 import 来引入其他包中相应的类。只有在这时, JDK 才会进行进一步的审查,即根据其他包中的这些类、类属变量及方法的可视度来审查是否符合使用要求。如果此审查通不过,编译就此卡住,直至你放弃使用这些类、类属变量及方法,或者将被引入的类、类属变量及方法的修饰符改为符合要求为止。如果此审查通过, JDK 最后进行命名是否冲突的审查。如果发现命名冲突,你可以通过在代码中引用全名的方式来显式地引用相应的类,如使用

java.util.Date = new java.util.Date()

或是

java.sql.Date = new java.sql.Date()


package
的第三大作用是简化 classpath 的设置。还记得第 16 步中的障碍吗?这里重新引用其 java 命令:

java -classpath "C:/Java Test";"C:/Java Test/DF" Hello

我们必须将所有的 .class 文件的路径一一告诉 JDK ,而不管 DF 其实就是 C:/Java Test 的子目录。如果要用到 100 个不同路径的 .class 文件,我们就得将 classpath 设置为一个特别长的字符串,很累。 package 的引入,很好地解决了这个问题。 package 的与 classpath 相结合,通过 import 指令为中介,将原来必须由 classpath 完成的类路径搜索功能,很巧妙地转移到 import 的身上,从而使 classpath 的设置简洁明了。我们先看下面的例子。

18. 
先在 Hello.java 中导入 DF.Person 。代码修改如下:

import DF.Person;

public class Hello {
  public static void main(String[] args) {
    Person person = new Person("Mike");
    System.out.println(person.getName());
  }
}

再将 DF 子文件夹中的 Person.java 设置一个 DF 包。代码修改如下:

package DF;

public class Person {
  private String name;
  public Person(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }
}

好了,神奇的命令行出现了:

javac -classpath "C:/Java Test" "C:/Java Test/Hello.java"
java -classpath "C:/Java Test" Hello

尽管这次我们只设置了 C:/Java Test classpath ,但编译及运行居然都通过了!事实上, Java 在搜索 .class 文件时,共有三种方法:
一是全局性的设置,详见第 12 步,其优点是一次设置,每次使用;
二是在每次的 javac java 命令行中自行设置 classpath ,这也是本文使用最多的一种方式,其优点是不加重系统环境变量的负担;
三是根据 import 指令,将其内容在后台转换为 classpath JDK 将读取全局的环境变量 classpath 及命令行中的 classpath 选项信息,然后将每条 classpath 与经过转换为路径形式的 import 的内容相合并,从而形成最终的 classpath.  在我们的例子中, JDK 读取全局的环境变量 classpath 及命令行中的 classpath 选项信息,得到 C:/Java Test 。接着,将 import DF.Person 中的内容,即 DF.Person 转换为 DF/Person,  然后将 C:/Java Test 与其合并,成为 C:/Java Test/DF/Person ,这就是我们所需要的 Person.class 的路径。在 Hello.java 中有多少条 import 语句,就自动进行多少次这样的转换。而我们在命令行中只需告诉 JDK 最顶层的 classpath 就行了,剩下的则由各个类中的 import 指令代为操劳了。这种移花接木的作法为我们在命令行中手工地设置 classpath 提供了极大的便利。

应注意的一点是, import 指令是与 package 配套使用的,只有在某类通过“ package pacakgename; ”设定了包名后,才能给其他类通过 import 指令导入。如果 import 试图导入一个尚未设置包的类, JVM 就会报错。

19. 
我们接下来看,当使用 JDK 类库时, classpath 如何设置。

20. 
修改 Hello.java ,内容如下:

import DF.Person;
import java.util.Date;

public class Hello {
  public static void main(String[] args) {
    Date date = new Date();
    System.out.println(date);

    Person person = new Person("Mike");
    System.out.println(person.getName());
  }
}

21. JDK
类库存放于 C:/Program Files/Java/jdk1.5.0/jre/lib/rt.jar 文件中。关于 jar 文件的介绍,已经超出了本文的范围,感兴趣的读者可以阅读 Horstmann 写的 Core Java 一书。

jar
文件可以用 WinRar 打开。用 WinRar 打开后,可以看到里面有一些文件夹,双击其中的 java 文件夹,再双击 util 的文件夹,可以在看到 Date.class 文件就在其中。如果你看过 Data.java 或其他 JDK 类库的源码 ( C:/Program Files/Java/jdk1.5.0/src.zip 文件中 ) ,你就会发现,像 java util 这些文件夹均是 package 。这也是 Hello.java 2 行中使用了 import 指令的原因。

我们可以通过 WinRar 的查找功能来定位某个类所在的包。在“查找文件”的窗口中的“要查找的文件名”文本框中输入 Date.class ,就会查找出在 rt.jar 文件中存在两个 Date.class 文件,一个是 java/sql/Date.class ,另一个是 java/util/Date.class 。其中, sql 下面的 Date.class 文件与数据库有关,并非我们这里所需, java/util/Date.class 才是我们所要的。

rt.jar
文件就像本文中的 C:/Java Test 中一样,是 JDK 类库的唯一入口。我们可以在命令行的 classpath 选项指定 .jar 文件。需要注意, .jar 文件的 classpath 设置有些特珠。在以前的例子中,我们设置 classpath 时都是设置了路径就行了,而对于 .jar 文件,我们必须将 .jar 文件名直接加到 classpath 中。

22. 
在命令行输入

javac -classpath "C:/Program Files/Java/jdk1.5.0/jre/lib/rt.jar";"C:/Java Test" "C:/Java Test/Hello.java"
java -classpath "C:/Program Files/Java/jdk1.5.0/jre/lib/rt.jar";"C:/Java Test" Hello

这样当然没有问题,因为我们指定了 rt.jar 文件及 C:/Java Test 两个 classpath 。但且慢,在命令行输入:

javac -classpath "C:/Java Test" "C:/Java Test/Hello.java"
java -classpath "C:/Java Test" Hello

不可思议的是,编译及运行成功了!令人惊讶的是在我们将 classspath 只设置为 C:/Java Test 的情况下, JDK 如何得出 java.util.Date classpath

原因在于,就像 java Path 路径已经悄悄在后台设置好一样, rt.jar classpath 路径也悄悄地在后台设置了。因此,我们不必多此一举手工设置其 classpath 了。

23. 
最后一点需要谈到的是,如果主类恰好也在一个 package ( 在大型的开发中,其实这才是一种最常见的现象 ) ,那么 java 命令行的类名前面就必须加上包名。

C:/Java Test 下面新建一个文件夹,名为 NF 。将 C:/Java Test 下面的 Hello.class 删除,将 Hello.java 移到 NF 文件夹下。打开 NF 文件夹下的 Hello.java ,为其设置 package 属性。

package NF;

import DF.Person;
import java.util.Date;

public class Hello {
  public static void main(String[] args) {
    Date date = new Date();
    System.out.println(date);

    Person person = new Person("Mike");
    System.out.println(person.getName());
  }
}

编译与以前没啥区别,只不过是修正一下改过之后的路径。

javac -classpath "C:/Java Test" "C:/Java Test/NF/Hello.java"

java 命令行却有了变化

java -classpath "C:/Java Test" NF.Hello

上面命令行语句中, NF.Hello 告诉 JDK Hello.class NF package 下面。

至此,本文有关 classpath package 的问题的讨论已经全部结束。由此可见, Java 的入门的确非常不易。如果初学 Java 的程序员一见到 Java 的编译竟是如此的复杂,多半就会抽身而退。因此,笔者认为, Sun J2SE Tutorial 中故意将编译的问题尽量简单化,以吸引更多的 Java 初学者。一旦品尝了 Java 的香醇可口的美味后,就不用担心他们退出了,因为咖啡是非常容易让人上瘾的。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值