你不知道java的10件事

2 篇文章 0 订阅

    你不知道java的10件事

   

    原文:10 Things You Didn't Know About Java  

  
    虽然我尝试了翻译这篇文章,但是有好多东西还是没搞明白,估计你看我翻译的也不会明白.今天(2014-12-22)在ImportNew上面看到了原文的另外一篇翻译文章,是的,翻译的非常好,希望对你有帮助.

    这是链接http://www.importnew.com/13859.html

    如果你看过那篇翻译,就不要看我的了,哈哈,我自己都不好意思说这是翻译了.

     你从一开始就在使用java吗?你是否还记得java被称作为”Oak”的时期?那时,面向对象仍然是一个热门的话题,使用C++的人们都认为java没有任何机会, Applets 风头正劲.

    我敢打赌你肯定不知道以下一半的事情.现在,让我们开始一些java内部运作的大惊喜.

1.并没有所谓的检查异常


    没错,java虚拟机(JVM)不知道异常,只有java语言自己知道.

    如今,每个人都同意检查异常是一个错误.正如Bruce Eckel 在Prague的 GeeCON闭幕词上所说,在java之后没有其他语言会约定使用检查异常,甚至 Java 8  新的流API都不再包含这些(在lambdas表达式中使用IO 或者JDBC时,是有点痛苦).

    (译者注:java8 引入了lambads表达式,使用它能够使设计代码更简洁)


    使用下面的代码可以证明JVM并不知道这些:

<span style="font-size:18px;"></pre><pre name="code" class="java">public class Test {
  
    // 方法没有声明 throws 子句
    public static void main(String[] args) {
        doThrow(new SQLException());
    }
  
    static void doThrow(Exception e) {
        Test.<RuntimeException> doThrow0(e);
    }
  
    @SuppressWarnings("unchecked")
    static <E extends Exception> 
    void doThrow0(Exception e) throws E {
        throw (E) e;
    }
}</span>

    上面的代码不仅能通过编译,而且抛出了异常SQLException ,你甚至不需要使用Lombok’s 的注解@SneakyThrows .


    (译者注:Lombok是一个使用注解简化java代码的库)
    (译者注:如果你在方法中没有声明throws子句,当程序出现异常时Lomok 注解@SneakyThrows 会偷偷的抛出检查异常)

       点这里获取关于这篇文章的更多细节, 或 Stack Overflow.

2.重载仅返回类型不同的方法

    这不能通过编译,不是吗?

<span style="font-size:18px;">class Test {
    Object x() { return "abc"; }
    String x() { return "123"; }
}
</span>

    是的.Java语言不允许在同一个类中存在"等价覆盖" 的两个方法.不管它们有不同的 throws 子句或是不同的返回类型.

    在 Javadoc  Class.getMethod(String,Class...).上面有如下说明:


    注意:它在一个类中可能会匹配到多个方法,虽然java语言禁止在一个类中声明多个签名相同而仅返回类型不同的方法,但是java虚拟机不会如此.在java虚拟机中,这种增强 的灵活性被用于实现多样的语言特性.例如,协变返回值类型能通过桥接方法实现;桥接方法和被覆盖的方法将有相同的签名,不同的返回类型.


    这很有意义.事实上,下面的语句所发生的几乎就是这样.

<span style="font-size:18px;">abstract class Parent<T> {
    abstract T x();
}
 
class Child extends Parent<String> {
    @Override
    String x() { return "abc"; }
}</span>


     查看生成的字节码:


<span style="font-size:18px;"><span style="font-size:18px;">// Method descriptor #15 ()Ljava/lang/String;
// Stack: 1, Locals: 1
java.lang.String x();
  0  ldc <String "abc"> [16]
  2  areturn
    Line numbers:
      [pc: 0, line: 7]
    Local variable table:
      [pc: 0, pc: 3] local: this index: 0 type: Child
 
// Method descriptor #18 ()Ljava/lang/Object;
// Stack: 1, Locals: 1
bridge synthetic java.lang.Object x();
  0  aload_0 [this]
  1  invokevirtual Child.x() : java.lang.String [19]
  4  areturn
    Line numbers:
      [pc: 0, line: 1]</span>
</span>



    因此,很好理解 T 在字节码中就是一个对象.
    这个合成的桥接方法实际上是由编译器生成的,因为Parent.x()的返回类型签名在某些调用位置可能会被期望成 object.
    加入的泛型没有这样的桥接方法就不可能以一种二进制的方式兼容.
    因此,改变JVM去支持这种特性只需很少的代价(同样也允许协变性压倒一切负效应)很聪明,不是吗?

    你分析过语言的细节和内幕吗?点这里发现更多有趣的细节.
 

3.这些都是二维数组

<span style="font-size:18px;">class Test {
    int[][] a()  { return new int[0][]; }
    int[] b() [] { return new int[0][]; }
    int c() [][] { return new int[0][]; }
}</span>

这是真的.你可能无法立刻理解上面方法的返回类型,但它们都是一样的.类似于下面的方法:


<span style="font-size:18px;">class Test {
    int[][] a = {{}};
    int[] b[] = {{}};
    int c[][] = {{}};
}</span>


你肯定认为这疯了.想象一下,为上面的方法使用 JSR-308 / Java 8 的类型注解. 句法的数量将会激增.

<span style="font-size:18px;">@Target(ElementType.TYPE_USE)
@interface Crazy {}
 
class Test {
    @Crazy int[][]  a1 = {{}};
    int @Crazy [][] a2 = {{}};
    int[] @Crazy [] a3 = {{}};
 
    @Crazy int[] b1[]  = {{}};
    int @Crazy [] b2[] = {{}};
    int[] b3 @Crazy [] = {{}};
 
    @Crazy int c1[][]  = {{}};
    int c2 @Crazy [][] = {{}};
    int c3[] @Crazy [] = {{}};
}
</span>


4.你不明白条件表达式


    你认为你在使用条件表达式时明白一切吗?让我告诉你吧,你不明白.

    大多数人都会认为下面两个代码片段是等价的:

<span style="font-size:18px;">Object o1 = true ? new Integer(1) : new Double(2.0);</span>

    真的一样吗?

<span style="font-size:18px;">Object o2;
 
if (true)
    o2 = new Integer(1);
else
    o2 = new Double(2.0);
</span>


   不一样.我们来验证一下:

<span style="font-size:18px;">System.out.println(o1);
System.out.println(o2);</span>

    程序的输出结果为:

    1.0
    1

   

    没错!条件运算符在必要的时候将实现数据类型的提升.下面的语句将会抛出一个NullPointException .

<span style="font-size:18px;">Integer i = new Integer(1);
if (i.equals(1))
    i = null;
Double d = new Double(2.0);
Object o = true ? i : d; // NullPointerException!
System.out.println(o);</span>


        点这里获取更多细节.

5.你也不明白复合赋值运算符


    看下面的代码:
<span style="font-size:18px;">i += j;
i = i + j;</span>


    乍一看它们应该是等价的,但事实上不是.见 JSL(java 语言规范):

        复合赋值表达式 E1 op= E2 与 E1 = (T)((E1) op (E2)) 是等价的,T的类型与E1相同,此外 E1仅计算一次

   

    这真是太美了,我想引用Peter Lawrey's 关于堆栈溢出问题的回答:

<span style="font-size:18px;">A good example of this casting is using *= or /=

    byte b = 10;
    b *= 5.7;
    System.out.println(b); // prints 57

    or

    byte b = 100;
    b /= 2.5;
    System.out.println(b); // prints 40

    or

    char ch = '0';
    ch *= 1.1;
    System.out.println(ch); // prints '4'

    or

    char ch = 'A';
    ch *= 1.5;
    System.out.println(ch); // prints 'a'</span>



6.随机整数

    这是一个难题.不要参看解答,你能独立的解决问题吗?

    运行下面的代码:

<span style="font-size:18px;">for (int i = 0; i < 10; i++) {
  System.out.println((Integer) i);
}</span>

    
我有时候会得到如下输出:

   

92
221
45
48
236
183
39
193
33
84

    这怎么可能?

    答案在这  .通过反射覆盖 JDK's 的 Integercache ,然后使用自动装箱和自动拆箱机制.

import java.lang.reflect.Field;
import java.util.Random;
 
public class Entropy {
  public static void main(String[] args)
  throws Exception {
 
    // Extract the IntegerCache through reflection
    Class<?> clazz = Class.forName(
      "java.lang.Integer$IntegerCache");
    Field field = clazz.getDeclaredField("cache");
    field.setAccessible(true);
    Integer[] cache = (Integer[]) field.get(clazz);
 
    // Rewrite the Integer cache
    for (int i = 0; i < cache.length; i++) {
      cache[i] = new Integer(
        new Random().nextInt(cache.length));
    }
 
    // Prove randomness
    for (int i = 0; i < 10; i++) {
      System.out.println((Integer) i);
    }
  }
}


运行上面的代码,你就可以得到类似的结果了.


7.GOTO



    在java中编写下列语句:

<span style="font-size:18px;">int goto = 1;</span>

    程序会编译失败,错误信息为:

<span style="font-size:18px;">Test.java:44: error: <identifier> expected
    int goto = 1;
       ^</span>


    因为goto是一个未使用的关键字.

    虽然无法在源码中直接使用 goto 但是我们可以通过 break ,continue 和 标记块实现.

    字节码的 goto ;


    向前跳转 :

<span style="font-size:18px;">label: {
  // do stuff
  if (check) break label;
  // do more stuff
}</span>


    它的字节码为:

<span style="font-size:18px;">2  iload_1 [check]
3  ifeq 6          // Jumping forward
6  ..
</span>


    向后跳转:

<span style="font-size:18px;">label: do {
  // do stuff
  if (check) continue label;
  // do more stuff
  break label;
} while(true);</span>

    它的字节码为:

<span style="font-size:18px;">2  iload_1 [check]
 3  ifeq 9
 6  goto 2          // Jumping backward
 9  ..</span>


    看,是不是出现了 goto

8.java 的类型别名

    在其他语言中可以很简单的使用类型别名 ,例如Ceylon

    (译者注:Ceylon是一种新兴的计算机编程语言,号称"java杀手",它不是Java,而是一种受Java影响的新语言.)

<span style="font-size:18px;">interface People => Set<Person> ;</span>


    以这种方式构造的 People 可以被 Set<Person> 替换 :

<span style="font-size:18px;">People?      p1 = null;
Set<Person>? p2 = p1;
People?      p3 = p2;</span>


    在 java中 ,我们无法在全局范围上定义类型别名. 由于存在 class 域 或 方法 域,
    亦可以定义. 考虑两个我们很不喜欢的命名 Integer 和 Long ,为它们取个简短的名称 I 和 L :

<span style="font-size:18px;">class Test<I extends Integer> {
    <L extends Long> void x(I i, L l) {
        System.out.println(
            i.intValue() + ", " +
            l.longValue()
        );
    }
}
</span>


    上面的程序中 , 在 TestClass 域内 定义 Integer 别名为 I ,在 x() 方法域中定义Long
    别名为 L . 我们可以这样使用上面的方法 :

<span style="font-size:18px;">new Test().x(1, 2L);</span>


    显然这种技术不值得重视. 在这个例子中 ,Integer 和 Long 都是 final 类型,也就意味着类型 I 和 L 是 有效的别名(那样的话,程序与类型兼容性也就无缘了).如果我们使      用的不是 final 类型,那么就应该使用泛型.

    看够了这些无聊的把戏了吧! 来点厉害的.

9. 一些不可判定的关系类型

    让我们来点咖啡 ,集中你的注意力,这可是很时髦的东西.
    考虑下面两个类型:

<span style="font-size:18px;">// A helper type. You could also just use List
interface Type<T> {}
 
class C implements Type<Type<? super C>> {}
class D<P> implements Type<Type<? super D<D<P>>>> {}
</span>


    现在,你知道 C 和 D 的类型吗 ?

    它们包含了递归,java.lang.Enum 也是递归的.这两种方式有些相似,但略有不同.

<span style="font-size:18px;">public abstract class Enum<E extends Enum<E>> { ... }
</span>


    由上面的规范可知,Enum 实际上是由一种糖衣语法实现的.
    (译者注:糖衣语法:指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用)

<span style="font-size:18px;">// This
enum MyEnum {}
 
// Is really just sugar for this
class MyEnum extends Enum<MyEnum> { ... }</span>


    考虑到这一点,让我们回到先前定义的两个类型.下列代码能编译成功吗?


<span style="font-size:18px;">class Test {
    Type<? super C> c = new C();
    Type<? super D<Byte>> d = new D<Byte>();
}
</span>


    很难回答,Ross Tate 有个答案,不可判定:

<span style="font-size:18px;">Is C a subtype of Type<? super C>?

Step 0) C <?: Type<? super C>
Step 1) Type<Type<? super C>> <?: Type (inheritance)
Step 2) C  (checking wildcard ? super C)
Step . . . (cycle forever)

And then:

Is D a subtype of Type<? super D<Byte>>?

Step 0) D<Byte> <?: Type<? super C<Byte>>
Step 1) Type<Type<? super D<D<Byte>>>> <?: Type<? super D<Byte>>
Step 2) D<Byte> <?: Type<? super D<D<Byte>>>
Step 3) List<List<? super C<C>>> <?: List<? super C<C>>
Step 4) D<D<Byte>> <?: Type<? super D<D<Byte>>>
Step . . . (expand forever)</span>


    尝试在你的Eclipse中编译上面的代码,崩溃了吧!

    看这句话:

有些类型关系在java中是不可判定的.


    如果你有兴趣了解这个奇怪的java特性更多细节,就读一读 Ross Tate的论文  "Taming Wildcards in Java's Type System"(与 Alan Leung 和 Sorin Lerner 合著),或者自己思考关联子类型多态性与泛型多态性.

10. 交集类型

    java 有一个很独特的特性称作交集类型( type intersections). 你可以声明一个泛型 ,它由两个类型的交集构成.例如:

<span style="font-size:18px;">class Test<T extends Serializable & Cloneable> {
}</span>


    要使用绑定的泛型参数 T 去实例化 Test 类, 这个参数就必须同时实现 Serializable 和 Cloneable.例如 String 不是 ,而 Date 是:

<span style="font-size:18px;">// Doesn't compile
Test<String> s = null;
 
// Compiles
Test<Date> d = null;</span>


    为了让你有一个专门的交集类型,这种特性在java8中得到了重用.如何用它呢?几乎没用.但是,当你在 lambda表达式中强行应用这样的类型时,就只有此种方法可行.

    假设你在方法中使用了这种疯狂的类型约束:

<span style="font-size:18px;"><T extends Runnable & Serializable> void execute(T t) {}</span>

    你需要一个实现了 Runnable 和Serializable 的对象.为了让你可以在某些地方执行它,或是发送它,

    Lambads 可以被序列化:
      如果一个lambda 表达式的目标类型和所需参数是可序列化的,那么这个表达式就能序列化.
    即使这是真的,那也不会自动的实现序列化标记接口.所以你必须自己动手.

    现在你有一个可序列化的,但是它不能被执行

<span style="font-size:18px;">execute((Serializable) (() -> {}));</span>

    所以你必须自己加上:

<span style="font-size:18px;">execute((Runnable & Serializable) (() -> {}));</span>


新手翻译作品,恳请读者批评指正.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值