核心 Java/Java 理论目录

2025-06-07

核心 Java/Java 理论

目录

Java 理论知识

这里的内容并非原创。这是我对以下资源的总结
。参考资料:Edureka 的
这篇Medium 文章(以及本系列的其他文章)都非常棒。

in28Minutes github - 这是面试准备的绝佳资源。(不过,请仔细核对其他来源。我发现他们对封装、非检查异常与检查异常的解释很奇怪。)

其他优质资源:

javarevisited是深入探讨问题的好资源。它尤其适合解决一些比较难/晦涩的问题,通过深入探究,可以让你理解得更清楚。

https://www.edureka.co/blog/interview-questions/oops-interview-questions/

OOP 快速摘要:
https://blog.udemy.com/oops-interview-questions/
https://blog.udemy.com/oop-interview-questions/

目录

Java相关内容

Java 语法

堵塞

-静态 VS 实例块

包装器类

字符串

对象类/Java 对象基础

数据结构实现(knpcode.com 有非常好的 DS 实现教程和文章

Java 实现 / 接口 / 通用类(由于没有时间,因此仅介绍通用实现

  • 地图实现
    • 哈希表
    • 树形图
    • LinkedHashMap
  • 集合实现
    • 哈希集
    • 树集
    • LinkedHashSet
  • 列表实现
    • 数组列表
    • 链表
  • 队列实现(见上文)
    • 优先队列
    • 链表
  • 出队实现(双端队列)(跳过此部分
    • 链表
    • 数组出队

OOP 概念和 Java 中的 OOP(本节主要参考教育文章:OOPJava 中的 OOP

Java 异常处理(JournalDev 异常处理文章主要用作本节的参考

OOP概念

  • SOLID(https://www.baeldung.com/solid-principles
    • 单一职责
    • 开放扩展,禁止修改
    • 利绍夫替换(基类的子类)
    • 接口隔离(拆分大接口,不拆分小接口)
    • 依赖反转(依赖于抽象类/接口而不是类)

UML

  • 需要了解不同的设计模式
  • 类图
    • 协会
    • 继承/泛化/抽象
    • 多重性
  • 序列图

设计模式

多线程

静态类型

参考:Oracle 文档

  • 在编译时执行类型检查
    • 提供类型安全
    • 如果 Java 代码包含错误,则编译将失败,直到错误被修复
  • 在使用变量之前声明其数据类型
    • int num; num = 5;对比num =5
    • 要求先声明变量,然后才能为其赋值。

静态类型的对立面是动态类型(例如 python)

基于类

参考:mozilla 开发者文档stack overflowzendeskwikipedia

基于类的编程/面向类是面向对象编程 (OOP) 的一种风格。
基于类的编程:区分对象和类。
基于原型的编程:没有区别。它有对象。从一个实例/对象开始并对其进行修改。

  • 定义类
    • 在类定义中定义类并指定方法(构造函数)来创建类的实例。
    • 原型:类定义与构造函数不可分离。构造函数创建具有初始属性和值集的对象
  • 子类/继承
    • 通过类定义创建层次结构Manager extends Employee
    • 原型:将对象与任何构造函数关联。(在子类的构造函数中调用另一个对象)
  • 添加和删​​除属性
    • 类定义后无法更改属性的类型/数量
    • 编译时创建的类和编译或运行时实例化的对象
    • 原型:可以在运行时删除或添加任何对象的属性。

与基于类相反的是基于原型/基于对象(例如 javascript)

什么是面向对象编程

参考:wiki & educative & educative - Oop JS

面向对象编程(OOP) 是一种基于“对象”概念的编程范式
。 程序被分解成相互通信的对象
片段。 每个对象由以下部分定义:

  • 它的功能(称为方法
  • 它的数据(称为属性

这是 OOP 的概述。如上一节所示,OOP 也有不同的“风格”(基于对象和基于类)。

从一些文章中我们还可以看到,“是 OOP 语言吗?”这个问题并不是一个直接的“是”或“否”的答案。

要成为面向对象编程语言 (OOP),编程语言必须满足一系列 OOP 条件/属性。根据编程语言是否满足部分/全部 OOP 条件,我们可以说“是”或“否”。
即使答案是“是”,编程语言也是面向对象的。仍然存在一个问题:“它是纯粹面向对象的吗?”

替代方案/对立面
面向对象编程没有“对立面”。但存在不同的编程范式,例如:(参考:SO )

  • 程序
  • 功能性等等

Java 不是纯 OOP

参考:GeeksforGeeksdataflair

Java不是纯粹的面向对象语言。因为它具有:

  1. 原始数据类型
  2. static 关键字
  3. 包装类

为了实现纯 OOP,语言必须满足所有 7 个条件:

  1. 封装/数据隐藏
  2. 遗产
  3. 多态性
  4. 抽象
  5. 所有预定义类型都是对象
  6. 所有用户定义类型都是对象
  7. 对对象执行的所有操作必须仅通过对象公开的方法

Java 不满足条件 5(所有预定义类型都是对象)和 7(对对象执行的操作必须仅通过对象公开的方法进行

  • 原始数据类型
  • static 关键字
    • 当类是静态的时,它可以在没有对象的情况下使用
    • 静态函数/变量。无需创建类的对象即可调用。
  • 包装类
    • 拆箱:在包装类上使用算术运算符(% 或 +=)时,编译器会在运行时将对象转换为原始数据类型。因此违反了条件 5(原始类型不是对象)。
    • 对象上的算术运算String test = "hello" + " world"在包装类上使用算术运算也违反了条件 7(可以通过调用其方法与对象进行通信)

Java 平台独立

Java 是 WORA(一次编写,随处运行)。Java 源代码可以在所有操作系统上运行。javac
编译器生成的字节码(.class)可以在任何操作系统上执行。字节码可以被任何安装在任何操作系统上的 JVM 理解。

JAVA是平台无关的语言,JVM是平台相关的,
不同的OS必须安装不同的JVM。

JVM 会将字节码(.class)转换为机器码,
由于运行的是机器码,因此这使得运行时间更快。

参考:javatpoint & codingningjas & guru99

Java:JVM、JRE、JDK

替代文本

JDK:Java 开发工具包
软件开发环境,用于制作小程序和 Java 应用程序。

  • JRE + 编译器 + 调试器JRE:Java 运行时环境,用于运行其他软件。要运行 Java,需要 JRE。它包含类库、加载器类和 JVM。
  • JVM + 库 + 其他组件(运行小程序/ Java 应用程序)JVM
  • 运行java字节码的虚拟机
  • maeks java 便携式
  • 带有 JIT(Just-In-Time)编译器,可将 Java 源代码转换为低级机器语言。

参考:guru99 & javatpoint & geeksforgeeks

Java JIT 编译

即时(JIT)编译,也称为动态编译
程序在运行时被编译为本机代码,以提高性能。

在 JIT 编译中,

  • 将字节码(.class)转换为正在运行的机器的机器码指令
    • 生成的机器代码针对运行机器的 CPU 架构进行了优化
  • 编译是在程序运行时完成的(而不是在执行之前)
  • 由于编译发生在运行时,JIT 编译器可以访问动态运行时信息,从而实现更好的优化

希望运行机器代码的效率能够克服每次运行时重新编译程序的低效率。

优化
JVM 执行字节码并计算函数执行次数,
如果次数超过预定义的限制,JIT 会将代码编译成处理器可直接执行的机器语言。

下一次,函数被计算,编译后的代码再次执行,与正常的解释不同 --> 执行速度更快

参考:freeCodeCamp & GeeksforGeeks & javatpoint

Java中的内存管理

内存管理是对象分配和释放的过程。Java 使用称为垃圾收集器的自动内存管理系统。

参考:javatpoint & oracle docs & peter lee medium

Java:堆空间 VS 栈内存

为了以最佳方式运行应用程序,JVM 将内存分为堆栈和堆内存
堆栈

  • 用于执行线程
  • 特定于方法的原始值
  • 从方法中引用堆中的对象。
  • 线程安全:每个线程都在自己的堆栈堆中运行:
  • 实际物体
  • 运行时为 Java 对象和 JRE 类分配动态内存
  • 字符串池
  • 非线程安全:需要通过适当的同步代码来保护

新对象总是在堆空间中创建,并且对这些对象的引用存储在栈内存中。当堆满了之后,就会进行垃圾回收。不再使用的对象会被清除,从而为新对象腾出空间。

    • 在 JVM 启动时创建,在应用程序运行时可能会增大/减小
    • 当堆满了的时候,垃圾就会被收集
    • 堆空间中创建的新对象对存储在堆栈内存中的对象的引用
    • 对象具有全局访问权限,可以从应用程序中的任何位置访问,不是线程安全的
    • 分为 3 个部分
    • 年轻一代:所有新对象都已分配并老化。
    • 老生代/终身代:存储长期存活的对象。对象存储在年轻代,设置其年龄阈值。当达到阈值时,对象将移动到老生代。
    • 永久生成:运行时类和应用程序方法的 JVM 元数据。
    • 当堆满了的时候,垃圾就会被收集
    • 按后进先出顺序引用
    • 当调用新方法时,会在堆栈顶部创建新块,其中包含特定于该方法的值(例如原始变量和对对象的引用)
    • 方法执行完毕 -> 相应的堆栈框架被刷新,流程返回到调用方法。剩余空间可用于下一个方法
package com.journaldev.test;

public class Memory {

    public static void main(String[] args) { // Line 1
        int i=1; // Line 2
        Object obj = new Object(); // Line 3
        Memory mem = new Memory(); // Line 4
        mem.foo(obj); // Line 5
    } // Line 9

    private void foo(Object param) { // Line 6
        String str = param.toString(); //// Line 7
        System.out.println(str);
    } // Line 8

}

Enter fullscreen mode Exit fullscreen mode

替代文本

  1. 运行程序 -> 将运行时类加载到堆空间
  2. 在第 1 行找到 main() 方法,运行时会创建堆栈内存,供 main() 方法线程使用。每当创建新对象时,
  3. 在堆内存中创建的对象
  4. 堆栈内存包含对它的引用。
  5. 当调用新方法时 ->在堆栈顶部创建新块(后进先出)
  6. 字符串,栈内存中的引用 -> 引用指向堆空间中的字符串池
  7. 当方法(foo())终止时,堆栈变为空闲(为函数创建的堆栈内存被销毁)
  8. 移至下面的下一个程序 (main())

替代文本

参考:stackify & baledung & JournalDev

Java垃圾收集

GC:Java 自动执行内存管理的过程。当 Java 程序在 JVM 上运行时,对象会在堆上
创建。当某些对象不再需要时,GC 会找到并删除它们以释放内存。

4 个垃圾收集器:如果被要求,计划轰炸它

  • 串行
  • 平行线
  • 并发标记清除
  • G1(h=垃圾优先)

好处

  • 自动处理未使用的对象/无法触及的对象的删除,以释放虚拟内存资源。

参考:stackify

Java:与 Python 的比较

  • Java 程序预计运行速度将比 Python更快

    • Python 调整输入时间,Python 运行时比 Java“更努力”(需要执行更多任务)
    • python 运行时必须检查对象并找到类型,然后调用操作
  • Java程序需要更长的开发时间

  • Java 程序比 Python 程序更长(代码行数)

    • java 是静态类型的

参考:python org

对象和类

:用户定义的蓝图,从中创建对象
对象:OOP 的基本单位,类的实例。

对象可以通过构造函数从类中创建。有两种

  • 默认/非参数化
    • 0 个参数
  • 参数化
    • 构造函数需要传递特定数量的参数

构造函数不能是抽象的、最终的或静态的

变量和方法

变量的类型

  • 当地的
  • 实例
  • 静止的

每个对象都有自己的实例变量副本,但是所有对象只共享一个静态变量副本

替代文本
替代文本

方法
方法包括

  • 修饰符
    • 例如私人/公共
  • 返回类型
  • 方法名称
  • 参数
  • 方法体/逻辑

静态方法属于类,无需实例化对象即可使用该方法

方法签名

方法签名<method name>()<argument types>)
包括:访问修饰符和返回类型

在 Java 中,同一个类中不能有 2 个以上具有相同方法签名的方法(即使方法具有不同的返回类型)

public void display(String info){
  System.out.println(info);
}

// ERROR: duplicate method Signature
public String display(String info){
  // logic
}
Enter fullscreen mode Exit fullscreen mode

套餐

具有相似功能的类、接口和子包的集合。
提供文件夹结构来组织类

包命名风格:

  • 小写
  • 反向域名

访问修饰符

修饰符设置类、方法或任何成员的可访问性

  • 民众
  • 私人的
  • 受保护
  • 默认(无修饰符)

替代文本
替代文本

参考:Jenkov 教程& GeeksforGeeks

抽象关键字

非访问修饰符适用于类和方法,但不适用于变量
,用于实现 Java 中的抽象(OOP 的支柱之一)

抽象类

无法创建/实例化抽象类的对象
抽象类的任何子类都必须实现所有抽象方法或其本身被声明为抽象。

abstract class class-name{
    //body of class
}
Enter fullscreen mode Exit fullscreen mode

由于类的部分实现,对象无法实例化。
抽象类依赖于子类来提供完整的实现。
任何子类

抽象方法

抽象方法不包含主体。子类将实现它们

abstract type method-name(parameter-list);
Enter fullscreen mode Exit fullscreen mode
  • 任何包含抽象方法的类都必须是抽象的
  • 以下修饰符组合不能与 abstract 一起使用
    • final(类和方法)
    • 私有(方法)
    • 静态(方法)
    • 同步(方法)
    • 本机/strictfp

Java 目标中抽象方法的进一步阅读

参考:GeeksforGeeksjavatpoint

抽象非法修饰符

摘要决赛

  • 类:final 用于防止继承
    • 抽象类依赖于子类来完成实现。
  • 方法: final 用于防止覆盖 -abstarct 方法需要在子类中被覆盖

抽象私有

  • 方法:私有方法无法在当前类之外访问
    • 子类不能访问私有抽象方法并且不能实现它。
  • 类:嵌套类。类中的所有内容都可以访问嵌套的私有类
    • 可以有一个嵌套的私有抽象类,例如

参考:tutorialspointstackoverflow

抽象静态

  • 方法:静态方法可以被隐藏但不能被覆盖
    • 抽象方法必须被重写才能实现。静态方法不能被重写,所以不能是抽象的。

参考:tutorialspointstackoverflow

抽象同步

摘要 strictfp

strictgp 是一个访问修饰符。除了 public 和 protected 之外,抽象方法上不允许使用任何修饰符。

Final 关键字

用于 完成类、变量和方法 实现

替代文本

  • 最终变量
    • 创建常量变量
    • 最终变量的值一旦赋值就无法更改。它将是常量。
    • 可以在构造函数中初始化。
  • Final 方法
    • 防止方法覆盖
    • 可以被继承但不能被覆盖。
    • 构造函数不能被设为 final。构造函数不能被继承,因此不能被覆盖,否则会出现编译时错误。
  • 最终课
    • 防止继承

参考javatpoint & GeeksforGeeks & tutorialspoint

静态关键字

用于定义独立于任何实例的类成员的变量、方法、块、嵌套类

  • 静态变量
    • 创建变量的单一副本并在类级别的所有对象之间共享
  • 静态方法
    • 可以在创建任何类的对象之前访问并且无需引用任何对象。
    • 只能直接调用其他静态方法
    • 只能直接访问静态数据
    • 不能引用thissuper
  • 静态嵌套/内部类
    • 主要是为了方便
    • 无需实例化外部类即可实例化内部类
    • 参考:https://stackoverflow.com/a/47568544
    • 适用于:构建器模式(内部构建器类是静态的)
  • 静态块
    • 进行计算来初始化静态变量
    • 只执行一次 - 当类第一次加载时。
class Test {
    // static variable
    static int a = 10;
    static int b;

    // static block
    static {
        System.out.println("Static block initialized.");
        b = a * 4;
    }
}
Enter fullscreen mode Exit fullscreen mode

参考文献:GeeksforGeeks & javatpoint & Baeldung

静态块与实例块

静态块:

  • 在类加载时运行
  • 将按照它们在类中出现的顺序执行
  • 父类的静态块首先执行,因为编译器在子类之前加载父类

实例块:

  • 在实例创建时运行
  • 每次构造函数调用期间的执行者
  • 父实例块将在子实例块之前首先运行

顺序是
超类 - 静态
子类 - 静态
超类 - 实例
超类 - 构造函数
子类 - 实例
子类 - 构造函数

super()请注意,如果开发人员尚未这样做,JVM 默认会将 ins 插入子类构造函数的第一行。

参考:https: //www.quora.com/In-Java-does-every-object-constructor-automatically-call-super-in-object-before-its-own-constructors
参考: https: //www.javamadesoeasy.com/2015/06/differences-between-instance.html

图片描述

参考: https: //www.baeldung.com/java-static-instance-initializer-blocks

包装类是不可变的

包装类是不可变的,因此像 add、subtarct 这样的操作会创建新的对象,而不会修改旧的

参考: 例如GeeksforGeeks 。

public static void main(String[] args)  {
        Integer i = new Integer(12);
        System.out.println(i); // 12
        modify(i);
        System.out.println(i); //12
}

private static void modify(Integer i) {
        i = i + 1;
}
Enter fullscreen mode Exit fullscreen mode

参数i在修改中被引用,但是它不会改变,因为对象是不可变的。

创建新对象
参考:SO & []

Integer a=3;
Integer b=3;
a+=b; // a=a+b
System.out.println(a); // 6
Enter fullscreen mode Exit fullscreen mode

在这种情况下,aa+=b实际上是一个引用,在行中,代码将引用a更改为指向不同的、同样不可变的Integer。a = new Integer(3+3)
a+=b

为什么要使用包装类

包装类将数据类型包装起来,并赋予其对象外观,从而为原始类型提供对象方法。
原因:

  • null 是可能值
  • 在 Collection 中使用它
  • 支持对象的方法(例如从其他类型创建Integer test = new Integer("55");

不同的包装类
替代文本

构造函数与 valueOf

创建包装类有两种方法

  • 构造函数Integer test = new Integer("100");
  • Integer test = Integer.valueOf("100");

构造函数:始终创建新对象,
valueOf:如果值在范围内,则可能返回缓存值。(例如,如果 long 值在 -128 到 127 之间,valueOf 会实现缓存)。如果缓存中没有值,则返回新对象。

通常建议使用valueOf ,因为它通过缓存频繁请求的值来获得更好的空间和时间性能。

由于包装类是不可变的,因此重用值非常重要。

自动装箱实际上也会调用静态 valueOf 方法

参考:java docs & SO answer

自动装箱和拆箱

Java 编译器会自动在原始类型和其对应的对象包装类之间进行转换。
另一种转换方式称为拆箱。

// autoboxing
Integer test = 9; 

// autounboxing
Integer test1 = Integer.valueOf(10);
test1++;
Enter fullscreen mode Exit fullscreen mode

自动装箱
从原始数据类型创建整数对象
Integer test = Integer.valueOf(9);

自动拆箱的
数学运算(+、%、+=)不适用于整数对象。Java 编译器编译代码时不会出错,因为它会自动拆箱,并在运行时调用intValue进行转换Integerinttest1.intValue()++;

参考:java 文档

自动装箱的优势

让开发人员编写更清晰、更易于阅读的代码。

此外,自动装箱使用静态 valueOf 方法,效率更高

参考:java 文档

类型转换 - 隐式 & 显式

类型转换:将Java 中的原始类型/接口类型/类类型
转换为其他类型。 如果两种类型兼容,Java 会自动执行转换,否则需要显式转换。

  • 隐式/扩展/自动
    • 如果两种类型兼容且目标类型大于源类型,发生

替代文本
将较小的值转换为较大的变量类型

byte i = 50; // 50
short j = i; // 50
int k = j; // 50
long l = k; // 50
float m = l; // 50.0
double n = m; // 50.0   
Enter fullscreen mode Exit fullscreen mode
  • 明确/缩小
    • 将较大的类型分配较小的类型

替代文本
将较大的值转换为较小的变量类型

double d = 75.0; // 75.0
float f = (float) d; // 75.0
long l = (long) f; // 75 
int i  = (int) l; // 75
short s = (short) i; // 75
byte b = (byte) s; // 75
Enter fullscreen mode Exit fullscreen mode

参考:GeeksforGeeks & javainterviewpoint & edureka

字符串 - 不可变

String 对象的值一旦创建就无法修改
任何修改都会创建一个 String 对象
。String 之所以不可变,主要是因为它封装了一个 char[]

String test = "hello";
test.concat(" world");
System.out.println(test); // hello
Enter fullscreen mode Exit fullscreen mode

String 对象的值未被修改。like运算符concat()生成一个新的 String原始值保持不变toUppercase()

String str = "hello";
str = str + "world";
System.out.println(str); //hello world
Enter fullscreen mode Exit fullscreen mode

在代码片段中,并str没有改变。而是实例化了一个新的 String 对象“hello world”并赋值给了 引用str

参考:SO

字符串值存储于内存的哪里?

取决于我们如何创造它们

  1. 字符串文字 -> 字符串常量池(在堆中)
  2. 新对象->堆内存
  • 字符串文字
    • 存储在字符串常量池(堆内存)中的值
    • 编译器找到字符串文字->检查是否存在于池中,是否重用。
  • 字符串对象构造函数
    • 在堆上创建新对象,在堆栈​​中引用。
    • 不重复使用价值

Java 字符串池

Java 中的字符串池是在 Java 堆内存中排序的字符串池。

  • 节省 Java 运行时空间
  • 有更多时间来创建字符串。

替代文本

唯一可能的是

  • Java 中的字符串是不可变的
  • 字符串驻留概念的实现

字符串池是享元设计模式的一个例子。

字符串文字
String test = "hello";
当通过字符串文字创建字符串时,

  • java 在字符串池中查找具有相同值的字符串
    • 找到:返回参考
    • 否则:在池中创建新的字符串->返回引用。

String 构造函数
强制 Java 在堆空间中创建新的 String 对象

String s1 = "Cat";
String s2 = "Cat";
String s3 = new String("Cat");

System.out.println(s1==s2); // true
System.out.println(s1==s3); // false
Enter fullscreen mode Exit fullscreen mode

参考:JournalDev

Java 字符串驻留

仅存储每个不同字符串值的 1 个副本的方法,该副本必须是不可变的。

Java String类有公共方法intern()

  • 在 String 对象上调用intern()方法。
  • 查找池中对象包含的字符串
  • 如果找到字符串
    • 返回池中的字符串
  • 别的:
    • 将字符串对象添加到池中并返回对字符串对象的引用。

因此,通过在字符串上应用 String.intern() 将确保具有相同内容的所有字符串共享相同的内存。例如,如果“hello”出现 100 次,则实习可确保实际上为 1 个“hello”分配内存。

String s1 = "Test"; // s1 in SCP
String s2 = "Test"; // s2 in SCP
String s3 = new String("Test"); // s3 in heap
final String s4 = s3.intern(); // s4 in SCP
System.out.println(s1 == s2); // true
System.out.println(s2 == s3); // false
System.out.println(s3 == s4); // false
System.out.println(s1 == s3); // false
System.out.println(s1 == s4); // true
System.out.println(s1.equals(s2)); // true
System.out.println(s2.equals(s3)); // true
System.out.println(s3.equals(s4)); // true
System.out.println(s1.equals(s4)); // true
System.out.println(s1.equals(s3)); // true
Enter fullscreen mode Exit fullscreen mode

参考:GeeksforGeeksDZone

字符串构造中创建的对象数量

String str = new String("Cat");
Enter fullscreen mode Exit fullscreen mode

创建 1 个或 2 个对象

  • 字符串文字
    • 在字符串池中创建“Cat”字符串(如果不存在)
  • 字符串str对象

Never + 循环中的字符串

每次连接都会创建一个新对象,因为不可变的字符串
使用字符串缓冲区/生成器

String test = "lup";
String test2 = "dup";
for (int i=0;i<100000;i++){
  test = test + test2;
}
Enter fullscreen mode Exit fullscreen mode

循环创建大约 100 000 个 String 对象。

字符串缓冲区

StringBuilder test = new StringBuilder("lup");
String test2 = "dup";
for (int i=0;i<100000;i++){
  test.append(test2);
}
String result = test.toString();
Enter fullscreen mode Exit fullscreen mode

这将产生更好的性能。

String 与 StringBuffer

  • String是不可变的,StringBuffer用来表示可以修改的值。
  • 都是线程安全的

StringBuilder 与 StringBuffer

String Builder不是线程安全的, String Buffer 是

Java什么是类

用于创建多个对象的模板。
它定义:

  1. 状态
  2. 对象可以表现出的行为

对象具有的状态
值。
在 Java 中,这些是对象的实例变量

属于类的行为
方法

对象的状态可以随着时间而改变,可以通过行为/类方法改变。

Java:

类的实例
可以通过构造函数创建对象

每个类的 ParentClass

Java Object 类是 Java 中每个类的超类/父类

如果您想要引用任何您不知道其类型的对象,这将很有用。

类将继承 Java Object 类的所有属性和方法。
例如。

  1. 哈希码
  2. 等于
  3. 克隆

Java 对象的 toString 方法

用于打印对象的内容
。继承自Java对象类。
默认:打印哈希码。
覆盖:打印通过toString返回的内容。

toString() 方法必须

  1. 公开
  2. 返回类型:字符串
  3. 不接受任何参数

Object类默认的toString方法

public String toString()
{
      return getClass().getName()+"@"+Integer.toHexString(hashCode());
}

MyClass test = new MyClass();
System.out.println(test);
// com.example.demo.MyClass@f7e66a96
Enter fullscreen mode Exit fullscreen mode

参考:GeeksforGeeks & in28Minutes

Java equals() 方法

用于比较两个对象
从 java Objects 类
继承默认:使用==运算符(检查引用是否指向同一个对象)
覆盖:通常实现为检查对象的所有属性是否具有相等的值

如果创建自己的自定义类,则必须重写 equals 方法来检查对象的内容。

equals() 方法标准

所有 equals 方法的实现必须满足以下条件

  • 反身
    • 对于任何参考值 x,x.equals(x) 返回 true
  • 对称
    • x.等于(y)==y.等于(x)
  • 及物动词
    • 如果 x.equals(y) = true & y.equals(z) = true,则 x.equals(z) == true。
  • 持续的
    • 多次调用 x.equals(y) 将始终返回 true 或始终/总是返回 false。
    • 重复操作时结果不会改变。
  • 空值比较
    • 对于任何非空引用 x,x.equals(null) 必须返回 false。

通常只需使用 IDE 生成

参考期刊 Dev & in28minutes

Java hashCode() 方法

用于类似 hashmap 的集合。
默认值:从堆中使用的对象的内存地址派生而来。
用于哈希计算,决定对象应该放在哪个 bucket 中
因此,hashCode()决定了哈希计算的有效性
好的哈希函数会将对象均匀地分布在不同的 bucket 中。

hashCode 方法标准

好的 hashCode 应该具有以下属性

  1. obj1.equals(obj2) == true --> obj1.hashCode() == obj2.hashCode()
  2. 一致。如果 obj equals() 中使用的值没有改变,则 obj.hashCode()多次运行时应该返回相同的值
  3. 如果 obj1.equals(obj2) == false --> obj1.hashCode可能== obj2.hashCode()。

基本上。

  • 两个不相等的物体
    • 可能有相同的哈希码(哈希冲突)
  • 两个相等的对象
    • 必须具有相同的哈希码
      • 如果两个相等的对象具有相同的哈希码,则可以将它们放在不同的存储桶中。
      • equals() 函数永远不会在 obj1 和 obj2 上运行。obj2 永远不会替换 obj1
      • hashmap 可以保存重复的键

equals() 和 hashCode() 的重要性

如果使用对象作为哈希键,则必须重写/实现 equals() 和 hashCode() 方法

equals() 和 hashCoe() 的实现遵循以下规则/契约:

  1. 如果 o1.equals(o2) --> o1.hashCode() == o2.hashCode()
  2. 如果 o1.hashCode() == o2.hashCode() == true,则 o1.equals(o2) 可能不为真

因此,如果您实现 equals 方法,那么最好覆盖 hashCode,以便两种方法都遵循契约。

然而,只有当您使用对象作为哈希表键时,这才会真正发挥作用

在散列中,
equals():用于检查键是否存在于Hashmap中,
如果实现错误,

  • hashMap.get(obj) 可能返回 null,因为错误的 equals 方法无法判断 obj 和 key 是否相等
  • hashMap.put(obj1) 可能会返回“重复”的键。因为 Java 无法判断 obj1.equals(obj) 是否覆盖 obj。

hashCode():决定将 obj 放入哪个 bucket 中
如果实现错误:

  • 等价的物体可以放在两个不同的桶里。
    • 由于 equals() 仅在 1 个 bucket 上运行,因此 Java 无法判断 obj 是否已存在于 HashMap 中
    • HashMap 可以存储“重复”的键。

参考:JournalDev

Java对象继承

一个类获取另一个类的属性(方法和字段)的过程。IS
-A 关系类型 类
ChildClass扩展ParentClass
接口:ChildClass实现Interfae1、Interface2

子类:子类
超类:父类


Java 中只有一个超类,

  • 子类只能扩展 1 个父类
  • 子类可以实现>=1个接口

继承构造函数的
子类继承了所有成员(字段、方法、嵌套类)。但构造函数不是成员,也不是被继承的
但超类的构造函数可以从子类调用super()

私有成员继承
子类不继承父类的私有成员。
但子类可以使用超类的公共/受保护方法(例如 getter、setter)来访问超类的私有字段。

继承时只创建子类的对象,不创建超类的对象

public class ChildClass extends ParentClass{
}
ChildClass test = new ChildClass()
Enter fullscreen mode Exit fullscreen mode

在这种情况下,仅创建 1 个 ChildClass 对象,而没有创建 ParentClass 对象。

参考:GeeksforGeekstutorialspointw3schools

Java == VS 等于

==:比较两个引用是否指向同一个对象,
可以适用==每个原始类型
equals():覆盖 Object equals 方法。

字符串类

  • 字符串池确保“==”适用于字符串文字(引用将指向池中的相同对象/地址)
  • equals():可以检查 String 对象的值(String 池确保 == 适用于字符串文字)对于 String,可以检查对象的值

对象类

java Object 的equals()默认实现实际上是 ==

public boolean equals(Object obj) {
  return (this == obj);
}

Enter fullscreen mode Exit fullscreen mode

为了使自定义对象正确实现equals()方法,您必须覆盖默认实现。

@Override
public boolean equals(Object o) {
  if (this == o) {
    return true;
  }
  if (o == null || getClass() !=  o.getClass()) {
    return false;
  }
  Simpson simpson = (Simpson) o;
  return age == simpson.age && weight == simpson.weight && name.equals(simpson.name);
}
Enter fullscreen mode Exit fullscreen mode

参考:GeeksforGeeks & infoworld

hashmap 内部工作原理

Hashamp 的工作原理是散列-一种将对象数据映射到某些代表性整数值的算法,
负载因子:条目/桶(0.75),
容量:桶数(16),
阈值:容量*负载因子(12)。

在Java中,阈值(默认)为12,即添加第12个关键值后,hashmap的容量将增加(两倍)

Java Hashmap 中主要有两种场景

  1. 构建并将物品放入(hashMap.put(key,value)
  2. 通过键从哈希图中检索项目(value = hashMap.get(key)

构造 HashMap

  • 16个初始桶
    • 初始容量 = buckets 数量 = 16(new HashMap<>()
  • 默认负载因子为 0.75
  • 默认阈值 = 12
    • 默认阈值 = 16 * 0.75 = 12
    • 对第 12 个键值对插入执行 rehash
  • 获取键的哈希码
    • hashmap.put(key,value)Java 计算key 的hashCode () key.hashcode()
  • 计算 bucket 索引(例如 6)
    • 因为哈希码可能大于桶的数量
    • 按位与。hashCode 与 bucket 数量。确定要插入键值对的 bucket 索引
  • 使用键值对创建 Node 对象
    • 类似 LinkeList 的节点

由于 hashCode 不唯一,因此会发生冲突

碰撞

  • 获取 key1.hashCode()
  • 获取 key1 的 bucket 索引(例如 6)
  • 索引 6 处有一个对象
  • 检查插入的对象和现有对象是否不相等
    • key.equals(key1)检查相等性
    • 如果相同:替换
    • 否则:碰撞
  • 通过链表将 key1 的节点连接到 key 的节点
    • keyNode.next = key1Node
    • 两个键值对都存储在索引 6 处

替代文本

随着插入的对象越来越多,负载因子也会随之增加。负载因子的增加意味着 get() / put() 操作会变得更加复杂,这将抵消 hashmap 的巨大优势(例如,O(1) 的 get 和 put 操作)。

为了保持较低的负载因子,一旦超过阈值,就会发生重新哈希

重新讨论

  • 超出阈值(12)
  • 第 12 次插入后 -> 重新散列
  • 创建具有两倍旧容量的新 Hashmp 对象(因此有 32 个存储桶)
  • hashMap.put(key,value)对旧 hashMap 的所有元素 重复。
    • 旧 hashmap 的所有键值对都放入新 hashMap 中
    • 必须重新计算它们的哈希码、索引
    • 然后插入(如果发生碰撞则插入链表)

重新哈希过程既O(number of buckets)耗费空间又耗费时间O(number of entries)
因此,必须在阈值和重新哈希之间取得良好的平衡。

  • 门槛高
    • 很少重复
    • 但是:哈希图负载因子很高
    • get 和 put 将会很复杂
  • 门槛低
    • hashmap 的负载因子较低,将具有高效的获取和放置
    • 经常重复 -> 浪费时间和空间

从 HashMap 获取项目

负载因子(&哈希函数)将影响 get() 的复杂性
value = hashMap.get(key)

  • 计算键的hashCode
  • 计算键的索引(例如 6)
  • 转到索引桶
  • 使用equals()将第一个节点与键进行比较
    • node.equals(key)
  • 如果两者相等:返回值
  • 否则,沿着链表向下走并检查节点是否存在。

参考:SO 答案&& javaconceptoftheday && javarevisited && javacodegeeks && geeksforgeeks && baeldung

向哈希表添加空键

只允许一个空键

  • 哈希计算尚未完成
  • (键,值)对始终添加到 bucket 0

参考:knpcode

Java中的HashSet内部实现

Hashset内部使用HashMap ,始终会将一个虚拟对象作为值添加到 HashMap 中。HashSet
类用于创建使用哈希表进行存储的集合。它仅包含唯一元素

像 HashMap 一样,

  • 容量 = 16
  • 负载系数 = 0.75
  • 阈值 = 0.75 * 16 = 12

添加值
HashSet 实现,值存储为 hashmap 的键。因此,set 仅包含唯一元素。
在底层 hashMap 数据中,始终会添加一个虚拟对象作为值。
hashSet.add(element);
底层实现是
hashMap.put(element,DUMMY)

获取值
没有值。只需使用键迭代器即可获取键
hashMap.keySet().iterator()

您可以使用 hashMap 来检查 Set 中是否存在键

删除值
与删除哈希图中的项目相同

参考:knpcode

ArrayList:容量 VS 大小

容量:列表在调整大小之前可以容纳的最大项目数。
大小:列表中的项目数。

List<Intger> test = new ArrayList(20);
test.add(1);
test.add(2);
test.add(3);
Enter fullscreen mode Exit fullscreen mode

在这种情况下,容量为 20,大小为 3。

ArrayList 在 Java 内部是如何工作的

Arraylist 是Java 中可调整大小的数组
实现。 由数组支持

  • 默认容量 = 10
  • add():确保 arraylist 具有所需容量。
    • 如果容量耗尽
    • 新建阵列,容量增加 50%
    • Arrays.copyOf()用于将元素从旧数组复制到新数组
  • remove():从数组中删除元素
    • 元素移动以填补被移除元素所造成的空隙

参考:GeeksforGeeks & javatpoint & knpcode

Java Vector类

Vector 本质上是一个线程安全的数组列表。Vector
同步的,可以在多个线程中使用它。

从 jdk 1.0 开始,Vector 出现在 jdk 中(在 arraylist 之前)

同步有开销,因此如果您不使用多个线程,则可以使用没有同步开销的类。

Java内部堆栈是如何实现的?

堆栈是 LIFO(后进先出)堆栈的对象
操作:推送、弹出、查看、清空、搜索

堆栈创建中没有项目。

使用底层 Vector 类实现。Vector 是一个可增长/动态数组。

这可能是由于向后兼容性(参见 Quora 来源)

参考:SO & Quora & java docs

Java 中的队列实现

队列是一种先进先出 (FIFO) 数据结构
操作:添加、移除、元素

在 Java 中,Queue 是一个接口
没有 Queue 类,必须实现它。

实现

  • 链表
  • 优先队列
    • 基于数据结构。
    • 按照构造时指定的顺序对元素进行排序。
    • 队列检索操作(轮询、删除、查看)访问队列头部的元素。
    • 如果有多个元素并列位于队首,则可任意打破平局

Map 实现:HashMap、TreeMap、LinkedHashMap

所有类都提供唯一的键->值映射,并实现 Map 接口。
区别在于时间保证和键的排序。HashMap
:实现为
哈希表,键和值均无排序。TreeMap :基于红黑树结构实现,并按键排序。LinkedHashMap
保留插入顺序
。Hashtable:同步 HashMap。

  • 哈希表
    • 通过链表数组实现
    • O(1) 查找和插入
    • 按键顺序 -> 任意
  • 树形图
    • 由红黑树实现。
    • O(logN) 查找和插入
    • 键顺序 -> 有序,需要按排序顺序迭代键
    • 不使用散列来存储密钥对值
  • 链接哈希
    • 在键值对之间实现双向链表(在顶部哈希图实现上),用于跟踪插入顺序
    • O(1) 查找和插入
    • 键顺序 -> 插入顺序

替代文本
来源

  • LinkedHashmap 操作
    • 在 hashmap 之上,维护一个额外的双向链表,使其“了解”键值对的顺序

替代文本
LinkedHashMap,内部实现Anmol Sehgal 撰写的 Medium 文章对 LinkedHashMap 进行了很好的解释

  • TreeMap内部工作原理
    • 不使用散列
    • 使用红黑树算法对对象核心进行排序替代文本

参考:oracle 文档& GeeksforGeeks & DZone

集合实现:HashSet、TreeSet、LinkedHashSet

HashSet -> HashMap
TreeSet -> TreeMap

参考:oracle 文档GeeksforGeeks

<a name="arraylist-linkedlist-java

">列表实现:ArrayList 与 LinkedList

两者都实现 List
Arraylist:动态添加和删除项目,自动调整自身大小,元素存储在连续内存中
LinkedList:线性数据结构,每个元素都是具有数据和地址部分的独立对象。元素使用点和地址链接。

替代文本

替代文本

  • 操作数据,如删除数据。(数组列表,底层数组的所有内容都必须移动)。

参考:javatpoint & GeeksforGeeks & Baeldung

什么是 OOP

OOP 是一种依赖于类和对象概念的编程实践
开发人员将根据类和对象来构建代码。

替代文本

另一个天堂是过程式编程。

  • 程序被分成更小的部分,称为 mathos
  • 方法是该技术的基本实体。
  • 使用方法实现代码可重用性

OOP 的好处是

  • 程序结构清晰
  • 可重用代码,Java 代码是 DRY(不要重复自己)
  • 代码更易于维护、修改和调试

OOP支柱:继承

允许类继承其他类的特性。

  • 通过派生旧类来创建新类的机制
  • 继承支持可重用性
  • 表示IS-A 关系

替代文本

子类与超类是IS-A关系。

public class SuperClass{}
public class SubClass **extends** SuperClass{}
Enter fullscreen mode Exit fullscreen mode

在Java中,对象类是所有对象的超类。

参考文献:javatpoint

继承类型

5种类型

  • 单身的
    • 类继承另一个类
  • 多级
    • 继承链
    • 柯基犬延伸为狗,狗延伸为动物
  • 等级制度
    • 两个或多个类继承单个类
  • 多个(接口)
    • 子类有>1个超类
  • 混合(接口)
    • 上述两种或两种以上类型的混合。
    • 由于 java 类不能支持多重继承,因此类不能支持混合继承。

替代文本
图片来自simplesnippets

为什么Java类不能支持多重继承

  • 降低复杂性/简化语言 假设子类(C)继承了两个类(A,B)。如果 A 和 B 有相同的方法(testMethod()),并且方法从子类中调用C.testMethod()。那么子类是实现 A 还是 B 中的方法?

参考文献:GeeksforGeeksjavatpoint

Java中的继承:OOP

参考:https ://www.geeksforgeeks.org/inheritance-in-java/#:~:text=important%20facts%20about%20inheritance%20in%20java%20

Java 中的继承通过以下方式实现:

  • 关键词
    • 延伸
    • 工具
  • 课程类型
    • 班级
    • 界面
    • 抽象类

重要事实

  • 默认超类
    • 除了对象之外,每个 Java 类都有超类
    • 每个类都是对象类的隐式子类
  • 仅 1 个超类
  • 继承构造函数
    • 不能继承超类构造函数
    • 可以super()在子类构造函数中调用超类构造函数
  • 私有成员继承
    • 不能继承私有成员(字段、方法、嵌套类)
    • 可以通过超类 getter / setter 访问/修改私有方法(super.getProperty()

可以做什么

  • 继承的字段
    • 使用它们
    • 在子类中声明新字段
  • 继承的方法
    • 使用它们
    • 覆盖它们(使用与超类相同的方法签名@Override
    • 隐藏 静态方法(使用与超类静态方法相同的方法签名)
    • 声明新方法
    • 编写使用超类构造函数的新子类构造函数(super()
    • 调用super()必须是子类中的第一个语句

扩展 VS 实现

替代文本

OOP支柱:抽象

隐藏实现细节并仅向用户显示功能的过程

抽象让开发人员专注于对象做什么而不是对象如何做。

函数的实现对用户是隐藏的

参考:SO

Java中的OOP抽象

Java 有两种实现抽象的方法

  1. 抽象类(0-100%)
  2. 界面 100%
  • 抽象类
    • 类声明为抽象类
    • 可以有抽象方法和非抽象方法
    • 需要扩展
    • 子类必须实现其所有抽象方法
    • 无法实例化
    • 可以有构造函数、数据成员、抽象方法和非抽象方法。
abstract class Bike{  
  abstract void run();  
}  
class Honda4 extends Bike{  
  void run(){
  System.out.println("running safely");
}  
public static void main(String args[]){  
   Bike obj = new Honda4();  
   obj.run();   // running safely
 }  
}  
Enter fullscreen mode Exit fullscreen mode
  • 界面
    • 只能有抽象方法(没有方法体)
    • Java 8 允许接口中的默认方法和静态方法
    • 允许多重继承
    • 实现接口的类必须实现接口中声明的所有方法
    • 无法实例化
    • 所有字段都是 public static final替代文本
interface Drawable{  
  void draw();  
  default void msg(){
    System.out.println("default method");
  }  
  static int cube(int x){
    return x*x*x;
  }
}  
class Rectangle implements Drawable{  
  public void draw(){
    System.out.println("drawing rectangle");
  }  
}  


Drawable d=new Rectangle();  
d.draw();  // drawing rectangle 
d.msg();  // default method
Drawable.cube(3); // 27
Enter fullscreen mode Exit fullscreen mode

参考:javatpoint & [javatpoint-interface]

Abstarct 类 VS 接口

替代文本
来自 Oracle 文档

何时使用:

  • 抽象类

    • 在密切相关的类之间共享代码*
    • 期望扩展抽象类的类具有许多通用方法/字段/需要除公共之外的访问修饰符(抽象类可以声明非公共方法和字段)
    • 声明静态或非最终字段
  • 界面

    • 不相关的类将实现接口
    • 使用多重继承
    • 预计 API 在一段时间内不会改变(如果改变 1 个接口方法,则所有类都必须改变)

参考:techvidvan

OOP支柱:封装

OOP 技术包装数据和代码以实现数据隐藏
技术是
数据隐藏:未经授权的用户不应访问/修改数据
优点:

  • 灵活性:可以决定哪些变量具有读/写权限
  • 控制:类可以控制字段中存储的内容(如果设置器是错误输入,则拒绝它)

替代文本

它是如何实现数据隐藏的?

  • 将数据(变量/字段)和操作数据的代码(方法)包装在一个单元中
  • 变量对其他类隐藏
  • 变量只能通过其当前类中的方法访问替代文本

代码现在变得像一个“保护盾”,可以防止未经授权的用户/方式访问数据。

参考:EducativejavatpointGeeksforGeekstutorialspoint

Java中的封装

Java 中的封装是通过以下方式实现的

  1. 将类的字段声明为私有的
  2. 提供公共的 setter 和 getter 方法以及修改和字段
public class EncapTest {
   private int age;

   public int getAge() {
      return age;
   }

   public void setAge( int newAge) {
      age = newAge;
   }
}
Enter fullscreen mode Exit fullscreen mode

紧密封装

类只有私有变量/字段

参考:SO

OOP支柱:多态性

物体呈现多种形态的能力。

  • 编译时多态性/静态多态性
    • 方法重载
  • 运行时多态性/动态方法调度
    • 方法覆盖在 Java 中,继承允许多态性(方法覆盖)
  • 编译时多态性(

    • 静态绑定/早期绑定/重载
    • 编译时与其功能绑定的对象
    • 调用由编译器解析
    • 快速执行:需要执行的方法尽早知道(在编译时)
    • 不太灵活:所有操作都在编译时执行
  • 运行时多态性

    • 动态绑定/后期绑定/覆盖
    • 运行时与功能绑定的对象
    • 方法调用由 JVM 而不是编译器决定
    • 执行缓慢:要执行的方法仅在运行时才知道
    • 更加灵活,所有操作都在运行时执行

替代文本
来自:GeeksforGeeks

多态性:方法重载

允许一个类拥有多个方法:

  • 同名
  • 不同数量和类型的参数列表在编译时,方法调用由编译器解析(javac)。也称为:编译时多态性/早期绑定

有两种方法可以实现这一点

  1. 改变参数的数量
  2. 更改数据类型
public void display(String info){
  System.out.println("String into");
}

// method overloading - change data type
public String display(int info){
  System.out.println(info);
  return "result";
}

public void display(String info1,String info2){
  System.out.println("2 information string");
}
Enter fullscreen mode Exit fullscreen mode

当调用display方法时,编译器根据参数 dev pass 显示结果。

  • 如果参数是 int
    • 称呼display(int)
  • 参数是字符串
    • 称呼display(string)
  • 如果没有找到匹配的类型并且没有歧义,则可以(隐式地)执行类型提升。

替代文本

类型提升示例:
如果未找到匹配的数据类型,则将一种类型提升为另一种类型。

public void sum(int a long b){
  System.out.println(a+b);
}

// second argment (int literal) will be promoted to long. 
sum(20,20); // 40
Enter fullscreen mode Exit fullscreen mode

如果有歧义,

  • 方法中没有匹配的类型参数
  • 每种方法都支持类似数量的参数
public void sum(int a,long b){
  System.out.println("a method invoked");
}

public void sum(long a,int b){
  System.out.println("b method invoked");
}

//ambiguity
sum(20,20); // Compile Time Error
Enter fullscreen mode Exit fullscreen mode

现在存在歧义,两种方法都提出了类似数量的论点。

好处

  • 增加程序的可读性
    • 无需为执行相同操作的函数创建并记住不同的名称。

方法重载条件
不能

  • 仅通过改变方法的返回类型是不可能的
    • 如果仅改变返回类型但方法签名保持不变,则存在歧义,应该调用哪种方法?
  • 不能重载仅通过 static 关键字不同的方法
    • 参数数量、参数类型和方法名称相同
    • 相同的方法签名 -> 不会使CAN超载
  • 可以重载 main() 方法
    • main() 方法通常接收字符串数组作为参数。
    • 但可以重载 main() 方法以不接收任何参数/接收不同的参数
  • 带有类型提升的方法重载
    • 如果未找到匹配的数据类型,则可以将一种类型隐式提升为另一种类型。
    • 一种类型不能被隐式地取消提升
  • 可以重载静态方法
    • 可以有 >=2 个具有相同名称但输入参数不同的静态方法。

参考:javatpoint & GeeksforGeeks

多态性:方法覆盖

允许子类/子类提供其超类或父类已经提供的方法的具体实现
子类方法必须具有

  • 同名
  • 参数的数量和类型
  • 返回类型 JVM 负责方法解析 --> 运行时多态性 引用对象的类型决定了将执行覆盖方法的哪个版本
class Base {
  public void show(){}
}

class Derived extends Base {
  // method overriding
  public void show(){}
}
Enter fullscreen mode Exit fullscreen mode
Derived der = new Derived();
der.show();
Enter fullscreen mode Exit fullscreen mode

JVM 首先检查子类(派生类)是否提供了方法(显示)的任何实现。
是 -> 调用派生的覆盖方法,
否 -> 调用父方法

Base obj = new Derived();
obj.show();
Enter fullscreen mode Exit fullscreen mode

如果是基类,则引用,但对象仍然是派生类。
方法解析在运行时进行。
调用派生类的 show 方法。

Derived obj = new Base(); // not valid
Enter fullscreen mode Exit fullscreen mode

这无效。
重要提示

引用对象的类型决定了将执行哪个版本的重写方法,而不是引用变量的类型
运行时对象决定将调用哪个方法 --> 重写 = 运行时多态性

规则

  • 必须具有相同的返回类型或至少是父级返回类型的子类型 异常处理
  • 超类重写方法不抛出异常
    • 子类重写方法只能抛出未经检查的异常(即编译时未检查的异常)
  • 超类覆盖方法抛出异常
    • 子类重写方法只能抛出相同的子类异常。可以
  • 覆盖访问修饰符
    • 可以允许更多但不能更少的访问权限(例如,受保护的实例方法可以公开但不能在子类中私有)
  • final 方法不能被覆盖
    • final 关键字防止覆盖
  • 在覆盖方法中调用父类方法
    • super.method()
  • 可以覆盖abstarct 方法
    • 接口/抽象类中的抽象方法必须在派生类中被覆盖
  • 可以覆盖 synchronied / strictfcp 方法不能
  • 静态方法不能被覆盖
    • 静态方法绑定到类而不是对象
    • 子类中具有 saae 方法签名的静态方法是方法隐藏,而不是方法覆盖
  • 无法覆盖主要方法
    • 主要方法是静态的
  • 私有方法不能被覆盖
    • 私有方法不能被继承
    • 编译时绑定的私有方法
  • 不能覆盖构造函数
    • 父构造函数和子构造函数不能有相同的名称
    • 构造函数不被继承

参考:GeeksforGeeksjavatpoint

覆盖 VS 隐藏(静态方法)

静态方法不能被覆盖。但它们可以被隐藏。
当子类具有与父类相同的静态方法时。静态方法会隐藏另一个静态方法。
区别在于,覆盖可以实现运行时多态性,而隐藏则不能。

代码示例和解释取自 coderanch:

class Foo {
    public static void classMethod() {
        System.out.println("classMethod() in Foo");
    }

    public void instanceMethod() {
        System.out.println("instanceMethod() in Foo");
    }
}

class Bar extends Foo {
    public static void classMethod() {
        System.out.println("classMethod() in Bar");
    }

    public void instanceMethod() {
        System.out.println("instanceMethod() in Bar");
    }
}

class Test {
    public static void main(String[] args) {
        Foo f = new Bar();
        f.instanceMethod(); // instanceMethod in Bar
        f.classMethod(); // classMethod in Foo
    }
}
Enter fullscreen mode Exit fullscreen mode

instantMethod(overriding) :JVM在运行时找到实例f 的实际类并确定要运行的方法。 f声明为Foo,但创建的类实际上是Bar。在运行时,JVM 发现fBar实例并调用 Bar 中的 instantMethod。

classMethod(hiding):类/静态方法。JVM 不需要实际实例来调用方法。在编译时,编译器会查看引用的声明类型 (Foo)来决定调用哪个方法。

因此静态方法不具有运行时多态性

替代文本

参考:Stack OverflowCodeRanch

OOP Java:关联

关联是两个独立类之间通过其对象建立的关系。可以是

  • 一对一
  • 一对多
  • 多对一
  • 多对多 组合和聚合是关联的两种形式

替代文本

聚合

特殊形式的协会

  • Has-A关系
  • 单向
  • 父母可以独立于孩子而存在,反之亦然
public class Foo {
  private Bar bar;
  Foo(Bar bar) {
    this.bar = bar;
  }
}
Enter fullscreen mode Exit fullscreen mode

如果 Foo 死了,对象 Bar 仍然可以“存活”。

替代文本

作品

限制形式的聚合,两个实体彼此高度依赖

  • *部分关系
  • 没有父实体,子对象就不能存在
  • 子对象的生命周期由其所属的父对象控制
public class House {
   private Room room;
   public House() {
       // without a house there can be no room, if house deleted, room is also deleted
       room = new Room();
   }
}
Enter fullscreen mode Exit fullscreen mode

替代文本

参考:GeeksforGeeks & infoworld

OOP Java:耦合

衡量一个类对另一个类的依赖程度,
依赖程度应该最小——低耦合

OOP Java:内聚性

衡量类的职责相关性的尺度是
类必须具有高度的凝聚力(职责之间应该高度相关)

异常处理

Java 异常处理框架仅处理运行时异常。编译时异常必须由开发人员处理,否则代码将无法运行。

参考:JournalDev

异常处理:关键字

  • 抛出-抛出-尝试捕获-最终
    • 在函数/代码块内部使用
    • 在 rpgoram 中明确抛出异常
  • 投掷
    • 用于方法签名
    • 声明方法可能抛出的异常,并且方法将不会处理它。
  • 尝试捕获
    • 代码中用于异常处理的代码块
    • 一个 try 块可以有多个 catch 块 - 可以嵌套
  • 尝试
    • 块的开始
  • 抓住
    • try 块的结尾来处理异常
    • 必需参数应为 Exception 类型
    • Java 7 自动资源管理(在 1 个 catch 块中捕获许多资源)
    • catch(IOException | SQLException ex){}
  • 最后
    • 选修的
    • 无论是否发生异常,都会执行
    • 仅当 finally 中抛出异常或 JVM 在中间崩溃时才执行(System.exit()
    • 可以与 try catch 块一起使用
    • 由于异常会暂停执行过程,可能会有一些资源打开但无法关闭,请在 finally 块中关闭它们

Java异常层次结构

当引发异常时,会创建一个异常对象。Throwable是 Java 异常层次结构的
类,它有两个子对象。

  • 错误
  • 例外
    • 检查异常
    • 运行时异常

Java 异常是分层的,并且通过继承来对不同类型的异常进行分类

替代文本

  • 错误
    • 无法预测或恢复(致命)
      • 硬件故障
      • 内存不足错误
      • StackOverflow错误
  • 例外
    • 可以预见和恢复的错误
  • 检查异常
    • 编译时检查异常
    • 通常发生在与外部资源交互时
    • FileNotFoundException
    • IO异常
  • 运行时异常
    • 编译时未检查异常
    • 通常是由于糟糕的编程/错误
    • 从数组中获取元素但给出错误的索引
    • ArrayIndexOutOfBound,算术异常

Java 未检查异常 VS 运行时异常

检查异常:

  • 强制提供 try-catch / try-finally 块
  • 编译器强制
  • 处理外部资源/网络运行时异常:非强制性
  • 运行时发生
    • 编程错误
  • 检查异常
    • 如果不处理,将导致编译时错误
      • 使用 try-catch/try-finally 块处理或指定方法用途throws
    • 通常错误情况超出了程序的直接控制范围
    • 通常与外部资源/网络交互
      • 数据库/网络
    • 例如ClassNotFoundException,,,,IOExceptionSQLExceptionFileNotFound
  • 运行时异常
    • 未经编译器检查
    • 一旦执行有缺陷的代码就会发生
    • 方法未强制声明,抛出未经检查的异常
    • 例如ArithmeticExcetion,,ArrayStoreExceptionNullPointerException

继承自 CheckedExcetion 还是 Runtime Excpetion?
已检查异常

  • 方法可能失败
  • 检查异常以确保在发生故障时进行替代处理(强制执行正确的错误处理)运行时异常

  • 会使代码变得丑陋(大量样板代码)

参考:GeeksforGeeks VS java67 & javadocs

Java异常类方法

所有方法都依赖于父Throwable

  • 字符串获取消息()
  • 字符串获取本地化消息()
  • 同步 Throwable getCause()
  • 字符串 toString()
  • 无效打印StackTrace()
  • 获取消息()
    • Throwable 的消息字符串
    • 通过构造函数创建异常时提供的消息
  • 获取本地化消息()
    • 子类可以重写它以向调用程序提供特定于语言环境的消息
  • 获取原因()
    • 异常原因,如果原因未知则为 null
  • 到字符串()
    • 可抛出类的名称和本地化消息
  • 打印堆栈指针()
    • 将堆栈跟踪信息打印到标准错误流
    • 已重载,可以传递 PrintStream 或 PrintWriter 作为参数,将堆栈跟踪信息写入文件或流

Java异常处理标准

允许

  • 如果 try & catch 有 return 语句,finally 仍然可以执行
  • 允许不带 catch 的 try ( try{}finally{})

不允许

  • 不使用 catch 和 finally 进行尝试

Java异常处理最佳实践

  • 使用特定异常
    • 不要抛出基本异常类
  • 尽早抛出/快速失败
    • 尽早抛出异常
  • 赶不上
    • 向调用者抛出异常
    • 让呼叫者决定如何处理并且呼叫者可以收到异常通知
  • 关闭资源
    • 关闭我最终阻止的所有资源
    • Java 7 try-with-resources
    • 例如。try (Scanner scanner = new Scanner(new File("test.txt"))) {scanner.doSomething()}
  • 记录异常
    • 抛出异常时记录异常消息
  • 单个 catch 块用于处理多个异常
    • catch(SQLException|AnotherException ex){}

参考:JournalDev

UML 类图

设计课程
替代文本

协会

非常通用的术语,
一个类使用了另一个类提供的功能
替代文本

作品

部分
替代文本

聚合

一个对象“拥有”另一个对象,但子对象可以在没有父对象的情况下存在
替代文本

多重性
替代文本

泛化/继承/抽象
替代文本

来源

UML 序列图

表示对象和类如何通过代码相互交互,
显示事件的顺序

替代文本

OOP:设计模式

针对 OOP 中常见模式或解决代码中给定设计问题的一篮子预先设计的解决方案/蓝图。

创建型:创建对象
结构型:类和对象的组合,形成更大的结构
行为型:对象之间的责任和交互

单例设计模式

将类的实例数量限制为一个。确保 JVM 中仅存在一个类实例。
用于:日志记录、驱动程序对象、缓存和线程池。
实现

  1. 私有构造函数用于限制其他类的实例化
  2. 同一类的私有静态变量,该变量仅是该类的实例
  3. 公共静态方法getInstance()返回类的实例,因此其他类可以获取单例的实例

技术:

  • 急切初始化——在使用之前创建单例
    • public static final SingletonClass instance = new SingletongClass()
    • 使用static
  • 延迟初始化
    • 在方法中创建实例getInstance()。可能会在多线程环境中引起问题
  • Thread Sage 单例(用于松弛初始化)
    • 使getInstance()方法synchronized每次只有一个线程执行此方法
  • 使用枚举声明单例
    • Java 确保任何枚举值只被实例化一次。不允许延迟初始化public enum EnumSingleton{INSTANCE;}
  • 序列化和单例
    • 实现readResolve()方法。当实现可序列化接口时,反序列化单例类时,会创建对象的新实例。protected Object readResolve() {return getInstance()}

替代文本

参考:vainoloJournalDev

工厂设计模式

定义一个接口或抽象类来创建一个对象,但让子类决定实例化哪个类,
实例化的责任在于工厂类而不是客户端程序

替代文本
替代文本

执行

  • 超类(Computer)可以是接口,abstart类或普通类
  • 工厂类可以是单例的,也可以将方法返回类(getComputer)保持为静态的。
    • 不断实例化工厂类非常麻烦
  • 根据输入参数,创建并返回不同的子类
public class ComputerFactory {

    public static Computer getComputer(String type, String ram, String hdd, String cpu){
        if("PC".equalsIgnoreCase(type)) return new PC(ram, hdd, cpu);
        else if("Server".equalsIgnoreCase(type)) return new Server(ram, hdd, cpu);

        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

优势

  • 从客户端代码中删除实际类的实例
  • PC实现类( & Server)与客户端代码之间的抽象层(ComputerFactory

抽象工厂
抽象工厂就像是工厂的工厂。

参考:javatpoint & JouralDev & tutorialspoint

装饰器设计模式

通过改变现有对象的结构为其添加新功能,
装饰类包装原始类并提供附加功能,保持类方法签名完整

  • 使用抽象类或接口与**组合*来实现*

替代文本

替代文本

装饰器类与组件接口实现了“HAS-A”关系。具体装饰器将扩展基础装饰器的功能并修改组件的行为。

// Component Implementations
public class BasicCar implements Car {

    @Override
    public void assemble() {
        System.out.print("Basic Car.");
    }

}

// Decorator
public class CarDecorator implements Car {

    protected Car car;

    public CarDecorator(Car c){
        this.car=c;
    }

    @Override
    public void assemble() {
        this.car.assemble();
    }

}

// Concrete Decorator
public class SportsCar extends CarDecorator {

    public SportsCar(Car c) {
        super(c);
    }

    @Override
    public void assemble(){
        super.assemble();
        System.out.print(" Adding features of Sports Car.");
    }
}
public class LuxuryCar extends CarDecorator {

    public LuxuryCar(Car c) {
        super(c);
    }

    @Override
    public void assemble(){
        super.assemble();
        System.out.print(" Adding features of Luxury Car.");
    }
}

// Testing Decorator Program
Car sportsCar = new SportsCar(new BasicCar());
sportsCar.assemble(); 
// Basic Car Adding features of Sports Car
        System.out.println("\n*****");

Car sportsLuxuryCar = new SportsCar(new LuxuryCar(new BasicCar())); sportsLuxuryCar.assemble(); 
// Basic car. Adding features of Luxury Car. Adding Features of SportsCar. 
Enter fullscreen mode Exit fullscreen mode

参考:Baeldung & tutorialspoint & JournalDev

享元设计模式

工厂方法,创建后通过存储对象来回收它们。返回现有对象,否则创建新对象。
享元对象必须是不可变的,任何针对状态的操作都必须由工厂方法执行
优点

  • 减少内存占用
  • 提高性能

何时使用

  • 应用程序要创建的对象数量应该很大
  • 对象创建占用大量内存并且耗时
  • 享元对象必须分为内在属性外在属性。内在属性使对象独特,外在属性由客户端代码设置,用于执行不同的操作。

参考:Baeldung & tutorialspoint & JournalDev

适配器设计模式

充当两个不兼容接口之间的连接器,否则无法直接连接。2
种实现

  • 类适配器
    • 使用继承并扩展适配接口
    • 两个不兼容接口之间的单个类称为适配器
  • 对象适配器
    • 使用组合和适配器包含适配对象。
    • 环绕类,允许 2 个类/接口一起工作,而无需更改任何一方的代码。

对象适配器
替代文本

  • 客户端只能看到目标
  • 适配器类实现目标接口并提供实现Request()
  • adapter由 adaptee组成

过程

  • 客户端调用适配器
  • 适配器进行转换
  • 向适配器发出必要的调用
public class SocketObjectAdapterImpl implements Targer{

//Using Composition for adapter pattern
    private Adaptee sock = new Adaptee ();

    @Override
    public Volt get120Volt() {
        return sock.getVolt();
    }

    @Override
    public Volt get12Volt() {
        Volt v= sock.getVolt();
        return convertVolt(v,10);
    }

    @Override
    public Volt get3Volt() {
        Volt v= sock.getVolt();
        return convertVolt(v,40);
    }

    private Volt convertVolt(Volt v, int i) {
        return new Volt(v.getVolts()/i);
    }
}
Enter fullscreen mode Exit fullscreen mode

类适配器
替代文本

  • 适配器实现目标
  • 继承适应者

过程

  • 客户端调用适配器
  • 适配器不会将请求委托给被适配器
  • 适配器使用从被适配者继承的方法SpecificRequest())来处理特定的请求
//Using inheritance for adapter pattern
public class SocketClassAdapterImpl extends Adaptee implements Targer{

    @Override
    public Volt get120Volt() {
        return getVolt();
    }

    @Override
    public Volt get12Volt() {
        Volt v= getVolt();
        return convertVolt(v,10);
    }

    @Override
    public Volt get3Volt() {
        Volt v= getVolt();
        return convertVolt(v,40);
    }

    private Volt convertVolt(Volt v, int i) {
        return new Volt(v.getVolts()/i);
    }

}
Enter fullscreen mode Exit fullscreen mode

替代文本

JournalDevGeeksforGeeks

观察者设计模式

对象之间存在一对多依赖关系,因此当一个对象(主体)改变状态时,其所有依赖者(观察者)都会收到通知并自动更新。

例如,愿望清单中的商品缺货,请告诉网站在有货时通知您。
替代文本

// subject
public interface Subject {

//methods to register and unregister observers
  public void register(Observer obj);
  public void unregister(Observer obj);

//method to notify observers of change
  public void notifyObservers();

//method to get updates from subject
  public Object getUpdate(Observer obj);
}

// object
public interface Observer {

//method to update the observer, used by subject
public void update();
// observers will print updated subject in this method

//attach with subject to observe
public void setSubject(Subject sub);
}

public class MyTopic implements Subject {

    private List<Observer> observers;
    private String message;
    private boolean changed;
    private final Object MUTEX= new Object();

    public MyTopic(){
        this.observers=new ArrayList<>();
    }
    @Override
    public void register(Observer obj) {
        if(obj == null) throw new NullPointerException("Null Observer");
        synchronized (MUTEX) {
        if(!observers.contains(obj)) observers.add(obj);
        }
    }

    @Override
    public void unregister(Observer obj) {
        synchronized (MUTEX) {
        observers.remove(obj);
        }
    }

    @Override
    public void notifyObservers() {
        List<Observer> observersLocal = null;
        //synchronization is used to make sure any observer registered after message is received is not notified
        synchronized (MUTEX) {
            if (!changed)
                return;
            observersLocal = new ArrayList<>(this.observers);
            this.changed=false;
        }
        for (Observer obj : observersLocal) {
            obj.update();
        }

    }

    @Override
    public Object getUpdate(Observer obj) {
        return this.message;
    }

    //method to post message to the topic
    public void postMessage(String msg){
        System.out.println("Message Posted to Topic:"+msg);
        this.message=msg;
        this.changed=true;
        notifyObservers();
    }

}

//create subject
MyTopic topic = new MyTopic();

//create observers
Observer obj1 = new MyTopicSubscriber("Obj1");
Observer obj2 = new MyTopicSubscriber("Obj2");
Observer obj3 = new MyTopicSubscriber("Obj3");

//register observers to the subject
topic.register(obj1);
topic.register(obj2);
topic.register(obj3);

//attach observer to subject
obj1.setSubject(topic);
obj2.setSubject(topic);
obj3.setSubject(topic);

//check if any update is available
obj1.update(); // Obj1:: No new message

//now send message to subject
topic.postMessage("New Message");

// OUTPUT
Message Posted to Topic:New Message
Obj1:: Consuming message::New Message
Obj2:: Consuming message::New Message
Obj3:: Consuming message::New Message
Enter fullscreen mode Exit fullscreen mode

代码片段中的互斥主体用于锁

序列图
替代文本

参考:vogella & JournalDev & Baeldung & tutorialspoint

策略设计模式

类行为或算法可以在运行时更改。
例如,Collections.sort()
两种方式:

  • 多次接口和实现
    • 从用于应用算法的接口开始
    • 对每种可能的算法多次实现
  • Java 8 和 lambda
    • 使用 lambda 代替匿名内部类型

替代文本

接口与实现

// Startegy interface
public interface Strategy {
   public int doOperation(int num1, int num2);
}

// Startegy concrete classes
public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
public class OperationSubstract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

// use context to call & execute startegy interface
public class Context {
   private Strategy strategy;

   public Context(Strategy strategy){
      this.strategy = strategy;
   }

   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

Context context = new Context(new OperationAdd());      
      System.out.println(context.executeStrategy(10, 5)); // 15

context = new Context(new OperationSubstract());        
      System.out.println(context.executeStrategy(10, 5)); //5 

context = new Context(new OperationMultiply());     
      System.out.println(context.executeStrategy(10, 5)); // 50
Enter fullscreen mode Exit fullscreen mode

使用 Java Lambdas
Java 8 可以在接口中创建静态方法

public interface Discounter {
    BigDecimal applyDiscount(BigDecimal amount);

    static Discounter christmasDiscounter() {
        return amount -> amount.multiply(BigDecimal.valueOf(0.9));
    }

    static Discounter newYearDiscounter() {
        return amount -> amount.multiply(BigDecimal.valueOf(0.8));
    }

    static Discounter easterDiscounter() {
        return amount -> amount.multiply(BigDecimal.valueOf(0.5));
    }
}
Enter fullscreen mode Exit fullscreen mode

序列图
替代文本

参考:Baeldung & tutorialspoint & JournalDev

状态设计模式

允许对象在内部状态改变时改变其行为(在运行时)。

  • 使用场景:对象依赖于其状态,且行为必须在运行时根据其内部状态进行更改。实现允许对象在不更改其类的情况下更改其行为

替代文本
Context 对象会根据其内部状态改变其行为。Context
对象会有一个关联状态,该状态会在程序执行期间发生变化。Context
会将行为委托给状态实现

例如:电视遥控器可以打开或关闭

// Staet interface 
public interface State {
    public void doAction();
}

// State implementations
public class TVStartState implements State {

    @Override
    public void doAction() {
        System.out.println("TV is turned ON");
    }

}

public class TVStopState implements State {

    @Override
    public void doAction() {
        System.out.println("TV is turned OFF");
    }

}

public class TVContext implements State {

    private State tvState;

    public void setState(State state){
        this.tvState=state;
    }

    public State getState() {
        return this.tvState;
    }

    @Override
    public void doAction() {
        this.tvState.doAction();
    }

}


TVContext context = new TVContext();
State tvStartState = new TVStartState();
State tvStopState = new TVStopState();
            context.setState(tvStartState);
context.doAction(); // TV is turned on  
        context.setState(tvStopState);
context.doAction(); // TV is turned off
Enter fullscreen mode Exit fullscreen mode

替代文本

参考:BaeldungtutorialspointJournalDevDZone

文章来源:https://dev.to/jing/core-java-java-theory-ge
PREV
我的第一次编程面试
NEXT
由 CSS 自定义属性支持的视差