第4章 - 面向对象编程
嗨,朋友!我是长安。
作为 Java 程序员,面向对象是你的老本行了。但 Lua 没有 class 关键字!那怎么实现类、继承、多态呢?答案是:Table + 元表(Metatable)。
🤔 元表是什么?
元表(Metatable)是 Lua 的一个核心机制,它允许你改变 Table 的行为。简单来说,元表就是一个"行为配置表",告诉 Lua 当你对一个 Table 做某些操作时该怎么处理。
local t = {}
local mt = {} -- 这是一个元表
-- 给 t 设置元表
setmetatable(t, mt)
-- 获取元表
print(getmetatable(t) == mt) -- true
🔑 __index 元方法
__index 是最重要的元方法,它决定了当你访问一个 Table 中不存在的键时,该去哪里找:
local defaults = {
color = "red",
size = 10,
visible = true
}
local mt = {
__index = defaults -- 找不到就去 defaults 里找
}
local button = setmetatable({}, mt)
button.text = "点击我"
print(button.text) -- 点击我(自己有,直接返回)
print(button.color) -- red(自己没有,去 defaults 找)
print(button.size) -- 10
print(button.visible) -- true
Java 对比
这就像 Java 的继承!子类访问属性时,先找自己的,找不到就去父类找。__index 就是 Lua 的"继承链"。
🏗️ 用 Table 实现类
基本类定义
-- 定义一个 "Animal" 类
local Animal = {}
Animal.__index = Animal -- 关键!让实例能找到类方法
-- 构造函数(类似 Java 的 constructor)
function Animal.new(name, sound)
local self = setmetatable({}, Animal)
self.name = name
self.sound = sound
return self
end
-- 方法
function Animal:speak()
print(self.name .. " says: " .. self.sound)
end
function Animal:getName()
return self.name
end
-- 使用
local dog = Animal.new("旺财", "汪汪汪")
local cat = Animal.new("咪咪", "喵喵喵")
dog:speak() -- 旺财 says: 汪汪汪
cat:speak() -- 咪咪 says: 喵喵喵
Java 等价
public class Animal {
private String name;
private String sound;
public Animal(String name, String sound) {
this.name = name;
this.sound = sound;
}
public void speak() {
System.out.println(name + " says: " + sound);
}
public String getName() {
return name;
}
}
Animal dog = new Animal("旺财", "汪汪汪");
Animal cat = new Animal("咪咪", "喵喵喵");
dog.speak(); // 旺财 says: 汪汪汪
原理图解
dog (实例 Table) Animal (类 Table)
┌──────────────────┐ ┌──────────────────┐
│ name = "旺财" │ │ __index = Animal │
│ sound = "汪汪汪" │ │ new = function │
│ [metatable] ─────┼────────►│ speak = function │
└──────────────────┘ │ getName = function│
└──────────────────┘
dog:speak()
1. dog 中找 speak → 没有
2. 通过元表的 __index 找到 Animal
3. Animal 中找到 speak → 调用!
🧬 继承
-- 父类:Animal
local Animal = {}
Animal.__index = Animal
function Animal.new(name, sound)
local self = setmetatable({}, Animal)
self.name = name
self.sound = sound
return self
end
function Animal:speak()
print(self.name .. " says: " .. self.sound)
end
function Animal:toString()
return "Animal: " .. self.name
end
-- 子类:Dog(继承 Animal)
local Dog = setmetatable({}, {__index = Animal}) -- Dog 继承 Animal
Dog.__index = Dog
function Dog.new(name, breed)
local self = Animal.new(name, "汪汪汪") -- 调用父类构造函数
setmetatable(self, Dog) -- 设置为 Dog 实例
self.breed = breed
return self
end
-- 子类新增方法
function Dog:fetch(item)
print(self.name .. " 捡回了 " .. item)
end
-- 重写父类方法(多态)
function Dog:toString()
return "Dog: " .. self.name .. " (" .. self.breed .. ")"
end
-- 使用
local myDog = Dog.new("旺财", "金毛")
myDog:speak() -- 旺财 says: 汪汪汪(继承的方法)
myDog:fetch("飞盘") -- 旺财 捡回了 飞盘(自己的方法)
print(myDog:toString()) -- Dog: 旺财 (金毛)(重写的方法)
Java 等价
public class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name, "汪汪汪"); // 调用父类构造函数
this.breed = breed;
}
public void fetch(String item) {
System.out.println(getName() + " 捡回了 " + item);
}
@Override
public String toString() {
return "Dog: " + getName() + " (" + breed + ")";
}
}
📊 常用元方法一览
| 元方法 | 触发条件 | Java 对比 |
|---|---|---|
__index | 访问不存在的键 | 继承/属性查找 |
__newindex | 给不存在的键赋值 | setter |
__tostring | tostring() 调用 | toString() |
__add | + 运算 | 运算符重载 |
__sub | - 运算 | 运算符重载 |
__mul | * 运算 | 运算符重载 |
__eq | == 比较 | equals() |
__lt | < 比较 | compareTo() |
__len | # 运算 | size() |
__call | 当作函数调用 | Callable |
__concat | .. 拼接 | — |
元方法示例
-- 实现一个向量类(演示运算符重载)
local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({x = x, y = y}, Vector)
end
-- 重载 + 运算符
function Vector.__add(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end
-- 重载 tostring
function Vector.__tostring(v)
return string.format("(%d, %d)", v.x, v.y)
end
-- 重载 == 运算符
function Vector.__eq(a, b)
return a.x == b.x and a.y == b.y
end
-- 使用
local v1 = Vector.new(1, 2)
local v2 = Vector.new(3, 4)
local v3 = v1 + v2 -- 触发 __add
print(tostring(v3)) -- (4, 6)
print(v1 == Vector.new(1, 2)) -- true
🔧 封装:getter/setter
-- 用 __newindex 实现属性校验
local Person = {}
Person.__index = Person
function Person.new(name, age)
local self = setmetatable({}, Person)
self._name = name -- 用下划线前缀表示"私有"
self._age = age
return self
end
function Person:getName() return self._name end
function Person:getAge() return self._age end
function Person:setAge(age)
if age < 0 or age > 150 then
error("年龄不合法: " .. age)
end
self._age = age
end
-- 使用
local p = Person.new("长安", 25)
print(p:getName()) -- 长安
print(p:getAge()) -- 25
p:setAge(26)
print(p:getAge()) -- 26
-- p:setAge(-1) -- 错误:年龄不合法: -1
📝 小结
- Lua 用 Table + 元表 实现面向对象
__index元方法是继承的核心——实例找不到属性就去类(元表)里找- 构造函数用
setmetatable({}, ClassName)创建实例 - 方法用冒号定义和调用,自动传入
self - 元方法可以重载运算符(
__add、__eq、__tostring等) - 虽然没有
private关键字,但可以用闭包或命名约定(下划线前缀)实现封装
➡️ 下一步
面向对象搞定了!下一章是重头戏——SpringBoot + Lua 整合实战,教你在 Java 项目中使用 Lua!
💪 练习题
- 创建一个
Rectangle类,包含width、height属性和area()方法。 - 创建一个
Square类继承Rectangle,构造函数只接收一个边长参数。 - 给
Rectangle添加__tostring元方法。 - 解释
__index元方法的作用。
答案提示
function Rectangle.new(w,h) return setmetatable({w=w,h=h}, Rectangle) end+function Rectangle:area() return self.w*self.h endfunction Square.new(s) return Rectangle.new(s,s) end+ 设置 Square 的元表指向 Rectanglefunction Rectangle.__tostring(r) return string.format("Rect(%dx%d)", r.w, r.h) end- 当访问 Table 中不存在的键时,Lua 会去元表的
__index指向的 Table(或函数)中查找
