Java有很多十分有用但不太为人知道的语法
只要稍微学过JavaSE便可以轻松读懂本文内容,并且在实战用派上用场
写一个内部类
通过生成一个和外部类的实例保持关联的实例,使得外部类的实例与内部类的实例之间,能够保持一种【类-实例】似的关系
public class Outer {
public class Inner {
}
}
如上写了一个内部类的话,可以像下面一样生成实例
Outer o = new Outer();
Inner i = o.new Inner();
在new内部类的实例之前,先生成一个外部类的实例
虽然如果用this代替o来传值的话,就要写成this.new这样的形式,但是this.是可以被省略的,所以用Outer类的方法来new一个Inner类的实例的情况,可以省略this.new,单纯用new Inner()就行
public class Outer {
public class Inner {
public class Inner2 {
public class Inner3 {
}
}
}
public void echo() {
Inner i = new Inner(); //省略this.new直接写new
Inner.Inner2 i2 = i.new Inner2();
Inner.Inner2.Inner3 i3 = i2.new Inner3();
}
}
当然了,多层生成也是非常实用的 (连续用.连接的感觉很爽吧)
new Outer().new Inner().new Inner2().new Inner3();
不写main方法来执行程序
因为在main方法被调用之前,class会先被初始化,在那个过程之中写入代码的话,就能够实现没有main方法的情况下执行程序
public class Starter {
static {
System.out.println("Hello!");
}
}
在这个例子中使用了最传统的锁定static初始化方式
这也是short coding或者code golf的选手最常用的手法之一,在命令行中我们可以如下一样执行:
C:\hoge>java Starter
Hello!
Exception in thread “main” java.lang.NoSuchMethodError: main
Hello!被输出在初始化类的阶段,在那之后的NoSuchMethodError中被提示不存在main方法
我们还能如下一样调用static的方法来执行程序
public class Starter {
static int i = echo();
private static int echo() {
System.out.println("Hello!");
return 0;
}
}
System.out.println方法
System.out可以用System.setOut()方法来替换
写一个继承java.io.PrintStream的类再稍微调整一下的话,就能愉快地使用System.out.println()啦!(比如添加一个有趣的语尾)
注释
我们都知道可以用//来注释一行内容。通过在注释中放置unicode-escape过的换行符,可以让代码伪装在注释中
比如下面这段代码:
public class Test {
public static void main(String[] args) {
int i = 5;
// #start unsafe# http://java.sun.com\u000a\u002f\u002a
int *ip = &i;
*ip += 3;
i = *ip;
// #end unsafe# http://java.sun.com\u002a\u002fi+=\u0033\u003b
System.out.println(i);
}
}
声明
变量的声明可以用过注解来实现。首先,我们在@Target里写上包含LOCAL_VARIABLE的注解
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Target(ElementType.LOCAL_VARIABLE)
public @interface Echo {
}
这样的话,我们就能在注解中声明变量啦 *1
@Echo Echo Echo = Echo();
其次,我们可以通过使用transient关键字和volatile关键字,来吓一吓不是很熟悉语法的人
不过用泛型的效果会更加好一些
public class Echo {
Echo Echo;
}
注意,这里的Echo并不是Echo型的,而是Echo泛型变量~
编译程序
java的命令有几个增添的option
如果在option中使用-g:none,那么debug的信息便不会被输出,stacktrace中的行号也会被变成unknown
而option里的-processor是关联着注解处理的
执行程序
同样地,在执行命令中也有几个很方便的option
例如-D可以调试系统属性,推荐改写path.separator
-javaagent也是有趣的一个option
数字的表现
在java的数字表现中,8进制是比较推荐使用的
System.out.println(012);
结果:
10
没有使用特别的符号,因此比较被难以察觉,能够让读代码的人多思考代码的含义
四则运算
希望记住的是Double.NaN
这是double类型的其中一种,作用是声明不是一种数字,在0.0/0.0这样的运算中发生
同样地,NaN和其他的值产生的运算结果也是NaN,在处理过程中添加NaN的话,便可以避开复杂的运算得到人为可见的结果
同时,NaN==NaN的情况下,结果是false也是一个需要注意的地方,在某个Java问答中,有人就是利用了这个特性出了下面这个题目:
问题29:循环大佬的花嫁(Bride of Looper)
请将下列循环通过声明i变成无限循环
while(i != i) {
}
增值和减值操作符
下列代码可能招致完型崩坏(semantic satiation)
int i = 0;
i = i++ + ++i;
System.out.println(i);
字符串和Object之间的加号会自动运用对象的toString方法
在Eclipse这些IDE的Debuger中确认变量的值的时候,也是用这个,因此适当地随便override一下会让debug更能沉淀时间
在Java5之后使用数组的机会少了很多,但是依然存在着用可变长参数的机会
public class Hoe<E> {
private Class<E> type;
public Hoe(E... e) {
@SuppressWarnings("unchecked")
Class<E> type = (Class<E>) e.getClass().getComponentType();
this.type = type;
}
public Class<E> getType() {
return type;
}
}
这样的话,便能获得到具体的泛型
比如:
Hoe<Fuga> hoe = new Hoe<Fuga>();
之中,hoe.getType()
便是Fuga.class(好用吧)
这个方法是通过在构造器中放置一个长度可变的参数,在调用new Hoe()的时候传递一个大小位0的Fuga[]给构造器,再从那之中获得具体的泛型类型
稍微修改Object#hashCode()和Object#equals可以让标准API的java.util.HashMap产生一些奇妙反应
java.lang.Comparable或者java.util.Comparator中,如果使得不等价的对象返回0时,可以消灭java.util.TreeMap的put进去的对象
if
通过在条件式中运用逻辑互斥或^(XOR)来让代码更加有高级感
if (a == null ^ b == null) {
throw new NullPointerException();
}
for
在控制中知名度比较低的应该属于标签(label)了吧?在for和while之中我们都会使用break和countinue,但是在多重循环中,我们应该学会控制某个单独的循环
yLoop: for (int y=0; y<height; y++) {
xLoop: for (int x=0; x<width; x++) {
if (map[x][y] == Type.BOMB) {
break yLoop;
}
}
}
正好可以通过标签+注释的方式,让代码看着像URL,有整洁感
http://example.jp/
for (int i=0; i<size; i++) {
}
break并不是只能在循环体中使用:
hoge: {
piyo: {
break hoge;
}
}
多使用strictfp关键字
用synchronized关键字的话比较土避开比较好
在finally之中用return和throw之类提高速度的关键字
通过java.lang.reflect包,可以参照动态的变量以及调用动态的方法
setAccessible方法甚至可以在外部调用private方法
通过自己写一个类加载器,可以在执行时更换类的内容
例如defineClass方法可以从byte数组中生成类,重写这个方法可以给byte数组里面加一些料
*1: 通常,Java的命名规范里变量通常是小写字母开始,在这里故意写成大写的,便能实现注释/类型/变量名/构造器名四个Echo拥有各自的功能
*2 Java5以后的泛型比起数组更常用List接口,长度可变的形参传递依然是在数组中实现所以用起来很麻烦