シンタックスシュガーとは?Java にはどのようなシンタックス シュガーがありますか?

Java コンパイルの原則の観点から、この記事はバイトコードとクラス ファイルに深く入り込み、Java におけるシンタックス シュガーの原則と使用法を理解します. Java シンタックス シュガーの使用方法を学びながら、これらのシンタックス シュガーの背後にある原則を理解するのに役立ちます.

1シンタックスシュガー

シュガー コーティングされた文法としても知られる構文糖は、英国のコンピューター科学者 Peter.J.Landin によって発明された用語です. これは、コンピューター言語に追加された特定の文法を指します. この文法は、言語の機能に影響を与えません. しかし、プログラマーが使用する方が便利です。要するに、シンタックス シュガーはプログラムをより簡潔で読みやすくします。

興味深いことに、プログラミングの分野では、文法上の砂糖以外に、文法上の塩や文法上のサッカリンという用語もあり、スペースが限られているため、ここでは拡張しません。

私たちが知っているほとんどすべてのプログラミング言語には、シンタックス シュガーがあります。著者は、文法的な砂糖の量は、言語が十分に強力であるかどうかを判断するための基準の 1 つだと考えています。

Javaは「低糖質言語」と言われることが多いですが、実はJava 7以降、主に「Project Coin」プロジェクトで開発されたJava言語レベルに様々な糖質が追加されています。現在の Java は低糖と考える人もいますが、今後も「高糖」の方向に発展していくでしょう。

2シンタックス シュガーの解決策

前述したように、シンタックス シュガーの存在は主に開発者の利便性のためです。しかし実際には、Java 仮想マシンはこれらのシンタックス シュガーをサポートしていません。これらのグラマティック シュガーは、コンパイル フェーズ中に単純な基本的な文法構造に還元されます。このプロセスがグラマティック シュガーの解決策です。

コンパイルについて言えば、Java 言語では javac コマンドを使用して、.java というサフィックスを持つソース ファイルを、Java 仮想マシンで実行できる .class というサフィックスを持つバイトコードにコンパイルできることを誰もが知っている必要があります。

com.sun.tools.javac.main.JavaCompiler のソース コードを見ると、compile() のステップの 1 つが desugar() を呼び出すことであることがわかります。これは、構文シュガー ソリューションの実現を担当します。

Java で最も一般的に使用されるシンタックス シュガーには、主にジェネリック、可変長パラメーター、条件付きコンパイル、自動アンボックス化、内部クラスなどが含まれます。この記事では、主にこれらの文法上の糖の背後にある原則を分析します。段階的にアイシングをはがして、それが何であるかを確認します。

3 キャンディー紹介

3.1スイッチは文字列と列挙をサポート

前述のように、Java 7 以降、Java 言語のシンタックス シュガーは徐々に強化されていますが、より重要なものの 1 つは、Java 7 のスイッチが String をサポートし始めたことです。

コーディングを始める前に、科学を普及させましょう Java のスイッチ自体は、基本的な型をサポートしています。int、charなど。

int 型の場合は値を直接比較します。char 型については、ASCII コードを比較してください。

したがって、コンパイラでは、スイッチで使用できるのは整数のみであり、すべてのタイプの比較は整数に変換する必要があります。例えばバイト。short、char (ackii コードは整数)、および int。

次に、次のコードを使用して、文字列に対するスイッチのサポートを見てみましょう。

public class switchDemoString {
    public static void main(String[] args) {
        String str = "world";
        switch (str) {
        case "hello":
            System.out.println("hello");
            break;
        case "world":
            System.out.println("world");
            break;
        default:
            break;
        }
    }
}

逆コンパイル後の内容は次のとおりです。

public class switchDemoString
{
    public switchDemoString()
    {
    }
    public static void main(String args[])
    {
        String str = "world";
        String s;
        switch((s = str).hashCode())
        {
        default:
            break;
        case 99162322:
            if(s.equals("hello"))
                System.out.println("hello");
            break;
        case 113318802:
            if(s.equals("world"))
                System.out.println("world");
            break;
        }
    }
}

このコードを見ると、元の文字列の切り替えが equals() および hashCode() メソッドによって実装されていることがわかります。幸い、hashCode() メソッドは long ではなく int を返します。

よく見ると実際のスイッチはハッシュ値で、equalsメソッドで比較してセキュリティチェックを行っているのですが、ハッシュが衝突する可能性があるので必要です。したがって、列挙型で切り替えたり、単純な整数定数を使用したりするほどパフォーマンスは高くありませんが、悪くもありません。

3.2ジェネリック

多くの言語がジェネリックをサポートしていることは誰もが知っていますが、多くの人が知らないのは、さまざまなコンパイラーがさまざまな方法でジェネリックを処理することです。

通常、コンパイラはジェネリックを次の 2 つの方法で処理します。コードの特殊化とコードの共有です。

C++ と C# はコードの特殊化の処理メカニズムを使用しますが、Java はコード共有のメカニズムを使用します。

コード共有メソッドは、ジェネリック型ごとに一意のバイトコード表現を作成し、ジェネリック型のインスタンスをこの一意のバイトコード表現にマップします。複数のジェネリック型インスタンスから一意のバイトコード表現へのマッピングは、型消去によって実現されます。

つまり、Java 仮想マシンの場合、彼は Map<String, String> マップの構文をまったく知りません。コンパイル段階で型消去によりシュガーを脱構文する必要があります。

型消去の主なプロセスは次のとおりです。

  • 1. すべてのジェネリック パラメーターを左端の境界 (最上位の親型) 型に置き換えます。

  • 2. すべての型パラメーターを削除します。

次のコード:

Map<String, String> map = new HashMap<String, String>();  
map.put("name", "hollis");  
map.put("wechat", "Hollis");  
map.put("blog", "www.hollischuang.com");  

シンタックス シュガーを脱糖すると、次のようになります。

Map map = new HashMap();  
map.put("name", "hollis");  
map.put("wechat", "Hollis");  
map.put("blog", "www.hollischuang.com");  

次のコード:

public static <A extends Comparable<A>> A max(Collection<A> xs) {
    Iterator<A> xi = xs.iterator();
    A w = xi.next();
    while (xi.hasNext()) {
        A x = xi.next();
        if (w.compareTo(x) < 0)
            w = x;
    }
    return w;
}

型消去後は次のようになります。

 public static Comparable max(Collection xs){
    Iterator xi = xs.iterator();
    Comparable w = (Comparable)xi.next();
    while(xi.hasNext())
    {
        Comparable x = (Comparable)xi.next();
        if(w.compareTo(x) < 0)
            w = x;
    }
    return w;
}

仮想マシンにはジェネリックはなく、通常のクラスと通常のメソッドのみ. すべてのジェネリック クラスの型パラメーターはコンパイル時に消去され、ジェネリック クラスには固有の Class オブジェクトがありません. たとえば、List<String>.class または List<Integer>.class はなく、List.class だけです。

3.3自動ボックス化とボックス化解除

オートボクシングとは、Java がプリミティブ型の値を対応するオブジェクトに自動的に変換することを意味し、たとえば int 変数を Integer オブジェクトに変換することをボックス化と呼び、逆に Integer オブジェクトを int 型の値に変換することをアンボックス化と呼びます。

ここでのボックス化とボックス化解除は、人間以外の自動変換であるため、自動ボックス化とボックス化解除と呼ばれます。

プリミティブ型 byte、short、char、int、long、float、double、および boolean に対応するカプセル化クラスは、Byte、Short、Character、Integer、Long、Float、Double、Boolean です。

まず、自動ボクシングのコードを見てみましょう。

public static void main(String[] args) {
    int i = 10;
    Integer n = i;
}

逆コンパイルされたコードは次のとおりです。

public static void main(String args[])
{
    int i = 10;
    Integer n = Integer.valueOf(i);
}

自動ボックス化解除のコードを見てみましょう。

public static void main(String[] args) {

    Integer i = 10;
    int n = i;
}

逆コンパイルされたコードは次のとおりです。

public static void main(String args[])
{
    Integer i = Integer.valueOf(10);
    int n = i.intValue();
}

逆コンパイルされた内容からわかるように、ボックス化時に Integer の valueOf(int) メソッドが自動的に呼び出されます。ボックス化を解除すると、Integer の intValue メソッドが自動的に呼び出されます。

したがって、ボックス化処理はラッパーの valueOf メソッドを呼び出すことで実現され、アンボックス化処理はラッパーの xxxValue メソッドを呼び出すことで実現されます。

3.4メソッドの可変長パラメータ

可変引数 (可変引数) は、Java 1.5 で導入された機能です。これにより、メソッドは任意の数の値をパラメーターとして受け取ることができます。

print メソッドが変数パラメーターを受け取る、次の変数パラメーター コードを見てください。

public static void main(String[] args)
    {
        print("Holis", "公众号:Hollis", "博客:www.hollischuang.com", "QQ:907607222");
    }

public static void print(String... strs)
{
    for (int i = 0; i < strs.length; i++)
    {
        System.out.println(strs[i]);
    }
}

逆コンパイルされたコード:

public static void main(String args[])
{
    print(new String[] {
        "Holis", "\u516C\u4F17\u53F7:Hollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com", "QQ\uFF1A907607222"
    });
}

// transient 不能修饰方法,这里应该是反编译错误了?
public static transient void print(String strs[])
{
    for(int i = 0; i < strs.length; i++)
        System.out.println(strs[i]);

}

逆コンパイルされたコードからわかるように、変数パラメーターが使用されると、まずメソッドの呼び出しによって渡された実際のパラメーターの数を長さとする配列が作成され、次にすべてのパラメーター値がこの配列に入れられ、次にこの配列をパラメータとして呼び出されたメソッドに渡します。

3.5列挙

Java SE5 は新しいタイプを提供します - Java の列挙型. キーワード enum は、名前付きの値の限定されたセットを新しいタイプとして作成でき、これらの名前付きの値は通常のプログラム コンポーネントとして使用できます. これは非常に便利な機能です.

ソースコードを見たい場合は、まずクラスを持っている必要がありますが、列挙型とはどのようなクラスですか? それは列挙型ですか?

答えは明らかにそうではありません.enumはクラスと同じで、単なるキーワードであり、クラスではありません.

では、列挙はどのクラスによって維持されているのでしょうか? 単純に列挙を記述します。

public enum t {
    SPRING,SUMMER;
}

次に、逆コンパイルを使用して、このコードがどのように実装されているかを確認します。逆コンパイル後のコードの内容は次のとおりです。

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }

    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }

    public static final T SPRING;
    public static final T SUMMER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER
        });
    }
}

コードを逆コンパイルすると、 public final class T extends Enum がこのクラスが Enum クラスを継承することを示し、 final キーワードがこのクラスを継承できないことを示していることがわかります。

enmu を使用して列挙型を定義すると、コンパイラは Enum クラスを継承するための最終的な型クラスを自動的に作成するため、列挙型は継承できません。

3.6内部クラス

内部クラスはネストされたクラスとも呼ばれ、内部クラスは外部クラスの通常のメンバーとして理解できます。

内部クラスがシンタックス シュガーでもある理由は、それがコンパイル時の概念にすぎないためです。

インナー クラス inner は outer.java で定義されます。コンパイルが成功すると、outer.class と outer$inner.class という 2 つのまったく異なる .class ファイルが生成されます。したがって、内部クラスの名前は外部クラスの名前と同じにすることができます。

public class OutterClass {
    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public static void main(String[] args) {

    }

    class InnerClass{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

上記のコードをコンパイルすると、OutterClass$InnerClass.class と OutterClass.class の 2 つのクラス ファイルが生成されます。

jad を使用して OutterClass.class ファイルを逆コンパイルしようとすると、コマンド ラインに次のように出力されます。

Parsing OutterClass.class...
Parsing inner class OutterClass$InnerClass.class...
Generating OutterClass.jad

彼は 2 つのファイルすべてを逆コンパイルし、OutterClass.jad ファイルを一緒に生成します。ファイルの内容は次のとおりです。

public class OutterClass
{
    class InnerClass
    {
        public String getName()
        {
            return name;
        }
        public void setName(String name)
        {
            this.name = name;
        }
        private String name;
        final OutterClass this$0;

        InnerClass()
        {
            this.this$0 = OutterClass.this;
            super();
        }
    }

    public OutterClass()
    {
    }
    public String getUserName()
    {
        return userName;
    }
    public void setUserName(String userName){
        this.userName = userName;
    }
    public static void main(String args1[])
    {
    }
    private String userName;
}

3.7条件付きコンパイル

- 通常の状況では、プログラム内のコードの各行がコンパイルに参加する必要があります。しかし、プログラムコードの最適化のために、内容の一部だけをコンパイルしたい場合があります.このとき、コンパイラが条件に一致するコードのみをコンパイルできるように、プログラムに条件を追加し、コンパイルする必要があります.条件を満たさないコード 放棄、これは条件付きコンパイルです。

C や CPP の場合と同様に、条件付きコンパイルは準備済みステートメントを介して実現できます。実際、条件付きコンパイルは Java でも実装できます。最初にコードを見てみましょう。

public class ConditionalCompilation {
    public static void main(String[] args) {
        final boolean DEBUG = true;
        if(DEBUG) {
            System.out.println("Hello, DEBUG!");
        }

        final boolean ONLINE = false;

        if(ONLINE){
            System.out.println("Hello, ONLINE!");
        }
    }
}

逆コンパイルされたコードは次のとおりです。

public class ConditionalCompilation
{

    public ConditionalCompilation()
    {
    }

    public static void main(String args[])
    {
        boolean DEBUG = true;
        System.out.println("Hello, DEBUG!");
        boolean ONLINE = false;
    }
}

まず、逆コンパイルされたコードに System.out.println("Hello, ONLINE!"); がないことがわかりましたが、これは実際には条件付きコンパイルです。

if(ONLINE) が false の場合、コンパイラはその中のコードをコンパイルしません。

したがって、Java 文法の条件付きコンパイルは、判定条件が一定の if 文によって実現されます。if 条件の true または false に従って、コンパイラは分岐が false であるコード ブロックを直接削除します。このメソッドが実現する条件付きコンパイルは、メソッド本体で実現する必要があり、Java クラス全体の構造やクラスの属性に対して条件付きコンパイルを実行することはできません。

これは実際、C/C++ の条件付きコンパイルよりも制限されています。Java 言語設計の初期には、条件付きコンパイルの機能は導入されていませんでした.制限はありますが、何もないよりはましです.

3.8 断言

Java では、Java SE 1.4 から assert キーワードが導入されました. Java コードの古いバージョンで assert キーワードを使用することによって引き起こされるエラーを回避するために、Java は実行時にデフォルトでアサーション チェックを有効にしません (現時点では、すべての Assertion ステートメントは、は無視されます!)。

アサーション チェックを有効にする場合は、スイッチ -enableassertions または -ea を使用して有効にする必要があります。

アサーションを含むコードを考えてみましょう:

public class AssertTest {
    public static void main(String args[]) {
        int a = 1;
        int b = 1;
        assert a == b;
        System.out.println("公众号:Hollis");
        assert a != b : "Hollis";
        System.out.println("博客:www.hollischuang.com");
    }
}

逆コンパイルされたコードは次のとおりです。

public class AssertTest {
   public AssertTest()
    {
    }
    public static void main(String args[])
{
    int a = 1;
    int b = 1;
    if(!$assertionsDisabled && a != b)
        throw new AssertionError();
    System.out.println("\u516C\u4F17\u53F7\uFF1AHollis");
    if(!$assertionsDisabled && a == b)
    {
        throw new AssertionError("Hollis");
    } else
    {
        System.out.println("\u535A\u5BA2\uFF1Awww.hollischuang.com");
        return;
    }
}

static final boolean $assertionsDisabled = !com/hollis/suguar/AssertTest.desiredAssertionStatus();


}

明らかに、逆コンパイルされたコードは、独自のコードよりもはるかに複雑です。したがって、assert のシンタックス シュガーを使用することで、多くのコードを節約できます。

実際、アサーションの基本的な実装は if 言語です. アサーションの結果が true の場合、何も行われず、プログラムは実行を続けます. アサーションの結果が false の場合、プログラムは AssertError をスローして、プログラムの実行。

-enableassertions は $assertionsDisabled フィールドの値を設定します。

3.9数値リテラル

Java 7 では、数値リテラルは、整数であるか浮動小数点数であるかにかかわらず、数値の間に任意の数のアンダースコアを挿入できます。これらのアンダースコアはリテラル値には影響しません。目的は読みやすくすることです。

例えば:

public class Test {
    public static void main(String... args) {
        int i = 10_000;
        System.out.println(i);
    }
}

逆コンパイル後:

public class Test
{
  public static void main(String[] args)
  {
    int i = 10000;
    System.out.println(i);
  }
}

逆コンパイル後、_は削除されます。つまり、コンパイラは数値リテラルの _ を認識しないため、コンパイル段階でそれを削除する必要があります。

3.10 for-each

強化された for ループ (for-each) は、誰にでもなじみがあると考えられています. 日常の開発でよく使用されます. for ループよりもはるかに少ないコードを記述します. では、このシンタックス シュガーはどのように実装されるのでしょうか?

public static void main(String... args) {
    String[] strs = {"Hollis", "公众号:Hollis", "博客:www.hollischuang.com"};
    for (String s : strs) {
        System.out.println(s);
    }
    List<String> strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");
    for (String s : strList) {
        System.out.println(s);
    }
}

逆コンパイルされたコードは次のとおりです。

public static transient void main(String args[])
{
    String strs[] = {
        "Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com"
    };
    String args1[] = strs;
    int i = args1.length;
    for(int j = 0; j < i; j++)
    {
        String s = args1[j];
        System.out.println(s);
    }

    List strList = ImmutableList.of("Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com");
    String s;
    for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s))
        s = (String)iterator.next();

}

コードは非常にシンプルで、for-each の原則は実際には通常の for ループと反復子を使用することです。

3.11 try-with-resource

Java では、ファイル操作、IO ストリーム、データベース接続などの非常に高価なリソースについては、使用後に close メソッドでクローズする必要があります。そうしないと、リソースが常にオープンになり、メモリ リークなどの問題が発生する可能性があります。 .

リソースを閉じる一般的な方法は、finally ブロックでリソースを解放することです。つまり、close メソッドを呼び出します。たとえば、次のようなコードを書くことがよくあります。

public static void main(String[] args) {
    BufferedReader br = null;
    try {
        String line;
        br = new BufferedReader(new FileReader("d:\\hollischuang.xml"));
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        // handle exception
    } finally {
        try {
            if (br != null) {
                br.close();
            }
        } catch (IOException ex) {
            // handle exception
        }
    }
}

Java 7 以降、jdk はリソースを閉じるためのより良い方法を提供します. 上記のコードを書き直すには、try-with-resources ステートメントを使用します. 効果は次のとおりです:

public static void main(String... args) {
    try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) {
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        // handle exception
    }
}

以前は IOUtils を使ってストリームを閉じていましたが、finally に大量のコードを記述する方法は使用していませんが、この新しいシンタックス シュガーはより洗練されているようです。

上記のコードを逆コンパイルして、その背後にある原則を確認します。

public static transient void main(String args[])
    {
        BufferedReader br;
        Throwable throwable;
        br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"));
        throwable = null;
        String line;
        try
        {
            while((line = br.readLine()) != null)
                System.out.println(line);
        }
        catch(Throwable throwable2)
        {
            throwable = throwable2;
            throw throwable2;
        }
        if(br != null)
            if(throwable != null)
                try
                {
                    br.close();
                }
                catch(Throwable throwable1)
                {
                    throwable.addSuppressed(throwable1);
                }
            else
                br.close();
            break MISSING_BLOCK_LABEL_113;
            Exception exception;
            exception;
            if(br != null)
                if(throwable != null)
                    try
                    {
                        br.close();
                    }
                    catch(Throwable throwable3)
                      {
                        throwable.addSuppressed(throwable3);
                    }
                else
                    br.close();
        throw exception;
        IOException ioexception;
        ioexception;
    }
}

実際、その背後にある原理も非常に単純で、コンパイラーは、私たちが実行しなかったリソースを閉じる操作を実行してくれます。

したがって、シンタックス シュガーの機能はプログラマーの使用を容易にすることですが、最終的にはコンパイラーが理解できる言語に変換する必要があることが再確認されました。

3.12ラムダ式

ラムダ式に関しては、インターネット上でシンタックス シュガーではないという人もいるので、疑う人もいるかもしれません。実は、この発言を訂正したい。

Labmda 式は、匿名内部クラスの構文糖衣ではありませんが、構文糖衣でもあります。実際の実装方法は、JVM の最下層で提供されるいくつかのラムダ関連 API に依存しています。

最初に単純なラムダ式を見てみましょう。リストを反復処理します。

public static void main(String... args) {
    List<String> strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");

    strList.forEach( s -> { System.out.println(s); } );
}

内部クラスのシンタックス シュガーではないというのはなぜですか? 前に、内部クラスはコンパイル後に 2 つのクラス ファイルを持つと言いましたが、ラムダ式を含むクラスはコンパイル後に 1 つのファイルしか持ちません。

逆コンパイルされたコードは次のとおりです。

public static /* varargs */ void main(String ... args) {
    ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
    strList.forEach((Consumer<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
}

private static /* synthetic */ void lambda$main$0(String s) {
    System.out.println(s);
}

forEach メソッドでは実際に java.lang.invoke.LambdaMetafactory#metafactory メソッドが呼び出され、メソッドの第 4 パラメーター implMethod でメソッドの実装が指定されていることがわかります。lambda$main$0 メソッドが実際にここで出力のために呼び出されていることがわかります。

少し複雑なものを見てみましょう。最初にリストをフィルタリングしてから出力します。

public static void main(String... args) {
    List<String> strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");

    List HollisList = strList.stream().filter(string -> string.contains("Hollis")).collect(Collectors.toList());

    HollisList.forEach( s -> { System.out.println(s); } );
}

逆コンパイルされたコードは次のとおりです。

public static /* varargs */ void main(String ... args) {
    ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
    List<Object> HollisList = strList.stream().filter((Predicate<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, lambda$main$0(java.lang.String ), (Ljava/lang/String;)Z)()).collect(Collectors.toList());
    HollisList.forEach((Consumer<Object>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$1(java.lang.Object ), (Ljava/lang/Object;)V)());
}

private static /* synthetic */ void lambda$main$1(Object s) {
    System.out.println(s);
}

private static /* synthetic */ boolean lambda$main$0(String string) {
    return string.contains("Hollis");
}

2 つのラムダ式は、それぞれ lambda$main$1 メソッドと lambda$main$0 メソッドを呼び出します。

したがって、ラムダ式の実装は、実際にはいくつかの基礎となる API に依存しています。コンパイル フェーズ中に、コンパイラはラムダ式を desugar し、内部 API を呼び出す方法に変換します。

遭遇する可能性のある4つのピット

4.1ジェネリック - ジェネリックがオーバーロードに遭遇したとき

public class GenericTypes {

    public static void method(List<String> list) {  
        System.out.println("invoke method(List<String> list)");  
    }  

    public static void method(List<Integer> list) {  
        System.out.println("invoke method(List<Integer> list)");  
    }  
}  

上記のコードでは、List と List というパラメーターの型が異なるため、オーバーロードされた関数が 2 つありますが、このコードはコンパイルできません。前に述べたように、パラメーター List と List はコンパイル後に消去され、同じネイティブ型の List になります. 消去アクションにより、これら 2 つのメソッドの機能シグネチャがまったく同じになります.

4.2ジェネリック - ジェネリックが catch に遭遇したとき

ジェネリック型パラメーターは、Java 例外処理の catch ステートメントでは使用できません。例外処理は実行時に JVM によって実行されるためです。型情報が消去されるため、JVM は MyException<String> と MyException<Integer> の 2 つの例外型を区別できません。

4.3ジェネリック - 静的変数がジェネリックに含まれる場合

public class StaticTest{
    public static void main(String[] args){
        GT<Integer> gti = new GT<Integer>();
        gti.var=1;
        GT<String> gts = new GT<String>();
        gts.var=2;
        System.out.println(gti.var);
    }
}
class GT<T>{
    public static int var=0;
    public void nothing(T x){}
}

上記のコードの出力は次のとおりです。型消去により、すべてのジェネリック クラス インスタンスが同じバイトコードに関連付けられ、ジェネリック クラスのすべての静的変数が共有されます。

4.4オートボクシングとアンボクシング - オブジェクトの等価性の比較

public static void main(String[] args) {
    Integer a = 1000;
    Integer b = 1000;
    Integer c = 100;
    Integer d = 100;
    System.out.println("a == b is " + (a == b));
    System.out.println(("c == d is " + (c == d)));
}

出力結果:

a == b is false
c == d is true

Java 5 では、メモリを節約してパフォーマンスを向上させるために、Integer の操作に新しい機能が導入されました。整数オブジェクトは、同じオブジェクト参照を使用してキャッシュと再利用を可能にします。

-128 から +127 の範囲の整数値で動作します。

オートボクシングにのみ適用されます。コンストラクターを使用してオブジェクトを作成することは適用されません。

4.5強化された for ループ

for (Student stu : students) {    
    if (stu.getId() == 2)     
        students.remove(stu);    
}

ConcurrentModificationException がスローされます。

Iterator は独立したスレッドで動作し、ミューテックス ロックを所有します。Iterator が作成された後、元のオブジェクトを指す単一リンクのインデックス テーブルが作成されます。元のオブジェクトの数が変更されても、インデックス テーブルの内容は同期的に変更されないため、インデックス ポインタが逆方向に移動すると、 iterate. オブジェクトが見つからないため、フェイル ファストの原則に従って、Iterator はすぐに java.util.ConcurrentModificationException をスローします。

したがって、Iterator は、反復オブジェクトが動作中に変更されることを許可しません。ただし、Iterator 自体のメソッド remove() を使用してオブジェクトを削除できます。Iterator.remove() メソッドは、現在の反復オブジェクトを削除する間、インデックスの一貫性を維持します。

5 まとめ

前のセクションでは、Java で一般的に使用される 12 のシンタックス シュガーを紹介しました。スペースの問題により、StringBuilder に基づく文字列連結など、他の一般的なシンタックス シュガーがあります. Java 10 の var キーワードは、インテリジェントな型推論を使用してローカル変数を宣言するため、ここでは言及しません.

いわゆるシンタックス シュガーは、開発を容易にするために開発者に提供される一種の構文です。ただし、この構文は開発者だけが知っています。実行するには、脱糖する必要があります。つまり、JVM が認識する構文に変換する必要があります。

文法を脱糖すると、私たちが毎日使用する便利な文法が、実際には他のより単純な文法で構成されていることがわかります。

これらのグラマティック シュガーを使用すると、日々の開発の効率を大幅に向上させることができますが、同時に過剰な使用を避けることもできます。ピットに落ちないように、使用する前に原理を理解するのが最善です。

おすすめ

転載: blog.csdn.net/qq_37284798/article/details/129681119