Java的隐藏功能

295

17
请注意,并不总是一个好主意使用这些隐藏的功能;往往会让阅读你代码的其他人感到惊讶和困惑。 - Kevin Bourrillion
1
你(/某人)应该像C#问题那样将答案整洁地总结在问题正文中。 - ripper234
100个回答

432

双括号初始化在几个月前我第一次发现时让我感到惊讶,以前从未听说过。

ThreadLocal通常不像锁那样广泛地被认知为存储每个线程状态的方法。

自JDK 1.5以来,Java已经拥有极其良好实现和强大的并发工具,除了锁之外,它们还存在于java.util.concurrent中,其中一个特别有趣的例子是java.util.concurrent.atomic子包,其中包含实现比较和交换操作的线程安全原语,并可以映射到这些操作的实际本机硬件支持版本。


40
双括号初始化……有点奇怪……我会谨慎地采用这种习惯用法,因为它实际上创建了对象的匿名子类,可能会导致混淆的equals/hashcode问题。 java.util.concurrent是一个真正伟大的包。 - MB.
6
我曾经教授过Java,但这是我第一次遇到这种语法……这说明你永远不能停止学习=8-) - Yuval
49
请注意,如果您保留对使用“双括号”语法(或我们称其为真实名称 - 带有初始化块的匿名类)初始化的集合的引用,您将隐式持有对外部对象的引用,这可能会导致严重的内存泄漏问题。我建议完全避免使用它。 - ddimitrov
51
“双括号初始化”是创建匿名内部类的一个非常委婉的称呼,掩盖了实际发生的情况,使内部类看起来像是旨在以这种方式使用。这是一种我希望保持隐藏的模式。 - erickson
11
实际上,这并不是一个静态块,而是一个“初始化块”,它与静态块有所不同,因为它在不同的时间执行(有关更多详细信息,请参见我在答案中提供的链接)。 - Boris Terzic
显示剩余11条评论

279

类型参数的联合变量:

public class Baz<T extends Foo & Bar> {}

例如,如果您想要接受既是Comparable类型又是Collection类型的参数:
public static <A, B extends Collection<A> & Comparable<B>>
boolean foo(B b1, B b2, A a) {
   return (b1.compareTo(b2) == 0) || b1.contains(a) || b2.contains(a);
}

这种人为的方法会在两个给定的集合相等或其中一个包含给定元素时返回 true,否则返回 false。需要注意的是,您可以在参数 b1 和 b2 上调用 Comparable 和 Collection 的方法。


我最喜欢使用它的场景是当你需要一个接受可追加字符序列(Appendable Charsequence)的方法。 - Neil Coffey
5
可以在那里使用“或”(OR)代替“&”吗? - mainstringargs
9
@Grasper: 不,该上下文中没有提供OR(不相交联合)。但是你可以使用一个不相交联合数据类型,例如Either<A, B>。这种类型是Pair<A, B>类型的对偶。请查看Functional Java库或Scala核心库,以获取这种数据类型的示例。 - Apocalisp
5
你应该说你只能扩展一个类和多个接口,而不是扩展一个类和一个接口,代码应该写成public class Baz<T extends Clazz & Interface1 & InterfaceI... 而不是 class Baz<T extends Clazz1 & ClazzI>。 - JohnJohnGa

219

前几天我被实例初始化程序惊到了。我删除了一些代码折叠方法,结果创建了多个实例初始化程序:

public class App {
    public App(String name) { System.out.println(name + "'s constructor called"); }

    static { System.out.println("static initializer called"); }

    { System.out.println("instance initializer called"); }

    static { System.out.println("static initializer2 called"); }

    { System.out.println("instance initializer2 called"); }

    public static void main( String[] args ) {
        new App("one");
        new App("two");
  }
}

执行main方法将显示:

static initializer called
static initializer2 called
instance initializer called
instance initializer2 called
one's constructor called
instance initializer called
instance initializer2 called
two's constructor called

如果你有多个构造函数并且需要共同的代码,则这些可能会非常有用。

它们还为初始化类提供了语法糖:

List<Integer> numbers = new ArrayList<Integer>(){{ add(1); add(2); }};

Map<String,String> codes = new HashMap<String,String>(){{ 
  put("1","one"); 
  put("2","two");
}};

38
相比需要手动调用的显式方法,这种方法的优点在于,如果有人后来添加了构造函数,他们不需要记得调用init();这将自动完成。这可以防止未来程序员产生错误。 - Mr. Shiny and New 安宇
40
与init()方法不同的是,它可以初始化final字段。 - Darron
2
如果您扩展该类并实例化各种子类,那么它是否仍会以相同的方式运作? - Kieran Senior
12
我敢打赌,“24小时学Java”这本书也有这个“显而易见的特性”。更多人应该去阅读它 :) - Özgür
显示剩余4条评论

201

JDK 1.6_07+ 包含一个名为VisualVM (bin/jvisualvm.exe)的应用程序,它是许多工具的良好GUI。它似乎比JConsole更全面。


它有一个插件(它是一个 NetBeans 工具),允许它使用 Jconsole 插件。非常不错。 - Thorbjørn Ravn Andersen
VisualVM是自面包切片以来最好的东西,真的!可惜它在针对JDK 1.5运行时并没有所有功能。 - sandos
我发现在某些情况下,VisualVM 运行缓慢或无法使用。商业软件 YourKit 没有这个问题,但不幸的是它不是免费的。 - Roalt
Visual VM的更新版本,从JDK 1.6.0_22及更高版本,有了很大的改进。我敢打赌JDK1.7会有更好的版本。 - djangofan

173

156

对于我面试的大多数Java开发人员来说,标记块都很令人惊讶。以下是一个示例:

// code goes here

getmeout:{
    for (int i = 0; i < N; ++i) {
        for (int j = i; j < N; ++j) {
            for (int k = j; k < N; ++k) {
                //do something here
                break getmeout;
            }
        }
    }
}

谁说在Java中goto只是一个关键字?:)


31
在某些情况下,在嵌套的循环结构中,继续进行下一次外循环迭代可能是有用的。这将是此功能的合理使用。 - alasdairg
75
许多程序员并不知道(也许这样更好),你实际上可以对任何旧的代码块进行标记并退出。它不必是循环 - 你可以定义一些任意的代码块,给它一个标签,并使用 break 语句来退出它。 - Neil Coffey
27
这并不是一个goto,它只能返回到先前的迭代(即:您无法向前跳转)。这与迭代返回false时发生的机制相同。说Java有goto就像说任何编译器生成JUMP指令的语言都有goto语句。 - Zombies
2
我想我唯一会使用这个结构的时候是在非常紧急的情况下,将汇编代码翻译成Java。 - RMorrisey
4
因此,你们面试的基础是Java知识问答而不是解决问题和思考设计的能力。我会确保永远不会参加你们公司的面试 :) - Javid Jamae
显示剩余13条评论

144

从JDK 1.5开始就已经实施的协变返回类型怎么样?虽然这是一个不太引人注目的补充,但据我所知,它对于泛型的使用绝对必要。

本质上,编译器现在允许子类将重写方法的返回类型缩小为原始方法返回类型的子类。 因此,下面的内容是被允许的:

class Souper {
    Collection<String> values() {
        ...
    }
}

class ThreadSafeSortedSub extends Souper {
    @Override
    ConcurrentSkipListSet<String> values() {
        ...
    }
}

你可以调用子类的values方法,并获得一个排序的线程安全Set,其中包含String无需将其向下转换ConcurrentSkipListSet


你能提供一个使用示例吗? - Allain Lalonde
24
我经常使用这个。clone()是一个很好的例子。它应该返回Object类型,这意味着你需要写类似于(List)list.clone()这样的代码来转换类型。但是如果你声明成List clone(){...},那么就不需要进行类型转换。 - Jason Cohen

142

在 finally 块中转移控制权会丢弃任何异常。以下代码不会抛出 RuntimeException -- 它会被丢弃。

public static void doSomething() {
    try {
      //Normally you would have code that doesn't explicitly appear 
      //to throw exceptions so it would be harder to see the problem.
      throw new RuntimeException();
    } finally {
      return;
    }
  }

来自http://jamesjava.blogspot.com/2006/03/dont-return-in-finally-clause.html

在Java中,finally语句块是无论try和catch语句块是否抛出异常都会执行的。然而,在finally块中使用return语句可能会导致一些奇怪的问题。因为如果在try或catch块中存在return语句,它将覆盖finally块中的返回值,从而可能导致不可预测的行为。

一个更好的解决方案是在finally块之前定义一个变量,并将其设置为要返回的值。然后在finally块中不要使用return语句,而是通过变量返回值。


7
虽然有些不好,但这也是finally如何工作的逻辑必然产生的结果。try/catch/finally流程控制做了它应该做的事情,但只在一定限度内有效。同样地,在catch/finally块内部造成异常也要小心,否则就会丢失原始异常。如果在try块内执行System.exit(),finally块将不会被调用。如果打破正常流程,那就打破了正常流程... - Neil Coffey
不要使用 finally { return; },而是只使用 finally{没有返回的代码}。这是合乎逻辑的,因为 finally 也旨在在发生异常时执行,并且作为异常处理程序的返回的唯一可能含义必须是忽略异常并 - 的确 - 返回。 - extraneon
21
这似乎更像是一种“陷阱”,而不是一个隐藏的功能,虽然它也可以被用作之一,但使用这种方法并不是一个好主意。 - davenpcj
如果你这样做,Eclipse会发出警告。我很了解Eclipse。如果你想捕获一个异常...那就捕获一个异常。 - helios
这就是你为那些从方法中间返回的脏代码所付出的代价。但仍然是一个不错的例子。 - Rostislav Matl
@Neil Coffey:如果你在finally块中引发了一个异常,但是又捕获了它,我想这应该没有问题吧? - Bart van Heukelom

140

还没有看到有人提到 instanceof 被实现成不需要检查 null 的方式。

而是:

if( null != aObject && aObject instanceof String )
{
    ...
}

只需使用:

if( aObject instanceof String )
{
    ...
}

9
很遗憾,这是一个不太为人知的功能。我看到很多与第一个代码块相似的代码。 - Peter Dolberg
4
这是因为Java有一种特殊的(隐式)空类型,您既看不到它,也无法引用它。 - Nick Hristov
更糟糕的是在C/C++中,在释放内存之前检查是否为空。这是一个如此基本的概念。 - Thomas Eding

134

让枚举中包含方法和构造函数让我感到惊讶。例如:

enum Cats {
  FELIX(2), SHEEBA(3), RUFUS(7);

  private int mAge;
  Cats(int age) {
    mAge = age;
  }
  public int getAge() {
    return mAge;
   }
}

您甚至可以使用“常量专用类体”,让特定的枚举值重写方法。

更多文档请参见这里


20
实际上,这个功能非常棒 - 它使枚举成为面向对象,并以非常干净的方式解决了许多初始化问题。 - Bill K
5
枚举作为单例?只有一个值的枚举? - Georgy Bolyuba
6
单例模式是对象的一个实例,而不是具有一个值的对象。 - dshaw
20
@Georgy: 参见Joshua Bloch的《Effective Java(第二版)》中的第3条; "虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例模式的最佳方式。" - Jonik
3
小小的建议:mAge 应该是 final 的。在枚举类型中,很少有非 final 的字段是必要的。 - Joachim Sauer
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接