1.面向对象基础面试题小结
面向对象基础
1 面向对象三大特点
封装、继承、多态
面向过程是将解决问题的过程拆分为一个个方法执行;面向对象是先抽象出对象,由对象执行方法的方式解决问题。
1)封装:将一个对象的属性封装在对象内部,不允许外部对象直接访问对象内部信息。
2)继承:不同类型对象,相互之间经常有一定共同点。具体表现为子类继承父类。
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法)。但是父类的私有属性和方法,子类只能拥有,无法访问。
- 子类可以拥有自己的属性和方法,即子类可对父类进行拓展。
- 子类可以自己方式实现父类方法(子类可重写父类方法)。
3)多态:一个对象具有多种状态,即提供一个统一的接口,使不同类型的对象可以实现同一个操作或方法。具体表现为父类引用指向子类实例。如Map<Integer, Integer>map = new HashMap<>();
父类引用是指使用父类类型声明一个变量map(map可引用父类实例或子类实例),父类引用存放在栈中;子类实例是指子类创建的具体对象如new HashMap(),子类实例存放在内存中。故声明的父类引用map指向子类实例new HashMap。
对象实例的相等表现为内存存放的内容是否相同;引用类型的相等表现为指向的内存地址是否相同。
特点:
- 对象类型和引用类型之间存在(继承)类/(实现)接口的关系;
- 父类引用可访问子类对象中继承父类的属性和方法,但父类无法访问子类特有的属性和方法;
- 编译器只识别父类引用,不考虑实际引用对象。运行期间会根据实际引用对象确定具体执行方法和属性;
- 如果子类重写父类方法,真正执行的是子类覆盖的方法,若子类没有覆盖父类方法,执行的是父类的方法。
2 构造方法
1)如果一个类没有声明构造方法,程序能正确运行吗
可以的,类的构造方法是为了初始化对象。类即使没有声明构造方法,也会有默认的不带参数的构造方法。
2)构造方法有什么特点?是否可被@override
特点:
- 名字与类相同;
- 无返回值,不能使用void声明构造函数;
- 生成类的对象时自动执行,无需调用;
构造方法不能被@override,但能被重载,如一个类中可以有多个构造函数。
3 重写和重载的区别
重写是指子类将父类本身的方法重写,在方法名、返回值类型,参数列表相同的情况下,子类对父类方法体进行重写。子类方法的访问修饰符不能低于父类。
访问修饰符等级排序:public > protected(同一包或不同包的子类都能访问) > default(同一包的所有类都能访问) > private(只能在当前类中被访问)
重载是指同一个类中,同名的方法有不同的参数列表(参数类型、参数个数、参数顺序等不同)。
4 接口和抽象类的区别
1)共同点
- 都不能被实例化
- 都能拥有抽象方法
- 都拥有默认方法(可理解为实现方法)Java8中通过default关键字在接口中定义默认方法),如MybatisPlus中Mapper接口中默认的增删改查方法,使得其存在部分实现方法,提高代码复用
Collection接口中的默认方法stream()获取流。
2)不同点
- 接口是对类的行为进行约束,即实现某接口的类型需要实现接口所有的方法,从而具备对应的行为能力;抽象类主要用于代码复用,强调所属关系(即抽象类和子类之间的继承关系,子类是抽象类的具体表现);
- 一个类只能继承一个父类,但可以实现多个接口;
- 接口中成员变量只能是public static final 类型的,不能被修改且必须有初始值;抽象类中成员变量默认是default,可在子类中重新定义,可被重新赋值;
- 接口中无构造函数,抽象类中可有构造函数;
final修饰的变量可称为常量,为保证常量的值在后续代码中不被修改,Java编译器要求其在声明时或构造方法时进行初始化赋值,否则会导致编译报错。
正确方式
1)声明时赋值
2)构造方式赋值
5 动态绑定和静态绑定
- 动态绑定:在运行时才能确定调用哪个函数或方法。当引用变量是一个基类类型,实际引用类型是派生类类型,动态绑定会根据对象实际类型调用对应方法或函数。动态绑定的作用是实现多态。
- 静态绑定:编译时即可确定调用哪个函数或方法。如一个类中有一个静态方法,在编译时就可确定调用该方法的地址,因此静态方法在编译时就确定的
6 深拷贝、浅拷贝、引用拷贝
浅拷贝:在堆上创建一个新的对象,如果对象内部是引用类型,则复制内部对象的引用地址,拷贝对象和原对象共用同一个内部对象;当其中一个对象修改了内部数据,另一个对象也会受到影响。
定义一个Address类和Person类实现Cloneable接口,重写clone()方法实现浅拷贝。
package com.ku.test.basic; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Person implements Cloneable{ private Address address; @Override public Person clone() throws CloneNotSupportedException { Person person = (Person)super.clone(); return person; } } package com.ku.test.basic; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Address implements Cloneable{ private String name; @Override public Address clone() throws CloneNotSupportedException { return (Address)super.clone();//强转为Address类型 } } package com.ku.test.basic; public class TestCopy { public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person(new Address("武汉")); Person personCopy = person.clone(); System.out.println(person.getAddress() == personCopy.getAddress()); } }
深拷贝:完全复制整个对象,包含对象所包含的内部对象;拥有各自的对象以及内存地址,两个对象相互独立。
package com.ku.test.basic; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor /** * 深拷贝 */ public class Person1 implements Cloneable{ private Address address; @Override public Person1 clone() throws CloneNotSupportedException { Person1 person1 = (Person1) super.clone(); person1.setAddress(person1.getAddress().clone()); return person1; } } 深拷贝
引用拷贝:两个不同的引用指向同一个对象
7 “==”和equals方法的区别
Java参数传递方式是基于值传递(引用拷贝,两个引用指向同一个对象),不管是基本数据类型还是引用数据类型,==比较的都是值,不过引用类型变量存储的值是内存地址。
区别:
- 对于基本数据类型,==比较的是值;
- 对于引用数据类型,==比较的是内存地址;
equals方法不能用于基本数据类型的比较,只能用于引用数据类型的比较。equals方法是Object类中的方法,Object是所有类的直接父类或间接父类,因此每个类中都存在equals方法。
public boolean equals(Object obj){
return (this == obj);
}
equals存在两种使用情况:
- 类如果没有重写equals方法,那么equals方法相当于==,比较的是两个对象的内存地址;
- 类如果重写了equals方法,那么equals方法比较的是对象的值;
当创建String类型的对象时,虚拟机会在常量池中查找是否存在值与创建的值相同的对象;如果存在将其赋予当前引用,如果不存在则在常量池中创建一个新的String对象。
public boolean equals(Object anObject) { if (this == anObject) {//地址相等则值相等,引用拷贝 return true; } if (anObject instanceof String) {//如果每个字符相等则值相等 String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } String中重写的equals方法
8 hashcode()方法
hashcode()的作用是获取哈希码(int 整形),也被称为散列码。哈希码是确定对象在哈希表中的索引位置。
hashcode()方法是Object中的本地方法,即所有类都存在hashcode()方法。
散列表存储的是键值对(key-value),特点是能够根据键快速检索出值。其中利用到了哈希码。
为什么要拥有hashcode()方法呢?
以HashSet检查重复为例,当对象加入HashSet时,HashSet会计算对象的hashcode判断对象插入的位置,同时也会将其与已存在对象的hashcode比较。如果不相等,则根据hashcode将对象散列到HashSet中;如果存在重复的hashcode,那么通过equals方法检查hashcode相等的两个对象是否相同,若相同则Hash不会让其加入;若不同则散列到其他位置。由于equals方法是通过重写来实现对象值的比较,而对象值的比较是需要比较对象每个成员变量的值;若成员变量较多或比较逻辑较复杂,那么equals方法开销较大。这样大大减少equals()方法比较的次数,提高了执行速度。
- 两个对象的hashcode不相等,两个对象一定不相等;
- 两个对象的hashcode相等,两个对象不一定相等(哈希冲突);
- 两个对象的hashcode相等且equals方法返回true,那么两个对象相等;
为什么重写equals()方法一定要重写hashcode()方法?
- 如果两个对象相等,那么他们的hashcode一定相等。如果两个对象的equals方法返回true,那么两个对象的hashcode相等。
- 如果重写equals方法()时,没有重写hashcode方法,则equals()方法判断相等的对象,它们的hashcode不相等;
9 字符串常量池
字符串常量池是JVM为提升性能,减少内存消耗针对字符串(String类)专门开辟的一块区域,目的是避免字符串的重复创建。
String s = new String("abc")会创建几个字符串对象?
- 如果创建的字符串在字符串中不存在指向它的引用,那么首先在字符串常量池中创建一个指向该字符串的引用,然后在堆中通过new String()创建一个对象。一共创建两个对象。
- 如果创建的字符串在字符串常量池中存在指向它的引用,那么new String()会在堆中创建一个对象。
String 中intern()方法的作用
intren()方法是一个native(本地)方法,作用是将指定字符串对象的引用保存在字符串常量池中,分为两种情况:
- 如果字符串常量池中保存了指定字符串的引用,则直接返回;
- 如果字符创常量池中没有保存指定字符串的引用,那么在字符串常量池中创建一个指向指定字符串的引用并返回;