编写类似代码行的更聪明的方法
Smarter way to write similar line of code
假设我有一个初始化方法,其中有很多初始化。这是一个例子:
public void initialize(URL url, ResourceBundle rb) {
date.setCellValueFactory(new PropertyValueFactory("date"));
site_address.setCellValueFactory(new PropertyValueFactory("site_address"));
bill_no.setCellValueFactory(new PropertyValueFactory("bill_no"));
product_name.setCellValueFactory(new PropertyValueFactory("product_name"));
shade.setCellValueFactory(new PropertyValueFactory("shade"));
size.setCellValueFactory(new PropertyValueFactory("size"));
mrp.setCellValueFactory(new PropertyValueFactory("mrp"));
qty.setCellValueFactory(new PropertyValueFactory("qty"));
less.setCellValueFactory(new PropertyValueFactory("less"));
amount.setCellValueFactory(new PropertyValueFactory("amount"));
}
通常我写第一行 date.setCellValueFactory(new PropertyValueFactory("date"));
并复制该行并修改其他行。
这对我来说似乎很奇怪,并且正在考虑一种更好的方法来做到这一点。我的 IDE 是 NetBeans
。
还有其他更聪明的方法来处理这个问题吗?
因为这些都有一个共同的方法,并且这个方法接收相同的 type
,也许一个解决方案是创建一个接口,将 setCellValueFactory(PropertyValueFactory pvf)
作为它唯一的方法。
然后将参数添加到数组并遍历数组并设置其参数:
public interface CVF{
public void setCellValueFactory(PropertyValueFactory pvf);
}
然后你的每个对象,即 date,site_address, etc . . .
应该 implement CVF
和 override setCellValueFactory(PropertyValueFactory pvf);
public void initialize(URL url, ResourceBundle rb) {
ArrayList<CVF> objs = new ArrayList<>();
objs.add(date);
objs.add(site_address);
// the rest of them . . .
String[] params = {"date","site_address","etc . . ."};
// keeping in mind the ORDER of insertion for both objs and params
for(int i=0; i< objs.size(); i++){
objs.get(i).setCellValueFactory(new PropertyValueFactory(params[i]));
}
}
甚至在 CVF
中添加第二种方法
public interface CVF{
public void setCellValueFactory(PropertyValueFactory pvf);
public String getParam();
}
然后让我们以 name
对象为例:
public class Name implements CVF{
@override
public void setCellValueFactory(PropertyValueFactory pvf){
// your code . . .
}
@override
public String getParam(){
return "name"; // this would change for each CVF
}
}
然后:
public void initialize(URL url, ResourceBundle rb) {
ArrayList<CVF> objs = new ArrayList<>();
objs.add(date);
objs.add(site_address);
// the rest of them . . .
for(CVF o : objs){
o.setCellValueFactory(new PropertyValueFactory(o.getParam());
}
}
第一个问题是,你的变量(日期、site_address等)实例是同一类型吗?
如果是,您可以使用对象和值创建地图,然后对其进行迭代(使用 guava 的 ImmutableMap
):
ImmutableMap<SomeCellObject,String> initializationMap = ImmutableMap.of(
date, "date",
site_address, "site_address"
// more ...
);
for (Map.Entry<SomeCellObject, String> entry : initializationMap.entrySet()) {
entry.getKey().setCellValueFactory(new PropertyValueFactory(entry.getValue()));
}
如果没有,您必须尝试在 Java 中输入鸭子。因为,Java 是静态类型语言,这比动态类型语言(如 Python)更难。 Wikipedia:
中提供了一个很好的例子
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DuckTyping {
interface Walkable { void walk(); }
interface Swimmable { void swim(); }
interface Quackable { void quack(); }
public static void main(String[] args) {
Duck d = new Duck();
Person p = new Person();
as(Walkable.class, d).walk(); //OK, duck has walk() method
as(Swimmable.class, d).swim(); //OK, duck has swim() method
as(Quackable.class, d).quack(); //OK, duck has quack() method
as(Walkable.class, p).walk(); //OK, person has walk() method
as(Swimmable.class, p).swim(); //OK, person has swim() method
as(Quackable.class, p).quack(); //Runtime Error, person does not have quack() method
}
@SuppressWarnings("unchecked")
static <T> T as(Class<T> t, final Object obj) {
return (T) Proxy.newProxyInstance(t.getClassLoader(), new Class[] {t},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return obj.getClass()
.getMethod(method.getName(), method.getParameterTypes())
.invoke(obj, args);
} catch (NoSuchMethodException nsme) {
throw new NoSuchMethodError(nsme.getMessage());
} catch (InvocationTargetException ite) {
throw ite.getTargetException();
}
}
});
}
}
class Duck {
public void walk() {System.out.println("I'm Duck, I can walk...");}
public void swim() {System.out.println("I'm Duck, I can swim...");}
public void quack() {System.out.println("I'm Duck, I can quack...");}
}
class Person {
public void walk() {System.out.println("I'm Person, I can walk...");}
public void swim() {System.out.println("I'm Person, I can swim...");}
public void talk() {System.out.println("I'm Person, I can talk...");}
}
原则上,esteban 的回答很好,但这段代码对您的应用程序来说更透明(您的 类 不必实现相同的接口)。
据我所知,没有特别优雅的方法可以做到这一点(除了创建很可能对手头的任务来说太过分的结构),而且无论你做什么,在某些时候你都必须输入属性并将它们与每一列相关联。我真的不建议子类化 TableColumn
或生成包装器 类 实现自定义接口定义标准 API 中已有的功能。
我通常只是通过编写一个方便的方法并调用它来节省多余的代码。基本思路是
private void configColumn(TableColumn<?,?> column, String property) {
column.setCellValueFactory(new PropertyValueFactory<>(property));
}
然后你的初始化变成
configColumn(date, "date");
configColumn(site_address, "site_address");
// etc ...
提高效率的技巧是最初将列命名为非常短的名称:
private void c(TableColumn<?,?> column, String property) {
column.setCellValueFactory(new PropertyValueFactory<>(property));
}
现在你要输入的东西少了很多:
c(date, "date");
c(site_address, "site_address");
// ...
然后一旦你把所有的都放进去,使用你的 IDE 将方法重命名为更易读的东西(我使用 Eclipse,所以你点击定义中的方法名称,选择 "Refactor" 和 "Rename" 然后输入新名称。我假设 NetBeans 具有类似的功能。)不要省略这部分,否则当您返回时您的代码将很难阅读。
如果你真的想用某种循环来做到这一点,那么假设你的 TableColumn
都与相应的属性具有相同的名称,你可以使用反射,但我认为你在可读性高于简洁性:
public void initialize() throws Exception {
List<String> colNames = Arrays.asList("date", "site_address", "bill_no" /*, ...*/);
for (String colName : colNames) {
Field f = this.getClass().getDeclaredField(colName);
TableColumn<?,?> column = (TableColumn<?,?>) f.get(this);
column.setCellValueFactory(new PropertyValueFactory(colName));
}
}
注意还有另一种方法,但是(没有一些相当难看的接线)你只能在你可以访问 FXMLLoader
的地方使用这个技巧(所以你不能在控制器中这样做,只能在加载 FXML 文件的代码)。 FXMLLoader
可以访问 namespace
,它是所有 fx:id
属性值和在 FXML 中创建的对象之间的映射(请注意,这里还有一些其他键值对也不仅仅是用 fx:id
定义的那些)。因此,再次假设您所有的 TableColumn
都具有与 属性 值匹配的 fx:id
属性值,您可以执行以下操作:
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml/file"));
Parent root = loader.load();
Map<String, Object> namespace = loader.getNamespace();
for (String fxid : namespace.keySet()) {
Object value = namespace.get(fxid);
if (value instanceof TableColumn) {
((TableColumn<?,?>)value).setCellValueFactory(new PropertyValueFactory(fxid));
}
}
假设我有一个初始化方法,其中有很多初始化。这是一个例子:
public void initialize(URL url, ResourceBundle rb) {
date.setCellValueFactory(new PropertyValueFactory("date"));
site_address.setCellValueFactory(new PropertyValueFactory("site_address"));
bill_no.setCellValueFactory(new PropertyValueFactory("bill_no"));
product_name.setCellValueFactory(new PropertyValueFactory("product_name"));
shade.setCellValueFactory(new PropertyValueFactory("shade"));
size.setCellValueFactory(new PropertyValueFactory("size"));
mrp.setCellValueFactory(new PropertyValueFactory("mrp"));
qty.setCellValueFactory(new PropertyValueFactory("qty"));
less.setCellValueFactory(new PropertyValueFactory("less"));
amount.setCellValueFactory(new PropertyValueFactory("amount"));
}
通常我写第一行 date.setCellValueFactory(new PropertyValueFactory("date"));
并复制该行并修改其他行。
这对我来说似乎很奇怪,并且正在考虑一种更好的方法来做到这一点。我的 IDE 是 NetBeans
。
还有其他更聪明的方法来处理这个问题吗?
因为这些都有一个共同的方法,并且这个方法接收相同的 type
,也许一个解决方案是创建一个接口,将 setCellValueFactory(PropertyValueFactory pvf)
作为它唯一的方法。
然后将参数添加到数组并遍历数组并设置其参数:
public interface CVF{
public void setCellValueFactory(PropertyValueFactory pvf);
}
然后你的每个对象,即 date,site_address, etc . . .
应该 implement CVF
和 override setCellValueFactory(PropertyValueFactory pvf);
public void initialize(URL url, ResourceBundle rb) {
ArrayList<CVF> objs = new ArrayList<>();
objs.add(date);
objs.add(site_address);
// the rest of them . . .
String[] params = {"date","site_address","etc . . ."};
// keeping in mind the ORDER of insertion for both objs and params
for(int i=0; i< objs.size(); i++){
objs.get(i).setCellValueFactory(new PropertyValueFactory(params[i]));
}
}
甚至在 CVF
public interface CVF{
public void setCellValueFactory(PropertyValueFactory pvf);
public String getParam();
}
然后让我们以 name
对象为例:
public class Name implements CVF{
@override
public void setCellValueFactory(PropertyValueFactory pvf){
// your code . . .
}
@override
public String getParam(){
return "name"; // this would change for each CVF
}
}
然后:
public void initialize(URL url, ResourceBundle rb) {
ArrayList<CVF> objs = new ArrayList<>();
objs.add(date);
objs.add(site_address);
// the rest of them . . .
for(CVF o : objs){
o.setCellValueFactory(new PropertyValueFactory(o.getParam());
}
}
第一个问题是,你的变量(日期、site_address等)实例是同一类型吗?
如果是,您可以使用对象和值创建地图,然后对其进行迭代(使用 guava 的 ImmutableMap
):
ImmutableMap<SomeCellObject,String> initializationMap = ImmutableMap.of(
date, "date",
site_address, "site_address"
// more ...
);
for (Map.Entry<SomeCellObject, String> entry : initializationMap.entrySet()) {
entry.getKey().setCellValueFactory(new PropertyValueFactory(entry.getValue()));
}
如果没有,您必须尝试在 Java 中输入鸭子。因为,Java 是静态类型语言,这比动态类型语言(如 Python)更难。 Wikipedia:
中提供了一个很好的例子import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DuckTyping {
interface Walkable { void walk(); }
interface Swimmable { void swim(); }
interface Quackable { void quack(); }
public static void main(String[] args) {
Duck d = new Duck();
Person p = new Person();
as(Walkable.class, d).walk(); //OK, duck has walk() method
as(Swimmable.class, d).swim(); //OK, duck has swim() method
as(Quackable.class, d).quack(); //OK, duck has quack() method
as(Walkable.class, p).walk(); //OK, person has walk() method
as(Swimmable.class, p).swim(); //OK, person has swim() method
as(Quackable.class, p).quack(); //Runtime Error, person does not have quack() method
}
@SuppressWarnings("unchecked")
static <T> T as(Class<T> t, final Object obj) {
return (T) Proxy.newProxyInstance(t.getClassLoader(), new Class[] {t},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return obj.getClass()
.getMethod(method.getName(), method.getParameterTypes())
.invoke(obj, args);
} catch (NoSuchMethodException nsme) {
throw new NoSuchMethodError(nsme.getMessage());
} catch (InvocationTargetException ite) {
throw ite.getTargetException();
}
}
});
}
}
class Duck {
public void walk() {System.out.println("I'm Duck, I can walk...");}
public void swim() {System.out.println("I'm Duck, I can swim...");}
public void quack() {System.out.println("I'm Duck, I can quack...");}
}
class Person {
public void walk() {System.out.println("I'm Person, I can walk...");}
public void swim() {System.out.println("I'm Person, I can swim...");}
public void talk() {System.out.println("I'm Person, I can talk...");}
}
原则上,esteban 的回答很好,但这段代码对您的应用程序来说更透明(您的 类 不必实现相同的接口)。
据我所知,没有特别优雅的方法可以做到这一点(除了创建很可能对手头的任务来说太过分的结构),而且无论你做什么,在某些时候你都必须输入属性并将它们与每一列相关联。我真的不建议子类化 TableColumn
或生成包装器 类 实现自定义接口定义标准 API 中已有的功能。
我通常只是通过编写一个方便的方法并调用它来节省多余的代码。基本思路是
private void configColumn(TableColumn<?,?> column, String property) {
column.setCellValueFactory(new PropertyValueFactory<>(property));
}
然后你的初始化变成
configColumn(date, "date");
configColumn(site_address, "site_address");
// etc ...
提高效率的技巧是最初将列命名为非常短的名称:
private void c(TableColumn<?,?> column, String property) {
column.setCellValueFactory(new PropertyValueFactory<>(property));
}
现在你要输入的东西少了很多:
c(date, "date");
c(site_address, "site_address");
// ...
然后一旦你把所有的都放进去,使用你的 IDE 将方法重命名为更易读的东西(我使用 Eclipse,所以你点击定义中的方法名称,选择 "Refactor" 和 "Rename" 然后输入新名称。我假设 NetBeans 具有类似的功能。)不要省略这部分,否则当您返回时您的代码将很难阅读。
如果你真的想用某种循环来做到这一点,那么假设你的 TableColumn
都与相应的属性具有相同的名称,你可以使用反射,但我认为你在可读性高于简洁性:
public void initialize() throws Exception {
List<String> colNames = Arrays.asList("date", "site_address", "bill_no" /*, ...*/);
for (String colName : colNames) {
Field f = this.getClass().getDeclaredField(colName);
TableColumn<?,?> column = (TableColumn<?,?>) f.get(this);
column.setCellValueFactory(new PropertyValueFactory(colName));
}
}
注意还有另一种方法,但是(没有一些相当难看的接线)你只能在你可以访问 FXMLLoader
的地方使用这个技巧(所以你不能在控制器中这样做,只能在加载 FXML 文件的代码)。 FXMLLoader
可以访问 namespace
,它是所有 fx:id
属性值和在 FXML 中创建的对象之间的映射(请注意,这里还有一些其他键值对也不仅仅是用 fx:id
定义的那些)。因此,再次假设您所有的 TableColumn
都具有与 属性 值匹配的 fx:id
属性值,您可以执行以下操作:
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml/file"));
Parent root = loader.load();
Map<String, Object> namespace = loader.getNamespace();
for (String fxid : namespace.keySet()) {
Object value = namespace.get(fxid);
if (value instanceof TableColumn) {
((TableColumn<?,?>)value).setCellValueFactory(new PropertyValueFactory(fxid));
}
}