面向对象_高级1
1. 关键字:static
-
使用范围:
- 在Java类中,可用static修饰属性、方法、代码块、内部类
-
被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
1.1 静态变量
- 语法格式
[修饰符] class 类{
[其他修饰符] static 数据类型 变量名;
}
- 特点
-
静态变量的默认值规则和实例变量一样。
-
静态变量值是所有对象共享。
-
静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。
-
如果权限修饰符允许,在其他类中可以通过“
类名.静态变量
”直接访问,也可以通过“对象.静态变量
”的方式访问(但是更推荐使用类名.静态变量的方式)。 -
静态变量的get/set方法也静态的,当局部变量与静态变量
重名时
,使用“类名.静态变量
”进行区分。
-
1.2 静态方法
- 语法格式
[修饰符] class 类{
[其他修饰符] static 返回值类型 方法名(形参列表){
方法体
}
}
- 特点
- 静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
- 只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
- 在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
- 静态方法可以被子类继承,但不能被子类重写。
- 静态方法的调用都只看编译时类型。
- 因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。
2. 单例设计模式
- 单例设计模式(Singleton Design Pattern)是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来访问该实例。单例模式在需要确保全局唯一性或控制资源的访问时非常有用。下面介绍两种实现方式:
- 饿汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}
- 私有化构造器的目的是防止外部类通过
new
关键字创建Singleton
类的实例。这样可以确保这个类只能通过内部的静态方法来创建实例。- 在类内部创建一个
Singleton
类的唯一实例,并将其赋值给一个静态变量single
。这个变量是类级别的,不依赖于任何对象实例。当类加载时,就会创建single
实例,确保只有一个实例存在。- 提供一个公共的静态方法
getInstance()
,用于返回Singleton
类的唯一实例。
- 懒汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
}
- 将构造器设为私有,防止外部类通过
new
关键字直接创建Singleton
类的实例。这确保了外部无法直接实例化该类,从而保证了单例模式的唯一性。- 声明一个静态变量
single
来持有Singleton
类的唯一实例。这个变量是static
的,意味着它是类级别的,不依赖于任何实例。初始时,single
是null
,表示尚未创建实例。- 提供一个公共的静态方法
getInstance()
,用于获取Singleton
类的唯一实例。这个方法是类级别的,可以在没有实例的情况下调用。
3. main 方法的语法
在 Java 中,main
方法是程序的入口点。Java 虚拟机(JVM)从 main
方法开始执行程序的代码。理解 main
方法的语法和功能对于编写 Java 程序至关重要。以下是对 main
方法语法的详细解释:
3.1 main
方法的基本语法
public static void main(String[] args) {
// 程序的代码
}
3.2 语法分析
-
public
:- 含义: 访问修饰符
public
表示main
方法可以被任何类访问。因为main
方法是程序的入口点,JVM 需要能够从外部调用它,所以必须将其声明为public
。
- 含义: 访问修饰符
-
static
:- 含义: 关键字
static
表示main
方法是静态的。静态方法属于类而不是类的实例。这意味着main
方法可以在没有创建类的实例的情况下被调用。JVM 不会先创建类的实例就直接调用main
方法,因此main
方法必须是静态的。
- 含义: 关键字
-
void
:- 含义:
void
表示main
方法没有返回值。main
方法的目的是启动程序,而不是返回数据给调用者。
- 含义:
-
main
:- 含义:
main
是方法名,JVM 根据这个方法名来识别程序的入口点。main
是一个标准名称,不可以更改。
- 含义:
-
String[] args
:- 含义:
String[]
是main
方法的参数类型,表示接收一个字符串数组。这个数组用来传递命令行参数给程序。 - 使用: 通过命令行运行 Java 程序时,可以传递参数,这些参数会作为字符串数组传递到
main
方法。例如,如果在命令行中运行java MyClass arg1 arg2
,那么args
数组将包含"arg1"
和"arg2"
。
- 含义:
4. 类的成员:代码块
在Java中,类的成员不仅包括字段(成员变量)和方法,还包括代码块(Code Blocks)。代码块分为静态代码块、实例代码块、同步代码块和局部代码块。每种代码块都有特定的用途和执行顺序。下面我们来全面介绍这些代码块:
4.1 静态代码块(Static Initialization Block)
-
定义: 静态代码块是使用
static
关键字定义的代码块。它在类加载时执行,并且只执行一次,无论创建多少个类的实例。 -
作用: 用于初始化静态变量,或在类加载时需要执行的逻辑。
-
执行时机: 静态代码块在类的静态成员(如静态变量和静态方法)被访问前,或者类的第一个对象被创建之前执行。
-
语法:
class MyClass { static { // 静态代码块的内容 System.out.println("Static block executed"); } }
-
示例:
class Example { static int counter; static { counter = 10; System.out.println("Static block executed, counter = " + counter); } public Example() { System.out.println("Constructor executed"); } } public class Main { public static void main(String[] args) { Example ex1 = new Example(); // 静态代码块在类加载时执行一次 Example ex2 = new Example(); // 此时不会再次执行静态代码块 } }
4.2 实例代码块(Instance Initialization Block)
- 定义: 实例代码块不使用
static
关键字,是在类中直接定义的代码块。每次创建对象时,都会执行一次实例代码块。 - 作用: 用于初始化实例变量,或者需要在构造器之前执行的逻辑。
- 执行时机: 实例代码块在每次调用构造器之前执行,无论使用哪个构造器。
- 语法:
class MyClass { { // 实例代码块的内容 System.out.println("Instance block executed"); } }
- 示例:
class Example { int id; { id = 100; System.out.println("Instance block executed, id = " + id); } public Example() { System.out.println("Constructor executed"); } public Example(int id) { this.id = id; System.out.println("Constructor with parameter executed, id = " + id); } } public class Main { public static void main(String[] args) { Example ex1 = new Example(); // 实例代码块和无参构造函数依次执行 Example ex2 = new Example(200); // 实例代码块和有参构造函数依次执行 } }
4.3 同步代码块(Synchronized Block)
- 定义: 同步代码块使用
synchronized
关键字定义,确保多线程环境下,某一代码块在同一时间只能被一个线程执行,从而避免线程安全问题。 - 作用: 保证代码块在多线程环境下的原子性和可见性。
- 执行时机: 当多个线程尝试访问同步代码块时,只有获得锁的线程才能进入,其他线程必须等待。
- 语法:
class MyClass { public void myMethod() { synchronized (this) { // 同步代码块的内容 } } }
- 示例:
class Counter { private int count = 0; public void increment() { synchronized (this) { count++; System.out.println("Count: " + count); } } } public class Main { public static void main(String[] args) { Counter counter = new Counter(); Runnable task = () -> { for (int i = 0; i < 5; i++) { counter.increment(); } }; Thread t1 = new Thread(task); Thread t2 = new Thread(task); t1.start(); t2.start(); } }
4.4 局部代码块(Local Block)
- 定义: 局部代码块是在方法内部或构造函数内部使用
{}
括起来的代码段,用于限制变量的作用域或控制执行流程。 - 作用: 在特定范围内定义变量,以节省内存或组织代码逻辑。
- 执行时机: 局部代码块在方法或构造函数被调用时,按照顺序执行。
- 语法:
public void myMethod() { { int x = 10; System.out.println("Local block executed, x = " + x); } // 这里 x 已经超出了作用域,无法访问 }
- 示例:
public class Main { public static void main(String[] args) { { int temp = 5; System.out.println("Local block, temp = " + temp); } // temp 变量在此处已经超出作用域,无法再访问 } }
总结
- 静态代码块: 在类加载时执行,主要用于初始化静态变量或在类加载时需要执行的代码。
- 实例代码块: 在每次创建对象时执行,用于初始化实例变量或在构造函数之前执行的代码。
- 同步代码块: 在多线程环境下使用,确保代码块在同一时间只能被一个线程执行,保证线程安全。
- 局部代码块: 用于限制变量作用域或组织代码逻辑,通常在方法或构造函数内部使用。
举例:分析加载顺序
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
//new Leaf();
}
}
本例解析:
这个问题涉及到构造器链的细节,以及Java如何确保在初始化子类对象时,父类部分先被正确初始化。
Java构造器的执行顺序
- 在Java中,当你创建一个对象时,构造器的执行顺序遵循以下规则:
-
先调用父类的构造器:在任何子类构造器中,如果没有显式调用
super(...)
,Java会在子类构造器的第一行自动插入super()
,即调用父类的无参构造器。 -
初始化父类部分:在父类的构造器执行完之前,子类的构造器不会执行。
-
执行子类的实例初始化块:父类构造器完成后,再执行子类的构造器和实例初始化块。
-
具体执行流程:我们先看这一部分代码
class Mid extends Root {
public Mid() {
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg) {
this(); // 调用当前类的无参数构造器 Mid()
System.out.println("Mid的带参数构造器,其参数值:" + msg);
}
}
class Root {
public Root() {
System.out.println("Root的无参数的构造器");
}
}
当执行 new Leaf()
时,Leaf
类的构造器调用了 super("尚硅谷")
,这会调用 Mid
的带参数构造器 Mid(String msg)
。让我们详细分析这个过程中每一步的执行:
Mid(String msg) 的执行
super("尚硅谷")
会调用Mid(String msg)
构造器。Mid(String msg)
构造器的第一行是this()
,这意味着调用Mid()
无参构造器。
Mid() 无参构造器的执行
- 当
this()
调用Mid()
构造器时(这一步非常关键!!!)- 根据Java的构造器调用规则,在
Mid()
构造器执行之前,必须先完成对其父类Root
的初始化。 - 因此,Java会自动在
Mid()
的构造器的最前面插入一个隐式的super()
,去调用Root
的无参构造器。
- 根据Java的构造器调用规则,在
Root() 构造器的执行
Root()
构造器执行,输出Root的无参数的构造器
。Root
类的构造器执行完毕后,控制权返回到Mid()
无参构造器中。
Mid() 无参构造器的执行
- 在
Root
类的构造器完成后,Mid()
构造器继续执行,输出Mid的无参数的构造器
。
回到 Mid(String msg) 构造器
- 完成
Mid()
构造器的执行后,控制权返回到Mid(String msg)
构造器继续执行,输出Mid的带参数构造器,其参数值:尚硅谷
。
总结:
在 this()
调用 Mid()
无参构造器时,Java需要确保父类 Root
部分已经被初始化。因此,首先会隐式调用 Root()
构造器。只有在 Root
构造器完成后,Mid()
构造器才会执行,这也是为什么在 this()
调用后,先执行 Root
构造器,而不是立即执行 Mid
的无参构造器。
这个过程确保了在任何对象创建时,父类部分的初始化总是在子类部分之前完成,从而保证了整个对象的正确构造和初始化。