Java面向对象实践小结(含面试题)

news2024/11/16 7:00:33

继承

作用

  1. 提高了代码的复用性。
  2. 让类与类之间产生了关系。有了这个关系,才有了多态的特性。

代码示范

父类代码

public class Parent {
    public void say() {
        System.out.println("父类的say方法");
    }
}

子类代码,继承父类,也就拥有了say方法

public class Son extends Parent {

}

测试代码

public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.say();
        /**
         * 输出结果
         * 父类的say方法
         */
    }
}

注意事项

  1. 不要为了某些功能而继承,继承之间应该是is a关系。
  2. Java只支持单继承
  3. Java支持多层级继承,例如3继承2,2继承1,那么3就拥有1和2的方法。

this和super关键字

简介

this代表调用该方法的引用,而super则代表父类对象的引用。当我们在类内部要使用自己的方法时可以使用this,要调用父类方法时,可以使用super。

this和super使用实例

父类的方法

public class Parent {
    public void say() {
        System.out.println("父类的say方法");
    }
}

子类方法

public class Son extends Parent {

    public void say2(){
        super.say();
        System.out.println("父类say完子类say");
    }
}

测试输出结果

public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.say2();
        /**
         * 输出结果
         * 父类的say方法
         * 父类say完子类say
         */
    }
}

继承工作原理解析

代码示例

可以看到父类的代码如下所示,设置一个num为3

public class Parent {
    private int num=3;

    public int getNum() {
        return num;
    }

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

子类继承父类,num设置为4

public class Son extends Parent {

    private int num = 4;

    public void show() {
        System.out.println(this.num);
    }
}

输出结果,可以看到输出的是子类的值,当然如果show中用的是super.getNum(),输出结果就为3,那么jvm是如何工作的呢?

public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.show();
        /**
         * 输出结果
         * 4
         */
    }
}

图解子父类初始化过程

  1. 方法区的非静态区加载父类和子类的信息
  2. 实例化son类时,堆区开辟一个空间
  3. 若son无num,super和this指向的都是父类的num,反之只有thi指向自己的num

在这里插入图片描述

重写

概述

子类编写一个方法参数和名字都和父类一样时,就会执行子类重写的代码,并且我们需要使用@Override关键字注明这个方法是重写的方法。

代码示例

父类的代码示例

public class Parent {
    public List getList() {
        return Collections.emptyList();
    }
}

子类的代码示例

public class Son extends Parent {

    @Override
    public List getList() {
        return super.getList();
    }
}

注意事项

  1. 子类重写父类方法是必须遵循两小一大原则。

     		1. 访问权限大于父类
     		2. 抛出异常小于父类
     		3. 返回值小于父类
    

代码如下所示,可以看到子类抛出的异常以及返回值都小于父类

父类

public class Parent {
    public List getList() throws Exception {
        return Collections.emptyList();
    }
}

子类

public class Son extends Parent {

    @Override
    public ArrayList getList()throws IllegalAccessException {
        return new ArrayList();
    }
}

  1. 重写时希望保留父类的逻辑可以在代码首行用代码覆盖一下。
public class Parent {
    public void say(){
        System.out.println("父类的say方法");
    }


}

如下所示子类基于父类的方法实现了自己的特定逻辑

public class Son extends Parent {

    @Override
    public void say() {
        super.say();
        System.out.println("子类的say方法");
    }
}

子父类中的构造函数

  1. 子类对象进行初始化时,会隐式的调用父类的无参构造方法。
  2. 如果你要显示调用父类构造方法一定要在第一行声明,否则你做的所有操作都有可能会被父类构造方法覆盖。

代码示例如下所示

父类的代码

public class Parent {


    public Parent() {
        System.out.println("父类的构造方法");
    }

    public void say(){
        System.out.println("父类的say方法");
    }


}

子类的代码

public class Son extends Parent {

    @Override
    public void say() {
        super.say();
        System.out.println("子类的say方法");
    }
}

输出结果如下所示,可以看到父类构造函数的方法的输出结果输出了

public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.say();
        /**
         * 输出结果
         * 父类的构造方法
         * 父类的say方法
         * 子类的say方法
         */
    }
}

final关键字

关键字简介

  1. final关键字可以修饰类、方法、变量
  2. 修饰类,则这个类不可被继承
  3. 修饰方法则这个方法不可便重写
  4. 修饰变量,若为引用类型则该引用指向地址不可被修改,但是引用可以被修改。若基础类型则值不可修改。而且这个关键字可以修饰成员变量和局部变量

代码示例

public final class Parent {

    private final int a = 3;

    public Parent() {
        final int c=2;
        System.out.println("父类的构造方法");
    }

    public final void say() {
        System.out.println("父类的say方法");
    }


}

抽象类

使用场景

当多个类中出现相同功能,但是功能主体不同,这是可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体。

抽象类特点

  1. 抽象方法一定在抽象类中。
  2. 抽象方法和抽象类都必须被abstract关键字修饰。
  3. 抽象类不可以用new创建对象。因为调用抽象方法没意义。
  4. 抽象类中的抽象方法要被使用,必须由子类继承并实现所有的抽象方法后,才能建立子类对象调用。如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。

抽象类和一般类没有太大的不同。

该如何描述事物,就如何描述事物,只不过,该事物出现了一些暂时不知道如何实现或者实现方式不一定。
这些不确定的部分,也是该事物的功能,需要明确出现。但是无法定义主体。这时,通过抽象方法来表示最为合适。

抽象类常见问题

abstract 关键字,和哪些关键字不能共存?

  1. final:被final修饰的类不能有子类。而被abstract修饰的类一定是一个父类。
  2. private: 抽象类中的私有的抽象方法,不被子类所知,就无法被复写。而抽象方法出现的就是需要被复写。
  3. static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。可是抽象方法运行没意义。

抽象类常见使用场景——模板方法模式

抽象类常常作为模板方法的实施方案,以下便是笔者之前写过关于模板方法的文章,感兴趣的可以看看

设计模式-模板方法

接口

格式特点:

  1. 接口中常见定义:常量,抽象方法。
  2. 接口中的成员都有固定修饰符。
    常量:public static final
    方法:public abstract
    记住:接口中的成员都是public的。

接口和抽象类有什么共同点和区别?

共同点

  1. 接口和抽象类都可以包含抽象方法和默认方法
  2. 都不能被实例化

不同点

  1. 接口的变量必须是public static final修饰,抽象类可以默认不赋初值
  2. 类可以继承多接口,抽象类只能继承一个
  3. 接口常用于定义行为,继承该接口的类就会拥有某些行为,而抽象类则是抽取共性提供抽象方法给子类实现

多态概述

什么是多态

多态即事物可以有多种存在形态,如:猫可以是猫类也可以是动物类

代码示例

Cat cat=new Cat();
Animal animal=new Cat();

多态的体现

父类引用指向子类对象,通俗来说就是父类引用可以指向自己的对象

public void static main(String[] args){
function(new Cat());//会输出cat的eat方法而不是Animal的
function(new Dog());//会输出dog的eat方法而不是Animal的
}


public static void function(Animal a)//Animal a = new Cat();
	{
		a.eat();
	}

多态的好处

提高了程序的扩展性,使得同一个函数得以复用,方便后续开发的扩展。

使用多态的前提

  1. 必须是类与类之间有继承(extends)或者实现关系(写一个接口让另一个类implement)
  2. 存在对父类的覆盖

多态的弊端

只能使用父类引用访问父类成员,即cat继承Animal类并且覆盖Animal的eat或者相关方法。

多态的转型

Animal a=new Cat();//父类指向子类 向下转型

if(a instanceof Cat )//判断是否该animal类是否可以转为cat
Cat c=(Cat)a;//强转为cat 向下转型

多态的常见面试题

成员函数在多态调用输出结果是什么?

答:编译看左边,运行看右边

在多态中成员函数的特点:
在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法。

父类

public class Parent {
    public void func() {
        System.out.println("Parent");
    }
}

子类

public class Son extends Parent {

    @Override
    public void func() {
        System.out.println("Son");
    }
}

输出结果可以看到,编译看左边,输出结果完全看右边

public class Main {
    public static void main(String[] args) {
        Parent p = new Son();
        p.func();
        /**
         * 输出结果
         * Son
         */
    }
}

成员变量编译和结果取决于什么?

成员变量编译和结果全都取决于引用类型,即左边声明的类名,代码如下所示

父类

public class Parent {
    public  int num=1;
    public void func() {
        System.out.println("Parent");
    }
}

子类

public class Son extends Parent {
    public  int num=2;
    @Override
    public void func() {
        System.out.println("Son");
    }
}

可以看到输出解决是父类的数据

public class Main {
    public static void main(String[] args) {
        Parent p = new Son();
        System.out.println(p.num);
        /**
         * 输出结果
         * 1
         */
    }
}

静态函数编译运行结果取决于什么?

全看左边的引用类型,引用类型是什么就输出什么

class Fu
{
	
	static void method4()
	{
		System.out.println("fu method_4");
	}
}


class Zi extends Fu
{
	

	static void method4()
	{
		System.out.println("zi method_4");
	}
}


class  Test
{
	public static void main(String[] args) 
	{
		
		Fu f = new Zi();
		f.method4();//看该对象引用类型 所以输出fu method_4

		Zi z = new Zi();
		z.method4();//看该对象引用类型 所以输出zi method_4
	}
}

图解代码运行原理

如下图,静态区加载的是类信息,而不是this和spuer关键字,而且静态方法也不可以被重写所以,所以运行结果就根据左边的引用类型决定

在这里插入图片描述

object类

概述

Object:是所有对象的直接后者间接父类,传说中的上帝。
该类中定义的肯定是所有对象都具备的功能。

equals和toString

equals方法

比较的是对象的地址空间是否相同

toString方法

输出的是对象的类型+“@”+内存地址的十六进制
Object类中已经提供了对对象是否相同的比较方法。

如果自定义类中也有比较相同的功能,没有必要重新定义。
只要沿袭父类中的功能,建立自己特有比较内容即可。这就是覆盖

代码

class Demo //extends Object
{
	private int num;
	Demo(int num)
	{
		this.num = num;
	}
	
	//覆盖原有的equals
	public boolean equals(Object obj)//Object obj = new Demo();
	{

		if(!(obj instanceof Demo))
			return false;
		Demo d = (Demo)obj;

		return this.num == d.num;
	}
	
	//覆盖原有的toString
	public String toString()
	{
		return "demo:"+num;
	}


}
class Person 
{
}


class ObjectDemo 
{
	public static void main(String[] args) 
	{
		Demo d1 = new Demo(4);
		System.out.println(d1);//输出语句打印对象时,会自动调用对象的toString方法。打印对象的字符串表现形式。
		Demo d2 = new Demo(7);
		System.out.println(d2.toString());//打印的是d2.getName()+"@@"+Integer.toHexString(d2.hashCode()


	}
}

创建和销毁对象注意事项

用静态工程替代构造器

遇到多构造器参数建议使用建造者模式
优点:

 1. 保证类的不可变
 2. 实现的可变参数
 3. 增加可读性
/**
 * 遇到多构造器参数建议使用建造者模式
 * 优点:
 * 1. 保证类的不可变
 * 2. 实现的可变参数
 * 3. 增加可读性
 */
public class NutritionFacts {

    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        private int servingSize;
        private int servings;

        //        可选参数,赋值上默认值
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    @Override
    public String toString() {
        return "NutritionFacts{" +
                "servingSize=" + servingSize +
                ", servings=" + servings +
                ", calories=" + calories +
                ", fat=" + fat +
                ", sodium=" + sodium +
                ", carbohydrate=" + carbohydrate +
                '}';
    }

    public NutritionFacts(Builder builder) {
        this.servingSize = builder.servingSize;
        this.servings = builder.servings;
        this.calories = builder.calories;
        this.fat = builder.fat;
        this.sodium = builder.sodium;
        this.carbohydrate = builder.carbohydrate;
    }

    
}

对象创建示例

public static void main(String[] args) {
        NutritionFacts nutritionFacts = new NutritionFacts.Builder(240, 8).calories(300).sodium(50).build();
        System.out.println(nutritionFacts.toString());//NutritionFacts{servingSize=240, servings=8, calories=300, fat=0, sodium=50, carbohydrate=0}

    }

避免创建不必要的对象

频繁创建对象会增加GC回收压力以及系统开销,所以我们应该避免创建不必要的对象

下面这段代码,从源码我们就可以看到matches方法会创建一个Pattern,所以我们可以对正则匹配的方法进行响应重构

 // Performance can be greatly improved! (Page 22)
    static boolean isRomanNumeralSlow(String s) {
        /**
         * 底层实际会创建一个Pattern 频繁调用会创建多个Pattern对象
         *  Pattern p = Pattern.compile(regex);
         *         Matcher m = p.matcher(input);
         *         return m.matches();
         */
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
                + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }

所以我们建议基于matches底层调用方式自己手写一个match方法避免没必要的对象创建

 /**
     * 手动创建pattern避免频繁创建对象
     */
    private static final Pattern pattern = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumeralFast(String s) {
        return pattern.matcher(s).matches();
    }

消除过期的对象引用

如下所示,Stack 的elements成员变量是一个数组,当我们调用pop逻辑上是将栈顶元素弹出,实际上jvm是无法感知这种逻辑弹出的,所以我们需要手动消除这个对象引用,否则会出现OOM异常

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        //引用指向数组,我们所谓的size以及活动非活动元素对于虚拟机来说都是无感的
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object element = elements[--size];
        //消除过期引用
        elements[--size] = null;
        return element;

    }

    /**
     * Ensure space for at least one more element, roughly
     * doubling the capacity each time the array needs to grow.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }


    public static void main(String[] args) {
        Stack stack = new Stack();
        for (String arg : args)
            stack.push(arg);

        while (true)
            System.err.println(stack.pop());
    }
}

try-with-resource优先于try-finally

使用try-with-resource相较于后者更加简洁、清晰、产生的异常信息也更有价值

public class Copy {
    private static final int BUFFER_SIZE = 8 * 1024;

    // try-finally is ugly when used with more than one resource! (Page 34)
    static void copy(String src, String dst) throws IOException {
        InputStream in = new FileInputStream(src);
        try {
            OutputStream out = new FileOutputStream(dst);
            try {
                byte[] buf = new byte[BUFFER_SIZE];
                int n;
                while ((n = in.read(buf)) >= 0)
                    out.write(buf, 0, n);
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
    }


    static void copyWithTryResource(String src, String dst) throws IOException {

        try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) {

            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        }
    }

    public static void main(String[] args) throws Exception{
        copy("D:\\source.txt","dst.txt");
        copyWithTryResource("D:\\source.txt","dst2.txt");
    }
}

参考文献

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

Effective Java中文版(第3版)

Java程序性能优化

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何确认网站是否有漏洞,如何找出网站存在的漏洞,找到漏洞该如何处理

如何确认网站或者服务器是否有漏洞 判断一个网站是否是存在漏洞的方法: 1.可以借助德迅云安全漏洞扫描功能来检查漏洞。 2.打开德迅云安全首页,点击最上面导航栏中的“安全产品”。 3.滑到“漏洞扫描”,选择“产品价格”服务。 4.选择您需…

【图像分类】【深度学习】【Pytorch版本】 DenseNet模型算法详解

【图像分类】【深度学习】【Pytorch版本】 DenseNet模型算法详解 文章目录 【图像分类】【深度学习】【Pytorch版本】 DenseNet模型算法详解前言DenseNet讲解Dense Block(稠密块)Dense Layer(稠密层)Transition Layer 过渡层DenseNet模型结构 DenseNet Pytorch代码完整代码附加…

DM8/达梦 数据库管理员使用手册详解

1.1DM客户端存放位置 Windows:DM数据库安装目录中tool文件夹和bin文件夹中。 Linux:DM数据库安装目录中tool目录和bin目录中。 1.2DM数据库配置助手 1.2.1Windows创建数据库 打开数据库配置助手dbca 点击创建数据库实例 选择一般用途 浏览选择数据库…

【基于大数据的人肥胖程度预测分析与可控策略】

基于大数据的人肥胖程度预测分析与可控策略 前言数据获取与清洗数据挖掘与分类建模1. K-means聚类2. 层次聚类3. DBSCAN4. 分类建模 数据可视化模型肥胖程度预测分析与可控策略结语 前言 随着现代生活方式的改变,肥胖问题逐渐成为全球性的健康挑战。为了更好地理解…

湖科大计网:计算机网络概述

一、计算机网络的性能指标 一、速率 有时候数据量也认为是以10为底的,看怎么好算。(具体吉大考试用什么待商榷) 二、带宽 在模拟信号系统中带宽的含义,本课程中用到的地方是:香农定理和奈奎斯特定理公式的应用之中。 …

未用的引脚如何处理?--持续更新中

前言: 随着集成电路规模的越来越大,如今的大规模芯片都集成了很多功能模块,但是在实际的电路设计中我们又不可能把芯片所有的功能模块(或者说接口)全部用上,因此总会有或多或少的管脚会“用不上”,那这些未用的管脚一般…

maven上传jar包到代码仓库

一、前言 一般被引用的包开发都是要求放在nexus仓库中&#xff0c;等到有jar包服务需要引用该包的时候直接从nexus仓库中获取即可&#xff0c;实现了该引用包的公用 二、代码配置 编辑代码中的pom.xml文件配置 vi pom.xml <distributionManagement><repository>&…

网络安全行业大模型调研总结

随着人工智能技术的发展&#xff0c;安全行业大模型SecLLM&#xff08;security Large Language Model&#xff09;应运而生&#xff0c;可应用于代码漏洞挖掘、安全智能问答、多源情报整合、勒索情报挖掘、安全评估、安全事件研判等场景。 本文首先介绍汇总了安全行业的大模型…

Java、JDK、JRE、JVM

Java、JDK、JRE、JVM 一、 Java 广义上看&#xff0c;Kotlin、JRuby等运行于Java虚拟机上的编程语言以及相关的程序都属于Java体系的一员。从传统意义上看&#xff0c;Java社区规定的Java技术体系包括以下几个部分&#xff1a; Java程序设计语言各种硬件平台上的Java虚拟机实…

Linux 进程终止

引入 在写 C 语言程序的时候&#xff0c;我们必写的结构就是&#xff1a; int main() {return 0; }在学习 C 语言的时候&#xff0c;我们好像并没有讨论过这个 return 0 有什么用&#xff0c;是干什么的&#xff01;return 1 可以吗&#xff1f;return 的返回值给谁看&#x…

英伟达危机大爆发!一夜之间,四面楚歌

今年以来&#xff0c;AI大模型明争暗斗、百花齐放。 但不管各种大模型打的有多厉害&#xff0c;很多人都认为“卖铲子”的英伟达才是最大赢家。 看一下英伟达今年的股票就知道英伟达赚的是多么盆满钵满。 英伟达CEO黄仁勋在发布 H200显卡时&#xff0c;应该是今年最意气风发的…

Swagger2的使用

手写Api文档的几个痛点&#xff1a; 文档需要更新的时候&#xff0c;需要再次发送一份给前端&#xff0c;也就是文档更新交流不及时。 接口返回结果不明确 不能直接在线测试接口&#xff0c;通常需要使用工具&#xff0c;比如postman 接口文档太多&#xff0c;不好管理 Sw…

Linux:进程优先级与命令行参数

目录 1.进程优先级 1.1 基本概念 1.2 查看系统进程 1.3 修改进程优先级的命令 2.进程间切换 2.1 相关概念 2.2 Linux2.6内核进程调度队列&#xff08;了解即可&#xff09; 3.命令行参数 1.进程优先级 1.1 基本概念 cpu资源分配的先后顺序&#xff0c;就是指进程的优…

探秘机器学习核心逻辑:梯度下降的迭代过程 (图文详解)

一 需求解函数 f() 和 g()函数分别为求y值和求导数的函数。 目的&#xff1a;求该函数的最小值&#xff1a; 代码&#xff1a; import numpy as np import matplotlib.pyplot as plt f lambda x : (x - 3.5) ** 2 - 4.5 * x 10 g lambda x : 2 * (x - 3.5) - 4.5x np.l…

class064 Dijkstra算法、分层图最短路【算法】

class064 Dijkstra算法、分层图最短路【算法】 算法讲解064【必备】Dijkstra算法、分层图最短路 code1 743. 网络延迟时间 // Dijkstra算法模版&#xff08;Leetcode&#xff09; // 网络延迟时间 // 有 n 个网络节点&#xff0c;标记为 1 到 n // 给你一个列表 times&…

一个或多个筛选器或者Listeners启动失败 的问题

核心&#xff1a; 这个就是有好多情况会导致这个问题&#xff0c;像是文件找不到&#xff0c;缺少jar包等原因&#xff0c;还是要看报错的具体信息。 报错情况&#xff1a; 一个或多个listeners启动失败&#xff0c;更多详细信息查看对应的容器日志文件 由于之前的错误&#x…

Flutter视频播放器在iOS端和Android端都能实现全屏播放

Flutter开发过程中&#xff0c;对于视频播放的三方组件有很多&#xff0c;在Android端适配都挺好&#xff0c;但是在适配iPhone手机的时候&#xff0c;如果设置了UIInterfaceOrientationLandscapeLeft和UIInterfaceOrientationLandscapeRight都为false的情况下&#xff0c;无法…

基于lambda简化设计模式

前言 虽说使用设计模式可以让复杂的业务代码变得清晰且易于维护&#xff0c;但是某些情况下&#xff0c;开发可能会遇到我为了简单的业务逻辑去适配设计模式的情况&#xff0c;本文笔者就以四种常见的设计模式为例&#xff0c;演示如何基于lambda来简化设计模式的实现。 策略…

postgresql自带指令命令系列二

简介 在安装postgresql数据库的时候会需要设置一个关于postgresql数据库的PATH变量 export PATH/home/postgres/pg/bin:$PATH&#xff0c;该变量会指向postgresql安装路径下的bin目录。这个安装目录和我们在进行编译的时候./configure --prefix [指定安装目录] 中的prefix参…

TypeScript中的单件设计模式

基本概念 &#xff08;1&#xff09; 了解设计模式 设计模式通俗的讲&#xff0c;就是一种更好的编写代码方案&#xff0c;打个比喻&#xff1a;从上海到武汉&#xff0c;你可以选择做飞机&#xff0c;做轮船&#xff0c;开车&#xff0c;骑摩托车多种方式&#xff0c;把出行…