面向对象与面向过程
面向过程:将问题分解成步骤,根据步骤实现函数,执行时依次调用函数,数据和对数据的操作是分离的。
面向对象:将问题分解成对象,描述对象在解决问题的步骤中的行为,对象与属性和行为是关联的。
面向过程的优点:性能高,不需要面向对象的实例化;
缺点:代码不易维护,扩展和复用
面向对象的优点:代码易维护、复用和扩展,可以设计出低耦合的系统
缺点:需要实例化对象,性能比面向过程低
JAVA技术体系
- java程序设计语言
- java虚拟机,JVM
- java类库API
- CLass文件格式
- 来自商业机构和开源社区的第三方类库API
把java程序设计语言、Java虚拟机、Java类库这三部分统称为JDK,是支持java程序开发的最小环境。
把Java类库中的Java SE API子集和java虚拟机两部分成为JRE,是支持java程序运行的标准环境。
Java与C++的区别
- 面向对象语言,java平台无关性
- java不提供指针直接访问内存,更安全
- java的类是单继承,C++支持多继承,但java的接口可以多继承
- java有自动内存管理机制,不需要程序员手动释放内存
- c语言中,字符串和字符数组都会有一个额外的字符"\0"表示结束
重载与重写
重载(编译时多态):在同一个类中,方法名相同,但参数不一致,那么可以说,一个方法是另一个方法的重载方法。
参数不一致:参数类型,个数,顺序不同
重写(运行时多态):⼦类继承⾃⽗类的相同⽅法,参数和返回类型和父类的方法保持一致,内部逻辑可以改变。
**注:**如果⽅法的返回类型是void和基本数据类型,则返回值重写时不可修改。但是如果⽅法的返回值是引⽤类型,重写时是可以返回该引⽤类型的⼦类的。
区别:
Java面向对象编程的三大特性:封装 继承 多态
. java三大特性之一封装
1. 封装的定义?
本质是信息隐藏;就是把对象的属性和操作结合成一个独立的整体,并尽可能隐藏对象的内部实现细节,仅保留一些接口允许外界访问内部数据。
2. 为什么需要封装?哪种业务场景里使用到封装?
(1)封装消除了传统结构方法中数据与操作分离带来的种种问题,提高了程序的可复用性和可维护性(便于修改);
(2)通过封装可以实现对数据的访问权限控制,提高了程序的安全性。
3. 如何使用封装?
java提供了四种访问权限,通过使用访问控制符修饰数据或者方法来实现封装;
所谓的访问,可以分为两种不同方式:第一,通过对象实例访问;第二,直接访问。
如果能直接放问,那一定也可以通过创建对象实例访问。
**~public:**公开的访问权限:可以跨包访问;
~default:默认访问权限(不加任何访问控制符):可以同一个包内访问;
~protected:受保护的访问权限,可以被子类访问(跨包也可以);
~private:私有的访问权限,只能在当前类中访问;
. java三大特性之二:继承
1. 继承的定义
继承是从已有的类中派生出新的类,新的类吸收已有类的数据属性和行为,并能扩展新的能力。
2. 为什么需要继承?哪种业务场景里使用到继承?
(1)继承是在一般的类基础上进行构造、建立和扩充新类的最有效手段;
(2)继承能够清晰地体现相关类建的层次结构关系,简化人们对事物的认识和描述。
(3)继承复用了基类的数据和方法,减少代码冗余,提升了代码可复用性、可维护性。
(4)处于完全性和可靠性的考虑,java只支持单继承;
3. 如何使用继承?
extends
. java三大特性之三:多态
1. 多态的定义
多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口;
多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。
抽象一点描述:多态是同一个行为具有多个不同表现形式或形态的能力。从编程的角度上来看,多态一般就是指同一个接口,使用不同的实例而执行不同操作,如图所示
2. 多态的优势
1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
2.可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
3.接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
4.灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
5.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
多态的好处:可以使程序有良好的扩展,并可以对所有多态类的对象进行通用处理,简化代码的编写。
3. 如何使用多态?
继承(多个⼦类对同⼀⽅法的重写)和接⼝(实现接⼝并覆盖接⼝中同⼀⽅法)。
多态存在的三个必要条件
1、继承
2、重写
3、父类(接口类型)引用指向子类(实现类)对象:Parent p = new Child();
String StringBuffer 和 StringBuilder 的区别是什么?String 为什么是不可变的?
String类中使用final关键字修饰字符数组,所以不可变;而StringBuffer 和 StringBuilder没有用final关键字修饰;
String中的对象是不可变的常量,线程安全;StringBuffer对字符串的操作方法加了同步锁,所以是线程安全的;StringBuilder并没有对方法加同步锁,非线程安全。
对于三者使⽤的总结:
- 操作少量的数据: 适⽤ String
- 单线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuilder
- 多线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuffer
自动装箱与拆箱
- 装箱:将基本类型用它们对应的应用类型包装起来
- 拆箱:将包装类型转换为基本数据类型
在⼀个静态⽅法内调⽤⼀个非静态成员为什么是非法的?
由于静态⽅法可以不通过对象进⾏调⽤,因此在静态⽅法⾥,不能调⽤其他⾮静态变量,也不可以访问⾮静态变量成员。
static关键字
- 修饰变量,这个静态变量不需要依赖于对象来进行访问,只要类被加载就可以通过类名去访问,这个成员会被该类的所有实例对象所共享。
- 修饰方法,静态方法也是不需要依赖于对象来进行访问,静态方法中不能调用非静态变量和方法。
- 修饰代码块优化程序性能,不能调用非静态变量和方法。
抽象类和接口的区别是什么?
抽象类
定义:如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类;抽象类可以构造出一个固定的一组行为的抽象描述,这组行为能够有任意个可能的具体实现方式。
特点:
- 抽象类不能被实例化,子类必须重写父类的所有抽象方法才能实例化,否则就必须声明为抽象类。
- 抽象类作为一种类的模板,不可修改但支持扩展,是实现面向对象设计原则中开-闭原则(OCP - Open-Closed Principle ) 的关键所在
- 含有抽象方法的类一定是抽象类,但是抽象类不一定含有抽象方法;
接口
定义:在JAVA编程语言中是一个抽象类型,是抽象方法的集合;
特点:
- 抽象类不能被实例化,实现类必须实现接口内所描述的所有方法才能实例化,否则就必须声明为抽象类。
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract;
注:JDK8及以后,允许我们在接口中定义static方法和default方法,并且有方法体;JDK 1.9 以后,允许将方法定义为 private,并且有方法体。使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9 私有接口方法。 - 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量。
接口与类的相同与异同
相同点:
- 都不能被初始化,继承类或实现类必须重写或实现所有抽象方法才能初始化,否则必须声明为抽象类。
异同点:
- 从设计层⾯来说,普通的类是对对象的抽象,抽象类是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。
- 抽象类体现的是一种继承关系(继承的优点),父类与派生类本质上是一种事物;而接口体现的是一种功能绑定关系,实现类实现接口方法相当于绑定了某个功能;用门这个例子来说明:
将门作为抽象概念,包含开门和关门两个动作,可以用抽象类或者接口来定义一个表示该抽象概念的类型;如果现在要求Door还要具有报警的功能,一种解决方案是在门的抽象类或者接口中加入报警方法,但这样违背了接口分离原则ISP(Interface Segregation Priciple),Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。导致接口太胖或者说是接口污染;
门的开、关门动作与报警器的行为是两种抽象概念,因此需要拆分成两个抽象类型,如果拆分成两个抽象类来定义,显然不行,Java语言不支持多继承;如果拆分成两个接口来定义,原则上可行,但是不够合理。因为报警门在概念本质上还是门,只是具备报警的功能;因此,门的开、关门动作作为基础固有的行为可以用抽象类来定义,而报警功能则用接口来定义。 - 接⼝的⽅法默认是 public ,所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认实现),⽽抽象类可以有⾮抽象的⽅法。
- 接⼝中除了 static 、 final 变量,不能有其他变量,⽽抽象类中则不⼀定。
- ⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。接⼝⾃⼰本身可以通过 extends 关键字扩展多个接⼝。
- 接⼝⽅法默认修饰符是 public ,抽象⽅法可以有 public 、 protected 和 default 这些修饰符(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)。
对象的相等与指向他们的引⽤相等,两者有什么不同?
对象的相等,⽐的是内存中存放的内容是否相等。⽽引⽤相等,⽐的是他们指向的内存地址是否相等
== 与 equals(重要)
== : 它的作⽤是判断两个对象的地址是不是相等。即判断两个对象是不是同一个对象(基本类型的双等号比较的是值,应用数据类型的双等号比较的是内存地址)
equals:作用是判断两个对象是否是同一个对象,一般有两种情况
- 类没有重写equals方法,这时候等价于通过双等号比较两个对象
- 类重写了equals方法,一般用来比较两个对象的内容是否相等
hashCode 与 equals (重要)
⾯试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写hashCode ⽅法?
1)hashCode()介绍:
作用是获取哈希码,返回的是一个int整数,哈希码的作用是确定该对象在哈希表中的索引位置。
2)为什么要用hashcode
以HashSet如何检查重复为例子来说明:
每加入一个对象,会先计算hashcode的值来判断对象加入的位置,如果有相符的hashcode,则调用equals方法判断两者是否真的相同,如果相同加入不成功,如果不同,就重新散列到其他位置
3) 为什么重写 equals 时必须重写 hashCode ⽅法?
为了保证:如果两个对象相等,则 hashcode ⼀定也是相同的。两个对象相等,对两个对象分别调⽤ equals⽅法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不⼀定是相等的 。因此,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖。
4) 为什么两个对象有相同的 hashcode 值,它们也不⼀定是相等的?
因为存储的时候发生碰撞,所谓碰撞也就是指的是不同的对象得到相同的 hashCode 。
ArrayList的扩容机制简述
- 初始状态下,容器的大小要么为0要么为自己设定的某个值。
- 调用add()方法时,在添加元素到集合中前会先将集合实际元素个数size+1,判定其是否大于数组长度elementDate.length(数组的最大容量,判定是否有数组溢出的风险),如果有则触发扩容机制,扩容机制算法是基于方法grow()的:
- 先判定数组大小。数组是空或者小于10,那么在扩容的时候将数组直接分配大小到10。
- 之后的每次扩容,是变成原数组长度的1.5倍:
- (和实际所需容量、minCapacity相比)如果1.5倍太小的话,则新的容量扩容成实际所需的容量大小(则将实际所需的容量大小(minCapacity)赋值给newCapacity)。
- (和实际所需容量、minCapacity相比)如果1.5倍太大的话,比较实际所需容量大小与MAX_ARRAY_SIZE的大小,如果大,则新的容量扩容成Integer.MAX_VALUE,否则扩容成MAX_ARRAY_SIZE。
Ps:数组的最大尺寸为2^31 = 2147483648,但是需要8bytes的存储大小表示数组的长度等元数据。所以数组的大小定义为Integer.MAX_VALUE - 8。
数组对象的元数据:
Class:只想描述对象类型的类信息的指针。
Flag:描述对象状态的标志的集合,包括对象的散列码以及对象的形状(即对象是否为数组)。
Lock:对象的同步信息(即对象当前是否同步)。
Size:数组的大小。
HashMap 的⻓度为什么是 2 的幂次⽅
- HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,实现这个算法就是取模
- 哈希值对length取余:hash%length,但计算机中直接求余效率不如位运算
- 优化方案是 用hash&(length-1)来代替hash%length,
- hash&(length-1) == hash%length 的前提是length是2的n次方;
final修饰局部变量的问题
- 基本类型:基本类型的值不能发生改变。
- 引用类型:引用类型的地址值不能发生改变,但是,该对象的堆内存的值是可以改变的。
值得注意的是,等号赋值的操作 应用在基本数据类型上相当于是将值改变;而应用在引用数据类型上相当于是将地址改变。