Java 是 1995
年由 sun
公司推出的一门高级语言。
Java 的四个基本特性是面向对象、平台无关性、安全性和简单性。
具体特点如下:
简单易学。
平台无关性。
面向对象
面向对象是一种程序设计技术,以木匠工作为例,使用面向对象方式实现的木匠的工作关注重点永远是制作椅子,其次才是工具。
而面向过程则优先关注制作工具。
与 C++
不同的是,Java
不支持多继承,取而代之的是更加简单的接口的概念。
面向对象三大特性: 封装、多态、继承。
编译与解释并存
可靠性
Java
通过早期检测以及运行时检测消除了容易出错的情况。C++
不同的是,C++
在操作数组、字符串方式上利用指针模型避免了重写内存或者损坏数据的问题。安全性: Java 适用于网络/分布式环境,为了达到这个目标,Java 在防病毒,防篡改做出很大的努力。
支持网络编程并且非常方便。
支持多线程。
什么是【编译型】语言和【解释型】语言?
编译型:编译型语言 会通过编译器将源代码一次性翻译成可被该平台执行的机器码。一般情况下,编译语言的执行速度比较快,开发效率比较低。常见的编译性语言有 C、C++、Go、Rust 等等。
解释型:解释型语言会通过解释器一句一句的将代码解释(interpret)为机器代码后再执行。解释型语言开发效率比较快,执行速度比较慢。常见的解释性语言有 Python、JavaScript、PHP 等等。
而 Java 是编译与解释并存,因为
java
源代码运行时需要先编译成为字节码文件(.class)
,然后再通过解释器翻译成机器码运行。
JavaSE
:标准版,即学生时期学习时使用的版本。JavaEE
:web
开发采用的技术架构。JavaME
:为嵌入式设备提供的解决方案Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。
JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
字节码和不同系统的 JVM 实现是 Java 语言 “一次编译,随处可以运行” 的关键所在。
JVM 并不是只有一种!只要遵守 JVM
设计规范就能开发出自己所需要的 Java
虚拟机,我们日常所用的 HotSpot VM
只是其中一种实现而已。
JDK(Java Development Kit) 是 Java
开发工具包,包含了 JRE
所有的东西,所以作为开发人员,只需要安装 JDK
即可。
JRE(Java Runtime Environment) 是 Java
运行环境,包含运行所需要的类库以及 JVM。
你可能认为如果仅仅要运行 Java
程序,安装 JRE
即可,但是某些 web
程序例如:需要将 JSP
转换为 Java servlet
就需要 jdk
编译了,所以保守起见,无论运行还是开发,我们都建议在操作系统上安装 JDK
。
Java
没有指针的概念,不能像 C++
一样直接操作内存,所以更加安全。Java
不支持多继承,但是可以通过多接口实现多继承。Java
只支持方法重载,不像 C++
一样可以运算符重载。Java
有自动内存管理垃圾回收机制 (GC)
,无需像 C++
一样手动释放。什么是 GC ?
有三种:
//
)/* */
)java doc
)标识符
:简单来说就是一个名字,比如 某个店铺名。
关键字
:被赋予特殊含义的标识符,比如 警察局,医院。
注意:所有关键字都是小写,在 IDE 中会以特殊颜色展示。
它们的作用是控制类、方法和变量的访问权限。
Java
中的访问控制关键字主要有以下四个:
public
: 表示公共的,任何地方都可以访问。在同一项目中或其他项目中,都可以通过引入类或模块进行访问。protected
: 表示受保护的,只有本类和其子类以及同一包中的其他类可以访问。在其他包中的子类不可以访问。default
(即不写访问控制符): 表示默认的,只有本类和同一包中的其他类可以访问,其他包中的类都不可以访问。private
: 表示私有的,只有本类中可以访问,其他类都不可以访问。可见性
同一个类 -> 同一个包 -> 子类 -> 全局范围
可见性 | private | default | protected | public |
---|---|---|---|---|
同一个类中 | ✔️ | ✔️ | ✔️ | ✔️ |
同一个包中 | ❌ | ❌ | ✔️ | ✔️ |
子类中 | ❌ | ❌ | ✔️ | ✔️ |
全局范围 | ❌ | ❌ | ❌ | ✔️ |
final
是 Java 中的一个关键字,可以用来修饰类、方法和变量,表示它们不可被修改。
被
final
关键字修饰会怎么样?
final
修饰类:表示该类是不可继承的,即不能有子类。final
修饰方法:表示该方法不能被子类重写,即不能被修改。final
修饰变量:表示该变量是一个常量,只能被赋值一次,不能被修改。final
关键字的主要作用如下:
final
关键字可以保证类、方法和变量在程序运行时不被修改,从而提高了代码的安全性和可维护性。final
关键字可以使得编译器在编译时进行优化,从而提高了代码的性能。final
关键字可以使得代码的含义更加明确,从而方便代码的维护和理解。主要是四个区别:
从语法形式上看:
public
,private
,static
等修饰符所修饰,static
所修饰;final
所修饰。从变量在内存中的存储方式来看:
如果成员变量是使用 static
修饰的,那么这个成员变量是属于类的,
如果没有使用 static
修饰,这个成员变量是属于实例的。
对象存在于堆内存,是【类的实例化】
局部变量则存在于栈内存,是【在方法里定义的】
从变量在内存中的生存时间上看:
成员变量是对象的一部分,它随着对象的创建而存在,
而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
从变量是否有默认值来看:
final
修饰的成员变量也必须显式地赋值),静态变量也就是被 static
关键字修饰的变量。
它可以被类的所有实例共享,无论一个类创建了多少个对象,它们都共享同一份静态变量。也就是说,静态变量只会被分配一次内存(属于类,只加载一下),即使创建多个对象,这样可以节省内存。
静态变量是通过类名来访问的,例如 StaticVariableExample.staticVar
(如果被 private
关键字修饰就无法这样访问了)。
public class StaticVariableExample {
// 静态变量
public static int staticVar = 0;
}
通常情况下,静态变量会被 final
关键字修饰成为常量。
public class ConstantVariableExample {
// 常量
public static final int constantVar = 0;
}
char
)只占 2 个字节;String
)占若干个字节。注意
char
在 Java 中占两个字节。
方法的返回值 是指我们获取到的某个方法体中的代码执行后产生的结果!
有四种类型:
这是因为
1、调用方式
在外部调用静态方法时,可以使用 类名.方法名
的方式,也可以使用 对象.方法名
的方式,
而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象 。
2、访问类成员是否存在限制
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),
而实例方法不存在这个限制。
重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。
区别点 | 重载方法 | 重写方法 |
---|---|---|
发生范围 | 同一个类 | 子类 |
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等 |
访问修饰 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
发生阶段 | 编译期 | 运行期 |
可变长参数是指在函数或方法中,参数的数量是可变的,即函数或方法可以接受不确定数量的参数。
可变长参数必须放在参数列表的最后一个位置,并且使用省略号(...
)来表示。
例如:
public static int sum(int... nums) {
int result = 0;
for (int num : nums) {
result += num;
}
return result;
}
int sum1 = sum(1, 2, 3); // sum1 = 6
int sum2 = sum(1, 2, 3, 4); // sum2 = 10
int sum3 = sum(1); // sum3 = 1
int sum4 = sum(); // sum4 = 0
也可传入数组
int[] nums = {1, 2, 3};
int sum5 = sum(nums); // sum5 = 6
遇到方法重载时会优先匹配固定参数还是可变参数的方法呢?
会优先匹配固定参数的方法,因为固定参数的方法匹配度更高。
注解可以看作是一种特殊的注释,本质上是继承了 Annotation
这一特殊接口,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。
注解只有被解析之后才会生效。我们可以使用 JDK
提供的内置注解也可以自定义注解。
Java
注解的出现主要是为了解决代码中大量重复性工作,例如:配置文件的读取、日志记录、数据校验等。可以帮助开发者更加方便地管理和维护代码(还可以实现一些特定的功能),提高程序的质量(和开发效率)。
常见的解析方法有两种:
编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用 @Override
注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的 @Value
、@Component
)都是通过反射来进行处理的。
定义一个注解 MyAnnotation
:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value();
}
在代码中使用注解,并通过反射机制获取注解信息:
@MyAnnotation("hello")
public class MyClass {
public static void main(String[] args) {
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); // 输出 "hello"
}
}
字节码注解解析:在类加载期间,通过 ASM 或 Javassist 等字节码操作库来解析注解信息,并修改字节码文件。这种方式可以在不改变源代码的情况下,对代码进行动态的修改和增强。
定义一个注解 MyAnnotation
:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value();
}
使用 ASM 操作库在类加载期间解析注解信息:
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadClassData(name);
Class<?> clazz = defineClass(name, bytes, 0, bytes.length);
// 解析注解信息
return clazz;
}
private byte[] loadClassData(String name) {
// 读取类字节码文件
}
}
MyClassLoader loader = new MyClassLoader();
Class<?> clazz = loader.loadClass("com.example.MyClass");
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); // 输出 "hello"
Java 的 SPI(Service Provider Interface)机制是一种用于动态加载和扩展服务的机制,它通过定义服务接口、服务提供者接口和加载配置文件的方式,实现了在运行时动态加载服务提供者实现的功能。
SPI 机制的
在 Java 中使用 SPI 机制需要完成以下步骤:
为了避免 SPI 机制中的冲突问题,可以使用类加载器隔离机制,即创建多个类加载器,每个类加载器加载不同的 jar 包和配置文件,从而实现服务提供者实现的隔离。同时,也可以规范命名空间的使用,避免不同的服务提供者实现使用相同的命名空间。
SPI 机制和 Spring 的 BeanFactory 都是用于实现插件化和扩展性的机制,但是它们的实现方式不同。SPI 机制是基于接口和配置文件的方式实现的,而 Spring 的 BeanFactory 是基于依赖注入和反射机制实现的。SPI 机制更加轻量级和灵活,适用于简单的应用场景,而 Spring 的 BeanFactory 更加强大和复杂,适用于大型的企业级应用。
如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
简单来说:
序列化的主要目的是:
通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
在项目中,JSON 数据的传输是非常常见的方式。JSON 是一种轻量级的数据交换格式,可以方便地在前后端之间传输数据。
以下是一些常见的在项目中使用 JSON 传输数据的场景:
前后端 API 交互:在前后端分离的项目中,前端和后端通常通过 API 进行交互。在这种情况下,后端将数据以 JSON 格式返回给前端,前端可以使用 JavaScript 解析 JSON 数据,将其转换为对象,并在页面上渲染数据。
RESTful API:RESTful API 是一种基于 HTTP 协议的 API 设计风格,通常使用 JSON 作为数据传输格式。在 Java 项目中,可以使用 Spring MVC 或 JAX-RS 等框架来构建 RESTful API,并使用一些 JSON 解析库将 JSON 请求和响应转换为 Java 对象。
WebSocket 通信:在前后端分离的实时通信应用中,WebSocket 是一种常见的通信协议。在这种情况下,前后端可以使用 JSON 作为通信协议,将消息以 JSON 格式传输。
静态资源加载:在前后端分离的项目中,前端通常使用 AJAX
或 Fetch API
从后端获取数据。在这种情况下,后端可以将数据以 JSON 格式返回给前端,前端可以使用 JavaScript 解析 JSON 数据,并在页面上渲染数据。
语法糖(Syntactic sugar)代指的是编程语言为了方便程序员开发程序而设计的一种特殊语法,这种语法对编程语言的功能并没有影响。
实现相同的功能,基于语法糖写出来的代码往往更简单简洁且更易阅读。
不过,JVM 其实并不能识别语法糖,Java 语法糖要想被正确执行,需要先通过编译器进行解糖,也就是在程序编译阶段将其转换成 JVM 认识的基本语法。这也侧面说明,Java 中真正支持语法糖的是 Java 编译器而不是 JVM。如果你去看
com.sun.tools.javac.main.JavaCompiler
的源码,你会发现在compile()
中有一个步骤就是调用desugar()
,这个方法就是负责解语法糖的实现的。
Java 中最常用的语法糖主要有泛型、自动拆装箱、变长参数、枚举、内部类、增强 for 循环、try-with-resources 语法、lambda 表达式等。
内部类是指定义在类内部的类,它可以访问外部类的私有变量和方法,从而实现对外部类的访问和控制。
内部类主要分为以下几种:
匿名内部类是指没有名字的内部类,它是一种简化的内部类语法,可以用来创建一个临时的、只使用一次的类。
匿名内部类通常用于实现接口或抽象类(花括号中的内容是匿名内部类的具体实现),匿名内部类可以使得代码更加简洁,但也会使得代码的可读性和可维护性降低,因此需要谨慎使用。
例如,下面的代码演示了如何使用匿名内部类实现一个 Runnable
接口:
Thread t = new Thread(new Runnable() {
public void run() {
// 线程执行的代码
}
});
t.start();