---
title: "Apa itu table dan metatable dalam Lua? - API7.ai"
description: "Table adalah satu-satunya mekanisme struktur data di Lua. Kita dapat menggunakan table untuk merepresentasikan array biasa, symbol table, set, record, queue, dan struktur data lainnya. Pelajari lebih lanjut dari postingan ini."
url: "https://api7.ai/id/learning-center/openresty/lua-table-and-metatable"
---

# Apa itu table dan metatable dalam Lua?

API7.ai

October 11, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

Hari ini kita akan mempelajari tentang satu-satunya struktur data di LuaJIT: `table`.

Tidak seperti bahasa skrip lain yang memiliki struktur data yang kaya, LuaJIT hanya memiliki satu struktur data, yaitu `table`, yang tidak dibedakan antara array, hash, koleksi, dan sebagainya, melainkan agak tercampur. Mari kita tinjau salah satu contoh yang disebutkan sebelumnya.

```
local color = {first = "red", "blue", third = "green", "yellow"}
print(color["first"])                 --> output: red
print(color[1])                         --> output: blue
print(color["third"])                --> output: green
print(color[2])                         --> output: yellow
print(color[3])                         --> output: nil
```

Dalam contoh ini, tabel `color` berisi array dan hash, dan dapat diakses tanpa saling mengganggu. Misalnya, Anda dapat menggunakan fungsi `ipairs` untuk mengiterasi hanya bagian array dari tabel.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
for k, v in ipairs(color) do
      print(k)
end
'
```

Operasi `table` sangat penting sehingga LuaJIT memperluas library tabel standar Lua 5.1, dan OpenResty memperluas library tabel LuaJIT lebih jauh lagi. Mari kita lihat masing-masing fungsi library ini.

## Fungsi library tabel

Mari kita mulai dengan fungsi library tabel standar. Lua 5.1 tidak memiliki banyak fungsi library tabel, jadi kita bisa melihatnya sekilas.

### `table.getn` Mendapatkan jumlah elemen

Seperti yang kami sebutkan di bab [Standard Lua dan LuaJIT](https://api7.ai/learning-center/openresty/luajit-vs-lua/), mendapatkan jumlah elemen tabel yang benar adalah masalah besar di LuaJIT.

Untuk urutan, Anda dapat menggunakan `table.getn` atau operator unary `#` untuk mengembalikan jumlah elemen yang benar. Contoh berikut mengembalikan angka 3 yang kita harapkan.

```
$ resty -e 'local t = { 1, 2, 3 }
  print(table.getn(t))
```

Nilai yang benar tidak dapat dikembalikan untuk tabel yang tidak berurutan. Pada contoh kedua, nilai yang dikembalikan adalah 1.

```
$ resty -e 'local t = { 1, a = 2 }
  print(#t) '
```

Untungnya, fungsi yang sulit dipahami seperti ini telah digantikan oleh ekstensi LuaJIT, yang akan kami sebutkan nanti. Jadi dalam konteks OpenResty, jangan gunakan fungsi `table.getn` dan operator unary `#` kecuali Anda tahu secara eksplisit bahwa Anda sedang mendapatkan panjang urutan.

Selain itu, `table.getn` dan operator unary `#` bukanlah kompleksitas waktu O(1) melainkan O(n), yang merupakan alasan lain untuk menghindarinya jika memungkinkan.

### `table.remove` Menghapus elemen yang ditentukan

Yang kedua adalah fungsi `table.remove`, yang menghapus elemen dalam tabel berdasarkan subskrip, yaitu hanya elemen di bagian array tabel yang dapat dihapus. Mari kita lihat contoh `color` lagi.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
  table.remove(color, 1)
  for k, v in pairs(color) do
      print(v)
  end'
```

Kode ini akan menghapus `blue` dengan subskrip 1. Anda mungkin bertanya, bagaimana cara menghapus bagian hash dari tabel? Caranya sederhana, yaitu dengan mengatur nilai yang sesuai dengan kunci ke `nil`. Jadi, dalam contoh `color`, `green` yang sesuai dengan `third` dihapus.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
  color.third = nil
  for k, v in pairs(color) do
      print(v)
  end'
```

### `table.concat` Fungsi penggabungan elemen

Yang ketiga adalah fungsi penggabungan elemen `table.concat`. Ini menggabungkan elemen-elemen tabel berdasarkan subskrip. Karena ini lagi-lagi berdasarkan subskrip, ini masih untuk bagian array dari tabel. Sekali lagi dengan contoh `color`.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
print(table.concat(color, ", "))'
```

Setelah menggunakan fungsi `table.concat`, ini akan mengeluarkan `blue, yellow` dan bagian hash dilewati.

Selain itu, fungsi ini juga dapat menentukan posisi awal subskrip untuk melakukan penggabungan; misalnya, ditulis seperti berikut

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow", "orange"}
print(table.concat(color, ", ", 2, 3))'
```

Kali ini outputnya adalah `yellow, orange`, melewatkan `blue`.

Jangan meremehkan fungsi yang tampaknya tidak berguna ini, tetapi ini dapat memiliki efek yang tidak terduga saat mengoptimalkan kinerja dan merupakan salah satu karakter utama dalam bab optimasi kinerja kami nanti.

### `table.insert` Menyisipkan elemen

Terakhir, mari kita lihat fungsi `table.insert`. Ini menyisipkan elemen baru di subskrip yang ditentukan, yang memengaruhi bagian array dari tabel. Untuk menggambarkan, sekali lagi, menggunakan contoh `color`.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
table.insert(color, 1,  "orange")
print(color[1])
'
```

Anda dapat melihat bahwa elemen pertama color menjadi orange, tetapi tentu saja, Anda dapat meninggalkan subskrip tidak ditentukan sehingga akan disisipkan di akhir antrian secara default.

Saya harus mencatat bahwa `table.insert` adalah operasi yang umum, tetapi kinerjanya tidak baik. Jika Anda tidak menyisipkan elemen berdasarkan skrip yang ditentukan, maka Anda perlu memanggil `lj_tab_len` LuaJIT setiap kali untuk mendapatkan panjang array untuk disisipkan di akhir antrian. Seperti `table.getn`, kompleksitas waktu untuk mendapatkan panjang tabel adalah O(n).

Jadi, untuk operasi `table.insert`; kita harus mencoba menghindari penggunaannya dalam kode panas. Misalnya:

```
local t = {}
for i = 1, 10000 do
     table.insert(t, i)
end
```

## Fungsi ekstensi tabel LuaJIT

Selanjutnya, mari kita lihat fungsi ekstensi tabel LuaJIT. LuaJIT memperluas Lua standar dengan dua fungsi tabel yang bermanfaat untuk membuat dan mengosongkan tabel, yang akan saya jelaskan di bawah ini.

### `table.new(narray, nhash)` Membuat tabel baru

Yang pertama adalah fungsi `table.new(narray, nhash)`. Alih-alih tumbuh sendiri saat menyisipkan elemen, fungsi ini akan mengalokasikan ukuran ruang array dan hash yang ditentukan, yang merupakan arti dari dua parameter `narray` dan `nhash`. Pertumbuhan sendiri adalah operasi yang mahal yang melibatkan alokasi ruang, `resize` dan `rehash`, dan harus dihindari sebisa mungkin.

Perhatikan di sini bahwa dokumentasi untuk `table.new` tidak ada di situs web LuaJIT tetapi tersembunyi di [dokumentasi ekstensi](https://github.com/openresty/luajit2/blob/v2.1-agentzh/doc/extensions.html) proyek GitHub, sehingga sulit ditemukan bahkan jika Anda mencarinya di Google, jadi tidak banyak insinyur yang mengetahuinya.

Berikut adalah contoh sederhana, dan saya akan menunjukkan cara kerjanya. Pertama-tama, fungsi ini adalah ekstensi, jadi sebelum Anda dapat menggunakannya, Anda perlu `require`.

```
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 100 do
   t[i] = i
end
```

Seperti yang Anda lihat, kode ini membuat tabel baru dengan 100 elemen array dan 0 elemen hash. Tentu saja, Anda dapat membuat tabel baru dengan 100 elemen array dan 50 elemen hash sesuai kebutuhan, yang legal.

```
local t = new_tab(100, 50)
```

Atau, jika Anda melampaui ukuran ruang yang telah ditentukan, Anda masih dapat menggunakannya seperti biasa, tetapi kinerjanya akan menurun, dan tujuan menggunakan `table.new` akan hilang.

Dalam contoh berikut, kami memiliki ukuran yang telah ditentukan sebesar 100, tetapi kami menggunakan 200.

```
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 200 do
   t[i] = i
end
```

Anda perlu menentukan ukuran ruang array dan hash di `table.new` sesuai dengan skenario aktual sehingga Anda dapat menemukan keseimbangan antara kinerja dan penggunaan memori.

### `table.clear()` Mengosongkan tabel

Yang kedua adalah fungsi clear `table.clear()`. Ini mengosongkan semua data dalam tabel tetapi tidak membebaskan memori yang digunakan oleh bagian array dan hash. Oleh karena itu, ini sangat bermanfaat saat mendaur ulang tabel Lua untuk menghindari overhead pembuatan dan penghancuran tabel berulang kali.

```
$ resty -e 'local clear_tab =require "table.clear"
local color = {first = "red", "blue", third = "green", "yellow"}
clear_tab(color)
for k, v in pairs(color) do
     print(k)
end'
```

Namun, tidak banyak skenario di mana fungsi ini dapat digunakan, dan dalam kebanyakan kasus, kita harus menyerahkan tugas ini kepada GC LuaJIT.

## Fungsi ekstensi tabel OpenResty

Seperti yang saya sebutkan di awal, OpenResty mempertahankan cabang LuaJIT-nya sendiri, yang juga memperluas tabel, [dengan beberapa API baru](https://github.com/openresty/luajit2/#new-api): `table.isempty,` `table. isarray`, `table.nkeys` dan `table.clone`.

Sebelum menggunakan API baru ini, harap periksa versi OpenResty, karena sebagian besar API ini hanya dapat digunakan dalam versi OpenResty setelah 1.15.8.1. Ini karena OpenResty tidak memiliki rilis baru selama sekitar satu tahun sebelum versi 1.15.8.1, dan API ini ditambahkan dalam interval rilis tersebut.

Saya telah menyertakan tautan ke artikel, jadi saya akan menggunakan `table.nkeys` sebagai contoh. Tiga API lainnya cukup mudah dipahami dari sudut pandang penamaan, jadi lihat dokumentasi GitHub, dan Anda akan memahaminya. Saya harus mengatakan bahwa dokumentasi OpenResty sangat berkualitas tinggi, termasuk contoh kode, apakah itu dapat di-JIT, apa yang harus diperhatikan, dll. Beberapa tingkat lebih baik daripada dokumentasi Lua dan LuaJIT.

Oke, kembali ke fungsi `table.nkeys`. Penamaannya mungkin membingungkan Anda, tetapi ini adalah fungsi yang mendapatkan panjang tabel dan mengembalikan jumlah elemen tabel, termasuk elemen array dan bagian hash. Oleh karena itu, kita dapat menggunakannya sebagai pengganti `table.getn`, misalnya, sebagai berikut.

```
local nkeys = require "table.nkeys"
print(nkeys({}))  -- 0
print(nkeys({ "a", nil, "b" }))  -- 2
print(nkeys({ dog = 3, cat = 4, bird = nil }))  -- 2
print(nkeys({ "a", dog = 3, cat = 4 }))  -- 3
```

## Metatable

Setelah membahas fungsi tabel, mari kita lihat `metatable` yang berasal dari `table`. Metatable adalah konsep unik di Lua, dan banyak digunakan dalam proyek-proyek nyata. Tidak berlebihan untuk mengatakan bahwa Anda dapat menemukannya di hampir semua library `lua-resty-*`.

`Metatable` berperilaku seperti operator overloading; misalnya, kita dapat meng-overload `__add` untuk menghitung penggabungan dua array Lua atau `__tostring` untuk mendefinisikan fungsi yang mengubah ke string.

Lua, di sisi lain, menyediakan dua fungsi untuk menangani metatable.

- Yang pertama adalah `setmetatable(table, metatable)`, yang mengatur metatable untuk tabel.
- Yang kedua adalah `getmetatable(table)`, yang mendapatkan metatable dari tabel.

Setelah semua ini, Anda mungkin lebih tertarik pada apa yang dilakukannya, jadi mari kita lihat apa yang sebenarnya digunakan metatable. Berikut adalah potongan kode dari proyek nyata.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1,
  patch = 1
  }
version = setmetatable(version, {
    __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(tostring(version))
'
```

Kami pertama-tama mendefinisikan tabel bernama `version`, dan seperti yang Anda lihat, tujuan kode ini adalah untuk mencetak nomor versi dalam `version`. Namun, kami tidak dapat mencetak `version` secara langsung. Anda dapat mencoba melakukan ini dan melihat bahwa mencetak langsung hanya akan mengeluarkan alamat tabel.

```
print(tostring(version))
```

Jadi, kami perlu menyesuaikan fungsi konversi string untuk tabel ini, yaitu `__tostring`, dan di sinilah metatable masuk. Kami menggunakan `setmetatable` untuk mengatur ulang metode `__tostring` dari tabel `version` untuk mencetak nomor versi: 1.1.1.

Selain `__tostring`, kami sering meng-override dua metamethode berikut dalam metatable dalam proyek nyata.

**Salah satunya adalah \_\_index**. Ketika kami mencari elemen dalam tabel, kami pertama-tama mencarinya langsung dari tabel, dan jika tidak menemukannya, kami melanjutkan ke `__index` dari metatable.

Kami menghapus `patch` dari tabel `version` dalam contoh berikut.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1
  }
version = setmetatable(version, {
     __index = function(t, key)
         if key == "patch" then
             return 2
         end
     end,
     __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(tostring(version))
'
```

Dalam kasus ini, `t.patch` tidak mendapatkan nilai, jadi ia pergi ke fungsi `__index`, yang mencetak 1.1.2.

`__index` tidak hanya bisa berupa fungsi tetapi juga tabel, dan jika Anda mencoba menjalankan kode berikut, Anda akan melihat bahwa mereka mencapai hasil yang sama.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1
  }
version = setmetatable(version, {
     __index = {patch = 2},
     __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(tostring(version))
'
```

**Metamethode lainnya adalah \_\_call**. Ini mirip dengan functor yang memungkinkan tabel untuk dipanggil.

Mari kita bangun di atas kode yang mencetak nomor versi dan lihat bagaimana cara memanggil tabel.

```
$ resty -e '
local version = {
  major = 1,
  minor = 1,
  patch = 1
  }
local function print_version(t)
     print(string.format("%d.%d.%d", t.major, t.minor, t.patch))
end
version = setmetatable(version,
     {__call = print_version})
  version()
'
```

Dalam kode ini, kami menggunakan `setmetatable` untuk menambahkan metatable ke tabel `version`, dan metamethode `__call` di dalamnya menunjuk ke fungsi `print_version`. Jadi, jika kami mencoba memanggil `version` sebagai fungsi, fungsi `print_version` akan dieksekusi di sini.

Dan `getmetatable` adalah operasi yang dipasangkan dengan `setmetatable` untuk mendapatkan metatable yang telah diatur, seperti kode berikut.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1
  }
version = setmetatable(version, {
     __index = {patch = 2},
     __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(getmetatable(version).__index.patch)
'
```

Selain tiga metamethode yang kami bahas hari ini, ada beberapa metamethode yang jarang digunakan yang dapat Anda konsultasikan di [dokumentasi](http://lua-users.org/wiki/MetamethodsTutorial) untuk mempelajari lebih lanjut saat Anda menemukannya.

## Pemrograman Berorientasi Objek

Terakhir, mari kita bicara tentang pemrograman berorientasi objek. Seperti yang Anda ketahui, Lua bukanlah bahasa Pemrograman Berorientasi Objek, tetapi kita dapat menggunakan metatable untuk mengimplementasikan OO.

Mari kita lihat contoh praktis. [lua-resty-mysql](https://github.com/openresty/lua-resty-mysql/blob/master/lib/resty/mysql.lua) adalah klien MySQL resmi OpenResty, dan ia menggunakan metatable **simulasi** kelas dan metode kelas, yang digunakan dengan cara berikut.

```
    $ resty -e 'local mysql = require "resty.mysql" -- pertama-tama referensikan library lua-resty
    local db, err = mysql:new() -- Buat instance baru dari kelas
    db:set_timeout(1000) -- Memanggil metode kelas
```

Anda dapat menjalankan kode di atas langsung dengan perintah `resty`. Baris-baris kode ini mudah dipahami; satu-satunya hal yang mungkin membuat Anda bingung adalah.

**Saat memanggil metode kelas, mengapa menggunakan titik dua alih-alih titik?**

Sebenarnya, titik dua dan titik sama-sama baik di sini, dan `db:set_timeout(1000)` dan `db.set_timeout(db, 1000)` persis sama. Titik dua adalah gula sintaksis di Lua yang memungkinkan menghilangkan argumen pertama `self` dari suatu fungsi.

Seperti yang kita semua tahu, tidak ada rahasia di depan kode sumber, jadi mari kita lihat implementasi konkret yang sesuai dengan baris kode di atas sehingga Anda dapat lebih memahami cara memodelkan pemrograman berorientasi objek dengan metatable.

```
local _M = { _VERSION = '0.21' } -- Menggunakan tabel simulasi kelas
local mt = { __index = _M } -- mt adalah singkatan dari metatable, __index merujuk ke kelas itu sendiri
-- Konstruktor kelas
function _M.new(self)
    local sock, err = tcp()
    if not sock then
          return nil, err
    end
    return setmetatable({ sock = sock }, mt) -- contoh simulasi kelas menggunakan tabel dan metatable
end

-- Fungsi anggota kelas
function _M.set_timeout(self, timeout) -- Gunakan argumen self untuk mendapatkan instance kelas yang ingin Anda operasikan
  local sock = self.sock
  if not sock then
      return nil, "not initialized"
  end

  return sock:settimeout(timeout)
end
```

Tabel `_M` mensimulasikan kelas yang diinisialisasi dengan satu variabel anggota `_VERSION` dan selanjutnya mendefinisikan fungsi anggota seperti `_M.set_timeout`. Dalam konstruktor `_M.new(self)`, kami mengembalikan tabel yang metatable-nya adalah `mt`, dan metamethode `__index` dari `mt` menunjuk ke `_M` sehingga tabel yang dikembalikan mensimulasikan instance dari kelas `_M`.

## Ringkasan

Baiklah, itu menyimpulkan konten utama untuk hari ini. Tabel dan metatable banyak digunakan dalam library `lua-resty-*` OpenResty dan proyek open source berbasis OpenResty. Saya harap pelajaran ini akan memudahkan Anda untuk membaca dan memahami kode sumber.

Ada fungsi standar lain di Lua selain tabel, yang akan kita pelajari bersama dalam pelajaran berikutnya.

Terakhir, saya ingin meninggalkan Anda dengan pertanyaan yang merenungkan. Mengapa library `lua-resty-mysql` meniru OO sebagai lapisan pembungkus? Silakan diskusikan pertanyaan ini di bagian komentar, dan silakan bagikan artikel ini dengan rekan dan teman Anda sehingga kita dapat berkomunikasi dan berkembang bersama.

Share article link

Share

Related Posts

[![Apa yang membuat OpenResty begitu istimewa](https://static.api7.ai/2022/10/14/634974e090ab4.png)](https://api7.ai/learning-center/openresty/openresty-getting-started)

## [Apa yang membuat OpenResty begitu istimewa](https://api7.ai/learning-center/openresty/openresty-getting-started)

[![Bagaimana jika ada konflik antara aturan Lua dan konfigurasi NGINX?](https://static.api7.ai/2022/10/14/634974e117ebd.png)](https://api7.ai/learning-center/openresty/conflict-between-lua-rules-and-nginx)

## [Bagaimana jika ada konflik antara aturan Lua dan konfigurasi NGINX?](https://api7.ai/learning-center/openresty/conflict-between-lua-rules-and-nginx)

Table of Contents

- [Fungsi library tabel](#fungsi-library-tabel)

  - [`table.getn` Mendapatkan jumlah elemen](#tablegetn-mendapatkan-jumlah-elemen)
  - [`table.remove` Menghapus elemen yang ditentukan](#tableremove-menghapus-elemen-yang-ditentukan)
  - [`table.concat` Fungsi penggabungan elemen](#tableconcat-fungsi-penggabungan-elemen)
  - [`table.insert` Menyisipkan elemen](#tableinsert-menyisipkan-elemen)

- [Fungsi ekstensi tabel LuaJIT](#fungsi-ekstensi-tabel-luajit)

  - [`table.new(narray, nhash)` Membuat tabel baru](#tablenewnarray-nhash-membuat-tabel-baru)
  - [`table.clear()` Mengosongkan tabel](#tableclear-mengosongkan-tabel)

- [Fungsi ekstensi tabel OpenResty](#fungsi-ekstensi-tabel-openresty)

- [Metatable](#metatable)

- [Pemrograman Berorientasi Objek](#pemrograman-berorientasi-objek)

- [Ringkasan](#ringkasan)
