『java解惑』読書ノート8——もっと多くの謎

9048 ワード

1.Javaでの非表示(hide):
質問:
次のプログラムはjavaの非表示を示すために使用されます.コードは次のとおりです.
class Base{
    public String className = "Base";
}

class Derived extends Base{
    private String className = "Derived";
}

public class Test{
    
    public static void main(String[] args){
        System.out.println(new Derived().className);
    }
}

出力Baseを印刷すべきだと思っている人もいれば、Derivedクラスのコンパイルが間違っていると思っている人もいます.同名の変数アクセス制御権限は親クラスよりも厳しいからです.
実際の状況は確かにプログラムのコンパイルが間違っていますが、Derivedクラスのコンパイルが間違っているのではなく、main関数の印刷文脈で間違っています.DerivedクラスのclassName属性はプライベートなのでアクセスできません.
理由:
JAvaでは、子クラスと親クラスが同じメソッド署名を持つメソッドをメソッド上書きと呼び、メソッド上書きにおける子クラスメソッドのアクセス制御権限を親クラスに厳しくすることはできないとともに、子クラスメソッドも親メソッドにより多くの異常を投げ出すことはできない.
一方、子と親が同じ名前の変数を持つことを変数非表示(hide)と呼び、変数非表示はアクセス制御権限の制限がないため、BaseクラスのclassName属性は共通であり、子クラスDerivedクラスは継承できるが、子クラスDerivedには同じ名前のclassName変数があるため、プライベートであっても、DerivedクラスのclassName変数は、親ベースクラスのclassName変数を非表示にします.
結論:
上記のプログラムの変数隠しによるコンパイルエラーを解決するには、次の2つの解決方法があります.
方法1:タイプ変換を使用して子を親に変換します.コードは次のとおりです.
class Base{
    public String className = "Base";
}

class Derived extends Base{
    private String className = "Derived";
}

public class Test{
    
    public static void main(String[] args){
        System.out.println(((Base)new Derived()).className);
    }
}

このときプログラムの印刷出力結果はBaseとなります.メソッド2:メソッドを使用して、変数の代わりに非表示を上書きします.コードは次のとおりです.
class Base{
    public String getClassName() {
        return "Base";
    }
}

class Derived extends Base{
    public String getClassName() {
        return "Derived";
    }
}

public class Test{
    
    public static void main(String[] args){
        System.out.println(new Derived().getClassName());
    }
}
この時点でプログラムの印刷結果はDerivedとなる.
JAvaでは、プログラム内でドメイン、メンバータイプ、さらには静的メソッドを非表示にすることができますが、非表示による問題は通常混乱します(非表示のドメインは継承を阻止されます)、同時に非表示もプログラム設計の原則におけるリス置換の原則に違反します.
2.javaでのマスク(Obscure):
質問:
次のプログラムはjavaのマスクを実証するために使用されます.コードは次のとおりです.
class X{
    static class Y{
        static String Z = "Black";
    }
    static C Y = new C();
}

class C{
    String Z = "White";
}

public class Test{
    
    public static void main(String[] args){
        System.out.println(X.Y.Z);
    }
}
プログラムの印刷出力BlackかWhiteかは、多くの人が確定できないと信じています.通常、コンパイラは曖昧なプログラムを拒否し、プログラムを変更すると曖昧に見えるので、コンパイラに拒否されるべきですが、実際にはプログラムが正常に実行され、印刷出力結果はWhiteです.
理由:
JAvaの変数は、同じ名前を持つタイプをマスクすることができます.彼女たちが同じ役割範囲内にある限り、この名前が変数とタイプが許可されている範囲に使用される場合、変数、すなわち変数マスクタイプ、同様に、1つの変数または1つのタイプがパケットをマスクすることができます.マスクは、2つの名前が異なる名前空間に位置する唯一の名前再利用形式である.
上記のプログラムコードは、Yがタイプ名であり変数名であり、同じ役割範囲にあるため、変数宣言の前に変数名がマスクされるが、変数宣言の前に変数名がマスクされるため、変数マスクタイプという原則をちょうど示している.
結論:
Javaのネーミング仕様に従うと、マスクを効果的に回避できます.javaのネーミング仕様は次のとおりです.
(1).変数名は通常、アルファベットで小文字のアルパカの命名法である.
(2).タイプ名は通常、頭文字の大文字のアルパカの命名法である.パッケージ名は全部小文字のはずです.
(3).定数名はすべて大文字でなければなりません(複数の単語は下線で直接接続されています).
(4).単一の大文字は、汎用インタフェースMapのようにタイプパラメータにのみ使用できます.
従って、上記のプログラムをネーミング仕様に従って書き換えると、以下のようなコードで出力Blackを曖昧に印刷することができる.
class Ex{
    static class Why{
        static String Z = "Black";
    }
    static See y = new See();
}

class See{
    String Z = "White";
}

public class Test{
    
    public static void main(String[] args){
        System.out.println(Ex.Why.Z);
    }
}
javaネーミング仕様に従うことがマスクを解決する最良の方法であるが、この方法に加えて、X,Y,Cの3種類の名前の変更が許されず、反射が許されない場合は、次の3種類の反射印刷出力Blackを使用することもできる.
方法1:
巧みなタイプ変換、コードは以下の通りです.
class X{
    static class Y{
        static String Z = "Black";
    }
    static C Y = new C();
}

class C{
    String Z = "White";
}

public class Test{
    
    public static void main(String[] args){
        System.out.println(((X.Y)null).Z);
    }
}
タイプ変換時に変換式オブジェクトの前にあるのはタイプのみ変数またはオブジェクトではないため、コンパイラは自動的に適切な選択を行うが、この例のこの方法はアクセスする静的ドメインにのみ有効であり、一定の限界がある.
方法2:
継承を使用します.コードは次のとおりです.
class X{
    static class Y{
        static String Z = "Black";
    }
    static C Y = new C();
}

class C{
    String Z = "White";
}

public class Test{
    static class Xy extends X.Y{}
    public static void main(String[] args){
        System.out.println(Xy.Z);
    }
}
はクラスのみを継承できるため、コンパイラは継承された変数ではなく、マスクを迂回することを区別することができます.
方法3:
汎用を使用します.コードは次のとおりです.
class X{
    static class Y{
        static String Z = "Black";
    }
    static C Y = new C();
}

class C{
    String Z = "White";
}

public class Test{
    public static <T extends X.Y> void main(String[] args){
        System.out.println(T.Z);
    }
}

汎用的な上境界と下境界はextendsのような役割を果たすことができる.
3.javaでのマスク(shadow):
質問:
次のコードはjavaのマスクを示します.コードは次のとおりです.
import static java.util.Arrays.toString;

public class Test{
    public static void main(String[] args){
        printArgs(1, 2, 3, 4, 5);
    }
    
    static void printArgs(Object... args){
        System.out.println(toString(args));
    }
}
上記コードにはjavaが静的にインポートする.util.ArraysクラスのtoString(Object[])メソッドでは、所与の配列を印刷出力することが望ましいので、プログラム印刷出力[1,2,3,4,5]が望ましい.
しかし、実際の状況は、プログラムコンパイルエラーThe method toString()in the type Object is not applicable for the arguments(Object[])である.
理由:
Javaでのマスクとは、1つの変数、メソッド、またはタイプが、閉じたテキスト範囲内の同じ名前のすべての変数、メソッド、またはタイプをそれぞれマスクできることを意味します.JAvaの同名ローカル変数優先同名グローバル変数が最も一般的な例です.
コンパイラが実行期間中に呼び出されるメソッドを選択する際、最初に行うことは、そのメソッドが確実に見つかる範囲内で選択することであり、コンパイラは適切な名前を持つメソッドを含む最小閉じた範囲内で選択することであり、上記プログラムにおける選択メソッドの最小範囲はTestクラスであり、Objectから継承されたtoStringメソッドを含む静的に導入されたtoStringメソッドは、Objectから継承された同名のメソッドによって適切に隠されています.
1つの宣言が別の宣言を非表示にすると、単純な名前は、非表示宣言に参照されたエンティティ、すなわち、その範囲に属するメンバーが静的インポートよりも優先されることを愛します.
結論:
上記のプログラムの問題はjavaマスクによるものであることが分かった後、解決が比較的容易になり、静的インポートの代わりに通常のインポート宣言を使用し、コードは以下の通りである.
import java.util.Arrays;

public class Test{
    public static void main(String[] args){
        printArgs(1, 2, 3, 4, 5);
    }
    
    static void printArgs(Object... args){
        System.out.println(Arrays.toString(args));
    }
}
javaではマスク(shadow)とマスク(obscure)が非常に似ており、多くの人が混同しているが、両者の違いは以下の通りである.
≪マスキング|Shadows|emdw≫:1つの宣言は、同じタイプの別の宣言のみをマスキングできます.1つのタイプ宣言は、別のタイプ宣言をマスキングできます.1つのメソッド宣言は、別のメソッド宣言を隠すことができます.1つの変数宣言は、別の変数宣言を非表示にすることができます.
マスク:変数宣言は、タイプとパケット宣言をマスクできます.タイプ宣言は、パッケージ宣言を隠すことができます.
4.条件オペレータ:
質問:
以下のプログラムのデモ条件オペレータはJDK 1にある.4とJDK 1.5以降の変更は、次のようになります.
import java.util.Random;

public class Test{
    private static Random rnd = new Random();
    public static Test flip(){
        return rnd.nextBoolean() ? Heads.INSTANCE : Tails.INSTANCE;
    }
    
    public static void main(String[] args){
        System.out.println(flip());
    }
}

class Heads extends Test{
    private Heads(){}
    public static final Heads INSTANCE = new Heads();
    public String toString(){
        return "heads";
    }
}

class Tails extends Test{
    private Tails(){}
    public static final Tails INSTANCE = new Tails();
    public String toString(){
        return "tails";
    }
}

上記のプログラムはJDK 1を使用していない.5の新しい特性はJDK 1である.5以降のバージョンは正常にコンパイルおよび実行できます.コンパイル時に「-source 1.4」パラメータを使用すると、JDKにJDK 1を使用させます.4それをコンパイルすると、コンパイルエラーが報告されます:incompatible types for?:neither is a subtype of the other......
理由:
条件オペレータ(?:)の動作はJDK 1である.5以前は非常に制限されていたが、2番目と3番目のオペランドが参照タイプである場合、条件オペレータは、HeadsとTailsが互いに相手のサブタイプではないため、2番目と3番目のオペランドタイプがコンパイルエラーと互換性がないことを要求する.
結論:
エラーの原因が分かったら、上記コードをJDK 1にしたい.4でのスムーズなコンパイルでは、オペランドの1つを共通のスーパークラスに変換できます.コードは次のとおりです.
public static Test flip(){
        return rnd.nextBoolean() ? (Test)Heads.INSTANCE : Tails.INSTANCE;
    }
JDK 1.5以降、条件オペレータは、2番目と3番目のオペランドが参照タイプである場合に常に合法的であり、その結果タイプは、T choose(T a,T b)に等価である2つのタイプの最小共通スーパークラスである.
JDK 1.4以降のバージョンでは、条件オペレータという制限による問題は非常に一般的で頻繁であり、コードは次のようなセキュリティ列挙モードを使用して問題を回避することが多い.
import java.util.Random;

public class Test{
    public static final Test HEADS = new Test("heads");
    public static final Test TAILS = new Test("tails");
    private final String name;
    private Test(String name){
        this.name = name;
    }
    public String toString(){
        return name;
    }
    private static Random rnd = new Random();
    public static Test flip(){
        return rnd.nextBoolean() ? HEADS : TAILS;
    }
    
    public static void main(String[] args){
        System.out.println(flip());
    }
}
JDK 1.5以降、javaの列挙を直接使用して作成できます.コードは次のとおりです.
import java.util.Random;

public enum Test{
    HEADS, TAILS;
    public String toString(){
        return name().toLowerCase();
    }
    private static Random rnd = new Random();
    public static Test flip(){
        return rnd.nextBoolean() ? HEADS : TAILS;
    }
    
    public static void main(String[] args){
        System.out.println(flip());
    }
}