面向对象的三个特征
封装, 继承, 多态, 这个应该是人人皆知, 有时候也会加上抽象.
多态的好处
允许不同类对象对同一消息做出响应, 即同一消息可以根据发送对象的不同而采用多种不同的行为方式 (发送消息就是函数调用). 主要有以下优点:
1. 可替换性: 多态对已存在代码具有可替换性
2. 可扩充性: 增加新的子类不影响已经存在的类结构
3. 接口性: 多态是超类通过方法签名, 向子类提供一个公共接口, 由子类来完善或者重写它来实现的.
4. 灵活性
5. 简化性
代码中如何实现多态
实现多态主要有以下三种方式:
1. 接口实现
2. 继承父类重写方法
3. 同一类中进行方法重载
虚拟机是如何实现多态的
动态绑定技术 (dynamic binding), 执行期间判断所引用对象的实际类型, 根据实际类型调用对应的方法.
接口的意义
接口的意义用三个词就可以概括: 规范, 扩展, 回调.
抽象类的意义
抽象类的意义可以用三句话来概括:
为其他子类提供一个公共的类型
封装子类中重复定义的内容
定义抽象方法, 子类虽然有不同的实现, 但是定义时一致的
接口和抽象类的区别
父类的静态方法能否被子类重写
不能. 重写只适用于实例方法, 不能用于静态方法, 而子类当中含有和父类相同签名的静态方法, 我们一般称之为隐藏.
什么是不可变对象
不可变对象指对象一旦被创建, 状态就不能再改变. 任何修改都会创建一个新的对象, 如 String,Integer 及其它包装类.
静态变量和实例变量的区别?
静态变量存储在方法区, 属于类所有. 实例变量存储在堆当中, 其引用存在当前线程栈.
能否创建一个包含可变对象的不可变对象?
当然可以创建一个包含可变对象的不可变对象的, 你只需要谨慎一点, 不要共享可变对象的引用就可以了, 如果需要变化时, 就返回原对象的一个拷贝. 最常见的例子就是对象中包含一个日期对象的引用.
java 创建对象的几种方式
采用 new
通过反射
采用 clone
通过序列化机制
前 2 者都需要显式地调用构造方法. 造成耦合性最高的恰好是第一种, 因此你发现无论什么框架, 只要涉及到解耦必先减少 new 的使用.
switch 中能否使用 string 做参数
在 idk 1.7 之前, switch 只能支持 byte, short, char, int 或者其对应的封装类以及 Enum 类型. 从 idk 1.7 之后 switch 开始支持 String.
switch 能否作用在 byte, long 上?
可以用在 byte 上, 但是不能用在 long 上.
String s1="ab", String s2="a"+"b", String s3="a", String s4="b", s5=s3+s4 请问 s5==s2 返回什么?
返回 false. 在编译过程中, 编译器会将 s2 直接优化为 "ab", 会将其放置在常量池当中, s5 则是被创建在堆区, 相当于 s5=new String("ab");
你对 String 对象的 intern() 熟悉么?
intern() 方法会首先从常量池中查找是否存在该常量值, 如果常量池中不存在则现在常量池中创建, 如果已经存在则直接返回.
比如
- String s1="aa";
- String s2=s1.intern();
- System.out.print(s1==s2);// 返回 true
Object 中有哪些公共方法?
- equals()
- clone()
- getClass()
- notify(),notifyAll(),wait()
- toString
java 当中的四种引用
强引用, 软引用, 弱引用, 虚引用. 不同的引用类型主要体现在 GC 上:
强引用: 如果一个对象具有强引用, 它就不会被垃圾回收器回收. 即使当前内存空间不足, JVM 也不会回收它, 而是抛出 OutOfMemoryError 错误, 使程序异常终止. 如果想中断强引用和某个对象之间的关联, 可以显式地将引用赋值为 null, 这样一来的话, JVM 在合适的时间就会回收该对象.
软引用: 在使用软引用时, 如果内存的空间足够, 软引用就能继续被使用, 而不会被垃圾回收器回收, 只有在内存不足时, 软引用才会被垃圾回收器回收.
弱引用: 具有弱引用的对象拥有的生命周期更短暂. 因为当 JVM 进行垃圾回收, 一旦发现弱引用对象, 无论当前内存空间是否充足, 都会将弱引用回收. 不过由于垃圾回收器是一个优先级较低的线程, 所以并不一定能迅速发现弱引用对象.
虚引用: 顾名思义, 就是形同虚设, 如果一个对象仅持有虚引用, 那么它相当于没有引用, 在任何时候都可能被垃圾回收器回收.
WeakReference 与 SoftReference 的区别?
这点在四种引用类型中已经做了解释, 这里简单说明一下即可:
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率, 但是 WeakReference , 一旦失去最后一个强引用, 就会被 GC 回收, 而软引用虽然不能阻止被回收, 但是可以延迟到 JVM 内存不足的时候.
为什么要有不同的引用类型
不像 C 语言, 我们可以控制内存的申请和释放, 在 Java 中有时候我们需要适当的控制对象被回收的时机, 因此就诞生了不同的引用类型, 可以说不同的引用类型实则是对 GC 回收时机不可控的妥协. 有以下几个使用场景可以充分的说明:
利用软引用和弱引用解决 OOM 问题: 用一个 HashMap 来保存图片的路径和相应图片对象关联的软引用之间的映射关系, 在内存不足时, JVM 会自动回收这些缓存图片对象所占用的空间, 从而有效地避免了 OOM 的问题.
通过软引用实现 Java 对象的高速缓存: 比如我们创建了一 Person 的类, 如果每次需要查询一个人的信息, 哪怕是几秒中之前刚刚查询过的, 都要重新构建一个实例, 这将引起大量 Person 对象的消耗, 并且由于这些对象的生命周期相对较短, 会引起多次 GC 影响性能. 此时, 通过软引用和 HashMap 的结合可以构建高速缓存, 提供性能.
java 中 == 和 eqauls() 的区别, equals() 和 `hashcode 的区别
== 是运算符, 用于比较两个变量是否相等, 而 equals 是 Object 类的方法, 用于比较两个对象是否相等. 默认 Object 类的 equals 方法是比较两个对象的地址, 此时和 == 的结果一样. 换句话说: 基本类型比较用 ==, 比较的是他们的值. 默认下, 对象用 == 比较时, 比较的是内存地址, 如果需要比较对象内容, 需要重写 equal 方法.
equals() 和 hashcode() 的联系
hashCode() 是 Object 类的一个方法, 返回一个哈希值. 如果两个对象根据 equal() 方法比较相等, 那么调用这两个对象中任意一个对象的 hashCode() 方法必须产生相同的哈希值.
如果两个对象根据 eqaul() 方法比较不相等, 那么产生的哈希值不一定相等 (碰撞的情况下还是会相等的.)
a.hashCode() 有什么用? 与 a.equals(b) 有什么关系
hashCode() 方法是相应对象整型的 hash 值. 它常用于基于 hash 的集合类, 如 Hashtable,HashMap,LinkedHashMap 等等. 它与 equals() 方法关系特别紧密. 根据 Java 规范, 使用 equal() 方法来判断两个相等的对象, 必须具有相同的 hashcode.
将对象放入到集合中时, 首先判断要放入对象的 hashcode 是否已经在集合中存在, 不存在则直接放入集合. 如果 hashcode 相等, 然后通过 equal() 方法判断要放入对象与集合中的任意对象是否相等: 如果 equal() 判断不相等, 直接将该元素放入集合中, 否则不放入.
有没有可能两个不相等的对象有相同的 hashcode
有可能, 两个不相等的对象可能会有相同的 hashcode 值, 这就是为什么在 hashmap 中会有冲突. 如果两个对象相等, 必须有相同的 hashcode 值, 反之不成立.
可以在 hashcode 中使用随机数字吗?
不行, 因为同一对象的 hashcode 值必须是相同的
a==b 与 a.equals(b) 有什么区别
如果 a 和 b 都是对象, 则 a==b 是比较两个对象的引用, 只有当 a 和 b 指向的是堆中的同一个对象才会返回 true, 而 a.equals(b) 是进行逻辑比较, 所以通常需要重写该方法来提供逻辑一致性的比较. 例如, String 类重写 equals() 方法, 所以可以用于两个不同对象, 但是包含的字母相同的比较.
3*0.1==0.3 返回值是什么
false, 因为有些浮点数不能完全精确的表示出来.
a=a+b 与 a+=b 有什么区别吗?
+= 操作符会进行隐式自动类型转换, 此处 a+=b 隐式的将加操作的结果类型强制转换为持有结果的类型, 而 a=a+b 则不会自动进行类型转换. 如:
- byte a = 127;
- byte b = 127;
- b = a + b; // error : cannot convert from int to byte
- b += a; // ok
- (译者注: 这个地方应该表述的有误, 其实无论 a+b 的值为多少, 编译器都会报错, 因为 a+b 操作会将 a,b 提升为 int 类型, 所以将 int 类型赋值给 byte 就会编译出错)
short s1= 1; s1 = s1 + 1; 该段代码是否有错, 有的话怎么改?
有错误, short 类型在进行运算时会自动提升为 int 类型, 也就是说 s1+1 的运算结果是 int 类型.
short s1= 1; s1 += 1; 该段代码是否有错, 有的话怎么改?
+= 操作符会自动对右边的表达式结果强转匹配左边的数据类型, 所以没错.
& 和 && 的区别
首先记住 & 是位操作, 而 && 是逻辑运算符. 另外需要记住逻辑运算符具有短路特性, 而 & 不具备短路特性.
- 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 文件内部可以有类?(非内部类)
只能有一个 public 公共类, 但是可以有多个 default 修饰的类.
如何正确的退出多层嵌套循环?
使用标号和 break;
通过在外层循环中添加标识符
内部类的作用
内部类可以有多个实例, 每个实例都有自己的状态信息, 并且与其他外围对象的信息相互独立. 在单个外围类当中, 可以让多个内部类以不同的方式实现同一接口, 或者继承同一个类. 创建内部类对象的时刻不依赖于外部类对象的创建. 内部类并没有令人疑惑的 "is-a" 管系, 它就像是一个独立的实体.
内部类提供了更好的封装, 除了该外围类, 其他类都不能访问.
final, finalize 和 finally 的不同之处
final 是一个修饰符, 可以修饰变量, 方法和类. 如果 final 修饰变量, 意味着该变量的值在初始化后不能被改变. finalize 方法是在对象被回收之前调用的方法, 给对象自己最后一个复活的机会, 但是什么时候调用 finalize 没有保证. finally 是一个关键字, 与 try 和 catch 一起用于异常的处理. finally 块一定会被执行, 无论在 try 块中是否有发生异常.
clone() 是哪个类的方法?
java.lang.Cloneable 是一个标示性接口, 不包含任何方法, clone 方法在 object 类中定义. 并且需要知道 clone() 方法是一个本地方法, 这意味着它是由 c 或 c++ 或 其他本地语言实现的.
深拷贝和浅拷贝的区别是什么?
浅拷贝: 被复制对象的所有变量都含有与原来的对象相同的值, 而所有的对其他对象的引用仍然指向原来的对象. 换言之, 浅拷贝仅仅复制所考虑的对象, 而不复制它所引用的对象.
深拷贝: 被复制对象的所有变量都含有与原来的对象相同的值, 而那些引用其他对象的变量将指向被复制过的新对象, 而不再是原有的那些被引用的对象. 换言之, 深拷贝把要复制的对象所引用的对象都复制了一遍.
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 也是很多面试喜欢问的地方, 能回答下以下三点就不错了:
1. 被 final 修饰的类不可以被继承
2. 被 final 修饰的方法不可以被重写
3. 被 final 修饰的变量不可以被改变. 如果修饰引用, 那么表示引用不可变, 引用指向的内容可变.
4. 被 final 修饰的方法, JVM 会尝试将其内联, 以提高运行效率
5. 被 final 修饰的常量, 在编译阶段会存入常量池中.
回答出编译器对 final 域要遵守的两个重排序规则更好:
1. 在构造函数内对一个 final 域的写入, 与随后把这个被构造对象的引用赋值给一个引用变量, 这两个操作之间不能重排序.
2. 初次读一个包含 final 域的对象的引用, 与随后初次读这个 final 域, 这两个操作之间不能重排序.
来源: http://www.jianshu.com/p/29463e637fe4