1、面向对象都有哪些特性以及你对这些特性的理解
(1)继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件有了一定的 延续性,同时继承也是封装程序中可变因素的重要手段。
(2)封装:通常认为封装 是把数据和操作数据的方法绑定 起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在 类中 编写的方法就是对实现细节的 一种封装;我们编写一个类 就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
(3)多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也成为前绑定),而方法重写(override)实现的是运行时的多态性(也成为后绑定)。运行时的多态是 面向对象最精髓的东西,要实现多态需要做两件事:1.方法重写(子类继承父类并重写父类中已有的或抽象的方法);2.对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
(4)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和 行为抽象两方面。 抽象值只关注对象有哪些属性和行为, 并不关注这些行为的细节是什么。
注意:默认情况下 面向对象有3大特性:封装、继承、多态,如果面试官让说出四大特性,那么把抽象 加上去。
2、访问权限修饰符public、private、protected,以及不写(默认)的区别
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | X |
default | √ | √ | X | X |
private | √ | X | X | X |
3、如果理解clone对象
3.1 为什么要用clone?
在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在 某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同的 新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A和B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值 语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。
3.2 new一个对象的过程和clone一个对象的过程区别
new操作符的本意是分配内存。程序执行到new操作符时,首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把它的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
clone在第一步是和new相似的,都是分配内存,调用clone方法时,分配的内存和原对象( 即调用clone方法的对象)相同,然后在使用原对象中的对应的各个域,填充新对象的域,填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
3.3 clone对象的使用
3.3.1 复制对象和复制引用的区别
Person p = new Person(23,"zhang");
Person p1 = p;
System.out.println(p);
System.out.printlin(p1);
当 Person p1=p; 执行 之后,是创建一个新的对象吗?首先看下打印结果:
com.lee.Person@2f9eelac
com.lee.Person@2f9eelac
可以看出, 打印的地址值是相同的,既然地址都是相同的,那么肯定是同一个对象。p和p1只是引用而已,它们都指向了一个相同的对象 Person(23,"zhang")。可以把这种现象叫做引用的复制。上面代码执行完成之后,内存的情景如下图 所示:
而下面的代码是真真正正的克隆了一个对象。
Person p = new Person(23,"zhang");
Person p1 = (Person)p.clone();
System.out.println(p);
System.out.println(p1);
从打印结果可以看出,两个对象的地址是不同的,也就是说创建了新的对象,而不是把原对象的地址 赋值给了一个新的引用变量:
com.lee.Person@2f9eee1ac
com.lee.Person@67f1fba0
以上代码执行完成后,内存中情景如下图所示:
3.3.2 深拷贝和浅拷贝
上面的示例代码中,Person有两个成员变量,分别是name和age,name是String类型,age是int类型。代码非常简单,如下图所示:
public class Person impelements Cloneable{
private int age;
private String name;
public Person(int age, String name){
this.age = age;
this.name = name;
}
public Person(){}
...省略get() set()方法
@Override
protected Object clone() throws CloneNotSupportedException{
return (Person)super.clone();
}
}
由于age是基本数据类型,那么对它的拷贝没有什么疑义,直接将一个4字节的整数值拷贝过来就行。但是name是String类型的,它只是一个引用,指向一个 真正的String对象,那么对它的拷贝 有两种方式:直接将原对象 中的name的引用值拷贝给新对象的name字段,或者根据原Person对象中的name指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的Person对象的name字段。这两种拷贝方式分别叫做浅拷贝和深拷贝。 浅拷贝和深拷贝的原理如 下图所示:
下面通过代码进行验证。如果两个Person对象的name的地址值相同,说明两个对象的name都指向同一个String对象,也就是浅拷贝,而如果 两个对象的name的地址值不同,那么说明指向不同的String对象,也就是在拷贝Person对象的时候,同时拷贝了name引用的String对象,也就是深拷贝。验证代码如下:
Person p = new Person(23,"name");
Person p1 = (Person)p.clone();
String result = p.getName()==p1.getName()?"clone是浅拷贝的":"clone是深拷贝的";
system.out.println(result);
打印结果 为:
clone是浅拷贝的
所以,clone方法执行的是浅拷贝,在编程的时候要注意这个细节。
如何进行深拷贝:
由 上一节的内容可以得出如下结论:如果想要深拷贝一个对象, 这个对象必须要实现Cloneable接口,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份,这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone 方法。那么,按照上面的结论,实现以下代码Body类组合了Head类,要想深拷贝Body类,必须在Body类的clone方法中将Head类也要拷贝一份。代码如下:
static class Body implements Cloneable{
public Head head;
public Body(){}
public Body(Head head){
this.head = head;
}
@Override
protected Object clone() throws CloneNotSupportException{
Body newBody = (Body)super.clone();
newBody.head = (Head)head.clone();
return newBody;
}
}
static class Head implements Cloneable{
public Face face;
public Head(){};
@Override
protected Object clone() throws CloneNotSupportException{
return super.clone();
}
}
public static void main(String[] args)throws CloneNotSupportException{
Body body = new Body(new Head(new Face()));
Body body = (Body)body.clone();
System.out.println("body == body1:"+(body == bady1));
System.out.println("body.head == body1.head:"+(body.head==body1.head));
打印结果为:
body == body1: false
body.head == body1.head : false