diff --git a/src/main/java/org/redkale/source/AbstractDataSource.java b/src/main/java/org/redkale/source/AbstractDataSource.java index 55d109710..afcd1ff26 100644 --- a/src/main/java/org/redkale/source/AbstractDataSource.java +++ b/src/main/java/org/redkale/source/AbstractDataSource.java @@ -338,35 +338,6 @@ public abstract class AbstractDataSource extends AbstractService implements Data return node.createPredicate(cache); } - /** - * 根据ResultSet获取对象 - * - * @param 泛型 - * @param info EntityInfo - * @param sels 过滤字段 - * @param row ResultSet - * - * @return 对象 - */ - protected T getEntityValue(EntityInfo info, final SelectColumn sels, final EntityInfo.DataResultSetRow row) { - return sels == null ? info.getFullEntityValue(row) : info.getEntityValue(sels, row); - } - - /** - * 根据ResultSet获取对象 - * - * @param 泛型 - * @param info EntityInfo - * @param constructorAttrs 构造函数字段 - * @param unconstructorAttrs 非构造函数字段 - * @param row ResultSet - * - * @return 对象 - */ - protected T getEntityValue(EntityInfo info, final Attribute[] constructorAttrs, final Attribute[] unconstructorAttrs, final EntityInfo.DataResultSetRow row) { - return info.getEntityValue(constructorAttrs, unconstructorAttrs, row); - } - /** * 根据翻页参数构建排序SQL * diff --git a/src/main/java/org/redkale/source/AbstractDataSqlSource.java b/src/main/java/org/redkale/source/AbstractDataSqlSource.java index 2a38091c6..5bc454573 100644 --- a/src/main/java/org/redkale/source/AbstractDataSqlSource.java +++ b/src/main/java/org/redkale/source/AbstractDataSqlSource.java @@ -330,7 +330,7 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement StringBuilder sb = new StringBuilder(); sb.append("CREATE TABLE IF NOT EXISTS `").append(info.getOriginTable()).append("`(\n"); EntityColumn primary = null; - T one = info.constructorAttributes == null ? info.getCreator().create() : null; + T one = !info.getBuilder().hasConstructorAttribute() ? info.getCreator().create() : null; for (EntityColumn column : info.getDDLColumns()) { if (column.primary) { primary = column; @@ -458,7 +458,7 @@ public abstract class AbstractDataSqlSource extends AbstractDataSource implement StringBuilder sb = new StringBuilder(); sb.append("CREATE TABLE IF NOT EXISTS ").append(info.getOriginTable()).append("(\n"); EntityColumn primary = null; - T one = info.constructorAttributes == null ? info.getCreator().create() : null; + T one = !info.getBuilder().hasConstructorAttribute() ? info.getCreator().create() : null; List comments = new ArrayList<>(); if (table != null && !table.comment().isEmpty()) { comments.add("COMMENT ON TABLE " + info.getOriginTable() + " IS '" + table.comment().replace('\'', '"') + "'"); diff --git a/src/main/java/org/redkale/source/DataJdbcSource.java b/src/main/java/org/redkale/source/DataJdbcSource.java index fdbfd46a0..e5d871599 100644 --- a/src/main/java/org/redkale/source/DataJdbcSource.java +++ b/src/main/java/org/redkale/source/DataJdbcSource.java @@ -1806,7 +1806,7 @@ public class DataJdbcSource extends AbstractDataSqlSource { prestmt = conn.prepareQueryStatement(prepareSQL); prestmt.setObject(1, pk); final DataResultSet set = createDataResultSet(info, prestmt.executeQuery()); - T rs = set.next() ? info.getFullEntityValue(set) : null; + T rs = set.next() ? info.getBuilder().getFullEntityValue(set) : null; set.close(); conn.offerQueryStatement(prestmt); slowLog(s, prepareSQL); @@ -1838,7 +1838,7 @@ public class DataJdbcSource extends AbstractDataSqlSource { prestmt = conn.prepareQueryStatement(sql); prestmt.setFetchSize(1); final DataResultSet set = createDataResultSet(info, prestmt.executeQuery()); - T rs = set.next() ? selects == null ? info.getFullEntityValue(set) : info.getEntityValue(selects, set) : null; + T rs = set.next() ? selects == null ? info.getBuilder().getFullEntityValue(set) : info.getBuilder().getEntityValue(selects, set) : null; set.close(); conn.offerQueryStatement(prestmt); slowLog(s, sql); @@ -1884,7 +1884,7 @@ public class DataJdbcSource extends AbstractDataSqlSource { prestmt = conn.prepareQueryStatement(sql); prestmt.setFetchSize(1); final DataResultSet set = createDataResultSet(info, prestmt.executeQuery()); - T rs = set.next() ? selects == null ? info.getFullEntityValue(set) : info.getEntityValue(selects, set) : null; + T rs = set.next() ? selects == null ? info.getBuilder().getFullEntityValue(set) : info.getBuilder().getEntityValue(selects, set) : null; set.close(); conn.offerQueryStatement(prestmt); slowLog(s, sql); @@ -1920,7 +1920,7 @@ public class DataJdbcSource extends AbstractDataSqlSource { final DataResultSet set = createDataResultSet(info, prestmt.executeQuery()); Serializable val = defValue; if (set.next()) { - val = info.getFieldValue(attr, set, 1); + val = info.getBuilder().getFieldValue(attr, set, 1); } set.close(); conn.offerQueryStatement(prestmt); @@ -1969,7 +1969,7 @@ public class DataJdbcSource extends AbstractDataSqlSource { final DataResultSet set = createDataResultSet(info, prestmt.executeQuery()); Serializable val = defValue; if (set.next()) { - val = info.getFieldValue(attr, set, 1); + val = info.getBuilder().getFieldValue(attr, set, 1); } set.close(); conn.offerQueryStatement(prestmt); @@ -2275,10 +2275,11 @@ public class DataJdbcSource extends AbstractDataSqlSource { final int limit = flipper == null || flipper.getLimit() < 1 ? Integer.MAX_VALUE : flipper.getLimit(); int i = 0; final DataResultSet rr = createDataResultSet(info, set); + EntityBuilder builder = info.getBuilder(); if (sels == null) { while (set.next()) { i++; - list.add(info.getFullEntityValue(rr)); + list.add(builder.getFullEntityValue(rr)); if (limit <= i) { break; } @@ -2286,7 +2287,7 @@ public class DataJdbcSource extends AbstractDataSqlSource { } else { while (set.next()) { i++; - list.add(info.getEntityValue(sels, rr)); + list.add(builder.getEntityValue(sels, rr)); if (limit <= i) { break; } diff --git a/src/main/java/org/redkale/source/EntityBuilder.java b/src/main/java/org/redkale/source/EntityBuilder.java new file mode 100644 index 000000000..d43a00d85 --- /dev/null +++ b/src/main/java/org/redkale/source/EntityBuilder.java @@ -0,0 +1,312 @@ +/* + * + */ +package org.redkale.source; + +import java.io.Serializable; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import org.redkale.annotation.Nullable; +import org.redkale.persistence.*; +import org.redkale.util.*; + +/** + * 可以是实体类,也可以是查询结果的JavaBean类 + * + * @author zhangjx + * @since 2.8.0 + */ +public class EntityBuilder { + + private static final ConcurrentHashMap cacheMap = new ConcurrentHashMap<>(); + + //Entity构建器 + private final Creator creator; + + //Entity构建器参数 + @Nullable + private final String[] constructorParameters; + + //key是field的name, value是Column的别名,即数据库表的字段名 + //只有field.name 与 Column.name不同才存放在aliasmap里. + @Nullable + private final Map aliasmap; + + //Entity构建器参数Attribute, 数组个数与constructorParameters相同 + @Nullable + private final Attribute[] constructorAttributes; + + //Entity构建器参数Attribute + @Nullable + private final Attribute[] unconstructorAttributes; + + private final HashMap> attributeMap; + + //数据库中所有字段, 顺序必须与querySqlColumns、querySqlColumnSequence一致 + private final Attribute[] attributes; + + EntityBuilder(Creator creator, + Map aliasmap, String[] constructorParameters, + Attribute[] constructorAttributes, + Attribute[] unconstructorAttributes, + HashMap> attributeMap, + Attribute[] queryAttributes) { + this.creator = creator; + this.aliasmap = aliasmap; + this.constructorParameters = constructorParameters; + this.constructorAttributes = constructorAttributes; + this.unconstructorAttributes = unconstructorAttributes; + this.attributeMap = attributeMap; + this.attributes = queryAttributes; + } + + public static EntityBuilder load(Class type) { + return cacheMap.computeIfAbsent(type, t -> create(t)); + } + + private static EntityBuilder create(Class type) { + Creator creator = Creator.create(type); + String[] constructorParameters = null; + try { + Method cm = creator.getClass().getMethod("create", Object[].class); + RedkaleClassLoader.putReflectionPublicMethods(creator.getClass().getName()); + RedkaleClassLoader.putReflectionMethod(creator.getClass().getName(), cm); + org.redkale.annotation.ConstructorParameters cp = cm.getAnnotation(org.redkale.annotation.ConstructorParameters.class); + if (cp != null && cp.value().length > 0) { + constructorParameters = cp.value(); + } else { + org.redkale.util.ConstructorParameters cp2 = cm.getAnnotation(org.redkale.util.ConstructorParameters.class); + if (cp2 != null && cp2.value().length > 0) { + constructorParameters = cp2.value(); + } + } + } catch (Exception e) { + throw new SourceException(type + " cannot find ConstructorParameters Creator"); + } + Class cltmp = type; + Map aliasmap = null; + Set fields = new HashSet<>(); + List> queryAttrs = new ArrayList<>(); + HashMap> attributeMap = new HashMap<>(); + do { + RedkaleClassLoader.putReflectionDeclaredFields(cltmp.getName()); + for (Field field : cltmp.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + if (Modifier.isFinal(field.getModifiers())) { + continue; + } + if (field.getAnnotation(Transient.class) != null) { + continue; + } + if (field.getAnnotation(javax.persistence.Transient.class) != null) { + continue; + } + if (fields.contains(field.getName())) { + continue; + } + final String fieldName = field.getName(); + final Column col = field.getAnnotation(Column.class); + final String sqlField = col == null || col.name().isEmpty() ? fieldName : col.name(); + if (!fieldName.equals(sqlField)) { + if (aliasmap == null) { + aliasmap = new HashMap<>(); + } + aliasmap.put(fieldName, sqlField); + } + Attribute attr; + try { + attr = Attribute.create(type, cltmp, field); + } catch (RuntimeException e) { + continue; + } + RedkaleClassLoader.putReflectionField(cltmp.getName(), field); + queryAttrs.add(attr); + fields.add(fieldName); + attributeMap.put(fieldName, attr); + } + } while ((cltmp = cltmp.getSuperclass()) != Object.class); + Attribute[] constructorAttributes; + Attribute[] unconstructorAttributes; + if (constructorParameters == null) { + constructorAttributes = null; + unconstructorAttributes = null; + } else { + constructorAttributes = new Attribute[constructorParameters.length]; + List> unconstructorAttrs = new ArrayList<>(); + List newquerycols1 = new ArrayList<>(); + List newquerycols2 = new ArrayList<>(); + for (Attribute attr : new ArrayList<>(queryAttrs)) { + int pos = -1; + for (int i = 0; i < constructorParameters.length; i++) { + if (attr.field().equals(constructorParameters[i])) { + pos = i; + break; + } + } + if (pos >= 0) { + constructorAttributes[pos] = attr; + } else { + unconstructorAttrs.add(attr); + } + } + unconstructorAttributes = unconstructorAttrs.toArray(new Attribute[unconstructorAttrs.size()]); + newquerycols1.addAll(newquerycols2); + List> newqueryattrs = new ArrayList<>(); + newqueryattrs.addAll(List.of(constructorAttributes)); + newqueryattrs.addAll(unconstructorAttrs); + queryAttrs = newqueryattrs; + } + return new EntityBuilder<>(creator, aliasmap, constructorParameters, constructorAttributes, + unconstructorAttributes, attributeMap, queryAttrs.toArray(new Attribute[queryAttrs.size()])); + } + + /** + * 将一行的ResultSet组装成一个Entity对象 + * + * @param sels 指定字段 + * @param row ResultSet + * + * @return Entity对象 + */ + public T getEntityValue(final SelectColumn sels, final EntityInfo.DataResultSetRow row) { + if (row.wasNull()) { + return null; + } + T obj; + Attribute[] attrs = this.attributes; + if (this.constructorParameters == null) { + obj = creator.create(); + } else { + Object[] cps = new Object[this.constructorParameters.length]; + for (int i = 0; i < this.constructorAttributes.length; i++) { + Attribute attr = this.constructorAttributes[i]; + if (sels == null || sels.test(attr.field())) { + cps[i] = getFieldValue(attr, row, 0); + } + } + obj = creator.create(cps); + attrs = this.unconstructorAttributes; + } + for (Attribute attr : attrs) { + if (sels == null || sels.test(attr.field())) { + attr.set(obj, getFieldValue(attr, row, 0)); + } + } + return obj; + } + + public T getFullEntityValue(final EntityInfo.DataResultSetRow row) { + return getEntityValue(constructorAttributes, constructorAttributes == null ? attributes : unconstructorAttributes, row); + } + + public T getFullEntityValue(final Serializable... values) { + return getEntityValue(constructorAttributes, constructorAttributes == null ? attributes : unconstructorAttributes, values); + } + + /** + * 将一行的ResultSet组装成一个Entity对象 + * + * @param constructorAttrs 构建函数的Attribute数组, 大小必须与this.constructorAttributes相同 + * @param unconstructorAttrs 非构建函数的Attribute数组 + * @param row ResultSet + * + * @return Entity对象 + */ + public T getEntityValue(final Attribute[] constructorAttrs, final Attribute[] unconstructorAttrs, final EntityInfo.DataResultSetRow row) { + if (row.wasNull()) { + return null; + } + T obj; + int index = 0; + if (this.constructorParameters == null) { + obj = creator.create(); + } else { + Object[] cps = new Object[this.constructorParameters.length]; + for (int i = 0; i < constructorAttrs.length; i++) { + Attribute attr = constructorAttrs[i]; + if (attr == null) { + continue; + } + cps[i] = getFieldValue(attr, row, ++index); + } + obj = creator.create(cps); + } + if (unconstructorAttrs != null) { + for (Attribute attr : unconstructorAttrs) { + if (attr == null) { + continue; + } + attr.set(obj, getFieldValue(attr, row, ++index)); + } + } + return obj; + } + + /** + * 将一行的ResultSet组装成一个Entity对象 + * + * @param constructorAttrs 构建函数的Attribute数组, 大小必须与this.constructorAttributes相同 + * @param unconstructorAttrs 非构建函数的Attribute数组 + * @param values 字段值集合 + * + * @return Entity对象 + */ + public T getEntityValue(final Attribute[] constructorAttrs, final Attribute[] unconstructorAttrs, final Serializable... values) { + if (values == null) { + return null; + } + T obj; + int index = -1; + if (this.constructorParameters == null) { + obj = creator.create(); + } else { + Object[] cps = new Object[this.constructorParameters.length]; + for (int i = 0; i < constructorAttrs.length; i++) { + Attribute attr = constructorAttrs[i]; + if (attr == null) { + continue; + } + cps[i] = values[++index]; + } + obj = creator.create(cps); + } + if (unconstructorAttrs != null) { + for (Attribute attr : unconstructorAttrs) { + if (attr == null) { + continue; + } + attr.set(obj, values[++index]); + } + } + return obj; + } + + public Serializable getFieldValue(Attribute attr, final EntityInfo.DataResultSetRow row, int index) { + return row.getObject(attr, index, index > 0 ? null : this.getSQLColumn(null, attr.field())); + } + + /** + * 根据field字段名获取数据库对应的字段名 + * + * @param tabalis 表别名 + * @param fieldname 字段名 + * + * @return String + */ + public String getSQLColumn(String tabalis, String fieldname) { + return this.aliasmap == null ? (tabalis == null ? fieldname : (tabalis + '.' + fieldname)) + : (tabalis == null ? aliasmap.getOrDefault(fieldname, fieldname) : (tabalis + '.' + aliasmap.getOrDefault(fieldname, fieldname))); + } + + public boolean hasConstructorAttribute() { + return constructorAttributes != null; + } + + public Creator getCreator() { + return creator; + } + +} diff --git a/src/main/java/org/redkale/source/EntityInfo.java b/src/main/java/org/redkale/source/EntityInfo.java index 2c6f8e3e9..e65a5a4ce 100644 --- a/src/main/java/org/redkale/source/EntityInfo.java +++ b/src/main/java/org/redkale/source/EntityInfo.java @@ -55,20 +55,11 @@ public final class EntityInfo { final JsonConvert jsonConvert; //Entity构建器 - private final Creator creator; + private final EntityBuilder builder; //Entity数值构建器 private final IntFunction arrayer; - //Entity构建器参数 - private final String[] constructorParameters; - - //Entity构建器参数Attribute, 数组个数与constructorParameters相同 - final Attribute[] constructorAttributes; - - //Entity构建器参数Attribute - final Attribute[] unconstructorAttributes; - //主键 final Attribute primary; @@ -376,12 +367,12 @@ public final class EntityInfo { this.tableStrategy = dts; this.arrayer = Creator.arrayFunction(type); - this.creator = Creator.create(type); + Creator creator = Creator.create(type); String[] cps = null; try { - Method cm = this.creator.getClass().getMethod("create", Object[].class); - RedkaleClassLoader.putReflectionPublicMethods(this.creator.getClass().getName()); - RedkaleClassLoader.putReflectionMethod(this.creator.getClass().getName(), cm); + Method cm = creator.getClass().getMethod("create", Object[].class); + RedkaleClassLoader.putReflectionPublicMethods(creator.getClass().getName()); + RedkaleClassLoader.putReflectionMethod(creator.getClass().getName(), cm); ConstructorParameters cp = cm.getAnnotation(ConstructorParameters.class); if (cp != null && cp.value().length > 0) { cps = cp.value(); @@ -394,7 +385,7 @@ public final class EntityInfo { } catch (Exception e) { logger.log(Level.SEVERE, type + " cannot find ConstructorParameters Creator", e); } - this.constructorParameters = cps; + String[] constructorParameters = cps; Attribute idAttr0 = null; Map aliasmap0 = null; Class cltmp = type; @@ -528,31 +519,33 @@ public final class EntityInfo { this.updateEntityAttributes = Utility.append(this.updateAttributes, this.primary); this.updateEntityColumns = Utility.append(this.updateColumns, this.primaryColumn); - if (this.constructorParameters == null) { - this.constructorAttributes = null; - this.unconstructorAttributes = null; + Attribute[] constructorAttributes; + Attribute[] unconstructorAttributes; + if (constructorParameters == null) { + constructorAttributes = null; + unconstructorAttributes = null; } else { - this.constructorAttributes = new Attribute[this.constructorParameters.length]; + constructorAttributes = new Attribute[constructorParameters.length]; List> unconstructorAttrs = new ArrayList<>(); List newquerycols1 = new ArrayList<>(); List newquerycols2 = new ArrayList<>(); for (Attribute attr : new ArrayList<>(queryAttrs)) { int pos = -1; - for (int i = 0; i < this.constructorParameters.length; i++) { - if (attr.field().equals(this.constructorParameters[i])) { + for (int i = 0; i < constructorParameters.length; i++) { + if (attr.field().equals(constructorParameters[i])) { pos = i; break; } } if (pos >= 0) { - this.constructorAttributes[pos] = attr; + constructorAttributes[pos] = attr; newquerycols1.add(queryCols.get(queryAttrs.indexOf(attr))); } else { unconstructorAttrs.add(attr); newquerycols2.add(queryCols.get(queryAttrs.indexOf(attr))); } } - this.unconstructorAttributes = unconstructorAttrs.toArray(new Attribute[unconstructorAttrs.size()]); + unconstructorAttributes = unconstructorAttrs.toArray(new Attribute[unconstructorAttrs.size()]); newquerycols1.addAll(newquerycols2); queryCols = newquerycols1; List> newqueryattrs = new ArrayList<>(); @@ -569,6 +562,8 @@ public final class EntityInfo { String field = this.queryAttributes[i].field(); this.queryColumns[i] = Utility.find(this.ddlColumns, c -> c.field.equals(field)); } + this.builder = new EntityBuilder<>(creator, aliasmap, constructorParameters, constructorAttributes, unconstructorAttributes, attributeMap, queryAttributes); + if (table != null) { StringBuilder querydb = new StringBuilder(); int index = 0; @@ -742,13 +737,22 @@ public final class EntityInfo { return cache != null && cache.isFullLoaded(); } + /** + * 获取Entity构建器 + * + * @return Creator + */ + public EntityBuilder getBuilder() { + return builder; + } + /** * 获取Entity构建器 * * @return Creator */ public Creator getCreator() { - return creator; + return builder.getCreator(); } /** @@ -1682,131 +1686,6 @@ public final class EntityInfo { return String.valueOf(value); } - /** - * 将一行的ResultSet组装成一个Entity对象 - * - * @param sels 指定字段 - * @param row ResultSet - * - * @return Entity对象 - */ - protected T getEntityValue(final SelectColumn sels, final DataResultSetRow row) { - if (row.wasNull()) { - return null; - } - T obj; - Attribute[] attrs = this.queryAttributes; - if (this.constructorParameters == null) { - obj = creator.create(); - } else { - Object[] cps = new Object[this.constructorParameters.length]; - for (int i = 0; i < this.constructorAttributes.length; i++) { - Attribute attr = this.constructorAttributes[i]; - if (sels == null || sels.test(attr.field())) { - cps[i] = getFieldValue(attr, row, 0); - } - } - obj = creator.create(cps); - attrs = this.unconstructorAttributes; - } - for (Attribute attr : attrs) { - if (sels == null || sels.test(attr.field())) { - attr.set(obj, getFieldValue(attr, row, 0)); - } - } - return obj; - } - - protected T getFullEntityValue(final DataResultSetRow row) { - return getEntityValue(constructorAttributes, constructorAttributes == null ? queryAttributes : unconstructorAttributes, row); - } - - public T getFullEntityValue(final Serializable... values) { - return getEntityValue(constructorAttributes, constructorAttributes == null ? queryAttributes : unconstructorAttributes, values); - } - - /** - * 将一行的ResultSet组装成一个Entity对象 - * - * @param constructorAttrs 构建函数的Attribute数组, 大小必须与this.constructorAttributes相同 - * @param unconstructorAttrs 非构建函数的Attribute数组 - * @param row ResultSet - * - * @return Entity对象 - */ - protected T getEntityValue(final Attribute[] constructorAttrs, final Attribute[] unconstructorAttrs, final DataResultSetRow row) { - if (row.wasNull()) { - return null; - } - T obj; - int index = 0; - if (this.constructorParameters == null) { - obj = creator.create(); - } else { - Object[] cps = new Object[this.constructorParameters.length]; - for (int i = 0; i < constructorAttrs.length; i++) { - Attribute attr = constructorAttrs[i]; - if (attr == null) { - continue; - } - cps[i] = getFieldValue(attr, row, ++index); - } - obj = creator.create(cps); - } - if (unconstructorAttrs != null) { - for (Attribute attr : unconstructorAttrs) { - if (attr == null) { - continue; - } - attr.set(obj, getFieldValue(attr, row, ++index)); - } - } - return obj; - } - - /** - * 将一行的ResultSet组装成一个Entity对象 - * - * @param constructorAttrs 构建函数的Attribute数组, 大小必须与this.constructorAttributes相同 - * @param unconstructorAttrs 非构建函数的Attribute数组 - * @param values 字段值集合 - * - * @return Entity对象 - */ - protected T getEntityValue(final Attribute[] constructorAttrs, final Attribute[] unconstructorAttrs, final Serializable... values) { - if (values == null) { - return null; - } - T obj; - int index = -1; - if (this.constructorParameters == null) { - obj = creator.create(); - } else { - Object[] cps = new Object[this.constructorParameters.length]; - for (int i = 0; i < constructorAttrs.length; i++) { - Attribute attr = constructorAttrs[i]; - if (attr == null) { - continue; - } - cps[i] = values[++index]; - } - obj = creator.create(cps); - } - if (unconstructorAttrs != null) { - for (Attribute attr : unconstructorAttrs) { - if (attr == null) { - continue; - } - attr.set(obj, values[++index]); - } - } - return obj; - } - - protected Serializable getFieldValue(Attribute attr, final DataResultSetRow row, int index) { - return row.getObject(attr, index, index > 0 ? null : this.getSQLColumn(null, attr.field())); - } - @Override public String toString() { return getClass().getSimpleName() + "(" + type.getName() + ")@" + Objects.hashCode(this);