1.Java的面向对象
Java 是一种面向对象的编程语言,这意味着它的设计和编程方法都集中在对象的概念上。以下是 Java 面向对象编程的一些关键概念和特性:
-
类与对象:在 Java 中,类是对象的模板或蓝图,它定义了对象的属性(字段)和行为(方法)。对象是类的实例化,它具体化了类的结构。
-
封装:封装是指将数据(字段)和操作数据的方法(方法)捆绑在一起的概念。在 Java 中,通过访问控制修饰符(例如 public、private、protected)来实现封装,以控制数据的访问。
-
继承:继承是指一个类可以继承另一个类的属性和方法。通过继承,子类可以重用父类的代码,并且可以扩展或修改父类的行为。
-
多态:多态是指同一个方法在不同的对象上可以表现出不同的行为。在 Java 中,多态可以通过方法重写(override)和方法重载(overload)来实现。
-
抽象类与接口:抽象类是一种不能实例化的类,它定义了一些方法的签名但没有具体的实现,需要子类来实现具体方法。接口是一种完全抽象的类,它只包含方法的签名,所有的方法都是抽象的。Java 中的类可以实现多个接口,但只能继承一个类。
-
构造方法与析构方法:构造方法是在创建对象时被调用的特殊方法,用于初始化对象的状态。Java 中的构造方法与类同名,没有返回类型,并且可以被重载。Java 中没有析构方法,但是可以通过垃圾回收器来自动释放不再使用的对象。
-
关键字
this
和super
:this
关键字用于引用当前对象,可以在构造方法、实例方法和代码块中使用。super
关键字用于引用父类的属性和方法,可以在子类中调用父类的构造方法和实例方法。
这些是 Java 面向对象编程的一些关键概念和特性。通过这些概念,Java 提供了一种灵活、可扩展和易于维护的编程模式,使得开发者可以更加高效地构建复杂的应用程序。
2.Java的数据类型
Java 语言中的数据类型可以分为两大类:原始数据类型(Primitive Data Types)和引用数据类型(Reference Data Types)。
原始数据类型(Primitive Data Types):
-
整数类型(Integral Types):
byte
:1 字节,范围为 -128 到 127。short
:2 字节,范围为 -32768 到 32767。int
:4 字节,范围为 -2^31 到 2^31 - 1。long
:8 字节,范围为 -2^63 到 2^63 - 1。
-
浮点类型(Floating-Point Types):
float
:4 字节,单精度,范围约为 ±3.40282347E+38F(有效位数约为 6-7 位)。double
:8 字节,双精度,范围约为 ±1.79769313486231570E+308(有效位数约为 15 位)。
-
字符类型(Character Type):
char
:2 字节,Unicode 字符,范围为 0 到 65535。
-
布尔类型(Boolean Type):
boolean
:用于表示逻辑值,只能取true
或false
。
引用数据类型(Reference Data Types):
-
类(Class):类是用户自定义的数据类型,它可以包含字段、方法、构造方法等。
-
接口(Interface):接口定义了一组方法的签名,没有具体的实现。类可以实现接口,从而实现接口定义的方法。
-
数组(Array):数组是相同类型数据的有序集合,可以是基本数据类型的数组或对象数组。
-
枚举(Enum):枚举类型是一种特殊的类,用于定义一组常量。
-
其他引用类型:包括自定义的引用类型、集合类、字符串类型等。
Java 的数据类型具有严格的定义和规范,开发者可以根据应用的需求选择合适的数据类型。原始数据类型用于存储简单的数值,而引用数据类型用于存储复杂的对象和数据结构。
3.Java的数据类型转换
在 Java 中,类型转换分为自动类型转换(隐式类型转换)和强制类型转换(显式类型转换)两种方式。
自动类型转换(隐式类型转换):
自动类型转换发生在目标类型的容量大于源类型的情况下,也就是说,数据从小范围类型转换为大范围类型时,编译器会自动完成类型转换,无需显式地进行说明。
例如:
int numInt = 10;
double numDouble = numInt; // 自动将 int 类型转换为 double 类型
在这个例子中,int
类型的 numInt
变量被自动转换为 double
类型的 numDouble
变量。
强制类型转换(显式类型转换):
强制类型转换发生在目标类型的容量小于源类型的情况下,需要使用强制类型转换运算符((type)
)将数据从一个类型转换为另一个类型。
例如:
double numDouble = 10.5;
int numInt = (int) numDouble; // 强制将 double 类型转换为 int 类型
在这个例子中,double
类型的 numDouble
变量被强制转换为 int
类型的 numInt
变量。
需要注意的是,强制类型转换可能导致数据丢失或截断,因此在进行强制类型转换时需要确保转换后的值在目标类型范围内。
4.String 的不可变性
在 Java 中,字符串(String)是不可变的,这意味着一旦创建了一个字符串对象,它的值就不能被修改。这种不可变性带来了一些优点,例如线程安全、缓存利用和安全性。
不可变性的优点:
-
线程安全:由于字符串是不可变的,多个线程可以安全地共享字符串对象而不需要额外的同步控制。
-
缓存利用:因为字符串是不可变的,可以被缓存起来,以提高性能和减少内存占用。
-
安全性:不可变的字符串在传递过程中不会被修改,可以避免一些安全漏洞,比如 SQL 注入攻击。
5. String.intern() 的底层原理
String.intern()
方法的底层原理涉及到 Java 中字符串常量池的管理机制。每个 JVM 中都有一个字符串常量池,用于存储字符串常量对象,以便于重用。当调用 intern()
方法时,会检查常量池中是否已经存在相同值的字符串,如果存在,则返回常量池中的字符串引用;如果不存在,则将当前字符串对象添加到常量池中,并返回该引用。
下面是 String.intern()
方法的大致实现原理:
- 当调用
intern()
方法时,首先检查当前字符串对象是否已经在常量池中存在。 - 如果常量池中已经存在相同值的字符串,则返回常量池中的字符串引用。
- 如果常量池中不存在相同值的字符串,则将当前字符串对象添加到常量池中,并返回该引用。
以下是一个简单的示例,说明了 String.intern()
方法的使用:
String s1 = new String("hello"); // 创建了两个对象:一个是常量池中的 "hello",一个是堆中的字符串对象
String s2 = "hello"; // 直接从常量池中获取 "hello",不会再创建新的对象
String s3 = s1.intern(); // 返回常量池中的 "hello" 的引用
System.out.println(s1 == s2); // false,s1 指向堆中的对象,s2 指向常量池中的对象 System.out.println(s2 == s3); // true,s2 和 s3 都指向常量池中的同一个对象
需要注意的是,intern()
方法不会在堆中创建新的字符串对象,它只是返回常量池中相同值的字符串对象的引用。因此,如果字符串常量池中已经存在相同值的字符串,则 intern()
方法返回的是常量池中的引用;如果字符串常量池中不存在相同值的字符串,则 intern()
方法将当前字符串对象添加到常量池中,并返回该引用。
6.Java虚拟机的常量池
Java 虚拟机的常量池(Constant Pool)是一种特殊的内存区域,用于存储字符串常量、类和接口的全限定名、字段名、方法名、字面量等。常量池是在编译期间被创建的,并且包含在每个类文件(.class 文件)中。
常量池的主要作用包括:
- 存储字符串常量:所有通过双引号(" ")定义的字符串常量都会被存储在常量池中。例如:"hello"。
- 存储类和接口的全限定名:类和接口的全限定名会被存储在常量池中。例如:java.lang.String。
- 存储字段名和方法名:字段名和方法名也会被存储在常量池中。
- 存储字面量:例如整数、浮点数、布尔值等字面量。
- 存储符号引用:用于解析类、字段和方法的符号引用。
- 存储一些特殊的符号:例如表示无效索引、空引用等特殊情况。
常量池的特点包括:
- 常量池是每个类文件独有的,而不是每个类独有的。这意味着如果两个类文件中都使用了相同的字符串常量,那么它们都会在各自的常量池中存储一份相同的字符串常量。
- 常量池中的字符串常量是不可变的,这与 Java 中字符串的不可变性相符。
- 常量池中的字面量和符号引用在运行时被解析为实际的内存地址。
- 常量池的大小是有限的,不同的 JVM 实现会有不同的限制,超出限制后可能会导致
java.lang.OutOfMemoryError: PermGen space
错误。 - 在 Java 7 及之前的版本中,常量池是存储在永久代(Permanent Generation)中;在 Java 8 及之后的版本中,常量池被移到了堆中,因此常量池的大小受到了堆大小的限制。
总之,常量池是 Java 虚拟机中重要的一部分,它为 Java 提供了一种高效存储和管理字符串常量以及其他常量的机制。
7.Java关键字及底层原理
7.1 fina关键字
7.2 static关键字
在 Java 中,static
关键字用于声明静态成员变量和静态方法。静态成员变量属于类而不是对象,静态方法可以直接通过类名调用,无需创建对象。
-
静态成员变量的底层原理:
- 静态成员变量在类加载的过程中被初始化,而不是在创建对象的时候。因此,所有的对象共享同一份静态成员变量。
- 在类加载时,Java 虚拟机(JVM)会为每个类的静态成员变量分配内存空间,并进行初始化。静态成员变量的初始化可以在变量声明时直接赋值,或者在静态代码块中进行赋值。
- 在内存中,静态成员变量被存储在方法区(Java 8 之前称为永久代,Java 8 及之后称为元空间)中,它们的值可以被所有的对象访问和修改。
-
静态方法的底层原理:
- 静态方法不依赖于对象的实例,可以直接通过类名调用。
- 在类加载时,静态方法被加载到方法区中,并且可以在任何时候被调用。
- 静态方法中不能直接访问类的非静态成员变量或非静态方法,因为它们需要通过对象来访问,而静态方法不依赖于对象。
- 静态方法通常用于工具类、辅助方法、工厂方法等,不需要依赖对象状态的操作。
总的来说,static
关键字的底层原理涉及类加载、内存分配和方法调用等方面。通过静态成员变量和静态方法,可以在不创建对象的情况下直接访问类的成员和方法,提供了更灵活和方便的编程方式。
7.3 transient关键字
在 Java 中,transient
关键字用于修饰成员变量,表示该变量不会被序列化。这意味着当对象被序列化时,被 transient
修饰的成员变量将不会被保存到序列化的结果中,而在对象被反序列化时,这些变量会被赋予默认值。
-
序列化:
- 当对象被序列化时,Java 虚拟机会将对象的状态转换为字节流,以便于传输或者持久化存储。
- 在序列化过程中,Java 虚拟机会忽略被
transient
修饰的成员变量,不会将其转换为字节流中的一部分。 - 因此,被
transient
修饰的成员变量不会被包含在序列化结果中,不会被保存到文件或者网络中。
-
反序列化:
- 当对象被反序列化时,Java 虚拟机会将字节流转换为对象的状态,以恢复对象的原始状态。
- 在反序列化过程中,被
transient
修饰的成员变量会被赋予默认值,而不是根据序列化结果来初始化。 - 对于基本数据类型,被
transient
修饰的成员变量会被赋予其对应的默认值(如 0、false 等);对于引用类型,被transient
修饰的成员变量会被赋予null
值。
总的来说,transient
关键字的底层原理涉及到对象的序列化和反序列化过程,它控制了对象在序列化和反序列化时哪些成员变量会被包含在结果中。通过使用 transient
关键字,可以在需要时排除某些敏感或临时性的成员变量,以保护数据的安全性或者减少序列化结果的大小。
------------------------------------------------------未完待续------------------------------------------------------------