>  기사  >  Java  >  Java 프로그래밍 아이디어의 이해

Java 프로그래밍 아이디어의 이해

零下一度
零下一度원래의
2017-06-25 10:52:171668검색

Java 프로그래밍 아이디어, Java 학습의 필수 고전입니다. 초보자든 전문가든 읽어 볼 가치가 있습니다. 다음은 이 책의 핵심 지식을 요약한 것입니다. 주요 유명 기업의 인터뷰 과정은 물론 대규모 인터뷰 과정 또한 간단한 개념 이해 질문(예: is-a 관계와 has-a 관계의 차이)을 포함하여 프로젝트 개발에서 일반적으로 사용되는 지식입니다. RTTI 및 JVM 기본 디컴파일과 관련된 심층적인 지식.


1. Java의 다형성 이해(C++와 다름)

  • Java의 정적 메소드 및 최종 메소드 외에도(프라이빗 메소드는 서브클래스에서 접근할 수 없기 때문에 본질적으로 최종 메소드입니다) 다른 모든 메소드는 동적으로 바인딩됩니다. 즉, 일반적으로 동적 바인딩을 수행해야 하는지 여부를 결정할 필요가 없습니다. 자동으로 수행됩니다.

    • 최종 메서드를 사용하면 컴파일러가 더 효율적인 코드를 생성할 수 있으므로 최종 메서드를 선언하면 어느 정도 성능이 향상될 수 있습니다(효과는 확실하지 않습니다).

    • 메서드가 정적이면 해당 동작은 다형성이 아닙니다.

      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()

  • Construct 함수는 다형성이 아닙니다. IC, 그들은 실제로는 정적 메서드이지만 정적 선언은 암시적입니다. 따라서 생성자를 재정의할 수 없습니다.

  • 부모 클래스 생성자 내에서 다형성 동작이 포함된 함수를 호출하면 예측할 수 없는 결과가 발생합니다. 하위 클래스 객체가 이때 초기화되지 않았고 이때 하위 클래스 메서드를 호출해도 원하는 결과를 얻을 수 없기 때문입니다.

    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(). 반경 = 0
    Glyph() after draw()
    RoundGlyph.RoundGlyph().

    왜 이렇게 출력되나요? 이를 위해서는 Java에서 생성자의 호출 순서를 명확하게 파악해야 합니다.
(1) 다른 일이 발생하기 전에 객체에 할당된 저장 공간을 바이너리 0으로 초기화합니다.

(2) 기본 클래스 생성자를 호출합니다. 다형성은 현재 서브클래스에서 다루는 draw() 메서드를 호출하기 때문에 루트에서 재귀합니다(RoundGlyph 생성자를 호출하기 전에 호출됨). 이때 반경 값은 0입니다.

(3) 선언 순서대로 멤버 초기화 메서드를 호출합니다.
(4) 마지막으로 하위 클래스의 생성자를 호출합니다.




비개인 메서드만 재정의할 수 있지만, 이때 컴파일러는 오류를 보고하지 않지만 예상대로 실행되지는 않는다는 점에 주의해야 합니다. 즉, 오버로드된 메서드가 아닌 하위 클래스를 위한 새로운 메서드를 덮어쓰는 것입니다. 따라서 서브클래스에서는 베이스 클래스의 프라이빗 메소드와 동일한 이름을 갖지 않는 것이 가장 좋다(상관없더라도 베이스 클래스의 프라이빗 메소드를 오버라이드할 수 있다고 생각하면 오해하기 쉽다).
  • 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.