JavaScript 高级程序设计读书笔记 第 6 章 面向对象的程序设计

第 6 章 面向对象的程序设计

JavaScript 中的对象定义为“无序属性的集合,其属性可以包含基本值、对象或者函数。”每一个对象都是基于一个引用类型创建的。

6.1 理解对象

创建自定义对象最简单的方式就是创建一个 Object 的实例,然后为它添加属性和方法。

6.1.1 属性类型

ECMAScript 中有两种属性:数据属性和访问器属性。

数据属性

数据属性 数据属性包含一个数据值的位置,在这个位置可以读取和写入值。数据属性有四个描述其行为的特性。

  • Configurable 表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。默认 true
  • Enumerable 表示能否通过 for-in 循环返回属性。默认 true
  • Writeble 表示能否修改属性的值。 true
  • Value包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认 undefined

要修改属性默认的特性,需要使用 Object.defineProperty() 方法。这个方法

接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符对象的属性必须是: configurable、 enumerable、 writable和 value。设置其中的一或多个值,可以修改对应的特性值。

注意一旦修改 configurable 则再也不能修改回去了。

在调用Object.defineProperty()方法时,如果不指定 configurable,enumerable 和 writable 特性的默认值都是 false。

访问器属性

访问器属性不包含数据值 它们包含一对 getter 和 setter 函数。
在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。访问器属性有如下4个特性。

  • Configurable表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。
  • Enumerable表示能否通过for-in 循环返回属性。
  • Get 在读取属性时调用的函数。默认值为 undefined
  • Set 在写人属性时调用的函数。默认值为 undefined

访问器属性不能直接定义,必须使用 Object.defineProperty 来定义。

6.1.2 定义多个属性

使用 Object.defineProperties 方法。可以同时定义多个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let obj = {};

Object.defineProperties(obj, {
_name: {
value: 'diamondyuan',
},
name: {
get: function() {
return this._name;
},
},
});

console.log(obj.name);

6.1.3 读取属性的特性

使用 Object. getOwnPropertyDescriptor 可以取得给定属性的描述符。

6.2 创建对象

6.2.1 工厂模式

用工厂模式函数来封装以特定接口创建对象的细节。

1
2
3
4
5
6
7
8
9
10
11
12
function personFactory(name, age) {
let o = new Object();
o.name = name;
o.age = age;
o.sayName = function() {
console.log(this.name);
};
return o;
}

let person1 = personFactory('DiamondYuan', 20);
person1.sayName();

6.2.2 构造函数模式

通过构造函数可以创建特定类型的对象,所以可以自定义构造函数。来定义自定义对象的属性和方法。

1
2
3
4
5
6
7
8
9
10
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function() {
console.log(this.name);
};
}

let person1 = new Person('DiamondYuan', 20);
person1.sayName();

调用构造函数需要使用 new 操作符,实际会经历下面四个步骤

  • 创建一个新对象
  • 将构造函数的作用域赋值给新对象
  • 执行构造函数中的代码
  • 返回新对象

通过 instanceof 可以识别构造函数类型。这个是比工厂模式好的地方。

1. 将构造函数当作函数

直接运行构造函数,会把值赋值给 window,在严格模式下会报错。可以使用 call apply 来模拟 new

2.构造函数的问题

  1. 如果直接在构造函数里定义函数,那么不同实例上的同名函数是不等的。
  2. 如果在全局里定义函数,再再构造函数里把函数绑定到对象上,这样在全局作用域下定义的函数只能被某个对象调用,而且需要定义很多全局函数,无封装性可言。

6.2.3 原型模式

我们创建的每一个函数都有一个 prototype 原型属性,指向包含可以由特定类型的所有实例共享的属性和方法,即 prototype 就是通过构造函数而创建的对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

  1. 理解原型对象

只要创建了一个函数,那么该函数会有一个 prototype 的属性,这个属性指向函数的原型对象。

所有原型对象都会有自动获得一个 constructor 的属性。这个属性包含一个指向 prototype 属性所在函数的指针。

1
2
3
4
function Person() {
}

console.log(Person.prototype.constructor === Person) //true

创建自定义构造函数,其原型对象只会取得 constructor 属性,其他方法都是由 Object 继承而来。每当调用构造函数创建一个新实例后,该实例都将包含一个指针,指向构造函数的原型对象。在 ECMA-262 第 5 版中管这个指针叫 [[Prototype]],在 Firefox Safari Chrome ,每一个对象都支持一个属性 __proto__

1
2
3
const person = new Person();

console.log(person.__proto__ === Person.prototype); //true

虽然在所有实现中都无法访问到 [[prototype]],但是可以通过 isPrototypeOf 方法来确认这种关系。

1
2
3
const person = new Person();

Person.prototype.isPrototypeOf(person); //true

ECMAScript 5 增加了一个新的方法 Object.getPrototypeOf 方法来获取 [[prototype]] 的值。

1
2
3
const person = new Person();

console.log(Object.getPrototypeOf(person) === Person.prototype);

当代码读取对象属性,首先会从对象实例本身开始,如果在实例中找到了具体给定名字的属性,则返回该属性的值。否则就去原型对象上面找。

虽然可以通过实例对象访问原型中的值,但是不能通过原型对象实例重写原型中的值。在实例中添加属性,会写到实例对象上,屏蔽同名的原型对象上的值。

可以通过 hasOwnProperty() 来检测一个属性是否存在于实例中。

1
2
3
4
5
6
7
8
9
function Person() {
}

Person.prototype.name = 'Old'
const person = new Person()
person.name = 'DiamondYuan'
console.log(person.hasOwnProperty('name')) //ture
delete person.name;
console.log(person.name) //old

Object.getOwnPropertyDescriptor 方法只能用于实例属性。如果要获取原型属性,必须直接在原型对象上调用。

  1. 原型与 in 操作符

for inObject.keys 可以获取对象全部可枚举的属性。如果要获取全部属性,包括可枚举与不可枚举,可以使用 Object.getOwnPropertyNames() 方法

1
2
3
4
5
function Person() {
}
Person.prototype.name = 'Old'
const person = new Person()
console.log(Object.getOwnPropertyNames(Person.prototype)) //[ 'constructor', 'name' ]
  1. 更简单的原型方法

可以用一个包含所有属性和方法的对象字面量来重写整个原型对象。

1
2
3
4
5
6
7
8
function Person() {}

Person.prototype = {
name: 'DiamondYuan',
sayName: function() {
return this.name;
},
};

此时 constructor 不再指向 Person 函数,需要自己设置成适当的值。而且需要用 Object.defineProperty 修改 constructor 的特性。

  1. 原型的动态性
    由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此。
    而如果重写了整个原型对象,那么就切断现有原型和之前已经存在的对象实例之间的联系,引用的还是原来的原型。
  2. 原声对象的原型
    原生对象也是在构造函数的原型上定义了方法。
1
console.log(Array.prototype.sort) //[Function: sort]

不推荐直接修改原生对象的原型

  1. 原型对象的问题

因为属性是被多个实例共享的。如果属性是引用类型,那么修改会在全部的实例中反映出来。

6.2.4 组合使用构造函数模式和原型模式

1
2
3
4
5
6
7
8
9
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
return this.name;
};
var person = new Person('DiamondYuan');

console.log(person.sayName());

用原型模式定义方法和共享的属性。构造函数定义实例属性。

6.2.5 动态原型模式

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name) {
this.name = name;

if (typeof this.sayName != 'function') {
Person.prototype.sayName = function() {
return this.name;
};
}
}

var person = new Person('DiamondYuan');

console.log(person.sayName());

可以把信息都封装在构造函数里。

6.2.6 寄生构造模式

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age) {
let o = new Object();
o.name = name;
o.age = age;
o.sayName = function() {
console.log(this.name);
};
return o;
}

var person = new Person('DiamondYuan', 12);

person.sayName();

和工厂模式代码一样,在构造函数内末尾添加return,重写构造函数的返回值。

6.2.7 稳妥构造函数

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
let o = new Object();
o.name = name;
o.sayName = function() {
return name;
};
return o;
}

var person = Person('DiamondYuan');

console.log(person.sayName());

不使用newthis,除了 sayName,没有其他任何方法访问到 name

6.3 继承

ECMAScript 中只有实现继承,无接口继承。

6.3.1 原型链

可以让子类型的原型对象指向父类型的实例。层层递进,构成了原型链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};

function SubType() {
this.subProperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
return this.subProperty;
};

var instance = new SubType();
console.log(instance.getSubValue()); //false
console.log(instance.getSuperValue()); //true
instance.__proto__.__proto__ === SuperType.prototype //true

注意,上面代码中 instance.constructor 指向了 SuperType。因为原来 这是因为原来 SubType. prototype中的 constructor 被重写了。 见 6.2.3.3

image.png

  1. 别忘了默认的原型

上面的例子还少了一环,所有的引用类型都继承了 Object ,这个继承也是通过原型链实现的。

image.png

SubType 继承了 SuperType,而 SuperType 继承了 Object。

  1. 确定原型和实例的关系
    第一种方法可以使用 instanceof 操作符。或者使用 isPrototypeOf 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};

function SubType() {
this.subProperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
return this.subProperty;
};

let instance = new SubType();

console.log(SubType.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance));/ true
console.log(Object.prototype.isPrototypeOf(instance)); // true
  1. 谨慎定义方法

给原型添加方法需要在替换原型之后。否则调用的还是原来的方法。

  1. 原型链的问题

所有引用类型的原型属性会被所有的实例共享。

6.3.2 借用构造函数

可以在子类的构造函数里面调用父类构造函数。

image.png

  1. 传递参数

构造函数的好处是可以传递参数。

  1. 问题

只能构造函数中定义,且超类原型中的方法对子类也不可见。

6.3.3 组合继承

借用构造函数和原型链,用构造函数继承原型的属性,用原型链继承原型上的方法。
image.png

6.3.4 原型式继承

借用原型基于已有的对象创建新的对象

1
2
3
4
5
function object(o){
function F(){};
F.prototype = 0;
return new F();
}

在 es5 中可以使用 Object.create 来实现。

image.png

6.3.5 寄生式继承

1
2
3
4
5
6
7
function createAnother(original) {
var clone = object(original);
clone.sayHi = function() {
alert('hi');
};
return clone;
}

创建一个封装继承过程的函数,在函数内部增强对象。缺点是函数无法复用。

6.3.6 寄生组合式继承

使用构造函数来继承属性,其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
image.png

image.png

6.4 小结

image.png

1. 编译与运行 VS Code

内容基于 1.37版本,可能与最新的源码不同
本人使用系统为 macOS ,其他系统可以查看 官方指南

环境

  • Git
  • Node.JS, x64, 版本 >= 10.16.0, < 11.0.0
  • Yarn
  • Python 版本号大于 2.7(Python 3 不支持)
  • C/C++ 的编译工具
    • Mac

Xcode 以及命令行工具(Command Line Tool),安装后会自动安装 gccmake

- 运行 `xcode-select --install` 安装命令行工具(Command Line Tool)

安装完毕后,运行 yarn 命令来安装全部的依赖。

1
2
cd vscode
yarn

JavaScript 高级程序设计读书笔记 第 5 章 引用类型

第 5 章 引用类型

引用类型的值(对象)是引用类型的一个实例。对象是使用 new 操作符后跟一个构造函数来创建的。构造函数本身就是一个函数。

1
var person = new Object();

JavaScript 高级程序设计读书笔记 第 4 章 变量、作用域和内存问题

第 4 章 变量、作用域和内存问题

4.1 基本类型和引用类型

ECMAScript 变量可能包含两种不同数据类型的:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。
在将一个值赋绐变量时,解析器必须确定这个值是基本类型值还是引用类型值。基本类型是按值访问的,引用类型是按引用访问的。

JavaScript 高级程序设计读书笔记 第 3 章 基本概念

第 3 章 基本概念

3.1 语法

3.1.1 区分大小写

ECMAScript 中的一切都区分大小写。

JavaScript 高级程序设计读书笔记 第 2 章 在HTML中使用 JavaScript

第 2 章 在 HTML 中使用 JavaScript

2.1 <script> 元素

在页面插入 JavaScript 主要方法,就是使用 <script> 元素。 <script> 常用有下面几个属性:

  • async(html5) 该布尔属性指示浏览器是否在允许的情况下异步执行该脚本。该属性对于内联脚本无作用 (即没有 src 属性的脚本)。
  • defer 这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。如果缺少 src 属性(即内嵌脚本),该属性不应被使用,因为这种情况下它不起作用。对动态嵌入的脚本使用 async=false 来达到类似的效果。
  • src 这个属性定义引用外部脚本的 URI,这可以用来代替直接在文档中嵌入脚本。指定了 src 属性的 script 元素标签内不应该再有嵌入的脚本
  • type 该属性定义 script 元素包含或 src 引用的脚本语言。属性的值为 MIME 类型; 支持的 MIME 类型包括text/javascript, text/ecmascript, application/javascript, 和application/ecmascript。如果没有定义这个属性,脚本会被视作 JavaScript。如果 type 属性为 module,代码会被当作 JavaScript 模块 。请参见 ES6 In Depth: Modules

代码出现 </script> 会导致错误。

1
2
3
4
5
<script type="text/javascript*>
function sayScript (){
alert("</script>")
}
</script>

JavaScript 高级程序设计读书笔记 第 1 章 JavaScript 简介

第 1 章 JavaScript 简介

完整的 Javascript 实现应该由三部分组成 ECMAScript DOM BOM

Leetcode 215 数组中的第K个最大元素

Leetcode 215 数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,它是数组有序排列后的第 k 个最大元素,而不是第 k 个不同元素。

例如,
给出 [3,2,1,5,6,4] 和 k = 2,返回 5。

注意事项:

你可以假设 k 总是有效的,1 ≤ k ≤ 数组的长度。

思路 1 堆

可以先取k个元素,放到一个数组中,然后把数组转换成最小堆。之后遍历剩下的全部元素,和最小堆的根进行比较。如果比根要大,则替换根,之后把数组重新转换成最小堆。遍历完成后,最小堆堆根即为第 k 大的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Solution {

public static int findKthLargest(int[] nums, int k) {
int[] heap = new int[k];
//首先取k个元素
System.arraycopy(nums, 0, heap, 0, k);
//从倒数k个节点开始 调整数组成为最小堆
for (int i = k / 2 - 1; i >= 0; i--) {
adjest(heap, i);
}
//如果元素小于最小堆 跳过。
//如果元素大于最小堆 把元素放在堆顶 然后调整堆
for (int i = k; i < nums.length; i++) {
if (heap[0] < nums[i]) {
heap[0] = nums[i];
adjest(heap, 0);
}
}
//返回堆顶堆元素
return heap[0];
}


/**
* 调整最小堆
* @param heap 堆
* @param i 从哪个 index 开始
*/
private static void adjest(int[] heap, int i) {
int temp = heap[i];
int length = heap.length;
for (int k = i * 2 + 1; k < length; k = 2 * k + 1) {
if (k + 1 < length && heap[k + 1] < heap[k]) {
k++;
}
if (temp <= heap[k]) {
break;
} else {
heap[i] = heap[k];
i = k;
}
}
heap[i] = temp;
}
}

题目链接

Leetcode-cn
Leetcode

Elasticsearch 入门教程(1) 安装 Elasticsearch 与 Kibana

本文基于版本 6.1.1

Elasticsearch 是基于 Lucene 的开源搜索引擎。封装了底层 Lucene 的接口并且提供了 REST API 的接口。开箱即用。

使用 docker 安装

用 docker-compose 部署最为方便。把下面的内容保存到 docker-compose.yaml 中,然后执行 docker-compose up -d 就可以启动容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
version: '2'
services:
elasticsearch:
image: registry.cn-hongkong.aliyuncs.com/yfd-ci/es-docker-image:v2018037
container_name: elasticsearch
hostname: elasticsearch
restart: always
networks:
- elk
environment:
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata1:/usr/share/elasticsearch/data
ports:
- 9200:9200
- 9300:9300
kibana:
image: registry.cn-hongkong.aliyuncs.com/yfd-ci/kibana-docker-image:v20180327-2
container_name: kibana
networks:
- elk
depends_on:
- elasticsearch
environment:
SERVER_NAME: kibana
ELASTICSEARCH_URL: http://elasticsearch:9200
ports:
- 5601:5601
volumes:
esdata1:
driver: local
networks:
elk:
driver: bridge

构建镜像的 Dockerfile 分别 如下

1
FROM docker.elastic.co/kibana/kibana-oss:6.1.1
1
2
3
4
5
6
FROM docker.elastic.co/elasticsearch/elasticsearch-oss:6.1.1

RUN cd /usr/share/elasticsearch/ && \
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-pinyin/releases/download/v6.1.1/elasticsearch-analysis-pinyin-6.1.1.zip && \
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.1.1/elasticsearch-analysis-ik-6.1.1.zip \
&& mv /usr/share/elasticsearch/plugins/analysis-ik/ /usr/share/elasticsearch/plugins/ik/

如果运行成功,那么 es 会在本地的9200端口运行。用 curl 请求9200端口,可以获取当前集群的名称,版本等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
curl localhost:9200
{
"name" : "OFyyM08",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "tEBFzeLWT5uuCas17Z0Waw",
"version" : {
"number" : "6.1.1",
"build_hash" : "bd92e7f",
"build_date" : "2017-12-17T20:23:25.338Z",
"build_snapshot" : false,
"lucene_version" : "7.1.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}

访问 localhost:5601 就可以看到 Kibana 的页面了

注意点

max virtual memory areas vm.maxmapcount [65530] is too low

如果执行失败 出现 max virtual memory areas vm.max*map*count [65530] is too low 的错误。需要执行下面的命令。

1
sudo sysctl -w vm.max_map_count=262144

Java Object 方法简析

Java Object 方法简析

Object 类位于java.lang ,我们可以看到源码中写到 Every class has {@code Object} as a superclass. All objects 即 Object 类是所有类的父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final native Class<?> getClass();

public native int hashCode();
public boolean equals(Object obj);

protected native Object clone() throws CloneNotSupportedException;

public String toString();

public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException;
public final void wait() throws InterruptedException;

protected void finalize() throws Throwable { }

getClass方法

1
2
3
 * @return The {@code Class} object that represents the runtime
* class of this object.
public final native Class<?> getClass();

从源码中可以看到 getClass 是 final 方法,无法被继承。同时是 native 方法。即其他语言例如 C 与 C++ 实现的方法。结果为对象的运行时 Class 对象。

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package im.yfd.demo;
class A {
}

class B extends A {
}

class Test{
public static void main(String[] args) {
A a = new A();
A b = new B();
System.out.println(a.getClass());
System.out.println(b.getClass());
a = b;
System.out.println(a.getClass());
}
}

返回结果

1
2
3
class im.yfd.demo.A
class im.yfd.demo.B
class im.yfd.demo.B

hashCode 方法与 equals 方法

equals

equals主要用于判断两个对象是否相等。约定 equals 有下列几个性质。

当object object1 object2 均为非空时。

  • 自反性:object.equals(object) 永远为 true
  • 对称性:object1.equals(object) == object.equals(object1) 永远为 true
  • 传递性:object.equals(object1) 为 true且object1.equals(object2) 为 true 时,object.equals(object2) 为 true。
  • 一致性:当 object 与 object1 均未修改时候。object.equals(object1) 结果永远保持不变。
  • 对于非空对象 object。那个 object.equals(null) 永远返回 false。

Object类中的默认实现为比较两个地址是否相同。等价于 ==

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

hashCode

hashCode也是一个native方法。该方法返回对象的哈希码,通常用在哈希表中。例如常用的 HashMap。

对于hashCode,我们 应该 遵循如下规则:

  • 在一个应用程序执行期间,任何时间对同一个对象调用 hashCode 方法。都必须返回用一个整数。这个整数在两次对同一个应用程序的执行在不需要保持一致。
  • 如果两个对象通过 equals 比较相等,那么 hashCode 方法必须产生同样的结果。
  • 如果两个对象通过 equals 比较不相等,那么 hashCode 方法产生的结果 不需要 向灯。但是如果不同对象产生不同的结果。那么有助于提高哈希表的效率

当重写 equals 时候 必须也要重写 hashcode 。否则在 HashMap 中就会出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Person {
Person(String name) {
this.name = name;
}
private String name;
@Override
public boolean equals(Object object) {
if (this == object) {
return false;
}
if (object == null || object.getClass() != this.getClass()) {
return false;
}
Person p = (Person) object;
if (p.name == null) {
return this.name == null;
}
return p.name.equals(this.name);
}
}


class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person a = new Person("name");
Person b = new Person("name");
Map<Person, String> map = new HashMap<>();
map.put(a, "a");
System.out.println(a.equals(b));
System.out.println(map.get(a));
System.out.println(map.get(b));
}
}

例如上面的代码的返回结果就是

1
2
3
true
a
null

因为 HashMap 会先根据 hashcode 来决定对象在哪个桶中。再在同一个桶中根据 equals 判断 key 是否相同。

clone方法

clone 会返回对象的拷贝。如果直接调用

1
2
3
4
5
6
class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Test test = new Test();
Test testClone = (Test) test.clone();
}
}

会报错 Exception in thread "main" java.lang.CloneNotSupportedException

finalize方法

finalize 方法会在对象被回收时候调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Test {

String name;

@Override
public void finalize() {
System.out.println("GC: " + name);
}

public static void main(String[] args) throws CloneNotSupportedException {
Test test = new Test();
test.name = "1";
System.out.println("test 1");
System.gc();
test = new Test();
System.gc();
System.out.println("test 2");
}
}

例如上面的代码返回结果

1
2
3
test 1
test 2
GC: 1
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×