当前位置: 首页 > news >正文

类的高级特性与语法细节

static 静态关键字

Java中的static关键字用于修饰类的成员(属性或方法),表示“静态”的含义,即属于类本身,而非某个对象。静态成员在内存中只有一份,在类加载时初始化,生命周期贯穿程序运行始终。下面分别介绍静态变量、静态方法和静态代码块,并结合示例说明其作用和特性。

静态变量(类变量)

定义与特性: 静态变量也称为“类变量”,由static修饰。与实例变量不同,静态变量属于类本身,对所有实例来说只有一份拷贝。也就是说,无论创建多少对象,静态变量都指向同一块存储空间,所有对象共享这一变量。当其中一个对象修改静态变量的值时,其他对象访问该静态变量时也能看到更新后的值。静态变量在类加载时就被分配内存(存储于方法区或元空间中),在程序结束或类卸载时才会释放。

生命周期比喻: 可以把类比作一座学校,类的实例对象就是学校里的每一个学生,那么静态变量就好比学校的公共公告板。公告板是属于整所学校的(属于类),所有学生(实例)都共享同一块公告板的信息。如果公告板上写着当前在校学生总数,那么每新增或减少一个学生,这个数字都会更新,所有人看到的都是同一个总人数。这块公告板在学校建立时就挂好了(类加载时初始化),直到学校关闭(程序结束/类卸载)才移除。无论有多少学生进出,这个公告板始终存在且只有一块。

示例: 假设我们有一个学生类,每当创建一个学生对象时,希望自动累加学生总数。这时就适合使用静态变量来记录学生总数:

class Student {String name;                  // 实例变量,每个对象都有自己的namestatic int totalStudents = 0; // 静态变量,所有对象共享public Student(String name) {this.name = name;totalStudents++; // 每创建一个学生对象,累计总数加1}
}public class TestStaticVar {public static void main(String[] args) {Student s1 = new Student("小明");Student s2 = new Student("小红");System.out.println("学生总数: " + Student.totalStudents); // 输出学生总数}
}

运行上面的代码,会输出:

学生总数: 2

这里我们通过Student.totalStudents访问静态变量,得到总数2。注意可以直接用类名访问静态变量,而不需要实例对象。这段代码演示了静态变量的共享特性:无论创建多少个Student对象,totalStudents在内存中只有一份,其值随每次构造函数调用而更新。

类变量 vs 实例变量:

  • 内存位置: 实例变量存储在堆内存的对象中;静态变量存储在方法区(或者JVM的元数据区)中。
  • 生命周期: 实例变量随着对象创建而存在,对象被垃圾回收后消失;静态变量随着类加载而存在,直到类卸载或JVM退出才消失。
  • 作用域: 实例变量必须通过对象引用访问;静态变量可以通过类名直接访问(当然也能通过对象访问,但会收到警告不建议这样用)。
  • 共享性: 每个对象都有自己的一份实例变量,互不影响;静态变量在类级别共享,所有实例共用同一份数据。
静态方法(类方法)

定义与特性: 静态方法是使用static修饰的方法,又称“类方法”。静态方法同样属于类本身,而不是某个对象,所以无需创建对象就可以调用。调用方式一般是ClassName.methodName()。静态方法常用于工具类或不依赖于实例状态的方法。例如Math.max(a,b)Arrays.sort(array)都是静态方法。静态方法在类加载时就可用,且由于没有this对象,它无法直接访问实例变量和实例方法,在静态方法内部也无法使用thissuper关键字(因为此时可能尚未有对象实例,this无所指)。静态方法只能访问本类的静态变量或调用静态方法,这一点需要特别注意。

使用场景比喻: 如果把类比作一个工厂模具,那么实例方法好比每个具体产品上的操作按钮,只能操作该产品自身的数据;而静态方法则像是工厂车间里公共可用的工具,不针对某个产品,它可以直接被拿来使用。例如,一个温度转换工具类可以提供静态方法把摄氏度转换为华氏度,这种操作不需要也不依赖特定的对象状态,就适合定义为静态方法。

示例: 我们在前述Student类中加入一个静态方法,用于打印当前学生总数:

class Student {// ...(前略,仍包含name和totalStudents等定义)// 静态方法:打印学生总数static void printTotalStudents() {System.out.println("当前学生总数: " + totalStudents);// 注意:静态方法中无法直接访问非静态成员,例如不能访问 name}
}public class TestStaticMethod {public static void main(String[] args) {// 未创建对象也可以调用静态方法Student.printTotalStudents(); // 输出: 当前学生总数: 0Student s1 = new Student("Alice");Student s2 = new Student("Bob");Student.printTotalStudents(); // 输出: 当前学生总数: 2}
}

在上面的代码中,Student.printTotalStudents()可以直接调用而不需要学生对象。第一次调用输出0(此时还没有创建学生实例,totalStudents初始为0),后续创建了两个学生再调用时输出2。静态方法无法访问实例变量,比如在printTotalStudents()中我们不能直接访问name属性,否则编译错误。这验证了静态方法只能操作静态数据或通过参数获取所需信息。

静态代码块

定义与作用: 静态代码块(Static Initialization Block)是用static { ... }包裹的一段代码块,位于类定义中。静态代码块在类加载时执行,并且只执行一次。它通常用于初始化静态变量的复杂逻辑,或执行类级别的一次性设置操作。静态块会在类被加载后、对象创建前就执行,比构造函数更早。若一个类中有多个静态块,执行顺序按照它们在类中出现的先后顺序。并且如果静态代码块和主方法在同一个类中,静态块的执行优先级高于main方法。

执行时机比喻: 把类加载想象成剧院开演前的准备阶段,静态代码块就是提前布置舞台的过程。观众(对象)还没入场之前,先把音响、灯光等准备好。这些准备工作(静态块)在开场时执行一次即可,所有场次通用,而不需要每个观众进场都重新布置舞台。

示例: 演示静态代码块的执行顺序:

class Example {static int staticVar;static {// 静态代码块System.out.println("静态代码块初始化");staticVar = 100;}public Example() {System.out.println("构造函数执行");}static {// 第二个静态代码块(如果有多个,按顺序执行)System.out.println("第二个静态代码块,被调用时staticVar=" + staticVar);}
}public class TestStaticBlock {public static void main(String[] args) {System.out.println("main方法开始");Example ex = new Example(); // 首次创建Example对象Example ex2 = new Example(); // 再次创建Example对象}
}

运行结果可能如下:

静态代码块初始化  
第二个静态代码块,被调用时staticVar=100  
main方法开始  
构造函数执行  
构造函数执行  

从输出可以看出:当Example类第一次被使用(这里是在main中首次创建对象)时,两个静态代码块按照顺序被执行了一次,静态变量staticVar在静态块中被初始化为100。随后进入main方法,创建第一个对象时调用构造函数,输出“构造函数执行”。创建第二个对象时,静态代码块没有再次执行(因为类已加载过),只调用构造函数。这说明静态代码块仅在类加载时运行一次。并且,静态代码块在main开始前已经执行完毕(即使它在代码中写在后面)。如果没有创建对象而直接使用类的静态成员,静态代码块也会在类加载时执行。例如若我们仅调用Example.staticVar,静态块依然会执行一次。

总结 static 要点
  • 加载时机: 类首次被加载(调用静态成员或创建对象时),静态变量分配内存并初始化,静态代码块执行。静态成员存在于类的生命周期中。
  • 共享特性: 静态成员在所有实例中共享一份,适合描述整个类的公共属性(如计数器、常量等)。
  • 访问方式: 建议通过类名.静态成员访问静态变量和方法。静态方法中不能直接使用实例成员或this关键字。
  • 典型应用: 工具类方法(如Math类),记录全局状态的数据(如缓存、计数),定义常量(与final结合)等。

理解了static关键字的行为,可以编写出更高效和语义清晰的代码。例如,将不随对象变化的属性设计为静态,可以减少每个对象的存储开销;将与对象无关的功能方法设计为静态,可以直接用类名调用,方便快捷。

final 关键字

Java中的final关键字表示“最终的、不可改变的”含义,可用于修饰变量、方法和类。被final修饰的元素具有以下含义:

  • final变量:值一旦初始化之后就无法更改(相当于常量)。
  • final方法:不能被子类重写(override)。
  • final类:不能被继承。

下面我们分别讲解这三种用法,并重点说明final在基本类型和引用类型变量上的区别。

final 修饰变量(常量)

final用于修饰变量时,该变量的值在初始化后便不可再修改。根据变量类型不同,其含义稍有区别:

  • 基本数据类型:被final修饰后,其数值在初始化后无法改变。
  • 引用类型:被final修饰后,引用在初始化后将一直指向同一个对象,不能指向别的对象。但引用指向的对象本身是可变的(除非对象自身是不可变类),也就是说可以修改对象内部的状态。

初始化要求: final变量必须在声明时或构造函数中被初始化一次。一旦赋值完成,就不能再重新赋值。如果试图在后续代码中修改其值,编译器会报错。

常量命名约定: 一般将final静态变量(即类常量)命名为全大写字母并用下划线分隔,例如:public static final int MAX_VALUE = 100;。这是一种代码规范,提示阅读者此变量是常量。

示例(基本类型):

class Constants {public static final double PI = 3.14159; // 定义一个常量
}
public class TestFinalVar {public static void main(String[] args) {System.out.println("圆周率: " + Constants.PI);// Constants.PI = 3.14; // 编译错误,无法给final变量赋值final int num = 5;// num = 6; // 编译错误,final基本类型值不可更改}
}

以上代码中,Constants.PI被定义为final且初始化为3.14159,之后无法再修改。尝试赋值Constants.PI = 3.14会导致编译错误。同样,局部变量num如果声明为final且赋值5,后续也不能重新赋值为6。final保证了这些值的不可变性。

示例(引用类型):

public class TestFinalReference {public static void main(String[] args) {final ArrayList<String> list = new ArrayList<>();list.add("Hello");list.add("World");System.out.println(list); // 输出: [Hello, World]// 修改引用本身:// list = new ArrayList<>(); // 编译错误,无法改变final引用指向// 但是可以修改对象内容:list.set(1, "Java");System.out.println(list); // 输出: [Hello, Java]}
}

在这个例子中,我们将list声明为final,并初始化为一个新的ArrayList对象。final保证了list引用会一直指向这个ArrayList对象。但通过list.addlist.set我们仍可以向列表中添加、修改元素——这些操作改变的是对象内部的数据,并不违反final约束。唯独尝试令list指向一个新的ArrayList实例会导致编译错误,因为list引用不可变。用生活中的比喻来说,final引用好比一张绑定的“车票”,一旦指定了目的地,就不能改签到别的地点,但你在目的地的活动(对象内部状态改变)不受影响。

特殊情况: 如果需要一个既是final又不可变的对象,那么对象本身也需要设计成不可变类(例如String类就是不可变的且引用常用final来修饰)。final关键字本身并不使对象内容不可变,它仅保证引用不改变或基本类型值不改变。

final 修饰方法

当一个方法被声明为final时,表示子类无法重写该方法的实现。父类的final方法对所有子类是封闭的,子类只能继承使用,不能修改行为。这在需要保持方法逻辑不被改变时很有用,例如一些安全性要求高或框架底层的方法,防止子类意外改变其功能。

特点:

  • final方法仍然可以被子类继承调用,但不能有与之同签名的override方法出现于子类,否则编译错误。
  • 将方法声明为final可能有助于编译器做性能优化(早期Java中有这个考虑,不过在现代JVM中,方法内联优化已经不依赖final关键字了)。主要原因还是出于设计和安全考虑。

示例:

class Animal {public final void sleep() {System.out.println("动物正在睡觉");}
}
class Dog extends Animal {// 试图重写sleep方法会导致编译错误// @Override// public void sleep() {//     System.out.println("小狗睡觉");// }
}
public class TestFinalMethod {public static void main(String[] args) {Dog dog = new Dog();dog.sleep(); // 调用继承自Animal的final方法}
}

在上述代码中,Animal类的sleep()方法被声明为final,因此Dog子类无法重写它。如果取消注释Dog中的sleep()方法,会出现编译错误:“无法重写最终方法”。运行dog.sleep()将直接调用父类Animal中定义的实现,输出“动物正在睡觉”。通过将方法设为final,保证了sleep方法的行为对所有动物子类都一致,不会被修改。

何时使用final方法: 当你设计一个类并希望某些方法的实现对子类是“固定的”或者出于安全考虑不想让子类改变它,就可以将该方法声明为final。例如java.lang.Object中的getClass()方法就是final的,保证了它始终返回正确的类信息,不能被篡改。

final 修饰类

当一个类被声明为final时,表示该类不能被继承final类通常出现在两种场景:

  1. 设计上不需要也不希望被继承:有些类天然就是最终形态,比如工具类、常量类,继承它们没有意义或者可能导致错误。
  2. 安全和不可变需求:有些类为了保证不可变性或安全性,禁止继承,防止子类破坏其性质。例如java.lang.String就是一个final类,任何人都不能定义一个子类去修改String的行为。这确保了字符串不可变的特性。

特点:

  • 所有的final类中的方法默认也隐式地是final(因为既然类无法继承,就不存在重写方法的问题)。因此final类的方法不需要显式声明为final(虽然语法上可以加,但没有意义)。
  • final类仍然可以实例化使用,但不能被作为父类。试图继承final类会导致编译错误。

示例:

final class Utility {public void doSomething() {System.out.println("执行某个操作");}
}
// 下面的代码如果取消注释将无法编译,因为Utility是final的
// class SubUtility extends Utility {}  // 编译错误:无法从最终类继承

Utility被声明为final,意味着不允许有子类。任何试图extends Utility的行为都会得到编译器错误提示。“最终类”在Java标准库中也很常见,例如java.lang.Math类就是final的,里面全是静态方法和常量;包装类如Integer, Double等也是final的,确保了它们的可靠性。

需要谨慎使用final来修饰类。因为一旦将类定义为final,就剥夺了其扩展的可能性。在框架设计中,一般只在必要时才将类设为final,过度使用可能降低代码的灵活性。

final 对基本类型和引用类型的不同点

这一点之前在final变量部分已阐述,总结如下:

  • 对于基本数据类型final变量,赋值后其值不可改变。这就是真正的常量,例如final int DAYS_IN_WEEK = 7;
  • 对于引用类型final变量,一旦引用指向某个对象后,就不能再改指向别的对象。但引用指向的对象的内部状态如果允许修改(对象是可变的),那么这种修改是被允许的。要获得真正不可变的对象,需要对象本身设计为不可变类(所有属性也用final且不提供修改方法)。

误区澄清: final和“不变”之间有关联但不完全相同。final保证的是引用不可变或值不可变,但如果希望一个对象完全不可变,需要将对象的所有字段也声明为final且不提供修改这些字段的方法。例如String类的实现中,内部字符数组是final且不提供修改方法,所以String对象一经创建内容就无法改变。换句话说,final关键字是构建不可变类的一块基石,但仅有final关键字并不足以确保对象不可变,设计不可变类还需遵守其他原则。

Object 类的核心方法

所有Java类都直接或间接继承自java.lang.Object类。Object是Java类层次的根,在它里面定义了一些非常重要的方法,几乎每个类都会用到或需要重写它们。核心的几个Object方法包括:

  • toString():返回对象的字符串表示。
  • equals(Object obj):判断两个对象是否“内容相等”。
  • hashCode():返回对象的哈希码值。
  • clone():创建对象的拷贝(克隆)。
  • finalize():对象被垃圾回收前的回调方法(已过时,不建议使用)。

下面我们逐一介绍这些方法的作用、默认行为,并通过示例说明如何正确地重写和使用它们。同时会讨论==运算符与equals()方法的区别,以及hashCode()equals()的契约关系,浅拷贝与深拷贝的区别等。

注意: 由于这些方法都是Object类定义的,所以所有Java对象都拥有这些方法。在实际编码中,根据需要可以覆盖(override)其中的一些方法以改变默认行为。

toString() 方法

作用: toString()方法用于返回对象的字符串表示形式。它常用于打印、日志或调试,方便我们查看对象的内容。默认情况下,Object.toString()返回的是类名@对象的哈希码的十六进制字符串,例如Car@6d06d69c。这样的信息对用户而言没有实际意义,因此通常我们会在自己的类中重写toString()方法,使其返回更友好的内容描述。

默认实现:Object类中,toString()被实现为:

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

可见默认行为并不显示对象内部信息。我们可以覆盖它以提供对象状态的文本描述。

重写原则: toString()应当返回简明易读能够代表对象主要信息的字符串。比如一个Person对象,可以返回包含姓名和年龄的字符串;一个集合对象可以返回其元素列表。很多IDE能够自动生成toString()方法实现,或可以使用Objects.toStringHelper等工具帮助生成。良好的toString()实现对调试程序非常有帮助。

示例: 定义一个Person类并重写toString()

class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + "}";}
}
public class TestToString {public static void main(String[] args) {Person p = new Person("张三", 25);System.out.println(p);               // 等价于 System.out.println(p.toString());String info = "人员信息: " + p;      // 在字符串连接时会自动调用 toString()System.out.println(info);}
}

运行输出:

Person{name='张三', age=25}  
人员信息: Person{name='张三', age=25}

可以看到,我们自定义的toString()返回了Person对象的姓名和年龄,格式为Person{name='张三', age=25},比默认的类名@哈希码易读多了。当我们直接打印对象p时,Java会自动调用它的toString()方法,因此能看到定制的信息。同样,将对象与字符串拼接时也会隐式调用toString()

建议: 在开发中,养成重写toString()的习惯,有助于日志输出和调试。尤其是在集合、实体类中打印内容,可以快速洞察对象状态。要确保toString()不会引发NullPointerException等异常,并避免在toString()中执行复杂逻辑或改变对象状态——通常应仅用于返回信息。

equals() 方法 与 “==” 运算符

作用: equals(Object obj)方法用于判断当前对象与另一对象是否“内容相等”。需要强调的是,“相等”可以有不同的语义:默认实现中,equals()==效果相同,都是比较两个引用是否指向同一个对象实例。但许多类会重写equals()使其表示对象内容的等价,例如字符串内容相等、业务主键相等等。

== 运算符 vs equals():

  • == 运算符:对基本类型,==直接比较值是否相等;对引用类型,==比较的是两引用是否指向同一个对象(即内存地址是否相同)。
  • equals() 方法:是一个实例方法,默认实现也是比较引用相同(即调用==)。但是类可以重写它,自定义“相等”逻辑,使之比较对象的关键字段是否相同,以表示内容相等。

默认实现: Object.equals(Object obj)内部其实就是简单地 return (this == obj);。因此,如果不重写,equals==是等价的,都要求是同一对象才返回true。

为何要重写equals: 因为在很多情况下,我们更关心对象所代表的数据是否相同,而非是否同一对象。例如,两个内容完全相同的字符串应该被视为相等,即使它们是不同的对象实例;两个Person对象只要姓名和身份证号相同,也可以认为是同一个人。在这些情形下,需要重写equals()方法来实现“内容比较”。

equals()重写契约: 重写equals()时应遵守自反性、对称性、传递性、一致性,以及对任何非null x,x.equals(null)应返回false。这是《Effective Java》等著作强调的内容。简单来说:

  • 自反性:x.equals(x)必须返回true。
  • 对称性:如果x.equals(y)返回true,则y.equals(x)也应返回true。
  • 传递性:如果x.equals(y)和y.equals(z)为true,则x.equals(z)也应为true。
  • 一致性:如果对象参与比较的信息未改变,多次调用equals结果不变。
  • 非空性:任何对象都不应等于null(x.equals(null)应返回false)。

示例1(字符串的equals):

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);       // false,因为不是同一对象
System.out.println(s1.equals(s2));  // true,因为String类重写了equals比较字符序列

这里s1s2是两个不同的String对象,==比较结果为false。但String.equals()被重写为比较字符串内容,两个都包含"hello",所以返回true。这说明对于引用类型,一般应使用.equals()来比较内容而不是==,除非你确实想判断是否同一对象。

示例2(自定义类的equals):

class Person {String id;String name;public Person(String id, String name) {this.id = id;this.name = name;}@Overridepublic boolean equals(Object o) {if (this == o) return true;              // 同一个对象if (!(o instanceof Person)) return false; // 类型不匹配Person other = (Person) o;// 判断身份证号和姓名是否相等(假设身份证号相同则视为同一人)return this.id.equals(other.id) && this.name.equals(other.name);}@Overridepublic int hashCode() {// 简单组合两个字段计算hash(重写equals时通常也要重写hashCode,下面会详细说)return id.hashCode() * 31 + name.hashCode();}
}
public class TestEquals {public static void main(String[] args) {Person p1 = new Person("110101199001011234", "李四");Person p2 = new Person("110101199001011234", "李四");Person p3 = new Person("110101199002029999", "李四");System.out.println(p1 == p2);          // false,不同对象System.out.println(p1.equals(p2));     // true,身份证和姓名都相同System.out.println(p1.equals(p3));     // false,身份证不同}
}

在这个示例中,我们重写了Person.equals()使其根据idname来判断两个Person是否相等。p1p2内容完全相同,因此equals返回true,而p1 == p2是false,因为它们不是同一对象。对于p1p3,尽管姓名相同但身份证不同,被视为不同人,equals返回false。

重要提示: 重写equals()务必也重写hashCode()方法,这是下面将讨论的内容。否则,在将对象放入哈希集合(如HashMapHashSet)时会出现逻辑错误。

hashCode() 方法

作用: hashCode()返回对象的哈希码,表现为一个整数。哈希码用于在哈希集合(如HashSetHashMapHashtable等)中快速查找、检索对象。哈希码与equals紧密相关:根据Java规范,如果两个对象根据equals()比较是相等的,那么它们的hashCode()必须相等。反之,如果equals()不相等,则hashCode()可以不同。但如果hashCode()也碰巧相同(不同对象可能出现相同hash,这称为哈希冲突),equals仍需最终区分开它们。

默认实现: Object.hashCode()通常根据对象的内存地址计算出一个整数(不是实际地址,但可以认为是基于地址的散列结果)。所以默认情况下,不同对象哪怕内容一样,hashCode通常也不同。

为何要重写hashCode: 当我们重写equals()判定两个对象可以相等时,必须相应重写hashCode()使得相等的两个对象返回相同的哈希码。否则,这些对象放在例如HashSet中时,会被散列到不同桶,导致集合认为它们是不同的元素,违背我们对相等的定义。

equals 和 hashCode 契约:

  • 如果a.equals(b)为true,那么a.hashCode()必须等于b.hashCode()
  • 如果a.equals(b)为false,a.hashCode()b.hashCode()可以相等也可以不相等(哈希冲突允许存在,只是会降低散列容器性能)。
  • 反过来,没有要求hashCode相等的两个对象必须equals返回true——hashCode相等可能是碰巧冲突,也可能是设计如此,但equals可以进一步严格判断。

常见做法: 重写hashCode()时,通常选取equals中用到的关键字段来计算hash值。比如上例Person用了id和name计算哈希。计算方法可以参考JDK中Objects.hash(Object... values)或IDE自动生成的方式,确保得到一个尽量均匀分布且与equals一致的结果。

示例(hashCode影响集合行为):

class Point {int x, y;public Point(int x, int y) { this.x = x; this.y = y; }@Overridepublic boolean equals(Object o) {if (!(o instanceof Point)) return false;Point other = (Point) o;return this.x == other.x && this.y == other.y;}// 注意:故意不重写hashCode()
}
public class TestHashCode {public static void main(String[] args) {Point pt1 = new Point(1, 2);Point pt2 = new Point(1, 2);System.out.println(pt1.equals(pt2)); // true,内容相等HashSet<Point> set = new HashSet<>();set.add(pt1);set.add(pt2);System.out.println("set大小: " + set.size());}
}

在这个例子中,Point.equals()判断两个点坐标都相等就返回true,但我们没有重写hashCode()。运行程序将输出:

true  
set大小: 2

这表明虽然pt1.equals(pt2)返回true,按理它们应被视为同一个元素,但由于hashCode()未重写,pt1pt2的哈希码不同(来源于Object.hashCode()),因此插入HashSet时被放入了不同的桶,集合认为它们是不同的对象,导致集合大小为2而不是1。这违反了集合的预期用法。

正确的做法是重写hashCode(),例如对于Point类可实现为:return 31 * x + y;(这是一个简单的哈希计算,把x, y组合)。一旦重写,pt1pt2将产生相同的hashCode=33,两者被放入HashSet时会先比较哈希发现一致,再调用equals确认等价,从而认定为重复元素,集合最终大小为1。

总结:

  • 如果重写equals(),请务必重写hashCode(),保证相等的对象哈希码也相等。
  • hashCode()返回值可以相等即使对象不相等,但要尽量减少这种冲突发生,提高散列效率。
  • HashMapHashSet这类集合中,会先比对hashCode,若不同直接认定不相等;若相同才进一步用equals()判断真伪。因此保持两者契约非常重要,以避免逻辑错误。

Java提供的许多类都正确地重写了equals()hashCode()(例如String、包装类、各种集合类),在实际开发自定义类时也应遵循这个规范。

clone() 方法

作用: clone()方法用于创建对象的拷贝,也称“克隆”。通过clone(),可以生成一个新对象,其内容与原对象相同。克隆有两种类型:

  • 浅拷贝(shallow copy):拷贝对象自身,但不拷贝内部引用指向的对象。也就是说,原对象和克隆对象内部的引用都指向同一个子对象。
  • 深拷贝(deep copy):拷贝对象自身,并递归地拷贝它引用的所有子对象。最终得到的克隆对象完全独立,不共享任何原始对象的可变部分。

使用前提: 要使对象可克隆,类需要实现java.lang.Cloneable接口(这是一个标记接口,没有方法)并重写clone()方法,通常调用super.clone()来实现原始的按位拷贝。如果一个类未实现Cloneable却调用Object.clone(),将抛出CloneNotSupportedException异常。

默认行为: Object.clone()是受保护的(protected)方法,默认实现是浅拷贝:即创建一个新的对象,将原对象的每个字段值都拷贝过去(对于基本类型就是复制值,引用类型就是复制引用)。因此默认clone产生的拷贝对象与原对象共享所有可变引用对象。

何时深拷贝: 如果对象包含引用类型字段,且希望克隆结果与原对象在各层级都相互独立(修改克隆不影响原对象),就需要深拷贝。这通常需要在clone()方法中手工实现:调用子对象的clone或者重新创建子对象,使得新对象拥有原对象子对象的副本。

示例(浅拷贝):

class Address implements Cloneable {String city;public Address(String city) { this.city = city; }@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone(); // Address只有基本字段,直接浅拷贝即可}@Overridepublic String toString() { return city; }
}
class Student implements Cloneable {String name;Address addr; // 引用类型字段public Student(String name, Address addr) {this.name = name;this.addr = addr;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone(); // 浅拷贝,addr引用会被直接复制}@Overridepublic String toString() {return name + " @ " + addr;}
}
public class TestClone {public static void main(String[] args) throws CloneNotSupportedException {Address address = new Address("北京");Student stu1 = new Student("小明", address);Student stu2 = (Student) stu1.clone();   // 克隆stu1得到stu2(浅拷贝)System.out.println("克隆前: stu1=" + stu1 + ", stu2=" + stu2);// 修改原对象的地址stu1.addr.city = "上海";System.out.println("修改原对象地址后: stu1=" + stu1 + ", stu2=" + stu2);}
}

输出结果:

克隆前: stu1=小明 @ 北京, stu2=小明 @ 北京  
修改原对象地址后: stu1=小明 @ 上海, stu2=小明 @ 上海

可以看到,克隆前两个学生对象的地址都是“北京”,克隆是成功的。随后我们修改了原对象stu1的地址城市为“上海”,结果stu2的地址也变成了“上海”。这说明stu1stu2addr引用指向同一个Address对象(浅拷贝行为)。因此修改其中一个的地址会影响另一个。如果这不是我们想要的效果,那么浅拷贝就不够,需要深拷贝来使stu1stu2拥有独立的Address对象。

示例(深拷贝): 为了实现深拷贝,我们需要修改Student.clone()方法,在克隆自身后,手动克隆其内部的Address对象:

@Override
protected Object clone() throws CloneNotSupportedException {// 先浅拷贝Student对象Student cloned = (Student) super.clone();// 再克隆Address对象,赋给新Student的addrcloned.addr = (Address) this.addr.clone();return cloned;
}

做了以上修改后,再次运行刚才的测试:此时修改stu1.addr.city为“上海”后,stu2.addr.city仍然保持“北京”,各自独立,达到了深拷贝的效果。

需要注意的是深拷贝的实现较为繁琐,当对象结构复杂时需要递归地克隆每一层对象。选择浅拷贝还是深拷贝取决于具体需求。很多情况下浅拷贝已经足够,而且效率更高;但如果共享可变对象会带来问题,就必须实现深拷贝或采取其他复制手段(比如通过序列化来复制对象)。

使用克隆的替代方案: 克隆在Java中有些争议,因为Cloneable接口机制被认为设计不够优雅,易出错。替代方案包括:

  • 提供拷贝构造函数或拷贝工厂方法。例如new Person(originalPerson),在构造函数中手动复制需要的字段。
  • 使用序列化:将对象写到流中再读出,得到一份深拷贝(代价较高,一般不推荐)。
  • 使用第三方库或自行实现通用的深拷贝工具等。

总之,clone()方法可以快捷地复制对象,但需要小心正确实现Cloneable接口和遵循浅/深拷贝策略,否则可能导致意想不到的共享或性能问题。

finalize() 方法

提示: finalize()方法在Java 9后已被标记为过时(Deprecated),不建议在新代码中使用。它存在诸多问题,包括无法保证及时执行、性能开销大、不确定性强等。这里介绍其作用是为了完整性,但实际开发应尽量避免依赖finalize()进行资源回收,推荐使用try-with-resources或显式关闭模式来管理资源。

作用: finalize()是定义在Object类中的一个保护方法:

protected void finalize() throws Throwable { }

它的设计目的是,当垃圾回收器(GC)准备回收某个对象时,如果该对象覆盖了finalize()方法,就会调用此方法让对象有一次执行清理操作的机会。通常用于释放非内存资源,比如关闭文件、网络连接等。可以将finalize()看做对象的终结器,在对象生命的尽头被调用。

调用时机: finalize()的调用由垃圾回收线程决定,而且不确定什么时候执行。一个对象被判定为垃圾后,GC可能立即回收它,也可能在稍后的某次GC才回收。在回收前如果存在finalize(),GC会执行它。如果执行缓慢,会拖延垃圾回收该对象甚至影响整体GC性能。此外,一个对象的finalize()只会被调用一次,即使对象在finalize()中被“拯救”(使自己重新有引用)后来又变成垃圾,也不会再第二次调用。

默认实现: Object.finalize()默认什么也不做。因此只有我们在子类中重写这个方法时,才会赋予其实际行为。

示例:

class Resource implements AutoCloseable {private String name;public Resource(String name) { this.name = name; }@Overrideprotected void finalize() throws Throwable {System.out.println("Finalize 被调用,正在清理资源: " + name);super.finalize();}@Overridepublic void close() {System.out.println("关闭资源: " + name);}
}
public class TestFinalize {public static void main(String[] args) throws InterruptedException {Resource res = new Resource("数据库连接");res = null;       // 使对象成为垃圾System.gc();      // 提示JVM执行垃圾回收Thread.sleep(1000); // 等待一会儿,确保GC完成System.out.println("程序结束");}
}

运行可能输出:

Finalize 被调用,正在清理资源: 数据库连接  
程序结束

可以看到,当我们将res设为null并调用System.gc()请求垃圾回收后,Resource对象的finalize()被执行了(输出了清理资源的信息)。但要强调,这种调用是不确定的。如果不调用System.gc()强制触发,有可能程序结束时都没有执行finalize,因为JVM可能没有发生垃圾回收。而我们通过Thread.sleep等待来增加finalize执行的机会,也不能100%保证所有环境下都奏效。

警告: 正因finalize()的这种不确定性,我们不应该依赖它来释放重要资源。例如文件句柄、数据库连接,应该使用try...finally或实现AutoCloseable然后用try-with-resources机制来确保及时释放。上例Resource实现了AutoCloseable.close()用于手动关闭,就是更好的模式。实际上,在Java 9及以后,finalize()被废弃,建议改用java.lang.ref.CleanerPhantomReference等更可控的机制进行终结操作。

小结: finalize()曾被作为Java提供的一个“对象临终钩子”,但由于其固有问题,现在几乎已经退出历史舞台。了解它是为了理解Java内存管理机制的一部分,但编写新代码时尽量不要使用。

包与访问权限控制

包(Package)是Java用于组织类和接口的一种命名空间机制。通过包,我们可以将功能相关的类归组,避免命名冲突,并控制类的访问范围。访问权限控制(Access Control)是Java提供的限定类、变量、方法可见性的机制,包括privatedefault(无修饰符)、protectedpublic四种级别,从最严格到最开放。下面我们依次介绍包的声明与导入、各访问修饰符的作用范围,并讨论不同包和继承情况下的访问权限。

包的声明与导入语法

包的声明: 在一个Java源文件开头,可以使用package语句声明该文件中定义的类所属的包。例如:

package com.example.utils;
public class StringUtil {// 类的定义
}

以上表示StringUtil类属于com.example.utils包。包名通常使用公司域名倒置+项目名+模块名等方式组织,以确保全局唯一性。例如java.utilorg.apache.commons.io都是包名。注意: 包声明必须是文件的第一条语句(紧随可能的注释或版权声明之后),且每个源文件至多只能有一个package声明。如果不写package,则类处于默认包(unnamed package),这一般只在最简单的示例程序中使用,实际项目应当明确定义包。

包的导入: 为了在一个类中使用不同包下的类,我们需要导入它们。导入通过import语句完成,有两种用法:

  • 导入指定类,例如:import java.util.Date; 表示导入java.util包中的Date类。
  • 导入整个包下所有类,使用通配符*,例如:import java.util.*; 导入java.util包中的所有公共类。(通配符不会导入子包中的类)

import语句通常放在包声明下面,类定义之前。需要注意,如果代码中直接使用类的全名(如java.util.Date date = new java.util.Date();),可以不import。但import可以简化书写。还有一种静态导入import static用于导入类的静态成员,便于直接使用(如导入Math.PI),这里不展开。

示例:

package com.myapp.model;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
public class User {private String name;private Date   birthDate;private List<String> tags = new ArrayList<>();// ...
}

这段代码说明:User类位于com.myapp.model包,我们从java.util包分别导入了DateListArrayList类,然后就可以直接使用这些类而无需每次写全称java.util.Date等。

四种访问修饰符的可见性范围

Java提供了四种访问权限修饰符,用来限制类、成员被访问的范围:

  • private:私有权限,最严格,只能在自身类内部访问。
  • (无修饰符)默认(包私有,package-private):在同一个包内部可访问,在其他包不可访问。也称为“friendly”或“package”访问权限。
  • protected:受保护权限,在同一个包内可以访问;在不同包的子类中也可以访问(有限制条件,见下文);其他情况下不能访问。
  • public:公共权限,最开放,任何地方(任何包)都可以访问。

这四种权限从小到大排列为:private < default < protected < public。可以把它想象成圈层:private仅类自身,default扩大到包,protected扩大到子类,public对所有开放。

下面详细说明各修饰符对类、本包、子类、外包的可见性:

  • private(私有):
    私有成员仅能在定义它的类内部访问,连同包的其他类也不行,子类也不行。常用于封装对象的内部状态,避免外界任意更改。如类的属性大多声明为private,然后通过公共的getter/setter访问。
  • 默认(包级):
    如果一个成员没有显式的访问修饰符,则它具有默认的包访问权限。这样的成员(或类)对同一包内的其他类可见,对包外是不可见的。包访问适合一些只应在内部使用的类或方法,不希望公开给整个世界,但同一团队模块内又需要互相调用的场景。
  • protected(受保护):
    受保护成员可被同包内的任何类访问(跟默认一样),此外还允许不同包的子类访问。但是对不同包的非子类依然是不可见的。需要注意,在子类中访问父类的protected成员,有一个限制:只能通过子类自身引用或子类类型的对象来访问,不能通过父类引用直接访问父类的protected成员(因为那属于外包访问场景)。简单来说,protected对于子类而言,就像是提高了可见性到子类的内部,但并不向整个外部包公开。
  • public(公共):
    公共成员没有访问限制,任何包的任何类都可以访问(只要能拿到引用)。公共类也可以被任何地方使用。大部分对外接口、API都是public的。

顶层类的访问权限: Java的顶级类(非内部类)只能是public或包级(默认)两种权限。也就是说,一个.java文件里定义的非内部类,要么声明为public(类名必须与文件名相同),要么不写修饰符表示包可见。不能将顶层类声明为protected或private——那是非法的。在内部类、成员上才有4种选项,而顶层类只有2种。通常,一个模块的入口点类会是public,其余一些辅助类可以包可见来隐藏实现。

示例代码演示各权限: 我们通过两个包pack1pack2来演示各种访问修饰符的作用。

// 文件:pack1/A.java
package pack1;
public class A {public    int pub    = 1;protected int prot   = 2;int def    = 3;  // 默认权限private   int priv   = 4;public void testAccess() {// 类内部可以访问所有自己的成员System.out.println("A.pub = " + pub);System.out.println("A.prot = " + prot);System.out.println("A.def = " + def);System.out.println("A.priv = " + priv);}
}
// 文件:pack1/B.java
package pack1;
class B {  // 默认访问级别的类B(包内可见,包外不可见)public void test() {A a = new A();// 同一个包中,B可以访问A的 pub, prot, def成员,但不能访问privSystem.out.println("B sees A.pub = " + a.pub);    // OKSystem.out.println("B sees A.prot = " + a.prot);  // OK (同包可以访问protected)System.out.println("B sees A.def = " + a.def);    // OK (同包访问默认权限)// System.out.println(a.priv); // ERROR: priv在A中是私有的,B无法访问}
}
// 文件:pack2/C.java
package pack2;
import pack1.A;
public class C extends A {public void test() {A a = new A();System.out.println("C sees a.pub = " + a.pub);    // OK, public随处可见// System.out.println("C sees a.prot = " + a.prot); // ERROR: 不在同包,通过父类引用无法访问protected// System.out.println("C sees a.def = " + a.def);   // ERROR: default权限,不同包不可见// System.out.println("C sees a.priv = " + a.priv); // ERROR: private不可见// 子类中可以通过继承获得prot属性访问权:System.out.println("C sees this.prot = " + this.prot); // OK, C继承了A.protSystem.out.println("C sees super.prot = " + super.prot); // OK, 等价于this.prot}
}

现在,让我们总结上述示例体现的规则:

  • A定义在pack1包中,具有public、protected、默认、private四种成员。A.testAccess()在类内部能够访问所有属性,验证了类对自身成员访问无障碍。
  • B也在pack1包,由于未声明public,所以B是包级可见的——意味着pack1包外无法使用B类(无法import pack1.B在其他包中)。在B.test()中,通过A a = new A();可以正常创建A实例,因为A是public的。然后访问a.puba.prota.def都成功,因为BA同包,包内可以访问protected和默认成员。而a.priv无法访问,private仅限A内部。
  • Cpack2包并继承自A。在C.test()中:
    • a.pub可访问,因为public全局可见。
    • a.def不可访问,因为defpack1包可见,对pack2来说不可见(无论继承与否,aA的引用,在pack2外包环境)。
    • a.prot也不可直接通过a访问。尽管prot是protected,CA的子类,但这里使用的是父类引用a(类型A)来访问,在pack2中这相当于“不同包非子类”情形,不允许。
    • 然而,C作为A的子类,继承了prot成员,因而可以在自身代码中通过this.prot或直接prot访问。这表示protected成员在子类内部是可见的,相当于子类拥有这个属性。
    • private依旧不可见,子类完全无权访问父类的私有成员。

从以上分析,可以归纳:

  • private:仅限本类内部。
  • 默认(包权限):本包内部的所有类可以访问,包外的类(无论是不是子类)都不行。
  • protected:本包内部可以访问。跨包情况下,只有该类的子类能访问,但访问时需要注意使用子类自身引用或子类继承得到的成员,而不能用父类实例去访问父类protected成员。
  • public:所有地方都能访问,没有限制。
访问控制对继承和跨包的影响

对于继承来说,需要关注父类成员在子类中的可见性

  • 子类继承了父类所有非private成员(构造方法除外),但是如果成员是default且子类处于不同包,那么虽然子类继承了该字段或方法,却因为包限制无法在子类中使用——从语义上可以理解为这个成员对子类是存在但不可见的,实际编译时会把default当做private对待,因为子类在不同包无法访问它。同理,包级的父类方法在子类中也无法调用。
  • 子类对父类的protected成员有访问权,即使在不同包。受保护成员对跨包子类可见,就像上例C可以访问prot,但必须是通过子类自身。比如在C中可以直接用prot(编译器会解析为this.prot),但不能用父类对象a.prot

访问控制对类本身和外部的影响:

  • 顶层类如果是默认访问,则只能在同包使用。比如上例B类,在pack1内可以使用,但pack2想使用B会发现无法导入(编译错误)。
  • public类可以被跨包使用,但这也要求它所在包被适当import或者全限定名引用。
  • 内部类(nested class)的访问遵循成员的访问规则,它本身相当于成员,可以被声明为private/protected等,限制其在外部的可见性。但这是更高级的话题了。

小结: 通过包和访问修饰符,我们可以实现良好的封装和模块化设计:

  • 将不需要公开给外部的类或成员声明为包级或private,保证模块内部的实现细节不泄露。
  • 仅将必要的接口声明为public供外部使用。
  • 使用protected来允许子类的定制扩展,同时对非子类隐藏实现。

掌握访问控制有助于编写安全、清晰的代码接口。例如,一个库的API会把实现类隐藏在包内部,只暴露接口供用户使用;框架会使用protected方法让用户在子类中扩展功能,但保持框架整体行为一致。

最后,需要注意的是,在实际项目中包的划分和访问控制的选用,应根据设计原则来:尽量降低各模块之间的耦合,保证内部实现的私密性和外部接口的易用性。在团队协作中,遵守约定使用正确的访问修饰符可以避免很多错误和混淆。

相关文章:

类的高级特性与语法细节

static 静态关键字 Java中的static关键字用于修饰类的成员&#xff08;属性或方法&#xff09;&#xff0c;表示“静态”的含义&#xff0c;即属于类本身&#xff0c;而非某个对象。静态成员在内存中只有一份&#xff0c;在类加载时初始化&#xff0c;生命周期贯穿程序运行始终…...

基于 RAG 的 Text2SQL 全过程的 Python 实现详解,结合 LangChain 框架实现自然语言到 SQL 的转换

什么是RAG 一、核心流程&#xff1a;三阶段协同 RAG的核心流程分为检索&#xff08;Retrieval&#xff09;、增强&#xff08;Augmentation&#xff09;、生成&#xff08;Generation&#xff09;三个阶段&#xff0c;形成“检索→知识整合→生成”的闭环。 1. 检索&#xff…...

使用 OpenCV 进行视觉图片调整的几种常见方法

以下是使用 OpenCV 进行视觉图片调整的几种常见方法&#xff1a; 调整图片大小 指定目标尺寸&#xff1a;使用cv2.resize()函数&#xff0c;通过设定目标图像的宽度和高度来调整图片大小。例如&#xff0c;将图片调整为 200x200 像素&#xff1a; import cv2 image cv2.imre…...

【特殊场景应对9】视频简历的适用场景与风险分析

写在最前 作为一个中古程序猿,我有很多自己想做的事情,比如埋头苦干手搓一个低代码数据库设计平台(目前只针对写java的朋友),比如很喜欢帮身边的朋友看看简历,讲讲面试技巧,毕竟工作这么多年,也做到过高管,有很多面人经历,意见还算有用,大家基本都能拿到想要的offe…...

Dify 1.3.0 为 LLM 节点引入了结构化输出支持

Dify 1.3.0 为 LLM 节点引入了结构化输出支持 0. 引言1. 使用方法 0. 引言 Dify 1.3.0 开始&#xff0c;在 LLM 节点支持结构化输出&#xff1a;Dify 已经为 LLM 节点引入了结构化输出支持。这意味着您的语言模型现在可以返回整齐组织且易于处理的数据。后端实现由 Nov1c444 在…...

【Linux网络】HTTP协议全解析 - 从请求响应到方法与Header

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

JSP实现用户登录注册系统(三天内自动登录)

JSP实现用户登录注册系统 引言 在Web开发中&#xff0c;用户认证是最基础且核心的功能之一。本文基于JSP技术&#xff0c;实现了一个包含注册、登录、自动登录&#xff08;3天内&#xff09;、退出等功能的用户系统&#xff0c;并在过程中解决了Cookie字符错误、错误信息回显…...

大数据模型现状分析

大数据模型现状分析 一、引言 在当今数字化时代&#xff0c;数据以前所未有的速度增长&#xff0c;大数据已成为推动各行业发展的核心动力。大数据模型作为挖掘数据价值的关键工具&#xff0c;正受到广泛关注与深入研究。通过对海量、多样且高速产生的数据进行处理和分析&…...

代码随想录算法训练营第二十八天

LeetCode题目: 509. 斐波那契数70. 爬楼梯746. 使用最小花费爬楼梯2444. 统计定界子数组的数目(每日一题) 其他: 今日总结 往期打卡 动态规划解题步骤: 确定递推公式确定遍历顺序记忆化搜索(确定dp数组以及下标的含义与初始化值)递推优化与空间优化 509. 斐波那契数 跳转: 5…...

HTML与安全性:XSS、防御与最佳实践

HTML 与安全性&#xff1a;XSS、防御与最佳实践 前言 现代 Web 应用程序无处不在&#xff0c;而 HTML 作为其基础结构&#xff0c;承载着巨大的安全责任。跨站脚本攻击&#xff08;XSS&#xff09;仍然是 OWASP Top 10 安全威胁之一&#xff0c;对用户数据和网站完整性构成严…...

三维重建(二十)——思路整理与第一步的进行

文章目录 一、整体思路二、细分三、之前存在问题四、任务安排五、第一步——找到内参并选定一种5.1 train的RTK5.2 test的RTK5.3 各选择一个5.3.1 train-185.3.2 test-193一、整体思路 这部分主要是宏观的讲一下整体框架。 从gshell里面提取核心参数,放入py3d,渲染出图片,…...

判断 ONNX 模型是否支持 GPU

&#x1f50d; 判断 ONNX 模型是否支持 GPU 的几个关键点&#xff1a; ✅ 1. 检查模型支持的 Execution Provider 可以通过下面的代码打印出来当前模型使用了什么设备&#xff1a; 需要安装好&#xff1a;onnxruntime-gpu import onnxruntime as ort session ort.InferenceSe…...

CANFD技术在实时运动控制系统中的应用:协议解析、性能测试与未来发展趋势

摘要&#xff1a; 本文深入探讨了CANFD技术在实时运动控制系统中的应用。通过对传统CAN协议与CANFD协议的对比分析&#xff0c;详细阐述了CANFD在提升数据传输效率、增强系统实时性与稳定性方面的优势。文章结合具体测试案例&#xff0c;对CANFD总线的性能指标进行了全面评估&a…...

Java基础 4.26

1.访问修饰符细节 package com.logic.modifier;public class A {public int n1 100;protected int n2 200;int n3 300;private int n4 400;public void m1() {//在同一个类中 可以访问public protected 默认 private 修饰属性和方法System.out.println(n1 " " …...

山东大学离散数学第九章习题解析

参考教材&#xff1a;离散数学教程&#xff0c;徐秋亮 / 栾俊峰 / 卢雷 / 王慧 / 赵合计 编著&#xff0c;山东大学计算机科学与技术学院 注&#xff1a;该解析为个人所写&#xff0c;涵盖了 2022-2023-2 学期赵合计老师所布置的所有课本习题&#xff1b;由于学识、认识及经验…...

5G融合消息PaaS项目深度解析 - Java架构师面试实战

5G融合消息PaaS项目深度解析 - Java架构师面试实战 场景&#xff1a;互联网大厂Java求职者面试&#xff0c;面试官针对5G融合消息PaaS项目进行提问。 第一轮提问 面试官&#xff1a;马架构&#xff0c;请简要介绍5G融合消息PaaS平台的核心功能和应用场景。 马架构&#xff…...

React-Redux

1、安装 npm i redux react-redux reduxjs/toolkit 2、基础使用方式&#xff08;无 Toolkit&#xff09; &#xff08;1&#xff09;核心Api createStore&#xff1a;创建数据仓库&#xff1b;store.dispatch()&#xff1a;用于派发action&#xff0c;执行修改动作&#xf…...

Linux基础篇、第4章_03系统磁盘高级管理LVM 逻辑卷管理器

题目&#xff1a;系统磁盘高级管理LVM 逻辑卷管理器 版本号: 1.0,0 作者: 老王要学习 日期: 2025.04.26 适用环境: Centos7 文档说明 本文档聚焦于 Centos7 系统下的磁盘高级管理&#xff0c;围绕 LVM 逻辑卷管理器展开。详细介绍了物理卷、卷组和逻辑卷的创建、管理与删除操…...

代码随想录算法训练营Day36

力扣1049.最后一块石头的重量Ⅱ【medium】 力扣474.一和零【meidum】 一、力扣1049.最后一块石头的重量Ⅱ【medium】 题目链接&#xff1a;力扣1049.最后一块石头的重量Ⅱ 视频链接&#xff1a;代码随想录 1、思路 把这个问题转换成尽可能将 stones 分成两个等分子集&#xf…...

iperf网络性能测试

iperf 是一个网络性能测试工具&#xff0c;用于测量网络带宽、延迟、抖动等性能指标。它支持 TCP 和 UDP 协议&#xff0c;可以在客户端和服务器模式下运行&#xff0c;广泛用于网络性能评估和故障排查。 主要功能 带宽测试&#xff1a;测量网络的最大可用带宽。延迟测试&…...

基于 Nginx 的 WebSocket 反向代理实践

一、HTTP 协议升级机制回顾 Upgrade/Connection 报头 客户端发起 WebSocket 握手时&#xff0c;会在普通 HTTP 请求中加入Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: <随机值> Sec-WebSocket-Version: 13服务端若接受协议切换&#xff0c;会以 101 Swit…...

C++ 同步原语

同步原语&#xff08;Synchronization Primitives&#xff09;是操作系统和编程语言提供的基本工具&#xff0c;用于在多线程或并发环境中协调线程&#xff08;或进程&#xff09;之间的执行顺序&#xff0c;管理共享资源的访问&#xff0c;以避免数据竞争&#xff08;data rac…...

mmap详解

mmap详解 mmap基础概念mmap内存映射原理mmap相关函数调用mmap的使用细节mmap和常规文件操作的区别 mmap基础概念 mmap是一种内存映射文件的方法&#xff0c;即将一个文件或者其它对象映射到进程的地址空间&#xff0c;实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一…...

基于大模型底座重构司法信息系统

前置篇章&#xff1a;法律智能体所需的基础知识 构建一个高效的法律智能体&#xff0c;特别是在基于RAG&#xff08;Retrieval-Augmented Generation&#xff09;架构的背景下&#xff0c;需要融合多种学科和领域的知识。以下是对法律智能体开发和应用所需核心基础知识的简要介…...

如何判断你的PyTorch是GPU版还是CPU版?

如何判断你的PyTorch是GPU版还是CPU版&#xff1f; PyTorch作为当前最流行的深度学习框架之一&#xff0c;支持在CPU和GPU(NVIDIA CUDA)上运行。对于深度学习开发者来说&#xff0c;正确识别PyTorch版本至关重要&#xff0c;因为GPU版本可以带来10-100倍的性能提升。本文将全面…...

Leetcode刷题记录19——无重复字符的最长子串

题源&#xff1a;https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/?envTypestudy-plan-v2&envIdtop-100-liked 题目描述&#xff1a; 思路一&#xff1a; 通过两个指针&#xff0c;第一个指针指向字串的开头&#xff0c;第二…...

SpringBoot程序的创建以及特点,配置文件,LogBack记录日志,配置过滤器、拦截器、全局异常

一、创建一个SpringBoot程序 在之前写过一篇如何创建SpringBoot程序&#xff0c;两种方式&#xff0c;方法1&#xff1a;通过maven创建SpringBoot项目 方法2&#xff1a;使用Spring Initialzr创建一个SpringBoot项目&#xff08;缺点&#xff1a;当创建项目时网络中断&#x…...

Ubuntu20.04 Ollama 配置相关

Ubuntu20.04 Ollama 配置相关 Ubuntu20.04 Ollama 配置相关ollama修改配置文件常用命令修改端口局域网访问 Ubuntu20.04 Ollama 配置相关 ollama修改配置文件常用命令 sudo gedit /etc/systemd/system/ollama.service systemctl daemon-reload systemctl restart ollama sys…...

python调用ffmpeg对截取视频片段,可批量处理

本文完全免费&#xff0c;非VIP文章&#xff0c;如果您发现需VIP可看全文&#xff0c;请邮箱联系我&#xff1a;openwebsitefoxmail.com 文章目录 python调用ffmpeg对截取视频片段&#xff0c;可批量处理用到的ffmpeg命令python调用bash指令的方法python处理代码准备函数python…...

【WLAN】华为无线AC双机热备负载分担—双链路热备份

配套实验拓扑可以下载学习交流&#xff1a;【WLAN】华为无线AC双机负载分担—双链路热备份 双链路备份的传统配置方式是在主、备AC上为AP指定对方AC的IP地址&#xff0c;并分别配置优先级&#xff0c;通过比较优先级的方式来确定主、备AC。为简化配置逻辑&#xff0c;新配置方式…...

学习笔记——《Java面向对象程序设计》-内部类、匿名类、异常类

参考教材&#xff1a; Java面向对象程序设计&#xff08;第3版&#xff09;微课视频版 清华大学出版社 1、内部类 类中可以有两种重要的成员&#xff1a;成员变量和方法。实际上Java还允许类可以有一种成员&#xff1a;内部类。 内部类可以使用其外嵌类中的成员变量&#x…...

BS架构与CS架构的对比分析:了解两种架构的不同特点与应用

目录 前言1. BS架构概述1.1 什么是BS架构&#xff1f;1.2 BS架构的主要特点 2. CS架构概述2.1 什么是CS架构&#xff1f;2.2 CS架构的主要特点 3. BS架构与CS架构的对比3.1 用户体验3.2 安全性3.3 适用场景 4. 结语 前言 在现代软件开发中&#xff0c;架构设计决定了应用的性能…...

ARM架构的微控制器总线矩阵优先级与配置

在 ARM 架构的微控制器中,总线矩阵的优先级与配置是确保多主设备(如 CPU、DMA 等)高效协同工作的关键。总线矩阵通过仲裁逻辑(Arbiter)管理主设备对共享资源的访问冲突,优先级配置直接影响系统的实时性、带宽利用率和任务响应速度。以下是总线矩阵优先级机制及配置的详细…...

高速系统设计理论基础

如前一章所述&#xff0c;在进行高速系统设计时&#xff0c;最重要的是要从基本理论出发&#xff0c;只有掌握了基本理论&#xff0c;然后才能谈到其他的设计技术和技巧。本章主要介绍微波电磁理论基础&#xff0c;及其在高速系统设计中的工程化分析方法和应用。通过对微波信号…...

Python-MCPServer开发

Python-MCPServer开发 使用FastMCP开发【SSE模式的MCPServer】&#xff0c;熟悉【McpServer编码过程】【McpServer调试方法】 1-核心知识点 1-熟悉【SSE模式的MCPServer】开发2-熟悉【stdio模式的MCPServer】开发3-熟悉【启动MCPServer】的三种方式 3.1-直接启动:python mcp_s…...

Springboot集成SSE实现消息推送+RabbitMQ解决集群环境下SSE通道跨节点事件推送问题

SSE连接介绍&#xff0c;SSE对比WebSocket Server-Sent Events (SSE) 是一种基于 HTTP 协议的轻量级实时通信技术&#xff0c;允许服务器向客户端推送数据。以下是 SSE 的主要特点&#xff1a; 单向通信&#xff1a;SSE 仅支持服务器向客户端推送数据&#xff0c;客户端不能通…...

Kettle学习

一、Kettle 简介 Kettle(现称为 Pentaho Data Integration)是一款开源ETL工具,支持从多种数据源抽取、转换和加载数据,广泛应用于数据仓库构建、数据迁移和清洗。其核心优势包括: 可视化操作:通过拖拽组件设计数据处理流程(转换和作业)。多数据源支持:数据库(MySQL/…...

科学养生,开启健康生活新方式

在快节奏的现代生活中&#xff0c;健康养生已成为人们关注的焦点。科学的养生方式不仅能增强体质&#xff0c;还能有效预防疾病&#xff0c;提升生活质量。​ 合理饮食是健康养生的基础。日常饮食应遵循均衡原则&#xff0c;保证蛋白质、碳水化合物、脂肪、维生素和矿物质的合…...

brew 安装openjdk查看其版本

使用brew&#xff08;如果你使用Homebrew安装&#xff09; 如果你通过Homebrew安装了OpenJDK&#xff0c;可以使用以下命令来查看安装的版本,&#xff1a; brew list --versions openjdk8 这将会列出所有通过Homebrew安装的OpenJDK版本及其版本号。 3. 查看/usr/libexec/ja…...

基于大模型对先天性幽门肥厚性狭窄预测及临床方案的研究报告

目录 一、引言 1.1 研究背景与目的 1.2 国内外研究现状 1.3 研究方法与创新点 二、先天性幽门肥厚性狭窄概述 2.1 定义与发病机制 2.2 流行病学特征 2.3 病理与解剖特点 三、大模型预测原理及构建 3.1 大模型简介 3.2 数据收集与预处理 3.3 模型训练与优化 四、大…...

【开源】基于51单片机的温湿度检测报警系统

项目说明 该设计是一个简易的基于51单片机的温湿度检测报警系统&#xff0c;功能说明&#xff1a; 使用LCD1602实时显示当前的温湿度。读取DHT11的温湿度值&#xff0c;如果温度大于最大设定值&#xff0c;LED1亮&#xff0c;如果温度小于最小设定值&#xff0c;LED2亮。如果…...

2025年4月25日第一轮

1.作文 The increasing reliance on onlineshopping has brought both convince and challenge to consumsers.It is of great nesscity for poeple to adapt better strategies to cope with these challenges.Resons and concrete evidence to support my view are as fello…...

AutoSAR从概念到实践系列之MCAL篇(二)——Mcu模块配置及代码详解(下)

欢迎大家学习我的《AutoSAR从概念到实践系列之MCAL篇》系列课程,我是分享人M哥,目前从事车载控制器的软件开发及测试工作。 学习过程中如有任何疑问,可底下评论! 如果觉得文章内容在工作学习中有帮助到你,麻烦点赞收藏评论+关注走一波!感谢各位的支持! 上一篇内容主要为…...

A. Ideal Generator

time limit per test 1 second memory limit per test 256 megabytes We call an array aa, consisting of kk positive integers, palindromic if [a1,a2,…,ak][ak,ak−1,…,a1][a1,a2,…,ak][ak,ak−1,…,a1]. For example, the arrays [1,2,1][1,2,1] and [5,1,1,5][5,…...

微信小程序核心技术栈

微信小程序核心技术栈 WXML一.WXML基础概念1.本质与特点2.与HTML的主要差异 二.数据绑定1.基础绑定2.高级绑定模式3.数据绑定限制 三.条件渲染1.基础条件判断2.多条件渲染优化3.条件渲染性能建议 四.列表渲染1.基础列表2.进阶用法3.wx:key的深度解析 五.模版系统1.定义模版2.使…...

系统架构设计中的ATAM方法:理论、实践与深度剖析

引言 在复杂系统架构设计中,如何在性能、安全性、可维护性等质量属性之间实现平衡,是每一位资深架构师必须面对的终极挑战。传统的架构评审往往依赖经验直觉或局部优化,而‌ATAM(Architecture Tradeoff Analysis Method,架构权衡分析方法)‌通过结构化分析框架,系统性解…...

模板引擎语法-过滤器

模板引擎语法-过滤器 文章目录 模板引擎语法-过滤器[toc]1.default过滤器2.default_if_none过滤器3.length过滤器4.addslashes过滤器5.capfirst过滤器6.cut过滤器7.date过滤器8.dictsort过滤器 1.default过滤器 default过滤器用于设置默认值。default过滤器对于变量的作用&…...

关于GoWeb(1)

Go Web &#xff08;1&#xff09; 一、网络通信与 Socket 编程 &#xff08;一&#xff09;Socket 编程基础 Socket 是网络通信的核心&#xff0c;它允许程序之间通过网络进行数据交换。在 Go 中&#xff0c;可以使用标准库 net 来实现 Socket 编程。 &#xff08;二&…...

实现从一个微信小程序跳转到另一个微信小程序

前言&#xff1a; 最近在公司完成了一个两个小程序之间进行跳转的需求,将跳转方式与携带参数的方式分享给伙伴们&#xff1a; 代码展示&#xff1a; wx.navigateToMiniProgram({// 另一个程序的appIdappId: "wxbbd...",//你希望跳转到另一个小程序的目标路径&#…...

【教程】Docker运行gitlab容器

Docker运行gitlab容器 前言1.拉取gitlab镜像2.创建挂载数据卷3.运行镜像4 登录Gitalb5 访问linux的gitlab地址&#xff0c;输入用户名与密码 前言 在linux系统中安装gitlab的教程 1.拉取gitlab镜像 不指定 默认拉取最新版镜像 docker pull gitlab/gitlab-ce 2.创建挂载数据…...