Java是一种非常成熟的编程语言 - 事实上,它已经走过21年了,如果它是一个人,它可以在美国随便混!随着年龄的增长,智慧也在增长,而至少有时候,有些东西会变得很怪异。在本文中,我将介绍Java语言的一些奇技淫巧的行为和特性。
在这里,没有特别的顺序去介绍一系列Java的奇技淫巧,仅供娱乐,或者你向朋友们推介它吧。
Java有goto和const关键字
虽然Java没有goto,但它确实作为保留关键字。const也是这样。这意味着您无法使用这两个名称定义变量:
int goto = 0;
int const = 0;
这样的定义是非法的,无法正常编译!
用“_”符号定义数字
Java允许您使用“_”符号定义数字。因此,您可以像这样编写数值:
int thousand = 1_000;
double bigValue = 1_000_000.456_555;
long thisIsSilly = 3______4__3;
Double.MIN_VALUE的值是我们无法设定的
为了展示Double.MAX_VALUE结果比预期的效果要完美,提供以下这样的数值:1.7976931348623157E308。您认为Double.MIN_VALUE会为您带来什么?4.9E-324!好,执行开始 – 结果这个值大于0!
Double.MIN_VALUE是返回大于0的最小Double值(最小正数)。如果您想要最小的Double值,则需要使用:-Double.MAX_VALUE。他们实际上可以更好地命名这些东西,我想知道这样的做法引起了多少人为的错误!
有趣的整数相等问题
谈到这个错误......让我告诉你一些迷惑不解的事情:
Integer ten = Integer.parseInt("10");
System.out.println(ten == Integer.valueOf(10));
//this is true
Integer thousand = Integer.parseInt("1000");
System.out.println(thousand == Integer.valueOf(1000));
//this is false
Integer对象的缓存值的大小范围是在[-128 127]区间。这意味着当我们在此数值范围内操作时,“==”比较能正常返回结果。但当数值不在此范围,对象相等的比较是否正常返回结果就很难说了!
想象一下,你可以编写单元测试,且一切都正常运行,只要你没有使用足够大的数字,但这可能会导致严重的错误,所以为了安全 - 提醒:当你经常使用对象数值比较相等时,请使用“.equals()”,而不是依赖于“==”比较相等,除非你非常肯定这种做法没有错误。
反射可以(大多数情况)做任何事情
这不应该作为一个Java奇技淫巧的内容,但通过反射,你可以重写final值(大多数时间)并可访问私有字段......但不一定经常是这样。
在我写How to write horrible Java的文章时,当我重写final值得时候,我发现了无法与预期结果一致的问题。Java中的常量,当final被内联时(通指内联函数),即使你的代码看起来可以正常运行—没有数值情况也会改变,这太不可思议了(查看我的文章了解详情和Stack Overflow答案)。
如果你需要,这是重写final的数值代码例子:
public static void notSoFinal() throws NoSuchFieldException, IllegalAccessException, InterruptedException {
ExampleClass example = new ExampleClass(10);
System.out.println("Final value was: "+ example.finalValue);
Field f = example.getClass().getDeclaredField("finalValue");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.setInt(example, 77);
System.out.println("Final value was: "+ example.finalValue);
}
你知道Java标签是什么玩意吗?
好吧,我们离开上面调皮的话题,回到Java正规上来。你知不知道Java循环标签?看一下这个循环语句:
outerLoop:
while (true) {
System.out.println("I'm the outer loop");
while(true){
System.out.println("I am the inner loop");
break outerLoop;
}
}
使用标签可以让你在处理嵌套循环的时候,继续或中断一个特定的循环……在不同语言环境下,这个有点类似goto,
现在让我们编写一个编译好了的,形迹可疑的代码:
int i = 3;
http://www.e4developer.com
while(i > 0){
System.out.println("http://www.e4developer.com");
i--;
}
可以正常编译并且正常运行,因为它只是一个附加注释的标记为http的循环。这会让那些不熟悉标签的人,使得它变得特别有趣!
枚举类
好吧,你可能知道这一点,但我还是想重复提一下。枚举是具有有限数量实例的特殊类。这意味着枚举可以:
实现接口
具有构造函数
实现不同方法
对于Scott Logic博客,我写了一篇名为 Java Enums – how to use them smarter 的文章,我展示了一些其他的使用方法。
For循环的灵活性
循环的标准,我相信你使用它们的次数,比你记忆中的还要多:
for(int i = 0; i < 100; i++){
//...
}
您知不知道里面所有条件是可选的吗?你不需要初始化一个变量,你也不需要一个条件停止,你也不需要增加任何东西......如果省略所有内容,你最终会得到一个有趣的无限循环语法:
for(;;){
//Infinite loop!
}
Java有初始化程序......以防万一...
这是一个非常通用的特性,但是我还是依然会遇到一些具有经验的Java开发人员,他们并不会真正意识到它的存在。在Java中,您可以写类加载(静态初始化程序)或构造函数(标准初始化程序)之前运行的代码块。它是这样的。
标准初始化程序:
int sum = 0;
{
for(int i = 0; i < 1; i++){
sum += 1;
}
}
静态初始化程序:
static double value = 0;
static {
for(int i = 0; i < 1; i++){
value += 1;
}
}
只需要将这些代码块放在类中,不要放在任何方法或构造函数中。
双括号初始化集合
关于初始化的话题,我会向你展示Java初始化集合的奇技淫巧:
Map<String, String> map = new HashMap<String, String>() {{
put("it", "really");
put("works", "!");
}};
Set<String> set = new HashSet<String>() {{
add("It");
add("works");
add("with");
add("other");
add("collections");
add("too");
}};
它在Java中被称为双括号初始化,我从未见过这样的写法,被任何人使用过……难道是因为没有人知道使用它吗?
在发表这篇文章之后,很多读者很快告诉我,这是我们应该避免的一个危险行为!比如应当使用辅助方法List.of()代替。(注意,双括号初始化会派生匿名类。派生的类this可以指向外部的类。通常这不是什么大问题,但在某些情况下使用不当会引起悲剧的问题,例如在序列化或垃圾回收时,应当注意这个问题)
Final值的可以放在后面初始化
这是一个小事情,但有些人认为,你在定义它们时时候,必须初始化常量值。实际情况不是这样,您只需要初始化它们一次就够了,你可以使用以下有效代码核对一下:
final int a;
if(someCondition){
a = 1;
} else {
a = 2;
}
System.out.println(a);
当我们混合初始化代码块和其他构造函数时,这么做会变得棘手。
泛型扩展的桥接
尽管存在可疑的实现(类型擦除),但泛型在Java中还是非常强大。我吃惊的是,我们可以允许定义我们需要的泛型类型。看看这个例子:
public class SomeClass<T extends ClassA & InterfaceB & InterfaceC>
{}
你特别需要注意你定义的T,这特性会对你非常有用!
你还有更多吗?
我希望你喜欢我分享的Java奇技淫巧。如果你还知道其他的Java特性和行为,并值得分享,请务必在评论或Twitter上告诉我!
原文:https://www.e4developer.com/2018/10/28/java-surprises-unexpected-behaviours-and-features/