Java面向对象思想以及原理以及内存图解

文章目录

  • 什么是面向对象
    • 面向对象和面向过程区别
    • 创建一个对象用什么运算符?
    • 面向对象实现伪代码
    • 面向对象三大特征
    • 类和对象的关系。
  • 基础案例
    • 代码实现
    • 实例化
    • 创建car对象时car引用的内存图
    • 对象调用方法过程
  • 成员变量和局部变量
    • 作用范围
    • 在内存中的位置
  • 关于对象的引用关系
    • 简介
    • 相关代码
    • 内存图解
    • 对象相等和引用相等的区别
    • 类的构造方法的作用是什么
    • 构造方法的特点
    • 深拷贝和浅拷贝区别
      • 浅拷贝
      • 深拷贝
  • 匿名对象
    • 实例代码
    • 匿名对象与实例对象的区别
      • 实例代码
      • 图解匿名与非匿名内存运行
    • 使用场景
  • 封装
    • 什么是封装
    • 什么时private修饰
    • 代码示例
  • 构造函数
    • 什么是构造函数
    • 构造函数的小细节
    • 构造代码块
      • 构造代码块示例以及与构造方法的区别
  • this关键字
    • 什么是this关键字
    • this的应用
      • 解决构造函数初始化的问题
      • 用于构造函数之间进行互相调用
  • static关键字
    • 什么是static关键字
    • static特点
    • 实例变量和类变量的区别
    • 静态使用注意事项:
    • 静态有利有弊
      • 利处
      • 弊处
    • 错误代码示范
    • 图解对象如何调用static变量
  • main函数
    • 主函数
    • 主函数的定义格式以及关键字含义
    • 主函数是固定格式的
    • 如何使用args
  • 静态代码块
    • 格式
  • 设计优化
    • 单例模式
      • 简介
      • 饿汉式
      • 懒汉式(线程不安全)
      • 懒汉式(线程安全)
      • 内部类模式
      • 双重锁校验(线程安全)
      • 枚举单例模式(线程安全)
  • 相关面试题
  • 参考文献

什么是面向对象

面向对象和面向过程区别

面向过程:面向过程是将解决问题的思路转为一个个方法。
面向对象:面向对象则是编写一个对象,将这些思路封装成一个个对象方法,后续调用这个对象解决问题,相对面向过程而言,这种思路更符合人的思维并且更易扩展、复用、维护。

面向对象和面向过程性能差距:人们常常认为面向过程性能高于面向对象,因为创建的对象开销远远大于面向过程,实际上Java面向对象性能差的原因并不是这个,真正的原因是Java为半编译语言,运行并不是直接拿着二进制机械码执行,而是需要结果字节码转换这一步。
而且面向过程的性能并不一定比面向过程快,面向过程也需要计算偏移量以及某些脚本语言的性能也一定比Java好。

创建一个对象用什么运算符?

用new运算符,创建的对象的实例会在堆内存中开辟一个空间。而引用则在栈内存中,指向对象实例。

面向对象实现伪代码

以人开门为例,人需要开门,所以我们需要创建一个门对象,描述门的特征,这个门可以开或者关。所以门的伪代码如下:

门{
	开()
	{
	操作门轴
	}
}

上文说到了,面向对象的特点就是符合人的思维,而人开门这个功能,我们就可以创建一个人的对象,编写一个开门的动作,把门打开。通过这种对象调用对象的方式完成了功能。后续我们需要狗开门,猫开门也只是编写一个方法调用门对象的开的动作。

{
	开门(门对象){.()
	}
 }


面向对象三大特征

  1. 封装
  2. 继承
  3. 多态

类和对象的关系。

以生活事务为例,现实生活中的对象:张三 李四。他们都有姓名、性别、学习Java的能力。

所以我们要想通过面向对象思想实现抽象出对象,就得提取共性,编写一个类有姓名、性别、学习Java的能力。

public class Student {
    
    private String name;
    private int sex;
    
    public void studyJava(){
        System.out.println(this.name+"学习java");
    }
}

描述时,这些对象的共性有:姓名,年龄,性别,学习java功能。再将这些分析映射到java中,就是以class定义的类进行展开。

public static void main(String[] args) {
        Student zhangsan=new Student();
        zhangsan.setName("张三");
        zhangsan.studyJava();

        Student lisi=new Student();
        lisi.setName("李四");
        lisi.studyJava();
        
//        输出结果
//        张三学习java
//                李四学习java
    }

而具体对象就是对应java在堆内存中用new建立实体。

基础案例

需求:描述汽车(颜色,轮胎数)。描述事物其实就是在描述事物的属性和行为。

属性对应在类中即变量行为对应的类中的函数(方法)

代码实现

public class Car {
    //描述颜色
    String color = "红色";
    //描述轮胎数
    int num = 4;

    //运行行为。
    public void run() {

        System.out.println("颜色:"+color + " 轮胎数:" + num);
    }
}

实例化

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.run();
    }
}

创建car对象时car引用的内存图

在这里插入图片描述

对象调用方法过程

首先我们看一段代码,这是一个人类的class类代码

public class Person {
    private String name;
    private int age;
    private static String country = "cn";

    public Person(String name, int age) {
        this.name = name;
        this.age = 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;
    }

    public static void showCountry() {
        System.out.println("showCountry " + country);
    }


    public void speak() {
        System.out.println(this.getName() + " speak");
    }
}

假如我们在main中编写这样一段代码,请问在内存中他是如何工作的呢?

 public static void main(String[] args) {
        Person p = new Person("张三", 18);
        p.setName("李四");
    }

我们先从类类加载时开始分析,由于static关键字修改的变量或者方法会随着jvm加载类时一起创建,所以countryshowCountry()在方法区是这样的。

在这里插入图片描述

然后main方法开始执行对应代码,首先main方法入栈,初始化一个p引用

在这里插入图片描述

堆区开辟一个空间,创建一个person实例,p引用指向这个内存空间

在这里插入图片描述

调用setName,setName入栈,完成name值修改之后销毁

在这里插入图片描述

成员变量和局部变量

作用范围

成员变量作用于整个类中。
局部变量变量作用于函数中,或者语句中。

在内存中的位置

成员变量:在堆内存中,因为对象的存在,才在内存中存在。
局部变量:存在栈内存中。

关于对象的引用关系

简介

对象引用用于指向0个或者多个对象实例,对象实例可以被多个对象引用指向。

相关代码

假如我们使用上文car类执行以下代码,那么在内存中会如何执行呢?

car c=new car();
c.num=5;
car c1=c;
c.run();

内存图解

  1. 首先堆区开辟一个空间创建car对象,初始化值
  2. 修改num为5
  3. c1引用指向c,如下图所示
    在这里插入图片描述

对象相等和引用相等的区别

  1. 对象相等:两个对象内存中的存放的内容都相等
  2. 引用相等:两个引用指向的内存地址相等。

类的构造方法的作用是什么

完成对象初始化,首先在堆区创建对象实例。

构造方法的特点

  1. 与类名相同
  2. 无返回值
  3. 生成对象时自动执行
  4. 不可重写可以重载

深拷贝和浅拷贝区别

浅拷贝

对象进行拷贝时,如果内部有引用类型,克隆对象仅仅是复制被克隆内部对象的引用地址

为了介绍浅拷贝我们贴出这样一段代码,可以看到一个学生类有id和name,以及一个Vector的引用对象

public class Student implements Cloneable {
    private String id;
    private String name;
    private Vector<String> vector;


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Vector<String> getVector() {
        return vector;
    }

    public void setVector(Vector<String> vector) {
        this.vector = vector;
    }


    public Student() {
        try {
            System.out.println("创建对象需要三秒......");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public Student newInstance() {
        try {
            return (Student) this.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return null;
    }

  
}

然后我们使用下面这段代码进行测试,可以看到输出结果为true,说明student2的vector是student1的。如下图所示,克隆对象的内部引用对象和student1是通用的

@Test
    public void cloneTest() throws CloneNotSupportedException {

        long start,end;
        start=System.currentTimeMillis();
        Student student=new Student();
        end=System.currentTimeMillis();
        System.out.println("学生1创建时间长 "+(end-start));


        student.setId("1");
        student.setName("小明");
        Vector<String> v = new Vector<>();
        v.add("000000");
        v.add("000001");
        student.setVector(v);

        start=System.currentTimeMillis();
        Student student2= student.newInstance();
        end=System.currentTimeMillis();
        System.out.println("学生2创建时间长 "+(end-start));


        for (String s : student2.getVector()) {
            System.out.println(s);
        }
//        false则说明深拷贝成功
        System.out.println(student.getVector()==student2.getVector());
    }

在这里插入图片描述

深拷贝

了解了浅拷贝之后,我们就可以解释深拷贝了,克隆对象的内部引用对象都是全新复制出来的一份

基于上文student代码我们对此进行改造,重写以下clone方法

@Override
    protected Object clone() throws CloneNotSupportedException {
        Student clone = new Student();
        clone.setId(this.getId());
        clone.setName(this.getName());

        //避免clone导致浅拷贝问题
        Vector<String> srcVector = this.getVector();

        Vector<String> dstVector = new Vector<>();
        for (String v : srcVector) {
            dstVector.add(v);
        }

        clone.setVector(dstVector);

        return clone;

    }

在这里插入图片描述

匿名对象

实例代码

如下所示,在堆区创建一个对象实例,用后即被销毁。为了介绍匿名对象,我们首先需要编写一个汽车类

public class Car {
    //描述颜色
    private String color = "红色";
    //描述轮胎数
    private int num = 4;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    //运行行为。
    public void run() {
        this.setNum(++num);
        System.out.println("颜色:" + color + " 轮胎数:" + num);
    }
}

然后我们使用测试单元进行测试


@Test
    public void anonymously(){
        new Car().run();

    }
		

上述代码的工作过程如下所示,可以看到完成方法调用之后

在这里插入图片描述

匿名对象与实例对象的区别

实例代码

可以看到我们现实创建一个非匿名的汽车类和匿名汽车类,并作为show方法的参数传入


public static void main(String[] args) {
        Car car = new Car();
        show(car);
        show(new Car());
        /**
         * 输出结果
         * 颜色:black 轮胎数:4
         * 颜色:black 轮胎数:4
         */
    }


    public static void show(Car c) {
        c.setNum(3);
        c.setColor("black");
        c.run();
    }

图解匿名与非匿名内存运行

非匿名对象内存运行过程图解

在这里插入图片描述

匿名对象完成方法调用后即被销毁

在这里插入图片描述

使用场景

  1. 当对对象的方法只调用一次时,可以用匿名对象来完成,这样写比较简化。如果对一个对象进行多个成员调用,必须给这个对象起个名字。

  2. 可以将匿名对象作为实际参数进行传递。

封装

什么是封装

以生活为例子,某公司老板招开发人员,招得开发人员后,开发人员工作过程不用看到,老板只关注开发结果,而老板只看到开发结果这一现象即封装。

什么时private修饰

private :私有,权限修饰符:用于修饰类中的成员(成员变量,成员函数)。私有只在本类中有效。

代码示例

如下所示,setAge就是对age赋值的封装,隐藏对年龄操作的细节,用户只需通过这个方法完成自己需要的赋值动作即可

public class Person {
    private int age;

    public void setAge(int a) {
        if (a > 0 && a < 130) {
            age = a;
            speak();
        } else
            System.out.println("feifa age");
    }

    public int getAge() {
        return age;
    }

    private void speak() {
        System.out.println("age=" + age);
    }
}

构造函数

什么是构造函数

  1. 对象一建立就会调用与之对应的构造函数。
  2. 构造函数的作用:可以用于给对象进行初始化。

构造函数的小细节

  1. 类默认有构造函数,显示创建后默认构造类就消失。
  2. 默认构造函数权限和类权限修饰符一致,例如类权限为public,则构造方法默认也为public,除非显示修改权限。

构造代码块

构造代码块示例以及与构造方法的区别

构造代码块。

作用:给对象进行初始化。
对象一建立就运行,而且优先于构造函数执行。
和构造函数的区别:
1. 构造代码块是给所有对象进行统一初始化,在jvm完成类加载时就会运行方法,也就是说调用静态方法的情况下,构造代码块也会被执行
2. 而构造函数是给对应的对象初始化。

构造代码块会随着对象实例的创建和运行。

public class Person {
    private String name;
    private int age;


    {
        System.out.println("person 类的构造代码块执行了");
        run();
    }
    public void run(){
        System.out.println("person run");
    }


    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;
    }

}

如下便是main函数的运行结果

public static void main(String[] args) {
        Person p=new Person();
        /**
         * person 类的构造代码块执行了
         * person run
         */
    }

this关键字

什么是this关键字

代表它所在函数所属对象的引用。简单来说,调用对象方法的对象就是this关键字多代表的对象。

this的应用

解决构造函数初始化的问题

如下代码,假如所有成员变量不加this,编译器则不会找成员变量name,导致赋值过程毫无意义。

在这里插入图片描述
输出结果

在这里插入图片描述

对此我们就可以使用this关键字即可解决问题

public class Person {
    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;
    }
}

用于构造函数之间进行互相调用

注意:this语句只能定义在构造函数的第一行。

public Person(String name, int age) {
        this(name);
        this.age = age;
    }

    public Person(String name) {
        this.name = name;
    }

static关键字

什么是static关键字

用法:是一个修饰符,用于修饰成员(成员变量,成员函数).
当成员被静态修饰后,就多了一个调用方式,除了可以被对象调用外,还可以直接被类名调用。类名.静态成员。

static特点

  1. 随着类的加载而加载。随着jvm完成类加载该变量或者方法就会被加载到方法区。
  2. 静态会随着类的消失而消失。说明它的生命周期最长。
  3. 优先于对象存在,即静态变量先存在,对象后存在。
  4. 被所有对象所共享,所以有时候我们需要考虑线程安全问题。
  5. 可以直接被类名所调用。

实例变量和类变量的区别

  1. 实例变量随着对象的创建而存放在堆内存上,而类变量即静态变量随着类加载而存放在方法区上。
  2. 实例变量随着对象实例消亡而消亡,而类变量随着类的消亡和消亡。

静态使用注意事项:

  1. 静态方法只能访问静态变量,实例对象则静态非静态都可以访问
  2. 静态方法不可使用this和super关键字,因为this和super都是对象实例的关键字,this关键字是指向对象实例,static关键字在类加载时候就能被指向,故不可使用这两个关键字。

静态有利有弊

利处

随着类加载而创建,每个对象公用一份,无需每个实例到堆区创建一份。

弊处

  1. 生命周期长
  2. 使用不当可能造成线程安全问题。
  3. 访问有局限性,静态方法只能访问静态相关变量或者方法。

错误代码示范

在这里插入图片描述

图解对象如何调用static变量

我们首先编写这样一段代码

public class Person {
    private String name;
    private int age;

    public static int staticVar=4;
    
   

}

然后我们的main方法进行这样的调用,在jvm内存是如何执行的呢?

 public static void main(String[] args) {
       Person person=new Person();
       person.staticVar=5;
               
    }
  1. main方法入栈
  2. 堆区创建person对象实例,p指向实例
  3. p实例通过堆区对象操作方法的静态变量,修改值为5

在这里插入图片描述

main函数

主函数

主函数是一个特殊的函数,程序执行的入口,可以被jvm执行。

主函数的定义格式以及关键字含义

public:代表着该函数访问权限是最大的。
static:代表主函数随着类的加载就已经存在了。
void:主函数没有具体的返回值。
main:不是关键字,但是是一个特殊的单词,可以被jvm识别。
(String[] args):函数的参数,参数类型是一个数组,该数组中的元素是字符串。字符串类型的数组。

主函数是固定格式的

只有符合上述的固定格式,jvm才能识别。

jvm在调用主函数时,传入的是new String[0];即可长度为0的String

如何使用args

class MainDemo 
{
	public static void main(String[] args)//new String[]
	{
		String[] arr = {"hah","hhe","heihei","xixi","hiahia"};

		MainTest.main(arr);
	}
}


class MainTest
{
	public static void main(String[] args)
	{
		for(int x=0; x<args.length; x++)
			System.out.println(args[x]);
	}
}

静态代码块

格式

static
{
	静态代码块中的执行语句。
}

我们在person类中编写一个静态代码块,然后调用其他静态方法,可以发现静态代码块会随着类的加载而完成执行,并且只执行一次

public class Person {
    

    static {
        System.out.println("静态代码块,随着方法执行而执行.....");
    }

    public static void func(){
        System.out.println("静态方法执行了");
    }
    
}

main方法调用示例

 public static void main(String[] args) {
       Person.func();
       Person.func();
        /**
         * 输出结果
         * 静态代码块,随着方法执行而执行.....
         * 静态方法执行了
         * 静态方法执行了
         */

    }

设计优化

单例模式

简介

对于重量级对象的创建可能会导致以下问题:

  1. 创建对象开销大
  2. GC压力大,可能导致系统卡顿

饿汉式

代码如下所示,可以看到对象随着类的加载就会立刻完成创建,这就导致假如我们使用这个类的某些不需要单例的方法也会完成对象的创建。
例如我们就像调用以下Singleton 的sayHello这个静态方法就会导致单例实例被创建,所以如果非必要我们不建议采用这种非延迟加载的单例模式

public class Singleton {
    private Singleton() {
        System.out.println("创建单例");
    }

    public static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    public static void sayHello(){
        System.out.println("hello");
    }

    
}

测试代码,可以看到调用静态方法单例就被被创建了

public static void main(String[] args) {

        Singleton.sayHello();
        /**
         * 输出:
         * 创建单例
         * hello
         */

    }

原理也很简单,静态变量和方法都在方法区,随着类被加载这些变量或者方法都会被加载。

在这里插入图片描述

懒汉式(线程不安全)

懒汉式即实现延迟加载的有效手段,代码如下所示

/**
 * 延迟加载的单例类 避免jvm加载时创建对象
 */
public class LazySingleton {
    private LazySingleton() {
        System.out.println("懒加载单例类");
    }

    private static LazySingleton instance = null;

    
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

    public static void sayHello(){
        System.out.println("hello");
    }

   
}

调用示例如下所示,可以看到静态方法调用后并没有创建实例,只有调用获取对象时才会得到对象实例

public static void main(String[] args) {
        LazySingleton.sayHello();
        LazySingleton.getInstance();
        /**
         * 输出结果
         * hello
         * 懒加载单例类
         */
    }

懒汉式的工作原理如下图所示,可以看到只有调用getInstance后,才会在堆内存中开辟一块内存空间创建对象

在这里插入图片描述

实际上,当前的懒汉式存在线程安全问题,如上内存图解所示,可能会有两个线程走到==null的判断中进而出现创建多个单例对象的情况。我们使用JUC的倒计时门闩调用获取单例的情况,可以看到对象被创建了多次。

 /**
     * 不加synchronized的懒加载 加上则没有下面这样输出结果
     */
    @Test
    public void threadTest0(){
        ExecutorService threadPool = Executors.newFixedThreadPool(1000);
        CountDownLatch countDownLatch=new CountDownLatch(1);
        for (int i = 0; i < 10000; i++) {
            threadPool.submit(()->{
                System.out.println(LazySingleton.getInstance());
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }


        countDownLatch.countDown();
        /**
         * 懒加载单例类
         * 懒加载单例类
         * 懒加载单例类
         * 懒加载单例类
         * com.optimize.design.LazySingleton@12423874
         * com.optimize.design.LazySingleton@350c55ec
         * com.optimize.design.LazySingleton@350c55ec
         * com.optimize.design.LazySingleton@350c55ec
         * 懒加载单例类
         * com.optimize.design.LazySingleton@350c55ec
         * com.optimize.design.LazySingleton@5897fc07
         * 懒加载单例类
         * com.optimize.design.LazySingleton@5897fc07
         * 懒加载单例类
         * com.optimize.design.LazySingleton@39d8305
         * com.optimize.design.LazySingleton@1a0eae7f
         * com.optimize.design.LazySingleton@1a0eae7f
         * 懒加载单例类
         * com.optimize.design.LazySingleton@1a0eae7f
         * 懒加载单例类
         */
    }

懒汉式(线程安全)

要想实现线程安全,我们只需要通过下面这种方式上锁即可保线程安全,但是缺点也很明显,在高并发情况下,获取对象的实践会随着增加

 /**
     * 增加 synchronized确保线程安全
     * @return
     */
    public synchronized static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

测试用例如下,可以看到饿汉式和线程安全懒汉式时间的差距

@Test
    public void test(){
        long start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            Singleton.getInstance();
        }
        long end=System.currentTimeMillis();
        System.out.println(end-start);


         start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            LazySingleton.getInstance();
        }
         end=System.currentTimeMillis();
        System.out.println(end-start);





        /**
         * 输出结果
         *
         * 创建单例
         * 3
         * 懒加载单例类
         * 20
         */
    }

内部类模式

上文提到的懒汉式的性能问题,所以我们可以使用内部类模式解决该问题,代码如下所示,可以看到我们在单例类的内部增加一个静态内部类,该类被加载时静态内部类并不会被加载,只有调用getInstance才会创建单例对象,并且该对象的创建是随着类的加载就完成创建,故这是一种线程友好的单例模式

/**
 * 线程安全的单例 但还是会被反射攻破
 */
public class StaticSingleton {
    private StaticSingleton() {
        System.out.println("静态内部类延迟加载");
    }

    private static class SingletonHolder {
        private static StaticSingleton instance = new StaticSingleton();
    }

    public static StaticSingleton getInstance(){
        return SingletonHolder.instance;
    }

    public static void sayHello(){
        System.out.println("hello");
    }

    
}

测试代码和输出结果

public static void main(String[] args) {
        StaticSingleton.sayHello();
        StaticSingleton.getInstance();
        /**
         * 输出结果
         * 
         * hello
         * 静态内部类延迟加载
         */
    }

内部类单例模式工作过程

在这里插入图片描述

实际上这种模式也有缺点,就是会被发射攻破,后续我们会介绍对应的解决方案

双重锁校验(线程安全)

双重锁校验的单例模式如下所示,可以看到双重锁校验的编码方式和简单,第一次判断避免没必要的执行,第二次判断避免第一次判定为空走到创建对象代码块的线程,从而避免线程安全问题

public class DoubleCheckLockSingleton {

    private static DoubleCheckLockSingleton instance = null;

    private DoubleCheckLockSingleton() {
        System.out.println("双重锁单例对象被创建");
    }

    public static DoubleCheckLockSingleton getInstance() {
        if (instance != null) {
            return instance;
        }

        synchronized (DoubleCheckLockSingleton.class) {
            //这一重校验是为了避免上面判空后进入休眠走到这个代码块的线程
            if (null == instance) {
                instance = new DoubleCheckLockSingleton();
                return instance;
            }
        }
        return instance;
    }


}

性能上我们可以看到双重锁校验的性能要好于静态内部类的方式

 @Test
    public void test(){
        long start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            Singleton.getInstance();
        }
        long end=System.currentTimeMillis();
        System.out.println(end-start);


         start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            LazySingleton.getInstance();
        }
         end=System.currentTimeMillis();
        System.out.println(end-start);


        start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            StaticSingleton.getInstance();
        }
        end=System.currentTimeMillis();
        System.out.println(end-start);


        start=System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            DoubleCheckLockSingleton.getInstance();
        }
        end=System.currentTimeMillis();
        System.out.println(end-start);


        /**
         * 创建单例
         * 6
         * 懒加载单例类
         * 20
         * 静态内部类延迟加载
         * 4
         * 双重锁单例对象被创建
         * 3
         */
    }

枚举单例模式(线程安全)

/**
 * 使用枚举保证类单例
 */
public enum Elvis {
    INSTANCE;
    private String name="elvis";

    public String getName() {
        return name;
    }

    public static Elvis getInstance(){
        return INSTANCE;
    }


    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }


}

可以看到这种方式不会被反射攻破

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Class<Elvis> elvisClass = Elvis.class;
        Elvis elvis1 = elvisClass.newInstance();
        System.out.println(elvis1.getName()); 

    }

输出结果

在这里插入图片描述

相关面试题

下面这段代码。new StaticCode(4)的输出结果?

public class StaticCode {
    int num = 9;

    StaticCode() {
        System.out.println("b");
    }

    static {
        System.out.println("a");
    }

    {
        System.out.println("c" + this.num);
    }

    StaticCode(int x) {
        System.out.println("d");
    }

    public static void show() {
        System.out.println("show run");
    }
}

答案:a c9 d

创建类,加载顺序为:

  1. 加载静态代码块
  2. 加载构造代码块
  3. 加载构造方法

参考文献

Java基础常见面试题总结(中)

Effective Java中文版(第3版)

Java系统性能优化实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/243178.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

6、生产者压缩算法面面观

生产者压缩算法面面观 1、怎么压缩&#xff1f;2、何时压缩&#xff1f;2.1、生产者端2.2、Broker 端 3、何时解压缩&#xff1f;4、各种压缩算法对比 压缩的思想&#xff0c;实际就是用时间去换空间的经典 trade-off 思想&#xff0c;在 Kafka 中&#xff0c;就是用 CPU 时间去…

Linux | 多线程

前言 本文主要介绍多线程基础知识&#xff0c;以及使用多线程技术进行并发编程&#xff1b;最后会介绍生产者消费者模型&#xff1b; 一、线程基本认识 1、什么是线程 如果你是科班出生&#xff0c;你肯定听过线程相关概念&#xff1b;但是你可能没有真正搞懂什么是线程&#…

十八)Stable Diffusion使用教程:艺术二维码案例

今天说说怎么样使用SD生成艺术二维码。 我们直接上图。 方式有三种,分别如下: 1)方式一:直接 contronet 的tile模型进行控制 使用QRBTF Classic生成你的二维码。 首先输入网址,选择喜欢的二维码样式(推荐第一种就行): 然后选择相应参数,这里推荐最大的容错率,定…

Linux 安装图形界面 “startx”

———————————————— 报错&#xff0c;如下&#xff1a; bash :startx command not found ———————————————— 解决方法&#xff1a; 1.先安装 — X Windows System&#xff0c;输入以下命令&#xff1a; yum groupinstall “X Window System”…

第一个“hello Android”程序

1、首先安装Android studio&#xff08;跳过&#xff09; Android Studio是由Google推出的官方集成开发环境&#xff08;IDE&#xff09;&#xff0c;专门用于Android应用程序的开发。它是基于JetBrains的IntelliJ IDEA IDE构建的&#xff0c;提供了丰富的功能和工具&#xff0…

2002-2023年各省环境规制力度数据(ZF报告词频环境规制关键词词频统计)

2002-2023年各省环境规制力度数据&#xff08;ZF报告词频环境规制关键词词频统计&#xff09; 1、时间&#xff1a;2001-2022年 2、指标&#xff1a;文本总长度、仅中英文-文本总长度、文本总词频-全模式、文本总词频-精确模式、环境规制力度词频和、环境保护、环保、污染、能…

Linux常用命令(二)

目录 Linux常用命令(二)1、grep命令2、df命令3、hostname命令4、ps命令5、top命令6、echo命令7、cal命令8、firewall-cmd命令9、du命令10、netstat命令 Linux常用命令(二) 1、grep命令 功能说明&#xff1a;查找文件里符合条件的字符串。 举 例&#xff1a;ps aux | grep yum…

高通平台开发系列讲解(SIM卡篇)SIM卡基础概念

文章目录 一、SIM卡基本定义二、卡的类型三、SIM卡的作用三、SIM卡基本硬件结构四、SIM卡的内部物理单元五、卡文件系统沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇文章将介绍SIM的相关组件。 一、SIM卡基本定义 功能和作用:SIM卡的主要功能是存储用户的身份信…

【Hadoop】Hadoop基础架构的变化

1.x版本架构2.x版本架构3.x版本架构参考 1.x版本架构 NameNode&#xff1a;&#xff0c;负责文件系统的名字空间(Namespace)管理以及客户端对文 件的访问。NameNode负责文件元数据的管理和操作。是单节点。 Secondary NameNode&#xff1a;它的职责是合并NameNode的edit logs到…

什么是自我力量?如何提高自我力量?

自我力量 &#xff0c;是承受力和容纳力的评估指标&#xff0c;可以理解为不逃避&#xff0c;承受情感、冲动和幻想的能力&#xff0c;提高学习和工作效率。在企业人才测评中&#xff0c;ES用于评估工作能力&#xff0c;在校学生则可用于评估学习效率。 自我力量 &#xff0c;…

【什么是POI,为什么它会导致内存溢出?】

什么是POI&#xff0c;为什么它会导致内存溢出 什么是POIExcel并没看到的那么小POI的溢出原理 拓展知识几种Workbook格式 什么是POI Apache POl&#xff0c;是一个非常流行的文档处理工具&#xff0c;通常大家会选择用它来处理Excel文件。但是在实际使用的时候经常会遇到内存溢…

关键点检测☞png格式换bmp,且labelme标注的json中imagePath同步修改格式

import os import cv2 import jsondef bmp2jpg(in_img_path, out_dir_name): # .png -> .bmp# img = cv2.imread(in_img_path) # 彩色图片,位深24img =</

【虹科分享】基于Redis Enterprise,LangChain,OpenAI 构建一个电子商务聊天机器人

如何构建你自己的商务聊天机器人&#xff1f;注意哦&#xff0c;是你自己的聊天机器人。一起来看看Redis Enterprise的向量检索是怎么帮你实现这个愿望的吧。 鉴于最近人工智能支持的API和网络开发工具的激增&#xff0c;似乎每个人都在将聊天机器人集成到他们的应用程序中。 …

使用入耳耳机对耳朵有损害吗?入耳耳机和骨传导耳机哪款更值得入手?

由于入耳式耳机的传声原理&#xff0c;长时间使用是会对耳朵造成损害的&#xff0c;骨传导耳机相比与入耳耳机&#xff0c;不用入耳佩戴&#xff0c;还能在一定程度上保护听力&#xff0c;所以骨传导耳机更值得入手。 一、入耳耳机和骨传导耳机有什么不同 人的听觉系统分为搜…

c 语言 堆的解析(自我理解)!!!堆排序,建堆

目录 1.堆是什么&#xff1f; 2.堆的实现和接口。&#xff08;小堆&#xff09; 1.头文件 2.初始化 3.摧毁 4.向上调整&#xff08;重点&#xff09; 5.向下调整&#xff08;重点&#xff09; 6.插入&#xff08;重点&#xff09; 7.删除&#xff08;重点&#xff09; …

【Linux】:线程(三)同步和消费者模型

线程的同步 一.条件变量二.生产者和消费者模型1.概念和特点2.实现基于阻塞队列的生产者消费者模型 同步&#xff1a;在保证数据安全的前提下&#xff0c;让线程能够按照某种特定的顺序访问临界资源&#xff0c;从而有效避免饥饿问题&#xff0c;叫做同步。 竞态条件&#xff1a…

【map】【动态规划】LeetCode2713:矩阵中严格递增的单元格数

本文涉及的基础知识点 二分查找算法合集 题目 给你一个下标从 1 开始、大小为 m x n 的整数矩阵 mat&#xff0c;你可以选择任一单元格作为 起始单元格 。 从起始单元格出发&#xff0c;你可以移动到 同一行或同一列 中的任何其他单元格&#xff0c;但前提是目标单元格的值 …

群晖(Synology)更换硬盘时间和精神双重折磨的教训

话说玩磁盘阵列的最后结果就是时间上负担不起&#xff0c;并且还被嫌弃。 在磁盘都到位后下一步就是要选择冗余类型了&#xff0c;对大部分人来说使用群晖自己提供的就好了&#xff0c;通常是 SHR。 什么是 SHR Synology Hybrid RAID&#xff08;SHR&#xff09;是 Synology…

为什么要使用国际语音群呼系统?

1.降本增效 通过批量导入客户的电话号码&#xff0c;由系统自动完成批量呼叫&#xff0c;企业可以节省人工拨号的费用&#xff0c;高效助力企业业务增长&#xff1b; 2.降低流失 通过批量群呼&#xff0c;企业可以724小时高并发无故障运行&#xff0c;智能锁定意向客户&…

【c语言】【visual studio】动态内存管理,malloc,calloc,realloc详解。

引言&#xff1a;随着大一期末的到来&#xff0c;想必许多学生都学到内存的动态管理这一部分了&#xff0c;看望这篇博客后&#xff0c;希望能解除你心中对这一章节的疑惑。 (・∀・(・∀・(・∀・*) 1.malloc详解 malloc的头文件是#include <sdtlib.h>,malloc - C Ref…
最新文章