首頁 > Java > java教程 > 主體

Java程式設計思想的理解

零下一度
發布: 2017-06-25 10:52:17
原創
1669 人瀏覽過

Java程式設計思想,Java學習必讀經典,不管是初學者還是大牛都值得一讀,這裡總結書中的重點知識,這些知識不僅經常出現在各大知名公司的筆試面試過程中,而且在大型專案開發中也是常用的知識,既有簡單的概念理解題(如is-a關係和has-a關係的區別),也有深入的涉及RTTI和JVM底層反編譯知識。

 


1. Java中的多態性理解(注意與C++區分)

  • Java中除了static方法和final方法(private方法本質上屬於final方法,因為不能被子類別存取)之外,其它所有的方法都是動態綁定,這意味著通常情況下,我們不必判定是否應該進行動態綁定—它會自動發生。

    • final方法會使編譯器產生更有效的程式碼,這也是為什麼說宣告為final方法能在一定程度上提高效能(效果不明顯)。

    • 如果某個方法是靜態的,它的行為就不具有多態性:

      class StaticSuper {public static String staticGet() {return "Base staticGet()";}public String dynamicGet() {return "Base dynamicGet()";}}class StaticSub extends StaticSuper {public static String staticGet() {return "Derived staticGet()";}public String dynamicGet() {return "Derived dynamicGet()";}}public class StaticPolymorphism {public static void main(String[] args) {StaticSuper sup = new StaticSub();System.out.println(sup.staticGet());System.out.println(sup.dynamicGet());}}
      登入後複製


      #輸出:

      Base staticGet()
      Derived dynamicGet()

  • 建構子並不具有多態性,它們實際上是static方法,只不過該static宣告是隱式的。因此,建構子不能夠被override。

  • 在父類別建構子內部呼叫具有多態行為的函數將導致無法預測的結果,因為此時子類別物件還沒初始化,此時呼叫子類別方法不會得到我們想要的結果。

    class Glyph {void draw() {System.out.println("Glyph.draw()");}Glyph() {System.out.println("Glyph() before draw()");draw();System.out.println("Glyph() after draw()");}}class RoundGlyph extends Glyph {private int radius = 1;RoundGlyph(int r) {radius = r;System.out.println("RoundGlyph.RoundGlyph(). radius = " + radius);}void draw() {System.out.println("RoundGlyph.draw(). radius = " + radius);}}public class PolyConstructors {public static void main(String[] args) {new RoundGlyph(5);}}
    登入後複製


    輸出:

    Glyph() before draw()
    RoundGlyph.draw(). radius = 0
    Glyph () after draw()
    RoundGlyph.RoundGlyph(). radius = 5

##為什麼會這樣輸出?這就要明確掌握Java中建構函數的呼叫順序:

(1)在其他任何事物發生之前,將分配給物件的儲存空間初始化成二進位0;

(2)呼叫基類構造函數。從根開始遞歸下去,因為多態性此時呼叫子類別覆寫後的draw()方法(要在呼叫RoundGlyph建構函式之前呼叫),由於步驟1的緣故,我們此時會發現radius的值為0;
(3)依宣告順序呼叫成員的初始化方法;
(4)最後呼叫子類別的建構子。

  • 只有非private方法可以被覆寫,但是還需要密切注意覆寫private方法的現象,這時雖然編譯器不會報錯,但也不會按照我們所期望的來執行,即覆蓋private方法對子類別來說是一個新的方法而非重載方法。因此,在子類別中,新方法名稱最好不要與基底類別的private方法採取相同名字(雖然沒關係,但容易誤解,以為能夠覆蓋基底類別的private方法)。

  • Java類別中屬性域的存取操作都由編譯器解析,因此不是多態的。父類別和子類別的同名屬性都會指派不同的儲存空間,如下:

    // Direct field access is determined at compile time.class Super {public int field = 0;public int getField() {return field;}}class Sub extends Super {public int field = 1;public int getField() {return field;}public int getSuperField() {return super.field;}}public class FieldAccess {public static void main(String[] args) {Super sup = new Sub();System.out.println("sup.filed = " + sup.field + ", sup.getField() = " + sup.getField());Sub sub = new Sub();System.out.println("sub.filed = " + sub.field + ", sub.getField() = " + sub.getField() + ", sub.getSuperField() = " + sub.getSuperField());}}
    登入後複製


    输出:

    sup.filed = 0, sup.getField() = 1
    sub.filed = 1, sub.getField() = 1, sub.getSuperField() = 0

    Sub子类实际上包含了两个称为field的域,然而在引用Sub中的field时所产生的默认域并非Super版本的field域,因此为了得到Super.field,必须显式地指明super.field。

    2. is-a关系和is-like-a关系

    • is-a关系属于纯继承,即只有在基类中已经建立的方法才可以在子类中被覆盖,如下图所示:

      基类和子类有着完全相同的接口,这样向上转型时永远不需要知道正在处理的对象的确切类型,这通过多态来实现。

    • is-like-a关系:子类扩展了基类接口。它有着相同的基本接口,但是他还具有由额外方法实现的其他特性。

      缺点就是子类中接口的扩展部分不能被基类访问,因此一旦向上转型,就不能调用那些新方法。

    3. 运行时类型信息(RTTI + 反射)

    • 概念
      RTTI:运行时类型信息使得你可以在程序运行时发现和使用类型信息。

    • 使用方式
      Java是如何让我们在运行时识别对象和类的信息的,主要有两种方式(还有辅助的第三种方式,见下描述):

      • 一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型,比如Shape s = (Shape)s1;

      • 另一种是“反射”机制,它运行我们在运行时发现和使用类的信息,即使用Class.forName()

      • 其实还有第三种形式,就是关键字instanceof,它返回一个bool值,它保持了类型的概念,它指的是“你是这个类吗?或者你是这个类的派生类吗?”。而如果用==或equals比较实际的Class对象,就没有考虑继承—它或者是这个确切的类型,或者不是。

    • 工作原理
      要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的,这项工作是由称为Class对象的特殊对象完成的,它包含了与类有关的信息。Java送Class对象来执行其RTTI,使用类加载器的子系统实现。

    无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用,获取方式有三种:
    (1)如果你没有持有该类型的对象,则Class.forName()就是实现此功能的便捷途,因为它不需要对象信息;
    (2)如果你已经拥有了一个感兴趣的类型的对象,那就可以通过调用getClass()方法来获取Class引用了,它将返回表示该对象的实际类型的Class引用。Class包含很有有用的方法,比如:

    package rtti;interface HasBatteries{}interface WaterProof{}interface Shoots{}class Toy {Toy() {}Toy(int i) {}}class FancyToy extends Toyimplements HasBatteries, WaterProof, Shoots {FancyToy() {super(1);}}public class RTTITest {static void printInfo(Class cc) {System.out.println("Class name: " + cc.getName() + ", is interface? [" + cc.isInterface() + "]");System.out.println("Simple name: " + cc.getSimpleName());System.out.println("Canonical name: " + cc.getCanonicalName());}public static void main(String[] args) {Class c = null;try {c = Class.forName("rtti.FancyToy"); // 必须是全限定名(包名+类名)} catch(ClassNotFoundException e) {System.out.println("Can't find FancyToy");System.exit(1);}printInfo(c);for(Class face : c.getInterfaces()) {printInfo(face);}Class up = c.getSuperclass();Object obj = null;try {// Requires default constructor.obj = up.newInstance();} catch (InstantiationException e) {System.out.println("Can't Instantiate");System.exit(1);} catch (IllegalAccessException e) {System.out.println("Can't access");System.exit(1);}printInfo(obj.getClass());}}
    登入後複製


    输出:

    Class name: rtti.FancyToy, is interface? [false]
    Simple name: FancyToy
    Canonical name: rtti.FancyToy
    Class name: rtti.HasBatteries, is interface? [true]
    Simple name: HasBatteries
    Canonical name: rtti.HasBatteries
    Class name: rtti.WaterProof, is interface? [true]
    Simple name: WaterProof
    Canonical name: rtti.WaterProof
    Class name: rtti.Shoots, is interface? [true]
    Simple name: Shoots
    Canonical name: rtti.Shoots
    Class name: rtti.Toy, is interface? [false]
    Simple name: Toy
    Canonical name: rtti.Toy

    (3)Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量。比如上面的就像这样:FancyToy.class;来引用。
    这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要置于try语句块中),并且它根除了对forName方法的引用,所以也更高效。类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。


    注意:当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象,初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非final静态域(注意final静态域不会触发初始化操作)进行首次引用时才执行:。而使用Class.forName时会自动的初始化。

    为了使用类而做的准备工作实际包含三个步骤:
    - 加载:由类加载器执行。查找字节码,并从这些字节码中创建一个Class对象
    - 链接:验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
    - 初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。

    这一点非常重要,下面通过一个实例来说明这两者的区别:

    package rtti;import java.util.Random;class Initable {static final int staticFinal = 47;static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);static {System.out.println("Initializing Initable");}}class Initable2 {static int staticNonFinal = 147;static {System.out.println("Initializing Initable2");}}class Initable3 {static int staticNonFinal = 74;static {System.out.println("Initializing Initable3");}}public class ClassInitialization {public static Random rand = new Random(47);public static void main(String[] args) {// Does not trigger initializationClass initable = Initable.class;System.out.println("After creating Initable ref");// Does not trigger initializationSystem.out.println(Initable.staticFinal);// Does trigger initialization(rand() is static method)System.out.println(Initable.staticFinal2);// Does trigger initialization(not final)System.out.println(Initable2.staticNonFinal);try {Class initable3 = Class.forName("rtti.Initable3");} catch (ClassNotFoundException e) {System.out.println("Can't find Initable3");System.exit(1);}System.out.println("After creating Initable3 ref");System.out.println(Initable3.staticNonFinal);}}
    登入後複製


    输出:

    After creating Initable ref
    47
    Initializing Initable
    258
    Initializing Initable2
    147
    Initializing Initable3
    After creating Initable3 ref
    74


    • RTTI的限制?如何突破? — 反射机制
      如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,也就是在编译时,编译器必须知道所有要通过RTTI来处理的类。

    可以突破这个限制吗?是的,突破它的就是反射机制。
    Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了FieldMethod以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()/set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。


    ####反射与RTTI的区别
    当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样),在用它做其他事情之前必须先加载那个类的Class对象,因此,那个类的.class文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络取得。所以RTTI与反射之间真正的区别只在于:对RTTI来说,编译器在编译时打开和检查.class文件(也就是可以用普通方法调用对象的所有方法);而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

    下面的例子是用反射机制打印出一个类的所有方法(包括在基类中定义的方法):

    package typeinfo;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.util.regex.Pattern;// Using reflection to show all the methods of a class.// even if the methods are defined in the base class.public class ShowMethods {private static String usage = "usage: \n" + "ShowMethods qualified.class.name\n" +"To show all methods in class or: \n" +"ShowMethods qualified.class.name word\n" +"To search for methods involving 'word'";private static Pattern p = Pattern.compile("\\w+\\.");public static void main(String[] args) {if(args.length < 1) {System.out.println(usage);System.exit(0);}int lines = 0;try {Class<?> c = Class.forName(args[0]);Method[] methods = c.getMethods();Constructor[] ctors = c.getConstructors();if(args.length == 1) {for(Method method : methods) {System.out.println(p.matcher(method.toString()).replaceAll(""));}for(Constructor ctor : ctors) {System.out.println(p.matcher(ctor.toString()).replaceAll(""));}lines = methods.length + ctors.length;} else {for(Method method : methods) {if(method.toString().indexOf(args[1]) != -1) {
    登入後複製

以上是Java程式設計思想的理解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板