Java基本概念与问题(二)

来源:互联网 发布:windows 优盘启动 编辑:程序博客网 时间:2024/06/16 22:48

11.简述abstract class和 interface的区别

    含有abstract修饰符的class 即为抽象类,abstract类不能创建实例对象,含有abstract的方法的类必须定义为abstract class ,abstract class 里的方法不必是抽象的,抽象类中定义抽象方法必须放在具体子类中实现,所以不能有抽象的构造方法或抽象的静态方法,如果子类没有实现抽象父类中的所有方法,那么,子类也必须定义为抽象类。
    接口(interface)可以说成是抽象类的特例。接口中的所有方法都必须是抽象的,接口中的方法定义默认为public abstract。接口中的变量是全局常量,即public static final修饰的。
    在语法上的区别
    1.抽象类里可以有构造方法,而接口内不能有构造方法。
    2.抽象类中可以有普通成员变量,而接口中不能有普通成员变量。
    3.抽象类中可以包含非抽象的普通方法,而接口中所有的方法必须是抽象的,不能有非抽象的普通方法。
    4.抽象类中的抽象方法的访问类型可以是public,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
    5.抽象类中可以包含静态方法,接口内不能包含静态方法。
    6.抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static类型,并且默认为public static类型。

    7.一个类可以实现多个接口,但只能继承一个抽象类。

    接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承具体类。抽象类中可以有静态的main方法。

    只有记住抽象类与普通类的唯一区别就是不能创建实例对象和允许有abstract方法。
    构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。
    再补充点两者在应用上的区别:接口更多的是在系统框架设计方法发挥作用,主要定义模块之间的通信,而抽象类在代码实现方面发挥作用,可以实现代码的重用。

    java中实现多态的机制靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。

    接口可以继承接口。抽象类可以实现接口,抽象类可以继承具体类。抽象类中可以有静态的main方法。


12.error和exception有什么区别

    异常是指java程序运行时(非编译)所发生的非正常情况或错误, Java使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别封装到一个对象来表示的,该对象中包含有异常的信息。Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable。Throwable下面又派生了两个子类:Error和Exception。
    Error 表示应用程序本身无法克服和恢复的一种严重问题,是java运行环境中的内部错误或者硬件问题,比如,内存资源不足、线程死锁等系统问题。对于这种错误,程序基本无能为力,除了退出运行别无选择。
    Exception表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常,系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组越界(ArrayIndexOutOfBoundsExcep
tion),空指针异常(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。
java为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须try..catch处理或用throws声明继续抛给上层调用方法处理,所以普通异常也称为checked异常,而系统异常可以处理也可以不处理,所以,编译器不强制用try..catch处理或用throws声明,所以系统异常也称为unchecked异常。
    exception(违例)表示一种设计或实现问题,表示需要捕捉或者需要程序进行处理的异常。它处理的是因为程序的瑕疵而引起的问题,或者在外的输入等引起的一般性问题,是程序必须处理的;也就是说,它表示如果程序运行正常,从不会发生的情况
    RuntimeException代表的是编程错误
    Error类和Exception类都继承自Throwable类。
     Error的继承关系:    java.lang.Object 
                java.lang.Throwable
                                    java.lang.Error 
     Exception的继承关系: java.lang.Objectjava.lang.Throwable
                       java.lang.Exception 
    二者的不同之处: 
    Exception:1.可以是可被控制(checked)或不可控制的(unchecked) 2.表示一个由程序员导致的错误 3.应该在应用程序级被处理
    Error:1.总是不可控制的(unchecked) 2.经常用来用于表示系统错误或低层资源的错误 3.如何可能的话,应该在系统级被捕捉
    Java 中定义了两类异常: 
    1) Checked exception: 这类异常都是Exception的子类 。异常的向上抛出机制进行处理,假如子类可能产生A异常,那么在父类中也必须throws A异常。可能导致的问题:代码效率低,耦合度过高。
     2) Unchecked exception: 这类异常都是RuntimeException的子类,虽然RuntimeException同样也是Exception的子类,但是它们是非凡的,它们不能通过client code来试图解决,所以称为Unchecked exception 。 

13.String 、StringBuffer 、StringBuilder的区别

    String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。String类表示内容不可改变的字符串。而StringBuffer类表示内容可以被修改的字符串。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,可以使用StringBuffers来动态构造字符数据。另外,String实现了equals方法,new String(“abc”).equals(new String(“abc”)的结果为true,而StringBuffer没有实现equals方法,所以,new StringBuffer(“abc”).equals(new StringBuffer(“abc”)的结果为false。
    接着要举一个具体的例子来说明,我们要把1到100的所有数字拼起来,组成一个串。
    StringBuffer sbf = new StringBuffer();  
        for(int i=0;i<100;i++){
    sbf.append(i);
    }
   上面的代码效率很高,因为只创建了一个StringBuffer对象,而下面的代码效率很低,因为创建了101个对象。
   String str = new String();  
        for(int i=0;i<100;i++){
    str = str + i;
    }

    String覆盖了equals方法和hashCode方法,而StringBuffer没有覆盖equals方法和hashCode方法,所以,将StringBuffer对象存储进Java集合类中时会出现问题。

    StringBuffer和StringBuilder类都表示内容可以被修改的字符串,StringBuilder是线程不安全的,运行效率高,如果一个字符串变量是在方法里面定义,这种情况只可能有一个线程访问它,不存在不安全的因素了,则用StringBuilder。如果要在类里面定义成员变量,并且这个类的实例对象会在多线程环境下使用,那么最好用StringBuffer。


14. 在JAVA中如何跳出当前的多重嵌套循环

    在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如,
        ok:for(int i=0;i<10;i++){for(int j=0;j<10;j++){System.out.println(“i=” + i + “,j=” + j);if(j == 5) break ok;}} 
    另外,通常并不使用标号这种方式,而是让外层的循环条件表达式的结果可以受到里层循环体代码的控制,例如,要在二维数组中查找到某个数字。
int arr[][] = {{1,2,3},{4,5,6,7},{9}};boolean found = false;for(int i=0;i<arr.length && !found;i++){for(int j=0;j<arr[i].length;j++){System.out.println(“i=” + i + “,j=” + j);if(arr[i][j]  == 5){found = true;break;}}} 
 

15.short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错

    对于short s1 = 1; s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。
    对于short s1 = 1; s1 += 1;由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。

16. char型变量中能不能存贮一个中文汉字

    char类型的变量占用两个字节,用来存储Unicode编码(unicode编码占用两个字节)的字符的,unicode编码字符集中包含了汉字,所以,char型变量中可以存储汉字。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。


17."=="和equals方法究竟有什么区别

    ==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符。
    如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较。
    equals方法是用于比较两个独立对象的内容是否相同,它比较的两个对象是独立的。例如,对于下面的代码:
        String a=new String("foo");
        String b=new String("foo");
    两条new语句创建了两个对象,然后用a,b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值是不相同的,所以,表达式a==b将返回false,而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。
    如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码如下:
    boolean equals(Object o){
        return this==o;
    }

    这说明,如果一个类没有自己定义equals方法,它默认的equals方法(从Object 类继承的)就是使用==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用==会得到同样的结果,如果比较的是两个独立的对象则总返回false。如果希望能够比较该类创建的两个实例对象的内容是否相同,那么必须覆盖equals方法,由自己写代码来决定在什么情况即可认为两个对象的内容是相同的。


18.stop()和suspend()方法为何不推荐使用 

    从SUN的官方文档可以得知,调用Thread.stop()方法是不安全的,这是因为当调用Thread.stop()方法时,会发生下面两件事:
    1. 即刻抛出ThreadDeath异常,在线程的run()方法内,任何一点都有可能抛出ThreadDeath Error,包括在catch或finally语句中。

    2. 释放该线程所持有的所有的锁

    当线程抛出ThreadDeath异常时,会导致该线程的run()方法突然返回来达到停止该线程的目的。ThreadDetath异常可以在该线程run()方法的任意一个执行点抛出。停止线程会导致解锁它已锁定的所有监视程序(ThreadDeath 异常传播到栈上后即解锁监视程序)。如果这些监视程序前面保护的任何对象处于不一致状态,则其它线程即可能将这些对象视为处于不一致状态。

    它的不安全主要是针对于第二点:释放该线程所持有的所有的锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用 thread.stop()后导致了该线程所持有的所有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些程序错误。

    关于如何正确停止线程,总结起来就下面3点(在停止线程时):

    1. 使用violate boolean变量来标识线程是否停止
    2. 停止线程时,需要调用停止线程的interrupt()方法,因为线程有可能在wait()或sleep(), 提高停止线程的即时性
    3. 对于blocking IO的处理,尽量使用InterruptibleChannel来代替blocking IO
    suspend()方法容易发生死锁。调用suspend()的时候,如果目标线程挂起时获得锁,则目标线程停下来仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。
所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。 
    Thread.suspend 从本质上就是易于死锁的。如果目标线程锁定在一个监视程序上,从而在关键系统资源挂起时保护资源,则在目标线程恢复前将没有线程能访问该资源。如果试图恢复目标线程的线程在调用 resume 之前试图锁定该监视程序,即出现死锁。这种死锁通常将自己显示为“冻结”进程。resume只与 suspend() 一起使用,但 suspend() 已经遭到反对,因为它具有死锁倾向。


19. sleep() 和 wait() 

    sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,主动让出cpu,cpu去执行其他线程,但是监控状态依然保持,到时后cpu回到这个线程上继续往下执行。调用sleep不会释放对象锁,如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。

    wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁)方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。 


20.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法

    分几种情况:
    1.其他方法前是否加了synchronized关键字,如果没加,则能。
    2.如果这个方法内部调用了wait,则可以进入其他synchronized方法。
    3.如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
    4.如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。
0 0
原创粉丝点击