面向对象三大基本特征

我们说面向对象的开发范式,其实是对现实世界的理解和抽象的方法.面向对象的三大特性,分别是封装性、继承性和多态性.

封装

封装指的是将客观的事物抽象化为类,并且使类中的数据和方法针对操作对象进行管理.

继承

继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展.
通过继承创造的新类被称为”子类”或者”派生类”,被继承的类称为”基类””超类”或者”父类”.继承实际上就是将一般变为特殊的过程.

多态

多态指的是一个类实例的相同方法在不同情况下有不同的表现形式。多态为不同的数据类型的实体提供了统一的接口。

基本数据类型

Java 中有 8 种基本数据类型,分别为:

基本类型 位数 默认值
int 32 0
short 16 0
long 64 0L
byte 8 0
char 16 ‘u0000’
float 32 0f
double 64 0d
boolean 1 false

注意:

  1. Java里使用long类型的数据一定要在数值后面加上L,否则将作为整型解析。
  2. char a = ‘h’char : 单引号,String a = “hello” : 双引号。

这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean
包装类型不赋值就是Null,而基本类型有默认值且不是Null。

包装类型的常量池技术

首先是JVM的虚拟内存分布:
image.png

  • 程序计数器是jvm执行程序的流水线,存放一些跳转指令。
  • 本地方法栈是jvm调用操作系统方法所使用的栈。
  • 虚拟机栈是jvm执行java代码所使用的栈。
  • 方法区存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置。
  • 虚拟机堆是jvm执行java代码所使用的堆。

Java中的常量池,实际上分为两种形态:静态常量池运行时常量池
静态常量池主要存放字面量符号引用量
而我们常说的常量池就是运行时常量池。其将class文件内的常量池加载到内存中,保存在方法区里。运行时常量池的一大特点就是具有动态性,运行期间产生的常量也可以加入常量池中。
常量池的好处主要是节省内存空间和运行时间,避免大量重复的创建和销毁对象。

自动装箱与拆箱

  • 装箱:将基本类型用它们对应的引用类型包装起来。
  • 拆箱:将包装类型转换为基本数据类型。
    1. Integer i = 10; //装箱
    2. int n = i; //拆箱
    装箱实际上就是调用了包装类的valueOf方法,拆箱则是调用了xxValue的方法。由此上述代码等价于:
    1. Integer i = Integer.valueOf(10); //装箱
    2. int n = i.intValue(); //拆箱
    需要注意的是,如果频繁的拆装箱,会严重影响系统性能,一般需要避免。

    静态方法和实例方法

    在Java中,方法可以是静态的也可以是实例方法。其最大的不同在于是否需要在使用方法时进行对象的实例化。
    1. // 静态方法的创建
    2. public class Person{
    3. public void method(){
    4. //
    5. }
    6. public static void staticMethod(){
    7. //
    8. }
    9. public static void main(){
    10. //示例化对象person
    11. Person person = new Person();
    12. //调用实例方法
    13. person.method();
    14. //调用静态方法
    15. Person.staticMethod();
    16. }
    当然,在外部调用静态方法的时候,也可以使用对象.方法名的方式,但是我们一般不这样使用以避免造成混淆。
    静态方法在访问类成员的时候也存在限制, 只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。 其原因在于静态方法在类加载时就会分配内存,可以通过类名直接访问,而非静态成员需要对象实例化后才存在,属于非法访问。

    重载和重写

    重载和重写的区别

    重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理

重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法

重载

发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。

重写

重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。

  • 方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
  • 如果父类方法访问修饰符为private/final/static则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
  • 构造方法无法被重写。

重写和重载的区别特征其实就是,重写是内部逻辑变,外部名称和参数都不变;而重载则是同名不同参数。

hashCode() 与 equals()

首先要注意的是==和equals()的区别

对于基本数据类型来说,==比较的是值,而对于引用数据类型来说则是对象的地址。因此如果使用其来比较诸如String这样的数据类型就会出现一些问题。因此我们要使用equals()
equals() 方法存在两种使用情况:

  • 类没有覆盖 **equals()**方法 :通过equals()比较该类的两个对象时,等价于通过==比较这两个对象,使用的默认是 Object类equals()方法。
  • 类覆盖了**equals()**方法 :一般我们都覆盖equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回true.

当我们需要使用equals()去比较两个引用数据类型的值是否相同时,我们会在设计该类时重写该方法以达到目的。

为什么要有 hashCode?

hashCode()的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode()定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含有hashCode()函数。另外需要注意的是: Object 的 hashCode()方法是本地方法,也就是用 C 语言或 C++ 实现的,该方法通常用来将对象的内存地址转换为整数之后返回。
引入hashCode()的目的是为了比较对象是否重复出现,这点就和我们使用hashset去存储是一样的,通过键值对来快速查找是否存在。

为什么还要有equals?

原因是哪怕再好的hash算法,也会存在碰撞的现象。因此要针对碰撞的情况再判断两个对象是否相等。判断的大致流程如下:

  1. 如果两个对象的hashCode不相等,则肯定是不同的对象。
  2. 如果两个对象的hashCode相等,通过equals判断是否为同一对象。

    为什么重写equals时必须重写hashCode方法

    因为两个相等的对象的hashCode值必须是相等。也就是说如果equals方法判断两个对象是相等的,那这两个对象的hashCode值也要相等。
    如果重写equals()时没有重写hashCode()方法的话就可能会导致equals方法判断是相等的两个对象,hashCode 值却不相等。

    深拷贝和浅拷贝