Java高级语法之反射

什么是反射

java.lang包提供java语言程序设计的基础类,在lang包下存在一个子包:reflect,与反射相关的APIs均在此处;

官方对reflect包的介绍如下:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.

The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

java.lang.reflect官方介绍

简单来说,反射机制就像类对照平静的湖面,湖面映射出类的字段、方法、构造函数等信息;反射机制不仅可以看到类信息,还能针对字段、方法等做出相应的操作。

测试实体类创建

为方便解释说明,首先创建一个实体类,用于测试使用

package cn.byuan.entity;

/**
* 学生实体类
*
* @author byuan
* @date 2022-01-25
*/
public class Student { /**
* 学生学号, 公共变量, 默认值: defaultStudentNo
* */
public String studentNo = "defaultStudentNo"; /**
* 学生姓名, 公共变量, 默认值: defaultStudentName
* */
public String studentName = "defaultStudentName"; /**
* 学生性别, 私有变量, 默认值: defaultStudentSex
* */
private String studentSex = "defaultStudentSex"; /**
* 学生年龄, 私有变量, 默认值: 0
* */
private Integer studentAge = 0; /**
* 公有无参构造方法
* */
public Student() { } /**
* 公有满参构造方法
* */
public Student(String studentNo, String studentName, String studentSex, Integer studentAge) {
this.studentNo = studentNo;
this.studentName = studentName;
this.studentSex = studentSex;
this.studentAge = studentAge;
} /**
* 私有构造方法
* */
private Student(String studentSex, Integer studentAge) {
this.studentSex = studentSex;
this.studentAge = studentAge;
} public String getStudentNo() {
return studentNo;
} public Student setStudentNo(String studentNo) {
this.studentNo = studentNo;
return this;
} public String getStudentName() {
return studentName;
} public Student setStudentName(String studentName) {
this.studentName = studentName;
return this;
} public String getStudentSex() {
return studentSex;
} public Student setStudentSex(String studentSex) {
this.studentSex = studentSex;
return this;
} public Integer getStudentAge() {
return studentAge;
} public Student setStudentAge(Integer studentAge) {
this.studentAge = studentAge;
return this;
} @Override
public String toString() {
return "Student{" +
"studentNo = " + this.studentNo + ", " +
"studentName = " + this.studentName + ", " +
"studentSex = " + this.studentSex + ", " +
"studentAge = " + this.studentAge +"}";
} /**
* 学生类说话方法
* */
private String speak(String message) {
return this.studentName + " : " + message;
} }

反射中的几个重要类及方法

在了解反射前,先来梳理下一个类(Class)本身中包含了哪些内容。

  1. 字段,字段又由修饰符、字段类型、字段名称、对应值等部分组成
  2. 构造方法,构造方法可简单分为无参与有参两大类
  3. 非构造方法,又称普通方法,方法由修饰符、返回值类型、方法名、形参表、方法体等部分构成

本文所论述的反射中几个重要类及其对应方法正基于以上内容

Class类

Class类代表了类本身,类本身包含字段,构造方法,非构造方法等内容,因此使用反射的第一步就是获取对象所对应的Class类。

仅就使用反射而言,我们需着重了解Class类的获取方式,下面给出实例

Class类实例

package cn.byuan.example;

import cn.byuan.entity.Student;

/**
* 获取 Class 的几种方式
*
* @author byuan
* @date 2022-01-25
*/
public class GetClassExample { public static void main(String[] args) throws ClassNotFoundException { // 获取 class 方式一: 通过类的全路径字符串获取 Class 对象
Class getClassExample1 = Class.forName("cn.byuan.entity.Student"); // 获取 class 方式二: 通过类名直接获取
Class getClassExample2 = Student.class; // 获取 class 方式三: 通过已创建的对象获取对应 Class
Student student1 = new Student();
Class getClassExample3 = student1.getClass(); } }

Field类的获取与常用属性

Class类为我们提供了两个方法用以获取Field类:

  1. getDeclaredFields: 获取所有声明的字段(包括公有字段和私有字段)
  2. getFields: 仅可获取公有字段

Field类代表了类中的属性字段,类中属性字段可分为两种,公有字段(public)与私有字段(private);

每个字段又具有四个属性:修饰符,字段类型,字段名称,对应值;Field也自然提供了对应方法对这四种属性进行获取:

  1. getModifiers():获取字段修饰符相加值,想要获取明确标识需要通过Modifier常量的toString方法对相加值进行解码
  2. getType():获取字段类型
  3. getName():获取字段名称
  4. get(Object):获取字段对应值

下面给出实例:

Field类实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier; /**
* 获取类字段的几种方式
*
* @author byuan
* @date 2021-01-25
*/
public class GetFieldExample { public static void main(String[] args) throws IllegalAccessException { Class studentClass = Student.class; // 获取类字段的两个方法: getDeclaredFields, getFields // 1. getDeclaredFields: 获取所有声明的字段(包括公有字段和私有字段)
Field[] declaredFieldArray = studentClass.getDeclaredFields();
printFieldInformation(declaredFieldArray); // 2. getFields: 仅可获取公有字段
Field[] fieldArray = studentClass.getFields();
printFieldInformation(fieldArray); // 获取字段对应值
Student student = new Student()
.setStudentSex("女")
.setStudentAge(18);
printFieldValue(student); } /**
* 打印类字段信息
*
* @param fieldArray 类字段对象列表
* */
public static void printFieldInformation(Field[] fieldArray) { for (Field fieldPart : fieldArray) { System.out.println("直接打印类字段对象: " + fieldPart); // 获取字段修饰符
String fieldModifier = Modifier.toString(fieldPart.getModifiers()); // 获取字段类型
String fieldType = fieldPart.getType().getName(); // 获取字段名称
String fieldName = fieldPart.getName(); System.out.println(fieldModifier + " " + fieldType + " " + fieldName); } System.out.println(); } /**
* 打印类字段属性
*
* @param t 泛型对象
* */
private static <T> void printFieldValue(T t) throws IllegalAccessException {
Field[] fieldValueArray = t
.getClass()
.getDeclaredFields(); for (Field fieldValuePart : fieldValueArray) {
// 对于有可能存在的 private 字段取消语言访问检查
fieldValuePart.setAccessible(true); // 字段名称
String filedName = fieldValuePart.getName(); // 字段对应值
String fieldValue = fieldValuePart.get(t).toString(); System.out.println(filedName + " = " + fieldValue); } } }

运行截图

Constructor类获取与常用属性

Class类为我们提供了两个方法用以获取Constructor类:

  1. getDeclaredConstructors():获取所有构造方法
  2. getConstructors():仅可获取公有构造方法

对于构造器,可简单将其分为两大类,无参构造器与带参构造器,构造器也是方法的一种,因此对于任意一构造器都由修饰符,方法名,形参表,方法体四部分组成;Constructor自然也为其组成部分提供了对应方法:

  1. 与字段类似,Constructors同样提供了getModifiers()方法:获取构造器修饰符相加值,想要获取明确标识需要通过Modifier常量的toString方法进行解码
  2. getName():获取构造器名称
  3. getParameterTypes():获取构造器参数列表

下面给出实例

Constructor类实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier; /**
* 获取构造方法的几种方式
*
* @author byuan
* @date 2022-01-25
*/
public class GetConstructorsExample { public static void main(String[] args) { Class studentClass = Student.class; // 获取类构造器的两个方法: getDeclaredConstructors, getConstructors // 1. getDeclaredConstructors: 获取所有构造方法
Constructor[] declaredConstructorArray = studentClass.getDeclaredConstructors();
printConstructorInformation(declaredConstructorArray); // 2. getConstructors, 仅可获取公有构造方法
Constructor[] constructorArray = studentClass.getConstructors();
printConstructorInformation(constructorArray); } /**
* 打印构造器信息
*
* @param constructorArray 构造器对象列表
* */
public static void printConstructorInformation(Constructor[] constructorArray) { for (Constructor constructorPart : constructorArray) { System.out.println("直接打印构造器对象: " + constructorPart); // 获取构造器修饰符
String constructorModifier = Modifier.toString(constructorPart.getModifiers()); // 获取构造器名称
String constructorName = constructorPart.getName(); // 获取构造器参数列表
Class[] constructorParameterArray = constructorPart.getParameterTypes(); // 打印构造器参数列表
System.out.print(constructorModifier + " " + constructorName + "(");
for (Class constructorParameterPart : constructorParameterArray) {
System.out.print(constructorParameterPart.getName() + " ");
}
System.out.println(")"); } System.out.println(); } }

运行截图

利用Constructor类实例化对象

一般而言,我们关心的不只是获取到对象构造器的具体信息,更重要的是如何利用反射实例化对象,针对对象实例化,Constructor提供了两种类型的方法:

  1. getConstructor(Class<?>... parameterTypes):获取指定形参表的构造方法,可借助其获取无参/指定参数的构造方法;

  2. newInstance(Object ... initargs):通过形参表传递实例化对象参数,与getConstructor配合使用;

    注意:Class也存在newInstance()方法,但仅能用来调用类的无参构造器

Constructor实例化对象实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; /**
* 构造器调用实例
*
* @author byuan
* @date 2022-01-26
*/
public class ConstructorsInvokeExample { public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Class studentClass = Student.class; // Class 类 的 newInstance 方法
studentClass.newInstance(); // 1. 调用公有无参构造器
Object object1 = studentClass
.getConstructor()
.newInstance();
System.out.println(object1.getClass()); // 2. 调用公有满参构造器
Constructor studentConstructorFull = studentClass
.getConstructor(String.class, String.class, String.class, Integer.class); Object object2 = studentConstructorFull
.newInstance("2022001", "赵一", "男", 18);
System.out.println(object2); // 3. 调用私有构造器
Constructor studentConstructorPrivate = studentClass
.getDeclaredConstructor(String.class, Integer.class); // 私有构造器需将 accessible 设置为 true, 取消语言访问检查
studentConstructorPrivate.setAccessible(true);
Object object3 = studentConstructorPrivate
.newInstance("女", 19);
System.out.println(object3); } }

运行截图

Method类的获取与常用属性

Class类为我们提供了两个方法用以获取Method类:

  1. getDeclaredMethods():获取所有非构造方法
  2. getMethods():仅可获取公有非构造方法

对于任意方法都可分为修饰符,方法名,形参表,方法体四部分;Method为其组成部分提供了对应方法:

  1. getModifiers():获取方法修饰符相加值,想要获取明确标识需要通过Modifier常量的toString方法进行解码
  2. getName():获取方法名称
  3. getParameterTypes():获取方法形参表

Method类实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier; /**
* 获取非构造方法的几种方式
*
* @author byuan
* @date 2022-01-26
*/
public class GetMethodExample { public static void main(String[] args) { Class studentClass = Student.class; // 获取非构造方法的两个方法: getDeclaredMethods, getMethods // 1. getDeclaredMethods: 获取所有非构造方法
Method[] declaredMethodArray = studentClass.getDeclaredMethods();
printMethodInformation(declaredMethodArray); // 2. getMethods, 仅可获取公有非构造方法
Method[] methodArray = studentClass.getMethods();
printMethodInformation(methodArray); } /**
* 打印非构造器方法信息
*
* @param methodArray 构造器对象列表
* */
public static void printMethodInformation(Method[] methodArray) { for (Method methodPart : methodArray) { System.out.println("直接打印非构造方法对象: " + methodArray); // 获取非构造器方法修饰符
String methodModifier = Modifier.toString(methodPart.getModifiers()); // 获取非构造器方法名称
String methodName = methodPart.getName(); String methodReturnType = methodPart.getReturnType().getName(); // 获取非构造方法参数列表
Class[] constructorParameterArray = methodPart.getParameterTypes(); // 打印非构造方法参数列表
System.out.print(methodModifier + " " + methodReturnType + " " + methodName + "(");
for (Class methodParameterPart : constructorParameterArray) {
System.out.print(methodParameterPart.getName() + " ");
}
System.out.println(")"); } System.out.println(); } }

运行截图

利用Method调用非构造方法

与利用Constructor实例化对象相似,Method同样需要两个方法用以调用非构造方法

  1. getDeclaredMethod(方法名, 形参表数组): 获取所有构造方法
  2. invoke(对象实例, 参数数组):方法调用

Method调用非构造方法实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; /**
* 非构造器方法调用实例
*
* @author byuan
* @date 2022-01-26
*/
public class MethodInvokeExample { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { Class studentClass = Student.class; // 对于私有的非构造方法, 需要使用 getDeclaredMethod 进行获取
// getDeclaredMethod(方法名, 形参表数组)
Method studentSpeakMethod = studentClass.getDeclaredMethod("speak", new Class[]{String.class}); // 取消语言访问检查
studentSpeakMethod.setAccessible(true); // invoke(对象实例, 参数数组)
Object object = studentSpeakMethod.invoke(studentClass.newInstance(), "Hello, world"); System.out.println(object); } }

运行截图

实例:利用反射机制编写对象拷贝工具类

在实际项目中,我们经常会遇到POJO与VO等类型对象进行相互转换的情况,如果直接进行转换则会使用大量的样板代码,为消除这样的代码,我们可以写一个简单的对象拷贝工具类进行解决;

业务分析

通过反射获取源对象Field数组,并利用Field类提供的set/get方法实现同名属性的拷贝;

创建两个具有相同属性的对象:Student与StudentOut

Student类
package cn.byuan.entity;

/**
* 学生实体类
*
* @author byuan
* @date 2022-01-25
*/
public class Student { /**
* 学生学号, 公共变量, 默认值: defaultStudentNo
* */
public String studentNo = "defaultStudentNo"; /**
* 学生姓名, 公共变量, 默认值: defaultStudentName
* */
public String studentName = "defaultStudentName"; /**
* 学生性别, 私有变量, 默认值: defaultStudentSex
* */
private String studentSex = "defaultStudentSex"; /**
* 学生年龄, 私有变量, 默认值: 0
* */
private Integer studentAge = 0; /**
* 公有无参构造方法
* */
public Student() { } /**
* 公有满参构造方法
* */
public Student(String studentNo, String studentName, String studentSex, Integer studentAge) {
this.studentNo = studentNo;
this.studentName = studentName;
this.studentSex = studentSex;
this.studentAge = studentAge;
} /**
* 私有构造方法
* */
private Student(String studentSex, Integer studentAge) {
this.studentSex = studentSex;
this.studentAge = studentAge;
} public String getStudentNo() {
return studentNo;
} public Student setStudentNo(String studentNo) {
this.studentNo = studentNo;
return this;
} public String getStudentName() {
return studentName;
} public Student setStudentName(String studentName) {
this.studentName = studentName;
return this;
} public String getStudentSex() {
return studentSex;
} public Student setStudentSex(String studentSex) {
this.studentSex = studentSex;
return this;
} public Integer getStudentAge() {
return studentAge;
} public Student setStudentAge(Integer studentAge) {
this.studentAge = studentAge;
return this;
} @Override
public String toString() {
return "Student{" +
"studentNo = " + this.studentNo + ", " +
"studentName = " + this.studentName + ", " +
"studentSex = " + this.studentSex + ", " +
"studentAge = " + this.studentAge +"}";
} /**
* 学生类说话方法
* */
private String speak(String message) {
return this.studentName + " : " + message;
} }
StudentOut类
package cn.byuan.api.out;

import cn.byuan.entity.Student;

/**
* 学生类出参
*
* @author byuan
* @date 2022-01-26
*/
public class StudentOut {
/**
* 学生学号, 公共变量
* */
private String studentNo; /**
* 学生姓名, 公共变量
* */
private String studentName; /**
* 学生性别, 私有变量
* */
private String studentSex; /**
* 学生年龄, 私有变量
* */
private Integer studentAge; public String getStudentNo() {
return studentNo;
} public StudentOut setStudentNo(String studentNo) {
this.studentNo = studentNo;
return this;
} public String getStudentName() {
return studentName;
} public StudentOut setStudentName(String studentName) {
this.studentName = studentName;
return this;
} public String getStudentSex() {
return studentSex;
} public StudentOut setStudentSex(String studentSex) {
this.studentSex = studentSex;
return this;
} public Integer getStudentAge() {
return studentAge;
} public StudentOut setStudentAge(Integer studentAge) {
this.studentAge = studentAge;
return this;
} @Override
public String toString() {
return "StudentOut{" +
"studentNo = " + this.studentNo + ", " +
"studentName = " + this.studentName + ", " +
"studentSex = " + this.studentSex + ", " +
"studentAge = " + this.studentAge +"}";
}
}
编写工具类
package cn.byuan.util;

import cn.byuan.api.out.StudentOut;
import cn.byuan.entity.Student; import java.lang.reflect.Field; /**
* 对象属性拷贝工具类
*
* @author byuan
* @date 2022-01-26
*/
public class BeanUtil { /**
* 对象拷贝工具
*
* @param sourceObject 源对象
* @param destClass 目的对象对应 Class
*
* @return 拷贝完毕后对象
* */
public static <T> T copyObject(Object sourceObject, Class<T> destClass) { if (sourceObject == null) {
return null; } try { T destObject = destClass.newInstance(); copyField(sourceObject, destObject); return destObject; } catch (InstantiationException e) {
e.printStackTrace(); } catch (IllegalAccessException e) {
e.printStackTrace(); } catch (Exception e) {
e.printStackTrace(); } return null; } /**
* 对象属性拷贝工具
*
* @param sourceObject 源对象
* @param destObject 目的对象
* */
private static void copyField(Object sourceObject, Object destObject) { if (sourceObject == null || destObject == null) {
return;
} // 获取源对象所有字段
Field[] sourceFieldArray = sourceObject.getClass().getDeclaredFields();
for (Field sourceFieldPart : sourceFieldArray) {
// 取消语言访问检查
sourceFieldPart.setAccessible(true); String sourceFieldName = sourceFieldPart.getName(); try {
// 根据属性名称获取目标对象对应类字段
Field destField = destObject
.getClass()
.getDeclaredField(sourceFieldName); destField.setAccessible(true); destField.set(destObject, sourceFieldPart.get(sourceObject)); } catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} } public static void main(String[] args) { Student student = new Student("2022001", "赵一", "男", 18); StudentOut studentOut = copyObject(student, StudentOut.class); System.out.println(studentOut); } }

Java高级语法之反射的更多相关文章

  1. 从一知半解到揭晓Java高级语法—泛型

    目录 前言 探讨 泛型解决了什么问题? 扩展 引入泛型 什么是泛型? 泛型类 泛型接口 泛型方法 类型擦除 擦除的问题 边界 通配符 上界通配符 下界通配符 通配符和向上转型 泛型约束 实践总结 泛型 ...

  2. Java基础学习笔记二十三 Java核心语法之反射

    类加载器 类的加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,链接,初始化三步来实现对这个类进行初始化. 加载就是指将class文件读入内存,并为之创建一个Class对象.任 ...

  3. Java高级特性之反射学习总结

    老规矩我们还是先提出几个问题,一门技术必然要能解决一定的问题,才有去学习掌握它的价值 一. 什么是反射? 二.反射能做什么? 一. 什么是反射? 用在Java身上指的是我们可以于运行时加载.探知.使用 ...

  4. Java高级特性之反射

    老规矩我们还是先提出几个问题,一门技术必然要能解决一定的问题,才有去学习掌握它的价值 一. 什么是反射? 二.反射能做什么? 一. 什么是反射? 用在Java身上指的是我们可以于运行时加载.探知.使用 ...

  5. JAVA高级语法

    高级语法 第三章:面向对象和高级语法 实例化: 不实例化,就是一个空指针 注意,声明和实例化是两个过程.声明的过程是不分配内存空间的,只有实例化才会真正分配空间 对变量的分类 实例变量只有实例化之后才 ...

  6. Java 高级基础——反射

    Java 高级基础--反射 反射的意义:Java 强类型语言,但是我们在运行时有了解.修改信息的需求,包括类信息.成员信息以及数组信息. 基本类型与引用类型 基本类型,(固定的 8 种) 整数:byt ...

  7. java高级——反射

    慕课网<反射——Java高级开发必须懂的>听课笔记 一.class类的使用 class ClassDemo { public static void main(String[] args) ...

  8. Java高级特性——反射机制(第二篇)

    在Java高级特性——反射机制(第一篇)中,写了很多反射的实例,可能对于Class的了解还是有点迷糊,那么我们试着从内存角度去分析一下. Java内存 从上图可以看出,Java将内存分为堆.栈.方法区 ...

  9. Java高级特性 第5节 序列化和、反射机制

    一.序列化 1.序列化概述 在实际开发中,经常需要将对象的信息保存到磁盘中便于检索,但通过前面输入输出流的方法逐一对对象的属性信息进行操作,很繁琐并容易出错,而序列化提供了轻松解决这个问题的快捷方法. ...

随机推荐

  1. 【LeetCode】276. Paint Fence 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 动态规划 日期 题目地址:https://leetco ...

  2. 【LeetCode】950. Reveal Cards In Increasing Order 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 模拟 日期 题目地址:https://leetcod ...

  3. 【LeetCode】99. Recover Binary Search Tree 解题报告(Python)

    [LeetCode]99. Recover Binary Search Tree 解题报告(Python) 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/p ...

  4. hdu 4503 湫湫系列故事——植树节(组合概率)

    这是一道求组合的题.中文题面应该能看懂,废话不多说下面来说说这道题. 可以选的总组合数是Ck3 那么选到3个人的关系都相同,要么都认识,要么都不认识.可以重反面来考虑,就是求三个人的关系不都相同. 那 ...

  5. oralce索引中INDEX SKIP SCAN 和 INDEX RANGE SCAN区别

    INDEX SKIP SCAN 当表中建立有复合索引的时候,查询时,除复合索引第一列外,别的列作为条件时,且优化器模式为CBO,这个时候查询可能会用到INDEX SKIP SCAN skip scan ...

  6. ios离线打包报错Showing Recent Messages :-1: HBuilder has conflicting provisioning settings. HBuilder is automatically signed for development, but a conflicting code signing identity iPhone Distribution has

    1.解决方案找到项目工程文件右击->显示包内容->双击project.pbxproj->搜索distribution改写成Developer

  7. 使用 jQuery 实现页面背景色的更换,通过下拉框选择对应的颜色,页面背景会随着选中的颜色进行更换

    查看本章节 查看作业目录 需求说明: 使用 jQuery 实现页面背景色的更换,通过下拉框选择对应的颜色,页面背景会随着选中的颜色进行更换 实现思路: 在页面中添加 <select> 标签 ...

  8. Ranger开源流水线docker化实践案例

    1.背景 开发部门决定在Apache Ranger开源社区贡献代码,目标是个人国内排名Top1,世界排名Top2,并且在已经成为Ranger项目的Committer情况下,争取成为Ranger项目的P ...

  9. EntityFrameworkCore数据迁移(二)

    接上一篇 EntityFrameworkCore数据迁移(一) 其实上一篇该写的都已经写完了,但是后来又想到两个问题,想了想还是也写出来吧 问题一 上一篇介绍的迁移过程,都是通过在程序包管理器控制台使 ...

  10. openmesh - impl - Remove Duplicated Vertices

    openmesh - impl - Remove Duplicated Vertices 关于openmesh元素删除实现的介绍参见:openmesh - src - trimesh delete a ...