本文共 10250 字,大约阅读时间需要 34 分钟。
一、java面试题
熟练掌握java是很关键的,大公司不仅仅要求你会使用几个api,更多的是要你熟悉源码实现原理,甚至要你知道有哪些不足,怎么改进,还有一些java有关的一些算法,设计模式等等。
(一) java基础面试知识点
java中==和equals和hashCode的区别
public boolean equals(Object obj) { return (this == obj);}
将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。回过来说get的时候,HashMap也先调key.hashCode()算出数组下标,然后看equals如果是true就是找到了,所以就涉及了equals。一个很常见的错误根源在于没有覆盖hashCode方法。在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。
java中int、char、long各占多少字节数
long double
8字节int与integer的区别
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); //trueInteger 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多态的理解
多态的好处:
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是线程安全的
什么是内部类?内部类的作用
可以将一个类的定义放在另一个类的定义的内部,这就是内部类。
内部类的作用:
给第三点举例:
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语言中, abstract class 和interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的 面向对象能力。
简单、规范性:如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口不仅告诉开发人员你需要实现那些业务,而且也将命名规范限制住了(防止一些开发人员随便命名导致别的程序员无法看明白)。
维护、拓展性:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。
可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。 如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些(一般软件服务商考虑的比较多)。
泛型中extends和super的区别
总结:
class Super{ } class Self extends Super{ } class Son extends Self{ } void test() { List 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类,因为 只能用于方法返回,告诉编译器此返参的类型的最小继承边界为T,T和T的父类都能接收,但是入参类型无法确定,只能接受null的传入 Super s2 = a.get(0); //Self类型可以用Super接收 Son s3 = a.get(0); //error:子类不能接收父类型参数 //-------------------------------------- List 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);//返回类型是未知的, 因为 只能用于限定方法入参,告诉编译器入参只能是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中方法调用字节码指令,如下:首先我们知道普通的public方法是能够被重写的,它在class方法中的字节码指令是invokevirtual,有了指令也要有参数——方法的入口地址,类似于这种invokevirtual address。该入口地址是要动态解析的,也就是将方法引用解析为直接引用,类似于这种invokevirtual 0xffffff ,也就是有一个符号引用转化成直接引用地址的过程,但是invokestatic它所需要的符号引用在类加载阶段符号引用解析成为直接引用了。也就是说它在运行的时候是这样的invokestatic 0xfffff。最终结论是由于所用指令不同,所以不能重写。
进程和线程的区别
final,finally,finalize的区别
final修饰符(关键字)
finally修饰符:
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)而是被隐藏.
原因:
更多文章请关注微信公众号:码老板