Java面试手册:核心基础-2

2019-04-22 16:22:52 浏览数 (1)

1.写clone()方法时,通常都有一行代码,是什么

  • clone 有缺省行为,super.clone();因为首先要把父类中的成员复制到位,然后才是复制自己的成员。
  • 面向对象的特征有哪些方面
  • 计算机软件系统是现实生活中的业务在计算机中的映射,而现实生活中的业务其实就是一个个对象协作的过程。面向对象编程就是按现实业务一样的方式将程序代码按一个个对象进行组织和编写,让计算机系统能够识别和理解用对象方式组织和编写的程序代码,这样就可以把现实生活中的业务对象映射到计算机系统中。
  • ==封装==:将对象封装成独立的,高度自治的,相对封闭的模块,实现“高内聚,低耦合”,降低相互的依赖性,这个对象状态(属性)由这个对象自己的行为(方法)来读取和改变,将属性(变量)定位private,只有这个类自己的方法才可以访问到这些成员变量,一个原则是:让方法和它待在一起。
  • 举例1:人要在黑板上画圆,这一共涉及三个对象:人、黑板、圆,画圆的方法要分配给哪个对象呢?由于画圆需要使用到圆心和半径,圆心和半径显然是圆的属性,如果将它们在类中定义成了私有的成员变量,那么,画圆的方法必须分配给圆,它才能访问到圆心和半径这两个属性,人以后只是调用圆的画圆方法、表示给圆发给消息而已,画圆这个方法不应该分配在人这个对象上。
  • 举例2: 司机将火车刹住了,刹车的动作是分配给司机,还是分配给火车,显然,应该分配给火车,因为司机自身是不可能有那么大的力气将一个火车给停下来的,只有火车自己才能完成这一动作,火车需要调用内部的离合器和刹车片等多个器件协作才能完成刹车这个动作,司机刹车的过程只是给火车发了一个消息,通知火车要执行刹车动作而已。
  • 面向对象的封装性,即将对象封装成一个高度自治和相对封闭的个体,对象状态(属性)由这个对象自己的行为(方法)来读取和改变。
  • ==继承==:表示的是类与类的关系,是子类共享父类所有数据和方法的一种机制,可提高软件的可重用性和扩展性,缺点是加强了耦合性。
  • 构造方法和private修饰的方法不可以被继承。
  • 具体做法是:在创建一个类的时候,讲一个已经存在的类所定义的内容变成自己的(复制)内容,并加入一些新的内容或者修改原来的内容以适应新的特殊要求。
  • ==多态==:程序运行时的多种状态,即编程的时候我们并不知道程序的“引用变量的具体类型,以及该变量发出的方法调用”。只有在运行期间才能确定,因此我们就可以在不用修改源代码,就可以实现将不同的引用变量绑定到不同的类实现上,让其具体的方法也改变(即在不修改程序代码,就可以实现修改运行时,所绑定的具体代码)。
  • 多态性:增强了软件的灵活性和扩展性。
  • ==抽象==: 将事物的相似和共性之处,拿出来,然后将这些事物归为一个类,忽略与当前主题和目标无关的那些方面,将注意力集中在与当前目标有关的方面,抽象包括行为抽象和状态抽象两个方面。
  • 把握一个原则:当前系统需要什么就只考虑什么。

2.java中实现多态的机制是什么

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

3.abstract class和interface有什么区别?

这道题的思路是先从总体解释抽象类和接口的基本概念,然后再比较两者的语法细节,最后再说两者的应用区别。比较两者语法细节区别的条理是:==先从一个类中的构造方法、普通成员变量和方法(包括抽象方法),静态变量和方法,继承性等6个方面逐一去比较回答==,接着从第三者继承的角度的回答,特别是最后用了一个典型的例子来展现自己深厚的技术功底。

  • 抽象类:含有abstract修饰符的class即为抽象类,abstract类不能创建的实例对象。
    • 含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。
    • abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,==所以,不能有抽象构造方法或抽象静态方法==。
    • 如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。
  • 接口:(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。
代码语言:javascript复制
 - 接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。
  • ==语法区别==:
    • 抽象类可以有构造方法,接口中不能有构造方法。
    • 抽象类中可以有普通成员变量,接口中没有普通成员变量
    • 抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
    • 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然eclipse下不报错,但应该也不行),==但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型==。
    • 抽象类中可以包含静态方法,接口中不能包含静态方法
    • 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
    • 一个类可以实现多个接口,但只能继承一个抽象类。
  • ==应用上的区别==:
    • 接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约
    • 而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法设计模式是抽象类的一个典型应用,假设某个项目的所有Servlet类都要用相同的方式进行权限判断、记录访问日志和处理异常,那么就可以定义一个抽象的基类,让所有的Servlet都继承这个抽象基类,在抽象基类的service方法中完成权限判断、记录访问日志和处理异常的代码,在各个子类中只是完成各自的业务逻辑代码。 //父类方法中间的某段代码不确定,留给子类干,就用模板方法设计模式。 public abstract class BaseServlet extends HttpServlet { public final void service(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException { //记录访问日志进行权限判断 if(具有权限) { try { doService(request,response); }catch(Excetpion e) { //记录异常信息 } } } protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException; //注意访问权限定义成protected,显得既专业,又严谨,因为它是专门给子类用的 } public class MyServlet1 extends BaseServlet { protected void doService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException { //本Servlet只处理的具体业务逻辑代码 } }

4.abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

  • abstract的method 不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系!
  • native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用。
    • 例如,FileOutputSteam类要硬件打交道,底层的实现用的是操作系统相关的api实现,例如,在windows用c语言实现的,所以,查看jdk的源代码,可以发现FileOutputStream的open方法的定义如下:``` private native void open(String name) throws FileNotFoundException;```
  • 如果我们要用java调用别人写的c语言函数,我们是无法直接调用的,我们需要按照java的要求写一个c语言的函数,用我们的这个c语言函数去调用别人的c语言函数。由于我们的c语言函数是按java的要求来写的,我们这个c语言函数就可以与java对接上,java那边的对接方式就是定义出与我们这个c函数相对应的方法,java中对应的方法不需要写具体的代码,==但需要在前面声明native==。
  • 关于synchronized与abstract合用的问题,我觉得也不行,synchronized应该是作用在一个具体的方法上才有意义,而且,方法上的synchronized同步所使用的同步锁对象是this,而抽象方法上无法确定this是什么。

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

首先内部类的总体方面的特点:例如,在两个地方可以定义,可以访问外部类的成员变量,不能定义静态成员,这是大的特点。然后再说一些细节方面的知识,例如,几种定义方式的语法区别,静态内部类,以及匿名内部类。

  • 内部类就是在一个类的内部定义的类,内部类中不能定义静态成员(静态成员不是对象的特性,只是为了找一个容身之处,所以需要放到一个类中而已
  • 内部类可以直接访问外部类中的成员变量,内部类可以定义在外部类的方法外面,也可以定义在外部类的方法体中 public class Outer{ int out_x = 0; public void method(){ Inner1 inner1 = new Inner1(); public class Inner2 { //在方法体内部定义的内部类 public method() { out_x = 3; } } Inner2 inner2 = new Inner2(); } //在方法体外面定义的内部类 public class Inner1 { } } public class Outer { public void start() { new Thread( new Runable(){public void run(){}; } ).start(); } }
    • 在方法内部定义的内部类前面不能有访问类型修饰符,就好像方法中定义的局部变量一样,但这种内部类的前面可以使用final或abstract修饰符。==这种内部类对其他类是不可见的其他类无法引用这种内部类==,但是这种内部类创建的实例对象可以传递给其他类访问。这种内部类必须是先定义,后使用,即内部类的定义代码必须出现在使用该类之前,这与方法中的局部变量必须先定义后使用的道理也是一样的。这种内部类可以访问方法体中的局部变量,但是,该局部变量前必须加final修饰符。
    • 在方法体内部还可以采用如下语法来==创建一种匿名内部类==,即定义某一接口或类的子类的同时,还创建了该子类的实例对象,无需为该子类定义名称:
    • 在方法体外面定义的内部类的访问类型可以是public,protecte,默认的,private等==4种类型==,这就好像类中定义的成员变量有4种访问类型一样,==它们决定这个内部类的定义对其他类是否可见==。
    1. 我们可以在外部创建实例对象,但是创建内部类的实例对象前应该先创建外部类,然后用这个外部类的实例对象去创建内部类的实例对象Outer outer = new Outer(); Outer.Inner1 inner1 = outer.new Innner1();
    2. 最后,在方法外部定义的内部类前面可以加上static关键字,从而成为Static Nested Class,它不再具有内部类的特性,所以,从狭义上讲,它不是内部类。Static Nested Class与普通类在运行时的行为和功能上没有什么区别,只是在编程引用时的语法上有一些差别,它可以定义成public、protected、默认的、private等多种类型,而普通类只能定义成public和默认的这两种类型。在外面引用Static Nested Class类的名称为“外部类名.内部类名”。在外面不需要创建外部类的实例对象,就可以直接创建Static Nested Class.
    3. 由于static Nested Class不依赖于外部类的实例对象,所以,static Nested Class能访问外部类的非static成员变量。当在外部类中访问Static Nested Class时,可以直接使用Static Nested Class的名字,而不需要加上外部类的名字了,在Static Nested Class中也可以直接引用外部类的static的成员变量,不需要加上外部类的名字。
    4. 在静态方法中定义的内部类也是Static Nested Class,这时候不能在类前面加static关键字,静态方法中的Static Nested Class与普通方法中的内部类的应用方式很相似,它除了可以直接访问外部类中的static的成员变量,还可以访问静态方法中的局部变量,但是,该局部变量前必须加final修饰符。

6.内部类可以引用它的包含类的成员吗?有没有什么限制?

  • 一般而言是可以的。如果不是静态内部类,那没有什么限制!
  • 但是,如果把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员:比如下面的代码 class Outer{ static int x; static class Inner{ void test(){ syso(x); } } }

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

  • 可以继承其他类或实现其他接口。不仅是可以,而是必须!

8.super.getClass()方法调用

代码语言:javascript复制
   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, 在test方法中,直接调用getClass().getName()方法,返回的是Test类名,由于getClass()在Object类中定义成了final,子类不能覆盖该方法。 所以,在test方法中调用getClass().getName()方法,其实就是在调用从父类继承的getClass()方法,等效于调用super.getClass().getName()方法。 所以,super.getClass().getName()方法返回的也应该是Test,如果想得到父类的名称,应该用如下代码:getClass().getSuperClass().getName();

9.jdk中哪些类是不能继承的?

  • 不能继承的是类是那些用final关键字修饰的类。一般比较基本的类型或防止扩展类无意间破坏原来方法的实现的类型都应该是final的,在jdk中比如,System,String,StringBuffer等类型。

10.String是最基本的数据类型吗?

  • 不是,是属于引用类型。引用类型还有数组,日期等类型,java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类。
  • 基本数据类型包括byte、int、char、long、float、double、boolean和short。

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

  • 没有。因为String由final修饰,被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。
  • 在这段代码中,s原先指向一个String对象,内容是 "Hello",然后我们对s进行了 操作,然而s所指向的那个对象是没有改变的,这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
  • 由此我们知道,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。
  • StringBuffer类,他允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,string和StringBuffer的对象转换十分容易,刚好解决了上面的不能修改的问题
  • 最后我们可以知道,如果要使用内容相同的字符串,不必每次都new一个String。比如下面的例子,在构造器中对一个叫S的string引用变量进行初始化,将其设置为初始值:应该如下这样做 public class Demo { private String s; ... public Demo { s = "Initial Value"; } ... }
  • 而不是==s = new String("Initial Value")==,因为这样每次都会掉用新的构造器,生成新的对象,性能低下的同时,内存开销大 没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。 也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
  • 这些结论都基于一个事实,那就是:对于字符串常量而言,如果内容相同,那么java就认为他们代表同一个String对象,而用关键字调用new调用构造器,总是会创建一个新的对象,无论内容是否相同。
  • 至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。==不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题==。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即 StringBuffer。

12.是否可以继承String类?

  • 不能,String类是final类,故不可以继承。 String支持的方法
  • 检查序列的单个字符串,
  • 比较字符串
  • 搜索字符串
  • 提取字符串
  • 创建字符串结果
  • 将字符串大小写进行转换
  • 字符串转换时通过tostring()方式转换的(toString是object对象定义的,我们需要时需要重写这个方法达到我们的要求)
  • string类的split()方法可以匹配正则表达式拆分字符串,故而可以将字符串拆分成单个字符串的形式。
  • 以及字符串串串联符号,“ ”,通过StringBuffer(StringBuider)和append()方法实现字符串可变,然后再转换成String类,因为StringBuffer对象如果存储到java集合中时会出问题.

13.String s = new String("xyz");创建了几个String Object? 二者之间有什么区别?

  • 两个或者一个对象。
  • ”xyz”对应一个对象,这个对象放在==字符串常量缓冲区==,常量”xyz”不管出现多少遍,都是缓冲区中的那一个。
  • New String每写一遍,就创建一个新的对象,它依据那个常量”xyz”对象的内容来创建出一个新String对象,如果以前就用过’xyz’,就不会创建”xyz”自己了,直接从缓冲区拿。

14.String 和StringBuffer的区别

  • JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。
  • String覆盖了equals方法和hashCode方法,而StringBuffer没有覆盖equals方法和hashCode方法,所以,将StringBuffer对象存储进Java集合类中时会出现问题。
  • String类: 表示内容不可改变的字符串,string重写了equals()方法,new String(“abc”).equals(new String(“abc”)的结果为true,
  • StringBuffer类: 表示内容可以被修改的字符串,因此当你知道字符数据要改变的时候你就可以使用==StringBuffer==(比如:你可以使用StringBuffer来动态构造字符数据),StringBuffer并没有实现equals()方法,故new StringBuffer(“abc”).equals(new StringBuffer(“abc”)的结果为false。 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; } 在讲两者区别时,应把循环的次数搞成10000,然后用endTime-beginTime来比较两者执行的时间差异,最后还要讲讲StringBuilder与StringBuffer的区别。

15.StringBuffer与StringBuilder的区别

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

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

  • 用正则表达式,代码大概为:==String [] result = orgStr.split(“,”);==
  • 用 StringTokenizer 代码为(高逼格): StringTokenizer tokener = StringTokenizer(orgStr,”,”); String [] result = new String[tokener .countTokens()]; int i=0; while(tokener.hasNext(){ result[i ]=toker.nextToken(); }

0 人点赞