Java的隐藏功能

295

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

10

当你不需要StringBuilder中包含的同步管理时,请使用StringBuilder而不是StringBuffer。这将提高应用程序的性能。

对于Java 7的改进甚至比任何隐藏的Java特性都要好:

不要在实例化时使用那些无限的<>语法:

Map<String, List<String>> anagrams = new HashMap<String, List<String>>();

// Can now be replaced with this:

Map<String, List<String>> anagrams = new HashMap<>();
  • 使用switch中的字符串: 链接

在switch语句中应该使用字符串,而不是旧式的C int类型。

String s = "something";
switch(s) {
 case "quux":
    processQuux(s);
    // fall-through

  case "foo":
  case "bar":
    processFooOrBar(s);
    break;

  case "baz":
     processBaz(s);
    // fall-through

  default:
    processDefault(s);
    break;
}

这是旧代码:

static void copy(String src, String dest) throws IOException {
    InputStream in = new FileInputStream(src);
    try {
        OutputStream out = new FileOutputStream(dest);
        try {
            byte[] buf = new byte[8 * 1024];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}

现在可以用这个更简单的代码来代替它:

static void copy(String src, String dest) throws IOException {
    try (InputStream in = new FileInputStream(src);
            OutputStream out = new FileOutputStream(dest)) {
        byte[] buf = new byte[8192];
        int n;
        while ((n = in.read(buf)) >= 0)
            out.write(buf, 0, n);
    }
}

4
你是不是想用 StringBuilder 而不是 StringWriterStringBufferStringBuilder 的 API 相同,但使用 StringWriter 需要进行一些代码更改。 - pjp
2
他实际上是指StringBuilder,它不是线程安全的,但速度更快。StringBuffer是线程安全的,但速度较慢。只有在构建字符串缓冲区时需要线程安全性时才应使用它。 - Archer

10

"const"是一个关键字,但你不能使用它。

注:该段内容已翻译为中文并保留了HTML标签,不包含解释。
int const = 1;   // "not a statement"
const int i = 1; // "illegal start of expression"

我猜编译器的作者认为它可能在未来被使用,所以最好保留它。


1
不算是“功能”,但肯定是“隐藏的”。 - Michael Myers
不仅仅是一个非功能性特性,而是一个反功能性特性!(我也没有提到goto是一样的——保留但未实现)。 - Michael Myers
这是为编译器保留的,以创建更好的错误消息。 - Paŭlo Ebermann
“goto”也是一个关键字,你不能使用。 ;) - Peter Lawrey
1
@Peter Lawrey: 我知道,但它已经发布了 - Michael Myers

9

标识符可以包含像umlauts这样的外语字符:

不必写成:

String title="";

有人可能会写:

String Überschrift="";

1
你可以像这样访问它:\u00dcberschrift = "OK"; - user85421
8
在我看来,这不是一个好的风格。如果你曾经不得不使用一些用你不理解的语言编写的代码和注释,你就会知道我的意思了。代码不应该进行本地化处理。 - Werner Lehmann
问题不在于在任何实际规模的项目中都会有使用不同语言的开发人员......问题在于在任何实际规模的项目中,都必须混合使用Windows和Unx(包括OS X)机器以及使用不同设置的不同IDE。再加上.java*文本文件没有描述其文件编码的元数据或良好的规范(规范允许任何字符编码),你就会得到灾难的配方。在字符串或标识符中使用非ASCII字符的Java程序员应该被枪毙。 - SyntaxT3rr0r
1
非ASCII字符串应该外部化,并配置构建脚本和/或预提交验证器以在检测到这种无知的胡言乱语时立即失败。我们这里的Ant构建脚本将失败,并将任何试图这样做的开发人员置于耻辱之墙上。 - SyntaxT3rr0r

9

我可以添加Scanner对象。它是最适合解析的工具。

String input = "1 fish 2 fish red fish blue fish";
Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");
System.out.println(s.nextInt());
System.out.println(s.nextInt());
System.out.println(s.next());
System.out.println(s.next());
s.close();

4
我认为这不是一个隐藏功能。Scanner 是一个库。 - Shervin Asgari

9
一种优化技巧,使您的代码更易于维护,同时减少并发错误的可能性。
public class Slow {
  /** Loop counter; initialized to 0. */
  private long i;

  public static void main( String args[] ) {
    Slow slow = new Slow();

    slow.run();
  }

  private void run() {
    while( i++ < 10000000000L )
      ;
  }
}

$ time java Slow
真实时间 0分15.397秒
$ time java Slow
真实时间 0分20.012秒
$ time java Slow
真实时间 0分18.645秒

平均时间: 18.018秒

public class Fast {
  /** Loop counter; initialized to 0. */
  private long i;

  public static void main( String args[] ) {
    Fast fast = new Fast();

    fast.run();
  }

  private void run() {
    long i = getI();

    while( i++ < 10000000000L )
      ;

    setI( i );
  }

  private long setI( long i ) {
    this.i = i;
  }

  private long getI() {
    return this.i;
  }
}

$ time java Fast
real 0m12.003s
$ time java Fast
real 0m9.840s
$ time java Fast
real 0m9.686s

平均时间:10.509秒

引用类作用域变量需要更多的字节码,而方法作用域变量则不然。在关键循环之前添加方法调用几乎不会增加额外开销(并且编译器可能会将调用内联)。

这种技术(始终使用访问器)的另一个优点是它消除了Slow类中的潜在错误。如果第二个线程不断将 的值重置为0(例如通过调用slow.setI(0)),那么Slow类永远无法结束其循环。调用访问器并使用本地变量消除了这种可能性。

在Linux 2.6.27-14上使用J2SE 1.6.0_13进行测试。


1
但是快并不等同于慢:成员变量“Fast.i”的值在循环期间/之后并没有改变。如果您第二次调用run()方法,Slow将会更快(仅增加“i”一次),而Fast仍然像以前一样缓慢,因为“Fast.i”仍然为零。 - user85421
你是正确的,Carlos。 要使Fast和Slow在单线程环境中具有相同的行为,则必须在“run”方法的末尾更新实例变量“i”,这不会对性能产生显着影响。 - Dave Jarvis
同时在调用calculate the runtime的附近,使用System.currentTimeMillis()也得到了一个“奇怪”的结果:慢的比快的还要快(慢=40.6秒,快=42.9秒),适用于WindowsXP上的1.6.0_13-b03。 - user85421
Carlos:在两个类中尝试四次运行,而不运行任何可能会占用CPU的程序(例如病毒检查器、系统更新、浏览器)。此外,在两个测试中都要排除第一次运行。Fast比Slow慢约2秒,这让我相信某些因素干扰了运行。(也就是说,通过其访问器获取和设置变量不应该需要2秒钟。) - Dave Jarvis
2
“过早优化”是一个用来描述程序员让性能考虑影响代码设计的情况。这可能导致设计不够简洁或代码不正确,因为优化使代码变得复杂,程序员分心于优化而忽略了其他重要方面。[参考:http://en.wikipedia.org/wiki/Optimization_%28computer_science%29] - jdigital
@jdigital:我认为这并不过早。当方法被同步时,它可以防止以下问题:https://dev59.com/XkzSa4cB1Zd3GeqPoJwq - Dave Jarvis

9

你选择的编码方式如何处理属性文件?以前,当你加载Properties时,你提供了一个InputStream,load()方法将其解码为ISO-8859-1。你实际上可以将文件存储在其他编码中,但是在加载后要使用类似于以下的恶心的hack来正确解码数据:

String realProp = new String(prop.getBytes("ISO-8859-1"), "UTF-8");

但是,从JDK 1.6开始,有一个load()方法可以接受Reader而不是InputStream,这意味着您可以从一开始就使用正确的编码(还有一个store()方法可以接受Writer)。对我来说,这似乎是个很大的问题,但它似乎是悄悄地被添加到JDK中的,没有任何声势。我几周前才偶然发现它,而快速的谷歌搜索只找到了一次提到它的轻描淡写的提及。


请不要进行您的黑客操作,因为它会干扰所有 \uXXXX 转义序列。最好在编辑文件之后部署之前使用 native2ascii 进行转换,或者使用 load() 方法的读取器变体。 - Paŭlo Ebermann

9

让我感到惊讶的是自定义序列化机制。

虽然这些方法是私有的!!,但是在对象序列化期间,JVM会“神秘地”调用它们。

private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

您可以创建自己的自定义序列化方式以使其更加“安全、快速、稀有、易用等等”。

如果必须通过节点传递大量信息,则应考虑此方法。可以更改序列化机制以发送数据的一半。很多时候,瓶颈不在平台上,而在于通过网络发送的数据量,这样做可能会节省成千上万美元的硬件费用。

这里有一篇文章。 http://java.sun.com/developer/technicalArticles/Programming/serialization/


这对于具有非可序列化成员的对象的自定义序列化也非常有用。 - Jorn

8

大多数人不知道他们可以克隆一个数组。

int[] arr = {1, 2, 3};
int[] arr2 = arr.clone();

你可以在任何 Object 上调用 clone。你只需要小心确保该 Object 实现了深度克隆。 - Finbarr
8
正好相反,它只是进行了浅拷贝;内部对象仅仅获得了另一个对它们的引用。最“简单”的深度克隆方式是序列化和反序列化,或者真正理解你要复制的内容。 - Donal Fellows
@Finbar:不能在任意对象上调用clone方法,只能在公开了该方法的类或当前类的对象上调用。 - Paŭlo Ebermann

8

当人们意识到可以使用反射调用私有方法并访问/更改私有字段时,他们有时会感到有些惊讶...

考虑以下类:

public class Foo {
    private int bar;

    public Foo() {
        setBar(17);
    }

    private void setBar(int bar) {
        this.bar=bar;
    }

    public int getBar() {
        return bar;
    }

    public String toString() {
        return "Foo[bar="+bar+"]";
    }
}

执行这个程序...
import java.lang.reflect.*;

public class AccessibleExample {
    public static void main(String[] args)
        throws NoSuchMethodException,IllegalAccessException, InvocationTargetException, NoSuchFieldException {
        Foo foo=new Foo();
        System.out.println(foo);

        Method method=Foo.class.getDeclaredMethod("setBar", int.class);
        method.setAccessible(true);
        method.invoke(foo, 42);

        System.out.println(foo);
        Field field=Foo.class.getDeclaredField("bar");
        field.setAccessible(true);
        field.set(foo, 23);
        System.out.println(foo);
    }
}

将产生以下输出:
Foo[bar=17]
Foo[bar=42]
Foo[bar=23]

setAccessible 调用可能会被安全管理器禁止。 - Paŭlo Ebermann

8

Java 6中的注解处理API对于代码生成和静态代码验证非常有前景。


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