Статьи

Java 7 — Project Coin декомпилирована

Привет всем, пора начинать писать в 2012 году. Как вы, возможно, уже видели в других блогах, есть несколько изменений, которые могут значительно облегчить жизнь вашему разработчику при программировании на Java: оператор diamond, Strings in Switch, попробуйте ресурсы мульти улов и т. д.

В этом посте (ЧАСТЬ I) мы рассмотрим некоторые небольшие изменения, представленные в Java 7 Project Coin (JSR 334), а затем (в ЧАСТИ II) мы собираемся декомпилировать их, чтобы увидеть, что делает компилятор (для только в образовательных целях).

Что вам нужно

Алмазный Оператор

Обобщения помогли нам уменьшить ClassCastExceptions среди прочего, но иногда это может затруднить чтение кода. Оператор Diamond — это действительно приятное изменение. Представьте, что вам нужно сгруппировать клиентов по городам. Вам нужно что-то вроде этого:

1
2
3
4
5
//suppose the classes City and Customer exist
...
Map<City,List<Customer>> map = new
HashMap<City,List<Customer>>();
...

Теперь, что если вам также нужно сгруппировать данные по странам:

1
2
3
4
//suppose the classes Country, City and Customer exist
...
Map<Country,MapltCity,List<Customer>>> map = new HashMapl&t;Country,MapltCity,ListltCustomer>>>();
...

Теперь это становится очень трудно читать, верно? Что делать, если вам также нужно сгруппировать по регионам?

1
2
3
4
//suppose the classes Region, Country, City and Customer exist
...
Map<Region,Map<Country,Map<City,List<Customer>>>> map = new HashMap<Region, Map<Country,Map<City,List<Customer>>>>();
...

Так что ты думаешь? Читать такой код совсем не просто. К счастью, новый Diamond Operator очень помогает в удобочитаемости кода. Последний код может быть перекодирован в Java 7 следующим образом:

1
2
3
4
//suppose the classes Region, Country, City and Customer exist
...
Map<Region,Map<Country,Map<City,List<Customer>>>> map = new HashMap<>();
...

Намного лучше!

Строки в Switch

Я так долго этого ждал !!! Я помню дни, когда я начинал в мире Java, и мне действительно нужно было использовать Strings в switch. Что ж, мое ожидание наконец закончилось. В более ранних версиях Java вы должны были написать что-то вроде этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
//in a class
...
public void stringToNumber(String str)
{
   if(str.equalsIgnoreCase("one"))
   {
      System.out.println(1);
   }
   else if(str.equalsIgnoreCase("two"))
   {
      System.out.println(2);
   }
   else if(str.equalsIgnoreCase("three"))
   {
      System.out.println(3);
   }
}
...

В Java 7 вы можете написать это так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
//in a class
...
public void stringToNumber(String str)
{
   switch(str)
   {
      case "one":
         System.out.println(1);
         break;
      case "two":  
         System.out.println(2);
         break;  
      case "three":
         System.out.println(3);
         break;
   }
}
...

Даже в NetBeans есть возможность автоматически конвертировать его:

Попробуй с ресурсами и Multi Catch

Это хорошее улучшение в этой версии, теперь вам не нужно беспокоиться о закрытии этих ResultSets, Statements, FileInputStreams … и т. Д. Вам просто нужно использовать новую структуру try, и компилятор позаботится об этом за вас. Вы даже можете создать свои собственные классы, чтобы они были Closeable (это новый интерфейс) с помощью новой структуры try. Ниже приводится классический доступ к файлам через потоки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//in a class
import java.io.*;
...
public static void copyFile(String path)
              throws IOException, NullPointerException
{
        File file = new File(path);
 
        FileOutputStream fout = null;
        FileInputStream fin = null;
        try {
            try {
                fout = new FileOutputStream("file.dat");
                fin = new FileInputStream(file);
 
                byte[] bytes = new byte[1024];
                int leido = 0;
                while ((leido = fin.read(bytes)) != -1) {
                    fout.write(bytes, 0, leido);
                }
            } finally {
                if (fout != null) {
                    fout.close();
                }
                if (fin != null) {
                    fin.close();
                }
            }
        } catch (NullPointerException ex) {
            ex.printStackTrace();
            throw ex;
        }catch (IOException ex) {
            ex.printStackTrace();
            throw ex;
        }
    }
...

Если вы заметили, чтобы быть уверенным в том, что открытые потоки закрываются после завершения, вы должны кодировать блок try / finally и закрывать их самостоятельно. В Java 7 вы можете добиться того же поведения лучше и с меньшим количеством строк кода, используя новую структуру try и новые классы NIO.2:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
//in a class
import java.nio.file.*;
import java.io.*;
...
public static void copyFile(String src)
              throws IOException, NullPointerException
{
        Path path = FileSystems.getDefault().getPath(src);
         
        try (FileOutputStream fout = new FileOutputStream("file.dat")) {
            Files.copy(path, fout);
        } catch (NullPointerException | IOException ex) {
            ex.printStackTrace();
            throw ex;
        }
}
...

Двоичные литералы и подчеркивания в литералах

Теперь вы можете выразить целое число в виде двоичного литерала, это идеально подходит при программировании низкоуровневых API, также вы можете использовать подчеркивания, чтобы сделать ваши значения более читабельными:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
//in a class
...
public static void coin()
{
   int binaryNum = 0b10; //This is number 2 in binary code
 
   double value1 = 1000000000; //hard to read?
   double value2 = 1_000_000_000; //Easy to read with Java 7
 
   double value3 = 0b101010110111; //hard to read?
   double value4 = 0b1010_1011_0111; //Easy to read with Java 7
 
   double pi = 3.14_15_92; //another example of readability
}
...

Таким образом, Project Coin — это меньше кода, больше производительности и лучшая читаемость кода! (Среди прочего здесь не видно).

Алмазный Оператор

Вот пример алмазного оператора, который мы только что видели в последнем посте:

1
2
3
4
5
6
7
8
//suppose the classes Region, Country, City and Customer exist
import java.util.*;
...
Map<region,map<country,map<city,list>>> map = new HashMap<>();
...
 
 
</region,map<country,map<city,list

Теперь давайте посмотрим, как выглядит сгенерированный код компилятором:

1
2
3
4
import java.util.*;
...
java.util.Map map = new HashMap();
...

Просто определение и создание карты старой школы … Почему? Потому что так работают дженерики:

  Когда вы берете элемент из коллекции, вы должны привести его к типу элемента, который хранится в коллекции.  Помимо того, что неудобно, это небезопасно.  Компилятор не проверяет, совпадает ли ваша приведение с типом коллекции, поэтому приведение может завершиться неудачей во время выполнения.

 Обобщения предоставляют вам способ сообщить тип коллекции для компилятора, чтобы ее можно было проверить.  Как только компилятор узнает тип элемента коллекции, он может проверить, что вы использовали коллекцию последовательно, и может вставить правильные преобразования значений, извлекаемых из коллекции. 

Это означает, что во время компиляции компилятор проверит, правильно ли вы используете классы, и добавит все необходимые преобразования в сгенерированный класс. Например:

1
2
3
4
5
6
7
//suppose the classes Region, Country, City and Customer exist
import java.util.*;
...
Map<region,map<country,map<city,list>>> map = new HashMap<>();
Map<country,map<city,list>> m = map.get(new Region());
...
</country,map<city,list</region,map<country,map<city,list

Вы получите что-то вроде этого:

1
2
3
4
5
6
//suppose the class Region exists
import java.util.*;
...
Map map = new HashMap();
Map m = (Map)map.get(new Region()); //the compiler added the cast
...

Строки в Switch

Вспомните пример Strings in switch, представленный в последнем посте:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
//in a class
...
public void stringToNumber(String str)
{
   switch(str)
   {
      case "one":
         System.out.println(1);
         break;
      case "two":  
         System.out.println(2);
         break;  
      case "three":
         System.out.println(3);
         break;
   }
}
...

После декомпиляции вы заметите, как теперь строки поддерживаются в состояниях коммутатора:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//in a class
...
public static void stringInSwitch(String str)
{
        String s = str;
        byte byte0 = -1;
        switch(s.hashCode())
        {
        case 110182:
            if(s.equals("one"))
                byte0 = 0;
            break;
 
        case 115276:
            if(s.equals("two"))
                byte0 = 1;
            break;
 
        case 110339486:
            if(s.equals("three"))
                byte0 = 2;
            break;
        }
        switch(byte0)
        {
        case 0: // ''
            System.out.println(1);
            break;
 
        case 1: // '01'
            System.out.println(2);
            break;
 
        case 2: // '02'
            System.out.println(3);
            break;
        }
}
...

Да … это маленький трюк. Это не так, как строки поддерживаются в операторах switch напрямую, но их hashCodes есть (hashCodes являются целыми числами). Глядя на код, я понимаю, что лучше не злоупотреблять использованием строк в операторах switch, потому что в конце вы получите два оператора switch…

Попробуй с ресурсами и Multi Catch

Вспомните пример с ресурсами и мультипатчем из предыдущего поста:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
//in a class
import java.nio.file.*;
import java.io.*;
...
public static void copyFile(String src)
              throws IOException, NullPointerException
{
        Path path = FileSystems.getDefault().getPath(src);
         
        try (FileOutputStream fout =
                        new FileOutputStream("file.dat")) {
            Files.copy(path, fout);
        } catch (NullPointerException | IOException ex) {
            ex.printStackTrace();
            throw ex;
        }
}
...

В этом примере используются try with resources и multicatch all в одном примере. Когда я попытался декомпилировать сгенерированный класс с помощью JAD Java Decompiler, я получил много ошибок по поводу вложенных операторов try, поэтому я решил попробовать JD Java Decompiler, и вот результат:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//in a class
import java.nio.file.*;
import java.io.*;
...
public static void copyFile(String src) throws IOException, NullPointerException
{
    Path path =
         FileSystems.getDefault().getPath(src, new String[0]);
    try
    {
      FileOutputStream fout = new FileOutputStream("file.dat");
      Throwable localThrowable2 = null;
      try
      {
        Files.copy(path, fout);
      }
      catch (Throwable localThrowable1)
      {
        localThrowable2 = localThrowable1;
        throw localThrowable1;
      }
      finally
      {
        if (fout != null)
        { //I added this { symbol for readability
 
          if (localThrowable2 != null)
          { //I added this { symbol for readability
 
            try
            {
              fout.close();
            }
            catch (Throwable x2)
            {
              localThrowable2.addSuppressed(x2);
            }
 
          } //I added this } symbol for readability
          else
          { //I added this { symbol for readability
 
              fout.close(); 
 
          } //I added this } symbol for readability
 
        } //I added this } symbol for readability
      }
    }
    catch (IOException ex)
    {
      ex.printStackTrace();
      throw ex;
    }
}
...

Из последнего фрагмента кода мы видим, как компилятор гарантирует, что любое исключение, выброшенное во время процесса копирования, не будет потеряно с помощью нового (JDK 7) метода + addSuppressed (Throwable): void класса Throwable . Это важно, потому что вам понадобятся все возможные исключения при поиске ошибки в вашем приложении. Также обратите внимание, что все операции закрытия выполняются в операторе finally, гарантирующем, что ресурсы всегда закрываются в конце процесса.

Двоичные литералы и подчеркивания в литералах

Я думаю, вы можете понять, что мы получим после декомпиляции этой последней функции …

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
//in a class
...
public static void coin()
{
   int binaryNum = 0b10; //This is number 2 in binary code
 
   double value = 1000000000; //hard to read?
   double value = 1_000_000_000; //Easy to read with Java 7
 
   double value = 0b101010110111; //hard to read?
   double value = 0b1010_1011_0111; //Easy to read with Java 7
 
   double pi = 3.14_15_92; //another example of readability
}
...

Да, ничего нового … компилятор просто переписывает значения без подчеркивания и переводит двоичные значения в целочисленные значения:

01
02
03
04
05
06
07
08
09
10
11
12
//in a class
...
public static void coin(
{
        int binaryNum = 2;
        double value1 = 1000000000D;
        double value2 = 1000000000D;
        double value3 = 2743D;
        double value4 = 2743D;
        double pi = 3.1415920000000002D;
}
...

Хорошо, это все для этого поста. Надеюсь, вам всем понравились новые функции Project Coin (JSR334) в Java 7. В Project Coin II в Java 8 появятся дополнительные улучшения, и мы проверим их все в следующих публикациях. увидимся!

Справка: Java — декомпилирована Project Coin, а Java 7 — декомпилирована Project Coin, часть II от нашего партнера по JCG