分享

​疯狂Java面试题

 以怪力乱神 2019-08-26

本大全每个月会定期更新,索取网址:http://www.

Java核心技术部分

Java核心技术部分的面试题,可能覆盖Java基本语法、面向对象(包括类定义、方法、构造器、递归、继承、抽象类、接口、枚举以及final、static等关键字)、Java常用API、Java集合框架(需要重点掌握)、注解(Annotation)、泛型、输入/输出、多线程、网络通信、反射、内存管理等相关内容,这些知识基本都可通过《疯狂Java讲义》一书找到详细解答。

这部分面试题大部分从网络收集、整理,也有部分题目来自疯狂软件学员面试之后的反馈。

1、面向对象的特征有哪些?

面向对象的三大特征:

继承:通过继承允许复用已有的类,继承关系是一种“一般到特殊”的关系,比如苹果类继承水果类,这个过程称为类继承。

派生出来的新类称为原有类的子类(派生类),而原有类称为新类的父类(基类)。

子类可以从父类那里继承得到方法和成员变量,而且子类类可以修改或增加新的方法使之适合子类的需要。

封装:封装是把对象的状态数据隐藏起来,再通过暴露合适的方法来允许外部程序修改对象的状态数据。Java的封装主要通过private、protected、public等访问控制符来实现。

多态:多态指的是当同一个类型的引用类型的变量在执行相同的方法时,实际上会呈现出多种不同的行为特征。比如程序有Animal a1 = new Animal (); Animal a2 = new Wolf();虽然a1、a2两个引用变量的类型都是Animal,但当它们调用同一个run()方法时,如果Wolf()类重写过Animal的run()方法,这就会导致a1、a2两个变量执行run()方法时呈现出不同的行为特征,这就是多态。多态增加了编程的灵活性,实际上大量设计模式都是基于多态类实现的。

除此之外,抽象也是一个重要的特征,抽象就是忽略与当前目标无关的相关方面,以便更充分地突出与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。

2、Java中实现多态的机制是什么?

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

正是由于这种机制,两个相同类型的引用变量,但由于它们实际引用了不同的对象,因此它们运行时可能呈现出多种不同的行为特征,这就被称多态。

3、一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?

可以有多个类,但只能有一个public的类,并且public的类名必须与文件的主文件名相同。

包含多个类的Java源文件编译之后会生成多个.class文件,每个类(包括外部类、内部类)都会生成一个对应的.class文件。

4、String是基本数据类型吗?

基本数据类型包括byte、short、int、long、char、float、double和boolean。String不是基本类型。String是引用类型。

java.lang.String类是final的,因此无法通过String类派生子类。

String也是一个不可变类(它所包含的字符序列是不可改变),因此如果程序需要使用的字符串所包含的字符序列需要经常改变,建议使用StringBuffer(线程安全、性能略差)类或StringBuilder类。

5、int 和 Integer 有什么区别

Java 提供两种不同的类型:引用类型和基本数据类型。

int是基本数据类型,Integer是Java为int提供的包装类。

Java为所有的基本类型提供了对应的包装类。

byte      Byte

short     Short

int       Integer

long      Long

char      Character

float      Float

double    Double

boolean   Boolean

基本类型的变量只能当成简单的直接量、参与表达式运算,不具备面向对对象的特征,基本类型的变量不能被赋为null;但包装类的变量则完全可以当成对象使用,它具有面向对象的特征,包装类的变量可以被赋为null。

因为Integer具有面向对象的特征,因此Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况,例如,要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer。在JSP开发中,Integer的默认为null,所以用EL输出为null的Integer时,将会显示为空白字符串,而int默认的默认值为0,用EL输出为将显示0。所以,int不适合作为Web层的表单数据的类型。

从Java 5开始,Java提供了自动装箱、自动拆箱功能,因此包装类也可以直接参与表达式运算,因此使用起来十分方便。

另外,Integer提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。

6、Java有没有goto?

goto是Java中的保留字,Java程序的标识符不允许使用goto。但Java也不支持使用goto进行跳转。

7、String 和StringBuffer、StringBuilder的区别

Java提供了:String、StringBuffer和StringBuilder,它们都是CharSequence的实现类,都可以作为字符串使用。

String代表了字符序列不可变的字符串;而StringBuffer、StringBuilder都代表了字符序列可变的字符串。

StringBuffer、StringBuilder的区别是StringBuffer是线程安全的、性能略低,而StringBuilder是线程不安全的,适合单线程环境使用,性能较好。

8、Collection 和 Collections的区别。

Collection是集合类(List、Set、Queue)的根接口。

Collections是针对集合类的一个工具类,它提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

9、说说&和&&的区别。

&和&&都可以用作逻辑与的运算符,当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。

&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(a >8 && b > 5),当a小于等于8时,由于&&之前的表达式已经为false了,因此&&之后的表达式根本不会执行;

再例如if(str != null && !str.equals(""))表达式,当str为null时,后面的表达式不会执行,因此不会出现NullPointerException如果将&&改为&,则可能抛出NullPointerException异常。

再例如if(x > 8 & ++y)与if(x > 8 && ++y ),当a小于等于8时,前一个表达式中y的值会增长;后一个表达式中y的值不会增加。

除此之外,&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。

10、Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?

Overload是方法的重载

Override是方法的重写,也叫覆盖。

Overload要求两个方法具有方法名相同、形参列表不同的要求,返回值类型不能作为重载的条件。

Override要求子类方法与父类方法具有“两同两小一大”的要求。两同指:即父类方法、子类方法的方法名相同、形参列表相同;两小指:子类方法返回值类型要么是父类方法返回值类型的子类、要么与父类方法返回值类型相同;子类方法声明抛出的异常类型要么是父类方法声明抛出的异常类型的子类、要么与父类声明抛出的异常类型相同;一大指:子类方法的访问权限要么与父类方法的访问权限相同,要么比父类方法的访问权限更大。

Overloaded的方法是可以改变返回值的类型。

11、Java如何跳出当前的多重嵌套循环?

在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break语句,即可跳出外层循环。例如,

    outer:

       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 ouer;

              }

       }

12、switch语句能否作用在byte上,能否作用在long上,能否作用在String上?

在Java 7以前,在switch(expr1)中,expr1只能是一个整数表达式(但不包括long和Long)或者枚举常量,整数表达式可以是int基本类型或Integer包装类型,byte、short、char都可以自动转换为int,它们都可作为switch表达式。

从Java 7开始,switch表达式的可以使用String。

13、String s = new String("xyz");创建了几个String Object?

两个。一个是直接量的"xyz"字符串对象,该字符串将会被缓存在字符串常量池中,以便以后复用这个字符串;另一个是通过new Sting()构造器创建出来的String对象,这个String对象保存在堆内存中。

通常来说,应该尽量使用直接量的String对象,这样具有更好的性能。

14、数组有没有length()这个方法? String有没有length()这个方法?

数组没有length()这个方法,有length属性。String有length()方法。

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会把s1+=1计算后的结果进行隐式的强制类型转换,因此它不会有任何错误。

16、char型变量中能不能存储一个中文字符?为什么?

char型变量是用来存储Unicode编码的字符的,Unicode编码字符集中包含了汉字,因此char型变量中可以存储汉字。不过,如果某个特殊的汉字没有被包含在Unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。

char类型的变量占两个字节,而Unicode编码中每个字符也占两个字节,因此char类型类型的变量可以存储任何一个Unicode字符。

17、用最有效率的方法算出2乘以8等于几?

2 << 3

因为将一个数左移n位,就相当于乘以了2的n次方,那么,一个数乘以8只要将其左移3位即可,而位运算CPU直接支持的,效率最高,所以,2乘以8等于几的最效率的方法是2 << 3。

但需要注意的是,如果这个数字本身已经很大,比如本身已经是2的30次方了,此时再用这种位移运算就可能导致“溢出”,这样就得不到正确结果了。

18、使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?

使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。例如,对于如下语句:

final StringBuilder a = new StringBuilder ("immutable");

执行如下语句将报告编译错误:

a = new StringBuilder ("");

但如下语句则是完全正确的

a.append("fkjava.org");

有人希望在定义方法的形参时,通过final修饰符来阻止方法内部修改传进来的实参:

public void method(final StringBuilder param)

{

}

实际上这没有用,在该方法内部仍然可以增加如下代码来修改实参对象:

param.append("fkjava.org");

19、"=="和equals方法究竟有什么区别?

==操作符的作用有两种:

A.如果==的两边都是基本类型变量、包装类对象所组成的表达式,==用于比较两边的表达式的值是否相等——只要两边的表达式的值相等,即使数据类不同,该运算符也会返回true。例如'a' == 97.0,将会返回true。

B.如果==的两边是引用类型的变量,==用于判断这两个引用类型的变量是否引用同一块内存,只有当它们引用同一块内存时,==才会返回true。

而equals()则是一个java.lang.Object类的一个方法,因此任何Java对象都可调用该方法与其他对象进行比较。java.lang.Object类的equals方法的实现代码如下:

boolean equals(Object o)

{

       return this==o;

}

从上面代码可以看出,如果一个类没有重写java.lang.Object的equals()方法时,此时equals()方法的比较结果与==的比较结果是相同的。

但Java允许任何类重写equals()方法,重写该方法就是让程序员来自己决定两个对象相等的标准——极端的情况下,我们完全可以设计出Person对象与Dog对象equals()比较返回true的情况——当然一般不会这么设计。

实际上重写equals()方法时通常会按如下格式:

       public boolean equals(Object obj)

       {

              if (this == obj)

                     return true;

              if (obj == null)

                     return false;

              if (getClass() != obj.getClass())

                     return false;

              Person other = (Person) obj;

              if (name == null)

              {

                     if (other.name != null)

                            return false;

              }

              else if (!name.equals(other.name))

                     return false;

              if (pass == null)

              {

                     if (other.pass != null)

                            return false;

              }

              else if (!pass.equals(other.pass))

                     return false;

              return true;

       }

上面重写equals()方法用于判断两个Person对象是否“相等”,程序只要两个Person对象的name、pass相等,程序就可以把这两个Person对象当成相等——这是系统业务决定的。如果业务需要,我们也可以增加更多的参与判断的Field,当然也可以只根据name进行判断——只要两个Person的name相等,就认为两个Person相等,这都是由系统的业务决定。

总结起来就是一句话:开发者重写equals()方法就可以根据业务要求来决定两个对象是否“相等”。

20、静态变量和实例变量的区别?

在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。

在程序运行时的区别:实例变量属于一个对象,必须先创建实例对象,它的实例变量才会被分配空间,才能使用这个实例变量。静态变量则属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。

例如,对于下面的程序:

public class VarTest

{

       public static int staticVar = 0;

       public int instanceVar = 0;

       public VarTest ()

       {

              staticVar++;

              instanceVar++;

              System.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar);

       }

}

上面程序中的staticVar变量随VarTest类初始化而分配内存、执行初始化的,以后无论创建多少个实例对象,不会再分配staticVar变量,因此用永远只有一个staticVar变量。

但instanceVar变量则是随着VarTest对象初始化而分配内存、执行初始化的,因此每创建一个实例对象,就会分配一个instanceVar,即可以分配多个instanceVar。因此上面程序中每创建一个VarTest对象,staticVar的值就会自加一,但创建每个VarTest对象的instanceVar都只自加1。

21、是否可以从一个static方法内部调用非static方法?

不可以。静态成员不能调用非静态成员。

非static方法属于对象,必须创建一个对象后,才可以在通过该对象来调用static方法。而static方法调用时不需要创建对象,通过类就可以调用该方法。也就是说,当一个static方法被调用时,可能还没有创建任何实例对象,如果允许从一个static方法中调用非static方法的调用,那个非static方法是没有调用对象的。因此Java不允许static方法内部调用非static方法。

22、Math.round(11.5)等於多少? Math.round(-11.5)等於多少?

Math类中提供了三个与取整有关的方法:ceil()、floor()、round(),这些方法的作用与它们的英文名称的含义相对应,例如,ceil的英文意义是天花板,该方法就表示向上取整,所以,Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11;floor的英文意义是地板,该方法就表示向下取整,所以,Math.floor(11.6)的结果为11,Math.floor(-11.6)的结果是-12;最难掌握的是round方法,它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。

23、请说出作用域public,private,protected,以及不写时的区别

这四个作用域的可见范围如下表所示。

作用域    当前类   同一package    子类    全局

public      √       √             √       √

protected   √        √             √      ×

default     √        √             ×      ×

private     √        ×             ×      ×

说明:如果在修饰的元素上面没有写任何访问修饰符,则表示default。

只要记住访问权限由小到大依次是private → default → protected → public,然后再记住Java存在的4个访问范围,就很容易画出上面的表格了。

24、外部类能用private、protected修饰吗?内部类可以用private、protected修饰吗?

外部类不能用private、protected修饰不能。内部类能用private、protected修饰。

外部类的上一级程序单位是包,因此它只有两个使用范围:包内和包外,因此它只能用public(表示可以在全局位置使用)和默认修饰符(default,表示只能被同一个包的其他类使用)修饰。

内部类的上一级程序单位是类,因此它有4个使用范围:当前类,同一个包内、当前类的子类中、全局范围,因此可以使用private、默认修饰符、protected、public的任意一个修饰符修饰。

25、一个类定义多个重载方法,参数分别是int ,char,和double,然后将double x = 2,传递进去,会选择哪个方法?

选择参数类型为double的方法。

26、说说has a与is a的区别。

is a是典型的“一般到特殊”的关系,也就是典型的继承关系。例如Apple is a Fruit。那么Apple是一种特殊的Fruit,也就是说Apple继承了Fruit。

has a是典型的“组合”关系。比如Wolf has a Leg,也就是Leg组合成了Wolf。

需要指出的是:由于继承会造成了对父类的破坏,因此有时候可以通过组合来代替的继承。使用继承的好处:程序语义更好理解。坏处是:子类可能重写父类方法,不利于父类封装;使用组合则造成语义的混淆,但组合类不会重写被组合类的方法,因此更利于被复合类的封装。

27、ClassLoader如何加载class 。

JVM里有多个类加载,每个类加载可以负责加载特定位置的类,例如,Bootstrap类加载(根类加载器)负责加载它负责加载Java的核心类(jre/lib/rt.jar中的类), JDK常用的String、Math、HashSet、ArrayList等类都位于rt.jar中;Extension类加载器负责加载jar/lib/ext/*.jar中的类,应用类加载器(App ClassLoader负责CLASSPATH指定的目录或JAR包中的类。除了Bootstrap之外,其他的类加载器本身也都是Java类,它们的父类是ClassLoader;

Bootstrap类加载器(根类加载器)非常特殊,它并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。

28、GC是什么? 为什么要有GC?   

GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的。

Java的System类和Runtime类都提供了“通知”程序进行垃圾回收的方法,例如如下代码:

Systme.gc();

Runtime.getInstance().gc();

但这两个方法只是“通知”Java进行垃圾回收,但实际上JVM何时进行垃圾回收,还是由JVM自己决定。

29、垃圾回收的优点和原理。并考虑2种回收机制。

传统的C/C++等编程语言,需要程序员负责回收已经分配的内存。显式进行垃圾回收是一件比较困难的事情,因为程序员并不总是知道内存应该何时被释放。如果一些分配出去的内存得不到及时回收,就会引起系统运行速度下降,甚至导致程序瘫痪,这种现象被称为内存泄漏。总体而言,显式进行垃圾回收主要有如下两个缺点:

A.程序忘记及时回收无用内存,从而导致内存泄漏,降低系统性能。

B.程序错误地回收程序核心类库的内存,从而导致系统崩溃。

与C/C++程序不同,Java语言不需要程序员直接控制内存回收,Java程序的内存分配和回收都是由JRE在后台自动进行的。JRE会负责回收那些不再使用的内存,这种机制被称为垃圾回收(Garbage Collection,也被称为GC)。通常JRE会提供一条后台线程来进行检测和控制,一般都是在CPU空闲或内存不足时自动进行垃圾回收,而程序员无法精确控制垃圾回收的时间和顺序等。

实际上,垃圾回收机制不可能实时检测到每个Java对象的状态,当一个对象失去引用后,它也不会被立即回收,只有等接下来垃圾回收器运行时才会被回收。

对于一个垃圾回收器的设计算法来说,大致有如下可供选择的设计:

A.串行回收(Serial)和并行回收(Parallel):串行回收就是不管系统有多少个CPU,始终只用一个CPU来执行垃圾回收操作;而并行回收就是把整个回收工作拆分成多部分,每个部分由一个CPU负责,从而让多个CPU并行回收,并行回收的执行效率很高,但复杂度增加,另外也有其他一些副作用,比如内存碎片会增加。

B.并发执行(Concurrent)和应用程序停止(Stop-the-world):。Stop-the-world的垃圾回收方式在执行垃圾回收的同时会导致应用程序的暂停。并发执行的垃圾回收虽然不会导致应用程序的暂停,但由于并发执行垃圾回收需要解决和应用程序的执行冲突(应用程序可能会在垃圾回收的过称中修改对象),因此并发执行垃圾回收的系统开销比Stop-the-world更好,而且执行时也需要更多的堆内存。

C.压缩(Compacting)和不压缩(Non-compacting)和复制(Copying):为了减少内存碎片,支持压缩的垃圾回收器会把所有的活对象搬迁到一起,然后将之前占用的内存全部回收。不压缩式的垃圾回收器只是回收内存,这样回收回来的内存不可能是连续的,因此将会有较多的内存碎片。较之压缩式的垃圾回收,不压缩式的垃圾回收回收内存快了,而分配内存时就会更慢,而且无法解决内存碎片的问题。复制式的垃圾回收会将所有可达对象复制到另一块相同的内存中,这种方式的优点是垃圾及回收过程不会产生内存碎片,但缺点也很明显,需要拷贝数据和额外的内存。

30、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

对于Java程序中对象而言,如果这个对象没有任何引用变量引用它,那么这个对象将不可能被程序访问,因此可认为它是垃圾;只要有一个以上的引用变量引用该对象,该对象就不会被垃圾回收。

对于Java的垃圾回收器来说,它使用有向图来记录和管理堆内存中的所有对象,通过这个有向图就可以识别哪些对象是“可达的”(有引用变量引用它就是可达的),哪些对象是“不可达的”(没有引用变量引用它就是不可达的),所有“不可达”对象都是可被垃圾回收的。

但对于如下程序:

class A

{

       B b;

}

class B

{

       A a;

}

public class Test

{

       public static void main(String[] args)

       {

              A a = new A();

              a.b = new B();

              a.b.a = a;

              a = null;

       }

}

上面程序中A对象、B对象,它们都“相互”引用,A对象的b属性引用B对象,而B对象的a属性引用A对象,但实际上没有引用变量引用A对象、B对象,因此它们在有向图中依然是不可达的,因此也会被当成垃圾处理。

程序员可以手动执行System.gc(),通知GC运行,但这只是一个通知,而JVM依然有权决定何时进行垃圾回收。

31、什么时候用assert。

assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。在实现中,assertion就是在程序中的一条语句,它对一个boolean表达式进行检查,一个正确程序必须保证这个boolean表达式的值为true;如果该值为false,说明程序已经处于不正确的状态下,assert将给出警告或退出。

Java的assert是关键字。

public class TestAssert

{

    public static void main(String[] args)

    {

         int a = 5;

         // 断言a>3

         assert a > 3;

         // 断言a<3,否则显示a不小于3,且a的值为:" + a

         assert a < 3 : "a不小于3,且a的值为:" + a;

    }

}

从上面代码可以看出,assert的两个基本用法如下:

assert logicExp;

asert logicExp : expr;

A.第一个直接进行断言,

B.第二个也是进行断言,但当断言失败失败时显示特定信息。

最后要指出:

虽然assert是JDK1.4新增的关键字,但有一点非常重要:

需要说明的是,Java命令默认不启动断言,

为了启动用户断言,应该在运行java命令时增加-ea(Enable Assert)选项。

为了启动系统断言,应该在运行java命令时增加-esa(Enable System Assert)选项。

32、Java中会存在内存泄漏吗,请简单描述。

为了搞清楚Java程序是否有内存泄露存在,首先了解一下什么是内存泄露:程序运行过程中会不断地分配内存空间;那些不再使用的内存空间应该即时回收它们,从而保证系统可以再次使用这些内存。如果存在无用的内存没有被回收回来,那就是内存泄露。

对于Java程序而言,只要Java对象一直处于可达状态,垃圾回收机制就不会回收它们——即使它们对于程序来说已经变成了垃圾(程序再也不需要它们了);但对于垃圾回收机制来说,它们还不是垃圾(还处于可达状态),因此不能回收。

看ArrayList中remove(int index)方法的源代码,程序如下:

public E remove(int index)

{

       // 检查index索引是否越界

       RangeCheck(index);

       // 使修改次数加1

       modCount++;

       // 获取被删除的元素

       E oldValue = (E)elementData[index];

       int numMoved = size - index - 1;

       // 整体搬家

       if (numMoved > 0)

              System.arraycopy(elementData, index+1

                     , elementData, index, numMoved);

       / /将ArrayList的size减1,

       // 并将最后一个数组赋为null,让垃圾回收机制回收最后一个元素

       elementData[--size] = null;

       return oldValue;

}

上面程序中粗体字代码elementData[--size] = null;就是为了避免垃圾回收机制而编写的代码,如果没有这行代码,这个方法就会产生内存泄露——每删除一个对象,但该对象所占用的内存空间却不会释放。

33、能不能自己写个类,也叫java.lang.String?

可以,但在应用的时候,需要用自己的类加载器去加载,否则,系统的类加载器永远只是去加载rt.jar包中的那个java.lang.String。

但在Tomcat的Web应用程序中,都是由webapp自己的类加载器先自己加载WEB-INF/classess目录中的类,然后才委托上级的类加载器加载,如果我们在Tomcat的Web应用程序中写一个java.lang.String,这时候Servlet程序加载的就是我们自己写的java.lang.String,但是这么干就会出很多潜在的问题,原来所有用了java.lang.String类的都将出现问题。

34、ArrayList如何实现插入的数据按自定义的方式有序存放

编程思路是:实现一个类对ArrayList进行包装,当程序试图向ArrayList中放入数据时,程序将先检查该元素与ArrayList集合中其他元素的大小,然后将该元素插入到指定位置。

class MyBean implements Comparable{

       public int compareTo(Object obj){

              if(! obj instanceof MyBean)

                     throw new ClassCastException()。

              MyBean other = (MyBean) obj;

              return age > other.age?1:age== other.age?0:-1;

       }

}

class MyTreeSet {

       private ArrayList  datas = new ArrayList();

       public void add(Object obj){

              for(int i=0;i<datas.size();i++){

                     if(obj.compareTo(datas.get(i) != 1){

                            datas.add(i,obj);

                     }

              }

       }

}

35、序列化接口的版本号(id)有什么用?

反序列化Java对象时必须提供该对象的class文件,现在的问题是随着项目的升级,系统的class文件也会升级,Java如何保证两个class文件的兼容性?

Java序列化机制允许为序列化类提供一个private static final long类型的serialVersionUID值,该Field值用于标识该Java类的序列化版本,也就是说如果一个类升级后,只要它的serialVersionUID值保持不变,序列化机制也会把它们当成同一个序列化版本。

通常建议程序员为序列化类指定serialVersionUID指定值!如果程序员没有为序列化类的serialVersionUID指定值,系统会该序列化类的serialVersionUID自动分配一个值。无论程序员对该类进行了怎样怎么样的修改(即使该修改对序列化没有任何影响),系统也会自动修改serialVersionUID的值;如果程序员主动为序列化类的serialVersionUID分配值,则可以控制只有对该类的修改影响序列化机制才去修改serialVersionUID值。

36、hashCode()方法的作用?

hashCode()方法与equals()方法相似,都是来自java.lang.Object类的方法,都允许用户定义的子类重写这两个方法。

一般来说,equals()这个方法是给用户调用的,如果你想根据自己的业务规则来判断两个对象是否相等,你可以重写equals()方法。简单来讲,equals方法主要是用来判断从表面上看或者从内容上看,两个对象是不是相等。

而hashCode()方法通常是给其他类来调用的,比如当我们要把两个对象放入HashSet时,由于HashSet要求两个对象不能相等,而HashSet判断两个对象是否相等的标准是通过equals()比较返回false、或两个对象的hashCode()方法返回值不相等——只要满足任意一个条件都可会认为两个对象不相等。

从这个角度来看,我们可以把hashCode()方法的返回值当成这个对象的“标识符”,如果两个对象的hashCode()相等,即可认为这两个对象是相等的。因此当我们重写一个类的equals()方法时,也应该重写它的hashCode()方法,而且这两个方法判断两个对象相等的标准也应该是一样的。

37、编写一个函数将一个十六进制数的字符串参数转换成整数返回。

       String str = "13abf";

       int len = str.length();

       int sum = 0;

       for(int i = 0 ; i < len ; i++)

       {

              char c = str.charAt(len - 1 - i);

              int n = Character.digit(c ,16);

              sum += n * (1 << (4 * i));

       }

       System.out.println(sum);

其实,也可以用Integer.parseInt(str,16),但面试官很可能是想考我们的编码基本功。

38、银行还款问题

银行贷款的还款方式中最常用的是一种叫“等额本息”,还款法,即借款人在约定还款期限内的每一期(月)归还的金额(产生的利息+部分本金)都是相等的,现有一笔总额为T元的N年期住房贷款,年利率为R,要求算出每一期的还款的本金和利息总额,请写出解决思路和任意一种编程语言实现的主要代码。

思路:既然是按月还款,那就要将N年按月来计算,即要还N*12个月,这样就可以求出每月要还的本金。由于每月要还的那部分本金所欠的时间不同,所以,它们所产生的利息是不同的,该部分本金的利息为:部分本金额*所欠月数*月利率。应该是这么个算法,如果利息还计利息,如果月还款不按年利率来算,老百姓算不明白的。

int monthMoney = T/N/12;

float monthRate = R/12;

int totalMonth = N * 12;

float totalRate = 0;

for(int i = 1 ; i <= totalMonth ; i++)

{

       totalRate += monthMoney * monthRate * i;

}

int result = monthMoney + totalRate/N/12;

39、任意数字序列“123456”之类,输出它们所有的排列组合

       String str = "fkjavx";

       char[] arr = str.toCharArray();

       String[] result = {""};

       for (int i = 0 ; i < arr.length ; i++ )

       {

              String[] tmp = new String[result.length * (arr.length - i)];

              int counter = 0;

              for (int j = 0 ; j < result.length; j++ )

              {

                     for (int k = 0 ; k < arr.length ; k++ )

                     {

                            System.out.println(j + " ----" + result[j]);

                            if(!result[j].contains(arr[k] + ""))

                            {

                                   tmp[counter++] = result[j] + arr[k];

                            }

                     }

              }

              result = tmp;

              System.out.println(java.util.Arrays.toString(result));

       }

       System.out.println(java.util.Arrays.toString(result));

40、构造器Constructor是否可被override?

构造器Constructor不能被继承,因此不能重写(Override),但可以被重载(Overload)。

41、接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)? 抽象类中是否可以有静态的main方法?

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

只要记住《疯狂Java讲义》中的归纳:抽象类的特征是有得有失,得到的功能是抽象类可以拥有抽象方法(当然也可以没有);失去的功能的是抽象类不能创建实例了。至于其他的,抽象类与普通类在语法上大致是一样的。

42、写clone()方法时,通常都有一行代码,是什么?

clone()有默认行为:super.clone();,因为首先要把父类中的成员复制到位,然后才是复制自己的成员。

43、abstract class和interface有什么区别?  

含有abstract修饰符的class即为抽象类,abstract 类不能创建的实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

需要说明的是,Java 8增强后的接口可以定义默认方法(使用default修饰的方法)和类方法(使用static修饰的方法),接口中的默认方法和类方法都不再是抽象方法,都需要提供方法体。Java 9则允许接口中定义private方法,private方法可以拥有方法体。

下面比较一下两者的语法区别:

1.抽象类可以有构造器,接口中不能有构造器。

2.抽象类中可以有普通成员变量,接口中没有普通成员变量

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。Java 8增强的接口可拥有默认方法和类方法,接口中的默认方法和类方法都不再是抽象方法,都需要提供方法体。Java 9则允许接口中定义private方法,private方法可以拥有方法体。

4. 抽象类中的抽象方法的访问类型可以是public,protected和默认访问权限。但接口中的方法只能是public的。如果普通实例方法则必须是抽象方法,如果是默认方法则必须使用default修饰;如果是类方法则必须使用static修饰。

5. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

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

下面接着再说说两者在应用上的区别:

接口更多的是在系统架构设计方法发挥作用,接口体现的是一种规范。而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板模式是抽象类的一个典型应用,假设项目中需要使用大量的DAO组件,这些DAO组件通常都具有增、删、改、查等基本方法,因此我们就可以定义一个抽象的DAO基类,然后让其他DAO组件来继承这个DAO基类,把这个DAO基类当成模板使用。

44、abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

abstract的method 不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系!

native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用。

关于synchronized与abstract不能同时使用。因为synchronized修饰一个方法时,表明将会使用该方法的调用者作为同步监视器,但对于一个abstract方法而言,它所在类是一个抽象类,抽象类也无法创建实例,因此也就无法确定synchronized修饰方法时的同步监视器了,因此synchronized与abstract不能同时使用。

45、什么是内部类? Static Nested Class 和 Inner Class的不同。

内部类就是在一个类的内部定义的类,非静态内部类中不能定义静态成员。静态内部类不能访问外部类的静态成员。

内部类作为其外部类的一个成员,因此内部类可以直接访问外部类的成员。但有一点需要指出:静态成员不能访问非静态成员,因此静态内部类不能访问外部类的非静态成员。

如果内部类使用了static修饰,那这个内部类就是静态内部类,也就是所谓的static Nested Class;如果内部类没有使用修饰,它就是Inner Class。除此之外,还有一种局部内部类:在方法中定义的内部类就是局部内部类,局部内部类只在方法中有效。

对于Static Nested Class来说,它使用了static修饰,因此它属于类成员,Static Nested Class的实例只要寄生在外部类中即可。因此使用Static Nested Class十分方便,开发者可以把外部类当成Static Nested Class的一个包即可。

对于Inner Class而言,它是属于实例成员,因此Inner Class的实例必须寄生在外部类的实例中,因此程序在创建Inner Class实例之前,必须先获得一个它所寄生的外部类的实例。否则程序无法创建Inner Class的实例。例如如下代码:

class Outer

{

       class Inner

       {

       }

}

public class Test

{

       public static void main(String[] args)

       {

              Outer.Inner inner;

              Outer outer = new Outer();

              // 必须先获得外部类的实例,然后才能调用构造器。

              inner = outer.new Inner();

       }

}

46、内部类可以引用它的外部类的成员吗?有没有什么限制?

内部类可以访问所在外部类的成员。

但有一点需要注意:静态成员不能访问非静态成员,因此静态内部类(属于静态成员)就不能访问外部类的非静态成员。

47、Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?

匿名内部类必须显式继承某个父类或实现某个接口,但匿名内部类只能显式继承某个父类或实现某个接口。下面是匿名内部类特殊的语法:

new 父类|父接口()

{

       类体实现部分

}

从上面语法不难看出,匿名内部类必须继承其他类或实现其他接口。

对于使用函数式接口(只包含一个抽象方法的接口),Java 8提供了简洁的Lambda表达式来创建对象。对于之包含一个抽象方法的函数式接口而言,可使用如下代码来创建实现该接口的对象

(被实现的抽象方法的形参列表) -> {

       实现抽象方法的方法体;

}

从上面语法规范可以看出,Lambda表达式就是负责实现函数式接口中的抽象方法,系统就根据Lambda表达式来创建实现函数式接口的对象。

48、super.getClass()方法调用

下面程序的输出结果是多少?

import java.util.Date;

public class Test extends Date{

       public static void main(String[] args) {

              new Test().test();

       }

       public void test(){

              System.out.println(super.getClass().getName());

       }

}

程序输出的是Test。

《疯狂Java讲义》(第2版)中有关于super关键字很透彻的解释:super它只是一个限定词,当用super引用时,它也是引用当前对象本身,只是super只是限定了访问当前对象从父类那里继承得到成员变量或方法。

如果需要访问父类的类名,应该使用如下语法:

super.getClass().getSuperclass().getName()

49、JDK中哪些类是不能继承的?

使用final修饰的类都不可以被继承。

实际上即使自己开发的类,也可以通过使用final修饰来阻止被继承。通过使用final修饰的类被称为最终类,最终类不能派生子类,这样该类就被完全地封闭起来了,不会有子类来重写它的方法,因此更加安全。

50、String s = "Hello";s = s + " world!";这两行代码执行后,原始的String对象中的内容到底变了没有?

没有。因为String被设计成不可变类(immutable),所以它的所有对象都是不可变对象。在这段代码中,s原先指向一个String对象,内容是 "Hello",然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。

通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为 String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。

实际上,当我们需要一个字符串对象时,应该使用如下语法来创建String对象:

Sring s = "fkjava.org";

也就是直接使用字符串直接量的语法。而不是:

String s = new String("fkjava.org");

对于第二种语法而言,每次都会调用构造器生成新的String对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。

基于这样一种想法,Java提供了字符串缓存池来管理字符串直接量,当程序多次用到同一个字符串直接量时,系统会让它们都引用字符串缓存池中的同一个String对象。因此使用在程序中使用字符串直接量可以充分利用这个特性来降低系统内存开销,提高程序性能。

51、是否可以继承String类?

String类是final类,不可以被继承。

52、如何把一段逗号分割的字符串转换成一个数组?

A. 在以前的时候,Java提供了一个StingTokenizer工具类来处理字符串分割的问题。比如使用如下语法:

StringTokenizer st = new StringTokenizer("this,is,a,test" , ",");

while (st.hasMoreTokens())

{

       System.out.println(st.nextToken());

}

这样程序将会输出

this

is

a

test

B. 后来Java为String类增加了正则表达式支持,StingTokenizer基本上没用了。因此上面代码可以简写为:

String [] result = "this,is,a,test".split(",");

其中result数组中就存放了this、is、a、test等字符串元素。

53、下面这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d";

答:对于如下代码:

String s1 = "a";

String s2 = s1 + "b";

String s3 = "a" + "b";

System.out.println(s2 == "ab");

System.out.println(s3 == "ab");

第一条语句打印的结果为false,第二条语句打印的结果为true。

Java会在编译时对字符串相加进行优化处理,如果整个表达式中所有参与运算的都是字符串直接量,Java会在编译时就把这个表达式的值计算出来,然后直接将结果赋值给字符串引用变量。因此上面题目中定义的String s = "a" + "b" + "c" + "d";实际上相当于直接定义了"abcd"的字符串直接量,所以,上面的代码应该只创建了一个String对象。

而且这个字符串直接量会被放入字符串缓存池中。如下两行代码,

String s = "a" + "b" + "c" + "d";

System.out.println(s == "abcd");

由于s引用了字符串缓存池中的"abcd"字符串,因此上面输出结果应该为true。

54、Collection框架中实现比较要实现什么接口

Java集合框架中需要比较大小的集合包括TreeMap、TreeSet,其中TreeMap会根据key-value对中key的大小进行排序,而TreeSet则会对集合元素进行排序。

因此TreeMap的key、TreeSet的集合元素,都需要可以比较大小。集合框架中之比较大小的有两种方式:

A.自然排序:对于自然排序来说,要求TreeMap中的所有key都实现Comparable接口,实现该接口时需要实现一个int compareTo(T o)方法,用于判断当前对象与o对象之间的大小关系。如果该方法返回正整数,则说明当前对象大于被比较的o对象;如果该方法返回0,说明两个对象相等;如果该方法返回负整数,则说明当前对象小于被比较的o对象;JDK的很多类都已经实现了Comparable接口,例如String、Date、BigDecimal等。

B.定制排序:定制排序需要在创建TreeMap或TreeSet时传入一个Comparator对象,此时TreeMap或TreeSet不再要求key、集合元素本身是可比较大小的,而是由Comparator来负责比较集合元素的大小。Comparator本身只是一个接口,因此创建Comparator对象只能是创建它的实现类的对象,Comparator的实现类需要实现int compare(T o1, T o2)方法,该方法用于判断o1、o2两个对象的大小,如果该方法返回正整数,则说明o1大于o2、如果该方法返回负整数,则说明o1小于o2、如果返回0,则说明两个对象相等。

55、ArrayList和Vector的区别

这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的——这是由List集合规范制订的。

ArrayList与Vector底层都是基于数组的,因此它们的实现代码也大致相似。区别在于Vector是一个古老的集合,从JDK1.0开始就有了,因此它包含了大量方法名很长的方法,JDK 1.2开始引入集合框架,引入List接口,才让Vector实现了List接口,因此又增加了一些List接口中定义的方法。总体来说,ArrayList可以完全代替Vector,除了在一些很古老的API中强制要求使用Vector之外。

Vector还有一个特征:它是线程安全的,因此性能比较差。而ArrayList并不是线程安全的,因此性能较好。实际上即使需要在多线程环境下使用List集合,也应该选择ArrayList,而不是Vector,因为Java还提供了一个Collections工具类,它可以把ArrayList包装成线程安全的集合类,例如如下代码:

List list = Collections.synchronizedList(new ArrayList());

56、HashMap和Hashtable的区别

HashMap与Hashtable的区别类似于ArrayList与Vector的区别。

Hashtable与Vector都是JDK 1.0就有一个一个古老的集合,因此Hashtable是一个继承自Dictionary的古老集合。

从JDK 1.2引入集合框架的Map接口之后,Java让Hashtable也实现了Map接口,因此Hashtable也新增实现了一些Map接口中定义的方法。实际上Hashtable与HashMap底层的实现很相似,它们都是基于Hash表的实现。

HashMap与Hashtable的区别主要有如下两点:

A.HashMap允许使用null作为key或value,而Hashtable不允许。

B.HashMap是线程不安全的,因此性能较好;但Hashtable是线程安全的,因此性能较差。

实际上,即使在多线程环境下,Java提供了Collections工具类把HashMap包装成线程安全的类,因此依然应该使用HashMap,如下代码所示:

Map map = Collections. synchronizedMap(new HashMap());

简单的说,编程时应该尽量避免使用Hashtable,除非在一个古老的API中强制要求Hashtable。

57、List 和 Map 区别?

表面来看,List是一个只是存放单个元素的集合,List集合所包含的元素可以重复,元素按放入的先后顺序来存放,程序可以通过元素的索引来读取元素,因此List相当于一个动态数组;Map则是一个存放key-value对的集合,Map里存放的key-value对是无序的,Map包含的key是不允许重复的。程序可以key来取出该key对应的value。

深入阐述:如果换个角度来看,完全可以把List当成Map来看,List相当于一个key都是int类型的Map,程序通过元素的索引(相当于通过int类型的key)来读取List集合的元素时,完全也可以当成Map根据key来读取value。从另一个角度来看,Map也可以当成元素索引可以是任意类型的List集合。

58、List, Set, Map是否继承自Collection接口?

List、Set是,Map不是。

59、List、Map、Set三个接口,存取元素时,各有什么特点?

Set集合是最接近Collection的集合,因此Set集合几乎没有在Collection增加什么方法。Set集合代表了元素无序、元素不允许重复的集合(Set只是在Collection规范上增加了元素不允许重复的约束)。

List集合则在Collection的基础上为元素增加了索引的特性,因此List集合代表了集合元素有序、集合元素可以重复的集合。

Map则代表了存放key-value对的集合,程序可以通过key来获取其中的value。

就Set集合来说,对于开发者而言,它的集合元素是无序的,似乎显得有些杂乱、无规律,但对计算机而言这不可能,因此计算机需要快速存、取Set集合中的元素。Set集合有两个实现类:HashSet与TreeSet,其中HashSet底层其实使用了一个数组来存放所有集合元素,然后通过Hash算法来决定每个集合元素在底层数组中存放位置,因此HashSet对集合元素的存、取就是Hash算法+数组存、取——也就是说HashSet只比数组存、取多了些Hash算法开销,因此性能非常快。TreeSet底层则完全是一个红黑树,因此红黑树是折衷平衡的排序二叉树,它底层没有数组开销,存、取元素时都是基于红黑树算法的,因此内存开销较小,但性能略差。

对于List集合而言,主要有两个实现:ArrayList与LinkedList,其中ArrayList底层是基于数组的,而且ArrayList存、取元素本身就是通过元素索引来进行的,因此ArrayList对元素的存、取性能非常好,几乎等同于存、取数组元素。但则添加、删除元素时需要对数组元素进行“整体搬家”,因此添加、删除元素时性能较差。而LinkedList底层则是基于一个链表实现的,当从链表中存、取元素时,需要定位元素的位置,系统开销较大。但添加、删除元素时,只要修改元素的引用(相当于指针)即可,因此性能非常好。

对于Map集合而言,其底层存、取性能与Set集合完全一样。其实Set集合本身就是基于Map实现的——如果我们把Map集合的所有value都当成空对象处理、只考虑Map集合的key,Map集合就变成了Set集合。换个角度来看,如果我们向Set集合中添加的对象是key-value所组成的Entry对象,那么Set集合也就变成了Map集合。

60、说出ArrayList,Vector, LinkedList的存储性能和特性

ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

LinkedList也是线程不安全的,LinkedList提供了一些方法,使得LinkedList可以被当作栈和队列来使用。

实际上Java提供了Collections工具类,它可以把ArrayList、LinkedList包装成线程安全的集合,因此实际编程中应该避免使用Vector。

61、去掉一个Vector集合中重复的元素

Vector newVector = new Vector();

for (int i = 0 ; i < vector.size() ; i++)

{

       Object obj = vector.get(i);

       if(!newVector.contains(obj))

       {

              newVector.add(obj);

       }

}

另外,还有一种更见简单的方式:将Vector添加到HashSet,例如如下代码:

HashSet set = new HashSet(vector);

但上面代码将会导致Vector中元素丢失顺序。

62、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?

说明:其实这个题目本身有问题!因为Set只是一个接口,它的不同实现类判断元素是否相等的标准是不同的。笼统地说,Set里的元素是不能重复的,判断元素重复使用equals()。而不是==。

对于HashSet而言,判断两个对象是否相等是通过equals()和hashCode()方法,只要两个对象通过 equals()比较返回false、或两个对象的hashCode()不相等,那么HashSet就会把它们当成不相同。

对于TreeSet而言,判断两个对象相等的唯一标准是:两个对象通过compareTo(Object obj)比较是否返回0,与equals()方法无关。只要两个对象通过compareTo(Object obj)比较没有返回0,Java就会把它们当成两个对象处理——这一点是很多人容易误解的,不过我们可以通过《疯狂Java讲义》中的一个示例来说明:

class Z implements Comparable

{

       int age;

       public Z(int age)

       {

              this.age = age;

       }

       // 重写equals()方法,总是返回true

       public boolean equals(Object obj)

       {

              return true;

       }

       //重写了compareTo(Object obj)方法,总是返回正整数

       public int compareTo(Object obj)

       {

              return 1;

       }

}

public class TreeSetTest2

{

       public static void main(String[] args)

       {

              TreeSet set = new TreeSet();

              Z z1 = new Z(6);

              set.add(z1);

              //输出true,表明添加成功

              System.out.println(set.add(z1));    //①

              //下面输出set集合,将看到有两个元素

              System.out.println(set);

              //修改set集合的第一个元素的age变量

               ((Z)(set.first())).age = 9;

              //输出set集合的最后一个元素的age变量,将看到也变成了9

              System.out.println(((Z)(set.last())).age);

       }

}

上面程序中两个Z对象通过equals()比较总会返回true,但通过compareTo(Object obj)比较总是不会返回0,因此两次向TreeSet中添加同一个元素,TreeSet会把它们当成不同的对象进行处理,最后TreeSet集合中会显示有两个对象,但实际上是同一个对象。

63、你所知道的集合类都有哪些?主要方法?

最常用的集合接口是 Set、List、Queue,它们都是Collection的子接口,除此之外还有Map接口。

对于Set集合而言,它的常用实现类包括HashSet与TreeSet。HashSet还有一个子类:LinkedHashSet。

对于List集合而言,它的常用实现类包括ArrayList、Vector与LinkedList。

对于Queue集合而言,它有一个子接口Deque(代表双端队列),它的常用实现类包括ArrayDeque与LinkedList。

对于Map集合而言,它的常用实现类是HashMap与TreeMap。HashMap还有一个子类:LinkedHashMap。

至于这些集合的方法,由于集合类也就是所谓的“容器类”,因此它的方法无非就是向容器中添加、删除、取出、遍历元素的方法。

对于List集合而言,由于它的集合元素都有有序的、有索引的,因此它包括了大量根据索引来添加、删除、取出集合元素的方法。

对于Deque集合而言,由于它是双端队列,即可当成队列使用,也可当成栈使用,因此它增加栈、队列的方法,如offer、peek、push、pop等。

对Map而言,它所包含的无非就是根据key来添加、删除、取出value的方法。

64、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

对。因为equals()方法可以由开发者重写,hashCode()方法也可以由开发者来重写,因此它们是否相等并没有必然的关系。

一般来说,如果对象要保存在HashSet或作为HashMap的key中,它们通过equals()比较相等,那么它们的hashCode()返回值也应该相等。

65、TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo()方法,还是使用的子类的compareTo()方法,还是抛异常!

根据TreeSet底层的实现:TreeSet底层的实现就是红黑树,因此当程序向TreeSet中添加集合元素时,程序会多次调用该对象的compareTo()方法与TreeSet中的集合元素进行比较,直到找到该元素在红黑树中应当所在节点位置。因此该问题的答案是:当前正在添加父类对象就多次调用父类对象的compareTo()方法;当前正在添加子类对象就多次调用子类对象的compareTo()方法。

至于程序是否抛出异常,则取决于compareTo()方法的实现,如果子类在实现compareTo()方法时,试图把被比较对象转换为子类对象之后再进行比较——如果TreeSet集合中已经包括了父类对象,这就会引起ClassCastException。

示例代码如下:

class A implements Comparable

{

       int age;

       public A(int age)

       {

              this.age = age;

       }

       public int compareTo(Object obj)

       {

              System.out.println("AAAAAAAAAA");

              A target = (A)obj;

              return age > target.age ? 1 : age < target.age ? -1 : 0;

       }

       public String toString()

       {

              return getClass() + ",age:" + age;

       }

}

class B extends A implements Comparable

{

       public B(int age)

       {

              super(age);

       }

       public int compareTo(Object obj)

       {

              System.out.println("BBBBBBBBB");

              A target = (A)obj;

              return age > target.age ? 1 : age < target.age ? -1 : 0;

       }

}

public class TreeSetTest2

{

       public static void main(String[] args)

       {

              TreeSet set = new TreeSet();

              set.add(new A(3));

              set.add(new B(1));

              set.add(new A(2));

              for(Iterator it = set.iterator(); it.hasNext() ;)

              {

                     System.out.println(it.next());

              }

       }

}

上面程序可以看到,输出:

AAAAAAAAAA

BBBBBBBBB

AAAAAAAAAA

AAAAAAAAAA

第一次添加A对象,所以调用A对象的compareTo()方法;第二次添加B对象,所以程序调用了B对象的compareTo()方法;第三次再次添加A对象,由于集合中已经有两个对象,因此程序两次调用了A对象的compareTo()方法与集合中的元素进行比较。

66、说出一些常用的类,包,接口,请各举5个

常用的包有:

java.lang包下包括Math、System、StringBuilder、StringBuffer、Runtime、Thread、Runnable等。

java.util包下包括List、Set、Map,以及这些接口的常用实现类:ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等。

java.io包下包括InputStream、OutputStream、Reader、Writer、FileInputStream、FileOutputStream、FileReader、FileWriter、BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter等

java.sql包下包括Connection、Statement、PreparedStatement、ResultSet等。

java.net包下包括Socket、ServerSocket、URL、URLConnection、DatagramPacket、DatagramSocket等。

如果为让别人感觉你对Android很熟悉,还应该增加一些Android常用的包、类,如:

android.app包下有:Activity、ListActivty、TabActivity、AlertDialog、AlertDialog.Builder、Notification、Service等。

android.content包下有:ContentProvider、ContentResolver、ContentUris、ContentValues、Context等

android.database包下有Cursor等

android.database.sqlite包下有:SQLiteDatabase、SQLiteOpenHelper等

android.graphics包下有Bitmap、BitmapFactory、Canvas、Color、Matrix、Paint、Path等。

android.widget包下有TextView、Button、CheckBox、RadioButton、ListView、GridView、Spinner、Gallery等。

67、Java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?

字节流,字符流。字节流由InputStream OutputStream派生出来,字符流由Reader、Writer派生出来。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。

Java的IO流还也分为节点流和过滤流,其中节点流是直接关联到物理IO节点的流,而过滤流则需要建立在其他流的基础之上。Java为过滤流提供了FilterInputStream、FilterOutputStream、FilterReader、FilterWriter这四个基类。

一般来说,建议读者按《疯狂Java讲义精粹》的11.4节(或疯狂Java讲义15.4节)关于IO流体系的表格进行回答。

68、字节流与字符流的区别

字节流和字符流区别非常简单,它们的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同:字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。

字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类。

字节流直接是基于字节进行输入、输出的,因此它的适用性更广。字符流则在处理文本内容的的输入、输出时更加方便——不会出现读取半个字符的情形。

Java提供了将字节流转换为字符串的InputStreamReader和OutputStreamWriter,但没有提供将字符流转化为字节流的方法。因为:字节流比字符流的使用范围更广,但字符流比字节流操作方便。如果有一个流已经是字符流了,也就是说是一个用起来更方便的流,为什么要转换成字节流呢?反之,如果现在有一个字节流,但我们知道这个字节流的内容都是文本内容,那么把它转换成字符流来处理就会更方便一些,所以Java只提供了将字节流转换字符流的转换流,没有提供将字符流转换成字节流的转换流。

69、什么是Java序列化,如何实现Java序列化?或者请解释Serializable接口的作用。

Java序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象,对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流(无论是从磁盘中获取,还是通过网络获取),都可以将这种二进制流恢复成原来的Java对象。

实现Java序列化有两种方式:

A.让Java类实现Serializable接口。

B.让Java类实现Externalizable接口,实现该接口时还必须实现readExternal()、writeExternal()这两个方法。

一旦Java类实现了上面两种接口,接下来程序中就可通过ObjectInputStream、ObjectOutputStream来读取、保存Java对象。

Serializable接口只是一个标记接口,实现该接口无需实现任何方法,实现了该接口的类就是可序列化的类。

序列化对于Java开发非常重要,例如在Web开发中,如果对象需要保存在了Session中,Tomcat在某些时候需要把Session中的对象序列化到硬盘,因此放入Session中的对象必须是可序列化的,要么实现Serializable接口,要么实现Externalizable接口。还有,如果一个对象要经过网络传输(比如RMI远程方法调用的形参或返回值),这个对象也应该是可序列化的。

70、描述一下JVM加载class文件的原理机制?

当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过

加载

连接

初始化

三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。

类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是我们前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:

A.从本地文件系统来加载class文件,这是前面绝大部分类加载方式。

B.从JAR包中加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就是放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。

C.通过网络加载class文件。

D.把一个Java源文件动态编译、并执行加载。

当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:

1.Bootstrap ClassLoader:根类加载器。

2.Extension ClassLoader:扩展类加载器。

3.System ClassLoader:系统类加载器。

Bootstrap ClassLoader,被称为引导(也称为原始或根)类加载器。它负责加载Java的核心类。在Sun的JVM中,当执行java.exe的命令时使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。

Extension Classloader,被称为扩展类加载器,它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext或者由java.ext.dirs系统属性指定的目录)中JAR包的类。

通过这种方式,我们就可以为Java扩展核心类以外的新功能,只要我们把自己开发的类打包成JAR文件,然后放入JAVA_HOME/jre/lib/ext路径即可(对于笔者的安装环境来说,扩展路径为:D:/Java/jdk1.8.0.5/jre/lib/ext)。

System Classloader,被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以类加载器作为父加载器。

71、heap和stack有什么区别。

stack内存指的是程序进入一个方法时,系统会专门为这个方法分配一块内存空间,这块内存空间也被称为该方法栈区,该方法的栈区专门用于存储该方法中定义的局部变量,包括基本类型的变量和引用变量。当这个方法结束时,该方法栈区将会自动被销毁,栈区中的所有局部变量都会随之销毁。

heap内存是Java虚拟机拥有的内存区,所有Java对象都将被放在heap内存内,位于heap内存中的Java对象由系统的垃圾回收器负责跟踪管理——也就是进行垃圾回收,当堆内存中的Java对象没有引用变量引用它时,这个Java对象就变成了垃圾,垃圾回收期就会在合适的时候回收它。

72、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后?

肯定会执行。finally{}块的代码只有在try{}块中包含遇到System.exit(0);之类的导致Java虚拟机直接退出的语句才会不执行。

当程序执行try{}遇到return时,程序会先执行return语句,但并不会立即返回——也就是把return语句要做的一切事情都准备好,也就是在将要返回、但并未返回的时候,程序把执行流程转去执行finally块,当finally块执行完成后就直接返回刚才return语句已经准备好的结果。

例如我们有如下程序:

public class Test

{

       public static void main(String[] args)

       {

              System.out.println(new Test().test());;

       }

       static int test()

       {

              int x = 1;

              try

              {

                     return x;

              }

              finally

              {

                     System.out.println("finally块执行:" + ++x);

              }

       }

}

此时的输出结果为:

finally块执行:2

1

看到上面程序中finally块已经执行了,而且程序执行finally块时已经把x变量增加到2了。但test()方法返回的依然是1,这就是由return语句执行流程决定的,Java会把return语句先执行完、把所有需要处理的东西都先处理完成,需要返回的值也都准备好之后,但是还未返回之前,程序流程会转去执行finally块,但此时finally块中的对x变量的修改已经不会影响return要返回的值了。

但如果finally块里也包含return语句,那就另当别论了, 因为finally块里的return语句也会导致方法返回,例如把程序该为如下形式:

public class Test

{

       public static void main(String[] args)

       {

              System.out.println(new Test().test());;

       }

       static int test()

       {

              int x = 1;

              try

              {

                     return x++;

              }

              finally

              {

                     System.out.println("finally块执行:" + ++x);

                     return x;

              }

       }

}

此时的输出结果为:

finally块执行:3

3

正如介绍的,程序在执行return x++;时,程序会把return语句执行完成,只是等待返回,此时x的值已经是2了,但程序此处准备的返回值依然是1。接下来程序流程转去执行finally块,此时程序会再次对x自加,于是x变成了3,而且由于finally块中也有return x;语句,因此程序将会直接由这条语句返回了,因此上面test()方法将会返回3。

73、下面的程序代码输出的结果是多少?

public class smallT

{

       public static void main(String args[])

       {

              smallT t = new smallT();

              int b = t.get();

              System.out.println(b);

       }

       public int get()

       {

              try

              {

                     return 1 ;

              }

              finally

              {

                     return 2 ;

              }

       }

}

输出结果是:2。

这个程序还是刚才介绍的return语句和finally块的顺序问题。

Java会把return语句先执行完、把所有需要处理的东西都先处理完成,需要返回的值也都准备好之后,但是还未返回之前,程序流程会转去执行finally块。但如果在执行finally块时遇到了return语句,程序将会直接使用finally块中的return语句来返回——因此上面程序将会输出2。

74、final, finally, finalize的区别。

final是一个修饰符,它可以修饰类、方法、变量。

final修饰类时表明这个类不可以被继承。

final修饰方法时表明这个方法不可以被其子类重写。

final修饰变量时可分为局部变量、实例变量和静态变量,当final修饰局部变量时,该局部变量可以被一次赋值,以后该变量的值不能发生改变;当final修饰实例变量时,实例变量必须由程序员在构造器、初始化块、定义时这3个位置的其中之一指定初始值;当final修饰静态变量时,静态变量必须由程序在静态初始化块、定义时这2个位置的其中之一指定初始值。

finally是异常处理语句结构的一部分,表示总会执行的代码块。

finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收。但实际上重写该方法进行资源回收并不安全,因为JVM并不保证该方法总被调用。

75、运行时异常与一般异常有何异同?

Checked异常体现了Java的设计哲学:没有完善错误处理的代码根本就不会被执行!

对于Checked异常的处理方式有两种:

A.当前方法明确知道如何处理该异常,程序应该使用try...catch块来捕获该异常,然后在对应的catch块中修补该异常。

B.当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。

运行时异常指的就是RuntimeException或其子类的实例,运行时异常则更加灵活,编译器不会强制要求程序员必须处理该异常,运行时异常可以既不显式声明抛出,也不使用try...catch进行捕捉。但如果程序需要捕捉运行时异常,也可以使用try...catch块来捕捉运行时异常。

运行时异常的优点在于灵活:程序员想处理就处理,不想处理直接忽略该异常即可。但由于编译器不会强制检查运行时异常,因此程序完全有可能在运行时因为这些异常而结束。常见的运行时异常有NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException、ArithmeticException、IllegalArgumentException等。

76、error和exception有什么区别?

Error错误,一般是指虚拟机相关的问题,如系统崩溃、虚拟机出错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。

由于编译器会对Error进行检查,不会强制要求程序员必须处理Error,因此Error也被归入unchecked异常分类中(另外:运行时异常也属于unchecked异常)。

Exception表示一种设计或实现问题。也就是说,程序员应该对这些情况进行考虑、并提供相应的处理。

77、Java中的异常处理机制的简单原理和应用。

程序运行过程中可能出现各种“非预期”情况,这些非预期情况可能导致程序非正常结束。为了提高程序的健壮性,Java提供了异常处理机制:

try

{

       s1...

       s2...

       s3...

}

catch(Exception ex)

{

       // 对异常情况的修复处理

}

对于上面处理流程,当程序执行try块里的s1、s2、s3遇到异常时,Java虚拟机将会把这个异常情况封装成异常对象,这个异常对象可以被后面对应的catch块捕捉到,这样保证这些异常会得到合适的处理。

Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception,Error错误,一般是指虚拟机相关的问题,如系统崩溃、虚拟机出错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。

Exception表示一种设计或实现问题。也就是说,程序员应该对这些情况进行考虑、并提供相应的处理。

异常有可分为Runtime异常和Checked异常,Checked异常体现了Java的设计哲学:没有完善错误处理的代码根本就不会被执行!对于Checked异常的处理方式有两种:

A.当前方法明确知道如何处理该异常,程序应该使用try...catch块来捕获该异常,然后在对应的catch块中修补该异常。

B.当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。

实际上Java的Checked异常后来“争议不断”,因为Checked异常要求程序员要么显式声明抛出,要么进行捕捉,不能对Checked异常不闻不问,这样就给编程带来了一定的复杂度,比如Spring、Hibernate框架的一大特点就是把Checked异常包装成了Runtime异常。

Runtime异常则比较灵活,开发者既可以选择捕获Runtime异常,也可以不捕获。

78、请写出你最常见到的5个runtime exception。

对于一个有1~2年左右编程经验的人来说,总会经常遇到一些常见的异常,其中有些就是Runtime Exception。比如:

NullPointerException - 当调用一个未初始化的引用变量(实际值为null)的实例Field、实例方法时都会引发该异常。

ArithmeticException - 算术异常。比如5/0将引发该异常。

ArrayIndexOutOfBoundsException:数组索引越界异常。

ClassCastException:类型转换异常。

IllegalArgumentException:参数非法的异常。

79、Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别代表什么意义?在try块中可以抛出异常吗?

try块表示程序正常的业务执行代码。如果程序在执行try块的代码时出现了“非预期”情况,JVM将会生成一个异常对象,这个异常对象将会被后面相应的catch块捕获。

catch块表示一个异常捕获块。当程序执行try块引发异常时,这个异常对象将会被后面相应的catch块捕获。

throw用于手动地抛出异常对象。throw后面需要一个异常对象。

throws用于在方法签名中声明抛出一个或多个异常类,throws关键字后可以紧跟一个或多个异常类。

finally块代表异常处理流程中总会执行的代码块。

对于一个完整的异常处理流程而言,try块是必须的,try块后可以紧跟一个或多个catch块,最后还可以带一个finally块。Java 7引入了自动关闭资源的try语句,这种自动关闭资源的try语句可以单独存在。例如如下代码是正确的:

try(

       // 声明、并创建可以被自动关闭的资源

)

{

       // 执行语句

}

在上面try关键字的圆括号内,可用于声明、并创建能被自动关闭的资源,这些能被自动关闭的资源必须实现Closeable或AutoCloseable接口。

80、Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用?

在Java5以前,有如下两种:

第一种:继承Thread类,重写它的run()方法。

代码如下:

new Thread()

{

       public void run()

       {

              // 线程执行体

       }

}.start();

第二种:实现Runnable接口,并重写它的run()方法。

代码如下:

new Thread(new Runnable()

{

       public void run()

       {

              // 线程执行体

       }    

}).start();

从上面代码不难看出,线程的执行体是一个run()方法,然后程序通过start()方法启动一条线程。

从Java 5开始,Java提供了第三种方式来创建多线程:实现Callable接口,并实现call()方法。Callable接口相当于Runnable接口的增强版,因为Callable接口中定义的call()方法既拥有返回值,也可以声明抛出异常。

代码如下:

new Thread(new FutureTask<Object >(new Callable<Object>()

{

       public Object call() throws Exception

       {

              // 线程执行体

       }

})).start();

不仅如此,Java 5还提供了线程支持,ExecutorService对象就代表了线程池,如果开发者利用ExecutorService来启动线程,ExecutorService底层会负责管理线程池。此时,开发者只要把Runnable对象传给ExecutorService即可。如下代码:

ExecutorService pool = Executors.newFixedThreadPool(3)

pool.execute(new Runnable()

{

       public void run()

       {

              // 线程执行体

       }

});

如果执行通过Callable方式实现的线程,则可按如下代码:

ExecutorService pool = Executors.newFixedThreadPool(3)

pool.execute(new FutureTask<Object >(new Callable<Object>()

{

       public Object call() throws Exception

       {

              //线程执行体

       }

}));

用synchronized关键字修饰同步方法。需要指出的是,非静态的同步方法的同步监视器是this,也就是调用该方法的对象,而静态的同步方法的同步监视器则是该类本身。因此使用synchronized修饰的静态方法、非静态方法的同步监视器并不相同,只有基于同一个同步监视器的同步方法、同步代码块才能实现同步。

反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

82、sleep()和wait()有什么区别?

sleep()是Thread类的静态方法,它的作用是让当前线程从运行状态转入阻塞状态,线程执行暂停下来,当一个线程通过sleep()方法暂停之后,该线程并不会释放它对同步监视器的加锁。

wait()是Object对象的方法,但实际上只有同步监视器才能调用该方法。当程序在同步代码块、或同步方法内通过同步监视器调用该方法时,将会导致当前线程释放对该同步监视器的加锁,而该线程则会进入该同步监视器的等待池中,直到该同步监视器调用notify()或notifyAll()来通知该线程。

83、同步和异步有何异同,在什么情况下分别使用他们?举例说明。

如果有一个资源需要被一个或多个线程共享,这个资源就变成了“竞争”资源,此时多条线程必须按某种既定的规则、依次访问、修改这个“竞争”资源,当一条线程正在访问、修改该“竞争”资源时,其他线程不能同时修改这份“竞争”资源,这就是同步处理。

对于一个银行账户,如果有多个线程试图去访问这个账户时,如果不对多个线程进行同步控制,有可能账户余额只有1000块,但多个线程都试图取款800块时,这些线程同时判断余额之后,都会显示余额足够,从而导致每个线程都取款成功。这显然不是我们希望看到结果。

当程序试图执行一个耗时操作时,程序不希望阻塞当前执行流,因此程序也不应该试图立即获取该耗时操作返回的结果,此时就使用异步编程了,典型的应用场景就是Ajax。当浏览器通过JavaScript发出一个异步请求之后,JavaScript执行流并不会停下来,而是继续向下执行,这就是异步。程序会通过监听器来监听远程服务器响应的到来。

84、多线程有几种实现方法?同步有几种实现方法?

在Java5以前,有如下两种:

第一种:继承Thread类,重写它的run()方法。

代码如下:

new Thread()

{

       public void run()

       {

              //线程执行体

       }

}.start();

第二种:实现Runnable接口,并重写它的run()方法。

代码如下:

new Thread(new Runnable()

{

       public void run()

       {

              // 线程执行体

       }

}).start();

从上面代码不难看出,线程的执行体是一个run()方法,然后程序通过start()方法启动一条线程。

从Java 5开始,Java提供了第三种方式来创建多线程:实现Callable接口,并实现call()方法。Callable接口相当于Runnable接口的增强版,因为Callable接口中定义的call()方法既拥有返回值,也可以声明抛出异常。

代码如下:

new Thread(new FutureTask<Object >(new Callable<Object>()

{

       public Object call() throws Exception

       {

              // 线程执行体

       }

})).start();

不仅如此,Java 5还提供了线程支持,ExecutorService对象就代表了线程池,如果开发者利用ExecutorService来启动线程,ExecutorService底层会负责管理线程池。此时,开发者只要把Runnable对象传给ExecutorService即可。如下代码:

ExecutorService pool = Executors.newFixedThreadPool(3)

pool.execute(new Runnable()

{

       public void run()

       {

              // 线程执行体

       }

});

如果执行通过Callable方式实现的线程,则可按如下代码:

ExecutorService pool = Executors.newFixedThreadPool(3)

pool.execute(new FutureTask<Object >(new Callable<Object>()

{

       public Object call() throws Exception

       {

              //线程执行体

       }

}));

85、启动一个线程是用run()还是start()?

启动一个线程是调用start()方法,使线程进入就绪状态,以后可以被调度为运行状态。run()方法是线程的线程执行体——也就是线程将要完成的事情。

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

当一个线程进行一个对象的synchronized方法之后,其他线程完全有可能再次进入该对象的其他方法。

不过要分几种情况来看:

1、如果其他方法没有使用synchronized关键字修饰,则可以进入。

2、如果当前线程进入的synchronized方法是static方法,其他线程可以进入其他synchronized修饰的非静态方法;如果当前线程进入的synchronized方法是非static方法,其他线程可以进入其他synchronized修饰的静态方法。

3、如果两个方法都是静态方法、或者都是非静态方法,并且都使用了synchronized修饰,但只要在该方法内部调用了同步监视器的wait(),则其他线程依然可以进入其他使用synchronized方法修饰的方法。

4、如果两个方法都是静态方法、或者都是非静态方法,并且都使用了synchronized修饰,而且没有在该方法内部调用了同步监视器的wait(),则其他线程不能进入其他使用synchronized方法修饰的方法。

87、线程的基本概念、线程的基本状态以及状态之间的关系

多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。线程(Thread)也被称作轻量级进程(Lightweight Process),线程是进程的执行单元。就像进程在操作系统中的地位一样,线程在程序中是独立的、并发的执行流。当进程被初始化后,主线程就被创建了。对于绝大多数的应用程序来说,通常仅要求有一个主线程,但我们也可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每条线程也是互相独立的。

线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。因为多个线程共享父进程里的全部资源,因此编程更加方便;但必须更加小心,我们必须确保线程不会妨碍同一进程里的其他线程。

线程的执行需要经过如下状态:

新建

就绪

运行

阻塞

死亡

各状态的转换关系如下图所示:

88、简述synchronized和java.util.concurrent.locks.Lock的异同 ?

主要相同点:Lock能完成synchronized所实现的所有功能

主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。

Java代码查错部分

1.

abstract class Name {

   private String name;

   public abstract boolean isStupidName(String name) {}

}

答案: 错。abstract方法必须以分号结尾,且不带花括号。

2.

public class Something {

   void doSomething () {

       private String s = "";

       int l = s.length();

   }

}

有错吗?

答案: 错。局部变量前不能放置任何访问修饰符 (private,public,和protected)。final可以用来修饰局部变量

(final如同abstract和strictfp,都是非访问修饰符,strictfp只能修饰class和method而非variable)。

3.

abstract class Something {

   private abstract String doSomething ();

}

这好像没什么错吧?

答案: 错。abstract的methods不能以private修饰。abstract的methods就是让子类implement(实现)具体细节的,怎么可以用private把abstract method隐藏起来呢? (同理,abstract method前不能加final)。

4.

public class Something {

   public int addOne(final int x) {

       return ++x;

   }

}

答案: 错。int x被修饰成final,意味着x不能在addOne method中被修改。

5.

public class Something {

   public static void main(String[] args) {

       Other o = new Other();

       new Something().addOne(o);

   }

   public void addOne(final Other o) {

       o.i++;

   }

}

class Other {

   public int i;

}

和上面的很相似,都是关于final的问题,这有错吗?

答案: 正确。在addOne method中,参数o被修饰成final。如果在addOne method里我们修改了o的引用 (比如: o = new Other();),那么如同上例这题也是错的。但这里修改的是o的成员变量,(成员变量),而o的reference并没有改变,因此程序没有错误。

6.

class Something {

    int i;

    public void doSomething() {

        System.out.println("i = " + i);

    }

}

有什么错呢?

答案: 正确。输出的是"i = 0"。int i是定义实例变量。系统会对实例变量执行默认初始化,因此i的默认被赋值为0 。

7.

class Something {

    final int i;

    public void doSomething() {

        System.out.println("i = " + i);

    }

}

和上面一题只有一个地方不同,就是多了一个final。

答案: 错。使用final修饰的实例变量必须由程序员显式指定初始值,为final变量指定初始值有3个地方:

A.定义时指定初始值:

B.在初始化块中指定初始值。

C.在构造器中指定初始值。

8.

public class Something {

     public static void main(String[] args) {

        Something s = new Something();

        System.out.println("s.doSomething() returns " + doSomething());

    }

    public String doSomething() {

        return "Do something ...";

    }

}

答案: 错。静态成员不允许访问非静态成员。上面main方法是静态方法,而doSomething()是非静态方法,因此程序导致编译错误。

9.

此处,Something类的文件名叫OtherThing.java

class Something {

    private static void main(String[] something_to_do) {

        System.out.println("Do something ...");

    }

}

答案: 正确。由于Something类不是public类,因此Java源文件的主文件名可以是任意的。但public class的名字必须和文件名相同。

10.

interface A{

   int x = 0;

}

class B{

   int x =1;

}

class C extends B implements A {

   public void printX(){

      System.out.println(x);

   }

   public static void main(String[] args) {

      new C().printX ();

   }

}

答案:错误。在编译时会发生错误。因为C类既实现了A接口,也继承B,因此它将会从A接口、B类中分别继承到成员变量x,因此上面程序中System.out.println(x);代码所引用的x是不明确的。

对于父类的变量,可以用super.x来明确指定,而接口的属性默认隐含为 public static final.所以可以通过A.x来明确指定。

11.

interface Playable {

       void play();

}

interface Bounceable {

       void play();

}

interface Rollable extends Playable, Bounceable {

       Ball ball = new Ball("PingPang");

}

class Ball implements Rollable {

       private String name;

       public String getName() {

              return name;

       }

       public Ball(String name) {

              this.name = name;

       }

       public void play() {

              ball = new Ball("Football");

              System.out.println(ball.getName());

       }

}

答案: 错。"interface Rollable extends Playable, Bounceable"没有问题。interface可继承多个interfaces,所以这里没错。问题出在interface Rollable里的"Ball ball = new Ball("PingPang");"。在接口里声明的成员变量总是常量,也就是默认使用public static final修饰。也就是说"Ball ball = new Ball("PingPang");"实际上是"public static final Ball ball = new Ball("PingPang");"。在Ball类的Play()方法中,"ball = new Ball("Football");"尝试去改变了ball的引用,而这里的ball引用变量是在Rollable中定义的,因此它有final修饰,final修饰的引用变量是不能被重新赋值的,因此上面程序会导致编译错误。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多