BAT面试题集锦——Java基础
Posted 风雨田
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BAT面试题集锦——Java基础相关的知识,希望对你有一定的参考价值。
一、java面试题
熟练掌握java是很关键的,大公司不仅仅要求你会使用几个api,更多的是要你熟悉源码实现原理,甚至要你知道有哪些不足,怎么改进,还有一些java有关的一些算法,设计模式等等。
(一) java基础面试知识点
java中==和equals和hashCode的区别
- ==是运算符,用于比较两个变量是否相等。一般用于基本类型的比较
- equals,是Objec类的方法,用于比较两个对象是否相等,默认Object类的equals方法是比较两个对象的地址,跟==的结果一样。
public boolean equals(Object obj)
return (this == obj);
- hashCode也是Object类的一个方法返回一个离散的int型整数。在集合类操作中使用,为了提高查询速度。(HashMap,HashSet等)
将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等
,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与
集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。
回过来说get的时候,HashMap也先调key.hashCode()算出数组下标,然后看equals如果是true就是找到了,
所以就涉及了equals。
一个很常见的错误根源在于没有覆盖hashCode方法。在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。
如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,
这样的集合包括HashMap、HashSet和Hashtable。
java中int、char、long各占多少字节数
- 字节 Byte 1个字节
- 整型 int 4字节
- 长整型 long 4字节
- 字符型 char 2字节
- 单精度 float 4字节
- 双精度 double 8字节
- 长双精度
long double
8字节
int与integer的区别
- Integer是int的包装类,int则是java的一种基本数据类型
- Integer变量必须实例化后才能使用,而int变量不需要
- Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
- Integer的默认值是null,int的默认值是0
Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false
上面:因为是对象地址比较
Integer i = new Integer(100);
int j = 100;
System.out.print(i == j); //true
为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较
Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false
非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同
Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true
Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false
java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);
public static Integer valueOf(int i)
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了
探探对java多态的理解
- 面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。
- 多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
- 实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
- 多态的作用:消除类型之间的耦合关系。
- 现实中,关于多态的例子不胜枚举。比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。
多态的好处:
- 可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
- 可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
- 接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3 所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。
- 灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
- 简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
String、StringBuffer、StringBuilder区别
首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String
String最慢的原因:
String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。
1 String str="abc";
2 System.out.println(str);
3 str=str+"de";
4 System.out.println(str);
首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对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就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。
在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的
总结一下
- String:适用于少量的字符串操作的情况
- StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
- StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
什么是内部类?内部类的作用
可以将一个类的定义放在另一个类的定义的内部,这就是内部类。
内部类的作用:
- 内部类可以很好的实现隐藏
- 内部类拥有外围类的所有元素的访问权限
- 可以实现多重继承(不要误解,这个是在一个外部类里面多个内部类继承不同基类达到一个外部类拥有多个基类的方法)
- 可以避免接口中的方法和同一个类中的方法同名的问题
给第三点举例:
public class Example1
public String name()
return "liutao";
public class Example2
public int age()
return 25;
public class MainExample
private class test1 extends Example1
public String name()
return super.name();
private class test2 extends Example2
public int age()
return super.age();
public String name()
return new test1().name();
public int age()
return new test2().age();
public static void main(String args[])
MainExample mi=new MainExample();
System.out.println("姓名:"+mi.name());
System.out.println("年龄:"+mi.age());
抽象类和接口区别
- 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
- 抽象类要被子类继承,接口要被类实现。
- 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
- 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
- 抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
- 抽象类里可以没有抽象方法
- 如果一个类里有抽象方法,那么这个类只能是抽象类
- 抽象方法要被实现,所以不能是静态的,也不能是私有的。
- 接口可继承接口,并可多继承接口,但类只能单根继承。
抽象类的意义
java中抽象类更利于代码的维护和重用。
抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。具体分析如下:
- 因为抽象类不能实例化对象,所以必须要有子类来实现它之后才能使用。这样就可以把一些具有相同属性和方法的组件进行抽象,这样更有利于代码和程序的维护。
比如本科和研究生可以抽象成学生,他们有相同的属性和方法。这样当你对其中某个类进行修改时会受到父类的限制,这样就会提醒开发人员有些东西不能进行随意修改,这样可以对比较重要的东西进行统一的限制,也算是一种保护,对维护会有很大的帮助。 - 当又有一个具有相似的组件产生时,只需要实现该抽象类就可以获得该抽象类的那些属性和方法。
比如学校又新产生了专科生这类学生,那么专科生直接继承学生,然后对自己特有的属性和方法进行补充即可。这样对于代码的重用也是很好的体现。
所以,Java中抽象类对于代码的维护和重用有很好的帮助,也是Java面向对象的一个重要体现。
抽象类是否可以没有方法和属性?
java中可以,但是实际意义。
接口的意义
-
重要性:在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的 面向对象能力。
-
简单、规范性:如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口不仅告诉开发人员你需要实现那些业务,而且也将命名规范限制住了(防止一些开发人员随便命名导致别的程序员无法看明白)。
-
维护、拓展性:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。
可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。
如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。 -
安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些(一般软件服务商考虑的比较多)。
泛型中extends和super的区别
总结:
-
- <? extends T> 只能用于方法返回,告诉编译器此返参的类型的最小继承边界为T,T和T的父类都能接收,但是入参类型无法确定,只能接受null的传入
-
- <? super T>只能用于限定方法入参,告诉编译器入参只能是T或其子类型,而返参只能用Object类接收
-
- ? 既不能用于入参也不能用于返参
class Super
class Self extends Super
class Son extends Self
void test()
List<? extends Self> a = new ArrayList<>();//参数类型上界是Self
a.add(new Son());//error 不能放入任何类型,因为编译器只知道a中应该放入Self的某个子类,但具体放哪种子类它并不知道,因此,除了null以外,不能放入任何类型
a.add(new Self());//error
a.add(new Super());//error
a.add(null);//error
Self s1 = a.get(0); //返回类型是确定的Self类,因为<? extends T> 只能用于方法返回,告诉编译器此返参的类型的最小继承边界为T,T和T的父类都能接收,但是入参类型无法确定,只能接受null的传入
Super s2 = a.get(0); //Self类型可以用Super接收
Son s3 = a.get(0); //error:子类不能接收父类型参数
//--------------------------------------
List<? super Self> b = new ArrayList<>();//参数类型下界是Self
b.add(new Son());//ok 只能放入T类型,且满足T类型的超类至少是Self,换句话说,就是只能放入Self的子类型
b.add(new Self());//ok 本身类型也可以
b.add(new Super());//ok 超类不可以
b.add(null);//ok
Object o1 = b.get(0);//返回类型是未知的, 因为<? super T>只能用于限定方法入参,告诉编译器入参只能是T或其子类型,而返参只能用Object类接收
Son o2 = b.get(0);//error
Self o3 = b.get(0);//error
Super o4 = b.get(0);//error
List<?> c = new ArrayList<>();
父类的静态方法能否被子类重写
public class A extends B
public static void f()
System.out.println("com.sdkd.A.f()");
public static void main(String[] args)
A.f();
class B
public static void f()
System.out.println("com.sdkd.B.f()");
调用A.f()发生了什么呢?
在JAVA虚拟机中提供了5中方法调用字节码指令,如下:
- invokestatic, 调用静态方法
- invokespecial, 调用实例构造器init方法、私有方法和父类方法
- invokevirtual, 调用所有的虚方法[public]
- invokeinterface , 调用接口方法,会在运行时再确定一个实现此接口的对象
- invokedynamic, 先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令是由引导方法决定的.
首先我们知道普通的public方法是能够被重写的,它在class方法中的字节码指令是invokevirtual,有了指令也要有参数——方法的入口地址,类似于这种invokevirtual address。该入口地址是要动态解析的,也就是将方法引用解析为直接引用,类似于这种invokevirtual 0xffffff ,也就是有一个符号引用转化成直接引用地址的过程,但是invokestatic它所需要的符号引用在类加载阶段符号引用解析成为直接引用了。也就是说它在运行的时候是这样的invokestatic 0xfffff。最终结论是由于所用指令不同,所以不能重写。
进程和线程的区别
- 进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元
- 同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进行至少包括一个线程。
- 进程的创建调用fork或者vfork,而线程的创建调用pthread_create,进程结束后它拥有的所有线程都将销毁,而线程的结束不会影响同个进程中的其他线程的结束
- 线程是轻两级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的
- 线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源
- 线程有自己的私有属性TCB,线程id,寄存器、硬件上下文,而进程也有自己的私有属性进程控制块PCB,这些私有属性是不被共享的,用来标示一个进程或一个线程的标志
final,finally,finalize的区别
final修饰符(关键字)
- 被final修饰的类,就意味着不能再派生出新的子类,不能作为父类而被子类继承。因此一个类不能既被abstract声明,又被final声明。
- 将变量或方法声明为final,可以保证他们在使用的过程中不被修改。被声明为final的变量必须在声明时给出变量的初始值,而在以后的引用中只能读取。
- 被final声明的方法也同样只能使用,不能重载。
finally修饰符:
- 是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。
finalize是方法名:
- java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
Serializable 和Parcelable 的区别
-
作用Serializable的作用是为了保存对象的属性到本地文件、数据库、网络流、rmi以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
-
效率及选择:
Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据,而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化
Serializable序列化不保存静态变量,可以使用Transient关键字对部分字段不进行序列化,也可以覆盖writeObject、readObject方法以实现序列化过程自定义
静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?
结论:java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏.
原因:
- 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
- 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
- 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。
更多文章请关注微信公众号:码老板
以上是关于BAT面试题集锦——Java基础的主要内容,如果未能解决你的问题,请参考以下文章