1、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回值类型进行区分?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不相同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回值类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。
2、抽象类(abstract类)和接口(interface)有什么异同?
抽象类和接口都不能实例化,但是可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全是抽象方法。抽象类中的成员可以是private、默认、protected、public的,而接口中的全部成员全都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。
3、Java中会存在内存泄漏吗,请简单描述。
理论上Java因为有垃圾回收机制(GC)不会存在内存泄漏问题(这也是Java被广泛应用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄漏的发生。
例如Hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄漏。下面例子中的代码也会导致内存泄露。
import java.util.Arrays;
import java.util.EmptyStackException;
public class MyStack<T>{
private T[] elements;
private int size = 0;
private static final int INIT_CAPACITY = 16;
public MyStack(){
elements = (T[]) new Object[INIT_CAPACITY];
}
private void ensureCapacity(){
if((elements.length == size)
elements = Arrays.copyOf(elements,2*size+1);
}
}
public void push(T elem){
ensureCapacity();
elenments[size++] = elem;
}
public void pop(){
if(size == 0){
throw new EmptyStackException();
return elements[--size];
}
}
上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明显的问题,它甚至可以通过你编写的各种单元测试。然而其中的pop方法却存在内存泄漏的问题,当我们用pop方法弹出栈中的对象时,该对象不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过期引用(obsolete reference)。在支持垃圾回收的语言中,内存泄漏时隐蔽的,这种内存泄漏其实就是无意识的对象保持。如果一个对象引用被无意识的保留起来,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引发Disk Paging(物理内存与硬盘的虚拟内存交换数据),甚至造成OutOfMemoryError。
4.抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?
都不能。抽象方法需要子类冲写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此是相互矛盾的。
5.阐述静态变量和实例变量的区别。
静态变量是被static修饰符修饰的变量,也成为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于一个实例,需要首先创建对象然后通过对象才能访问到它。静态变量可以实现多个对象共享内存。
6.如何实现克隆?
(1)实现Cloneable接口并重写Object类中的clone()方法;
(2)实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下。
package com.test.practice;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Clone {
private Clone(){
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();
}
}
package com.test.practice;
import java.io.Serializable;
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 + "]";
}
}
/**
* 小汽车类
*/
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 + "]";
}
}
public class CloneTest implements Serializable {
public static void main(String[] args) {
try {
Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300));
Person p2 = Clone.clone(p1); // 深度克隆
p2.getCar().setBrand("BYD"); // 修改克隆的Person对象p2关联的汽车对象的品牌属性
// 原来的Person对象p1关联的汽车不会受到任何影响
// 因为在克隆Person对象时其关联的汽车对象也被克隆了
System.out.println(p1);
System.out.println(p2);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种事方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。