Java基础知识与面试题整理

2020-04-29  本文已影响0人  白泽丶丶

# Java基础知识与面试题整理(一)

@[toc]

----

----

## 垃圾回收机制

传统C/C++等编程语言需要程序员显示进行垃圾回收,显示进行垃圾回收主要有以下两个缺点:

1. 程序忘记回收无用内存,从而导致内存泄漏,降低系统性能。

2. 程序错误地回收程序核心类库的内存,从而导致系统崩溃。

JAVA程序的内存分配和回收都是由JRE在后台自动进行的。JRE会负责回收那些不在使用的内存,这种机制被称为**垃圾回收**(Garbage Collection,简称**GC**)

通常JRE会提供一条超级线程来进行检测和控制,一般都是在`CPU空闲`或`内存不足时`自动进行垃圾回收,而程序员无法精准控制垃圾回收的时间和顺序。

Java语言规范没有明确的说明JVM使用的是哪种垃圾回收算法,但任何垃圾回收算法一般要做两件事: 1.`发现无用对象`;2.`回收被无用对象占据的内存空间,使该空间可以被程序再次使用`;

**垃圾回收特点**:

  - 垃圾回收机制的工作目标是`回收无用对象的内存空间`,这些内存空间都是JVM`堆内存`里的内存空间,垃圾回收只能回收`内存资源`,对其他物理资源,如数据库连接、磁盘IO等资源则无能为力;

  - 为了更快地让垃圾回收机制回收那些不再使用的对象,可以通过将`该对象的引用置为null`,通过这种方式暗示垃圾回收机制可以回收该对象。

  - 垃圾回收发生的`不可预知性`:由于不同JVM才用了不同的垃圾回收机制和不同的垃圾回收算法,因此它有可能是`定时发生`,有可能适当`CPU空闲`时发生,也有可能是和原始垃圾回收一样,等到`内存消耗出现极限`时发生,这些和垃圾回收机制的选择和具体的设置都有关系。虽然程序员可以通过调用该对象的`finalize()`方法或`system.gc()`等方法来建议系统进行垃圾回收,但这种调用仅仅是建议,依然不能`精确`控制垃圾回收机制的执行。

  - 垃圾回收的精确性主要包括2个方面:一、垃圾回收机制能够精确`标记`活着的对象;二、垃圾回收器能够精确地定位对象之间的`引用关系`。前者是完全回收所有废弃对象的前提,否则就可能造成内存泄漏。而后者是实现`归并`和`复制`等算法的必要条件,通过这种引用关系,可以保证所有对象都能被可靠地回收,所有对象都能被重新分配,从而有效的`减少内存碎片`的产生。

  - 现在的JVM有多种不同的垃圾回收实现,每种回收机制因其算法差异,可能其表现各异,有的当垃圾回收开始时就`停止应用程序的运行`,有的当垃圾回收运行时,同时允许应用程序的`线程`运行,还有的在同一时间垃圾回收`多线程`运行。

**特点(简化版)**:

- 垃圾回收机制只负责回收`堆内存`中的对象,不会回收任何物理资源。

- 程序无法精确控制垃圾回收的运行。

- 垃圾回收机制回收任何资源之前,总会调用它的`finalize`方法,该方法可能使对象重新复活,从而导致垃圾回收机制取消回收。

> 在我们编写Java程序时,一个基本原则是:**对于不再需要的对象,不要引用它们**。

如果保持了对这些对象的引用,垃圾回收机制暂时不会回收该对象,则会造成系统可用内存越来越少,这时垃圾回收执行的频率也越高,从而导致系统的性能下降。

### 对象在内存中的状态

- **激活状态**:对象被创建后,有一个以上的引用变量引用它,这个对象就处于激活状态,程序可通过引用变量来调用该对象的属性和方法。

- **去活状态**:如果程序中某个对象没有任何引用变量引用它,这个对象就处于去活状态。这个状态下,垃圾回收机制准备回收该对象所占内存,再回收之前,系统会调用去活状态对象的finalize方法进行资源清理,如果在这个方法中重新又引用变量引用它,那么这个对象变为激活状态,否则该对象进入死亡状态。

- **死亡状态**:当对象没有任何引用变量引用,且系统已经调用过该对象的finalize方法,仍未变为激活状态,那么该对象就处于死亡状态。垃圾回收会回收处于死亡状态的对象。

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190308123209893.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

**强制垃圾回收**:

- 调用System类的gc()静态方法:`System.gc();`

- 调用Runtime对象的gc()实例方法:`Runtime.getRuntime().gc();`

这种强制只是通知系统进行垃圾回收,系统是否会进行垃圾回收不确定。

**finalize方法**

该方法定义在Object类中,原型为:`protected void finalize() throws Throwable`

任何Java类都可以覆盖finalize方法,在该方法中清理资源。

**finalize特点**:

- 永远不要主动调用某个对象的finalize方法,该方法应交给垃圾回收机制调用。

- finalize方法何时被调用,是否会调用具有`不确定性`。

- 当JVM执行去活对象方法时,可能使该对象或其他对象重新变为激活状态

- 当JVM执行finalize出现了异常,垃圾回收机制`不会报异常`,程序继续执行。

### 对象的软、弱和虚引用

Java语言对象的引用有四种:

- ` 强应用`:程序通过引用变量来操作实际对象,被强引用的对象不能被垃圾回收机制回收。

- `软引用(SoftReference)`:通过`SoftReference`类实现。如果一个对象只有软引用,当系统空间足够时,不会被系统回收,程序也可以使用该对象;当系统空间不足时,系统将会回收它。软引用通常用于对内存敏感的程序中。

- `弱引用(WeakReference)`:通过`WeakReference`类实现。弱引用比软引用的引用级别更低。如果一个对象只有弱引用,当垃圾回收运行时,那么无论系统内存是否足够,该对象都会被回收。

- `虚引用(PhantomReference)`:虚引用完全类似没有引用。虚引用主要用于跟踪对象被垃圾回收的状态。虚引用不能单独使用,必须和引用队列(ReferenceQueue)联合使用。

上面三个引用类都包含了一个get()方法,用于获取被他们所引用的对象。

举一个弱引用的例子:

```

public static void main(String[] strs) {

        String str = new String("哈哈哈");

//        String str = "哈哈哈";

        //创建引用队列

        ReferenceQueue rq = new ReferenceQueue();

        //创建一个虚引用

        PhantomReference pr = new PhantomReference(str, rq);

        //创建弱引用对象

//        WeakReference wr = new WeakReference(str);

        str = null;

        System.out.println("去除虚引用所引用的对象:" + pr.get());

//        System.out.println("弱引用被清理前:" + wr.get());

        System.gc();

        System.runFinalization();

//        System.out.println("弱引用被清理后:" + wr.get());

        System.out.println("取出引用队列中最先进入队列的引用与pr比较:" + (rq.poll() == pr));

    }

```

被注释的部分是一个弱引用对象的简单举例,未被注释的部分是虚引用 + 引用队列的举例。

**好处**:

- 使用这些引用类可以避免在程序执行期间将对象留在内存中,并且垃圾收集器可以随意的释放对象。如果希望尽可能减小程序在其生命周期所占用的内存大小时,这些引用类就很有好处。

> ***注意:要使用这些特殊的引用类,就不能保存对对象的强引用。***

当程序希望从弱引用中取出引用对象时,该对象可能已经被回收了,这时就要重新创建对象。

```

obj = wr.get();

if(obj == null){

//重新创建一个对象,再次使用虚引用引用它

wr = new WeakReference(recreateIt());//1

//取出虚引用

obj = wr.get();2

}

...操作obj对象...

//再次切断obj对象强引用

obj = null;

```

这种写法有一定问题,由于垃圾回收的不确定性,如果系统在1的时候又将弱引用对象回收了,那么在程序运行到2时,所获取的对象还是null。正确写法应该是:

```

obj = wr.get();

if(obj == null){

obj = recreatIt();

wr = new WeakReference(obj );

}

...操作obj对象...//这时用强引用直接操作obj对象

//再次切断obj对象强引用

obj = null;

```

#### 面试题:1. 哪些情况下的对象会被垃圾回收机制处理掉?

> 答:**不被程序引用的对象**。

> **引用计数法**(存在缺陷)

> **可达性分析算法**:虚拟机会将一些对象定义为GC Roots,从GC Roots出发一直沿着引用链向下寻找,如果某个对象不能通过GC Roots寻找到,那么虚拟机就认为该对象可以被回收。

![在这里插入图片描述](https://ask.qcloudimg.com/http-save/yehe-3002827/f9zecp0uiz.jpeg?imageView2/2/w/1620)

满足以下条件的对象可以看做是GC roots:

  1. 虚拟机`栈`中引用的对象。

  2. 方法区中的`静态属性`引用的对象。

  3. 方法区中的`常量`引用的对象。

  4. `本地方法栈`中JNI(Native方法)的引用的对象。

#### 面试题:2. 垃圾回收机制何时会进行垃圾回收?

> 答:由于JVM的垃圾回收机制和不同垃圾回收机制算法不同,可能是在CPU空闲时、可能定时发生、也可能是内存消耗极限时。

#### 面试题:3.简述java垃圾回收机制

>...

#### 面试题:4.java类中成员的初始化顺序(包括父类和子类的静态代码块、静态变量,普通变量和代码块以及构造方法)

> 答:.用一段程序的运行结果来说明:

> ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190307144527295.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

## 使用Javadoc生成文档注释

API文档主要用于说明类、方法、属性的功能,因此javadoc工具只处理文档源文件在类、接口、方法、属性、构造器和内部类之前的注释,忽略其他地方的文档注释。并且javadoc只处理`public`和`protected`修饰的类、方法和属性。如果希望提取privated修饰的内容,则可以在使用javadoc工具室添加`-private`选项。

javadoc常用选项:

  - `-d`<directory>: 该选项制定一个路径,用于将生成的API文档放到这个路径下。

  - `-windowtitle`<text>: 该选项指定一个字符串,用于设置API文档的浏览器`窗口标题`。

  - `doctitle`<html-code>: 该选项指定一个HTML的文本,用于指定概述页面的标题。

  - `-header`<html-code>: 该选项指定一个HTML的文本,用于指定概述页面的页眉。

举例:

> javadoc -d apidoc -windowtitle 测试 -doctitle 学习javadoc工具的测试API文档 -header 自定义类 Test*.java

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190228163946781.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

## Java关键字

| abstract | assert | boolean | break | byte | case |

--|--|--|--|--|--

| catch | char | class| continue | default | do |

| double | else | enum | extends| final | finally |

| float | for | if | implements | import | int |

| interface | instanceof | long | native | new | package|

| private | protected | public | return | short | static|

| strictfp | super | switch | synchronized | this | throw |

| throws | transient | try | void | volatile | while |

除了上面48个关键字,java还包含两个保留字:`goto`和`const`

Java还提供了3个特殊的直接量(literal):`true`、`false`和`null`

## Java数据类型分类

Java支持的类型氛围两类:`基本类型`和`引用类型`。

基本类型包括`boolean类型`和`数值类型`。

数值类型有`整数类型`和`浮点类型`。

整数型包括byte、short、int、long、char,浮点类型包括float、和double。

> ***注意:有的时候也把char型称为字符型,实际上字符型也是一种整数型。***

> ***注意:空引用(null)只能被转换成引用类型,不能被转换成基本类型。***

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190228172437905.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

### 整型:

- `byte`:一个byte型整数在内存里占8位,表数范围是-128~127(2的7次方)。

- `short`: 一个short型整数在内存里占16位,表数范围是-32768~32767(2的15次方).

- `int`: 一个int型整数在内存里占32位,表数范围是-2147483648到2147483647(2的31次方)

- `long`: 一个long型整数在内存里占64位,表数范围是-9223372036854775808~9223372036854775807。(2的63次方)

Java中整数常量有3种表示方式:10进制、8进制和16进制。

8进制整数常量以0开头。16进制的整数以0x或0X开头。

例如:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190228174033181.png)

### 字符型

Java语言使用`Unicode编码集`作为编码方式,Unicode支持世界上所有书面语言字符,因此Java支持各种语言字符。

字符常量有三种表现方式:

  - 直接通过`单个字符`来指定字符常量,例如,‘A’,‘9’,‘0’等。

  - 通过`转义字符`表示特殊字符常量,例如,‘\n’,‘\t’等。

  - 直接用`Unicode值`来表示字符常量,格式是'\uXXXX',其中XXXX表示一个16进制的整数。

| 转义字符 |说明  | Unicode表示方式 |

|--|--|--|--|

| \b | 退格符 | \u0008 |

| \n | 换行符 | \u000a |

| \r | 回车符 | \u000d |

| \t | 制表符 | \u0009 |

| \" | 双引号 | \u0022 |

| \' | 单引号 | \u0027 |

| \\ | 反斜杠 | \u005c |

char类型的值也可以直接作为`整数型`的数值来使用,但它是一个16位无符号整数,即全部是正数,表数范围是0~65535。

### 浮点型:

Java浮点类型有两种:`float`和`double`。

- `float`: 单精度浮点数,占4个字节,32位。第一位是符号位,接下来8位表示指数,接下来23位表示尾数。

- `double`: 双精度浮点数,占8个字节,64位。第一位是符号位,接下来11位表指数,接下来52位表尾数。

Java浮点数有两种表现形式:

  - 1. 十进制数形式。

  - 2. 科学计数法形式。

> ***注意:Java浮点数使用二进制的科学计数法来表示,因此可能不精确标识一个浮点数。虽然double比float更精准,但是还是可能出现这种情况。如果要精确保存一个浮点数,可以考虑使用`BigDecimal`类。[精确浮点运算传送门](https://blog.csdn.net/weixin_39397471/article/details/88050491)***

只有`浮点类型`的数值才能用科学计数法表示。例如51200是一个int类型的值,512E2则是一个浮点型的值。

Java还提供了三个特殊的浮点数值:`正无穷大`、`负无穷大`和`非数`,用于表示溢出和出错。

- `正无穷大`:正数除以0得到正无穷大。可以通过Double或Float的POSITIVE表示。

- `负无穷大`:负数除以0的到负无穷大。可以通过Double或Float的NEGATIVE_INFINITY表示。

- `非数`:0.0除以0.0或对负数开方得到一个非数。非数通过Double或Float的NaN表示。

所有正无穷大数值都是相等的,所有负无穷大数值也是相等的,而NaN不与任何数值相等,甚至不和NaN相等。

> ***注意:只有`浮点数`除以0才可以得到正无穷大或负无穷大。因为Java会自动把和浮点数运算的0当成0.0处理。如果一个整数除以0,则会抛异常。***

#### 面试题:1.int、char、long各占多少字节数

> 答:int占4个字节,char占2个字节,long占8个字节。

#### 面试题:2.Math.round(-11.5)与Math.round(11.5)的值各是多少?

> 答:-11和12

> 1、小数点后第一位=5

2、正数:Math.round(11.5)=12

3、负数:Math.round(-11.5)=-11

4、

5、小数点后第一位<5

6、正数:Math.round(11.46)=11

7、负数:Math.round(-11.46)=-11

8、

9、小数点后第一位>5

10、正数:Math.round(11.68)=12

11、负数:Math.round(-11.68)=-12

> </br>

> 总结规律:

>    1. 小数点后第一位小于5:直接舍去小数部分。

>    2. 小数点后第一位等于5:正数直接加1,负数舍去小数部分。

>    3. 小数点后第一位大于5:整数部分绝对值加1,正负不变。

### 位运算符:

Java支持的运算符有:

  - `&`: 按位与。

  - `|`:按位或。

  - `~`: 按位非。

  - `^`: 按位异或。

  - `<<`:左位移运算符。

  - `>>`:右位移运算符。

  - `>>>`:无符号右移运算符。

| 第一个运算数 | 第二个运算数 | 按位与 | 按位或 | 按位异或 |

|--|--|--|--|--|

| 0 | 0 | 0 | 0 | 0  |

| 1 | 1 | 1 | 1 |  1 |

| 1 | 0 | 0 | 1 |  1 |

| 0 | 1 | 0 | 1 | 0  |

`~`运算符运算规则:

  1. 首先原码除符号位外取反得到反码。

  2. 反码加1得到补码。

  3. 再取反得到结果。(此时符号位也取反)

例如:~-5 = 4

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190301113429273.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

补充一下十进制负数转二进制的知识点:

> 先说一下原码、反码与补码。

1. `原码`:符号位加上数字的二进制表示,int为例,第一位表示`符号 `(0正数 1负数),对于原码来说,绝对值相等的正数和负数只有符号位不同。

2. `反码`:如果一个数为正,则与补码相同。如果一个数为负,则符号位为1(`符号位不变化,其余位数取反`)

3. `补码`:如果一个数如果为正,则它的原码、反码、补码相同;一个数如果为负,去到反码然后加1(`反码加1就是补码`)。Java使用补码表示负数。

负数转二进制步骤:-3

1. 首先将负数转换为对应的原码。

`0000 0000 0000 0000 0000 0000 0000 0011`

2. 再将原码按位取反,得到反码。

`1111 1111 1111 1111 1111 1111 1111 1100 `

3. 将反码+1得到补码。

`1111 1111 1111 1111 1111 1111 1111 1101 `

这就是负数的二进制表示了。

逆转这个过程得到十进制的数。1111 1111 1111 1111 1111 1111 1111 1101

1. 先取反,得到反码:

`0000 0000 0000 0000 0000 0000 0000 0010`

2. 反码+1,得到:

`0000 0000 0000 0000 0000 0000 0000 0011`

3. 转为10进制,最后的到3。

> ***注意:如果int数值类型位数不满32位,需要在高位补0。***

Java的右移运算符有两个,>>和>>>

1. `>>`:把操作时的二进制码右移指定位数后,左边空出来的位数以`符号位`补充。

2. `>>>`:把操作时的二进制码右移指定位数后,左边空出来的位数以`0`补充。

`>>`、`>>>`和`<<`三个位移运算只适合对byte、short、char、int和long等整型进行运算,同时还有以下运算规则:

  - 对于低于int(byte、short等)总是先自动类型转换位int后再位移。

  - 对于int类型的整数`a>>b`,系统先用32对b取余后再进行运算,例如:a>>33和a>>1结果相同,a>>32和a结果一样。

  - 对于long类型,则是用64对b进行取余,因为long类型是64位的。

### 比较运算

数值型对应的包装类的实例是可以与数值型的值进行比较的,这种比较是直接取出包装类所包装的`数值`进行比较,也就是自动`拆箱`比较。

在看下面这段代码:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190301140152668.png)

同样的两个Integer实例,装箱后结果为什么不一样呢?

这跟Integer的设计有关:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190301140345581.png)

从上面的代码可以看出,系统把一个`-128~127`之间的数字自动装箱成Integer,并放入了一个名为cache的数组保存起来。当有这个范围的数值需要装箱时,直接引用到cache数组中对应的值,所以在这个范围内的数组装箱后`全部相等`。

与此类似的还有String类,String类也会对通过直接量直接赋值的String实例进行缓存。如图:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190301141317139.png)

### 循环

使用`break`结束一个循环:break用于完全结束一个循环,跳出循环体。

`break`不仅可以结束其所在的循环,还可以结束其外层循环:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190301143750885.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

break后紧跟一个标签语句,会让程序直接结束outer指定的循环。

> ***注意:break后必须是一个有效标签,且必须再break所在循环的外层循环之前定义才有意义。***

`continue·`可以用于终止本次循环。

与break类似,`continue`也可以紧跟一个标签,用于直接结束标签所标识循环的当次循环,重新开始下一次循环。

用法与注意与break一致。

### 数组

定义数组语法:

  - type[] arrayName 

  - type arrayName[];(不推荐)

type[]是一种新类型,与type类型完全不同。例如int是基本类型,int[]是引用类型。

> ***注意:定义数组时不能指定数组长度(动态初始化时可以指定长度)。***

> ***注意:foreach循环迭代数组元素时,并不能改变数组元素的值,因此不要对foreach的循环变量进行赋值。***

实际`数组元素`被存储在`堆`(heap)内存中;数组引用变量是一个`引用类型变量`,被存储在`栈`(stack)内存中。

## 面向对象

static修饰的成员不能访问没有static修饰的成员。

静态成员不能直接访问非静态成员。

static修饰的方法无论是`类本身`或是`对象`调用都会得到相同的执行结果。

### 方法的参数传递机制

一段代码执行的结果就足以说明问题了:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190306154004915.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

- 基本类型参数传递的是值,即`值传递`。就是将实际参数值得副本(复制品)传入方法内,而参数本身不会受到任何影响。

- 引用类型参数传递的是地址,即`址传递`。

其实参数传递的也是值,从底层来看,引用对象是将引用对象拷贝了一份传到方法中,此时堆内存存放的真正对象有两个引用变量,用图说明:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190306155954912.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

这两个引用变量确实是两个,但是引用的对象本身只有堆内存的唯一一个。所以现在无论哪个引用变量为对象赋值,改变的都是同一个对象。

更直观的证明一下,可以在调用完swap方法后,将dw对象引用指向null,就变为:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190306160223669.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

此时swap中的引用断了,但main方法栈中引用不受影响,这确实是两个引用对象。当main方法栈中的引用也断了时,堆内存中的对象将没有引用,就会变为一个垃圾对象,垃圾回收机制将在适当时机来回收。

### 形参长度可变的方法

JDK1.5之后,Java允许定义形参长度可变的方法,定义方式与规则如下:

  - 长度可变的形参只能处于形参列表的最后。

  - 一个方法中只能包括一个长度可变的形参。

  - 调用包含一个长度可变的形参方法时,这个长度可变的形参即可以传入多个参数,也可以传入一个数组。

```

//长度可变形参

    public static void sayList(String... strs) {

        System.out.println("传入参数是:");

        for (int i = 0; i < strs.length; i++) {

            System.out.println(strs[i]);

        }

    }

```

```

//调用:

sayList("李森","大锤","大宝");//使用多个参数 调用长度可变形参

sayList(new String[]{"李森","大锤","大宝"});//使用数组 调用长度可变形参

```

运行结果是一样的:

![在这里插入图片描述](https://img-blog.csdnimg.cn/2019030616184821.png)

### 成员变量的初始化和内存中的运行机制

一个类在使用之前要经过类加载、类验证、类准备、类解析类初始化等几个阶段。

以Person类为例,系统会在第一次使用Person类时加载这个类,并初始化这个类。

在类的准备阶段,系统将会为该类的`类属性`分配内存空间,并指定默认初始值。如图:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190307104556689.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

在系统创建一个Person对象时,实例属性是在创建实例时分配内存空间、并指定初始值的。

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190307104817195.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zOTM5NzQ3MQ==,size_16,color_FFFFFF,t_70)

### 局部变量的初始化和内存中的运行机制

局部变量定义后,必须经过`显示初始化`后才能使用,系统不会为局部变量执行初始化。直到程序为这个变量赋值时,系统才会为局部变量分配内存。

与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在方法的`栈内存`中。

- 如果局部变量是`基本类型`变量,则直接把这个变量的值保存在该变量对应的内存中;

- 如果局部变量是`引用类型`变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对象或数组。

### 使用访问控制符

Java的访问控制级别由小到大依次如图:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190307111000163.png)

- `private`:类访问权限。只能在该类的内部访问。

- `default`:包访问权限。可以被相同包下其他类访问。

- `protected`:子类访问权限。即可以被同一包其他类访问,也可以被不同包下子类访问。

- `public`:公共访问权限。可以被所有类访问。

|  | private | default | protected | public |

|--|--|--|--|--|

| 同一类中 | √ | √ | √ | √ |

| 同一包中 |  | √ | √ | √ |

| 子类中 |  |  | √ | √ |

| 全局范围内 |  |  |  | √ |

> ***注意:如果一个Java源文件里的所有类都没有使用public修饰,则这个Java源文件的文件名可以是一切合法文件名;但如果一个Java源文件里定义了一个public修饰的类,则这个源文件的文件名必须与public类的类名相同。***

### package和import

package语句必须作为源文件第一句非注释语句。

一个源文件只能有一个包(只包含一条package语句)。

Java包机制需要两个方面的保证:

1. 源文件里使用package语句指定包名。

2. class文件必须放在对应的路径下。

- `import`语句:`*`可以表示包下所有类,而不能表示包。

例如:import package.subpackage.*;表示引入subpackage下所有类。

- `import static`语句:是JDK1.5引入的一种静态导入的语法,用于导入指定类的某个静态属性或全部静态属性值。`*`表示类下全部属性名。

例如:import staticpackage.subpackage.ClassName.*;

### Java的常用包

- `java.lang`:包含Java语言核心类,如String、Math、System和Thread等,系统会自动导入这个包下所有类。

- `java.util`:包含Java大量工具类/接口和集合框架/接口,如Arrays、List和Set等。

- `java.net`:包含Java网络编程相关类/接口。

- `java.io`:包含Java输入/输出编程相关的类/接口。

- `java.text`:包含一些Java格式化相关的类。

- `java.sql`:包含Java进行DJBC数据库编程的相关类/接口。

- `java.awt`:包含抽象窗口工具集(Abstract Window ToolKits)的相关类/接口,这些类主要用于构建图形用户界面(GUI)程序。

- `java.swing`:包含Swing图形用户界面编程的相关类/接口,这些类可用于构建平台无关的GUI程序。

### 重写父类的方法

方法的重写要遵循“**两同两小一大**”,

- `“两同”`:方法名相同,形参列表相同。

- `“两小”`:子类方法返回值应比父类方法返回值类型更小或相等。子类方法声明抛出的异常应比父类的方发声明抛出的异常类更小或相等。

-` “一大”`:子类方法的访问权限应比父类方法更大或相等。

> ***注意:覆盖方法和被覆盖的方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法。***

#### 面试题 1.父类的静态方法能否被子类重写

> 答:可以,子类可以重写父类静态方法。

### 利用组合实现复用

直接用一段代码来说明:

```

class Animal {

    private void beat() {

        System.out.println("心脏跳动。。。");

    }

    public void breach() {

        beat();

        System.out.println("吸一口气,吐一口气,呼吸中。。。");

    }

}

class Wolf {

    //将要组合部分嵌入类中属性,实现组合(有点类似于代理模式)

    private Animal animal;

    public Wolf(Animal animal) {

        this.animal = animal;

    }

    public void run() {

        //直接复用Animal提供的breath方法

        animal.breach();

        System.out.println("在陆地快速奔跑。。。");

    }

}

public class ZuHeDemo {

    public static void main(String[] strs) {

        Animal animal = new Animal();

        Wolf wolf = new Wolf(animal);

        wolf.run();

    }

}

```

### 接口与抽象类

接口与抽象类特征:

- 都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。

- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

接口与抽象类用法差别:

- 接口里只能包含抽象方法;抽象类则完全可以包含普通方法。

- 接口里不能定义静态方法;抽象类里可以定义静态方法。

- 接口里只能定义静态常量属性,不能定义普通属性;抽象类里则既可以定义静态常量属性,也可以定义普通属性。

- 接口不包含构造器;抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。

- 接口里不能包含初始化块,抽象类则可以包含初始化块。

- 一个类最多只能有一个直接父类,包括抽象类。但一个类可以实现多个接口。

#### 面试题 1.接口与抽象类的区别

> 答:接口与抽象类的差别非常大,这种差别主要体现在二者的设计目的上。总的来说接口是对动作的抽象,抽象类是对根源的抽象。

> 接口体现的是一种规范。在程序中使用接口时,接口是多个模块间的耦合标准。接口是一个契约,定义接口一定要慎重,因为一旦接口修改,对整个系统甚至其他系统的影响将是辐射式的,导致系统中大部分类都需要改写。

> 抽象类体现的是一种模板式的设计。

### 内部类

成员内部类是一种与属性、方法构造器和初始化相似的类成员,局部内部类和匿名内部类则不是类成员。

非静态内部类中不能有静态方法、静态属性、静态初始化块。

非静态内部类默认持有外部类的引用,这也是容易造成内存泄漏的一个危险点。

> ***注意:静态内部类里不可以有静态初始化代码块,但可以包含普通初始化块。***

#### 面试题 1.什么是内部类 ,内部类作用

> 答:把一个类放到另一个类的内部定义,这个定义在其他类内部的类就被称为内部类。

> 内部类作用:

> 1. 内部类提供了更好的封装,可以把内部类隐藏在外部类之内。

> 2. 内部类成员可以直接访问外部类的私有数据,因为内部类被当做其外部类成员,同一个类的成员可以相互访问。但外部类不可以访问内部类细节。

> 3. 匿名内部类适合用于创建那些仅需要一次使用的类。

### 静态内部类

静态内部类可以包含静态成员,可以包含非静态成员。

静态内部类也可以定义在接口里。

#### 匿名内部类

匿名内部类应该遵循两条规则:

- 匿名内部类不能是抽象类

- 匿名内部类不能定义构造器

#### 闭包(Closure)和回调

代码是最好的实践,直接通过代码说明:

```

//程序员

class Programmer{

    private String name;

    public Programmer(String name) {

        this.name = name;

    }

    public void work(){

        System.out.println(name + "正在敲代码...");

    }

}

//教师

interface Teacher{

    void work();

}

//既是程序员也是教师

class TeachProgrammer extends Programmer{

    public TeachProgrammer(String name) {

        super(name);

    }

    public void teach(){

        System.out.println("教师正在台上讲课...");

    }

    private class Closure implements Teacher{

        @Override

        public void work() {

            teach();

        }

    }

    public Teacher getCallBack(){

        return new Closure();

    }

}

public class ClosureCallBackDemo {

    public static void main(String[] strs) {

        TeachProgrammer tp = new TeachProgrammer("白泽");

        //直接调用从程序员继承的工作方法(work)

        tp.work();

        //表面上调用Teacher的work(),实际上回调的是teach()方法

        tp.getCallBack().work();

    }

}

```

内部类可以很方便的调用其外部类的属性方法,这样可以让编程更加灵活。

### 枚举类

枚举类与普通类区别:

- 枚举类可以实现一个或多个接口,使用enum关键字定义的枚举类默认继承了`java.lang.Enum`类,而不是`Object`。其中Enum实现了`java.lang.Serializable`和`java.lang.Comparable`两个接口。

- 枚举类的构造器只能使用`private`修饰。

- 枚举类的所有实例必须在枚举类中`显示列出`。列出这些实例时,系统会默认添加`public static final`修饰符。

- 所有枚举类都提供了一个`values`方法,可以遍历所有枚举值。

举一个支付状态的枚举类:

```

interface PayStateInterface {

    void info();

}

public enum PayState implements PayStateInterface {//支付状态(0未支付 10 已支付)

    WAIT_PAY(0) {

        public void info() {

            System.out.println("这个状态是待支付状态");

        }

    }, ALREAD_PAY(10) {

        public void info() {

            System.out.println("这个状态是已支付");

        }

    };

    private final int value;

    private PayState(int value) {

        this.value = value;

    }

    public static PayState valueOf(int type) {

        switch (type) {

            case 0:

                return WAIT_PAY;

            case 10:

                return ALREAD_PAY;

            default:

                return null;

        }

    }

    public int getValue() {

        return value;

    }

}

```

举例代码中还用到了枚举类实现接口知识点,可以根据不同实例来实现各自方法,也可以由枚举类实现一个公共的方法。

java.lang.Enum类中提供了一些方法:

- `int compareTo(E o)`:用于与指定枚举对象比较顺序,如果该枚举对象位于指定枚举对象之后,返回正整数,位于指定枚举对象之前,返回负整数,否则返回0。

- `int ordinal()`;返回枚举值在枚举类中的索引值。(第一个枚举值的索引值为0)

- `public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name)`:用于返回指定枚举类中指定名称的枚举值。

- `String toString()`:返回枚举常量的名称。

- `String name()`:与toString()类似。

### Java修饰符适用范围总表

|  | 顶层类/接口 | 成员属性 | 方法 | 构造器 | 初始化块 | 成员内部类 | 局部成员 |

|--|--|--|--|--|--|--|--|

| public | √ | √ | √ | √ |  | √ |  |

| protected |  | √ | √ | √ |  | √ |  |

| 包访问控制符 | √ | √ | √ | √ | O | √ | O |

| private |  | √ | √ | √ |  | √ |  |

| abstract | √ |  | √ |  |  | √ |  |

| final | √ | √ | √ |  |  | √ | √ |

| static |  | √ | √ |  |  | √ |  |

| strictfp | √ |  | √ |  |  | √ |  |

| synchronized |  |  | √ |  |  |  |  |

| native |  |  | √ |  |  |  |  |

| transient |  | √ |  |  |  |  |  |

| volatile |  | √ |  |  |  |  |  |

- `strictfp`关键字含义是FP-strict,就是`精确浮点`的意思。如果想让浮点运算更加精准,就可以使用strictfp修饰类、接口和方法。修饰的范围内Java编译器和运行环境会完全依照浮点规范IEEE-754来执行。

- `native`:使用native修饰的方法类似于一个抽象方法。如果某个方法需要利用`平台相关特性`,或者访问系统硬件时,则可以把该方法使用native修饰,再把该方法交给`C`去实现。一旦Java程序中包含native方法,这个程序将失去跨平台的功能。

## 问答模块

### 1. JRE和JVM的关系是怎样的?

> 答:简单地说:**JRE包含JVM**。

> JVM是运行Java程序的核心虚拟机,而运行Java程序不仅需要核心虚拟机,还需要其他的类加载器,字节码校验器以及大量的基础类库。JRE出了包含JVM之外,还包含运行Java程序的其他环境支持。

> ***注意:所有Java关键字都是小写的,true、false和null都不是Java关键字。***

### 2.为什么有栈内存和堆内存之分?

> 答:当一个方法执行时,每个`方法`都会建立自己的`内存栈`,在这个方法内定义的`变量`会逐个放在这块内存栈里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有**在方法中定义的变量都是放在栈内存中的**;当我们在程序中创建一个对象时,这个对象将被保存到`运行时数据区`中,以便反复利用(因为对象创建的成本通常较大),这个运行时数据区就是`堆内存`。堆内存中的对象**不会随方法的结束而销毁**,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会销毁。只有一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在合适的时候回收它。

### 3.为什么方法的返回值类型不能用于区分重载的方法

> 答:对于int f()和String f()两个方法,如果这样调用:int result = f();,系统可以识别我们是想用返回值类型为int的方法;但java调用方法时可以忽略返回值,如果这样调用:f();,可以判断调用哪个方法吗?Java系统也会糊涂。系统一糊涂,肯定就是你错了。因此,**java里不能使用返回值类型作为区分方法重载的依据**。

### 4.非静态内部类对象和外部类对象的关系是怎样的?

> 答:非静态内部类对象必须寄存在外部类对象里,而外部类对象则非必须有非静态内部类对象寄存在其中。因此外部类访问访问费静态内部类成员时,可能非静态内部类对象根本不存在,为非静态内部类访问外部类成员时,外部类对象一定存在。

上一篇下一篇

猜你喜欢

热点阅读