【Java】Object 的 clone 方法分析

学习视频:https://study.163.com/course/introduction/1006177009.htm

学习目标

  1. 能够理解clone方法的由来
  2. 能够使用clone方法创建对象
  3. 能够理解克隆对象和原对象的关系
  4. 能够理解clone方法创建对象与new关键字和反射创建对象的不同
  5. 能够理解浅表复制和深层复制的含义
  6. 能够探寻对象的复制必须实现Cloneable接口的底层源码

1. 克隆方法的由来

问题一:什么是克隆(clone)方法?
答:创建并返回此对象的一个副本–按照原对象, 创建一个新对象(复制原对象的内容)。

问题二:已经存在 new关键字反射技术都可以创建对象,为什么还需要一个Object的clone方法呢?
答:必然是new关键字和 反射技术,存在一些弊端。看下面的例子体会弊端在哪。

1.1 new关键字和反射创建对象的弊端

我们来看一个需求:使用new关键字和反射 创建内容一模一样的对象,并且打印它们的哈希值。
演示素材–Person:

package com.yynm.pojo;

/**
 * @Description: Person实体类
 * @Author: yeyulemon
 * @Date: 2021-12-16 21:06
 **/
public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

演示Person类使用new关键字和反射创建内容一模一样的对象,并且打印它们的哈希值

/**
 * @Description: 使用new关键字和反射创建内容一模一样的对象,并且打印它们的哈希值
 * @Param: []
 * @return: void
 * @Author: yeyulemon
 * @Date: 2021/12/16 21:12
 */
@Test
public void test01() throws Exception {
    Person person1 = new Person();
    person1.setName("张三");
    person1.setAge(18);

    Person person2 = new Person();
    person2.setName("张三");
    person2.setAge(18);

    System.out.println(person1 + ":" + person1.hashCode());
    System.out.println(person2 + ":" + person2.hashCode());

    Class clazz = Class.forName("com.yynm.pojo.Person");
    Person person3 = (Person) clazz.getConstructor().newInstance();
    person3.setName("张三");
    person3.setAge(18);

    Person person4 = (Person) clazz.getConstructor().newInstance();
    person4.setName("张三");
    person4.setAge(18);

    System.out.println(person3 + ":" + person3.hashCode());
    System.out.println(person4 + ":" + person4.hashCode());
}

效果

com.yynm.pojo.Person@dc24521:230835489
com.yynm.pojo.Person@10bdf5e5:280884709
com.yynm.pojo.Person@6e1ec318:1847509784
com.yynm.pojo.Person@7e0b0338:2114650936

总结:通过new和反射可以创建内容一模一样的对象。但是,创建对象之后,通过setter方法设置一模一样的内容,如果需要创建更多内容一致的对象,那么就需要调用非常多的setter方法。

接下来,使用Object的clone方法演示,更加简便快捷,复制对象的操作!

1.2 使用clone方法创建对象

1.2.1 使用步骤

  1. 在需要clone方法的类上实现Cloneable接口
  2. 重写clone方法,在自己的clone方法中调用父类的clone方法,将返回值类型强转成本类类型,将当前clone方法修饰符改为public
  3. 在测试中调用对象的clone方法

1.2.2 代码演示

  1. 在需要clone方法的类上实现Cloneable接口
  2. 重写clone方法,在自己的clone方法中调用父类的clone方法,将返回值类型强转成本类类型,将当前clone方法修饰符改为public
package com.yynm.pojo;

/**
 * @Description: Person实体类
 * @Author: yeyulemon
 * @Date: 2021-12-16 21:06
 **/
public class Person implements Cloneable {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

  1. 在测试中调用对象的clone方法
/**
* @Description: 在测试中调用对象的clone方法
* @Param: []
* @return: void
* @Author: yeyulemon
* @Date: 2021/12/16 21:33
*/
@Test
public void test02() throws Exception {
    Person person1 = new Person();
    person1.setName("张三");
    person1.setAge(18);

    Person person2 = person1.clone();

    System.out.println(person1 + ":" + person1.hashCode());
    System.out.println(person2 + ":" + person2.hashCode());
}

效果

com.yynm.pojo.Person@dc24521:230835489
com.yynm.pojo.Person@10bdf5e5:280884709

总结:通过使用clone方法,我们发现大大的减少了创建重复对象代码。这也就是clone方法存在的意义。

2. 克隆出来的对象和原来的对象有什么关系

通过上面的测试,我们已经知道了,克隆出来的对象内容一致,但是对象哈希值不一样,所以是不同对象。那么两个对象的内容之间有什么关联呢?两个对象的内容是彼此独立,还是,两个对象底层使用的同一个内容呢?
素材(新Person):

package com.yynm.pojo;

/**
 * @Description: Person实体类
 * @Author: yeyulemon
 * @Date: 2021-12-16 21:06
 **/
public class Person implements Cloneable {
    private String name;
    private Integer age;
    private Children children;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Children getChildren() {
        return children;
    }

    public void setChildren(Children children) {
        this.children = children;
    }

    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

素材(新Children):

package com.yynm.pojo;

/**
 * @Description: Children实体类
 * @Author: yeyulemon
 * @Date: 2021-12-16 21:49
 **/
public class Children {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

测试代码:

/**
* @Description: 测试克隆对象和原对象之间成员变量的联系
 * @Param: []
 * @return: void
 * @Author: yeyulemon
 * @Date: 2021/12/16 21:51
 */
@Test
public void test03() throws Exception {
    Person person1 = new Person();
    person1.setName("张三");
    person1.setAge(18);
    Children children = new Children();
    children.setName("李四");
    children.setAge(18);
    person1.setChildren(children);

    Person person2 = person1.clone();

    System.out.println(person1 + ":" + person1.hashCode() + ";" + children + ":" +person1.getChildren().hashCode());
    System.out.println(person2 + ":" + person2.hashCode() + ";" + children + ":" +person2.getChildren().hashCode());
}

效果:

com.yynm.pojo.Person@10bdf5e5:280884709;com.yynm.pojo.Children@6e1ec318:1847509784
com.yynm.pojo.Person@7e0b0338:2114650936;com.yynm.pojo.Children@6e1ec318:1847509784

结论:通过测试发现克隆出来的对象虽然不一致,但是底层的成员变量的哈希值是一致的。
这种复制我们称之为: 浅表复制
浅表复制内存结构:

【Java】Object 的 clone 方法分析

3. 能不能让克隆出来的对象其中成员变量也变成新对象

3.1 浅表复制的弊端

由于浅表复制导致克隆的对象中成员变量的底层哈希值一致,如果我们操作其中一个对象的成员变量内容,就会导致所有的克隆对象的成员变量内容发生改变。
测试代码:

@Test
public void test04() throws Exception {
    Person person1 = new Person();
    Children children = new Children();
    children.setName("张三");
    children.setAge(18);
    person1.setChildren(children);
    Person person2 = person1.clone();
    System.out.println(person1 + ":" + person1.hashCode() + ";" + person1.getChildren().getName());
    System.out.println(person2 + ":" + person2.hashCode() + ";" + person2.getChildren().getName());
    children.setName("李四");
    children.setAge(18);
    System.out.println(person1 + ":" + person1.hashCode() + ";" + person1.getChildren().getName());
    System.out.println(person2 + ":" + person2.hashCode() + ";" + person2.getChildren().getName());
}

效果:

com.yynm.pojo.Person@573fd745:1463801669;张三
com.yynm.pojo.Person@15327b79:355629945;张三
com.yynm.pojo.Person@573fd745:1463801669;李四
com.yynm.pojo.Person@15327b79:355629945;李四

结论:clone方法默认的赋值操作是浅表复制,浅表复制存在弊端–仅仅创建新的对象,对象的成员内容底层哈希值是一致的,因此,不管是原对象还是克隆对象,只有其中一个修改了成员的数据,就会影响所有的原对象和克隆对象。
要解决浅表复制的问题:进行深层的复制。

3.2 深层复制

目的:不仅在执行克隆的时候,克隆对象是一个新对象,而且,克隆对象中的成员变量,也要求是一个新的对象。

3.2.1 开发步骤

  1. 修改Children类实现Cloneable接口
  2. 修改Children类重写clone方法
  3. 修改Person类重写clone方法,在clone方法中调用children的clone方法

3.2.2 代码实现

  1. 修改Children类实现Cloneable接口
  2. 修改Children类实现clone方法
    Children类:
package com.yynm.pojo;

/**
 * @Description: Children实体类
 * @Author: yeyulemon
 * @Date: 2021-12-16 21:49
 **/
public class Children implements Cloneable {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public Children clone() throws CloneNotSupportedException {
        return (Children) super.clone();
    }
}

Person类:

package com.yynm.pojo;

/**
 * @Description: Person实体类
 * @Author: yeyulemon
 * @Date: 2021-12-16 21:06
 **/
public class Person implements Cloneable {
    private String name;
    private Integer age;
    private Children children;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Children getChildren() {
        return children;
    }

    public void setChildren(Children children) {
        this.children = children;
    }

    @Override
    public Person clone() throws CloneNotSupportedException {
         Person personClone = (Person) super.clone();
         personClone.setChildren(this.children.clone());
        return personClone;
    }
}

测试代码:

@Test
public void test05() throws Exception {
    Person person1 = new Person();
    Children children = new Children();
    children.setName("张三");
    children.setAge(18);
    person1.setChildren(children);
    Person person2 = person1.clone();
    System.out.println(person1 + ":" + person1.hashCode() + ";" + person1.getChildren().hashCode());
    System.out.println(person2 + ":" + person2.hashCode() + ";" + person2.getChildren().hashCode());
}

效果:

com.yynm.pojo.Person@573fd745:1463801669;355629945
com.yynm.pojo.Person@4f2410ac:1327763628;1915503092

深层复制内存结构:

【Java】Object 的 clone 方法分析

4. 使用clone接口实现深层复制的弊端

4.1 使用clone接口实现深层复制的弊端

以上的方法虽然完成了深层复制,但是修改类中成员变量对应的源码,如果成员变量特别多,那么就需要修改多个类的源码。
例如以下代码,我们就需要修改两个成员变量对应类的源码(Children,Grandson):

package com.yynm.pojo;

/**
 * @Description: Person实体类
 * @Author: yeyulemon
 * @Date: 2021-12-16 21:06
 **/
public class Person implements Cloneable {
    private String name;
    private Integer age;
    private Children children;
    private Grandson grandson;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Children getChildren() {
        return children;
    }

    public void setChildren(Children children) {
        this.children = children;
    }

    public Grandson getGrandson() {
        return grandson;
    }

    public void setGrandson(Grandson grandson) {
        this.grandson = grandson;
    }

    @Override
    public Person clone() throws CloneNotSupportedException {
        Person personClone = (Person) super.clone();
        personClone.setChildren(this.children.clone());
        personClone.setGrandson(this.grandson.clone());
        return personClone;
    }
}

结论:使用克隆接口完成深度复制的弊端:

1. 重复实现Cloneable接口
2. 重复实现clone方法
3. 重复改写Person类的clone方法

可以使用IO流的方式进行复制操作(深度复制),可以解决重复修改源代码的问题。

4.2 使用IO进行克隆复制(深度复制)

4.2.1 使用IO复制相关的API介绍

1、ByteArrayOutputStream

【Java】Object 的 clone 方法分析
构造方法:
【Java】Object 的 clone 方法分析
2、ByteArrayInputStream
【Java】Object 的 clone 方法分析
构造方法:
【Java】Object 的 clone 方法分析
3、ObjectOutputStream
【Java】Object 的 clone 方法分析
构造方法:
【Java】Object 的 clone 方法分析
将对象写入流的方法:
【Java】Object 的 clone 方法分析
4、ObjectInputStream
【Java】Object 的 clone 方法分析
构造方法:
【Java】Object 的 clone 方法分析
要调用的方法:
【Java】Object 的 clone 方法分析
简单演示:一个对象的复制。
开发步骤:
  1. 创建ByteArrayOutputStream,将数据可以转换成字节
  2. 创建ObjectOutputStream,关联ByteArrayOutputStream
  3. 使用ObjectOutputStream的writeObject方法,读取要复制的对象
  4. 使用ByteArrayInputStream读取ByteArrayOutputStream的转化的对象字节数据
  5. 创建ObjectInputStream并用readObject读取对象字节数据返回新对象
    素材User类:
package com.yynm.pojo;

import java.io.Serializable;

/**
 * @Description:
 * @Author: yeyulemon
 * @Date: 2021-12-19 20:09
 **/
public class User implements Serializable {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

测试:

@Test
public void test06() throws Exception {
    User user1 = new User();
    user1.setUsername("张三");
    user1.setPassword("123456");

    // 1. 创建ByteArrayOutputStream,将数据可以转换成字节
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    // 2. 创建ObjectOutputStream,关联ByteArrayOutputStream
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    // 3. 使用ObjectOutputStream的writeObject方法,读取要复制的对象
    objectOutputStream.writeObject(user1);
    // 4. 使用ByteArrayInputStream读取ByteArrayOutputStream的转化的对象字节数据
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
    // 5. 创建ObjectInputStream并用readObject读取对象字节数据返回新对象
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    User user2 = (User) objectInputStream.readObject();

    System.out.println(user1 + ":" + user1.hashCode());
    System.out.println(user2 + ":" + user2.hashCode());
}

效果:

com.yynm.pojo.User@2b05039f:721748895
com.yynm.pojo.User@77468bd9:2001112025

4.3 使用IO改写Person的clone方法

4.3.1 开发步骤

  1. 克隆涉及的所有的类实现Serializable
  2. 修改Person类的clone方法,使用IO复制对象
  3. 测试演示

4.3.2 代码实现

  1. 克隆涉及的所有的类实现Serializable
  2. 修改Person类的clone方法,使用IO复制对象
package com.yynm.pojo;

import java.io.*;

/**
 * @Description: Person实体类
 * @Author: yeyulemon
 * @Date: 2021-12-16 21:06
 **/
public class Person implements Cloneable, Serializable {
    private String name;
    private Integer age;
    private Children children;
    private Grandson grandson;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Children getChildren() {
        return children;
    }

    public void setChildren(Children children) {
        this.children = children;
    }

    public Grandson getGrandson() {
        return grandson;
    }

    public void setGrandson(Grandson grandson) {
        this.grandson = grandson;
    }

    @Override
    public Person clone() throws CloneNotSupportedException {
        try {
            // 1. 创建ByteArrayOutputStream,将数据可以转换成字节
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 2. 创建ObjectOutputStream,关联ByteArrayOutputStream
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            // 3. 使用ObjectOutputStream的writeObject方法,读取要复制的对象
            oos.writeObject(this);
            // 4. 使用ByteArrayInputStream读取ByteArrayOutputStream的转化的对象字节数据
            ByteArrayInputStream bAIS = new ByteArrayInputStream(baos.toByteArray());
            // 5. 创建ObjectInputStream并用readObject读取对象字节数据返回新对象
            ObjectInputStream oIS = new ObjectInputStream(bAIS);
            Person personClone = (Person) oIS.readObject();
            return personClone;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

测试:

@Test
public void test07() throws Exception {
    Person person1 = new Person();
    Person person2 = person1.clone();
    System.out.println(person1 + ":" + person1.hashCode());
    System.out.println(person2 + ":" + person2.hashCode());
}

效果:

com.yynm.pojo.Person@4dcbadb4:1305193908
com.yynm.pojo.Person@77468bd9:2001112025

5. 为什么使用clone方法需要实现Cloneable接口

答:源代码就是这么设定的,实现接口仅仅是一个可以使用clone方法的标记。
那么源代码在哪里设定的呢?
查看jdk源码我们发现:

【Java】Object 的 clone 方法分析
因此,我们需要查看native修饰的背后的源码,这个一直要追溯到jdk底层C,C++源码。

5.1 下载完整jdk源码

下载地址:http://jdk.java.net/java-se-ri/7
查看步骤

【Java】Object 的 clone 方法分析
【Java】Object 的 clone 方法分析
【Java】Object 的 clone 方法分析
源码展示:
JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
  JVMWrapper("JVM_Clone");
  Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
  const KlassHandle klass (THREAD, obj->klass());
  JvmtiVMObjectAllocEventCollector oam;

#ifdef ASSERT
  // Just checking that the cloneable flag is set correct
  if (obj->is_javaArray()) {
    guarantee(klass->is_cloneable(), "all arrays are cloneable");
  } else {
    guarantee(obj->is_instance(), "should be instanceOop");
    bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass());
    guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");
  }
#endif

  // Check if class of obj supports the Cloneable interface.

  // All arrays are considered to be cloneable (See JLS 20.1.5)
  if (!klass->is_cloneable()) {
    ResourceMark rm(THREAD);
    THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
  }

  // Make shallow object copy
  const int size = obj->size();
  oop new_obj = NULL;
  if (obj->is_javaArray()) {
    const int length = ((arrayOop)obj())->length();
    new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);
  } else {
    new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);
  }
  // 4839641 (4840070): We must do an oop-atomic copy, because if another thread
  // is modifying a reference field in the clonee, a non-oop-atomic copy might
  // be suspended in the middle of copying the pointer and end up with parts
  // of two different pointers in the field.  Subsequent dereferences will crash.

  // 4846409: an oop-copy of objects with long or double fields or arrays of same
  // won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead
  // of oops.  We know objects are aligned on a minimum of an jlong boundary.

  // The same is true of StubRoutines::object_copy and the various oop_copy
  // variants, and of the code generated by the inline_native_clone intrinsic.

  assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");
  Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,
                               (size_t)align_object_size(size) / HeapWordsPerLong);
  // Clear the header
  new_obj->init_mark();

  // Store check (mark entire object and let gc sort it out)
  BarrierSet* bs = Universe::heap()->barrier_set();
  assert(bs->has_write_region_opt(), "Barrier set does not have write_region");
  bs->write_region(MemRegion((HeapWord*)new_obj, size));

  // Caution: this involves a java upcall, so the clone should be
  // "gc-robust" by this stage.

  if (klass->has_finalizer()) {
    assert(obj->is_instance(), "should be instanceOop");
    new_obj = instanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL);
  }

  return JNIHandles::make_local(env, oop(new_obj));
JVM_END

校验当前类是否实现克隆接口的代码:

// Check if class of obj supports the Cloneable interface.

// All arrays are considered to be cloneable (See JLS 20.1.5)
if (!klass->is_cloneable()) {
  ResourceMark rm(THREAD);
  THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
}

注释翻译:

数组类型默认可以直接克隆,而其它对象实现clone需要先实现Cloneable接口,否则抛出:
CloneNotSupportedException异常

结论,对象使用clone方法必须实现Cloneable接口。

Original: https://www.cnblogs.com/yeyulemon/p/16020410.html
Author: 夜雨柠檬
Title: 【Java】Object 的 clone 方法分析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/580433/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Apache Flink系列-②什么是Apache Flink?

    Apache Flink是一个在无界和有界数据流上进行有状态计算的框架。Flink提供了不同抽象级别的多个API,并为常见用例提供了专用库。 在这里,我们介绍Flink易于使用且富…

    Java 2023年6月5日
    058
  • Feign源码解析系列-注册套路

    感谢不知名朋友的打赏,感谢你的支持! 开始 在追寻Feign源码的过程中发现了一些套路,既然是套路,就可以举一反三,所以值得关注。这篇会详细解析Feign Client配置和初始化…

    Java 2023年6月13日
    069
  • Javaweb学习-JSP

    从JSP开始学习创建web项目 posted @2022-03-24 21:21 HelloHui 阅读(9 ) 评论() 编辑 Original: https://www.cnb…

    Java 2023年6月9日
    075
  • Spring 源码(4)在Spring配置文件中自定义标签如何实现?

    基于 Spring源码在处理定制的标签时是通过定制的命名空间处理器和 xsd文件进行解析的,在 spring的 classpath下的 META-INF/spring.schema…

    Java 2023年6月14日
    062
  • 面试突击55:delete、drop、truncate有什么区别?

    在 MySQL 中,删除的方法总共有 3 种:delete、truncate、drop,而三者的用法和使用场景又完全不同,接下来我们具体来看。 1.delete detele 可用…

    Java 2023年5月29日
    087
  • SpringBoot:SpringBoot和SpringCloud版本匹配问题导致Bean冲突问题

    报错信息 Error starting ApplicationContext. To display the conditions report re-run your appli…

    Java 2023年5月30日
    083
  • JAVA 异常 基本知识

    异常 异常定义 异常是运行过程中出现的错误 人为错误:填写错误等 随机错误:网络中断、内存耗尽等 一个健壮的程序必须处理各种各样的错误 Java的异常是class Object T…

    Java 2023年6月9日
    066
  • Sublime Text 编译 运行 Java 源代码 包 类文件

    Sublime Text 编译 Java 包 更新记录 2022/05/23 解决SublimeText控制台用户输入问题 前言 目前还存在很多问题,不过暂时能用,就先不折腾了,等…

    Java 2023年6月5日
    071
  • java stream 简单函数

    写在前面本文为笔者学习的一些心得,如有问题,评论请轻喷本文分为以下部分:中间操作终止操作归纳 中间操作 对 list 进行操作,返回一个新的 list 主要函数 作用 过滤操作 截…

    Java 2023年6月5日
    089
  • 用Java中的File类模拟实现对系统文件的增删改查效果

    码字不易,三连支持一波吧 😃 IO操作向来是各大语言的热区,而对文件的操作也是重中之重。那么在Java中也给我们提供了很多关于文件操作的类。今天我就用一个比较基本的 File类来模…

    Java 2023年6月7日
    077
  • SpringBoot 系列 web 篇之自定义返回 Http Code 的 n 种姿势

    虽然 http 的提供了一整套完整、定义明确的状态码,但实际的业务支持中,后端并不总会遵守这套规则,更多的是在返回结果中,加一个 code 字段来自定义业务状态,即便是后端 5xx…

    Java 2023年5月30日
    061
  • Spring 4 集成 redis 实现缓存 二

    项目开发过程中经常用到mybatis,为了提升查询效率,mybatis支持一级和二级缓存,一级缓存基于SqlSession级别,默认开启,二级缓存基于Mapper级别;一级和二级缓…

    Java 2023年6月5日
    086
  • 快速上手Spring项目

    注 : spring 需要导入commons-logging进行日志记录 . 我们利用maven , 他会自动下载对应的依赖项 . <dependency> <g…

    Java 2023年6月15日
    072
  • Java是一门强类型语言

    数据类型 语言类型 强类型语言 要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用 弱类型语言 java的数据类型 1 基本类型(primitive type)2 引用类…

    Java 2023年6月9日
    069
  • 【Redis】简单动态字符串SDS

    C语言字符串 char *str = "redis"; // 可以不显式的添加\0,由编译器添加 char *str = "redis\0"…

    Java 2023年6月8日
    099
  • SpringCloudAlibaba 微服务讲解(四)Sentinel–服务容错(二)

    4.7 Sentinel 规则 4.7.1 流控规则 流量控制,其原理是监控应用流量的QPS(每秒查询率)或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的榴莲高…

    Java 2023年6月7日
    074
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球