Skip to content

Latest commit

 

History

History
282 lines (165 loc) · 15.7 KB

TR-2.md

File metadata and controls

282 lines (165 loc) · 15.7 KB

TR-2 究竟做了些什么

?> 该部分内容适合新手
如果你发现其中内容过于简单,请考虑跳过以 TR 标识的章节

我们一步步分析,看看前面两节,你都做了哪些伟大的事情。

创建了一个包

首先,你点击「New」、「Package」,这是在告诉 IDEA:「帮我创建一个包!」。

IDEA 当然愿意帮你,但是它需要知道你的包名(Package Name)是什么。

建立包就是在 Java 的世界中申请了一个空间,因此你需要给包提供名字。不然 Java 分不清哪个是哪个,那可就乱套了。

你输入的是:

rarityeg.helloworld

这段文字可以被看作:

<名字>.<名字>

每个「名字」都是一个包。所以这个标题应该改成「创建了两个包」,我们实际上是先创建了一个名为 rarityeg 的包,又在其中创建了一个名为 helloworld 的包。

那么包到底有什么用呢?它可以使得我们编写的类被合理地放到各个地方去,便于查找。例如,helloworld 这个包,别人一看就知道里面包含着「Hello World」的代码,如果不使用包,或者改名为 asu7fg35a435w43f 这样的名字,就乱成一团啦!

点(.)用来分隔各个包。

创建了一个类

(Class)是存放信息的地方。是 Java 代码运行的地方。

你通过「New」、「Java Class」指示 IDEA 创建一个类,它为你做了,按照你输入的名字 HelloWorld。这是类名(Class Name),类名遵循 Java 标识符规范

  • 只能由大小写字母,数字,$ (美元符)和 _(下划线) 构成,其中数字不能用于开头
  • 不能使用关键字(Keyword)

但是,为了使名字看上去易懂,我们采用帕斯卡命名法

  • 所有的单词的首字母大写,其余字母小写

这样就给类取了名字。

那到底什么是类呢?

此处内容引用自 RUNOOB Java 教程

一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用(Call)彼此的方法来协同工作。

  • 对象(Object):对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
  • (Class):类是一个模板,它描述一类对象的行为和状态。
  • 方法(Method):方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。
  • 实例变量(Instance Variable):每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。

Minecraft 里面有很多东西,如果把它们全都理解为对象,再按照上面的办法处理,是不是就显得很轻松了呢?

那我们这里创建的 HelloWorld 类代表的又是什么呢?

插件本身

HelloWorld 代表了我们这个插件,我们画出了一张蓝图(类),把插件的信息全部写在上面,然后交给了 Java 和 Bukkit。Bukkit 按照我们的蓝图,盖了个房子(对象),然后开始处理。

请记住:在 Java 中,我们只能画蓝图(定义类),这叫开发(Development),盖房子(创建对象)这样的工作我们在开发时没法做,那是 Java 在运行时(Runtime)要做的事情。

接下来我们继续往下看。

创建类时,IDEA 为我们自动补全了如下内容:

package rarityeg.helloworld;

public class HelloWorld {
}

代码是一行一行写出来的,我们也要一行一行读。

第一个是 package 语句。

?> 什么是语句
从上一个分号开始,到下一个分号为止,是一个语句。以大括号括起来的部分作为一个整体,和其它部分放在一起,即使后面没有分号,也是一个语句。
例如:int i = 5; 是一个语句,if (a == 6){语句;语句;} 中虽然含有多个语句,但 {} 将它们括在一起,与前面的 if 等部分一起,这是一个语句。
对了!语句的最后必须有一个分号(;),Java 用分号来识别一般语句的结束,所以一般不能省略分号,只有 } 作为语句的结尾时才可以省略分号。这种事情多写写代码就知道了

package 语句用于指定这个类属于哪个包。这就像房子在那里,你住进去后还要改写户口本一样。

接下来的 public class HelloWorld {} 整体是一个语句。这表示「定义类」,class 是用于定义类的关键字(Keyword),public 是访问修饰符,HelloWorld 是类的标识符(Identifier),大括号内是类的内容。

?> 什么是关键字
关键字是用于实现指定功能而约定好的字符,它们有各自的功能。这里的 class 关键字的作用就是「定义一个类」,编译器看到它,就知道:「哦,要定义一个类了,应该怎么怎么样……」

?> 什么是访问修饰符
在 Java 中,类的有些数据是只给自己用的,有些是公开的,为了保证数据安全,Java 设计了访问修饰符public 表示「公共」,意思就是:「HelloWorld 这个类是公开的,大家随意使用哈~」

此外,你还会发现顶部的标题栏中写着 HelloWorld.java,为什么这和类的名字一样呢?

因为这是 Java 的规范。类的名字和文件名必须相同,想想也是,如果我在家门口挂上混乱的名字,那就是撒谎造假,万万不能容忍!除此之外,一山不容二虎,一份文件中绝不能有两个公共类,不然 Java 就疯掉啦!

继承了 JavaPlugin

到目前为止,代码都是由工具生成的,下面才是我们自己输入的第一份。

改动后的代码像这样:

package rarityeg.helloworld;

import org.bukkit.plugin.java.JavaPlugin;

public class HelloWorld extends JavaPlugin {
    @Override
    public void onEnable() {
        getLogger().info("Hello, world!");
    }
}

首先大家把目光放到 import 上。还记得吗?我们说过有些代码已经由别人为我们写好了,那叫什么?对,那是轮子import 的意思是「导入」,也就是告诉 Java:「我们现在要用这个 JavaPlugin 啦,请你帮我们找一下。」

这里我们给出的是完整的类路径。因为我们要导入的这个类并不在我们自己的空间中,Bukkit 的开发人员使用了名为 org.bukkit 的包,Bukkit 的分类工作很仔细,他们精心地将我们需要的类放在了 org.bukkit.plugin.java 下。

接下来我们来看 extends 关键字。这表示「继承」。什么是继承呢?

此处内容引用自 RUNOOB Java 教程

EXTENDS

「食草动物」继承了「动物」,「食肉动物」也继承了「动物」,它们都具有和「动物」类一样的方法。

继承需要符合的关系是:子类 is a 父类,父类更通用,子类更具体。

虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。

这里就相当于,JavaPlugin 是一幅蓝图,已经画好了插件的基本结构,我们把它 Copy 过来,在上面进行一些自定义的修改。我们不用操心插件是怎么被 Bukkit 加载的,因为 JavaPlugin 中已经为我们写好了。

那你应该知道后面的 JavaPlugin 是什么了吧?这又是一个标识符。为什么这里不需要写长长的 org.bukkit.plugin.java 呢?Java 很聪明,已经导入的内容,它会帮我们「记住」。这就像你去朋友家玩,第一次需要知道哪个区哪条道几单元几零几,第二次还需要那么麻烦吗?

定义了方法

再往下看,大家先忽略 @Override 注解,看看这个部分:

public void onEnable() {
    getLogger().info("Hello, world!");
}

这就是定义了方法。那这里为什么没有像 public method onEnable 这样的 method 关键字呢?

不需要。有且仅有方法的名字后面有一对小括号 (),既然这样就可以识别出来,那为什么还要多一个关键字呢?浪费空间吗?所以,没有 method 关键字。

方法有时也被称为函数(Function),它们是一样的。我会尽可能按 Java 规范使用「方法」一词,但如果我说「函数」,你要知道我说的就是「方法」。

public 表示「onEnable 方法是公共的,可以被随意使用哦~」

void 是一个新词汇。这是啥东西?

这是返回值(Return Value)的类型。想想你在初中数学中学到的函数,它是不是进行了一系列计算最后得出一个结果呢?结果就叫返回值。void 表示「这个函数的返回值为空」或「这个函数不需要返回值」。如果这里不是 void,这里应当填写返回值所属的类(返回值也是一个对象啊)。

现在你应该能够隐隐约约感受到面向对象的感觉了吧?任何东西都可以使用对象来表示,并用抽象的类来描述。

小括号内是参数(Argument),参数可以有一个,可以没有,也可以有很多。不同于返回值,这里如果没有参数,不写就可以了,没必要写一个 void

紧接着小括号后面的是一对大括号 {},大括号里面的内容就是方法体(Method Body)。方法体中含有一堆语句,当这个方法被调用时,Java 就会自动执行其中的每条语句。

?> 什么是调用
调用(Call)表示「使用」。这个方法好不容易写好了,以后可以在各种地方使用了,使用方法就被称为调用,与此相对的,我们现在正在做的就是定义(Define)方法。

那么我们再看看大括号里面的内容:

getLogger().info("Hello, world!");

这条语句实际上可以被拆成两个部分:

getLogger()

<上面那个方法的返回值>.info("Hello, world!");

第一部分是一个方法调用,调用了 getLogger 方法。等一下,这个方法在哪里?我怎么没见到它?

getLogger 实际上是 JavaPlugin 的一个方法。由于我们已经继承了 JavaPlugin,Java 会认为:「这已经是一家人了,就请随意使用吧。」于是我们就可以使用 getLogger 了。

那这为什么就表示「调用」呢?因为我们在 getLogger 后面加了一对括号 ()。看到括号,Java 就认为:「需要的东西都准备好啦,不这个时候开始,什么时候开始?」于是就调用了这个方法。

这个方法执行后返回一个 Logger 的实例,也就是一个日志记录器。

这里再次体现了面向对象的思想。

那么我们做一个等量代换,把刚刚的第二部分:

<上面那个方法的返回值>.info("Hello, world!");

里面 <上面那个方法的返回值> 换成 <一个日志记录器>

<一个日志记录器>.info("Hello, world");

哦!这就很明白啦,我们使用了获得的日志记录器中的 info 方法。

?> 这个点是什么
. 在 Java 中表示「的」。把 <一个日志记录器>.info 翻译过来就是:一个日志记录器「的」info 方法。不是你的,不是我的,不是随便一个日志记录器的,而是那个日志记录器「的」。

再往下看。

info 方法后同样有一对 (),这也表示「调用」。那为什么 () 里面还放进了奇怪的东西呢?

这是参数info 方法在定义时就规定好了需要一个 String 类型的参数。也就是这个意思:「要我来帮忙?可以,但是,你要给我东西。」

info 只需要一个 String 就可以运行了。等等,哪里有 String 啊?

String 实际上是一个 Java 内置类型,简单说来就像论坛的元老们一样。内置类型和 Java 的关系很铁,Java 一直都认识它。String 用于描述一个字符串,那么按照 Java 规范,用引号 "" 夹在两侧就可以使得文字成为字符串。所以这里 "Hello, world!" 就是一个字符串。我们相当于提供了一个 Stringinfo。好啦,这样 info 就愉快地完成了交易,开始干活了。

对了!一个语句的末尾需要有分号,所以千万不要忘掉了分号哦!

重写标记

现在我们回过头看 @Override 注解。这是啥子意思?

@Override 表示「覆盖」。还是举蓝图的例子。我们复制了这份蓝图,可以在上面加自己喜欢的东西。但是我突然看到蓝图上面有一点画得不好,这可怎么办啊,要是能自己来画就好了。

Java 为我们提供了这一功能,它叫重写(Override)。重写的作用就是允许子类重新定义父类的方法。

要使用重写很简单:

  1. 在子类中编写相同的方法,访问修饰符要兼容,返回值要一样,名字(标识符)要一样,参数的顺序和类型也要一样。(要做修改,你要告诉我改哪里啊)
  2. 在方法的定义的前面,加上 @Override 注解。注解可以认为是「可以自己定义的关键字」。和关键字的功能差不多。@Override 注解表示「这里要重写函数了,各位,注意一下!」

至于 Java 到底是怎么帮我们「擦掉」原来的方法,又是怎么「画上」新的方法,我们不需要在意。这项工作是 Java 来完成的。

那为什么这里我们能够改写 onEnable 呢?你可能已经知道了——定义在 JavaPlugin 里面了嘛!

plugin.yml

plugin.yml 是一个数据文件,这也意味着它不是 Java 管理的,所以大家可以松一口气了。这种以 .yml 结尾的文件叫做 YAML 文件,设计它的目的是存储和描述数据。

YAML 像字典一样。左边是(Key),右边是(Value),中间用冒号和一个空格分开。

因此再来看我们的文件,不难理解了吧?

name: HelloWorld
main: rarityeg.helloworld.HelloWorld
version: 1.0
api-version: 1.16

Bukkit 在读取我们的插件时,会在这部字典里面查找 namemain 等信息。我们需要把这些告诉 Bukkit。因此我们填写了这些数据。

main 指向的是一个类,和上面 import 时一样需要详细的路径,因为 Bukkit 并不知道我们的家在哪里,所以我们要把地址给它。

version 指定了插件的版本,一个插件发布后可能有更新,Bukkit 依照这个来判断插件是否有更新的版本。

api-version 是 Bukkit API 的版本。这主要是为了兼容而设计的。我们的插件最终可能不是在最合适的版本中运行,其它版本的 Bukkit 会根据 api-version 决定如何加载这个插件。

战后总结

哇!这一节我们讲了好多东西!你学到了这些:

  • 什么是包,如何创建包
  • 什么是类,如何创建类
  • 什么是方法,如何定义方法
  • 什么是继承,如何进行继承
  • 什么是调用,如何进行调用
  • 什么是重写,如何进行重写
  • 什么是类路径,为什么需要类路径

当然,最最重要的,还是你的成果:一个插件!

我建议你把这里的代码删掉,重新自己敲一遍,看看你有没有真正理解上面的内容。

好啦,按照我的习惯,每一章结束时,我都要放一首歌,这次也不例外,请听一听,这会给你带来不一样的感受的……

<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width="100%" height="86" src="//music.163.com/outchain/player?type=2&id=430297476&auto=0&height=66"></iframe>