表头合并单元格. 照例不多说了, 看代码吧.
首先需要定义一个接口, 看看表头是怎么合并的

/** *//**
* 列头组
*
* @author Brad.Wu
* @version 1.0
*/

public interface Group ...{

/** *//**
* 获取所在行
*
* @return
*/
public int getRow();


/** *//**
* 获取所在列
*
* @return
*/
public int getColumn();


/** *//**
* 获取占列个数
*
* @return
*/
public int getColumnSpan();


/** *//**
* 获取占行个数
*
* @return
*/
public int getRowSpan();


/** *//**
* 获取文字
*
* @return
*/
public Object getHeaderValue();
}

这个和HTML的写法其实一样的. 主要就是每个Cell所在的位置, 占的行列数以及文字.
接下来是一个默认的实现. 其实不写接口也可以, 因为通常不会对表头做动作的.

/** *//**
* 默认Group实现
*
* @author Brad.Wu
* @version 1.0
*/

public class DefaultGroup implements Group ...{
private int row = 0;

private int column = 0;

private int rowSpan = 1;

private int columnSpan = 1;

private Object headerValue = null;


/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getRow()
*/

public int getRow() ...{
return this.row;
}


/** *//**
* @param row 要设置的 row。
*/

public void setRow(int row) ...{
this.row = row;
}


/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getColumn()
*/

public int getColumn() ...{
return this.column;
}


/** *//**
* @param column 要设置的 column。
*/

public void setColumn(int column) ...{
this.column = column;
}


/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getColumnSpan()
*/

public int getColumnSpan() ...{
return this.columnSpan;
}


/** *//**
* @param columnSpan 要设置的 columnSpan。
*/

public void setColumnSpan(int columnSpan) ...{
this.columnSpan = columnSpan;
}


/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getRowSpan()
*/

public int getRowSpan() ...{
return this.rowSpan;
}


/** *//**
* @param rowSpan 要设置的 rowSpan。
*/

public void setRowSpan(int rowSpan) ...{
this.rowSpan = rowSpan;
}


/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getHeaderValue()
*/

public Object getHeaderValue() ...{
return this.headerValue;
}


/** *//**
* @param headerValue 要设置的 headerValue。
*/

public void setHeaderValue(Object headerValue) ...{
this.headerValue = headerValue;
}
}

重写一个表头组件
import java.awt.Component;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JTable;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;


/** *//**
* 可以合并的列头
*
* @author Brad.Wu
* @version 1.0
*/
@SuppressWarnings("serial")

public class GroupableTableHeader extends JTableHeader ...{
private int rowCount = 0;

private int columnCount = 0;

private List<Group> groups = new ArrayList<Group>();


public GroupableTableHeader() ...{
// 这个是必须的, 因为如果可以拖动列的位置, 那么一切都完蛋了.
// 如果你想实现这个功能, 那么只能你自己去做了, 我可不想做这个, 看上去超烦的
this.setReorderingAllowed(false);
}


/**//*
* (非 Javadoc)
*
* @see javax.swing.table.JTableHeader#updateUI()
*/
@Override

public void updateUI() ...{
setUI(new GroupableTableHeaderUI());
}


/**//*
* 获取指定行列的位置
*/

public Rectangle getHeaderRect(int row, int column) ...{
Rectangle r = new Rectangle();
TableColumnModel cm = getColumnModel();

Group group = this.getGroup(row, column);
r.height = getHeight();


if (column < 0) ...{
// x = width = 0;

if (!getComponentOrientation().isLeftToRight()) ...{
r.x = getWidthInRightToLeft();
}

} else if (column >= cm.getColumnCount()) ...{

if (getComponentOrientation().isLeftToRight()) ...{
r.x = getWidth();
}

} else ...{

for (int i = 0; i < group.getColumn(); i++) ...{
r.x += cm.getColumn(i).getWidth();
}

for (int i = group.getColumn(), j = group.getColumn() + group.getColumnSpan() - 1; i < j; i++) ...{
r.width += cm.getColumn(i).getWidth();
}

if (!getComponentOrientation().isLeftToRight()) ...{
r.x = getWidthInRightToLeft() - r.x - r.width;
}
// r.width = cm.getColumn(column).getWidth();
}
return r;
}


/** *//**
* 获取Group的Y位置
*
* @param group
* @return
*/

public int getYOfGroup(Group group) ...{
int row = group.getRow();
TableCellRenderer renderer = this.getDefaultRenderer();
Component comp = renderer.getTableCellRendererComponent(getTable(), group.getHeaderValue(),
false, false, group.getRow(), group.getColumn());
return row * comp.getPreferredSize().height;
}


/** *//**
* 获取Group的高度
*
* @param group
* @return
*/

public int getHeightOfGroup(Group group) ...{
int rowSpan = group.getRowSpan();
TableCellRenderer renderer = this.getDefaultRenderer();
Component comp = renderer.getTableCellRendererComponent(getTable(), group.getHeaderValue(),
false, false, group.getRow(), group.getColumn());
return rowSpan * comp.getPreferredSize().height;
}


private int getWidthInRightToLeft() ...{

if ((table != null) && (table.getAutoResizeMode() != JTable.AUTO_RESIZE_OFF)) ...{
return table.getWidth();
}
return super.getWidth();
}


/** *//**
* 增加Group
*
* @param group
*/

public void addGroup(Group group) ...{
groups.add(group);
int row = group.getRow();
int rowSpan = group.getRowSpan();
rowCount = Math.max(rowCount, row +