- 面向对象的3个特征
封装,继承,多态,(抽象)。
- 多态的好处:
可替换性:多态对已存在代码具有可替换性。
可扩充性:增加的子类不影响已经存在的类结构。
接口性:多态是超类通过方法签名,向子类提供一个公共接口,有子类来完善或重写它来实现的。
灵活性:
简化性:
- 实现多态:接口实现,继承父类重写方法,同一类中进行方法重载。
- 虚拟机实现多态:动态绑定技术,执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法。
- 接口的意义:规范,扩展,回调。
- 抽象类的意义:为其他类提供一个公共的类型;封装子类中重复定义的内容。定义抽象方法,子类虽然有不同实现,但是定义时一致的。
- 静态变量与实例变量的区别:静态变量存储在方法区,属于类所有。实例变量存储在堆当中,其引用存在当前线程栈。
- Java创建对象的几种方式:1.采用new 2.通过反射3.采用clone4.通过序列化机制。
- Switch :jdk1.7前 byte,short,char,int或者对应的封装类,Enum类型。1.7后,支持String
- Object 公共方法:equals(),clone(),getClass(),notify(),notifyAll(),wait(),toString(),hashCode()
- Java中四种引用:强引用,软引用,弱引用,虚引用。不同的引用类型主要体现在GC上。
强引用:如果一个对象具有强引用,则不会被垃圾回收器回收。如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null.
软引用:如果内存空间够,软引用能被继续使用。内存不足才会被垃圾回收器回收。
弱引用:无论当前内存空间是否充足,都会将弱引用回收。不过垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。
虚引用:任何时候都可能被垃圾回收器回收。
利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题.
通过软引用实现Java对象的高速缓存:比如我们创建了一Person的类,如果每次需要查询一个人的信息,哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这将引起大量Person对象的消耗,并且由于这些对象的生命周期相对较短,会引起多次GC影响性能。此时,通过软引用和 HashMap 的结合可以构建高速缓存,提供性能。
- equals()和hashcode()的联系:如果两个对象根据equals()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值。如果两个对象根据equals()方法比较不相等,那么产生的hash值不一定相等(碰撞的情况下还是会相等的)
- 退出多层嵌套循环:1.使用标号和break 2通过在外层循环中添加调试符。
- 内部类:提供了更好的封装,除了该外围类,其他类不能访问。
- Clone():java.lang.Cloneadble是一个标示性的接口,不包含任何方法,clone方法在object类中定义。并且需要知道clone()方法是一个本地方法,这意味着它是由c或c++或其他本地语言实现的。
- 深克隆:被复制的对象的所有变量都含有与原来对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不是原有的那些被引用的对象。换言之,深克隆把要复制的对象所引用的对象都复制了一遍。
浅克隆:被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑得对象,而不复制它所引用的对象。
- String,StringBufffer(线程安全),StringBuilder(线程不安全)
- 垃圾回收 标记-清除;标记-复制;标记-整理;分代回收。
- 如何判断一个对象是否应该被回收:1.引用计数法2.对象可达性分析。由于引用计数法存在相互引用导致无法进行GC的问题,所以目前JVM多使用对象可达性分析算法。
- 垃圾回收:
最基本的做法是分代回收。内存中的区域被划分为不同的世代,对象根据其存活的时间被保存在对应世代的区域中。一般的实现是划分成3个世代:年轻,年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候,它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说,一个应用中的大部分对象的存活时间都很短。比如局部变量的存活时间就只在方法的执行过程中。基于这一点,对于年轻世代的垃圾回收算法就可以很有针对性。
- 多线程上下文切换:值CPU控制权由一个已经在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。
- 相比扩展Thread,实现Runnable接口可能更优。原因有二:
- java不支持多继承。因此扩展Thread类就代表这个子类不能扩展其他类。而实现Runnable接口的类还可能扩展另一个类。
- 类可能只要求可执行即可,因此继承整个Thread类的开销过大。
- Thread类中的start()和run()方法有什么区别:start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法时,只会在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
- 描述一下JVM加载class文件的原理机制?
- 答:JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:
Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
22、char 型变量中能不能存贮一个中文汉字,为什么?
答:char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。
补充:使用Unicode意味着字符在JVM内部和外部有不同的表现形式,在JVM内部都是Unicode,当这个字符被从JVM内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以Java中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如InputStreamReader和OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于C程序员来说,要完成这样的编码转换恐怕要依赖于union(联合体/共用体)共享内存的特征来实现了。
29、如何实现对象克隆?
答:有两种方式:
??1). 实现Cloneable接口并重写Object类中的clone()方法;
??2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
public static <T> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
}
}
下面是测试代码:
import java.io.Serializable;
/**
* 人类
* @author 骆昊
*
*/
class Person implements Serializable {
private static final long serialVersionUID = -9102017020286042305L;
private String name; // 姓名
private int age; // 年龄
private Car car; // 座驾
public Person(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}
/**
* 小汽车类
* @author 骆昊
*
*/
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌
private int maxSpeed; // 最高时速
public Car(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}
}
class CloneTest { public static void main(String[] args) { try { Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300)); Person p2 = MyUtil.clone(p1); // 深度克隆 p2.getCar().setBrand("BYD"); // 修改克隆的Person对象p2关联的汽车对象的品牌属性 // 原来的Person对象p1关联的汽车不会受到任何影响 // 因为在克隆Person对象时其关联的汽车对象也被克隆了 System.out.println(p1); } catch (Exception e) { e.printStackTrace(); } }}
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。
30、GC是什么?为什么要有GC?
答:GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。
补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。标准的Java进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建的对象。Java平台对堆内存回收和再利用的基本算法被称为标记和清除,但是Java对其进行了改进,采用“分代式垃圾收集”。这种方法会跟Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域:
- 伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。
- 幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。
- 终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。
与垃圾回收相关的JVM参数:
-Xms / -Xmx — 堆的初始大小 / 堆的最大大小
-Xmn — 堆中年轻代的大小
-XX:-DisableExplicitGC — 让System.gc()不产生任何作用
-XX:+PrintGCDetails — 打印GC的细节
-XX:+PrintGCDateStamps — 打印GC操作的时间戳
-XX:NewSize / XX:MaxNewSize — 设置新生代大小/新生代最大大小
-XX:NewRatio — 可以设置老生代和新生代的比例
-XX:PrintTenuringDistribution — 设置每次新生代GC后输出幸存者乐园中对象年龄的分布
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值
-XX:TargetSurvivorRatio:设置幸存区的目标使用率
39、如何实现字符串的反转及替换?
答:方法很多,可以自己写实现也可以使用String或StringBuffer/StringBuilder中的方法。有一道很常见的面试题是用递归实现字符串反转,代码如下所示:
public static String reverse(String originStr) {
if(originStr == null || originStr.length() <= 1)
return originStr;
return reverse(originStr.substring(1)) + originStr.charAt(0);
}