面向对象的特征
封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象.
封装 就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别,将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
继承是面向对象的基本特征之一,继承机制允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
多态同一个行为具有多个不同表现形式或形态的能力。是指一个类实例(对象)的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽
象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的
细节是什么。
多态的好处
允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用).主要有以下优点:
- 可替换性:多态对已存在代码具有可替换性.
- 可扩充性:增加新的子类不影响已经存在的类结构.
- 接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的.
- 灵活性:
- 简化性:
虚拟机是如何实现多态的
动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法.如果你知道Hotspot中oop-klass模型的实现,对这个问题就了解比较深了.
接口的意义
接口的意义用三个词就可以概括:规范,扩展,回调.
接口和抽象类的区别
比较点 抽象类 接口
默认方法 抽象类可以有默认的方法实现 java 8之前,接口中不存在方法的实现
实现方式 子类使用extends关键字来继承抽象类.如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现 子类使用implements来实现接口,需要提供接口中所有声明的实现.
构造器 抽象类中可以有构造器 接口中不能
和正常类区别 抽象类不能被实例化 接口则是完全不同的类型
访问修饰符 抽象方法可以有public,protected和default等修饰 接口默认是public,不能使用其他修饰符
多继承 一个子类只能存在一个父类 一个子类可以存在多个接口
添加新方法 抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 如果往接口中添加新方法,则子类中需要实现该方法
父类的静态方法能否被子类重写?
不能.重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法,我们一般称之为隐藏.
在类中使用static修饰的静态方法会随着类的定义而被分配和装载入内存中;而非静态方法属于对象的具体实例,只有在类的对象创建时在对象的内存中才有这个方法的代码段。
注意: 非静态方法既可以访问静态数据成员 又可以访问非静态数据成员,而静态方法只能访问静态数据成员;
非静态方法既可以访问静态方法又可以访问非静态方法,而静态方法只能访问静态数据方法
什么是不可变对象?好处是什么?
不可变对象指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象,如 String、Integer及其它包装类.不可变对象最大的好处是线程安全.
静态变量和实例变量的区别?
静态变量存储在方法区,属于类所有.实例变量存储在堆当中,其引用存在当前线程栈.需要注意的是从JDK1.8开始用于实现方法区的PermSpace被MetaSpace取代了.
java 创建对象的几种方式
java中提供了以下四种创建对象的方式:
- new创建新对象
- 通过反射机制
- 采用clone机制
- 通过序列化机制
前两者都需要显式地调用构造方法. 对于clone机制,需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在java中序列化可以通过实现Externalizable或者Serializable来实现.
Object中有哪些公共方法?
equals(),clone(),getClass(),notify(),notifyAll(),wait(),toString
java中==和eqauls()的区别?
-
==是运算符,用于比较两个变量是否相等,对于基本类型而言比较的是变量的值,对于对象类型而言比较的是对象的地址.
-
equals()是Object类的方法,用于比较两个对象内容是否相等.默认Object类的equals()实现如下:
public class Object {
…
public boolean equals(Object obj) {
return (this == obj);
}
…
}
不难看出此时equals()是比较两个对象的地址,此时直接==比较的的结果一样.对于可能用于集合存储中的对象元素而言,通常需要重写其equals()方法.
a==b与a.equals(b)有什么区别
如果a 和b 都是对象,则 a==b 是比较两个对象内存地址,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true.而 a.equals(b) 是进行内容比较,其比较结果取决于equals()具体实现.多数情况下,我们需要重写该方法,如String 类重写 equals()用于两个不同对象,但是包含的字母相同的比较
Object中的equals()和hashcode()的联系
hashCode()是Object类的一个方法,返回一个哈希值.如果两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值;如果两个对象根据eqaul()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的.)
a.hashCode()有什么用?与a.equals(b)有什么关系
hashCode()方法是为对象产生整型的 hash 值,用作对象的唯一标识.它常用于基于 hash 的集合类,如 Hashtable,HashMap等等.根据 Java 规范,使用 equal()方法来判断两个相等的对象,必须具有相同的 hashcode.
将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合.如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的任意对象是否相等:如果equal()判断不相等,直接将该元素放入集合中,否则不放入.
有没有可能两个不相等的对象有相同的hashcode
有可能.在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值.当hash冲突产生时,一般有以下几种方式来处理:
- 拉链法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储.
- 开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
- 再哈希:又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个….等哈希函数计算地址,直到无冲突.
可以在hashcode中使用随机数字吗?
不行,因为同一对象的 hashcode 值必须是相同的.
一个子类对象创建时,jvm中加载的处理的顺序
& 和 &&的区别
基础的概念不能弄混:&是位操作,&&是逻辑运算符.需要记住逻辑运算符具有短路特性,而&不具备短路特性.来看看一下代码执行结果?
public class Test{
static String name;
public static void main(String[] args){
if(name!=null&userName.equals("")){
System.out.println("ok");
}else{
System.out.println("erro");
}
}
}
上述代码将会抛出空指针异常.原因你懂得.
内部类有什么作用?
内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立.在单个外围类当中,可以让多个内部类以不同的方式实现同一接口,或者继承同一个类.创建内部类对象的时刻不依赖于外部类对象的创建.内部类并没有令人疑惑的”is-a”关系,它就像是一个独立的实体.此外,内部类提供了更好的封装,除了该外围类,其他类都不能访问.
深拷贝和浅拷贝的区别是什么?
- 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.
- 深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都复制了一遍.
解释内存中的栈(stack)、堆(heap)和方法区(method area)
的用法。
答:
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的
现场保存都使用 JVM 中的栈空间;
而通过 new 关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为 Eden、Survivor(又可分为 From Survivor 和 To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被 JVM 加载的类信息、常量、静态变量、JIT 编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的 100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过 JVM的启动参数来进行调整,栈空间用光了会引发 StackOverflowError,而堆和常量池空间不足则会引发 OutOfMemoryError。String str = new String(“hello”);上面的语句中变量 str 放在栈上,用 new 创建出来的字符串对象放在堆上,而”hello”这个字面量是放在方法区的。
补充 1:较新版本的 Java(从 Java 6 的某个更新开始)中,由于 JIT 编译器的发
展和”逃逸分析”技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象一
定分配在堆上这件事情已经变得不那么绝对了。
补充 2:运行时常量池相当于 Class 文件常量池具有动态性,Java 语言并不要求
常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String
类的 intern()方法就是这样的。
数组有没有 length()方法?String 有没有 length()方法?
数组没有 length()方法,有 length 的属性。String 有 length()方法。JavaScript
中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆
构造器(constructor)是否可被重写(override)?
答:
构造器不能被继承,因此不能被重写,但可以被重载。
19、String 和 StringBuilder、StringBuffer 的区别?
答:
Java 平台提供了两种类型的字符串:String 和 StringBuffer/StringBuilder,它
们可以储存和操作字符串。其中 String 是只读字符串,也就意味着 String 引用的
字符串内容是不能被改变的。而 StringBuffer/StringBuilder 类表示的字符串对象
可以直接进行修改。StringBuilder 是 Java 5 中引入的,它和 StringBuffer 的方
法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被
synchronized 修饰,因此它的效率也比 StringBuffer 要高。
面试题 1 - 什么情况下用+运算符进行字符串连接比调用
StringBuffer/StringBuilder 对象的 append 方法连接字符串性能更好?
面试题 2 - 请说出下面程序的输出。
class StringEqualTest {
public static void main(String[] args) {
String s1 = “Programming”;
String s2 = new String(“Programming”);
String s3 = “Program”;
String s4 = “ming”;
String s5 = “Program” + “ming”;
String s6 = s3 + s4;
System.out.println(s1 == s2);
System.out.println(s1 == s5);
System.out.println(s1 == s6);
System.out.println(s1 == s6.intern());
System.out.println(s2 == s2.intern());
}
}
补充:解答上面的面试题需要清除两点:1. String 对象的 intern 方法会得到字符
串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与 String 对象
的 equals 结果是 true),如果常量池中没有对应的字符串,则该字符串将被添加
到常量池中,然后返回常量池中字符串的引用;2. 字符串的+操作其本质是创建
了 StringBuilder 对象进行 append 操作,然后将拼接后的 StringBuilder 对象用
toString 方法处理成 String 对象,这一点可以用 javap -c StringEqualTest.class
命令获得 class 文件对应的 JVM 字节码指令就可以看出来
重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
答:
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,
而后者实现的是运行时的多态性。
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。
面试题:华为的面试题中曾经问过这样一个问题 - “为什么不能根据返回类型来
区分重载”,快说出你的答案吧
描述一下 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 所指定的目
录中记载类,是用户自定义加载器的默认父加载器。
抽象类(abstract class)和接口(interface)有什么异同?
答:
抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如
果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实
现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中
可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其
中的方法全部都是抽象方法。抽象类中的成员可以是 private、默认、protected、
public 的,而接口中的成员全都是 public 的。抽象类中可以定义成员变量,而接
口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而
抽象类未必要有抽象方法