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

【JAVA】十三、基础知识“接口”精细讲解!(三)(新手友好版~)

目录

1. Object类

1.1 Object的概念

1.2 Object例子

2. toString

2.1 toString的概念

2.2 为什么要重写toString

2.3 如何重写toString

3. 对象比较equals方法

3.1 equals( ) 方法的概念

3.2 Object类中的默认equals实现

3.3 如何正确重写equals方法

4. hashCode方法

4.1 hashCode的概念

4.2 对hashCode理解

5. 接口使用实例(comparable接口) 

6. Clonable接口和深浅拷贝


1. Object类

1.1 Object的概念

        在Java中,Object类位于java.lang包中,是java默认提供的一个类,是所有类的父类即使你没有显式地写extends Object去继承Java也会默认让你的类继承Object父类,即所有类的对象都可以使用Object的引用进行接受。

// 这两段代码实际上是完全等价的
class MyClass { /*...*/ }class MyClass extends Object { /*...*/ }

1.2 Object例子

我们可以看一个例子:

1. Object 类

  • 是Java中所有类的父类
  • 任何对象都可以用Object类型接收
  • 提供默认方法比如:toString(),equals(),hashCode()(这三个知识点后面会进行详细的讲解啦)以下图片是Object提供的方法:

2. 多态特性

function(Object obj)可以接受:

  • Person对象(new Person())
  • Student对象(new Student())
  • 甚至String等其他所有的对象

3. 默认输出说明

  • 直接打印对象时,默认调用 toString()
  • 默认格式:类名@哈希码(比如Person@1b6d3586)

2. toString


2.1 toString的概念

        toString( ) 是Java中所有类都继承Object类里面的一个方法,用于返回对象的字符串表示形式。默认实现返回的是类名@哈希码 ,但通常我们会重写它。


2.2 为什么要重写toString

        默认的 to String( ) 输出可读性差,比如说:

Person person = new Person("小美", 21);
System.out.println(person); // 输出结果是:Person@1b6d3586

        可以看到输出的结果是类名@哈希码,而不是我们想要的名字小美,年龄21。我们再看下一种重写了to String( ) 方法的情况:

System.out.println(person); // 输出的结果是:Person{name='小美', age=21}

这样就能够更加直观地查看对象内容啦~


2.3 如何重写toString

如何重写toString方法,可以参考这个:

点击重写后,就会出现下面框起来的一段代码,那就表示重写了toString方法: 

 此时我们实例化一个对象并且输出结果:

public class Test {public static void main(String[] args) {Person person = new Person(21,"小美");System.out.println(person);}
}

结果是: 

按照之前的,若是没有那段重写toString的代码,输出结果就是:

        我们已经反复说过了,当类没有重写to String( )方法时,直接打印对象或调用to String( )会得到类似类名@哈希码的结果,这是因为所有的Java类都隐式继承自Object类,Object.toString( )的默认实现如下:

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


3. 对象比较equals方法

        接下来我们看下面的代码,person1和person2都叫做小美,年龄21,我们比较person1和person2:

public class Test {public static void main(String[] args) {Person person1 = new Person(21,"小美");System.out.println(person1);Person person2 = new Person(21,"小美");System.out.println(person2);System.out.println("---------------------------");//分割线//比较person1和person2是否是同一个对象System.out.println(person1 == person2);}
}

输出结果为:

        我们会发现结果是不一样的,结果是false;

        我们可以通过结果看到person1和person2的地址是不一样的,也就意味着person1 == person2 比较里面存的值是不一样的,person1和person2里面存的是地址地址都不一样,结果返回的肯定是false啦~

        总结说一下就是,此时我们可以理解成此时比较的都是变量中的值,可以认为是地址。

        假设这个时候我们加上equals方法~

  • 使用person1. equals(person2)进行比较:
public class Test {public static void main(String[] args) {Person person1 = new Person(21,"小美");System.out.println(person1);Person person2 = new Person(21,"小美");System.out.println(person2);System.out.println("---------------------------");//分割线//比较person1和person2是否是同一个对象//System.out.println(person1 == person2);System.out.println(person1.equals(person2));}
}

输出结果:

        结果仍然是false。这又是为什么呢,我们找到equals这个方法细看:

        在这里,谁调用equals方法谁就是this,很明显person1就是this。这种情况下面两种写法是没有区别的。

那么这种情况我们该怎么办呢

此时我们不仅要重写我们的toString方法,还要重写我们的equals方法

package demo1;
import javax.xml.namespace.QName;
//定义一个名为 Person 的类
class Person {//String是字符串类型,用于存储文本public String name;//int是整数类型,用于存储数字public int age;// 构造方法,用于创建Person对象时初始化对象public Person(int age,String name) {this.name = name;this.age = age;}//重写toString方法,用于打印对象信@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}//重写equals方法@Overridepublic boolean equals(Object obj) {return true;}
}
public class Test {public static void main(String[] args) {Person person1 = new Person(21,"小美");System.out.println(person1);Person person2 = new Person(21,"小美");System.out.println(person2);System.out.println("---------------------------");//分割线//比较person1和person2是否是同一个对象System.out.println(person1 == person2);//调用equals方法比较System.out.println(person1.equals(person2));}
}

结果:

        我们可以看到上面重写equals方法中,return true;equals( )被重写总是返回true,所以无论System.out.println(person1.equals(person2));输出什么,返回的永远都是true;若是改成return false;不管输出什么,最后返回的永远都是false。

总结一下,若是我们要比较内容上的不同,则需要调用equals方法:

  • 使用person1. equals(person2)进行比较
  • 重写我们的equals方法

📌现在,我们正式介绍一下java中的equals方法~

3.1 equals( ) 方法的概念

        equals( ) 是 Java 中用于比较两个对象是否"相等"的方法,定义在Object 类中。所有 Java 类都继承自Object,因此所有对象都有equals( ) 方法。

基本要点:

  • 默认行为:Object 类中的 equals( ) 方法实现是 == 比较,比较两个对象的内存地址是否相同~

  • 重写目的:我们通常重写 equals( ) 来定义对象内容的比较逻辑,而不是内存地址比较~

  • 与 == 的区别== 比较引用类型变量(内存地址)或基本类型变量的值,equals( ) 比较对象的内容(重写后)。

Person person1 = new Person(21,"小美");
Person person2 = new Person(21,"小美");System.out.println(person1 == person2);      // false - 比较引用
System.out.println(person1.equals(person2)); // true  - 比较内容

3.2 Object类中的默认equals实现

        所有Java类都继承自 Object 类,其默认的 equals 方法实现就是使用==比较:

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

这显然不能满足我们比较对象内容的需求,因此需要重写equals方法。 


3.3 如何正确重写equals方法

让我们通过一个完整的Person类示例来理解如何正确重写equals方法:

//定义一个名为 Person 的类
class Person {//String是字符串类型,用于存储文本public String name;//int是整数类型,用于存储数字public int age;// 构造方法,用于创建Person对象时初始化对象public Person(int age,String name) {this.name = name;this.age = age;}//重写toString方法,用于打印对象信@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +  // 输出name属性", age=" + age +          //输出1age属性'}';}//重写equals方法,用于比较两个Person对象是否相等@Overridepublic boolean equals(Object obj) {// 1.检查传入的对象是否为nullif (obj == null) {return false ;// null不等于任何对象}// 2.检查是否是同一个对象(内存地址是否相同)if(this == obj) {return true ;//同一个对象当然相等}// 3.检查对象类型是否匹配// instanceof用于检查对象是否是某个类的实例// 如果不是Person类对象if (!(obj instanceof Person)) {return false ;//类型不同肯定不相等}// 4.向下转型,比较属性值Person person = (Person) obj ;// 5.比较关键属性值// 对于基本类型age用==比较// 对于引用类型name用equals比较(注意name不能为null)return this.name.equals(person.name) && this.age==person.age ;}
}

其中这一部分就是重写:

可以像我们上述的那样写,也可以用电脑生成的这样写:

 @Overridepublic boolean equals(Object obj) {//检查是否是同一个对象if (this == obj) return true;//检查是否是null或者类型不同if (obj == null || getClass() != obj.getClass()) return false;//类型转换Person person = (Person) obj;//比较年龄和名字是否相同return age == person.age && Objects.equals(name, person.name);}

接下来测试一下我们的equals方法:

public class Test {public static void main(String[] args) {//创建两个内容相同但内存地址不同的Person对象Person person1 = new Person(21, "小美");System.out.println(person1);Person person2 = new Person(21, "小美");System.out.println(person2);System.out.println("---------------------------");//分割线//比较person1和person2是否是同一个对象,== 比较的是内存地址System.out.println(person1 == person2);//调用equals方法比较,equals比较的是内容(因为我们重写了equals方法)System.out.println(person1.equals(person2));}
}

结果为:

 总结一下:

  • 如果是以后自定义的类型,那么一定要记住重写equals方法
  • 比较对象中的内容是否相同的时候,一定要重写equals方法

4. hashCode方法

4.1 hashCode的概念

        hashCode也是Java中Object类提供的一个方法,它返回对象的哈希码值(一个32位整数)。哈希码的主要用途是提高哈希表的性能,让对象能够快速被查找

hashcode方法源码: 

// Object类中的默认实现
public native int hashCode();

        📌 简单理解:可以把hashCode想象成对象的"身份证号",虽然不能完全唯一标识一个对象,但可以用来快速区分大多数对象。

可以看刚刚toString方法的源码:

        hashCode( )这个方法,帮我们算了一个具体的对象位置,这个位置是经过处理之后的一个哈希值~

        那么为什么需要hashCode?

假设我们有10000个Person对象要存储:

  • 没有hashCode:每次查找都需要遍历所有对象,调用equals比较,时间复杂度O(n)。

  • 有hashCode:先比较hashCode快速定位大致范围,再精细比较,时间复杂度接近O(1)。


4.2 对hashCode理解

 我们输出一下两个person的哈希值:

public class Test {public static void main(String[] args) {//创建两个内容相同但内存地址不同的Person对象Person person1 = new Person(21, "小美");System.out.println(person1);Person person2 = new Person(21, "小美");System.out.println(person2);System.out.println("---------------------------");//分割线System.out.println(person1.hashCode());System.out.println(person2.hashCode());}
}

结果显然是不同的:

        这个时候我们有一个需求,我们认为这两个person就是同一个对象,将来往哈希表里存放的时候,不用多存一份,我们认为这两个就是一份,放在同一个位置,这个时候逻辑就不一样了,这个时候我们重写hashCode:

再观察结果会发现一样了:

        这儿也就说明重写hashCode后,逻辑上将这两个person的属性name和age传给这个方法,它在逻辑上算出了同一个位置。

        如不重写hashCode,原来它们该出现在同一个位置,但是不会了。

        后面讲到哈希表的时候,就知道两个一样的对象我们想放在同一个位置,此时就可以利用重写这个方法来实现啦~


5. 接口使用实例(comparable接口) 

现在,假设我们有两位学生,我们想比较一下两位学生年龄谁大谁小:

class Student {public String name;public int age;//构造方法,用于创建Student对象时初始化属性public Student(String name,int age) {this.name = name;this.age = age;}//重写toString方法,用于打印对象信息@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class Test2 {Student student1 = new Student("小美",21);Student student2 = new Student("小帅",22);
}

        或许有的友友们想直接:System.out.println(student1 > student2);但是不可以的,这两个不是基本数据类型。目前比不了两个学生的年龄,这两个学生是引用类型,这个时候就需要implements了

  • Student类实现了Comparable接口,表示学生对象可以互相比较。
  • 泛型<Student>表示这是Student类之间的比较。
  • Comparable是Java中的一个接口,位于java.lang包中。它定义了对象的自然排序方式,任何实现了Comparable接口的类都可以和同类对象进行比较和排序

当我们实现这个接口后,我们需要重写Comparable里的一个compareTo方法

 @Overridepublic int compareTo(Student o) {return 0;}

        我们要在这里面填写用什么比较,比如说拿名字比或者拿年龄进行比较,这里我们用年龄进行比较,这样实现:

 @Overridepublic int compareTo(Student o) {//比较年龄,return返回比较结果://负数:当前对象"小于"参数对象//零:两对象"相等"//正数:当前对象"大于"参数对象return this.age - o.age;}

如果这里看不懂,我们也可以这样写:

@Overridepublic int compareTo(Student o) {//参数o是要比较的另一个学生对象if(this.age > o.age) {return 1;// 如果当前学生年龄较大,返回1(表示this应该排在o后面)} else if (this.age < o.age) {return -1;// 如果当前学生年龄较小,返回-1(表示this应该排在o前面)}else {return 0;// 如果两人年龄相同,返回0(表示两者顺序相等)}}

然后将两者进行比较:

public class Test2 {public static void main(String[] args) {Student student1 = new Student("小美", 21);Student student2 = new Student("小帅", 22);//student1这个对象和student2这个对象进行比较System.out.println(student1.compareTo(student2));}
}

        System.out.println(student1.compareTo(student2));这就将student1和student2进行了比较。我们看结果是一个负数:

        比较出student1的年龄比student2的年龄小。 

        总结一下: 自定义类型想要比较大小,要实现comparable这个接口,一定要重写里面的compareTo方法


comparable和equals都用来比较对象,那么二者有什么区别?

Comparableequals()
目的判断两个对象的大小判断两个对象是否逻辑相等
所属接口/类java.lang.Comparable要重写compareToObject类方法(可重写)
返回值int(负数/0/正数)boolean(true/false)
// Comparable接口
public interface Comparable<T> {int compareTo(T o);  // 返回:-1(小于)、0(等于)、1(大于)
}// Object.equals()
public boolean equals(Object obj);  // 返回:true/false

        再说一说这个接口的不好之处:对类的侵入性比较强,比如说我们根据姓名去比较大小,可能就会出现问题。当然我们有另一种解决方式。

        这时候我们Student不实现Comparable接口,我们再定义一个AgeComparator类去实现另一个接口Comparator,在里面指定是Student。

在这个接口里面有一个compare方法,我们重写它就可以:

实际我们这样写就可以:

//AgeComparator类:专门用来比较Student对象的年龄
//实现了Comparator<Student>接口表示这是一个Student的比较
class AgeComparator implements Comparator<Student> {//实现compare方法 - 定义两个学生的年龄比较规则//o1 第一个学生对象//o2 第二个学生对象//return 比较结果://结果负数:o1的年龄 < o2的年龄//结果为零:o1的年龄 == o2的年龄//结果为正数:o1的年龄 > o2的年龄@Overridepublic int compare(Student o1, Student o2) {//用o1的年龄减去o2的年龄得到比较结果//例如:21岁 vs 22岁 → 21-22 = -1(表示o1比o2小)return o1.age - o2.age;}
}
public class Test2 {public static void main(String[] args) {Student student1 = new Student("小美", 21);Student student2 = new Student("小帅", 22);//创建年龄比较器的实例AgeComparator ageComparator = new AgeComparator();//使用比较器比较两个学生,并且输出结果。System.out.println(ageComparator.compare(student1, student2));}
}

        我们通过创建一个对象,通过对象的引用去调用compare,我们传的是student,则把student1和student2传进去就好了,我们的返回值是一个整数,通过sout输出就好。结果为:

说明student1的年龄比studen2的年龄小一岁。

        同样我们可以根据姓名去比较

        再定义一个NameComparator类去实现另一个接口Comparator,在里面指定是Student。在这个接口里面有一个compare方法,我们重写它就可以:

我们这样去实现它:

//NameComparator类:专门用来按姓名比较Student对象
class NameComparator implements Comparator<Student> {
//实现compare方法 - 定义两个学生姓名的比较规则
//o1 第一个学生对象
//o2 第二个学生对象
//return 比较结果:
//负数:o1的姓名在字典序中小于o2的姓名
//零:两个姓名相同
//正数:o1的姓名在字典序中大于o2的姓名@Overridepublic int compare(Student o1, Student o2) {//使用String类的compareTo方法进行字符串比较//compareTo会逐个比较字符的Unicode值return o1.name.compareTo(o2.name);}
}
public class Test2 {public static void main(String[] args) {Student student1 = new Student("小美", 21);Student student2 = new Student("小帅", 22);//创建姓名比较器的实例NameComparator nameComparator = new NameComparator();//使用比较器比较两个学生的姓名,并且输出结果。System.out.println(nameComparator.compare(student1,student2));}
}

输出的结果为:

        Java中字符串比较是通过 String.compareTo( )方法实现的,规则如下:

逐个字符比较:从第一个字符开始,比较对应位置的 Unicode 值,有三种可能结果

  • 如果字符不同:返回当前字符的 Unicode 差值

  • 如果字符相同:继续比较下一个字符

  • 如果全部字符相同但长度不同:最后return返回长度差值

        中文比较是基于 Unicode 编码的:每个汉字都有对应的 Unicode 值,比较的时候会转换成 Unicode 数值进行比较啦~

比如说博主这里的"小美" vs "小帅":

  • "小"和"小"相同(Unicode: 23567)
  • 比较第二个字:"美"(32654)和 "帅"(24069)
  • 32654 - 24069 = 8585(正数,表示"美" > "帅")

以上是比较器的使用,这种方法对类的侵入性不强~


下面是完整的详细代码,希望能对友友们有帮助:

class Student{public String name;public int age;//构造方法,用于创建Student对象时初始化属性public Student(String name,int age) {this.name = name;this.age = age;}//重写toString方法,用于打印对象信息@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
//AgeComparator类:专门用来比较Student对象的年龄
//实现了Comparator<Student>接口表示这是一个Student的比较
class AgeComparator implements Comparator<Student> {//实现compare方法 - 定义两个学生的年龄比较规则//o1 第一个学生对象//o2 第二个学生对象//return 比较结果://结果负数:o1的年龄 < o2的年龄//结果为零:o1的年龄 == o2的年龄//结果为正数:o1的年龄 > o2的年龄@Overridepublic int compare(Student o1, Student o2) {//用o1的年龄减去o2的年龄得到比较结果//例如:21岁 vs 22岁 → 21-22 = -1(表示o1比o2小)return o1.age - o2.age;}
}
//NameComparator类:专门用来按姓名比较Student对象
class NameComparator implements Comparator<Student> {
//实现compare方法 - 定义两个学生姓名的比较规则
//o1 第一个学生对象
//o2 第二个学生对象
//return 比较结果:
//负数:o1的姓名在字典序中小于o2的姓名
//零:两个姓名相同
//正数:o1的姓名在字典序中大于o2的姓名@Overridepublic int compare(Student o1, Student o2) {//使用String类的compareTo方法进行字符串比较//compareTo会逐个比较字符的Unicode值return o1.name.compareTo(o2.name);}
}
public class Test2 {public static void main(String[] args) {Student student1 = new Student("小美", 21);Student student2 = new Student("小帅", 22);//创建年龄比较器的实例AgeComparator ageComparator = new AgeComparator();//使用比较器比较两个学生,并且输出结果。System.out.println(ageComparator.compare(student1, student2));//创建姓名比较器的实例NameComparator nameComparator = new NameComparator();//使用比较器比较两个学生的姓名,并且输出结果。System.out.println(nameComparator.compare(student1,student2));}
}

6. Clonable接口和深浅拷贝

   Cloneable 是 Java 中的一个标记接口,位于 java.lang 包中。它没有任何方法,只是用来标记一个类可以被克隆

下面通过具体代码对这个知识点进行讲解:

我们实例化了一位age是6岁的人:

class Person {public int age;public Person(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +" age=" + age +'}';}
}
public class Test3 {public static void main(String[] args) {Person person1 = new Person(6);}
}

这个时候我们观察对应的内存结构:

        我们现在有一个要求,将左边引用所指的对象克隆一份,也就是将来还会有person2,这个时候就要调用 clone 方法了。

但是这个时候不能成功的调用,会出现报错:

1. 没有实现 Cloneable 接口

        Java中有规定,要使用 clone( ) 方法,类必须显式实现 Cloneable 接口Cloneable是一个标记接口(没有方法的接口),它告诉 JVM 这个类允许被克隆。不实现它调用 clone( ) 会抛CloneNotSupportedException异常。

2. 没有重写 clone( ) 方法

        即使实现了 Cloneable 接口,还需要重写 Object 类的 clone( ) 方法并将其访问修饰符改为public ,因为 Object 中的 clone( ) 是 protected 的。


我们重写clone( ) 方法

        这个时候我们如果看到CloneNotSupportedException异常,我们要处理掉它,将鼠标光标点在红线clone( )处,按住Alt和Enter,再点击第一个。

代码就会变为:

但这个时候又会出现另一个错误,这个时候我们要向下转型

        为什么要向下转型呢?我们之前讲过在Java 中,clone( )方法是在 Object 类中定义的。

        注意它的返回类型是Object,而不是具体的子类类型。 虽然我们重写了 clone( ) 方法,但是方法签名中的返回类型仍然是 Object 编译器只知道返回的是 Object ,不知道实际是 Person。所以我们要向下转型,强制类型转换,显式告诉编译器:这个对象实际上是 Person 类型。


现在我们重写了clone( )方法,也进行了向下转型,但是我们还没有实现接口!接下来实现接口Cloneable

我们来观察这个接口的源码:

        会发现接口内部全部都是空的,没有定义任何一个方法,是空接口,也被称为标记接口。仅仅作为一个"标记",告诉 JVM 这个类的对象允许被克隆。

现在我们也实现了接口,我们输出观察结果:

public class Test3 {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(6);Person person2 = (Person)person1.clone();System.out.println(person1);System.out.println(person2);}
}

成功实现了克隆,这个时候我们观察内部结构:

        克隆的时候,会克隆出一模一样的部分(右边上面蓝色框),也有个地址0x88,此时我们的person2就存储了克隆出来新的对象的内容,整体的克隆就完成了,是通过调用super.clone( )去帮助完成克隆。

下面是完整的代码,希望能对友友们有帮助:

/*** Person类实现了Cloneable接口,表示这个类的对象可以被克隆* Cloneable是一个标记接口,内部没有方法,只是用来表示允许克隆*/
class Person implements Cloneable {public int age;//public Money m;//构造方法 - 创建Person对象时初始化属性public Person(int age) {this.age = age;}//重写toString方法 - 定义对象打印时的显示格式@Overridepublic String toString() {return "Person{" +" age=" + age +'}';}/*** 重写clone方法 - 实现对象克隆功能* @return 克隆后的新对象* @throws CloneNotSupportedException 如果不支持克隆则抛出异常*/@Overrideprotected Object clone() throws CloneNotSupportedException {// 调用Object类的clone()方法实现浅拷贝return super.clone();}
}
public class Test3 {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(6);// 克隆person1创建person2// 需要强制转型,因为clone()返回的是Object类型Person person2 = (Person)person1.clone();System.out.println(person1);System.out.println(person2);}
}

😼现在我们对这个代码做出小小的改动,我们添加一个Money类:

class Money {public double money = 66.6;
}
class Person implements Cloneable {// 定义两个属性:age(年龄)和m(钱)public int age;// 基本数据类型,直接存储值public Money m;// 引用类型,存储的是对象的地址//构造方法 - 创建Person对象时初始化属性public Person(int age) {this.age = age;this.m = new Money();}
@Overridepublic String toString() {return "Person{" +" age=" + age +'}';}
@Overrideprotected Object clone() throws CloneNotSupportedException {// 调用Object类的clone()方法实现浅拷贝return super.clone();}
}

然后我们在测试类中写如下输出:

public class Test3 {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(6);// 克隆person1创建person2// 需要强制转型,因为clone()返回的是Object类型Person person2 = (Person)person1.clone();//打印两个Person对象中的money值System.out.println(person1.m.money);System.out.println(person2.m.money);}
}

这个时候观察结果money都是66.6:

 这个时候我们再做一些改变,将克隆后的person2的money改为99.9,再来输出一下改完的钱:

public class Test3 {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(6);// 克隆person1创建person2// 需要强制转型,因为clone()返回的是Object类型Person person2 = (Person)person1.clone();// 打印两个Person对象中的money值System.out.println(person1.m.money);// 输出person1的钱System.out.println(person2.m.money);// 输出person2的钱System.out.println("----------------------");//分割线// 修改person2的money值为99.9person2.m.money = 99.9;// 再次打印两个对象的money值System.out.println(person1.m.money);System.out.println(person2.m.money);}
}

这个时候观察结果,发现都是99.9:

        原来我们期望的是person2的money更改,person1的不更改,但是现在两个都更改了。我们看到的这种情况叫做浅拷贝

我们接下来分析一下这种情况的原因,我们观察内部结构:

        我们 new person 的时候,现在里面不仅有age,也有money,m是一个引用,也会占据一块内存(堆上下面的蓝色方框)。我们在构造方法中实例化了一块对象,也就意味着我们 new Money( )里面存了一个0x65(堆上右边橙色方框)。此时第一行代码结束。

        接下来我们克隆对象,我们将person1所指的对象(堆上下面的蓝色方框)克隆一份,也就是有一样的一份占了一块内存,person2所指向克隆出来的这份对象(堆上上面的蓝色区域),地址是0x981,person2里面存的是0x981(栈上红色方框区域)。我们要注意,我们克隆的是person1所指向的对象,但是没有拷贝对象里面的对象,那么意味着m还是指向原来的66.6的money(绿色箭头)。age还是10,m还是0x65。此时不管是输出person1里面money的值还是person2里面的money的值,都是66.6。此时分割线上面的代码讲解完毕。


        接下来讲解分割线下面的代码,修改了person2里面的money内容,改为99.9。

        现在我们修改了,改为99.9,我们通过person1去拿的时候,也是99.9,通过person2去拿的时候,也是99.9,此时我们看到的这个现象就是浅拷贝。

完整代码如下:希望能帮助大家理解浅拷贝:

class Money {public double money = 66.6;
}
class Person implements Cloneable {// 定义两个属性:age(年龄)和m(钱)public int age;// 基本数据类型,直接存储值public Money m;// 引用类型,存储的是对象的地址//构造方法 - 创建Person对象时初始化属性public Person(int age) {this.age = age;this.m = new Money();}
@Overridepublic String toString() {return "Person{" +" age=" + age +'}';}
@Overrideprotected Object clone() throws CloneNotSupportedException {// 调用Object类的clone()方法实现浅拷贝return super.clone();}
}
public class Test3 {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(6);// 克隆person1创建person2// 需要强制转型,因为clone()返回的是Object类型Person person2 = (Person)person1.clone();// 打印两个Person对象中的money值System.out.println(person1.m.money);// 输出person1的钱System.out.println(person2.m.money);// 输出person2的钱System.out.println("----------------------");//分割线// 修改person2的money值为99.9person2.m.money = 99.9;// 再次打印两个对象的money值System.out.println(person1.m.money);System.out.println(person2.m.money);}
}

        那么什么是深拷贝呢,就是我们希望将对象里面的对象也克隆一份。接下来我们回到代码上进行深拷贝。我们希望改动person2的money,让它变为99.9,而person1的不变还是66.6。

具体步骤:

  1. 让Money类也实现Cloneable接口

只有实现了这个接口的Money类才能被克隆啦~

  1. 在Person类重写的clone方法中

  • 先调用super.clone()完成基本类型的拷贝
  • 然后对引用类型字段m手动调用clone()方法
  • 这样person1和person2就会有各自独立的Money对象

深拷贝完整的代码如下:

// Money类实现Cloneable接口才能被克隆
class Money implements Cloneable{public double money = 66.6;// 重写clone方法,允许Money对象被克隆@Overrideprotected Object clone() throws CloneNotSupportedException {// 调用父类(Object)的clone方法实现克隆return super.clone();}
}
/*** Person类实现了Cloneable接口,表示这个类的对象可以被克隆* Cloneable是一个标记接口,内部没有方法,只是用来表示允许克隆*/
class Person implements Cloneable {// 定义两个属性:age(年龄)和m(钱)public int age;// 基本数据类型,直接存储值public Money m;// 引用类型,存储的是对象的地址//构造方法 - 创建Person对象时初始化属性public Person(int age) {this.age = age;this.m = new Money();// 创建一个新的Money对象}//重写toString方法 - 定义对象打印时的显示格式@Overridepublic String toString() {return "Person{" +" age=" + age +'}';}/*** 重写clone方法 - 实现对象克隆功能* @return 克隆后的新对象* @throws CloneNotSupportedException 如果不支持克隆则抛出异常*/@Overrideprotected Object clone() throws CloneNotSupportedException {// 1. 先调用父类的clone方法完成基本类型的拷贝(浅拷贝)//这会复制age基本类型,并复制m引用(此时两个对象的m指向同一个Money对象)Person tmp = (Person) super.clone();// 2. 对引用类型m进行克隆,创建新的Money对象(深拷贝关键步骤)//将tmp的m指向克隆出来的新Money对象,而不是原来的那个tmp.m = (Money) this.m.clone();// 3. 返回深拷贝后的对象return tmp;}
}
public class Test3 {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(6);// 克隆person1创建person2// 需要强制转型,因为clone()返回的是Object类型Person person2 = (Person)person1.clone();// 打印两个Person对象中的money值System.out.println(person1.m.money);// 输出person1的钱System.out.println(person2.m.money);// 输出person2的钱System.out.println("----------------------");//分割线// 修改person2的money值为99.9person2.m.money = 99.9;// 再次打印两个对象的money值// 如果是浅拷贝,person1的money也会变成99.9// 但这里是深拷贝,所以只有person2的money改变System.out.println(person1.m.money);System.out.println(person2.m.money);}
}

 输出结果为:

这里再对深拷贝的知识点讲解一下:

        深拷贝是指创建一个新对象,并递归地复制原对象及其引用的所有对象与浅拷贝的区别在于:

  • 浅拷贝只复制基本类型,引用类型只复制引用地址(新旧对象共享引用对象)

  • 深拷贝:完全复制整个对象结构,包括所有引用对象(新旧对象完全不共享任何引用)

对于上述代码实现深拷贝的过程再进行总结一下:

1.让所有相关类实现Cloneable接口

class Money implements Cloneable { ... }
class Person implements Cloneable { ... }

2.在每个类中重写clone()方法 

@Override
protected Object clone() throws CloneNotSupportedException {return super.clone();
}

3.在包含引用类型的类中,手动克隆引用对象 

Person tmp = (Person) super.clone();  // 先浅拷贝
tmp.m = (Money) this.m.clone();      // 再手动克隆引用对象
return tmp;

        我们要知道,深拷贝和浅拷贝看的就是代码的实现过程,不能单纯的认为克隆这个方法就是且拷贝或者是深拷贝,不是说某个方法是深拷贝或者浅拷贝,而是这整个代码的实现过程是深拷贝或者浅拷贝~


制作不易,更多内容加载中~感谢友友们的点赞收藏关注~~

如有问题欢迎批评指正,祝友友们生活愉快,学习工作顺顺利利!

相关文章:

【JAVA】十三、基础知识“接口”精细讲解!(三)(新手友好版~)

目录 1. Object类 1.1 Object的概念 1.2 Object例子 2. toString 2.1 toString的概念 2.2 为什么要重写toString 2.3 如何重写toString 3. 对象比较equals方法 3.1 equals( ) 方法的概念 3.2 Object类中的默认equals实现 3.3 如何正确重写equals方法 4. hashCode方…...

每周靶点分享:Angptl3、IgE、ADAM9及文献分享:抗体的多样性和特异性以及结构的新见解

本期精选了《脂质代谢的关键调控者Angptl3》《T细胞活化抑制因子VISTA靶点》《文献分享&#xff1a;双特异性抗体重轻链配对设计》三篇文章。以下为各研究内容的概述&#xff1a; 1. 脂质代谢的关键调控者Angptl3 血管生成素相关蛋白3&#xff08;Angptl3&#xff09;是血管生…...

网络协议之DHCP和PXE分析

写在前面 本文看下DHCP和PXE相关内容。 1&#xff1a;正文 不知道你自己手动配置过IP地址没有&#xff0c;在Linux的环境中可以通过如下的命令们来进行配置&#xff1a; $ sudo ifconfig eth1 10.0.0.1/24 $ sudo ifconfig eth1 up以及&#xff1a;$ sudo ip addr add 10.0…...

SSH 服务部署指南

本指南涵盖 OpenSSH 服务端的安装、配置密码/公钥/多因素认证&#xff0c;以及连接测试方法。 适用系统&#xff1a;Ubuntu/Debian、CentOS/RHEL 等主流 Linux 发行版。 1. 安装 SSH 服务端 Ubuntu/Debian # 更新软件包索引 sudo apt update# 安装 OpenSSH 服务端 sudo apt i…...

表达式求值(算法题)

#include <bits/stdc.h> // 引入常用头文件 using namespace std;stack<int> num; // 存储操作数的栈 stack<char> op; // 存储运算符的栈/* 执行一次运算操作&#xff1a;1. 从num栈弹出两个操作数(n2先弹出&#xff0c;作为右操作数)2. 从op栈弹出运算符…...

IO流--13--MultipartFile

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 MultipartFile1. 概述2. 常用方法解析2.1 getName方法2.2 getOriginalFileName方法2.3 getContentType方法2.4 isEmpty方法2.5 getSize方法2.6 getBytes方法2.7 get…...

leetcode 242. Valid Anagram

题目描述 因为s和t仅仅包含小写字母&#xff0c;所以可以开一个26个元素的数组用来做哈希表。不过如果是unicode字符&#xff0c;那就用编程语言自带的哈希表。 class Solution { public:bool isAnagram(string s, string t) {int n s.size();if(s.size() ! t.size())return …...

内核态函数strlcpy及strscpy以及用户态函数strncpy

一、背景 编写C程序时有一类看似简单实则经常暗藏漏洞的问题就是字符串的处理。对于字符串的处理&#xff0c;常用的函数如strcpy&#xff0c;sprintf&#xff0c;strcat等&#xff0c;这些函数的区别无外乎就是处理\0结尾相关的逻辑。字符串的长度有时候并不能很好确定&#…...

Matlab 车辆四自由度垂向模型平稳性

1、内容简介 Matlab221-车辆四自由度垂向模型平稳性 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略基于Simulink的汽车平顺性仿真_1_杜充 基于Simulink的汽车平顺性仿真分析_谢俊淋...

【hadoop】Sqoop数据迁移工具的安装部署

一、Sqoop安装与配置 步骤&#xff1a; 1、使用XFTP将Sqoop安装包sqoop-1.4.6.bin__hadoop-2.0.4-alpha.tar.gz发送到master机器的主目录。 2、解压安装包&#xff1a; tar -zxvf ~/sqoop-1.4.6.bin__hadoop-2.0.4-alpha.tar.gz 3、修改文件夹的名字&#xff0c;将其改为s…...

只出现一次的数字(暴力、哈希查重、异或运算)

目录 一.题目 题目解析 题目链接 二.解题过程 俗手&#xff08;暴力&#xff1a;数组模拟哈希表&#xff09; 思路 代码示例 提交情况 本手&#xff1a;哈希查重 思路 代码示例 提交情况 妙手&#xff1a;异或运算 思路 代码示例 提交情况 作者的个人gitee 作者…...

Spark缓存

生活中缓存容量受成本和体积限制&#xff08;比如 CPU 缓存只有几 MB 到几十 MB&#xff09;&#xff0c;但会通过算法&#xff08;如 “最近最少使用” 原则&#xff09;智能决定存什么&#xff0c;确保存的是 “最可能被用到的数据”。 1. 为什么需要缓存&#xff1f; 惰性执…...

linux中的常用命令(一)

目录 常用的快捷键 1- tab键:命令或者路径提示及补全&#xff1b; 2-ctrlc:放弃当前输入&#xff0c;终止当前任务或程序 3-ctrll;清屏 4-ctrlinsert:复制 5-鼠标右键:粘贴&#xff1b; 6-altc:断开连接/ctrlshift r 重新连接 7-alt1/2/3/等&#xff1a;切换回话窗口 8-上下键…...

Lua学习笔记

文章目录 前言1. Lua的数据类型2. Lua的控制结构2.1 循环2.1.1 for2.1.1.1 数值循环2.1.1.2 迭代循环2.1.2 while2.1.3 repeat-until 2.2 条件语句2.3 函数 3. Lua中的变量作用域 前言 Lua是一种轻量级的、高效的、可扩展的脚本语言&#xff0c;由巴西里约热内卢天主教大学&am…...

5月8日星期四今日早报简报微语报早读

5月8日星期四&#xff0c;农历四月十一&#xff0c;早报#微语早读。 1、外交部回应中美经贸高层会谈&#xff1a;这次会谈是应美方请求举行的&#xff1b; 2、河南许昌官方&#xff1a;胖东来联合京东物流打造的供应链产业基地将于今年投入运营&#xff1b; 3、我国外汇储备…...

P2415 集合求和 详解

此题我认为主要考数学逻辑&#xff0c;这个题目考的是你面对代码时&#xff0c;是否会从中去找规律推导一个数学公式。 先看题目: 此题目与集合有关&#xff0c;所以对于数学基础不好的同学&#xff0c;我会先给你讲一下这个集合的相关知识。 一&#xff0c;首先&#xff0c;…...

#define ccw (0)和#define ccw 0什么区别

目录 区别 一般建议 简单总结 #define ccw (0) 和 #define ccw 0 这两者在大多数情况下的功能非常相似&#xff0c;但在细节上有一些区别&#xff0c;主要涉及宏展开时的行为。 区别 #define ccw (0)&#xff1a;宏定义的内容是&#xff08;0&#xff09;&#xff0c;带括…...

跨平台移动开发框架React Native和Flutter性能对比

背景与架构 React Native 和 Flutter 都是跨平台移动开发框架&#xff0c;但它们的性能表现因架构差异而异。React Native 在 2025 年采用了 Bridgeless New Architecture&#xff08;版本 0.74&#xff09;&#xff0c;使用 JavaScript Interface (JSI) 替代传统的 JavaScrip…...

【PhysUnits】2 SI 量纲 实现解析(prefix.rs)

源码 这是一个编译时量纲检查的物理单位库。 //! Physical Units Library with Type-Level Dimension Checking //! 带类型级量纲检查的物理单位库 //! //! This module provides type-safe physical unit representations using Rusts type system //! to enforce dimension…...

新能源汽车赛道变局:传统车企子品牌私有化背后的战略逻辑

2025年5月&#xff0c;一则资本市场动态引发行业震动&#xff1a;某国内头部传统车企宣布拟以每股2.57美元的价格私有化旗下高端新能源品牌&#xff0c;若交易完成&#xff0c;该新能源品牌将正式从纽交所退市。这一决策发生在全球新能源汽车行业经历剧烈洗牌、资本市场估值逻辑…...

[matlab]private和+等特殊目录在新版本matlab中不允许添加搜索路径解决方法

当我们目录包含有private,或者时候matlab搜索目录不让添加&#xff0c;比如截图&#xff1a; 在matlab2018以前这些都可以加进去后面版本都不行了。但是有时候我们必须要加进去才能兼容旧版本matlab库&#xff0c;比如mexopencv库就是这种情况。因此我们必须找到一个办法加进去…...

ImportError: cannot import name ‘Optional‘ from ‘pydantic‘

概览 再使用Optional定义fastapi可选参数时&#xff0c;出现了错误&#xff1a; ImportError: cannot import name Optional from pydantic python version: 3.8 pydantic version: 2.9.2 快速解决方案 Optional导入修改为typing包&#xff0c;如下 from typing import List…...

“水木精灵” 王泫梓妍时尚造型引关注

“水木精灵” 王泫梓妍一组时尚照片曝光&#xff0c;再次展现其独特时尚品味与青春活力。 照片中&#xff0c;王泫梓妍身着白色针织开衫搭配深蓝色牛仔短裙&#xff0c;开衫上精致的水钻装饰与深蓝色海军领增添了细节亮点&#xff0c;牛仔短裙的金色纽扣设计别致&#xff0c;整…...

数据结构-堆排序

1.定义 -堆中每个节点的值都必须大于等于&#xff08;或小于等于&#xff09;其左右子节点的值。如果每个节点的值都大于等于其子节点的值&#xff0c;这样的堆称为大根堆&#xff08;大顶堆&#xff09;&#xff1b;如果每个节点的值都小于等于其子节点的值&#xff0c;称为…...

影响服务器性能的主要因素是什么

在这个数字化高速发展的时代&#xff0c;服务器就像是幕后的超级英雄&#xff0c;默默支撑着我们丰富多彩的网络世界。首先&#xff0c;硬件配置堪称服务器性能的基石。就好比一辆跑车&#xff0c;强大的引擎&#xff08;CPU&#xff09;、宽敞的跑道&#xff08;内存&#xff…...

为什么 MySQL 用 B+ 树作为数据的索引,以及在 InnoDB 中数据库如何通过 B+ 树索引来存储数据以及查找数据

http://www.liuzk.com/410.html 索引是一种数据结构&#xff0c;用于帮助我们在大量数据中快速定位到我们想要查找的数据。 索引最形象的比喻就是图书的目录了。注意这里的大量&#xff0c;数据量大了索引才显得有意义&#xff0c;如果我想要在 [1,2,3,4] 中找到 4 这个数据&am…...

若依框架Ruoyi-vue整合图表Echarts中国地图标注动态数据

若依框架Ruoyi-vue整合图表Echarts中国地图 概述创作灵感预期效果整合教程前期准备整合若依框架1、引入china.json2、方法3、data演示数据4、核心代码 完整代码[毫无保留]组件调用 总结 概述 首先&#xff0c;我需要回忆之前给出的回答&#xff0c;确保这次的内容不重复&#…...

可撤销并查集,原理分析,题目练习

零、写在前面 可撤销并查集代码相对简单&#xff0c;但是使用场景往往比较复杂&#xff0c;经常用于处理离线查询&#xff0c;比较经典的应用是结合线段树分治维护动态连通性问题。在一些较为综合的图论问题中也经常出现。 前置知识&#xff1a;并查集&#xff0c;扩展域并查…...

中介者模式(Mediator Pattern)详解

文章目录 1. 中介者模式概述1.1 定义1.2 基本思想2. 中介者模式的结构3. 中介者模式的UML类图4. 中介者模式的工作原理5. Java实现示例5.1 基本实现示例5.2 飞机空中交通控制示例5.3 GUI应用中的中介者模式6. 中介者模式的优缺点6.1 优点6.2 缺点7. 中介者模式的适用场景8. 中介…...

Java网络编程:深入剖析UDP数据报的奥秘与实践

在浩瀚的计算机网络世界中,数据传输协议扮演着至关重要的角色。其中,用户数据报协议(UDP,User Datagram Protocol)以其独特的“轻量级”和“无连接”特性,在众多应用场景中占据了一席之地。与更为人熟知的传输控制协议(TCP,Transmission Control Protocol)相比,UDP提…...

17.thinkphp的分页功能

一&#xff0e;分页功能 1.不管是数据库操作还是模型操作&#xff0c;都使用paginate()方法来实现(第一种方式)&#xff1b; //查找user表所有数据&#xff0c;每页显示5条 returnView::fetch(index, [list > User::paginate(5)]); 页数&#xff1a; 2.创建一个静态模版页面…...

Pandas比MySQL快?

知乎上有人问&#xff0c;处理百万级数据&#xff0c;Python列表、Pandas、Mysql哪个更快&#xff1f; Pands是Python中非常流行的数据处理库&#xff0c;拥有大量用户&#xff0c;所以拿它和Mysql对比也是情理之中。 实测来看&#xff0c;MySQL > Pandas > Python列表…...

问题 | 低空经济未来发展前景机遇及挑战

低空经济 **一、发展前景与机遇**1. **政策红利加速释放&#xff0c;顶层设计逐步完善**2. **技术突破驱动商业化落地**3. **应用场景多元化拓展**4. **万亿级市场潜力** **二、主要挑战**1. **空域管理与安全监管难题**2. **技术瓶颈与产业链短板**3. **法规与标准体系待完善*…...

Matlab 分数阶PID控制

1、内容简介 Matlab218-分数阶PID控制 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...

如何对 Oracle 日志文件进行校验

目录 一、基本概述 二、基础知识 1、工具介绍 (1)BBED (2)dump 2、数据解析 (1)BLOCK 0 (2)BLOCK 1 (3)Block n( >=2 ) (4)redo record header (5)redo change 1)redo change header 2)redo change length list (6)Example 三、参考代码…...

从零开始用 AI 编写一个复杂项目的实践方法论

从零开始用 AI 编写一个复杂项目的实践方法论 这篇文章我用ai润色了一下&#xff0c;但是初稿是完全由我个人整理的逻辑思路&#xff0c;不是完全由ai生成的。其中内容也确实是我在实践中遇到问题、解决问题、总结出来的经验。 在从零开发一个复杂项目时&#xff0c;直接把目标…...

k8s监控方案实践(一):部署Prometheus与Node Exporter

k8s监控方案实践&#xff08;一&#xff09;&#xff1a;部署Prometheus与Node Exporter 文章目录 k8s监控方案实践&#xff08;一&#xff09;&#xff1a;部署Prometheus与Node Exporter一、Prometheus简介二、PrometheusNode Exporter实战部署1. 创建Namespace&#xff08;p…...

2.5 特征值与特征向量

本章围绕特征值、特征向量及其应用展开&#xff0c;是线性代数的核心章节之一。以下从四个核心考点系统梳理知识体系&#xff1a; 考点一&#xff1a;矩阵的特征值与特征向量 1. 计算方法 具体矩阵&#xff1a; 解特征方程 ∣ λ E − A ∣ 0 |\lambda E - A| 0 ∣λE−A∣…...

从简历筛选到面试管理:开发一站式智能招聘系统源码详解

当下&#xff0c;如何打造一款高效、精准的一站式智能招聘系统&#xff0c;成为了很多人力资源科技公司和创业团队关注的焦点。在这篇文章中&#xff0c;将带你深入了解如何从零开始开发一款智能招聘系统源码&#xff0c;涵盖从简历筛选到面试管理的全流程。 一、招聘系统的核心…...

10.进程控制(下)

一、进程程序替换&#xff08;重点&#xff09; 在程序替换过程中&#xff0c;并没有创建新的进程&#xff0c;只是把当前进程的代码和数据用新程序的代码和数据进行覆盖式的替换。 1&#xff09;一旦程序替换成功&#xff0c;就去执行新代码了&#xff0c;后序代码不执行 2&am…...

【Python 字符串】

Python 中的字符串&#xff08;str&#xff09;是用于处理文本数据的基础类型&#xff0c;具有不可变性、丰富的内置方法和灵活的操作方式。以下是 Python 字符串的核心知识点&#xff1a; 一、基础特性 定义方式&#xff1a; s1 单引号字符串 s2 "双引号字符串" s…...

最新CDGP单选题(第四章)补充

31、 [单选] 企业数据模型主题域的识别准则必须在整个企业模型中保持一致,以下哪项是常用的主题域识别准则: A:使用规范化规则,从系统组合中分离主题域...

Cursor降智找不到文件(Cursor降智)

文章目录 明明提供了上下文&#xff0c;却找不到文件&#xff01; 明明提供了上下文&#xff0c;却找不到文件&#xff01; 解决办法&#xff0c;删除codebase index&#xff0c;最好再把那个Index new folders by default给设置为Disabled。 这样设置貌似就不会出现找不到文件…...

Python基于Django和MySQL实现突发公共卫生事件舆情分析系统(有大屏功能)

说明&#xff1a;这是一个系统实战项目&#xff08;附带代码文档安装讲解&#xff09;&#xff0c;如需代码文档安装讲解可以直接到文章最后关注获取。 系统演示如下&#xff1a; Python基于Django和MySQL实现突发公共卫生事件舆情分析系统(有大屏功能) 项目背景 随着互联网的…...

cat、more和less的区别

在 Linux 系统中&#xff0c;cat、more 和 less 都是用于查看文件内容的命令&#xff0c;但它们在功能和使用场景上有显著区别。以下是它们的详细对比&#xff1a; 1. cat 命令 功能&#xff1a; - 直接输出整个文件&#xff1a;一次性将文件内容全部显示在终端上&#xff…...

Python cv2特征检测与描述:从理论到实战

在计算机视觉领域&#xff0c;特征检测与描述是图像匹配、物体识别、SLAM等任务的核心技术。本文将结合OpenCV的cv2库&#xff0c;系统讲解特征检测与描述的原理&#xff0c;并通过Python代码演示主流算法的实现。 一、为什么需要特征检测与描述&#xff1f; 图像本质是像素矩…...

实践005-Gitlab CICD全项目整合

文章目录 环境准备环境准备集成Kubernetes Gitlab CICD项目整合项目整合整合设计 后端Java项目部署后端Java项目静态检查后端Java项目镜像构建创建Java项目部署文件创建完整流水线 前端webui项目部署前端webui项目镜像构建创建webui项目部署文件创建完整流水线 构建父子类型流水…...

为了摸鱼和吃瓜,我开发了一个网站

平时上班真的比较累&#xff0c;摸鱼和吃瓜还要跳转多个平台的话&#xff0c;就累上加累了。 所以做了一个聚合了全网主流平台热搜的网站。 目前市面上确实有很多这种网站了&#xff0c;所以目前最主要有两点和他们不同&#xff1a; 给热搜列表增加了配图&#xff0c;刷的时候…...

React -> AI组件 -> 调用Ollama模型, qwen3:1.7B非常聪明

使用 React 搭建一个现代化的聊天界面&#xff0c;支持与 Ollama 本地部署的大语言模型进行多轮对话。界面清爽、功能完整&#xff0c;支持 Markdown 渲染、代码高亮、<think> 隐藏思考标签、流式渐进反馈、暗黑模式适配等特性。 &#x1f9e9; 核心功能亮点 ✅ 模型选择…...

算法中的数学:约数

1.求一个整数的所有约数 对于一个整数x&#xff0c;他的其中一个约数若为i&#xff0c;那么x/i也是x的一个约数。而其中一个约数的大小一定小于等于根号x&#xff08;完全平方数则两个约数都为根号x&#xff09;&#xff0c;所以我们只需要遍历到根号x&#xff0c;然后计算出另…...