Java经典面试题(其一)——Java异常和克隆

Java经典面试题(其一)——Java异常和克隆

谈一谈Java中的Error和Exception

1.Error和Exception的联系

继承关系:Error和Exception都是继承于Throwable,RuntimeException继承自Exception。

Error和RuntimeExceptime及其子类被称为未检查异常(Unchecked exception),其它异常称为受检查异常(Checked Exception)。

2.Error和Exception的区别

Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存不足,方法调用栈溢出等。如Java.lang.StackOverFlowError和Java.lang.OutOfMemoryError。对于这类错误,Java编译器不去检查他们。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。

Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能的处理异常,使程序恢复运行,而不应该随意终止异常。

3.运行时异常和受检查异常

Exception又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception)。

RuntimeException:其特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即时没有用try…catch捕获,也没有用throws抛出,还是会编译通过,如除数为零的ArithmeticException、错误的类型转换、数组越界访问和试图访问空指针等。处理RuntimeException的原则是:如果出现RuntimeException,那么一定是程序员的错误。

受检查异常(IOException等):这类异常如果没有try…catch也没有throws抛出,编译器是通不过的。这是在应用环境中出现的外部错误。

4.throw和throws两个关键字有什么不同

throw是用来抛出任意异常的,你可以抛出任意Throwable,包括自定义的异常类对象;throws总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常。如果方法抛出了异常,那么调用这个方法的时候就需要处理这个异常。

5.try-catch-finally-return执行顺序

1> 不管是否有异常产生,finally块中代码都会执行;

2> 当try和catch中有return语句时,finally块仍然会执行;

3> finally是在return后面的表达式运算执行的,所以函数返回值在finally执行前确定的,无论finally中的代码怎么样,返回的值都不会改变,仍然是之前return语句中保存的值;

4> finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

6.常见的几种RunException

一般面试中Java Exception(RuntimeExceptime)是必会问道的问题,常见的异常列出四五种,是基本要求。

常见的几种如下:

NullPointerException - 空指针引用异常

ClassCastException - 类型强制转换异常

IllegalArgumentException - 传递非法参数异常

ArithmeticException - 算术运算异常

ArrayStoreException - 向数组中存放与声明类型不兼容对象异常

IndexOutOfBoundsException - 下标越界异常

NegativeArraySizeException - 创建一个大小为负数的数组错误异常

NumberFormatException - 数字格式异常

SecurityException - 安全异常

UnsupportedOperationException - 不支持的操作异常

NegativeArrayException - 数组负下标异常

EOFException - 文件已结束异常

FileNotFoundException - 文件未找到异常

SQLException - 操作数据库异常

IOException - 输入输出异常

NoSuchMethodException - 方法未找到异常

java.lang.AbstractMethodError - 抽象方法错误。当应用试图调用抽象方法时抛出。

java.lang.AssertionError - 断言错。用来指示一个断言失败的错误。

java.lang.ClassCircularityError - 类循环依赖错误。在初始化一个类时,若检测到类之间循环依赖则抛出该异常。

java.lang.ClassFormatError - 类格式错误。当Java虚拟机试图从一个文件中读取Java类,而检测到该文件的内容不符合类的有效格式时输出。

java.lang.Error - 错误。是所有错误的基类,用于标识严重的程序运行问题。这些问题通常描述一些不应被应用程序捕获的反常情况。

详解Java中的对象克隆

1.在Java语言中,我们说两个对象是否相等通常有两层含义:

对象的内容是否相等,通常使用到对象equals(Object o)函数;

引用的地址是否相同,使用运算符==比较即可。

当两个对象通过赋值符号 = 赋值时,表明这两个对象指向了内存中同一个地址,所以改变其中一个对象的内容,也就是间接改变了另一个对象的内容。有时候,我们需要从一个已经存在的对象重新拷贝一份出来,并且不仅这两个内容相等,在内存中存在两个独立的存储地址,互不影响,这时,就需要用到Java的克隆机制。

2.Cloneable

通过Cloneable接口可以很轻松地实现Java对象的克隆,只需要implements Cloneable并实现Object的clone()方法即可,如:

public class User implements Cloneable{

    private String username;

    private String password;

    public User(String username, String password) {
        super();
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public boolean equals(Object obj) {
        User user = (User) obj;
        if (username.equals(user.username) && password.equals(user.password)) {
            return true;
        }
        return false;
    }

}

// 注意这里对象实现的是Object的clone方法,因为Cloneable是一个空接口

package java.lang;

/**
 * A class implements the Cloneable interface to
 * indicate to the {@link java.lang.Object#clone()} method that it
 * is legal for that method to make a
 * field-for-field copy of instances of that class.
 * 

* Invoking Object's clone method on an instance that does not implement the * Cloneable interface results in the exception * CloneNotSupportedException being thrown. *

* By convention, classes that implement this interface should override * Object.clone (which is protected) with a public method. * See {@link java.lang.Object#clone()} for details on overriding this * method. *

* Note that this interface does not contain the clone method. * Therefore, it is not possible to clone an object merely by virtue of the * fact that it implements this interface. Even if the clone method is invoked * reflectively, there is no guarantee that it will succeed. * * @author unascribed * @see java.lang.CloneNotSupportedException * @see java.lang.Object#clone() * @since JDK1.0 */ public interface Cloneable { } // 从源码中可以看出,需要实现Object类中的clone()方法(注意:clone()方法是一个native方法,同时抛出了一个异常) /** * Creates and returns a copy of this object. The precise meaning * of "copy" may depend on the class of the object. The general * intent is that, for any object {@code x}, the expression: *

*
     * x.clone() != x
* will be true, and that the expression: *
*
     * x.clone().getClass() == x.getClass()
* will be {@code true}, but these are not absolute requirements. * While it is typically the case that: *
*
     * x.clone().equals(x)
* will be {@code true}, this is not an absolute requirement. *

* By convention, the returned object should be obtained by calling * {@code super.clone}. If a class and all of its superclasses (except * {@code Object}) obey this convention, it will be the case that * {@code x.clone().getClass() == x.getClass()}. *

* By convention, the object returned by this method should be independent * of this object (which is being cloned). To achieve this independence, * it may be necessary to modify one or more fields of the object returned * by {@code super.clone} before returning it. Typically, this means * copying any mutable objects that comprise the internal "deep structure" * of the object being cloned and replacing the references to these * objects with references to the copies. If a class contains only * primitive fields or references to immutable objects, then it is usually * the case that no fields in the object returned by {@code super.clone} * need to be modified. *

* The method {@code clone} for class {@code Object} performs a * specific cloning operation. First, if the class of this object does * not implement the interface {@code Cloneable}, then a * {@code CloneNotSupportedException} is thrown. Note that all arrays * are considered to implement the interface {@code Cloneable} and that * the return type of the {@code clone} method of an array type {@code T[]} * is {@code T[]} where T is any reference or primitive type. * Otherwise, this method creates a new instance of the class of this * object and initializes all its fields with exactly the contents of * the corresponding fields of this object, as if by assignment; the * contents of the fields are not themselves cloned. Thus, this method * performs a "shallow copy" of this object, not a "deep copy" operation. *

* The class {@code Object} does not itself implement the interface * {@code Cloneable}, so calling the {@code clone} method on an object * whose class is {@code Object} will result in throwing an * exception at run time. * * @return a clone of this instance. * @throws CloneNotSupportedException if the object's class does not * support the {@code Cloneable} interface. Subclasses * that override the {@code clone} method can also * throw this exception to indicate that an instance cannot * be cloned. * @see java.lang.Cloneable */ protected native Object clone() throws CloneNotSupportedException; // 从clone()函数的注释中能够看出对象与克隆对象之间的关系,测试代码如下(注意:我们在User对象中重写了equals()函数) public static void main(String[] args) throws CloneNotSupportedException{ User userOne, userTwo, userThree; userOne = new User("username", "password"); userTwo = userOne; userThree = (User) userOne.clone(); System.out.println(userTwo==userOne); //true System.out.println(userTwo.equals(userOne)); //true System.out.println(userThree==userOne); //false System.out.println(userThree.equals(userOne)); //true } // 测试结果显示,通过clone()函数,我们成功地从userOne对象中克隆出来一份独立的userThree对象

  1. 浅克隆与深克隆
// 谈此之前,我们先看一个例子,定义一个名为Company的类,并添加一个类型为User的成员变量

public class Company implements Cloneable{

    private User user;

    private String address;

    public Company(User user, String address) {
        super();
        this.user = user;
        this.address = address;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public boolean equals(Object obj) {
        Company company = (Company) obj;
        if (user.equals(company.getUser()) && address.equals(company.address)) {
            return true;
        }
        return false;
    }

}

// 测试代码及测试结果如下

public static void main(String[] args) throws CloneNotSupportedException{
        Company companyOne, companyTwo, companyThree;
        companyOne = new Company(new User("username", "password"), "上海市");
        companyTwo = companyOne;
        companyThree = (Company) companyOne.clone();

        System.out.println(companyTwo==companyOne);                //true
        System.out.println(companyTwo.equals(companyOne));        //true

        System.out.println(companyThree==companyOne);            //false
        System.out.println(companyThree.equals(companyOne));    //true

        System.out.println(companyThree.getUser()==companyOne.getUser());            //true ? 这里为什么不是false呢
        System.out.println(companyThree.getUser().equals(companyOne.getUser()));    //true

    }

问题来了,companyThree与companyOne的User是同一对象!也就是说companyThree只是克隆了companyOne的基本数据类型的数据,而对于引用的数据没有进行深度的克隆。也就是俗称的浅克隆。

浅克隆:顾名思义,就是很表层的克隆,只克隆对象自身的引用地址;

深克隆:也称“N层克隆”,克隆对象自身以及对象所包含的引用类型对象的引用地址。

这里需要注意的是,对于基本数据类型(primitive)和使用常量池方式创建的String类型,都会针对原值克隆,所以不存在引用地址一说。当然不包括他们对应的包装类。

所以使用深克隆可以解决上述Company类的Clone()函数:

@Override
    protected Object clone() throws CloneNotSupportedException {
        Company company = (Company) super.clone();
        company.user = (User) company.getUser().clone();
        return company;
    }

// 再运行测试代码,就能得到companyThree.getUser()==companyOne.getUser() 为false的结果

4.Serializable实现

通过上述介绍,我们知道,实现一个对象的克隆,需要如下几步:

对象所在的类实现Cloneable接口;

2.重写clone()函数,如果包涵引用类型的成员变量,需要使用深克隆。

如果对象不包含引用类型成员或者数量少的话,使用Cloneable接口还能接受,但当对象包含多个引用类型的成员,同时这些成员又包含了引用类型的成员,那层层克隆岂不是相当繁琐,并且维护不便?所以,这里接受一种更加方便的实现方式,使用ObjectOutputStream和ObjectInputStrean来实现对象的序列化和反序列化

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public abstract class BeanUtils {
    @SuppressWarnings("unchecked")
    public static  T cloneTo(T src) throws RuntimeException {
        ByteArrayOutputStream memoryBuffer = new ByteArrayOutputStream();
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        T dist = null;
        try {
            out = new ObjectOutputStream(memoryBuffer);
            out.writeObject(src);
            out.flush();
            in = new ObjectInputStream(new ByteArrayInputStream(memoryBuffer.toByteArray()));
            dist = (T) in.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (out != null)
                try {
                    out.close();
                    out = null;
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            if (in != null)
                try {
                    in.close();
                    in = null;
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
        }
        return dist;
    }
}

// 只要要克隆的对象以及对象所包含的引用类型的成员对象所在的类实现了java.io.Serializable 接口即可做到完美克隆。

你可能感兴趣的:(JAVA技术提高)