CompileMind

compile code, compile mind

0%

距离上一次博文过了接近一个月了,这期间毕设虽忙,但不至于忙到连博文都写不了的地步。早就在寻思着什么时候更一篇文章,但是就是懒,好像每天忙完毕设的事情之后,就感觉自己今天做完一件事,就可以休息了,就可以放纵自己了。其实不过都是借口罢了。趁着这个时间,写写东西,回顾一下写过的东西,觉得写的东西还是太分散了,或者说,目前为止都没有一个明确的写作目标,这里也表明一下,顺便也为今后的写作约束一下自己。

枚举的简单使用

在java中,我们可以使用enum关键字来定义枚举:

1
2
3
public enum Color {
RED, GREEN, BLUE;
}

就像上面一样,我们定义了一个名为Color的枚举类,包含了RED、GREEN、BLUE三个常量。当我们使用枚举类的时候,直接通过枚举类名.枚举常量即可。就像如下的形式:

1
2
3
4
5
6
7
8
9
10
void f(Color c) {
switch (c) {
case RED: System.out.println("It's RED"); break;
case GREEN: System.out.println("It's GREEN"); break;
default: System.out.println("It's BLUE");
}
}
// 调用
f(Color.RED);
// 输出 "It's RED"

枚举的本质

虽然Java提供枚举类的定义,但是实际上他并不是Java中一个新的对象类型,我们通过对Color枚举类进行反编译,得到如下的反编译结果:

1
2
3
4
5
6
7
8
9
10
11
$ javap Color.class 
// 反编译结果
Compiled from "Color.java"
public final class EnumerationAndAnnotation.Color extends java.lang.Enum<EnumerationAndAnnotation.Color> {
public static final EnumerationAndAnnotation.Color RED;
public static final EnumerationAndAnnotation.Color GREEN;
public static final EnumerationAndAnnotation.Color BLUE;
public static EnumerationAndAnnotation.Color[] values();
public static EnumerationAndAnnotation.Color valueOf(java.lang.String);
static {};
}

我们可以看到,枚举类实际上在编译的过程中,被编译器进行调整,它并不是一个新的类型,本质上依然是一个类(Color),这个类继承了java.lang.Enum,而对于每一个枚举常量,实际上是public static final修饰的枚举类的静态实例对象。

同时注意,编译器会为我们添加两个新的static方法:values() 和 valueOf(java.lang.String),其实分别作用是返回枚举类中定义的所有的枚举常量,以及根据枚举名来获取枚举常量(注意,这里就是定义枚举常量的枚举名)。

当然,由于每一个枚举常量实际上是实现了java.lang.Enum的枚举类的一个静态实例对象,而这个过程是编译器为我们进行的,所以,自然,我们可以在枚举类中定义任何的方法、变量,以及构造函数的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public enum Color {
RED("RED"), GREEN("GREEN"), BLUE("BLUE");

// 定义类中的实例变量
private String colorName;
// 定义构造函数,注意上面的枚举常量必须满足这种构造方式
Color(String colorName) {
this.colorName = colorName;
}
// 定义实例对象的方法
public void printColorName() {
System.out.println("Color name is " + colorName);
}
// 重写toString方法
@Override
public String toString() {
return colorName;
}
}

自然,我们可以枚举常量当作一个枚举的实例化对象,调用枚举类中的方法:

1
2
3
4
public static void main(String[] args) {
System.out.println(Color.RED); // RED
Color.RED.printColorName(); // Color name is RED
}

为什么枚举构造器不能访问枚举的静态域

查看下面这段代码:

1
2
3
4
5
6
7
public enum Color {
RED, GREEN, BLUE;
private static int value = 0;
Color() {
System.out.println(value); // 编译错误!构造器无法访问静态变量
}
}

我们可以看到这里编译不通过,提示我们枚举构造器不能够访问枚举的静态域(以及静态变量)。我们知道,一般的类中,静态域以及静态变量是优于实例对象的变量、方法的初始化的。这里简要复习一下类中变量的加载机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 辅助静态变量的初始化
class Init {
public Init(String init) {
System.out.println("Init()" + init);
}
}
class Fa {
// 父类静态变量
static final Init initFa = new Init("Fa's static");
// 父类实例变量
Init initFa2 = new Init("Fa's no static");
// 父类静态域
static {
System.out.println("Fa's static");
}
// 父类构造方法
Fa() {
System.out.println("Fa()");
}
}
class Su extends Fa {
// 子类静态域
static {
System.out.println("Su's static");
}
// 子类静态变量
static final Init initSu = new Init("Su's static");
// 子类实例变量
Init initSu2 = new Init("Su's no static");
// 子类构造函数
Su() {
System.out.println("Su()");
}
}

接下来,我们new出Su实例对象,并观察输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
new Su();
}
/* 输出
Init()Fa's static // 1
Fa's static // 2
Su's static // 3
Init()Su's static // 4
Init()Fa's no static// 5
Fa() // 6
Init()Su's no static// 7
Su() // 8
*/

我们可以看到,有static修饰的始终优于实例对象的相关的初始化的,在输出中 1 - 4 是static修饰部分,5 - 8是实例域部分。此外,在继承情形下,父类由于子,输出中 1- 2 是父类static域的初始化,3 - 4 是子类static域的初始化。在static域加载完成之后,才开始加载父类非static域,最后加载子类的非static域。注意,都为static修饰的情况下,加载顺序根绝定义时候的顺序而来,1、2与3、4就可以看出。

看到这里,也许会有点疑问,既然静态域加载优于实例域(包含构造函数),那为什么在枚举类中就不行呢?让我们回到前面对枚举类的反编译,其实答案就出来了。反编译的过程我们可以看到,我们的枚举常量实际上是我们枚举类的静态实例化对象,在编译器的修改下,我们运行加载枚举类的过程中,枚举常量是static修饰的,其他静态域也是static修饰的,枚举常量又排在其他静态域的前面,按照上面的额初始化顺序,首先就会调用构造器实例化枚举常量对象,此时,枚举类中的其他静态域都还没来得及初始化,自然在构造函数中不能访问静态域了。有人可能想说,那我静态域放到枚举常量前面,让他先加载怎么样?很遗憾,Java不允许这样做:

1
2
3
4
public enum Color { // 编译不通过!!!
private static int value = 0;
RED, GREEN, BLUE;
}

使用抽象函数为枚举统一方法

上面的内容探讨了枚举类的一些基础,这里提一些关于使用枚举的代码策略设计。
有的时候,我们想要给枚举常量定义某一些通用的方法,同时,针对不同的枚举,该通用方法呈现不同的具体内容。例如,我现在有一个如下的Color枚举类,当针对不同的Color常量的时候,能有一个方式给我返回该颜色的十六进制颜色码。也许你会如下来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
public enum Color {
RED, GREEN, BLUE;
public String getHexCode() {
switch (this) {
case RED:
return "#FF0000";
case GREEN:
return "#00FF00";
default:
return "#0000FF";
}
}
}

以上的方式较为简洁与易读,但是存在问题:枚举常量越来越多,case会不断增长,如果编写代码的过程由于不注意,增加颜色常量的时候,忘记了增加对应的case,那么编译是不会有任何的问题的,但是却隐含的将增加的颜色常量也返回的是BLUE的十六进制颜色码。

鉴于上述的问题,我们需要某种方式来防止我们犯错,能够想到的,就是通过编译器来告诉我们。于是,我们在枚举类中定一个抽象方法getHexCode,于是乎,对于每一个枚举常量,编译器会提示我们实现具体实例的getHexCode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum Color {
RED {
@Override
public String getHexCode() {
return "#FF0000";
}
}, GREEN {
@Override
public String getHexCode() {
return "#00FF00";
}
}, BLUE {
@Override
public String getHexCode() {
return "#0000FF";
}
};

abstract public String getHexCode();
}

在这样的定义下,代码牺牲了一定的简洁性,但是易读性丝毫不输于最开始的方式。针对Color枚举类,我们定义了抽象方法,表明了对于Color中的每一个枚举常量,都应该有getHexCode方法,返回自己的十六进制颜色码。如果我们新添加了枚举常量,而没有实现该方法,编译器会报错警告我们。

如果没有泛型

学习Java,必不可少的一个过程就是需要掌握泛型。泛型起源于JDK1.5,为什么我们要使用泛型呢?泛型可以使编译器知道一个对象的限定类型是什么,这样编译器就可以在一个高的程度上验证这个类型消除了强制类型转换,使得代码可读性好,而这个过程是发生在编译时期的,即在编译时期发现代码中类型转换的错误所在,及时发现,而不必等到运行时期抛出运行时期的类型转换异常。

初探Scala 特质trait

在Scala中,trait(特质)关键字有着举足轻重的作用。就像在Java中一样,我们只能在Scala中通过extends进行单一继承,但trait可以让我们从语义上实现了多重继承。通过对继承的类混入(mixin)多个特质,来达到多重继承的目的。乍一看,trait和Java中的interface接口很像,但是,细节上它们又有着大不同。

在Centos7版本之后,防火墙应用已经由从前的iptables转变为firewall这款应用了。但是,当今绝大多数的Linux版本(特别是企业中)还是使用的6.x以下的Centos版本,所以对iptables的了解还是很有必要的。此外,需要说明的是iptables自身并不具备防火墙的功能,它需要通过内核netfilter(网络过滤器)来实现,与firewalld一样,他们的作用都是用于维护规则,而真正使用规则干活的是内核的netfilter,只不过firewalld和iptables的结构以及使用方法不一样,他们都只是一个外壳应用罢了。打个比方,就好像有一本书,同样的内容,一种是纸质的,另一种是电子的,我们翻阅它的方式不同,给它做笔记的方式不同,但是内容(内核)一样。

早些时间心血来潮买过一个树莓派,但是当时只是玩一玩,买来按照网上的教程摆弄了一下就闲置了。最近毕业设计,做时序数据分析的相关的工作,刚好想起能够用到树莓派+Node-RED来生成模拟的时序数据。于是开始搭建相关的环境。特此记录一下。

树莓派搭建Node-RED环境

树莓派自2015年开始是默认就带NODE-RED的,但是如今已是2018年:)自带的版本已经很老了,可通过下面的命令进行自动升级NODE.JS和NODE-RED

在Linux下一切都是文件,无论是设备还是接口,亦或是网卡等均被抽象成了文件,并且有相关的内核代码进行调度。然而,在一切都是文件的前提下,最需要进行探讨的则是文件存储的根源:文件系统。文件系统的好坏能够更加完美的解决在一个操作系统中对于文件的管理。

如今我们大多数情况都会使用IDE来进行Java项目的开发,而一个如今众多优秀的IDE已经能够帮助我们自动的部署并调试运行我们的Java程序。然而在早期我们进行Java开始需要手动的建立逻辑包(package)与目录来管理我们的Java项目或是更高级一点的则是使用ant这样的构建工具。作为Javaer,对于Java的编译过程应当是熟悉的,这样即使脱离了IDE我们依然能够很好的理解Java的构建过程。

macOS下的Java与JDK的路径曾经困扰过我一段时间,今天稍有些忘记,故记下笔记,整理一下。Java与JDK的关系不在本文笔记之内,Javaer常识。