码迷,mamicode.com
首页 > 编程语言 > 详细

Java重要知识点

时间:2015-12-13 02:11:32      阅读:374      评论:0      收藏:0      [点我收藏+]

标签:

1.Java中除了static方法和final方法之外,其它所有的方法都是动态绑定,如同C++的虚函数,但是我们不需要显示的声明。

  private方法本质上属于final方法(因此不能被子类访问)。

  构造函数本质上属于static方法,只不过该static声明是隐式的。

  final方法会使编译器生成更有效的代码,这也是为什么说声明为final方法能在一定程度上提高性能(效果不明显)。

  如果某个方法是静态的,它的行为就不具有多态性。

  在父类构造函数内部调用具有多态行为的函数将导致无法预测的结果,因为此时子类对象还没初始化,此时调用子类方法不会得到我们想要的结果。

  只有非private方法才可以被覆盖,但覆盖private方法对子类来说是一个新的方法而非重载方法。因此,在子类中,新方法名最好不要与基类的private方法采取同一名字。

  Java类中属性域的访问操作都由编译器解析,因此不是多态的。父类和子类的同名属性都会分配不同的存储空间。为了得到父类的属性field,必须显式地指明super.field。

 

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

  is-a关系属于纯继承,即只有在基类中已经建立的方法才可以在子类中被覆盖。基类和子类有着完全相同的接口,这样向上转型时永远不需要知道正在处理的对象的确切类型,这通过多态来实现。

  is-like-a关系:子类扩展了基类接口。它有着相同的基本接口,但是他还具有由额外方法实现的其他特性。缺点就是子类中接口的扩展部分不能被基类访问,因此一旦向上转型,就不能调用那些新方法。

  has-a关系:某个类包含另一个类或接口的引用。

 

3.Java中构造函数的调用顺序

    a.在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制0。

    b.调用基类构造函数。

        c.按声明顺序调用成员的初始化方法。

    d.最后调用子类的构造函数。

  由于存在表态数据,实际过程为

    b.继承体系的所有静态成员初始化(先父类,后子类)

    c.父类初始化完成(普通成员的初始化-->构造函数的调用)

    d.子类初始化(普通成员-->构造函数)

 

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

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

  Java是如何让我们在运行时识别对象和类的信息的,主要有3种方式

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

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

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

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

  无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用,获取方式有三种:

    a.如果你没有持有该类型的对象,则Class.forName()就是实现此功能的便捷途,因为它不需要对象信息。

    b.如果你已经拥有了一个感兴趣的类型的对象,那就可以通过调用getClass()方法来获取Class引用了,它将返回表示该对象的实际类型的Class引用。

    c.使用类字面常量。比如这样:String.class;来引用。

    这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要置于try语句块中),并且它根除了对forName方法的引用,所以也更高效。类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。

    注意:

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

    而使用Class.forName时会自动的初始化。

  为了使用类而做的准备工作实际包含三个步骤:

    - 加载:由类加载器执行。查找字节码,并从这些字节码中创建一个Class对象

    - 链接:验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。

    - 初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。

  如果不知道某个对象的确切类型,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文件。

 

 5.代理模式与Java中的动态代理

代理模式
  在任何时刻,只要你想要将额外的操作从“实际”对象中分离到不同的地方,特别是当你希望能够很容易地做出修改,从没有使用额外操作转为使用这些操作,或者反过来时,代理就显得很有用(设计模式的关键是封装修改)。例如,如果你希望跟踪对某个类中方法的调用,或者希望度量这些调用的开销,那么你应该怎样做呢?这些代码肯定是你不希望将其合并到应用中的代码,因此代理使得你可以很容易地添加或移除它们。

动态代理
  Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。

测试代码

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Interface {
    void doSomething();
    void somethingElse(String arg);
}

class RealObject implements Interface {
    @Override
    public void doSomething() {
        System.out.println("[RealObject]doSomething.");
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("[RealObject]somethingElse:" + arg);
    }
}

class DynamicProxyHandler implements InvocationHandler {

    private Object mProxy;

    public DynamicProxyHandler(Object proxy) {
        mProxy = proxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("proxy class: " + proxy.getClass());
        System.out.println("method: " + method + ". args: " + args);
        System.out.println();
        if (args != null) {
            for (Object arg : args)
                System.out.println(" " + arg);
        }
        return method.invoke(this.mProxy, args);
    }
}

class SimpleDynamicProxy {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("哈哈");
    }

    public static void main(String[] args) {
        RealObject real = new RealObject();
        consumer(real);
        System.out.println("-- insert a proxy and call again --");
        Interface proxy = (Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[] { Interface.class }, new DynamicProxyHandler(real));
        consumer(proxy);
    }
}

  结果

[RealObject]doSomething.
[RealObject]somethingElse:哈哈
-- insert a proxy and call again --
proxy class: class $Proxy0
method: public abstract void Interface.doSomething(). args: null

[RealObject]doSomething.
proxy class: class $Proxy0
method: public abstract void Interface.somethingElse(java.lang.String). args: [Ljava.lang.Object;@6adcc4e2

 哈哈
[RealObject]somethingElse:哈哈

 

 

6.访问控制权限

Java访问权限修饰词:public、protected、包访问权限(默认访问权限,有时也称friendly)和private。

包访问权限:当前包中的所有其他类对那个成员具有访问权限,但对于这个包之外的所有类,这个成员却是private。

protected:继承访问权限。protected使基类某些成员访问权限赋予派生类。protected也提供包访问权限,即相同包内的其他类都可以访问protected元素。protected指明对类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的”。

 

7.组合和继承之间的选择

  组合和继承都允许在新的类中放置子对象,组合是显式的这样做,而继承则是隐式的做。

  组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。即在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所定义的接口,而非所嵌入对象的接口。为取得此效果,需要在新类中嵌入一个现有类的private对象。但有时,允许类的用户直接访问新类中的组合成分是极具意义的,即将成员对象声明为public。如果成员对象自身都隐藏了具体实现,那么这种做法是安全的。当用户能够了解到你正在组装一组部件时,会使得端口更加易于理解。比如Car对象可由public的Engine对象、Wheel对象、Window对象和Door对象组合。但务必要记得这仅仅是一个特例,一般情况下应该使域成为private。

  在继承的时候,使用某个现有类,并开发一个它的特殊版本。通常,这意味着你在使用一个通用类,并为了某种特殊需要而将其特殊化。稍微思考一下就会发现,用一个“交通工具”对象来构成一部“车子”是毫无意义的,因为“车子”并不包含“交通工具”,它仅是一种交通工具(is-a关系)。

  “is-a”(是一个)的关系是用继承来表达的,而“has-a”(有一个)的关系则是用组合来表达的。

  到底是该用组合还是继承,一个最清晰的判断方法就是问一问自己是否需要从新类向基类进行向上转型,需要的话就用继承,不需要的话就用组合方式。

 

8.final关键字

  当final修饰的是基本数据类型时,它指的是数值恒定不变(就是编译期常量,如果是static final修饰,则强调只有一份),而对对象引用而不是基本类型运用final时,其含义会有一点令人迷惑,因为用于对象引用时,final使引用恒定不变,一旦引用被初始化指向一个对象,就无法再把它指向另一个对象。然而,对象其自身却是可以被修改的,Java并未提供使任何对象恒定不变的途径(但可以自己编写类以取得使对象恒定不变的效果),这一限制同样适用数组,它也是对象。

使用final方法真的可以提高程序效率吗?
  将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里。只要编译器发现一个final方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。

  虚拟机(特别是hotspot技术)能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个final 方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。

  类内所有private 方法都自动成为final。由于我们不能访问一个private 方法,所以它绝对不会被其他方法覆盖(若强行这样做,编译器会给出错误提示)。可为一个private方法添加final指示符,但却不能为那个方法提供任何额外的含义。

记住:只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。

 

 9.

 

 

 

 

参考资料:

《Java编程思想》

http://www.cnblogs.com/lanxuezaipiao/p/4153070.html

Java重要知识点

标签:

原文地址:http://www.cnblogs.com/diysoul/p/5042176.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!