美文网首页
BigData-Java总结大全(二)苏暖人

BigData-Java总结大全(二)苏暖人

作者: 北京大数据苏焕之 | 来源:发表于2018-08-01 21:40 被阅读63次

    ## BigData-Java总结大全(二)区别设计 ##


    1.this与super的区别

    this与super关键字在java中构造函数中的应用:
    **

    super()函数

    **
    super()函数在子类构造函数中调用父类的构造函数时使用,而且必须要在构造函数的第一行,例如:

    class Animal {
    public Animal() {
        System.out.println("An Animal");
    }
    }
    class Dog extends Animal {
    public Dog() {
        super();
        System.out.println("A Dog");
        //super();错误的,因为super()方法必须在构造函数的第一行
        //如果子类构造函数中没有写super()函数,编译器会自动帮我们添加一个无参数的super()
    }
    }
    class Test{
    public static void main(String [] args){
        Dog dog = new Dog();
    }
    }
    

    执行这段代码的结果为:

    An Animal 
    A Dog
    

    定义子类的一个对象时,会先调用子类的构造函数,然后在调用父类的构造函数,如果父类函数足够多的话,会一直调用到最终的父类构造函数,函数调用时会使用栈空间,所以按照入栈的顺序,最先进入的是子类的构造函数,然后才是邻近的父类构造函数,最后再栈顶的是最终的父类构造函数,构造函数执行是则按照从栈顶到栈底的顺序依次执行,所以本例中的执行结果是先执行Animal的构造函数,然后再执行子类的构造函数。

    class Animal {
     private String name;
        public String getName(){
        name = name;
    }
    public Animal(String name) {
        this.name = name;
    }
    }
    class Dog extends Animal {
        public Dog() {
         super(name);
    }
    }
    class Test{
    public static void main(String [] args){
        Dog dog = new Dog("jack");
        System.out.println(dog.getName());
    }
    }
    

    运行结果:

    jack
    

    当父类构造函数有参数时,如果要获取父类的private的成员变量并给其赋值作为子类的结果,此时在定义子类的构造函数时就需要调用父类的构造函数,并传值,如上例所示。

    this()函数

    this()函数主要应用于同一类中从某个构造函数调用另一个重载版的构造函数。this()只能用在构造函数中,并且也只能在第一行。所以在同一个构造函数中this()和super()不能同时出现。
    例如下面的这个例子:

    class Mini extends Car {
    Color color;
    //无参数函数以默认的颜色调用真正的构造函数
    public Mini() {
        this(color.Red);
    }
    //真正的构造函数
    public Mini(Color c){
        super("mini");
        color = c;
    }
    //不能同时调用super()和this(),因为他们只能选择一个
    public Mini(int size) {
        super(size);
        this(color.Red);
    }
    

    所以综上所述,super()与this()的区别主要有以下: 
    不同点: 
    1、super()主要是对父类构造函数的调用,this()是对重载构造函数的调用 
    2、super()主要是在继承了父类的子类的构造函数中使用,是在不同类中的使用;this()主要是在同一类的不同构造函数中的使用 
    
    相同点: 
    1、super()和this()都必须在构造函数的第一行进行调用,否则就是错误的
    

    2.逻辑运算符&&||的区别

    对于&来说,如果左侧条件为false,也会计算右侧条件的值,而对于&&来说,如果左侧的条件为false,则不计算右侧的条件,这种现象被称作短路现象。

    对于|来说,如果左侧条件为false,也会计算右侧条件的值 ,而对于||来说,如果左侧的条件为false,则不计算右侧的条件。

    public class OperationTest2{
    public static void main(String[] args){
    ////逻辑运算符
        int a=5;
        boolean flag=5>4&a++<++a;
        System.out.println("flag="+flag);
        a=5;
        flag=5>4&a++>++a;
        System.out.println("flag="+flag+",a="+a);
        a=5;
        flag=5<4&a++>++a;
        System.out.println("flag="+flag+",a="+a);
        a=5;
        flag=5<4&&a++>++a;// 当 && 的左边为false时,则右边不运算。
        System.out.println("flag="+flag+",a="+a);
        a=5;
        flag=5>4|a++>++a;
        System.out.println("flag="+flag+",a="+a);
        a=5;
        flag=5>4||a++>++a;//当 || 的左边为 true时,则右边不运算。
        System.out.println("flag="+flag+",a="+a);
        
    }
    }
    

    3.dowhile 与while的区别

    while的语法是 while(条件判断){执行函数}

    do while的语法是 do{执行函数}while(条件判断)

    假如 用do while的时候

    int i = -1; 
    do { System.out.print(i)} while (i >= 0);
    

    显示结果是-1。

    用while的时候

    int i = -1; 
    while (i >= 0){ cout << i << endl; };
    

    是没有显示结果的。

    即,do{} while()的时候,是会先执行一遍命令,如果条件满足了,会继续执行;如果条件不满足,那么暂停。用while(){}的时候,先看条件满不满足,如果不满足,就不会执行。

    假如我们给玩家一个提示信息,玩家只有输入yes 之后,才能跳过

    {
    int i;
    System.out.print("请问1+1等于几?");
    do {
        System.out.print( "请输入正确答案:  ");
        Scanner csn = new Scanner(System.in);
      i=csn.next();
    } while (i != 2);
    System.out.print("输入正确,欢迎来到无限恐怖的世界");
    return 0;
    }
    

    在这段命令里,如果你输入的不正确(数值的不等于判断是!=),他就会不停的让你输入正确的答案,即执行while范围内的,如果你输入的是正确答案,那么就跳过执行下面。

    do while适合在要求必须是某一种回答(或满足条件)的情况下,使用。因为如果不满足会不断执行。

    如果换成while,则程序写成:

    public static void main(String []args)
    {
    int i;
    System.out.print("请问1+1等于几?");
    System.out.print("请输入正确答案:");
    Scanner csn = new Scanner(System.in);
     i=csn.next();
    
     while (i != 2) {
        System.out.print( "请输入正确答案:  ");
        i = csn.next();
    };
    System.out.print("输入正确,欢迎来到无限恐怖的世界");
    return 0;
    }
    

    对比而言,则在wihle前,打了一遍wihle范围内的语句。

    即,do while可以少打一遍wihle范围内的语句

    4.方法的重写与重载的区别

    方法重写与方法重载的相同点:

    (1)方法的名称相同
    (2)都可以用于抽象方法和非抽象方法之间。

    方法重写与方法重载的不同点:

    (1)方法重写要求参数签名必须相同,而方法的重载要求参数签名必须不同。
    (2)方法重写要求返回类型必须相同,而方法重载对此没有限制。
    (3)方法重写只能用于子类重写父类的方法,而方法的重载用于同一个类的所有方法(包括从父类中继承的方法)
    (4)方法重写对方法的访问权限和抛出的异常有特殊的要求,而方法的重载对此没有任何限制
    (5)父类的一个方法只能被子类重写一次,而一个方法在所在的类中可以被多次重载。

    5.面向对象与面向过程的区别


    面向过程

    优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

    缺点:没有面向对象易维护、易复用、易扩展

    面向对象

    优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

    缺点:性能比面向过程低

    面向对象程序设计的一些显著的特性包括:

    .程序设计的重点在于数据而不是过程;

    .程序被划分为所谓的对象;

    .数据结构为表现对象的特性而设计;

    .函数作为对某个对象数据的操作,与数据结构紧密的结合在一起;

    .数据被隐藏起来,不能为外部函数访问;

    .对象之间可以通过函数沟通;

    .新的数据和函数可以在需要的时候轻而易举的添加进来;

    .在程序设计过程中遵循由下至上(bottom-up)的设计方法。

    面向对象程序设计在程序设计模式中是一个新的概念,对于不同的人可能意味着不同的内容。我们对面向对象程序设计的定义是"面向对象程序设计是一种方法,这种方法为数据和函数提供共同的独立内存空间,这些数据和函数可以作为模板以便在需要时创建类似模块的拷贝。这样的程序设计方法称为面向对象程序设计。"

    从以上定义可以看到,一个对象被认为是计算机内存中的一个独立区间,在这个区间中保存着数据和能够访问数据的一组操作。因为内存区间是相互独立的,所以对象可以不经修改就应用于多个不同的程序中。

    ## 什么是面向对象程序设计? ##

    面向对象程序设计(OOP)技术汲取了结构化程序设计中好的思想,并将这些思想与一些新的、强大的理念相结合,从而给你的程序设计工作提供了一种全新的方法。通常,在面向对象的程序设计风格中,你会将一个问题分解为一些相互关联的子集,每个子集内部都包含了相关的数据和函数。同时,你会以某种方式将这些子集分为不同等级,而一个对象就是已定义的某个类型的变量。当你定义了一个对象,你就隐含的创建了一个新的数据类型。

    ## 对象 ##

    在一个面向对象的系统中,对象是运行期的基本实体。它可以用来表示一个人或者说一个银行帐户,一张数据表格,或者其它什么需要被程序处理的东西。它也可以用来表示用户定义的数据,例如一个向量,时间或者列表。在面向对象程序设计中,问题的分析一般以对象及对象间的自然联系为依据。如前所述,对象在内存中占有一定空间,并且具有一个与之关联的地址,就像Pascal中的record和 C中的结构一样。

    当一个程序运行时,对象之间通过互发消息来相互作用。例如,程序中包含一个"customer"对象和一个 "account"对象,而customer对象可能会向account对象发送一个消息,查询其银行帐目。每个对象都包含数据以及操作这些数据的代码。即使不了解彼此的数据和代码的细节,对象之间依然可以相互作用,所要了解的只是对象能够接受的消息的类型,以及对象返回的响应的类型,虽然不同的人会以不同的方法实现它们。

    ## 类 ##

    我们刚才提到,对象包含数据以及操作这些数据的代码。一个对象所包含的所有数据和代码可以通过类来构成一个用户定义的数据类型。事实上,对象就是类类型(class type)的变量。一旦定义了一个类,我们就可以创建这个类的多个对象,每个对象与一组数据相关,而这组数据的类型在类中定义。因此,一个类就是具有相同类型的对象的抽象。例如,芒果、苹果和桔子都是fruit类的对象。类是用户定义的数据类型,但在一个程序设计语言中,它和内建的数据类型行为相同。比如创建一个类对象的语法和创建一个整数对象的语法一模一样。

    数据抽象和封装

    把数据和函数包装在一个单独的单元(称为类)的行为称为封装。数据封装是类的最典型特点。数据不能被外界访问,只能被封装在同一个类中的函数访问。这些函数提供了对象数据和程序之间的接口。避免数据被程序直接访问的概念被称为"数据隐藏"。

    抽象指仅表现核心的特性而不描述背景细节的行为。类使用了抽象的概念,并且被定义为一系列抽象的属性如尺寸、重量和价格,以及操作这些属性的函数。类封装了将要被创建的对象的所有核心属性。因为类使用了数据抽象的概念,所以它们被称为抽象数据类型(ADT)。

    ## 封装 ##

    封装机制将数据和代码捆绑到一起,避免了外界的干扰和不确定性。它同样允许创建对象。简单的说,一个对象就是一个封装了数据和操作这些数据的代码的逻辑实体。

    在一个对象内部,某些代码和(或)某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

    ## 继承 ##

    继承是可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。例如,知更鸟属于飞鸟类,也属于鸟类。这种分类的原则是,每一个子类都具有父类的公共特性。

    在 OOP中,继承的概念很好的支持了代码的重用性(reusability),也就是说,我们可以向一个已经存在的类中添加新的特性,而不必改变这个类。这可以通过从这个已存在的类派生一个新类来实现。这个新的类将具有原来那个类的特性,以及新的特性。而继承机制的魅力和强大就在于它允许程序员利用已经存在的类(接近需要,而不是完全符合需要的类),并且可以以某种方式修改这个类,而不会影响其它的东西。

    注意,每个子类只定义那些这个类所特有的特性。而如果没有按级分类,每类都必须显式的定义它所有的特性。

    ## 多态 ##

    多态是OOP的另一个重要概念。多态的意思是事物具有不同形式的能力。举个例子,对于不同的实例,某个操作可能会有不同的行为。这个行为依赖于所要操作数据的类型。比如说加法操作,如果操作的数据是数,它对两个数求和。如果操作的数据是字符串,则它将连接两个字符串。

    多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。多态在实现继承的过程中被广泛应用。

    面向对象程序设计语言支持多态,术语称之为"one interface multiple method(一个接口,多个实现)"。简单来说,多态机制允许通过相同的接口引发一组相关但不相同的动作,通过这种方式,可以减少代码的复杂度。在某个特定的情况下应该作出怎样的动作,这由编译器决定,而不需要程序员手工干预。

    在多函数程序中,许多重要的数据被声明为全局变量,这样它们才可以被所有的函数访问。每个函数又可以具有它自己的局部变量。全局变量很容易被某个函数不经意之间改变。而在一个大程序中,很难分辨每个函数都使用了哪些变量。如果我们需要修改某个外部数据的结构,我们就要修改所有访问这个数据的函数。这很容易导致bug的产生。

    在结构化程序设计中,另一个严重的缺陷是不能很好的模拟真实世界的问题。这是因为函数都是面向过程的,而不是真正的对应于问题中的各个元素。

    面向过程的程序设计的一些特点如下:

    .强调做(算法);

    .大程序被分隔为许多小程序,这些小程序称为函数;

    .大多数函数共享全局数据;

    .数据开放的由一个函数流向另一个函数。函数把数据从一种形式转换为另一种形式。

    采用由上至下的程序设计方法。

    ## 动态绑定 ##

    绑定指的是将一个过程调用与相应代码链接起来的行为。动态绑定的意思是,与给定的过程调用相关联的代码只有在运行期才可知。它与多态和继承的联系极为紧密。一个多态引用的函数调用决定于这个引用的动态类型。

    ## 消息传递 ##

    一个面向对象的程序由许多对象组成,这些对象之间需要相互沟通。因此,在面向对象程序设计语言中,程序设计的主要步骤如下:

    1、创建类,这些类定义了对象及其行为;

    2、由类定义创建对象;

    3、建立对象之间的通讯。

    对象之间通过收发信息相互沟通,这一点类似于人与人之间的信息传递。信息传递的概念使得真实世界的直接模拟更易于和建立系统交流。

    对于某个特定对象来说,消息就是请求执行某个过程,因此,消息的接收对象会调用一个函数(过程),以产生预期的结果。传递的消息的内容包括接收消息的对象的名字,需要调用的函数的名字,以及必要的信息。

    对象就有一个生命周期。它们可以被创建和销毁。只要对象正处于其生存期,就可以与其进行通讯。

    ## OOP的优点 ##

    OOP具有许多优点,无论是对于程序设计者或者用户来说都是如此。面向对象为软件产品扩展和质量保证中的许多问题提供了解决办法。这项技术能够大大提高程序员的生产力,并可提高软件的质量以及降低其维护费用。其主要的优点陈列于下:

    1、通过继承,我们可以大幅减少多余的代码,并扩展现有代码的用途;

    2、我们可以在标准的模块上(这里所谓的"标准"指程序员之间彼此达成的协议)构建我们的程序,而不必一切从头开始。这可以减少软件开发时间并提高生产效率;

    3、数据隐藏的概念帮助程序员们保护程序免受外部代码的侵袭;

    4、允许一个对象的多个实例同时存在,而且彼此之间不会相互干扰;

    5、允许将问题空间中的对象直接映射到程序中;

    6、基于对象的工程可以很容易的分割为独立的部分;

    7、以数据为中心的设计方法允许我们抓住可实现模型的更多细节;

    8、面向对象的系统很容易从小到大逐步升级;

    9、对象间通讯所使用的消息传递技术与外部系统接口部分的描述更简单;

    10、更便于控制软件复杂度。

    当需要将以上所说的所有特性有机的结合于一个面向对象系统中,它们之间的相对重要性就取决于工程的类型和程序员的喜好。为了获得上述的某些优势,必须考虑很多事情。例如,对象库必须可以被重用。技术还在不停的发展,现有的产品也会很快的更新换代。如果重用没有能够实现,那么就需要进行严格的控制和管理。

    易于使用的开发软件往往难以编写。面向对象程序设计工具有望解决这个问题。

    面向对象程序设计语言

    面向对象技术并不是某个特定语言的特权。如同结构化程序设计一样,OOP概念可以在很多语言比如C和Pascal中实现。但是,当程序越来越大时,程序设计工作会变得拙劣而混乱。而一个支持OOP概念的程序设计语言则可以让一切变得简单。

    一个语言必须支持几个主要的OOP概念才能称其是面向对象的。根据所支持的OOP特性,语言可以分为以下两类:

    1、基于对象的程序设计语言;

    2、面向对象的程序设计语言。

    基于对象的程序设计语言仅支持封装和对象辨识。

    一个面向对象的程序设计语言所要支持的重要特性如下:

    .数据封装

    .数据隐藏和访问机制

    .对象的自动初始化和清除

    .操作符重载

    支持对象风格程序设计的语言称为基于对象的程序设计语言。它们不支持继承和动态绑定。

    Ada就是一个典型的基于对象的程序设计语言。

    面向对象的程序设计不仅仅包含基于对象程序设计的特性,还支持继承和动态绑定。

    ## OOP的应用 ##

    OOP最有前途的应用领域如下:

    1、实时系统;

    2、仿真和建模;

    3、面相对象数据库;

    4、超文本、超媒体和扩展文本;

    5、AI和专家系统;

    6、神经网络和并行程序设计;

    7、决策支持和办公自动化系统;

    8、CIM/CAM/CAD系统。

    业务逻辑比较简单的适合用面向过程,例如统计当前在线用户数,一条SQL语句就可以搞定的没有多大必要用面向对象,也没有必要用什么设计模式,那样就是简单问题复杂化了。

    业务逻辑比较复杂的适合用面向对象,这样当需求有变化时,维护起来会比较方便。

    面向对象的局部是面向过程的,比如某个类里面某个方法里面实际就是面向过程的,而面向过程会借用面向对象的一些优点来进行优化。

    网上发现了一篇文章,说了一下OP与OO的不同,并且打了一个比喻,通俗易懂。

    有人这么形容OP和OO的不同:用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,北京叫盖饭,东北叫烩饭,广东叫碟头饭,就是在一碗白米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。我觉得这个比喻还是比较贴切的。

    蛋炒饭制作的细节,我不太清楚,因为我没当过厨师,也不会做饭,但最后的一道工序肯定是把米饭和鸡蛋混在一起炒匀。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。

    蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。

    到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。

    盖浇饭的好处就是"菜""饭"分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是"可维护性"比较好,"饭" 和"菜"的耦合度比较低。蛋炒饭将"蛋""饭"搅和在一起,想换"蛋""饭"中任何一种都很困难,耦合度很高,以至于"可维护性"比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。

    6.向上转型与向下转型

    java的向上转型与向下转型


    向上转型:

    首先要有一个父类,一个子类,

    Person p=new Person();

    p=new Student();

    OK,这就是向上转型,可以简化成:Person p=new Student();

    1) p是Person的引用,指向Student的对象,p不是对象;

    2) p只能调用父类中有的成员变量与成员函数,子类中新有的方法与变量p不能使用,而执行的主体是子类的主体。

    例如

    image

    p可以调用eat方法,执行的是Student里的函数主体;也可以调用sleep方法,子类里没有sleep方法,所以执行父类里函数主体;但不能调用read方法,因为Person没有read方法;

    向下转型:

    向下转型是在向上转型的基础上加一行

    Student s=(Student)p;

    同样s也不是对象,是引用

    s可以调用父类与子类里的所有方法,都执行子类中的函数主体,子类中没有的,则执行父类中的函数主体。

    总结:

    1、父类引用可以指向子类对象,子类引用不能指向父类对象。

    2、把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转 型。

    如Father father = new Son();

    3、把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转型。

    如father就是一个指向子类对象的父类引用,把father赋给子类引用son 即Son son =(Son)father;

    其中father前面的(Son)必须添加,进行强制转换。

    4、upcasting 会丢失子类特有的方法,但是子类overriding 父类的方法,子类方法有效

    7. final、finally、finalize的区别

    final、finally和finalize的区别是什么?

    这是一道再经典不过的面试题了,我们在各个公司的面试题中几乎都能看到它的身影。
    final、finally和finalize虽然长得像孪生三兄弟一样,但是它们的含义和用法却是大相径庭。
    这一次我们就一起来回顾一下这方面的知识。

    final关键字

    我们首先来说说final。它可以用于以下四个地方:

    1. 定义变量,包括静态的和非静态的。
    2. 定义方法的参数。
    3. 定义方法。
    4. 定义类。
    

    我们依次来回顾一下每种情况下final的作用。首先来看第一种情况,如果final修饰的是一
    个基本类型,就表示这个变量被赋予的值是不可变的,即它是个常量;如果final修饰的是
    一个对象,就表示这个变量被赋予的引用是不可变的,这里需要提醒大家注意的是,不可改
    变的只是这个变量所保存的引用,并不是这个引用所指向的对象。在第二种情况下,final的
    含义与第一种情况相同。实际上对于前两种情况,有一种更贴切的表述final的含义的描述,
    那就是,如果一个变量或方法参数被final修饰,就表示它只能被赋值一次,但是JAVA虚拟
    机为变量设定的默认值不记作一次赋值。

    被final修饰的变量必须被初始化。初始化的方式有以下几种:

    1. 在定义的时候初始化。
    2. final变量可以在初始化块中初始化,不可以在静态初始化块中初始化。
    3. 静态final变量可以在静态初始化块中初始化,不可以在初始化块中初始化。
    4. final变量还可以在类的构造器中初始化,但是静态final变量不可以。
    5. 
    

    通过下面的代码可以验证以上的观点:

    Java代码
    public class FinalTest {
    // 在定义时初始化
    public final int A = 10;
    public final int B;
    // 在初始化块中初始化
    {
    B = 20;
    }
    // 非静态final变量不能在静态初始化块中初始化
    // public final int C;
    // static {
    // C = 30;
    // }
    // 静态常量,在定义时初始化
    public static final int STATIC_D = 40;
    public static final int STATIC_E;
    // 静态常量,在静态初始化块中初始化
    static {
    STATIC_E = 50;
    }
    // 静态变量不能在初始化块中初始化
    // public static final int STATIC_F;
    // {
    // STATIC_F = 60;
    // }
    public final int G;
    // 静态final变量不可以在构造器中初始化
    // public static final int STATIC_H;
    // 在构造器中初始化
    public FinalTest() {
    G = 70;
    // 静态final变量不可以在构造器中初始化
    // STATIC_H = 80;
    // 给final的变量第二次赋值时,编译会报错
    // A = 99;
    // STATIC_D = 99;
    }
    // final变量未被初始化,编译时就会报错
    // public final int I;
    // 静态final变量未被初始化,编译时就会报错
    // public static final int STATIC_J;
    }
    

    我们运行上面的代码之后出了可以发现final变量(常量)和静态final变量(静态常量)未
    被初始化时,编译会报错。

    用final修饰的变量(常量)比非final的变量(普通变量)拥有更高的效率,因此我们在实
    际编程中应该尽可能多的用常量来代替普通变量,这也是一个很好的编程习惯。
    当final用来定义一个方法时,会有什么效果呢?正如大家所知,它表示这个方法不可以被
    子类重写,但是它这不影响它被子类继承。我们写段代码来验证一下:

    Java代码
    class ParentClass {
    public final void TestFinal() {
    System.out.println("父类--这是一个final方法");
    }
    }
    public class SubClass extends ParentClass {
    /**
    * 子类无法重写(override)父类的final方法,否则编译时会报错
    */
    // public void TestFinal() {
    // System.out.println("子类--重写final方法");
    // }
    public static void main(String[] args) {
    SubClass sc = new SubClass();
    sc.TestFinal();
    }
    }
    

    这里需要特殊说明的是,具有private访问权限的方法也可以增加final修饰,但是由于子类
    无法继承private方法,因此也无法重写它。编译器在处理private方法时,是按照final方法
    来对待的,这样可以提高该方法被调用时的效率。不过子类仍然可以定义同父类中的
    private方法具有同样结构的方法,但是这并不会产生重写的效果,而且它们之间也不存在必
    然联系。

    最后我们再来回顾一下final用于类的情况。这个大家应该也很熟悉了,因为我们最常用的
    String类就是final的。由于final类不允许被继承,编译器在处理时把它的所有方法都当作
    final的,因此final类比普通类拥有更高的效率。而由关键字abstract定义的抽象类含有必须
    由继承自它的子类重载实现的抽象方法,因此无法同时用final和abstract来修饰同一个类。
    同样的道理,final也不能用来修饰接口。

    final的类的所有方法都不能被重写,但这并不表示final的类的属性(变量)值也是不可改变的,要想做到final类的属性值不可改变,必须

    给它增加final修饰,请看下面的例子:

    Java代码
    public final class FinalTest {
    int i = 10;
    public static void main(String[] args) {
    FinalTest ft = new FinalTest();
    ft.i = 99;
    System.out.println(ft.i);
    }
    }
    

    运行上面的代码试试看,结果是99,而不是初始化时的10。

    finally语句

    接下来我们一起回顾一下finally的用法。这个就比较简单了,它只能用在try/catch语句中,
    并且附带着一个语句块,表示这段语句最终总是被执行。请看下面的代码:

    Java代码
    public final class FinallyTest {
    public static void main(String[] args) {
    try {
    throw new NullPointerException();
    } catch (NullPointerException e) {
    System.out.println("程序抛出了异常");
    } finally {
    System.out.println("执行了finally语句块");
    }
    }
    }
    

    运行结果说明了finally的作用:

    1. 程序抛出了异常
    2. 执行了finally语句块
    

    请大家注意,捕获程序抛出的异常之后,既不加处理,也不继续向上抛出异常,并不是良好
    的编程习惯,它掩盖了程序执行中发生的错误,这里只是方便演示,请不要学习。
    那么,有没有一种情况使finally语句块得不到执行呢?大家可能想到了
    return、continue、break这三个可以打乱代码顺序执行语句的规律。那我们就来试试看,这
    三个语句是否能影响finally语句块的执行:

    Java代码
    public final class FinallyTest {
    // 测试return语句
    public ReturnClass testReturn() {
    try {
    return new ReturnClass();
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    System.out.println("执行了finally语句");
    }
    return null;
    }
    // 测试continue语句
    public void testContinue() {
    for (int i = 0; i < 3; i++) {
    try {
    System.out.println(i);
    if (i == 1) {
    continue;
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    System.out.println("执行了finally语句");
    }
    }
    }
    // 测试break语句
    public void testBreak() {
    for (int i = 0; i < 3; i++) {
    try {
    System.out.println(i);
    if (i == 1) {
    break;
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    System.out.println("执行了finally语句");
    }
    }
    }
    public static void main(String[] args) {
    FinallyTest ft = new FinallyTest();
    // 测试return语句
    ft.testReturn();
    System.out.println();
    // 测试continue语句
    ft.testContinue();
    System.out.println();
    // 测试break语句
    ft.testBreak();
    }
    }
    class ReturnClass {
    public ReturnClass() {
    System.out.println("执行了return语句");
    }
    }
    

    上面这段代码的运行结果如下:

    1. 执行了return语句
    2. 执行了finally语句
    3.
    4. 0
    5. 执行了finally语句
    6. 1
    7. 执行了finally语句
    8. 2
    9. 执行了finally语句
    10.
    11. 0
    12. 执行了finally语句
    13. 1
    14. 执行了finally语句
    15. 
    

    很明显,return、continue和break都没能阻止finally语句块的执行。从输出的结果来看,
    return语句似乎在 finally语句块之前执行了,事实真的如此吗?我们来想想看,return语句
    的作用是什么呢?是退出当前的方法,并将值或对象返回。如果 finally语句块是在return语
    句之后执行的,那么return语句被执行后就已经退出当前方法了,finally语句块又如何能被
    执行呢?因此,正确的执行顺序应该是这样的:编译器在编译return new ReturnClass();时,
    将它分成了两个步骤,new ReturnClass()和return,前一个创建对象的语句是在finally语句块
    之前被执行的,而后一个return语句是在finally语句块之后执行的,也就是说finally语句块
    是在程序退出方法之前被执行的。同样,finally语句块是在循环被跳过(continue)和中断
    (break)之前被执行的。

    finalize方法

    最后,我们再来看看finalize,它是一个方法,属于java.lang.Object类,它的定义如下:

    Java代码
    protected void finalize() throws Throwable { }
    

    众所周知,finalize()方法是GC(garbage collector)运行机制的一部分,关于GC的知识我们
    将在后续的章节中来回顾。

    在此我们只说说finalize()方法的作用是什么呢?
    finalize()方法是在GC清理它所从属的对象时被调用的,如果执行它的过程中抛出了无法捕
    获的异常(uncaught exception),GC将终止对改对象的清理,并且该异常会被忽略;直到
    下一次GC开始清理这个对象时,它的finalize()会被再次调用。
    请看下面的示例:

    Java代码
    public final class FinallyTest {
    // 重写finalize()方法
    protected void finalize() throws Throwable {
    System.out.println("执行了finalize()方法");
    }
    public static void main(String[] args) {
    FinallyTest ft = new FinallyTest();
    ft = null;
    System.gc();
    }
    }
    

    运行结果如下:

    • 执行了finalize()方法
    程序调用了java.lang.System类的gc()方法,引起GC的执行,GC在清理ft对象时调用了它
    的finalize()方法,因此才有了上面的输出结果。调用System.gc()等同于调用下面这行代码:

    Java代码
    Runtime.getRuntime().gc();
    

    调用它们的作用只是建议垃圾收集器(GC)启动,清理无用的对象释放内存空间,但是GC
    的启动并不是一定的,这由JAVA虚拟机来决定。直到 JAVA虚拟机停止运行,有些对象的
    finalize()可能都没有被运行过,那么怎样保证所有对象的这个方法在JAVA虚拟机停止运行
    之前一定被调用呢?答案是我们可以调用System类的另一个方法:

    Java代码
    public static void runFinalizersOnExit(boolean value) {
    //other code
    }
    

    给这个方法传入true就可以保证对象的finalize()方法在JAVA虚拟机停止运行前一定被运行
    了,不过遗憾的是这个方法是不安全的,它会导致有用的对象finalize()被误调用,因此已经
    不被赞成使用了。

    由于finalize()属于Object类,因此所有类都有这个方法,Object的任意子类都可以重写
    (override)该方法,在其中释放系统资源或者做其它的清理工作,如关闭输入输出流。
    通过以上知识的回顾,我想大家对于final、finally、finalize的用法区别已经很清楚了。

    8.throw 与throws的区别

    抛出异常

    抛出异常有三种形式,一是throw,一个throws,还有一种系统自动抛异常。下面它们之间的异同。
    系统自动抛异常

    当程序语句出现一些逻辑错误、主义错误或类型转换错误时,系统会自动抛出异常。如:

    public static void main(String[] args) { 
     int a = 5, b =0; 
        System.out.println(5/b); 
        //function(); 
    } 
    

    系统会自动抛出ArithmeticException异常:

    Exception in thread "main" java.lang.ArithmeticException: / by zero
    at test.ExceptionTest.main(ExceptionTest.java:62)
    

    再如

    public static void main(String[] args) { 
    String s = "abc"; 
    System.out.println(Double.parseDouble(s)); 
    //function(); 
    } 
    

    系统会自动抛出NumberFormatException异常:

    Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224)
    at java.lang.Double.parseDouble(Double.java:510)
    at test.ExceptionTest.main(ExceptionTest.java:62)
    

    throw

    throw是语句抛出一个异常。
    语法:throw (异常对象);
    如:
    throw e;
    一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常。如:

    public static void main(String[] args) { 
    String s = "abc"; 
    if(s.equals("abc")) { 
      throw new NumberFormatException(); 
    } else { 
      System.out.println(s); 
    } 
    //function(); 
    } 
    

    会抛出异常:

    Exception in thread "main" java.lang.NumberFormatException
    at test.ExceptionTest.main(ExceptionTest.java:67)
    

    throws

    throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)
    语法:(修饰符)(方法名)([参数列表])[throws(异常类)]{......}
    如:
    public void function() throws Exception{......}
    当某个方法可能会抛出某种异常时用于throws 声明可能抛出的异常,然后交给上层调用它的方法程序处理。如:

    public static void function() throws NumberFormatException{ 
    String s = "abc"; 
    System.out.println(Double.parseDouble(s)); 
     } 
    
     public static void main(String[] args) { 
    try { 
      function(); 
    } catch (NumberFormatException e) { 
      System.err.println("非数据类型不能转换。"); 
      //e.printStackTrace(); 
    } 
    } 
    

    处理结果如下:
    非数据类型不能转换。
    throw与throws的比较

    1、throws出现在方法函数头;而throw出现在函数体。
    2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
    3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
    

    好的编程习惯:

    1.在写程序时,对可能会出现异常的部分通常要用try{...}catch{...}去捕捉它并对它进行处理;
    2.用try{...}catch{...}捕捉了异常之后一定要对在catch{...}中对其进行处理,那怕是最简单的一句输出语句,或栈输入e.printStackTrace();
    3.如果是捕捉IO输入输出流中的异常,一定要在try{...}catch{...}后加finally{...}把输入输出流关闭;
    4.如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,然后交给调用它的上层函数进行处理。
    

    举例:
    throws E1,E2,E3只是告诉程序这个方法可能会抛出这些异常,方法的调用者可能要处理这些异常,而这些异常E1,E2,E3可能是该函数体产生的。
    throw则是明确了这个地方要抛出这个异常。
    如:

    void doA(int a) throws IOException,{
      try{
         ......
    
      }catch(Exception1 e){
       throw e;
      }catch(Exception2 e){
       System.out.println("出错了!");
      }
      if(a!=b)
       throw new Exception3("自定义异常");
    }
    

    代码块中可能会产生3个异常,(Exception1,Exception2,Exception3)。

    如果产生Exception1异常,则捕获之后再抛出,由该方法的调用者去处理。

    如果产生Exception2异常,则该方法自己处理了(即System.out.println("出错了!");)。所以该方法就不会再向外抛出Exception2异常了,void doA() throws Exception1,Exception3 里面的Exception2也就不用写了。

    而Exception3异常是该方法的某段逻辑出错,程序员自己做了处理,在该段逻辑错误的情况下抛出异常Exception3,则该方法的调用者也要处理此异常。

    throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。

    throws语句用在方法声明后面,表示再抛出异常,由该方法的调用者来处理。

    throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。

    throw是具体向外抛异常的动作,所以它是抛出一个异常实例。

    throws说明你有那个可能,倾向。

    throw的话,那就是你把那个倾向变成真实的了。

    如果是系统异常的话可以什么都不用做,也可以针对方法抛出一个异常,因为系统异常是可以被系统自动捕获的,所以这个异常究竟是要在方法内部解决还是交给上层函数去解决其实效果是一样的。但是我查了很多资料,即使会抛出异常能被系统所捕获的话还是建议针对方法写一个throws,因为这样在完成一个大型任务的时候可以让别的程序员知道这里会出现什么异常。

    如果是自己定义的异常,则必须要用throws抛出该方法可能抛出的异常,否则编译会报错

    9.自动装箱与自动拆箱的区别

    自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动地转换成对应的对象。自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。

    如果你在Java1.5下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合(Collections)中放入原始类型值,因为集合只接收对象。通常这种情况下你的做法是,将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。使用Integer,Double,Boolean等这些类我们可以将原始类型值转换成对应的对象,但是从某些程度可能使得代码不是那么简洁精炼。为了让代码简练,Java 1.5引入了具有在原始类型和对象类型自动转换的装箱和拆箱机制。但是自动装箱和拆箱并非完美,在使用时需要有一些注意事项,如果没有搞明白自动装箱和拆箱,可能会引起难以察觉的bug。

    本文将介绍,什么是自动装箱和拆箱,自动装箱和拆箱发生在什么时候,以及要注意的事项。

    什么是自动装箱和拆箱

    自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。

    原始类型
    byte,short,char,int,long,float,double和boolean对应的封装类为
    Byte,Short,Character,Integer,Long,Float,Double,Boolean。
    

    自动装箱拆箱要点

    自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。

    自动装箱是将boolean值转换成Boolean对象,byte值转换成Byte对象,
    char转换成Character对象,float值转换成Float对象,
    int转换成Integer,long转换成Long,short转换成Short,自动拆箱则是相反的操作。`
    

    何时发生自动装箱和拆箱##

    自动装箱和拆箱在Java中很常见,比如我们有一个方法,接受一个对象类型的参数,如果我们传递一个原始类型值,那么Java会自动讲这个原始类型值转换成与之对应的对象。最经典的一个场景就是当我们向ArrayList这样的容器中增加原始类型数据时或者是创建一个参数化的类,比如下面的ThreadLocal。

    ArrayList<Integer> intList = new ArrayList<Integer>();
    intList.add(1); //autoboxing - primitive to object
    intList.add(2); //autoboxing
    
    ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>();
    intLocal.set(4); //autoboxing
    
    int number = intList.get(0); // unboxing
    int local = intLocal.get(); // unboxing in Java
    

    举例说明

    上面的部分我们介绍了自动装箱和拆箱以及它们何时发生,我们知道了自动装箱主要发生在两种情况,一种是赋值时,另一种是在方法调用的时候。为了更好地理解这两种情况,我们举例进行说明。

    赋值时

    这是最常见的一种情况,在Java 1.5以前我们需要手动地进行转换才行,而现在所有的转换都是由编译器来完成。

    //before autoboxing
    Integer iObject = Integer.valueOf(3);
    Int iPrimitive = iObject.intValue()
    
    //after java5
    Integer iObject = 3; //autobxing - primitive to wrapper conversion
    int iPrimitive = iObject; //unboxing - object to primitive conversion
    

    方法调用时

    这是另一个常用的情况,当我们在方法调用时,我们可以传入原始数据值或者对象,同样编译器会帮我们进行转换。

    public static Integer show(Integer iParam){
      System.out.println("autoboxing example - method invocation i: " + iParam);
     return iParam;
    }
    
    //autoboxing and unboxing in method invocation
    show(3); //autoboxing
    int result = show(3); //unboxing because return type of method is Integer
    

    show方法接受Integer对象作为参数,当调用show(3)时,会将int值转换成对应的Integer对象,这就是所谓的自动装箱,show方法返回Integer对象,而int result = show(3);中result为int类型,所以这时候发生自动拆箱操作,将show方法的返回的Integer对象转换成int值。

    自动装箱的弊端

    自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能。

    Integer sum = 0;
    for(int i=1000; i<5000; i++){
     sum+=i;
    }
    

    上面的代码sum+=i可以看成sum = sum + i,但是+这个操作符不适用于Integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成Integer对象。其内部变化如下

    sum = sum.intValue() + i;
    Integer sum = new Integer(result);
    

    由于我们这里声明的sum为Integer类型,在上面的循环中会创建将近4000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。

    重载与自动装箱

    当重载遇上自动装箱时,情况会比较有些复杂,可能会让人产生有些困惑。在1.5之前,value(int)和value(Integer)是完全不相同的方法,开发者不会因为传入是int还是Integer调用哪个方法困惑,但是由于自动装箱和拆箱的引入,处理重载方法时稍微有点复杂。一个典型的例子就是ArrayList的remove方法,它有remove(index)和remove(Object)两种重载,我们可能会有一点小小的困惑,其实这种困惑是可以验证并解开的,通过下面的例子我们可以看到,当出现这种情况时,不会发生自动装箱操作。

    public void test(int num){
    System.out.println("method with primitive argument");
    
    }
    
    public void test(Integer num){
    System.out.println("method with wrapper argument");
    
    }
    
    //calling overloaded method
    AutoboxingTest autoTest = new AutoboxingTest();
    int value = 3;
    autoTest.test(value); //no autoboxing 
    Integer iValue = value;
    autoTest.test(iValue); //no autoboxing
    
    Output:
    method with primitive argument
    method with wrapper argument
    

    要注意的事项

    自动装箱和拆箱可以使代码变得简洁,但是其也存在一些问题和极端情况下的问题,以下几点需要我们加强注意。

    对象相等比较

    这是一个比较容易出错的地方,”==“可以用于原始值进行比较,也可以用于对象进行比较,当用于对象与对象之间比较时,比较的不是对象代表的值,而是检查两个对象是否是同一对象,这个比较过程中没有自动装箱发生。进行对象值比较不应该使用”==“,而应该使用对象对应的equals方法。看一个能说明问题的例子。

    public class AutoboxingTest {
    
    public static void main(String args[]) {
    
        // Example 1: == comparison pure primitive – no autoboxing
        int i1 = 1;
        int i2 = 1;
        System.out.println("i1==i2 : " + (i1 == i2)); // true
    
        // Example 2: equality operator mixing object and primitive
        Integer num1 = 1; // autoboxing
        int num2 = 1;
        System.out.println("num1 == num2 : " + (num1 == num2)); // true
    
        // Example 3: special case - arises due to autoboxing in Java
        Integer obj1 = 1; // autoboxing will call Integer.valueOf()
        Integer obj2 = 1; // same call to Integer.valueOf() will return same
                            // cached Object
    
        System.out.println("obj1 == obj2 : " + (obj1 == obj2)); // true
    
        // Example 4: equality operator - pure object comparison
        Integer one = new Integer(1); // no autoboxing
        Integer anotherOne = new Integer(1);
        System.out.println("one == anotherOne : " + (one == anotherOne)); // false
    
    Integer i = new Integer(1111);
        System.out.println(i==1111);  //true  该例说明了包装类和直接数作比较会自动拆箱而不是装箱,比较的字面值得大小
    }
    
    }
    

    Output:
    i1==i2 : true
    num1 == num2 : true
    obj1 == obj2 : true
    one == anotherOne : false
    

    值得注意的是第三个小例子,这是一种极端情况。obj1和obj2的初始化都发生了自动装箱操作。但是处于节省内存的考虑,JVM会缓存-128到127的Integer对象。因为obj1和obj2实际上是同一个对象。所以使用”==“比较返回true。

    容易混乱的对象和原始数据值

    另一个需要避免的问题就是混乱使用对象和原始数据值,一个具体的例子就是当我们在一个原始数据值与一个对象进行比较时,如果这个对象没有进行初始化或者为Null,在自动拆箱过程中obj.xxxValue,会抛出NullPointerException,如下面的代码

    private static Integer count;
    
    //NullPointerException on unboxing
    if( count <= 0){
     System.out.println("Count is not started yet");
    }
    

    缓存的对象

    这个问题就是我们上面提到的极端情况,在Java中,会对-128到127的Integer对象进行缓存,当创建新的Integer对象时,如果符合这个这个范围,并且已有存在的相同值的对象,则返回这个对象,否则创建新的Integer对象。

    10.同步与死锁的区别

    进程同步:我们把异步环境下的一组并发进程因直接制约而互相发送消息而进行互相合作、互相等待,使得各进程按一定的速度执行的过程称为进程间的同步。具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。 如果我们对一个消息或事件赋以唯一的消息名,则我们可用过程 wait (消息名) 表示进程等待合作进程发来的消息,而用过程 signal (消息名) 表示向合作进程发送消息。

    进程死锁: 如果多个进程同时占有对方需要的资源而同时请求对方的资源,而它们在得到请求之前不会释放所占有的资源,那么就会导致死锁的发生,也就是进程不能实现同步。

    11.List与Set的区别

    List接口 ##


    List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。和下面要提到的Set不同,List允许有相同的元素。

    Set接口


    Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。  很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素

    List和Set都是接口


    他们各自有自己的实现类,有无顺序的实现类,也有有顺序的实现类。最大的不同就是List是可以重复的。而Set是不能重复的。List适合经常追加数据,插入,删除数据。但随即取数效率比较低。Set适合经常地随即储存,插入,删除。但是在遍历时效率比较低。


    list,set都是可以使用collections.sort()排序的

    12.HashMap,HashTable,TreeMap的区别

    最近用到了这三种集合类,由于不是很熟练,所以想整理下。而且前段时间面试,一般情况下面试官喜欢问HashMap和Hashtable的主要区别,其用意是想问被面试者这俩那个是线程安全的。但是HashMap和Hashtable之间的差别不止线程安全那么简单。两者的主要区别如下:

    相同点:都实现了Map接口,都是轻量级的实现。两者采用的Hash算法几乎一样,所以性能不会有很大的差异。

    1.语法上面的区别:

    1)HashMap允许键值为空,Hashtable不允许。

    2)HashMap包含了containsvalue和containsKey,不包含有contains。

    2.安全方面的区别

    HashTable支持线程安全的,而HashMap不支持线程同步,是非线程安全的。因此,HashMap相对来说效率可能会高于Hashtable。

    3.源码级别的区别

    Hashtable,hash数组默认的大小是11,增加的方式是old*2+1,而HashMap中,hash数组的默认大小是16,而且一定是2的指数。

    相较于HashMap和HashTable,TreeMap是利用红黑树来实现的,实现了SortMap接口,能够对保存的记录根据键进行排序。所以一般需要排序的情况下是选择TreeMap来进行。

    13。String,StringBuilder,StringBuffer三者的区别

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下,便于大家观看,也便于加深自己学习过程中对这些知识点的记忆,如果哪里有误,恳请指正。

    这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。

    首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String

    1.String最慢的原因:


    String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:

    1 String str="abc";
    2 System.out.println(str);
    3 str=str+"de";
    4 System.out.println(str);
    

    如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

    而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

    另外,有时候我们会这样对字符串进行赋值

    1 String str="abc"+"de";
    2 StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");
    3 System.out.println(str);
    4 System.out.println(stringBuilder.toString());
    

    这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和

    String str="abcde";
    

    是完全一样的,所以会很快,而如果写成下面这种形式

    1 String str1="abc";
    2 String str2="de";
    3 String str=str1+str2;
    

    那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。

    2. 再来说线程安全


    在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的

    如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

    3. 总结一下


    String:适用于少量的字符串操作的情况

    StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

    StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

    相关文章

      网友评论

          本文标题:BigData-Java总结大全(二)苏暖人

          本文链接:https://www.haomeiwen.com/subject/yahdvftx.html