基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

题目的基本顺序是:

  • 基本语法
  • 类相关的语法
  • 内部类的语法
  • 继承相关的语法
  • 异常的语法
  • 线程的语法
  • 集合的语法
  • io 的语法
  • 虚拟机方面的语法

因文章篇幅的问题,本文分(一)(二)两篇进行讲解,知识点很详细,可尽情享受,另外我这边也整理了一些知识点笔记及对应的面试题,有须要的能够刷到文末获取领取方式!java

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

能够有多个类,但只能有一个public的类,而且public的类名必须与文件名相一致。程序员

二、Java有没有goto?

java中的保留字,如今没有在java中使用。web

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

&和&&均可以用做逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,不然,只要有一方为false,则结果为false。面试

&&还具备短路的功能,即若是第一个表达式为false,则再也不计算第二个表达式,例如,对于if(str != null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,因此不会出现NullPointerException若是将&&改成&,则会抛出NullPointerException异常。If(x==33 & ++y>0) y会增加,If(x==33 && ++y>0)不会增加正则表达式

&还能够用做位运算符,当&操做符两边的表达式不是boolean类型时,&表示按位与操做,咱们一般使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。 算法

备注:这道题先说二者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来代表本身理解透彻深刻、实际经验丰富。 编程

四、在JAVA中如何跳出当前的多重嵌套循环?

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

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

五、switch语句可否做用在byte上,可否做用在long上,可否做用在String上?

在switch(expr1)中,expr1只能是一个整数表达式或者枚举常量(更大字体),整数表达式能够是int基本类型或Integer包装类型,因为,byte,short,char均可以隐含转换为int,因此,这些类型以及这些类型的包装类型也是能够的。显然,long和String类型都不符合switch的语法规定,而且不能被隐式转换成int类型,因此,它们不能做用于swtich语句中。 api

六、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编译器会对它进行特殊处理,所以能够正确编译。

七、char型变量中能不能存贮一个中文汉字?为何?

char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,因此,char型变量中固然能够存储汉字啦。不过,若是某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。补充说明:unicode编码占用两个字节,因此,char类型的变量也是占用两个字节。

备注:后面一部分回答虽然不是在正面回答题目,可是,为了展示本身的学识和表现本身对问题理解的透彻深刻,能够回答一些相关的知识,作到知无不言,言无不尽。

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

2 << 3,

由于将一个数左移n位,就至关于乘以了2的n次方,那么,一个数乘以8只要将其左移3位便可,而位运算cpu直接支持的,效率最高,因此,2乘以8等於几的最效率的方法是2 << 3。

九、请设计一个一百亿的计算器

首先要明白这道题目的考查点是什么,一是你们首先要对计算机原理的底层细节要清楚、要知道加减法的位运算原理和知道计算机中的算术运算会发生越界的状况,二是要具有必定的面向对象的设计思想。

首先,计算机中用固定数量的几个字节来存储的数值,因此计算机中可以表示的数值是有必定的范围的,为了便于讲解和理解,咱们先以byte 类型的整数为例,它用1个字节进行存储,表示的最大数值范围为-128到+127。-1在内存中对应的二进制数据为11111111,若是两个-1相加,不考虑Java运算时的类型提高,运算后会产生进位,二进制结果为1,11111110,因为进位后超过了byte类型的存储空间,因此进位部分被舍弃,即最终的结果为11111110,也就是-2,这正好利用溢位的方式实现了负数的运算。-128在内存中对应的二进制数据为10000000,若是两个-128相加,不考虑Java运算时的类型提高,运算后会产生进位,二进制结果为1,00000000,因为进位后超过了byte类型的存储空间,因此进位部分被舍弃,即最终的结果为00000000,也就是0,这样的结果显然不是咱们指望的,这说明计算机中的算术运算是会发生越界状况的,两个数值的运算结果不能超过计算机中的该类型的数值范围。因为Java中涉及表达式运算时的类型自动提高,咱们没法用byte类型来作演示这种问题和现象的实验,你们能够用下面一个使用整数作实验的例子程序体验一下:

int a = Integer.MAX_VALUE;
        int b = Integer.MAX_VALUE;
        int sum = a + b;
        System.out.println(“a=”+a+”,b=”+b+”,sum=”+sum);

先不考虑long类型,因为int的正数范围为2的31次方,表示的最大数值约等于210001000*1000,也就是20亿的大小,因此,要实现一个一百亿的计算器,咱们得本身设计一个类能够用于表示很大的整数,而且提供了与另一个整数进行加减乘除的功能,大概功能以下:
()这个类内部有两个成员变量,一个表示符号,另外一个用字节数组表示数值的二进制数
()有一个构造方法,把一个包含有多位数值的字符串转换到内部的符号和字节数组中
()提供加减乘除的功能

public class BigInteger{
        int sign;
        byte[] val;
        public Biginteger(String val)   {
            sign = ;
            val = ;
        }
        public BigInteger add(BigInteger other) {

        }
        public BigInteger subtract(BigInteger other)    {

        }
        public BigInteger multiply(BigInteger other){

        }
        public BigInteger divide(BigInteger other){

        }

}

备注:要想写出这个类的完整代码,是很是复杂的,若是有兴趣的话,能够参看jdk中自带的java.math.BigInteger类的源码。面试的人也知道谁都不可能在短期内写出这个类的完整代码的,他要的是你是否有这方面的概念和意识,他最重要的仍是考查你的能力,因此,你不要由于本身没法写出完整的最终结果就放弃答这道题,你要作的就是你比别人写得多,证实你比别人强,你有这方面的思想意识就能够了,毕竟别人可能连题目的意思都看不懂,什么都没写,你要勇于答这道题,即便只答了一部分,那也与那些什么都不懂的人区别出来,拉开了距离,算是矮子中的高个,机会固然就属于你了。另外,答案中的框架代码也很重要,体现了一些面向对象设计的功底,特别是其中的方法命名很专业,用的英文单词很精准,这也是能力、经验、专业性、英语水平等多个方面的体现,会给人留下很好的印象,在编程能力和其余方面条件差很少的状况下,英语好除了能够使你得到更多机会外,薪水能够高出一千元。

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

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

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

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

a=new StringBuffer("");

可是,执行以下语句则能够经过编译:

a.append(" broken!"); 

有人在定义方法的参数时,可能想采用以下形式来阻止方法内部修改传进来的参数对象:

public void method(final  StringBuffer  param){
    }

实际上,这是办不到的,在该方法内部仍然能够增长以下代码来修改参数对象:

param.append("a");

十一、"=="和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。

在实际开发中,咱们常常要比较传递进行来的字符串内容是否等,例如,String input = …;input.equals(“quit”),许多人稍不注意就使用==进行比较了,这是错误的,随便从网上找几个项目实战的教学视频看看,里面就有大量这样的错误。记住,字符串的比较基本上都是使用equals方法。

若是一个类没有本身定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码以下:

boolean equals(Object o){
return this==o;
}

这说明,若是一个类没有本身定义equals方法,它默认的equals方法(从Object 类继承的)就是使用==操做符,也是在比较两个变量指向的对象是不是同一对象,这时候使用equals和使用==会获得一样的结果,若是比较的是两个独立的对象则总返回false。若是你编写的类但愿可以比较该类建立的两个实例对象的内容是否相同,那么你必须覆盖equals方法,由你本身写代码来决定在什么状况便可认为两个对象的内容是相同的。

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

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

在程序运行时的区别:实例变量属于某个对象的属性,必须建立了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,因此也称为类变量,只要程序加载了类的字节码,不用建立任何实例对象,静态变量就会被分配空间,静态变量就能够被使用了。总之,实例变量必须建立对象后才能够经过这个对象来使用,静态变量则能够直接使用类名来引用。

例如,对于下面的程序,不管建立多少个实例对象,永远都只分配了一个staticVar变量,而且每建立一个实例对象,这个staticVar就会加1;可是,每建立一个实例对象,就会分配一个instanceVar,便可能分配多个instanceVar,而且每一个instanceVar的值都只自加了1次。

public class VariantTest{
        public static int staticVar = 0; 
        public int instanceVar = 0; 
        public VariantTest(){
            staticVar++;
            instanceVar++;
            System.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar);
        }
}

备注:这个解答除了说清楚二者的区别外,最后还用一个具体的应用例子来讲明二者的差别,体现了本身有很好的解说问题和设计案例的能力,思惟敏捷,超过通常程序员,有写做能力!

1三、是否能够从一个static方法内部发出对非static方法的调用?

不能够。由于非static方法是要与对象关联在一块儿的,必须建立一个对象后,才能够在该对象上进行方法调用,而static方法调用时不须要建立对象,能够直接调用。也就是说,当一个static方法被调用时,可能尚未建立任何实例对象,若是从一个static方法中发出对非static方法的调用,那个非static方法是关联到哪一个对象上的呢?这个逻辑没法成立,因此,一个static方法内部发出对非static方法的调用。

1四、Integer与int的区别

int是java提供的8种原始数据类型之一。Java为每一个原始类型提供了封装类,Integer是java为int提供的封装类。int的默认值为0,而Integer的默认值为null,即Integer能够区分出未赋值和值为0的区别,int则没法表达出未赋值的状况,例如,要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer。在JSP开发中,Integer的默认为null,因此用el表达式在文本框中显示时,值为空白字符串,而int默认的默认值为0,因此用el表达式在文本框中显示时,结果为0,因此,int不适合做为web层的表单数据的类型。

在Hibernate中,若是将OID定义为Integer类型,那么Hibernate就能够根据其值是否为null而判断一个对象是不是临时的,若是将OID定义为了int类型,还须要在hbm映射文件中设置其unsaved-value属性为0。

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

1五、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.round(x+0.5),即将原来的数字加上0.5后再向下取整,因此,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。

1六、下面的代码有什么不妥之处?

基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

1七、请说出做用域public,private,protected,以及不写时的区别

这四个做用域的可见范围以下表所示。
说明:若是在修饰的元素上面没有写任何访问修饰符,则表示friendly。

基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

备注:只要记住了有4种访问权限,4个访问范围,而后将全选和范围在水平和垂直方向上分别按排从小到大或从大到小的顺序排列,就很容易画出上面的图了。

1八、Overload和Override的区别。Overloaded的方法是否能够改变返回值的类型?

Overload是重载的意思,Override是覆盖的意思,也就是重写。

重载Overload表示同一个类中能够有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不一样)。

重写Override表示子类中的方法能够与父类中的某个方法的名称和参数彻底相同,经过子类建立的实例对象调用这个方法时,将调用子类中的定义方法,这至关于把父类中定义的那个彻底相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,由于子类能够解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。若是父类的方法是private类型,那么,子类则不存在覆盖的限制,至关于子类中增长了一个全新的方法。

至于Overloaded的方法是否能够改变返回值的类型这个问题,要看你倒底想问什么呢?这个题目很模糊。若是几个Overloaded的方法的参数列表不同,它们的返回者类型固然也能够不同。但我估计你想问的问题是:若是两个方法的参数列表彻底同样,是否可让它们的返回值不一样来实现重载Overload。这是不行的,咱们能够用反证法来讲明这个问题,由于咱们有时候调用一个方法时也能够不定义返回结果变量,即不要关心其返回结果,例如,咱们调用map.remove(key)方法时,虽然remove方法有返回值,可是咱们一般都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表彻底相同的方法,仅仅是返回类型不一样,java就没法肯定编程者倒底是想调用哪一个方法了,由于它没法经过返回结果类型来判断。

override能够翻译为覆盖,从字面就能够知道,它是覆盖了一个方法而且对其重写,以求达到不一样的做用。对咱们来讲最熟悉的覆盖就是对接口方法的实现,在接口中通常只是对方法进行了声明,而咱们在实现时,就须要实现接口声明的全部方法。除了这个典型的用法之外,咱们在继承中也可能会在子类覆盖父类中的方法。在覆盖要注意如下的几点:

  1. 覆盖的方法的标志必需要和被覆盖的方法的标志彻底匹配,才能达到覆盖的效果;
  2. 覆盖的方法的返回值必须和被覆盖的方法的返回一致;
  3. 覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
  4. 被覆盖的方法不能为private,不然在其子类中只是新定义了一个方法,并无对其进行覆盖。

overload对咱们来讲可能比较熟悉,能够翻译为重载,它是指咱们能够定义一些名称相同的方法,经过定义不一样的输入参数来区分这些方法,而后再调用时,VM就会根据不一样的参数样式,来选择合适的方法执行。在使用重载要注意如下的几点:

  1. 在使用重载时只能经过不一样的参数样式。例如,不一样的参数类型,不一样的参数个数,不一样的参数顺序(固然,同一方法内的几个参数类型必须不同,例如能够是fun(int,float),可是不能为fun(int,int));
  2. 不能经过访问权限、返回类型、抛出的异常进行重载;
  3. 方法的异常类型和数目不会对重载形成影响;
  4. 对于继承来讲,若是某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,若是定义的话,也只是定义了一个新方法,而不会达到重载的效果。

1九、构造器Constructor是否可被override?

构造器Constructor不能被继承,所以不能重写Override,但能够被重载Overload。

基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

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

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

备注:只要明白了接口和抽象类的本质和做用,这些问题都很好回答,你想一想,若是你是java语言的设计者,你是否会提供这样的支持,若是不提供的话,有什么理由吗?若是你没有道理不提供,那答案就是确定的了。

只有记住抽象类与普通类的惟一区别就是不能建立实例对象和容许有abstract方法。

2一、写clone()方法时,一般都有一行代码,是什么?

clone 有缺省行为,super.clone();由于首先要把父类中的成员复制到位,而后才是复制本身的成员。

2二、面向对象的特征有哪些方面

计算机软件系统是现实生活中的业务在计算机中的映射,而现实生活中的业务其实就是一个个对象协做的过程。面向对象编程就是按现实业务同样的方式将程序代码按一个个对象进行组织和编写,让计算机系统可以识别和理解用对象方式组织和编写的程序代码,这样就能够把现实生活中的业务对象映射到计算机系统中。
面向对象的编程语言有封装、继承 、抽象、多态等4个主要的特征。

封装:
封装是保证软件部件具备优良的模块性的基础,封装的目标就是要实现软件部件的“高内聚、低耦合”,防止程序相互依赖性而带来的变更影响。在面向对象的编程语言中,对象是封装的最基本单位,面向对象的封装比传统语言的封装更为清晰、更为有力。面向对象的封装就是把描述一个对象的属性和行为的代码封装在一个“模块”中,也就是一个类中,属性用变量定义,行为用方法进行定义,方法能够直接访问同一个对象中的属性。一般状况下,只要记住让变量和访问这个变量的方法放在一块儿,将一个类中的成员变量所有定义成私有的,只有这个类本身的方法才能够访问到这些成员变量,这就基本上实现对象的封装,就很容易找出要分配到这个类上的方法了,就基本上算是会面向对象的编程了。把握一个原则:把对同一事物进行操做的方法和相关的方法放在同一个类中,把方法和它操做的数据放在同一个类中

例如,人要在黑板上画圆,这一共涉及三个对象:人、黑板、圆,画圆的方法要分配给哪一个对象呢?因为画圆须要使用到圆心和半径,圆心和半径显然是圆的属性,若是将它们在类中定义成了私有的成员变量,那么,画圆的方法必须分配给圆,它才能访问到圆心和半径这两个属性,人之后只是调用圆的画圆方法、表示给圆发给消息而已,画圆这个方法不该该分配在人这个对象上,这就是面向对象的封装性,即将对象封装成一个高度自治和相对封闭的个体,对象状态(属性)由这个对象本身的行为(方法)来读取和改变。一个更便于理解的例子就是,司机将火车刹住了,刹车的动做是分配给司机,仍是分配给火车,显然,应该分配给火车,由于司机自身是不可能有那么大的力气将一个火车给停下来的,只有火车本身才能完成这一动做,火车须要调用内部的离合器和刹车片等多个器件协做才能完成刹车这个动做,司机刹车的过程只是给火车发了一个消息,通知火车要执行刹车动做而已。

抽象:
抽象就是找出一些事物的类似和共性之处,而后将这些事物归为一个类,这个类只考虑这些事物的类似和共性之处,而且会忽略与当前主题和目标无关的那些方面,将注意力集中在与当前目标有关的方面。例如,看到一只蚂蚁和大象,你可以想象出它们的相同之处,那就是抽象。抽象包括行为抽象和状态抽象两个方面。例如,定义一个Person类,以下:

class Person{
        String name;
        int age;
}

人原本是很复杂的事物,有不少方面,但由于当前系统只须要了解人的姓名和年龄,因此上面定义的类中只包含姓名和年龄这两个属性,这就是一种抽像,使用抽象能够避免考虑一些与目标无关的细节。我对抽象的理解就是不要用显微镜去看一个事物的全部方面,这样涉及的内容就太多了,而是要善于划分问题的边界,当前系统须要什么,就只考虑什么。

继承:
在定义和实现一个类的时候,能够在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容做为本身的内容,并能够加入若干新的内容,或修改原来的方法使之更适合特殊的须要,这就是继承。继承是子类自动共享父类数据和方法的机制,这是类之间的一种关系,提升了软件的可重用性和可扩展性。

多态:
多态是指程序中定义的引用变量所指向的具体类型和经过该引用变量发出的方法调用在编程时并不肯定,而是在程序运行期间才肯定,即一个引用变量倒底会指向哪一个类的实例对象,该引用变量发出的方法调用究竟是哪一个类中实现的方法,必须在由程序运行期间才能决定。由于在程序运行时才肯定具体的类,这样,不用修改源程序代码,就可让引用变量绑定到各类不一样的类实现上,从而致使该引用调用的具体方法随之改变,即不修改程序代码就能够改变程序运行时所绑定的具体代码,让程序能够选择多个运行状态,这就是多态性。多态性加强了软件的灵活性和扩展性。例如,下面代码中的UserDao是一个接口,它定义引用变量userDao指向的实例对象由daofactory.getDao()在执行的时候返回,有时候指向的是UserJdbcDao这个实现,有时候指向的是UserHibernateDao这个实现,这样,不用修改源代码,就能够改变userDao指向的具体类实现,从而致使userDao.insertUser()方法调用的具体代码也随之改变,即有时候调用的是UserJdbcDao的insertUser方法,有时候调用的是UserHibernateDao的insertUser方法:
UserDao userDao = daofactory.getDao(); userDao.insertUser(user);

比喻:人吃饭,你看到的是左手,仍是右手?

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

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

2四、abstract class和interface有什么区别?

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

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

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

  1. 抽象类能够有构造方法,接口中不能有构造方法。
  2. 抽象类中能够有普通成员变量,接口中没有普通成员变量
  3. 抽象类中能够包含非抽象的普通方法,接口中的全部方法必须都是抽象的,不能有非抽象的普通方法。
  4. 抽象类中的抽象方法的访问类型能够是public,protected和(默认类型,虽然eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,而且默认即为public abstract类型。
  5. 抽象类中能够包含静态方法,接口中不能包含静态方法
  6. 抽象类和接口中均可以包含静态成员变量,抽象类中的静态成员变量的访问类型能够任意,但接口中定义的变量只能是public static final类型,而且默认即为public static final类型。
  7. 一个类能够实现多个接口,但只能继承一个抽象类。

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

接口更多的是在系统架构设计方法发挥做用,主要用于定义模块之间的通讯契约。而抽象类在代码实现方面发挥做用,能够实现代码的重用,例如,模板方法设计模式是抽象类的一个典型应用,假设某个项目的全部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只处理的具体业务逻辑代码
        } 

}

父类方法中间的某段代码不肯定,留给子类干,就用模板方法设计模式。

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

2五、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是什么。

2六、什么是内部类?Static Nested Class 和 Inner Class的不一样。

内部类就是在一个类的内部定义的类,内部类中不能定义静态成员(静态成员不是对象的特性,只是为了找一个容身之处,因此须要放到一个类中而已,这么一点小事,你还要把它放到类内部的一个类中,过度了啊!提供内部类,不是为让你干这种事情,无聊,不让你干。我想多是既然静态成员相似c语言的全局变量,而内部类一般是用于建立内部对象用的,因此,把“全局变量”放在内部类中就是毫无心义的事情,既然是毫无心义的事情,就应该被禁止),内部类能够直接访问外部类中的成员变量,内部类能够定义在外部类的方法外面,也能够定义在外部类的方法体中,以下所示:

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,protecte,默认的,private等4种类型,这就好像类中定义的成员变量有4种访问类型同样,它们决定这个内部类的定义对其余类是否可见;对于这种状况,咱们也能够在外面建立内部类的实例对象,建立内部类的实例对象时,必定要先建立外部类的实例对象,而后用这个外部类的实例对象去建立内部类的实例对象,代码以下:

Outer outer = new Outer();
Outer.Inner1 inner1 = outer.new Innner1();

在方法内部定义的内部类前面不能有访问类型修饰符,就好像方法中定义的局部变量同样,但这种内部类的前面能够使用final或abstract修饰符。这种内部类对其余类是不可见的其余类没法引用这种内部类,可是这种内部类建立的实例对象能够传递给其余类访问。这种内部类必须是先定义,后使用,即内部类的定义代码必须出如今使用该类以前,这与方法中的局部变量必须先定义后使用的道理也是同样的。这种内部类能够访问方法体中的局部变量,可是,该局部变量前必须加final修饰符。

对于这些细节,只要在eclipse写代码试试,根据开发工具提示的各种错误信息就能够立刻了解到。

在方法体内部还能够采用以下语法来建立一种匿名内部类,即定义某一接口或类的子类的同时,还建立了该子类的实例对象,无需为该子类定义名称:

public class Outer
{
        public void start()
        {
            new Thread(
new Runable(){
                    public void run(){};
}
).start();
        }
}

最后,在方法外部定义的内部类前面能够加上static关键字,从而成为Static Nested Class,它再也不具备内部类的特性,全部,从狭义上讲,它不是内部类。Static Nested Class与普通类在运行时的行为和功能上没有什么区别,只是在编程引用时的语法上有一些差异,它能够定义成public、protected、默认的、private等多种类型,而普通类只能定义成public和默认的这两种类型。在外面引用Static Nested Class类的名称为“外部类名.内部类名”。在外面不须要建立外部类的实例对象,就能够直接建立Static Nested Class,例如,假设Inner是定义在Outer类中的Static Nested Class,那么能够使用以下语句建立Inner类:

Outer.Inner inner = new Outer.Inner();

因为static Nested Class不依赖于外部类的实例对象,因此,static Nested Class能访问外部类的非static成员变量。当在外部类中访问Static Nested Class时,能够直接使用Static Nested Class的名字,而不须要加上外部类的名字了,在Static Nested Class中也能够直接引用外部类的static的成员变量,不须要加上外部类的名字。

在静态方法中定义的内部类也是Static Nested Class,这时候不能在类前面加static关键字,静态方法中的Static Nested Class与普通方法中的内部类的应用方式很类似,它除了能够直接访问外部类中的static的成员变量,还能够访问静态方法中的局部变量,可是,该局部变量前必须加final修饰符。

备注:首先根据你的印象说出你对内部类的整体方面的特色:例如,在两个地方能够定义,能够访问外部类的成员变量,不能定义静态成员,这是大的特色。而后再说一些细节方面的知识,例如,几种定义方式的语法区别,静态内部类,以及匿名内部类。

2七、内部类能够引用它的包含类的成员吗?有没有什么限制?

彻底能够。若是不是静态内部类,那没有什么限制!
若是你把静态嵌套类看成内部类的一种特例,那在这种状况下不能够访问外部类的普通成员变量,而只能访问外部类中的静态成员,例如,下面的代码:

class Outer
{
    static int x;
    static class Inner
    {
        void test()
        {
            syso(x);
        }
    }
}

答题时,也要能察言观色,揣摩提问者的心思,显然人家但愿你说的是静态内部类不能访问外部类的成员,但你一上来就顶牛,这很差,要先顺着人家,让人家满意,而后再说特殊状况,让人家吃惊。

2八、Anonymous Inner Class (匿名内部类) 是否能够extends(继承)其它类,是否能够implements(实现)interface(接口)?

能够继承其余类或实现其余接口。不只是能够,而是必须!

2九、String是最基本的数据类型吗?

基本数据类型包括byte、int、char、long、float、double、boolean和short。

java.lang.String类是final类型的,所以不能够继承这个类、不能修改这个类。为了提升效率节省空间,咱们应该用StringBuffer类

30、Java 中怎么获取一份线程 dump 文件?

在 Linux 下,你能够经过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java应用的 dump 文件。在 Windows 下,你能够按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置。若是你使用 Tomcat。

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

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

经过上面的说明,咱们很容易导出另外一个结论,若是常常对字符串进行各类各样的修改,或者说,不可预见的修改,那么使用String来表明字符串的话会引发很大的内存开销。由于 String对象创建以后不能再改变,因此对于每个不一样的字符串,都须要一个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。

3二、是否能够继承String类?

String类是final类故不能够继承。

3三、String s = new String("xyz");建立了几个String Object? 两者之间有什么区别?

两个或一个,”xyz”对应一个对象,这个对象放在字符串常量缓冲区,常量”xyz”无论出现多少遍,都是缓冲区中的那一个。New String每写一遍,就建立一个新的对象,它一句那个常量”xyz”对象的内容来建立出一个新String对象。若是之前就用过’xyz’,这句表明就不会建立”xyz”本身了,直接从缓冲区拿。

3四、String 和StringBuffer的区别

JAVA平台提供了两个类: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;
}

在讲二者区别时,应把循环的次数搞成10000,而后用endTime-beginTime来比较二者执行的时间差别,最后还要讲讲StringBuilder与StringBuffer的区别。

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

基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

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

若是不查jdk api,我很难写出来!我能够说说个人思路:

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

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

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

3七、下面这条语句一共建立了多少个对象: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,这说明javac编译能够对字符串常量直接相加的表达式进行优化,没必要要等到运行期去进行加法运算处理,而是在编译时去掉其中的加号,直接将其编译成一个这些常量相连的结果。

题目中的第一行代码被编译器在编译时优化后,至关于直接定义了一个”abcd”的字符串,因此,上面的代码应该只建立了一个String对象。写以下两行代码,

String s = "a" + "b" + "c" + "d";
        System.out.println(s == "abcd");

最终打印的结果应该为true。

3八、try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,何时被执行,在return前仍是后?

也许你的答案是在return以前,但往更细地说,个人答案是在return中间执行,请看下面程序代码的运行结果:

public  class Test {

    /**
     * @param args add by zxx ,Dec 9, 2008
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(new Test().test());;
    }

    static int test()
    {
        int x = 1;
        try
        {
            return x;
        }
        finally
        {
            ++x;
        }
    }

}

---------执行结果 ---------
1

运行结果是1,为何呢?主函数调用子函数并获得结果的过程,比如主函数准备一个空罐子,当子函数要返回结果时,先把结果放在罐子里,而后再将程序逻辑返回到主函数。所谓返回,就是子函数说,我不运行了,你主函数继续运行吧,这没什么结果可言,结果是在说这话以前放进罐子里的。

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

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。

我能够经过下面一个例子程序来帮助我解释这个答案,从下面例子的运行结果中能够发现,try中的return语句调用的函数先于finally中调用的函数执行,也就是说return语句先执行,finally语句后执行,因此,返回的结果是2。Return并非让函数立刻返回,而是return语句执行后,将把返回结果放置进函数栈中,此时函数并非立刻返回,它要执行finally语句后才真正开始返回。

在讲解答案时能够用下面的程序来帮助分析:

public  class Test {

    /**
     * @param args add by zxx ,Dec 9, 2008
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(new Test().test());;
    }

    int test()
    {
        try
        {
            return func1();
        }
        finally
        {
            return func2();
        }
    }

    int func1()
    {
        System.out.println("func1");
        return 1;
    }
    int func2()
    {
        System.out.println("func2");
        return 2;
    }   
}
-----------执行结果-----------------

func1
func2
2

结论:finally中的代码比return 和break语句后执行

40、final, finally, finalize的区别

final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
内部类要访问局部变量,局部变量必须定义成final类型,例如,一段代码……

finally是异常处理语句结构的一部分,表示老是执行。

finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,能够覆盖此方法提供垃圾收集时的其余资源回收,例如关闭文件等。JVM不保证此方法总被调用

4一、运行时异常与通常异常有何异同?

异常表示程序运行过程当中可能出现的非正常状态,运行时异常表示虚拟机的一般操做中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,可是并不要求必须声明抛出未被捕获的运行时异常。

因文章篇幅的问题,本文分(一)(二)两篇进行讲解,知识点很详细,可尽情享受,另外我这边也整理了一些知识点笔记及对应的面试题,有须要的能够进个人Java学习交流群:909666042免费获取

部分资料图分享

基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

更多笔记分享

基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

最后,用 Martin Fowler 的一句话做为结尾:「任何傻瓜都能写计算机能理解的代码,优秀的程序员编写人类可以理解的代码。」(Any fool can write code that a computer can understand. Good programmers write code that humans can understand)

相关文章
相关标签/搜索