基础概念
面向对象的三个特征
封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象.
多态的好处
允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用).主要有以下优点:
- 可替换性:多态对已存在代码具有可替换性.
- 可扩充性:增加新的子类不影响已经存在的类结构.
- 接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的.
- 灵活性:
- 简化性:
虚拟机是如何实现多态的
动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法.如果你知道Hotspot中oop-klass模型的实现,对这个问题就了解比较深了.
接口的意义
接口的意义用三个词就可以概括:规范,扩展,回调.
抽象类的意义
抽象类的意义可以用三句话来概括:
- 为其他子类提供一个公共的类型
- 封装子类中重复定义的内容
- 定义抽象方法,子类虽然有不同的实现,但是定义时一致的
接口和抽象类的区别
比较点 | 抽象类 | 接口 |
---|---|---|
默认方法 | 抽象类可以有默认的方法实现 | java 8之前,接口中不存在方法的实现 |
实现方式 | 子类使用extends关键字来继承抽象类.如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现 | 子类使用implements来实现接口,需要提供接口中所有声明的实现. |
构造器 | 抽象类中可以有构造器 | 接口中不能 |
和正常类区别 | 抽象类不能被实例化 | 接口则是完全不同的类型 |
访问修饰符 | 抽象方法可以有public,protected和default等修饰 | 接口默认是public,不能使用其他修饰符 |
多继承 | 一个子类只能存在一个父类 | 一个子类可以存在多个接口 |
添加新方法 | 抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 | 如果往接口中添加新方法,则子类中需要实现该方法 |
父类的静态方法能否被子类重写?
不能.重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法,我们一般称之为隐藏.
什么是不可变对象?好处是什么?
不可变对象指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象,如 String、Integer及其它包装类.不可变对象最大的好处是线程安全.
静态变量和实例变量的区别?
静态变量存储在方法区,属于类所有.实例变量存储在堆当中,其引用存在当前线程栈.需要注意的是从JDK1.8开始用于实现方法区的PermSpace被MetaSpace取代了.
能否创建一个包含可变对象的不可变对象?
当然可以,比如final Person[] persons = new Persion[]{}
.persons是不可变对象的引用,但其数组中的Person实例却是可变的.这种情况下需要特别谨慎,不要共享可变对象的引用.这种情况下,如果数据需要变化时,就返回原对象的一个拷贝.
java 创建对象的几种方式
java中提供了以下四种创建对象的方式:
- new创建新对象
- 通过反射机制
- 采用clone机制
- 通过序列化机制
前两者都需要显式地调用构造方法. 对于clone机制,需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在java中序列化可以通过实现Externalizable或者Serializable来实现.
switch中能否使用string做参数?
在JDK 1.7之前,switch只能支持byte,short,char,int或者其对应的包装类以及Enum类型.从JDK 1.7之后switch开始支持String类型.但到目前为止,switch都不支持long类型.
Object中有哪些公共方法?
equals()
,clone()
,getClass()
,notify(),notifyAll(),wait()
,toString
java中==
和eqauls()
的区别?
==
是运算符,用于比较两个变量是否相等,对于基本类型而言比较的是变量的值,对于对象类型而言比较的是对象的地址.equals()
是Object类的方法,用于比较两个对象内容是否相等.默认Object类的equals()
实现如下:
public class Object {
......
public boolean equals(Object obj) {
return (this == obj);
}
......
}
不难看出此时equals()
是比较两个对象的地址,此时直接==
比较的的结果一样.对于可能用于集合存储中的对象元素而言,通常需要重写其equals()
方法.
a==b
与a.equals(b)
有什么区别
如果a 和b 都是对象,则 a==b
是比较两个对象内存地址,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true.而 a.equals(b)
是进行内容比较,其比较结果取决于equals()
具体实现.多数情况下,我们需要重写该方法,如String 类重写 equals()
用于两个不同对象,但是包含的字母相同的比较:
public boolean equals(Object anObject) {
if (this == anObject) {
// 同一个对象直接返回true
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
// 按字符依次比较
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
Object中的equals()
和hashcode()
的联系
hashCode()
是Object类的一个方法,返回一个哈希值.如果两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值;如果两个对象根据eqaul()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的.)
a.hashCode()有什么用?与a.equals(b)有什么关系
hashCode()
方法是为对象产生整型的 hash 值,用作对象的唯一标识.它常用于基于 hash 的集合类,如 Hashtable,HashMap等等.根据 Java 规范,使用 equal()
方法来判断两个相等的对象,必须具有相同的 hashcode.
将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合.如果hashcode相等,然后通过equal()
方法判断要放入对象与集合中的任意对象是否相等:如果equal()
判断不相等,直接将该元素放入集合中,否则不放入.
有没有可能两个不相等的对象有相同的hashcode
有可能.在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值.当hash冲突产生时,一般有以下几种方式来处理:
- 拉链法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储.
- 开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
- 再哈希:又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个….等哈希函数计算地址,直到无冲突.
可以在hashcode中使用随机数字吗?
不行,因为同一对象的 hashcode 值必须是相同的.
& 和 &&的区别
基础的概念不能弄混:&是位操作,&&是逻辑运算符.需要记住逻辑运算符具有短路特性,而&不具备短路特性.来看看一下代码执行结果?
public class Test{
static String name;
public static void main(String[] args){
if(name!=null&userName.equals("")){
System.out.println("ok");
}else{
System.out.println("erro");
}
}
}
上述代码将会抛出空指针异常.原因你懂得.
在.java文件内部可以有多少类(非内部类)?
在一个java文件中只能有一个public公共类,但是可以有多个default修饰的类.
如何正确的退出多层嵌套循环?
- 使用标号和break;
- 通过在外层循环中添加标识符
内部类有什么作用?
内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立.在单个外围类当中,可以让多个内部类以不同的方式实现同一接口,或者继承同一个类.创建内部类对象的时刻不依赖于外部类对象的创建.内部类并没有令人疑惑的”is-a”关系,它就像是一个独立的实体.此外,内部类提供了更好的封装,除了该外围类,其他类都不能访问.
final
,finalize()
和finally{}
的不同之处
三者没有任何相关性,遇到有问着问题的面试官就拖出去砍了吧.final是一个修饰符,用于修饰变量,方法和类.如果 final 修饰变量,意味着该变量的值在初始化后不能被改变.finalize()
方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会.但是该方法由Finalizer线程调用,但调用时机无法保证.finally是一个关键字,与 try和catch一起用于异常的处理,finally{}
一定会被执行,在此处我们通常用于资源关闭操作.
clone()
是哪个类的方法?
java.lang.Cloneable 是一个标示性接口,不包含任何方法.clone ()
方法在 Object 类中定义的一个Native方法:
protected native Object clone() throws CloneNotSupportedException;
深拷贝和浅拷贝的区别是什么?
-
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.
-
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都复制了一遍.
static都有哪些用法?
所有的人都知道static关键字这两个基本的用法:静态变量和静态方法.也就是被static所修饰的变量/方法都属于类的静态资源,类实例所共享.
除了静态变量和静态方法之外,static也用于静态块,多用于初始化操作:
public calss PreCache{
static{
//执行相关操作
}
}
此外static也多用于修饰内部类,此时称之为静态内部类.
最后一种用法就是静态导包,即import static
.import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名,比如:
import static java.lang.Math.*;
public class Test{
public static void main(String[] args){
//System.out.println(Math.sin(20));传统做法
System.out.println(sin(20));
}
}
final有哪些用法?
final也是很多面试喜欢问的地方,但我觉得这个问题很无聊,通常能回答下以下5点就不错了:
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.
- 被final修饰的方法,JVM会尝试将其内联,以提高运行效率
- 被final修饰的常量,在编译阶段会存入常量池中.
除此之外,编译器对final域要遵守的两个重排序规则更好:
- 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
- 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序.
数据类型相关
java中int char,long各占多少字节?
这个问题其实很无聊.应该去问java中的各种数据类型在不同的平台运行时期所占位数一样么?
类型 | 字节 |
---|---|
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
char | |
64位的JVM当中,int的长度是多少?
Java中数据类型所占用的位数和平台无关,在 32 位和64位 的Java 虚拟机中,int 类型的长度都是占4字节.
int和Integer的区别?
Integer是int的包装类型,在拆箱和装箱中,二者自动转换.int是基本类型,直接存数值;而integer是对象;用一个引用指向这个对象.由于Integer是一个对象,在JVM中对象需要一定的数据结构进行描述,相比int而言,其占用的内存更大一些.
String s = new String("abc")
创建了几个String对象?
2个.一个是字符串字面常数,在字符串常量池中;另一个是new出来的字符串对象,在堆中.
请问s1==
s3是true还是false,s1==
s4是false还是true?s1==
s5呢?
String s1 = "abc";
String s2 = "a";
String s3 = s2 + "bc";
String s4 = "a" + "bc";
String s5 = s3.intern();
s1==
s3返回false,s1==
s4返回true,s1==
s5返回true.
“abc"这个字符串常量值会直接方法字符串常量池中,s1是对其的引用.由于s2是个变量,编译器在编译期间无法确定该变量后续会不会改,因此无法直接将s3的值在编译器计算出来,因此s3是堆中"abc"的引用.因此s1!=s3.对于s4而言,其赋值号右边是常量表达式,因此可以在编译阶段直接被优化为"abc”,由于"abc"已经在字符串常量池中存在,因此s4是对其的引用,此时也就意味s1和s4引用了常量池中的同一个"abc".所以s1==
s4.String中的intern()
会首先从字符串常量池中检索是否已经存在字面值为"abc"的对象,如果不存在则先将其添加到字符串常量池中,否则直接返回已存在字符串常量的引用.此处由于"abc"已经存在字符串常量池中了,因此s5和s1引用的是同一个字符串常量.
以下代码中,s5==
s2返回值是什么?
String s1="ab";
String s2="a"+"b";
String s3="a";
String s4="b";
String s5=s3+s4;
返回false.在编译过程中,编译器会将s2直接优化为"ab",将其放置在常量池当中;而s5则是被创建在堆区,相当于s5=new String(“ab”);
你对String对象的intern()熟悉么?
Stirng中的intern()
是个Native方法,它会首先从常量池中查找是否存在该常量值的字符串,若不存在则先在常量池中创建,否则直接返回常量池已经存在的字符串的引用. 比如
String s1="aa";
String s2=s1.intern();
System.out.print(s1==s2);
上述代码将返回true.因为在"aa"会在编译阶段确定下来,并放置字符串常量池中,因此最终s1和s2引用的是同一个字符串常量对象.
String,StringBuffer和StringBuilder区别?
String是字符串常量,final修饰;StringBuffer字符串变量(线程安全);StringBuilder 字符串变量(线程不安全).此外StringBuilder和StringBuffer实现原理一样,都是基于数组扩容来实现的.
String和StringBuffer的区别?
String和StringBuffer主要区别是性能:String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象.所以尽量不要对String进行大量的拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能.
StringBuffer是对象本身操作,而不是产生新的对象,因此在有大量拼接的情况下,我们建议使用StringBuffer(线程安全).
需要注意现在JVM会对String拼接做一定的优化,比如
String s="This is only "+ "simple" +"test";
以上代码在编译阶段会直接被优化成会`String s=“This is only simple test”