关于面试:助你加薪2021年春招面向编程看这一篇就够了让你面试无忧

3次阅读

共计 24552 个字符,预计需要花费 62 分钟才能阅读完成。

面向对象

要了解面向对象思维,咱们先要晓得什么是对象?

《Java 编程思维》中提到“万物皆为对象”的概念。它将对象视为一种奇异的变量,它除了能够存储数据之外还能够对它本身进行操作。它可能间接反映现实生活中的事物,例如人、车、小鸟等,将其示意为程序中的对象。每个对象都具备各自的状态特色(也能够称为属性)及行为特色(办法),java 就是通过对象之间行为的交互来解决问题的。

面向对象就是把形成问题的事物分解成一个个对象,建设对象不是为了实现一个步骤,而是为了形容某个事物在解决问题中的行为。

类是面向对象中的一个很重要的概念,因为类是很多个具备雷同属性和行为特色的对象所形象进去的,对象是类的一个实例。

类具备三个个性:封装、继承和多态。

三大特色

  • 封装:核心思想就是“暗藏细节”、“数据安全”,将对象不须要让外界拜访的成员变量和办法私有化,只提供合乎开发者志愿的私有办法来拜访这些数据和逻辑,保障了数据的平安和程序的稳固。所有的内容对外部不可见。
  • 继承:子类能够继承父类的属性和办法,并对其进行拓展。将其余的性能继承下来持续倒退。
  • 多态:同一种类型的对象执行同一个办法时能够体现出不同的行为特色。通过继承的高低转型、接口的回调以及办法的重写和重载能够实现多态。办法的重载自身就是一个多态性的体现。

三大思维

面向对象思维从概念上讲分为以下三种:OOA、OOD、OOP

OOA:面向对象分析(Object Oriented Analysis)

OOD:面向对象设计(Object Oriented Design)

OOP:面向对象程序(Object Oriented Programming)

类与对象

类示意一个共性的产物,是一个综合的特色,而对象,是一个共性的产物,是一个个体的特色。(相似生存中的图纸与实物的概念。)

类必须通过对象才能够应用,对象的所有操作都在类中定义。

类由属性和办法组成:

  • 属性:就相当于人的一个个的特色
  • 办法:就相当于人的一个个的行为,例如:谈话、吃饭、唱歌、睡觉

一个类要想真正的进行操作,则必须依附对象,对象的定义格局如下:

类名称 对象名称 = new 类名称() ;

如果要想拜访类中的属性或办法(办法的定义),则能够依附以下的语法模式:

拜访类中的属性:对象. 属性 ;

调用类中的办法:对象. 办法(理论参数列表) ;

  • 类必须编写在.java 文件中;
  • 一个.java 文件中,能够存在 N 个类,然而只能存在一个 public 润饰的类;
  • .java 文件的文件名必须与 public 润饰的类名齐全始终;
  • 同一个包中不能有重名的类;

匿名对象

  • 没有对象名称的对象就是匿名对象。即栈内存中没有名字,而堆内存中有对象。
  • 匿名对象只能应用一次,因为没有任何的对象援用,所以将称为垃圾,期待被 GC 回收。
  • 只应用一次的对象能够通过匿名对象的形式实现,这一点在当前的开发中将常常应用到。
public static void main(String[] args){//Math2 m=new Math2();
    //int num=m.sum(100,200);
    // 不通过创建对象名,间接实例对象调用,这就是匿名对象。因为没有对象名指向对象,所以只能调用一次,而后被 GC 回收。int num = new Math().sum(100,200);
    System.out.println(num);
}
class Math2{int sum(int x,int y){return x+y;}
}

对象内存剖析如下图所示:

创建对象的内存剖析

栈(stack)

Java 栈的区域很小 , 大略 2m 左右 , 特点是存取的速度特地快

栈存储的特点是:先进后出

存储速度快的起因:

栈内存, 通过‘栈指针’来创立空间与开释空间 !

指针向下挪动, 会创立新的内存, 向上挪动, 会开释这些内存 !

这种形式速度特地快 , 仅次于 PC 寄存器 !

然而这种挪动的形式, 必须要明确挪动的大小与范畴 ,

明确大小与范畴是为了不便指针的挪动 , 这是一个对于数据存储的限度, 存储的数据大小是固定的 , 影响了程序 的灵活性 ~

所以咱们把更大部分的数据存储到了堆内存中

堆存储的是:

根本数据类型的数据以及援用数据类型的援用!

例如:

int a =10; 
Person p = new Person(); 

10 存储在栈内存中 , 第二句代码创立的对象的援用§存在栈内存中

堆(heap)

寄存的是类的对象;

Java 是一个纯面向对象语言, 限度了对象的创立形式 :

所有类的对象都是通过 new 关键字创立

new 关键字, 是指通知 JVM , 须要明确的去创立一个新的对象 , 去开拓一块新的堆内存空间:

堆内存与栈内存不同, 长处在于咱们创建对象时 , 不用关注堆内存中须要开拓多少存储空间 , 也不须要关注内存占用

时长 !

堆内存中内存的开释是由 GC(垃圾回收器)实现的

垃圾回收器回收堆内存的规定 :

当栈内存中不存在此对象的援用时, 则视其为垃圾 , 期待垃圾回收器回收 !

例如:

Person p0 = new Person(); 
Person p1 = p0; 
Person p2 = new Person(); 

堆在逻辑上分为三局部:

新生代(Young Generation,常称为 YoungGen)

老年代(Old Generation,常称为 OldGen、TenuringGen)

永恒代(Permanent Generation,常称为 PermGen)

新生区(New/Young Generation):

新生代(Young Generation),常称为 YoungGen,位于堆空间。

新生区又分为 Eden 区和 Survior(幸存区)。

Eden:新创建的对象

Survior 0、1:通过垃圾回收,然而垃圾回收次数小于 15 次的对象。

养老区(Old Generation):

老年代常称为 OldGen,位于堆空间

Old:垃圾回收次数超过 15 次,仍然存活的对象。

永恒区(Permanent Generation):

永恒代常称为 PermGen,位于非堆空间。

永恒区是一个常驻内存区域,用于寄存 JDK 本身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,敞开 JVM 才会开释此区域所占用的空间。

public static void main(String[] args){
    String s1 = "123456";
    String s2 = "123456";
    System.out.println(s1==s2)// 后果:true----------- 第一次定义 s1 寄存在堆中的永恒区,所以第二次属于调用
}

办法区

办法区(Method Area),又称永恒代,又称非堆区(Non-Heap space)

办法区是被所有线程共享:

  1. 所有的字段和办法字节码,以及一些非凡办法如构造函数,接口代码也再此定义。
  2. 简略说,所有定义的办法的信息都保留在该区域,此区属于共享区间。
  3. 这些区域贮存的是:动态变量 + 常量 + 类信息(构造方法 / 接口定义)+ 运行时常量池。
  4. 然而,实例变量存在堆内存中,和办法区无关。

以上,只是逻辑上的定义。在 HotSpot 中,办法区仅仅只是逻辑上的独立,实际上还是蕴含在 java 堆中,也就是说,办法区在物理上属于 java 堆区中的一部分,而永恒区(Permanent Generation)就是办法区的实现。

寄存的是

  • 类信息
  • 动态的变量
  • 常量
  • 成员办法

办法区中蕴含了一个非凡的区域 (常量池)(存储的是应用 static 润饰的成员)

办法区的实现的演变

jdk1.7 之前:hotspot 虚拟机对办法区的实现为永恒代。

jdk1.8 及之后:hotspot 移除了永恒代用元空间(Metaspace)。

运行时 常量池 和 字符串常量池 的变动

jdk1.7 之前:运行时常量池(蕴含字符串常量池)寄存在办法区,此时 hotspot 虚拟机对办法区的实现为永恒代。

jdk1.7:字符串常量池 被办法区拿到了堆中;运行时常量池 剩下的货色还在办法区,也就是 hotspot 中的永恒代。

jdk1.8:hotspot 移除了永恒代,用元空间(Metaspace)取而代之。这时候,字符串常量池 还在堆中,运行时常量池 还在办法区,只不过办法区的实现从永恒代变成元空间(Metaspace)。

代码应用内存状况如下图所示:

上图形容了程序运行时内存的状况,当程序运行结束,栈内的会清空 b2、b1,这样堆内存中的 Book 对象就没有一个援用指向他,即栈内存中没有指向他的,则满足了 GC 的清理准则,GC 会主动清理掉堆内存中的 Book 对象。

上图形容了两个对象 b1、b2 的在栈和堆中内存的应用状况,当 b2=b1 时,b1 指向的地址就笼罩了 b2 的指向地址,这样原来 b2 对象在堆中的内存就没货色指向他的地址了,这就满足了 GC 的主动清理准则。

public static void main(String[] args){
    String s1 = "锄禾日当午";
    String s2 = "汗滴禾下土";
    String s3 = "窗前明月光";
    text1 = text1+text2+text3;// 先计算 text1+text2,产生地址为 0x126 的对象,接着再计算 0x126 对象 +text3,产生 0x127 对象
    System.out.println(text1);// 输入:锄禾日当午汗滴禾下土窗前明月光
}

上图形容了三个字符串拼接成一个新的字符串的内存应用状况,能够看到栈中 text1 指向的地址被扭转,然而堆中产生两个没有指向的对象垃圾,这是十分消耗内存的,所以平时应该防止字符串拼接。

PC 寄存器

PC 寄存器保留的是以后正在执行的 JVM 指令的 地址;

在 Java 程序中, 每个线程启动时, 都会创立一个 PC 寄存器;

本地办法栈

保留本地 (native) 办法的地址

外部类

在 Java 中,能够将一个类定义在另一个类外面或者一个办法外面,这样的类称为外部类。

宽泛意义上的外部类一般来说包含这四种:

1、成员外部类

2、部分外部类

3、匿名外部类

4、动态外部类

成员外部类

成员外部类是最一般的外部类,它的定义为位于另一个类的外部,形如上面的模式:

public class Demo{public static void main(String[] args){
        // 内部应用成员外部类 
        Outer outter = new Outer(100); 
        Outer.Inner inner = outter.new Inner();
        inner.say();       // 输入:200    
                           //     100
    }
}
class Outer { 
    private double x = 0; 
    public Outer(double x) {this.x = x;}
    class Inner { 
        private double x=200;
        // 外部类 
        public void say() {System.out.println(x); 
            System.out.println(Outer.this.x);
        }
    }
}

特点:成员外部类能够无条件拜访外部类的所有成员属性和成员办法(包含 private 成员和动态成员)。不过要留神的是,当成员外部类领有和外部类同名的成员变量或者办法时,会产生暗藏景象,即默认状况下拜访的是成员外部类的成员。

如果要拜访外部类的同名成员,须要以上面的模式进行拜访:

外部类.this. 成员变量

外部类.this. 成员办法

部分外部类

部分外部类是定义在一个办法或者一个作用域外面的类,它和成员外部类的区别在于部分外部类的拜访仅限于办法内或者该作用域内。

例如:

interface Person{public void say();
}
public class Demo{public static void main(String[] args){
        // 部分外部类
        class PersonImp implements Person{
            @Override
            public void say(){System.out.prinln("新编写的部分外部类的 say 办法内容");
            }
        }
        PersonImp p=new PersonImp();
        // 这里像调用 haha()办法,然而须要一个 Person 类,为此专门创立一个 class 文件类很浪费时间,所以应用部分外部类
        haha(p);
    }
    public static void haha(Person p){}}

// 窗口敞开
public static void main(String[] args){Frame f=new Frame("QQ 登陆器");
    f.setVisible(true);
    f.setSize(300,200);
    class MyWindowListener implements WindowListener{
        @Override
        public void windowClosing(WindowEvent e){System.out.println("哈哈哈");
        }
    }
    MyWindowListener l=new MyWindowListener();
    // 想要增加一个窗口敞开的事件,能够应用部分类
    f.addWindowListener(l);
}

留神: 部分外部类就像是办法外面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。部分外部类也是只能拜访 final 类型变量。

匿名外部类

匿名外部类因为没有名字,所以它的创立形式有点儿奇怪。匿名外部类创立进去只能应用一次,和匿名对象相似。创立格局如下:

new 父类结构器(参数列表)| 实现接口(){// 匿名外部类的类体局部}

interface Person{public void say();
}
public class Demo{public static void main(String[] args){
        // 匿名外部类
        Person p=new Person(){public void say(){System.out.println("锄禾日当午");
            }
        }
        haha(p);
    }
    public static void haha(Person p){}}

在这里咱们看到应用匿名外部类咱们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有 class 关键字,这是因为匿名外部类是间接应用 new 来生成一个对象的援用。当然这个援用是隐式的。

在应用匿名外部类的过程中,咱们须要留神如下几点:

1、应用匿名外部类时,咱们必须是继承一个类或者实现一个接口,然而两者不可兼得,同时也只能继承一个类或 者实现一个接口。

2、匿名外部类中是不能定义构造函数的。

3、匿名外部类中不能存在任何的动态成员变量和静态方法。

4、匿名外部类为部分外部类,所以部分外部类的所有限度同样对匿名外部类失效。

5、匿名外部类不能是形象的,它必须要实现继承的类或者实现的接口的所有形象办法。

6、只能拜访 final 型的局部变量。JDK1.8 之后变量默认为 final 类型,然而只有第二次赋值,就不再是 final 类型的了。

只能拜访 final 类型的局部变量的起因,因为部分类编译的时候是独自编译成一个文件,所以在文件中有 final 变量的备份。

动态外部类

动态外部类也是定义在另一个类外面的类,只不过在类的后面多了一个关键字 static。

动态外部类是不须要依赖于外部类对象的,这点和类的动态成员属性有点相似,并且它不能应用外部类的非 static 成员变量或者办法。

格局:

public class Demo {public static void main(String[] args) {Book.Info info = new Book.Info(); 
        info.say();}
}
class Book { 
    static class Info {public void say(){System.out.println("这是一本书");
        }
    } 
}

包装类

在 Java 中有一个设计的准则“所有皆对象”,那么这样一来 Java 中的一些根本的数据类型,就齐全不合乎于这种设计思维,因为 Java 中的八种根本数据类型并不是援用数据类型,所以 Java 中为了解决这样的问题,引入了八种根本数据类型的包装类。

以上的八种包装类,能够将根本数据类型依照类的模式进行操作。

然而,以上的八种包装类也是分为两种大的类型的:

  • Number:Integer、Short、Long、Double、Float、Byte 都是 Number 的子类示意是一个数字。
  • Object:Character、Boolean 都是 Object 的间接子类。

拆箱和装箱操作

以下以 Integer 和 Float 为例进行操作

将一个根本数据类型变为包装类,那么这样的操作称为装箱操作。

将一个包装类变为一个根本数据类型,这样的操作称为拆箱操作,

因为所有的数值型的包装类都是 Number 的子类,Number 的类中定义了如下的操作方法,以下的全副办法都是进行拆箱的操

作。

装箱操作:

在 JDK1.4 之前,如果要想装箱,间接应用各个包装类的构造方法即可,例如:

int temp = 10 ;                 // 根本数据类型 
Integer x = new Integer(temp) ; // 将根本数据类型变为包装类

在 JDK1.5,Java 新增了主动装箱和主动拆箱,而且能够间接通过包装类进行四则运算和自增自建操作。例如:Float f = 10.3f ;             // 主动装箱 
float x = f ;                 // 主动拆箱 
System.out.println(f * f) ; // 间接利用包装类实现 
System.out.println(x * x) ; // 间接利用包装类实现

字符串转换

应用包装类还有一个很优良的中央在于:能够将一个字符串变为指定的根本数据类型,此点个别在接管输出数据上应用较多。

在 Integer 类中提供了以下的操作方法:

public static int parseInt(String s);// 将 String 变为 int 型数据

在 Float 类中提供了以下的操作方法:

public static float parseFloat(String s);// 将 String 变为 Float

在 Boolean 类中提供了以下操作方法:

public static boolean parseBoolean(String s);// 将 String 变为 boolean

根本数据类型和包装类型的区别

1、包装类是对象,领有办法和字段,对象的调用都是通过援用对象的地址,根本类型不是
2、包装类型是援用的传递,根本类型是值的传递
3、申明形式不同,根本数据类型不须要 new 关键字,而包装类型须要 new 在堆内存中进行 new 来分配内存空间
4、存储地位不同,根本数据类型间接将值保留在值栈中,而包装类型是把对象放在堆中,而后通过对象的援用来调用他们
5、初始值不同,eg:int 的初始值为 0、boolean 的初始值为 false 而包装类型的初始值为 null
6、应用形式不同,根本数据类型间接赋值应用就好,而包装类型是在汇合如 coolection Map 时会应用

Integer 类型的重点

Integer a=1000,b=1000;
System.out.println(a==b);
/**
上述代码返回 false,因为
IntegerCache.low = -128
IntegerCache.high = 127
    所以下面 `Integer a = 1000,b = 1000;` 其实都是 `new Integer(1000);` 所以调配的内存地址必定不一样, 所以 `==` 比拟就成 `false` 了
    */

抽象类

抽象类必须应用 abstract class 申明

一个抽象类中能够没有形象办法。形象办法必须写在抽象类或者接口中。

格局:

abstract class 类名{// 抽象类} 

形象办法

只申明而未实现的办法称为形象办法(未实现指的是:没有“{}”办法体),形象办法必须应用 abstract 关键字申明。

格局:

// 抽象类 
abstract class 类名{public abstract void 办法名() ; // 形象办法,只申明而未实现
}

不能被实例化

在抽象类的应用中有几个准则:

  • 抽象类自身是不能间接进行实例化操作的,即:不能间接应用关键字 new 实现。不能被咱们创立,然而 jvm 虚构器能够创立。
  • 一个抽象类必须被子类所继承,被继承的子类(如果不是抽象类)则必须覆写 (重写) 抽象类中的全副形象办法。

常见问题

1、抽象类是否应用 final 申明?

不能,因为 final 属润饰的类是不能有子类的,而抽象类必须有子类才有意义,所以不能。

2、抽象类是否有构造方法?

能有构造方法,而且子类对象实例化的时候的流程与一般类的继承是一样的,都是要先调用父类中的构造方法(默认是无参的),之后再调用子类本人的构造方法。

抽象类和一般类的区别

1、抽象类必须用 public 或 protected 润饰(如果为 private 润饰,那么子类则无奈继承,也就无奈实现其形象办法)。默认缺省为 public;

2、抽象类不能够应用 new 关键字创建对象,然而在子类创建对象时,形象父类也会被 JVM 实例化;

3、如果一个子类继承抽象类,那么必须实现其所有的形象办法。如果有未实现的形象办法,那么子类也必须定义为 abstract 类;

接口

如果一个类中的全副办法都是形象办法,全副属性都是全局常量,那么此时就能够将这个类定义成一个接口。

定义格局:

interface 接口名称{ 
    全局常量 ; 
    形象办法 ; 
}

面向接口编程思维

这种思维是接口是定义(标准,束缚)与实现(名实拆散的准则)的拆散。

长处:

1、升高程序的耦合性

2、易于程序的扩大

3、有利于程序的保护

全局常量和形象办法的简写

因为接口自身都是由全局常量和形象办法组成,所以接口中的成员定义能够简写:

1、全局常量编写时,能够省略 public static final 关键字,例如:

public static final String INFO = "内容" ; 
// 简写后:String INFO = "内容" ; 

2、形象办法编写时,能够省略 public abstract 关键字,例如:

public abstract void print() ; 
// 简写后:void print() ; 

接口的实现 implements

接口能够多实现:

格局:

class 子类 implements 父接口 1, 父接口 2...{ } 
// 以上的代码称为接口的实现。那么如果一个类即要实现接口,又要继承抽象类的话,则依照以下的格局编写即可:class 子类 extends 父类 implements 父接口 1, 父接口 2...{ }

留神:如果一个接口要想应用,必须依附子类。子类(如果不是抽象类的话)要实现接口中的所有形象办法。

接口的继承 extends

继承是 java 面向对象编程技术的一块基石,因为它容许创立分等级档次的类。

继承就是子类继承父类的特色和行为,使得子类对象(实例)具备父类的实力域和办法,或子类从父类继承办法,使得子类具备父类雷同的行为。

继承的限度:Java 中只有单继承,多重继承,没有多继承(即一个子类只能有一个父类)。多重继承艰深来讲就是爷爷、爸爸、孙子。

<mark style=”box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;”> 接口因为都是形象局部,不存在具体的实现,所以容许多继承 </mark>, 例如:

interface C extends A,B{ }

student 类实例化时先实例化 person,默认调用的 person 的无参构造方法

public class Demo{public static void main(String[] args){Student student = new Student();
        student.say();}
}
class Person{
    private String name;
    private int age;
    public Person(){supper();// 平时 supper()能够省略,作用时默认调用父类的无参构造方法}
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    public void say(){System.out.println("姓名:"+name+",年龄:"+age);
    }
}
class Student extends Person{Student(){supper("张三",1);
    }
}
// 后果为:// 姓名:张三,年龄:1

supper

  • 通过 supper 能够拜访父类的构造方法、属性、办法。
  • 通过 supper 调用父类构造方法的代码,必须写在第一行。
  • supper 和 this 调用构造函数时都须要放在第一行,然而两者不会同时应用,因为不可能调用本身构造函数的同时还调用父类的构造方法

接口与抽象类的区别

1、抽象类要被子类继承,接口要被类实现。

2、接口只能申明形象办法,抽象类中能够申明形象办法,也能够写非形象办法。

3、接口里定义的变量只能是公共的动态的常量,抽象类中的变量是一般变量。

4、抽象类应用继承来应用,无奈多继承。接口应用实现来应用,能够多实现

5、抽象类中能够蕴含 static 办法,然而接口中不容许(静态方法不能被子类重写,因而接口中不能申明静态方法)

6、接口不能有构造方法,然而抽象类能够有

7、1.8 后接口容许呈现有办法体的办法

多态

多态:就是对象的多种表现形式,(多种体现状态)

多态的体现

对象的多态性,从概念上十分好了解,在类中有子类和父类之分,子类就是父类的一种状态,对象多态性就从此而来。

ps: 办法的重载 和 重写 也是多态的一种,不过是办法的多态(雷同办法名的多种状态)。

重载: 一个类中办法的多态性体现。

重写: 子父类中办法的多态性体现。

多态的应用:对象的类型转换

相似于根本数据类型的转换:

  • 向上转型:将子类实例变为父类实例 |- 格局:父类 父类对象 = 子类实例;
  • 向下转型:将父类实例变为子类实例 |- 格局:子类 子类对象 =(子类)父类实例;
public class Demo{public static void main(String[] args){Student student1=new Student();
        Nurse nurse1=new Nurse();
        // 向上转型,父类援用指向子类对象
        Person person1=student1;
        person1.say();        // 输入:我是学生
        Person person2=nurse1;
        person2.say();        // 输入:我是护士
        // 向下转型
        Student student2=(Student)person1;
        student2.say();        // 输入:我是学生
        // 向下转型须要留神的是不能把原来是护士的张三转成学生   例如:Student student3=(Student)person2;
        student3.say();        // 此处会报错     

        // 向上转型比拟高级的用法
        Student student4=new Student();
        say(student4);        // 输入:我是学生
    }
    public static void say(Person person){person.say();
    }
}
abstract class Person{public abstract void say();
}
class Student extends Person{
    @Override
    public void say(){System.out.println("我是学生");
    }
}
class Nurse extends Person{
    @Override
    public void say(){System.out.println("我是护士")
    }
}

留神:向上转型的对象,是通过父类调用子类笼罩或继承父类的办法,不是父类的办法。而且此时父类对象不能调用子类特有的办法。

Instanceof

作用:

判断某个对象是否是指定类的实例,则能够应用 instanceof 关键字

格局:

实例化对象 instanceof 类 // 此操作返回 boolean 类型的数据

Object 类

Object 类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承 Object 类。

// 例如咱们定义一个类:public class Person{ }
// 其实它被应用时 是这样的:public class Person extends Object{ }

Object 的多态

应用 Object 能够接管任意的援用数据类型

public static void main(String[] args){
    String text="123";
    say(text);
    int a=10;
    say(a);
}
public static void say(Object o){System.out.println(o)
}

toString()

  • 倡议重写 Object 中的 toString 办法。此办法的作用:返回对象的字符串示意模式;
  • Object 的 toString 办法,返回对象的内存地址;
  • System.out.println(对象名)个别输入时调用的时对象的 toString 办法;

equals()

倡议重写 Object 中的 equals(Object obj)办法,此办法的作用:批示某个其余对象是否“等于”此对象。

Object 的 equals 办法:实现了对象上最具区别的可能等价关系; 也就是说,对于任何非空援用值 x 和 y,当且仅当 x 和 y 援用同一对象

(x == y 具备值 true)时,此办法返回 true。

equals 办法重写时的五个个性:

自反性:对于任何非空的参考值 x,x.equals(x)应该返回 true。

对称性:对于任何非空援用值 x 和 y,x.equals(y)应该返回 true 当且仅当 y.equals(x)回报 true。

传递性:对于任何非空援用值 x,y 和 z,如果 x.equals(y)回报 true 个 y.equals(z)回报 true,而后 x.equals(z)应该返回 true。

一致性:对于任何非空援用值 x 和 y,屡次调用 x.equals(y)始终返回 true 或始终返回 false,前提是未修改对象上的 equals 比拟中应用的信息。

非空性:对于任何非空的参考值 x,x.equals(null)应该返回 false。

class Person{
    private String name;
    private int age;
    public boolean equals(Object o){
        // 判断内存地址是否雷同
        if(this==o){return true;}
        // 非空性
        if(o==null){return false;}
        // 判断是否是同一个类
        if(o instanceof Person){
            // 向下转型
            Person p2=(Person)o;
            // 此处调用的是 String 里的 equals()办法,和 Object 不同
            if(this.name.equals(p2.name)&&this.age==p2.age){return true;}
        }
        return false;
    }
}

equals 和 == 的区别

前者是比拟两个数是否等价,后者是比拟地址

可变参数

一个办法中定义完了参数,则在调用的时候必须传入与其一一对应的参数,然而在 JDK 1.5 之后提供了新的性能,能够依据须要主动传入任意个数的参数。

语法:

返回值类型 办法名称(数据类型…参数名称){// 参数在办法外部,以数组的模式来接管} 

public class Demo{public static void main(String[] args){System.out.println(sum(1));                // 输入:1
        System.out.println(sum(1,2));            // 输入:3
        System.out.println(sum(1,2,3));            // 输入:6
        System.out.println(sum(1,2,3,4));        // 输入:10
    }
    public static int sum(int... nums){
        int n=0;
        for(int i=0;i<nums.length;i++){n+=num[i];
        }
        return n;
    }
}

留神:可变参数只能呈现在参数列表的最初。

递归

递归,在数学与计算机科学中,是指在办法的定义中应用办法本身。也就是说,递归算法是一种间接或者间接调用本身方

法的算法。

递归流程图如下:

// 5 的阶乘
public class Demo{public static void main(String[] args){int n=fact(5);        
        System.out.println(n); // 后果:120
    }
    public static int fact(int n){if(n==1){return 1;}else{n*fact(n-1);
        }
    }
}

留神:能用循环实现的工作,尽量不要应用递归,因为太耗费内存。

异样解决

异样是在程序中导致程序中断运行的一种指令流。

例如,当初有如下的操作代码:

public class ExceptionDemo01{public static void main(String argsp[]){ 
        int i = 10 ; 
        int j = 0 ; 
        System.out.println("============= 计算开始 =============") ; 
        int temp = i / j ; // 进行除法运算 
        System.out.println("temp =" + temp) ; 
        System.out.println("============= 计算完结 =============") ; 
    } 
};
// 运行后果://============= 计算开始 ============= 
Exception in thread "main" java.lang.ArithmeticException: / by zero at ExceptionDemo01.main(ExceptionDemo01.java:6) 

以上的代码在“int temp = i / j ;”地位处产生了异样,一旦产生异样之后,异样之后的语句将不再执行了,所以当初的程序并没有正确的执行结束之后就退出了。

那么,<mark style=”box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;”> 为了保障程序出现异常之后依然能够正确的执行结束 </mark>,所以要采纳异样的解决机制。

如果要想对异样进行解决,则必须采纳规范的解决格局,解决格局语法如下:

try{// 有可能产生异样的代码段}catch(异样类型 1 对象名 1){// 异样的解决操作}catch(异样类型 2 对象名 2){// 异样的解决操作} ... 
finally{// 异样的对立进口}

public class ExceptionDemo01{public static void main(String argsp[]){ 
        int i = 10 ; 
        int j = 0 ; 
        System.out.println("============= 计算开始 =============") ;
        try{
            int temp = i / j ; // 进行除法运算 
            System.out.println("temp =" + temp) ; 
            System.out.println("============= 计算完结 =============") ; 
        }catch(ArithmeticException e){System.out.println("除数不能为零") ; 
        }
    } 
};

try+catch 的解决流程

1、一旦产生异样,则零碎会主动产生一个异样类的实例化对象。

2、那么,此时如果异样产生在 try 语句,则会主动找到匹配的 catch 语句执行,如果没有在 try 语句中,则会将异样抛出.

3、所有的 catch 依据办法的参数匹配异样类的实例化对象,<mark style=”box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;”> 如果匹配胜利,则示意由此 catch 进行解决 </mark>。

留神:应用 try…catch 捕捉异样不是简略的提醒就行了,那样意义很小,咱们应该想方法解决异样。

finally

在进行异样的解决之后,在异样的解决格局中还有一个 finally 语句,那么此语句将作为异样的对立进口,不论是否产生了异样,最终都要执行此段代码。

<mark style=”box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;”> 留神:finally 在一些状况是不会被执行的,比方电脑被关机了(办法强制中断)。</mark>

<mark style=”box-sizing: border-box; outline: 0px; background-color: rgb(248, 248, 64); color: rgb(0, 0, 0); overflow-wrap: break-word;”> 惟一会使 finally 不执行的代码:</mark>

public class Demo{public static void main(String[] args){haha();
    }
    public static void haha(){
        try{
            int a = 10;
            int b = 0;
            System.out.println(a/b);
        }catch(Exception e){System.out.println("呈现了异样");
            // 退出 JVM
            System.exit(0);
        }finally{System.out.println("锄禾日当午,汗滴禾下土");
        }
    }
}
// 后果:呈现了异样

finally 两种执行状况:

// 案例一
public class Demo1{public static void main(String[] args){Person p=haha();
        System.out.println(p.age);    // 输入:28
    }
    public static Person haha(){Person p=new Person();
        try{
            p.age=18;
            return p;
        }catch(Exception e){return null;}finally{p.age=28;}
    }
    static class Person{int age;}
}
//finally 会再 return 筹备数据返回的阶段执行,所以,无论是否 return,finally 都是执行。// 案例二
public class Demo2{public static void main(String[] args){int a = haha();
        System.out.println(a);    // 输入:10
    }
    public static int haha(){
        int a = 10;
        try{return a;}catch(Exception e){return null;}finally{a = 20;}
    }
}
// 后果和案例一不一样,是因为两者返回的数据类型不一样。// 案例一返回的是援用数据类型,在 return 的筹备数据返回的阶段,备份的是堆内存地址,所以堆内存里的 Person 对象的 age 扭转,return 备份的值都会扭转。// 案例二返回的是根本数据类型,在 return 的筹备数据返回阶段,备份的是值,即 10,所以无论栈内存中的 a 如何扭转,都不会影响 return 备份的 10。

案例一内存应用状况如下图所示:

案例二内存应用状况如下图所示:

异样体系结构

异样指的是 Exception,Exception 类,在 Java 中存在一个父类 Throwable(可能的抛出)

Throwable 存在两个子类:

  1. Error:示意的是谬误,是 JVM 收回的错误操作, 只能尽量避免,无奈用代码解决。
  2. Exception:个别示意所有程序中的谬误,所以个别在程序中将进行 try…catch 的解决。

受检异样代码会飘红,不受检异样不会。

多异样捕捉的留神点:

1、捕捉更粗的异样不能放在捕捉更细的异样之前。

2、如果为了不便,则能够将所有的异样都应用 Exception 进行捕捉。

非凡的多异样捕捉写法:

catch(异样类型 1 | 异样类型 2 对象名){// 示意此块用于解决异样类型 1 和 异样类型 2 的异样信息}
// 还有能够间接应用 Exception 类捕捉异样,这样所有的异样都能捕捉,毛病是针对性差

throws 关键字

在程序中异样的根本解决曾经把握了,然而随异样一起的还有一个称为 throws 关键字,此关键字次要在办法的申明上应用,示意办法中不解决异样,而交给调用处解决。

格局:

返回值 办法名称()throws Exception{}

如果是传参导致的异样,应该通过 throws 将异样抛出去:

public class Demo{public static void main(String[] args){shutDown("0");// 此处也是受检异样(飘红),因为 shutDown()办法把异样抛给了调用者,所以须要捕捉或者抛出异样。}
    public static void shutDown(String text) throws IOException{Runtime.getRuntime().exec(text);// 此处为受检异样(飘红),须要捕捉或者抛出异样
    }                                    // 艰深点来讲,抛出异样就是通知调用者,我这个办法有异样,你须要解决。}

throw 关键字

throw 关键字示意在程序中人为的抛出一个异样,因为从异样解决机制来看,所有的异样一旦产生之后,实际上抛出的就是一个异样类的实例化对象,那么此对象也能够由 throw 间接抛出。

代码:

throw new Exception("抛着玩的。") ; 

示例:

public class Demo{public static void main(String[] args){Person person = new Person();
        person.setAge(-1);
    }
}
class Person{
    private int age;
    public void setAge(int age){if(age<0 || age>180){RuntimeException e = new RuntimeException("年龄不合理");
            throw e;
        }else{this.age = age;}
    }
}

上述代码运行后果如下图所示:

RuntimeExcepion 与 Exception 的区别

留神察看如下办法的源码:

Integer 类:

 public static int parseInt(String text)throws NumberFormatException 

此办法抛出了异样,然而应用时却不须要进行 try…catch 捕捉解决,起因:

因为 NumberFormatException 并不是 Exception 的间接子类,而是 RuntimeException 的子类,只有是 RuntimeException 的子类,则示意程序在操作的时候能够不用应用 try…catch 进行解决,如果有异样产生,则由 JVM 进行解决。当然,也能够通过 try catch 解决。

自定义异样类

编写一个类,继承 Exception,并重写一参构造方法 即可实现自定义受检异样类型。

编写一个类,继承 RuntimeException,并重写一参构造方法 即可实现自定义运行时异样类型。

例如:

class MyException extends Exception{ // 继承 Exception,示意一个自定义异样类 
    public MyException(String msg){super(msg) ; // 调用 Exception 中有一个参数的结构 
    } 
} 
public class Demo{public static void main(String[] args){Person person = new Person();
        person.setAge(-1);
    }
}
class Person{
    private int age;
    public void setAge(int age) throws MyException{if(age<0 || age>180){MyException e = new MyException("年龄不合理");
            throw e;// 此处会飘红,因为属于受检异样,所以必须得抛出异样; 不要捕捉异样,因为本人生成异样又本人捕捉异样,很脑残
        }else{this.age = age;}
    }
}

自定义异样能够做很多事件,例如:

class MyException extends RuntimeException{public MyException(String msg){super(msg) ; // 在这里给保护人员发短信或邮件,告知程序呈现了 BUG。} 
}

try-with-resources

//jdk1.7 之前
public static void main(String[] args){
     FileReader fr = null;
    try{fr = new FileReader("d://book.txt");
        int c = fr.read();// 读取一个字节
        System.out.prinyln((char)c);
    } catch(IOException e){e.printStackTrace();
    } finally {
        try{fr.close();
        } catch (Exception e){e.printStackTrace();
        }
    }
}
//jdk1.7 时
public static void main(String[] args){try(FileReader fr = new FileReader("d://book.txt")){//try 小括号里的对象必须是实现了 AutoCloseable,这样才会主动敞开对象
        int c = fr.read();// 读取一个字节
        System.out.prinyln((char)c);
    } catch(IOException e){e.printStackTrace();
    }
}
//jdk9 进行了优化
public static void main(String[] args){FileReader fr = new FileReader("d://book.txt");
    PrintWriter pw = new PrintWriter("d://book.txt");
    try(fr;pw){//try 小括号里能够搁置多个对象,对象之间用分号分隔
        int c = fr.read();// 读取一个字节
        System.out.prinyln((char)c);
    } catch(IOException e){e.printStackTrace();
    }
}
// 自定义实现了 Closeable 的对象
public static void main(String[] args){CloseDemo d = new CloseDemo();
    try(d){} catch(Exception e){}}
static class CloseDemo implements Closeable{
    @Override
    public void close() throws IOException{Sytem.out.println("close 办法被调用了");
    }
}// 输入:close 办法被调用了

构造方法(结构器)

Person p = new Person();

在右侧 Person 前面呈现的小括号, 其实就是在调用构造方法

作用:

用于对象初始化。

执行机会:

在创建对象时, 主动调用

特点:

所有的 Java 类中都会至多存在一个构造方法

如果一个类中没有明确的编写构造方法, 则编译器会主动生成一个无参的构造方法, 构造方法中没有任何的代

码!

如果自行编写了任意一个结构器, 则编译器不会再主动生成无参的构造方法。

定义的格局 :

与一般办法基本相同, 区别在于: 办法名称必须与类名雷同, 没有返回值类型的申明;

  • 倡议自定义无参构造方法,不要对编译器造成依赖,防止谬误产生。
  • 当类中有十分量成员变量时,倡议提供两个版本的构造方法,一个是无参构造方法,一个是全属性做参数的构造方法。
  • 当类中所有成员变量都是常量或者没有成员变量时,倡议不提供任何版本的结构。

重载(Overload)

办法的重载

  • 办法名称雷同, 参数类型或参数长度不同或程序不同, 能够实现办法的重载;
  • 办法的重载与返回值无关;
  • 办法的重载 , 能够让咱们在不同的需要下, 通过传递不同的参数调用办法来实现具体的性能。
int sum(int x, int y){
    int z = x + y;
    return z;
}
double sum(double x, double y){
    double z = x + y;
    return z;
}

构造方法的重载

  • 一个类, 能够存在多个构造方法;
  • 参数列表的长度或类型不同即可实现构造方法的重载;
  • 构造方法的重载 , 能够让咱们在不同的创建对象的需要下, 调用不同的办法来实现对象的初始化;

重写(Override)

  1. 参数列表必须齐全与被重写的办法雷同;
  2. 返回类型必须齐全与被重写的返回类型雷同;
  3. 拜访权限不能比父类被重写的办法的拜访权限更低。例如父类办法为 public,子类就不能为 protected;
  4. 父类的成员办法只能被它的子类继承;
  5. 申明为 static 和 private 的办法不能被重写,然而可能被再次申明;
public class Demo{public static void main(String[] args){Student student=new Student();
        student.say();}
}
class Person{public void say(){System.out.println("锄禾日当午,汗滴禾下土。");
    }
}
class Student extends Person{public void say(){System.out.println("锄禾日当午,玻璃好上霜。要不及时擦,整不好得脏。");
    }
}
// 后果为:// 锄禾日当午,玻璃好上霜。要不及时擦,整不好得脏。

重写与重载的区别

  • 重写办法名返回值雷同参数雷同;
  • 重载办法名雷同返回值雷同参数能够不同,个数能够不同;
  • 重写产生在父子类中,重载产生在一个类中;
  • 重载与拜访权限无关;
  • 异样解决:重载与异样无关;重写异样范畴能够更小,然而不能抛出新的异样;

Java 两种外围机制

Java 虚拟机(Java Virtual Machine) JVM

JVM 能够了解成一个可运行 Java 字节码的虚构计算机系统

  • 它有一个解释器组件,能够实现 Java 字节码和计算机操作系统之间的通信
  • 对于不同的运行平台,有不同 的 JVM。
  • JVM 屏蔽了底层运行平台的差异,实现了“一次编译,随处运行”。

垃圾回收器(Garbage Collection) GC

  • 不再应用的内存空间该当进行回收 - 垃圾回收。
  • 在 C/C++ 等语言中,由程序员负责回收无用内存。
  • Java 语言打消了程序员回收无用内存空间的责任:
  • JVM 提供了一种零碎线程跟踪存储空间的分配情况。并在 JVM 的闲暇时,查看并开释那些能够被开释的存储空间。
  • 垃圾回收器在 Java 程序运行过程中主动启用,程序员无奈准确管制和干涉。

JAVA 跨平台原理

标识符

Java 对包、类、办法、参数和变量等因素命名时应用的字符序列称为标识符。

规定如下:

  • 由字母、数字、下划线(_)和美元符号($)组成。
  • 不能以数字结尾。
  • 辨别大小。
  • 长度无限度。
  • 不能是 Java 中的保留关键字。

标识符命名习惯:见名知意。

关键字

Java 中有一些赋予特定的含意,有专门用处的字符串称为关键字(keyword)。全副是小写。

this

在 Java 根底中,this 关键字是一个最重要的概念。应用 this 关键字能够实现以下的操作:

  • 调用类中的属性
  • 调用类中的办法或构造方法,留神:在一个构造方法中,调用另一个构造方法时,调用的代码必须编写在构造方法的第一行。
  • 示意以后对象
class Person{
    private String name;
    private int age;
    Person(){
        // 调用上面的构造方法,如果上面还有代码,必须写在第一行
        this("张三",12);
    }
    Person(String name,int age){
        // 调用类中的属性
        this.name=name;
        this.age=age;
    }
}

static

概述

static 示意“动态”的意思,能够用来润饰成员变量和成员办法(后续还会学习 动态代码块 和 动态外部类)。static 的次要作用在于创立独立于具体对象的域变量或者办法。

简略了解:

被 static 关键字润饰的办法或者变量不须要依赖于对象来进行拜访,只有类被加载了,就能够通过类名去进行拜访。并且不会因为对象的屡次创立 而在内存中建设多份数据。

重点:

  1. 动态成员 在类加载时加载并初始化;
  2. 无论一个类存在多少个对象 , 动态的属性, 永远在内存中只有一份(能够了解为所有对象专用);
  3. 在拜访时:动态不能拜访非动态 , 非动态能够拜访动态;
  4. 动态润饰的办法,被调用时,有可能对象还未创立;

// 示例一
class Demo{public static void main(String[] args){
        /**
        Emp e1 = new Emp("张三","北京");
        Emp e2 = new Emp("李四","北京");
        Emp e3 = new Emp("王二","北京");
        Emp e4 = new Emp("麻子","北京");
        // 假如公司迁址到天津
        e1.setRegion("天津");
        e2.setRegion("天津");
        e3.setRegion("天津");
        e4.setRegion("天津");
        */// 上述代码替换地址工作量十分大,所以能够把地址定义成动态变量
        Emp.region="北京";
        Emp e1 = new Emp("张三");
        Emp e2 = new Emp("李四");
        Emp e3 = new Emp("王二");
        Emp e4 = new Emp("麻子");
        Emp.region="天津";
    }
}
class Emp{
    private String name;
    //private String region;
    static String region;
    Emp(String name,String region){
        this.name=name;
        this.region=region;
    }
    Emp(String name){this.name=name;}
    Emp(){}
    public String getName(){return name;}
    public void setName(String name){this.name=name;}
    public String getRegion(){return region;}
    public void setRegion(String region){this.region=region;}
}

// 示例二
public class Demo {public static void main(String[] args) {Clothes clothes1 = new Clothes();
        Clothes clothes2 = new Clothes();
        Clothes clothes3 = new Clothes();}
}
class Clothes{
    static int count;
    Clothes(){
        count++;
        System.out.println("序号:"+count);
    }
}
// 输入:// 序号:1
// 序号:2
// 序号:3

final

final 用于润饰属性 (类里定义的标识符称为属性) 和变量(办法体里定义的标识符成为变量:

  • 通过 final 润饰的属性和变量都是常量,就是不能再次赋值的变量或属性;
  • final 润饰的局部变量,只能赋值一次(能够先申明后赋值);
  • final 润饰的成员属性,必须在申明时赋值;
  • 全局常量(public static final)能够在任何地位被拜访;
  • 常量的命名标准:由一个或多个单词组成,单词之间必须应用下划线隔开,所有字母大写,例如:SQL_INSERT;
// 如果常量定义时没有赋值初始值,那么能够赋值一次
final int a;
a=10;

final 用于润饰类:

final 润饰的类,不能被继承。

final 用于润饰办法:

final 润饰的办法,不能被子类重写。

封装 private

// 咱们察看如下代码:class Person{ 
    private String name ; // 示意姓名 
    private int age ;     // 示意年龄 
    void tell(){System.out.println("姓名:" + name + ";年龄:" + age) ;
    } 
};
public class Demo{public static void main(String args[]){Person per = new Person() ;
        per.name = "张三" ;
        per.age = -30 ;
        per.tell() ;} 
}; 
// 以上的操作代码并没有呈现了语法错误,然而呈现了逻辑谬误(年龄 -30 岁)在开发中,为了避免出现逻辑谬误,咱们倡议对所有属性进行封装,并为其提供 setter 及 getter 办法进行设置和获得 操作。批改代码如下:class Person{ 
    private String name ; // 示意姓名 
    private int age ;     // 示意年龄 
    void tell(){System.out.println("姓名:" + getName() + ";年龄:" + getAge()) ;
    }
    public void setName(String str){name = str ;}
    public void setAge(int a){if(a>0&&a<150) age = a ; 
    }
    public String getName(){return name ;}
    public int getAge(){return age ;} 
};
public class OODemo10{public static void main(String args[]){Person per = new Person() ;
        per.setName("张三") ; 
        per.setAge(-30) ; 
        per.tell() ;}
};

代码块

一般代码块

在执行的流程中 呈现的 代码块,咱们称其为一般代码块。

结构代码块

在类中的成员代码块,咱们称其为结构代码块,在每次对象创立时执行,执行在构造方法之前。

动态代码块

在类中应用 static 润饰的成员代码块,咱们称其为动态代码块,在类加载时执行。每次程序启动到敞开,只会 执行一次的代码块。

同步代码块

在后续多线程技术中学习。

面试题:

构造方法 与 结构代码块 以及 动态代码块的执行程序:

动态代码块 –> 结构代码块 –> 构造方法

public static void main(String[] args){// 一般代码块,就是 {} 的范畴
    { }
    Person p1 = new Person();
    Person p2 = new Person();}
class Person{
    // 结构代码块
    // 区别于构造方法,无论用户调用哪一个构造方法来创建对象,结构代码块都必然执行。{System.out.println("对象创立时执行 1");
    }
    // 动态代码块
    // 随着类的加载(第一次应用),动态代码执行。// 因为类只加载一次,所以动态代码只执行一次。static{System.out.println("动态代码块执行")
    }
    // 构造方法
    // 构造方法不肯定会执行,因为构造方法存在重载,这就取决于用户创建对象时采纳哪个重载。public Person(){System.out.println("对象创立时执行 2");
    }
}
// 输入:动态代码块执行                只执行一次
//      对象创立时执行 1
//        对象创立时执行 2
//      对象创立时执行 1
//        对象创立时执行 2

  1. 把性能类似或相干的类或接口组织在同一个包中,不便类的查找和应用。
  2. 包如同文件夹一样,不同的包中的类的名字是能够雷同的,当同时调用两个不同包中雷同类名的类时,应该加上包名加以区别。因而,包能够防止名字抵触。
  3. 包也限定了拜访权限,领有包拜访权限的类能力拜访某个包中的类。

包的应用规定

– 包中 java 文件的定义:

在.java 文件的首部,必须编写类所属哪个包,格局:

package 包名;

– 包的定义:

通常由多个单词组成,所有单词的字母小写,单词与单词之间应用. 隔开,个别命名为“com. 公司名. 我的项目名. 模块名…”。

标准由来:

因为 Java 面向对象的个性,每名 Java 开发人员都能够编写属于本人的 Java Package,为了保障每个 Java Package 命名的唯一性,在最新的 Java 编程标准中,要求开发人员在本人定义的包名前加上惟一的前缀。因为互联网上的域名称是不会反复的,所以少数开发人员采纳本人公司在互联网上的域名称作为本人程序包的惟一前缀。例如:

com.java.xxx

最初

欢送关注公众号:前程有光,支付一线大厂 Java 面试题总结 + 各知识点学习思维导 + 一份 300 页 pdf 文档的 Java 外围知识点总结!这些材料的内容都是面试时面试官必问的知识点,篇章包含了很多知识点,其中包含了有基础知识、Java 汇合、JVM、多线程并发、spring 原理、微服务、Netty 与 RPC、Kafka、日记、设计模式、Java 算法、数据库、Zookeeper、分布式缓存、数据结构等等。

正文完
 0