关于JAVA你所不知道的10件事

来源:互联网 发布:实况巅峰数据图拉姆 编辑:程序博客网 时间:2024/04/29 21:42

   

    因此,从一开始你就是使用JAVA工作?记住JAVA还被称作“Oak”的日子,当面向对象还是一个热门话题,使用C++编程的人认为JAVA毫无机会,仅仅只能做一些诸如应用小程序之类的事。


    我敢打赌你不知道至少一半以下所列的事。让我们开始在这一周中许多让人惊喜的关于JAVA的工作。



1.没有所谓的检查异常


    对,你没有看错。除开JAVA语言,JAVA虚拟机并不其他这样的东西。
    今天,每个人都认同检查异常是错误。正如Bruce Eckel(Think in JAVA的作者)在布拉格 GeeCon会议上所定的基调,在JAVA之后没有任何语言从事于使用检查异常,甚至JAVA 8在新的流API中也不再围绕它(当你使用IO或者JDBC在你的lambdas中,事实上有一些小的痛苦)。
    你是否想证明JVM并不知道任何事,请使用下面的代码测试:

 
public class Test {      // No throws clause here    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;    }}
    不要使用这进行编译,它仍然会抛出SQLException。你甚至不需要再看Lombok's @SneakyThrows 来了解它。
   更多的细节可以查看此链接 ,或者 查看StackOverflow上的这个解答


2.你可以拥有仅仅是不同返回类型的方法重载


     它不能被编译,真的是这样吗?


class Test {    Object x() { return "abc"; }    String x() { return "123"; }}

   是这样的。JAVA语言不允许在同一个类中有相同的方法重载,不管它们是否可能抛出不同的异常或者返回不同类型的值。
   但请稍等片刻,看一看JAVA文档中关于 Class.getMethod(String, Class...) 它写道:

       "注意这里可能会有不止一个匹配的方法在一个类中,因为当JAVA语言禁止类中声明相同签名但是有不同返回值类型的许多方法,但是JVM并不会这样。这在虚拟机中提高了灵活性,能实现许多的语言特性。比如:协变式返回值可以桥方法实现,桥方法和这个方法使用相同的签名和不同的返回值类型进行重写。"

   哇,是的,这样做是有意义的。事实上,当你像如下这样写时就十分的漂亮:

  
abstract class Parent<T> {    abstract T x();} class Child extends Parent<String> {    @Override    String x() { return "abc"; }}
   检验在Chind中生成的字节码:

  
// Method descriptor #15 ()Ljava/lang/String;// Stack: 1, Locals: 1java.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: 1bridge 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]

   如你所见,T在字节码是确实是对象,这非常容易理解。
   合成的桥方法在编译时确实生成了,因为Parent.x()返回类型签名可能预料到Object会在某一点会被调用,添加没有诸如桥方法的范型不可能在一个二进制平台存在。因此,改变JVM使允许这个特性只有少许麻烦(作为一个副作用也允许协变覆盖)。

   你深入到语言的细节和内部吗?这里有许多有趣的细节。

3.所有的这些都是二维数组


     
class Test {    int[][] a()  { return new int[0][]; }    int[] b() [] { return new int[0][]; }    int c() [][] { return new int[0][]; }}

    是的,即使在你的头脑中分析不能马上理解上面方法的返回类型,但它们都是相同的,与下面的代码片段类似:

class Test {    int[][] a = {{}};    int[] b[] = {{}};    int c[][] = {{}};}

   你认为这不可思议吗?想象使用JSR-308/Java 8注解类型在上面的例子中。语法的数量让你抓狂了吗:
@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 [] = {{}};}

          类型注解。设备的神秘只是超过了它的力量。

  或者用其他的说法:
         当我做这最后一次提交仅仅是我的假期四周前

   我推荐你寻找一个关于上面的用例进行真实的练习


4.你并不了解条件语句


    你以为你已经知道所有使用过的条件语句?让我告诉你,并非如此,你们中的大多数人会认为下面两个片段是等价的:
Object o1 = true ? new Integer(1) : new Double(2.0);

与这一样?
Object o2; if (true)    o2 = new Integer(1);else    o2 = new Double(2.0);

错,让我们快速测试一下:
System.out.println(o1);System.out.println(o2);
程序会打印如下:
1.01

    条件操作符会实现数值类型的提升。if"needed"条件中,带有强引用标记。因此,你期望这个程序抛出NullPointException?
 
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);

关于上面讨论的更多信息可查看这里


5.你同样并不了解混合赋值操作符

    很离奇吗,让我们考虑下面两段代码:
i += j;i = i + j;

   直觉上,它们是相同的,对吗?你猜怎么着,这两段并不一样,JLS的规定是这样的:

      混合赋值表达式 E1 op= E2 相当于 E1 = (T)((E1)op(E2))(op表示操作符号)。当T是E1类型,除了E1只计算一次。

 非常地完美,这里我想引用Peter Lawrey's在Stack Overflow上的回答。
 一个很好的例子是使用 *= 或者 /=:
byte b = 10;b *= 5.7;System.out.println(b); // prints 57

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

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

或者
char ch = 'A';ch *= 1.5;System.out.println(ch); // prints 'a'

   6.随机的整数

   这是一个难题,先不要看答案,看看你能否找出解答的办法。当我运行下面的程序时:
for (int i = 0; i < 10; i++) {  System.out.println((Integer) i);}

有时,我得到下面的输出:
922214548236183391933384

这怎么可能?
.
.
.
.
.
.
.
.
.
答案在这儿

它通过反射重写了JDK中的Integer cache,然后用自动封箱和自动解箱。
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有GOTO!打印出来:
int goto = 1;
结果会是:
Test.java:44: error: <identifier> expected    int goto = 1;       ^

这是因为goto是Java中未使用的关键字。仅仅因为这个原因。
但这并不是最令人感兴趣的部分,最有趣的是你能真正实现goto通过break、continue和标签区块。
向前:
label: {  // do stuff  if (check) break label;  // do more stuff}
在字节码中:
2  iload_1 [check]3  ifeq 6          // Jumping forward6  ..
向后退:
label: do {  // do stuff  if (check) continue label;  // do more stuff  break label;} while(true);
在字节码中:
2  iload_1 [check] 3  ifeq 9 6  goto 2          // Jumping backward 9  ..


8.Java有类型别名

   其他语言中,我们可以很容易地定义类型别名:
interface People => Set<Person>;

一个people类型以如下方式构造,然后用Set<Person>交换
People?      p1 = null;Set<Person>? p2 = p1;People?      p3 = p2;

在Java中,我们不能在顶层中定义类型别名,但是我们可以在类的范围,或者方法范围更定义。让我们考虑下我们不高兴使用Integer、long等命名,我们想要一个短点的命名,比如I,L。很简单:
class Test<I extends Integer> {    <L extends Long> void x(I i, L l) {        System.out.println(            i.intValue() + ", " +             l.longValue()        );    }}

在上面的程序中,Integer在Test类中用别名"I"命名,同样地Long也在x()这个方法中用L重新命名了,我们可以调用上面的方法:
new Test().x(1, 2L);
这种方法当然是不被认真对待的,因为,Interger和Long都是final类型的,这意味着类型I和L是有效地别名(大多数,赋值兼容只有一条路径)。如果我们使用非final类型,我们就得使用普通地泛型。

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


     好吧,下面的会很有趣,准备一杯浓咖啡,考虑下边两种情况:
// A helper type. You could also just use Listinterface Type<T> {}class C implements Type<Type<? super C>> {}class D<P> implements Type<Type<? super D<D<P>>>> {}

类C和类D是什么含义呢?
它们类似递归(但又有不同)java.lang.Enum 是递归。思考:
public abstract class Enum<E extends Enum<E>> { ... }

根据上面的描述,真实的Enum实现仅仅只是单纯的语法糖:
// Thisenum MyEnum {}// Is really just sugar for thisclass MyEnum extends Enum<MyEnum> { ... }

考虑到这一点,让我们回到两种类型,下边的编译吗:
class Test {    Type<? super C> c = new C();    Type<? super D<Byte>> d = new D<Byte>();}

困难的问题,Ross Tate有回答,实际上,问题是不可判定的。
类C是<?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)

然后
D类是<?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)

让我们写下
      一些类型的关系是不可判定的


10. 类型交集

   Java有一个很独特的特性叫做类型交集,你可以声明一个泛型类实际上有两种类型的交集,比如:
class Test<T extends Serializable & Cloneable> {}

泛型类参数T,你捆绑一个Test类的实例,它必须实现Serializable和Cloneable两个接口,比如:String类型不是适合的参数,但Date适合:
// Doesn't compileTest<String> s = null;// CompilesTest<Date> d = null;

这个特性在Java8中重用了,你现在可以构造相交的特别类型。这有用处吗?几乎没有,但是你想强迫lambda表达式类型,没有其他的方式。让我们假设你使用这个疯狂的类型约束你的方法:
<T extends Runnable & Serializable> void execute(T t) {}


你想实现Runnable同样也实现了Serializable接口,以防你想执行它或者在别的线程发送它,Lambda和系列化真是有些怪异。
Lamdbas表达可以序列化
     你可以序列化一个lambda表达化,如果它的目标类型拥有的参数是序列化的

但即使这样,它们并不自动实现Serializable接口,强制它们转化成那种类型,但当你只是转化成Serializable时
 
execute((Serializable) (() -> {}));
lambda不会运行。

因此,要转化成两种类型:
execute((Runnable & Serializable) (() -> {}));

结论

    我经常有句关于SQL的话,现在是用下边这句话对这篇文章作出一个结论的时刻了:

       Java是一种神秘性超过了它力量的设计。

0 0
原创粉丝点击