根据条件筛选坐标点,并从取出的点位中获取最外围可以组成凸多边形的点位

根据条件筛选坐标点,并从取出的点位中获取最外围可以组成凸多边形的点位

具体需求根据街道中的单据量过滤5公里范围内,订单用户量80%范围的街道,并画出凸多边形
算法思路
1、根据某点过滤范围内的坐标

private void filterOverLimit(List<ShopSalesStreetPoint> radiationPointList, Point2D.Double shopPoint) {
        ShopSalesStreetPoint point0 = radiationPointList.get(0);
        Point2D.Double aDouble = new Point2D.Double(point0.getX(), point0.getY());
        Iterator<ShopSalesStreetPoint> it = radiationPointList.iterator();
        while (it.hasNext()) {
            ShopSalesStreetPoint streetPoint = it.next();
            double distance = DiscretePointUtil.countPointDistance(aDouble, new Point2D.Double(streetPoint.getX(), streetPoint.getY()));
            if (distance > 5000) {
                it.remove();
            }
        }
    }

2、递归
首先创建第一个点并将订单量返回用于统计,默认点位数组根据订单用户量从大到小排序

private BigDecimal processFirstPoint(List<ShopSalesStreetPoint> allStreetPointList, List<ShopSalesStreetPoint> pointList) {
        //提前计算第一个节点
        ShopSalesStreetPoint firstPoint = allStreetPointList.get(0);
        pointList.add(firstPoint);
        BigDecimal checkValue = firstPoint.getValue();
        //移除第一个
        allStreetPointList.remove(0);
        return checkValue;
    }

计算阈值,达到80%阈值则返回

  //达到阈值则返回
  if (checkThresholdValue(checkValue, allValue)) {
      return selectedPoint2DList;
  }
  public static boolean checkThresholdValue(BigDecimal check, BigDecimal all) {
        //四舍五入不进位RoundingMode.DOWN
        return check.divide(all, 3, RoundingMode.DOWN).multiply(new BigDecimal(100)).compareTo(new BigDecimal(VALUE_ACCOUNTED)) >= 0;
    }
 //执行后续
        selectedPoint2DList = process(checkValue, allValue, allStreetPointList, selectedPoint2DList);

从已选点位中选取最小凸多边形,并且输出排序好的点位

private List<ShopSalesStreetPoint> getMinimumBoundingPolygon(List<ShopSalesStreetPoint> selectedPoint2DList) {
        if (selectedPoint2DList.size() > 2) {
            selectedPoint2DList.forEach(point -> point.setFounded(false));
            selectedPoint2DList = MinimumBoundingPolygon.findSmallestPolygon(selectedPoint2DList);
        }
        return selectedPoint2DList;
    }

process方法为递归方法具体内容如下:

private List<ShopSalesStreetPoint> process(BigDecimal checkValue, BigDecimal allValue,
                                               List<ShopSalesStreetPoint> restStreetPointList, List<ShopSalesStreetPoint> selectedPoint2DList) {
        //达到阈值则返回
        if (checkThresholdValue(checkValue, allValue)) {
            return selectedPoint2DList;
        }
        ShopSalesStreetPoint curPoint = restStreetPointList.get(0);
        checkValue = checkValue.add(curPoint.getValue());
        //达到阈值则返回
        if (checkThresholdValue(checkValue, allValue)) {
            selectedPoint2DList = getMinimumBoundingPolygon(selectedPoint2DList);
            return selectedPoint2DList;
        }
        selectedPoint2DList.add(curPoint);
        //根据已有点位获取最大多边形
        if (selectedPoint2DList.size() > 2) {
            selectedPoint2DList = getMinimumBoundingPolygon(selectedPoint2DList);
        }
        //达到阈值则返回
        if (checkThresholdValue(checkValue, allValue)) {
            return selectedPoint2DList;
        }
        //移除第一个进入判断将范围内的所有点移除,并加入总额返回
        restStreetPointList.remove(0);
        //所有节点循环结束退出
        if (restStreetPointList.isEmpty()) {
            return selectedPoint2DList;
        }
        checkValue = removeRestStreetPoint(checkValue, selectedPoint2DList, restStreetPointList);
        //达到阈值则返回
        if (checkThresholdValue(checkValue, allValue)) {
            return selectedPoint2DList;
        }
        if (restStreetPointList.isEmpty()) {
            return selectedPoint2DList;
        }
        selectedPoint2DList = process(checkValue, allValue, restStreetPointList, selectedPoint2DList);
        return selectedPoint2DList;
    }

递归主函数中用到的工具类如下

package cn.com.gome.shop.analyze.backend.service.assistant;

import cn.com.gome.shop.analyze.backend.data.assistant.ShopSalesStreetPoint;
import org.springframework.stereotype.Service;

import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class PointService {


    /**
     * 是否有 横断
* 参数为四个点的坐标 * * @param px1 * @param py1 * @param px2 * @param py2 * @param px3 * @param py3 * @param px4 * @param py4 * @return */
public static boolean isIntersect(double px1, double py1, double px2, double py2, double px3, double py3, double px4, double py4) { boolean flag = false; double d = (px2 - px1) * (py4 - py3) - (py2 - py1) * (px4 - px3); if (d != 0) { double r = ((py1 - py3) * (px4 - px3) - (px1 - px3) * (py4 - py3)) / d; double s = ((py1 - py3) * (px2 - px1) - (px1 - px3) * (py2 - py1)) / d; if ((r >= 0) && (r <= 1) && (s >= 0) && (s <= 1)) { flag = true; } } return flag; } /** * 目标点是否在目标边上边上
* * @param px0 目标点的经度坐标 * @param py0 目标点的纬度坐标 * @param px1 目标线的起点(终点)经度坐标 * @param py1 目标线的起点(终点)纬度坐标 * @param px2 目标线的终点(起点)经度坐标 * @param py2 目标线的终点(起点)纬度坐标 * @return */
public static boolean isPointOnLine(double px0, double py0, double px1, double py1, double px2, double py2) { boolean flag = false; double ESP = 1e-9;//无限小的正数 // if ((Math.abs(Multiply(px0, py0, px1, py1, px2, py2)) < ESP) && ((px0 - px1) * (px0 - px2) <= 0) // && ((py0 - py1) * (py0 - py2) <= 0)) { // flag = true; // } if (((px0 - px1) * (py1 - py2)) == ((px1 - px2) * (py0 - py1)) && (px0 >= min(px1, px2) && px0 <= max(px1, px2)) && ((py0 >= min(py1, py2)) && (py0 <= max(py1, py2)))) { flag = true; } return flag; } public static Double min(Double d1, Double d2) { if (d1 < d2) { return d1; } return d2; } public static Double max(Double d1, Double d2) { if (d1 > d2) { return d1; } return d2; } public static double Multiply(double px0, double py0, double px1, double py1, double px2, double py2) { return ((px1 - px0) * (py2 - py0) - (px2 - px0) * (py1 - py0)); } /** * 判断目标点是否在多边形内(由多个点组成)
* 备用方案 * * @param px 目标点的经度坐标 * @param py 目标点的纬度坐标 * @param polygonXA 多边形的经度坐标集合 * @param polygonYA 多边形的纬度坐标集合 * @return */
@Deprecated public static boolean isPointInPolygon(double px, double py, ArrayList<Double> polygonXA, ArrayList<Double> polygonYA) { boolean isInside = false; double ESP = 1e-9; int count = 0; double linePoint1x; double linePoint1y; double linePoint2x = 180; double linePoint2y; linePoint1x = px; linePoint1y = py; linePoint2y = py; for (int i = 0; i < polygonXA.size() - 1; i++) { double cx1 = polygonXA.get(i); double cy1 = polygonYA.get(i); double cx2 = polygonXA.get(i + 1); double cy2 = polygonYA.get(i + 1); //如果目标点在任何一条线上 if (isPointOnLine(px, py, cx1, cy1, cx2, cy2)) { return true; } //如果线段的长度无限小(趋于零)那么这两点实际是重合的,不足以构成一条线段 if (Math.abs(cy2 - cy1) < ESP) { continue; } //第一个点是否在以目标点为基础衍生的平行纬度线 if (isPointOnLine(cx1, cy1, linePoint1x, linePoint1y, linePoint2x, linePoint2y)) { //第二个点在第一个的下方,靠近赤道纬度为零(最小纬度) if (cy1 > cy2) count++; } //第二个点是否在以目标点为基础衍生的平行纬度线 else if (isPointOnLine(cx2, cy2, linePoint1x, linePoint1y, linePoint2x, linePoint2y)) { //第二个点在第一个的上方,靠近极点(南极或北极)纬度为90(最大纬度) if (cy2 > cy1) count++; } //由两点组成的线段是否和以目标点为基础衍生的平行纬度线相交 else if (isIntersect(cx1, cy1, cx2, cy2, linePoint1x, linePoint1y, linePoint2x, linePoint2y)) { count++; } } if (count % 2 == 1) { isInside = true; } return isInside; } public static boolean isInOnLine(Double pointLon, Double pointLat, Double lon1, Double lon2, Double lat1, Double lat2) { Line2D.Double aDouble = new Line2D.Double(lon1, lat1, lon2, lat2); double v = aDouble.ptLineDist(pointLon, pointLat); return v > 0; } /** * 判断是否在多边形区域内 * * @param pointLon 要判断的点的纵坐标 * @param pointLat 要判断的点的横坐标 * @param lons 区域各顶点的纵坐标数组 * @param lats 区域各顶点的横坐标数组 * @return */ public static boolean isInPolygon(Double pointLon, Double pointLat, Double[] lons, Double[] lats) { return isInPolygon(pointLon, pointLat, Arrays.asList(lons), Arrays.asList(lats)); } /** * 判断是否在多边形区域内 * * @param pointLon 要判断的点的纵坐标 * @param pointLat 要判断的点的横坐标 * @param lons 区域各顶点的纵坐标数组 * @param lats 区域各顶点的横坐标数组 * @return */ public static boolean isInPolygon(Double pointLon, Double pointLat, List<Double> lons, List<Double> lats) { // 将要判断的横纵坐标组成一个点 Point2D.Double point = new Point2D.Double(pointLon, pointLat); // 将区域各顶点的横纵坐标放到一个点集合里面 List<Point2D.Double> pointList = new ArrayList<Point2D.Double>(); for (int i = 0; i < lons.size(); i++) { Point2D.Double polygonPoint = new Point2D.Double(lons.get(i), lats.get(i)); pointList.add(polygonPoint); } return check(point, pointList); } /** * 判断点是否在多边形区域内 * * @param checkStreetPoint * @param streetPointList * @return */ public static boolean isInPolygon(Point2D.Double checkStreetPoint, List<ShopSalesStreetPoint> streetPointList) { // 将区域各顶点的横纵坐标放到一个点集合里面 List<Point2D.Double> point2DList = new ArrayList<Point2D.Double>(); for (ShopSalesStreetPoint streetPoint : streetPointList) { Point2D.Double polygonPoint = new Point2D.Double(streetPoint.getY(), streetPoint.getX()); point2DList.add(polygonPoint); } return check(checkStreetPoint, point2DList); } /** * 一个点是否在多边形内 * * @param point 要判断的点的横纵坐标 * @param polygon 组成的顶点坐标集合 * @return */ private static boolean check(Point2D.Double point, List<Point2D.Double> polygon) { GeneralPath generalPath = new GeneralPath(); Point2D.Double first = polygon.get(0); // 通过移动到指定坐标(以双精度指定),将一个点添加到路径中 generalPath.moveTo(first.x, first.y); polygon.remove(0); for (Point2D.Double d : polygon) { // 通过绘制一条从当前坐标到新指定坐标(以双精度指定)的直线,将一个点添加到路径中。 generalPath.lineTo(d.x, d.y); } // 将几何多边形封闭 generalPath.lineTo(first.x, first.y); generalPath.closePath(); // 测试指定的 Point2D 是否在 Shape 的边界内。 return generalPath.contains(point); } }

下列工具类出自:https://blog.csdn.net/qq_38567039/article/details/118672509

package cn.com.gome.shop.analyze.backend.service.assistant;

import cn.com.gome.shop.analyze.backend.data.assistant.Point;
import org.gavaghan.geodesy.Ellipsoid;
import org.gavaghan.geodesy.GeodeticCalculator;
import org.gavaghan.geodesy.GlobalCoordinates;

import java.awt.geom.Point2D;
import java.util.List;

/**
 * 

* 离散点计算工具 *

*

 * 离散点计算工具
 *
 *  y
 *  ↑   ·  ·
 *  │  · ·   ·
 *  │ ·  · ·   ·
 *  │  ·  ·
 * —│————————————→ x
 * 
*/
public class DiscretePointUtil { /** *

* 查找离散点集中的(min_x, min_Y) (max_x, max_Y) *

*

     * 查找离散点集中的(min_x, min_Y) (max_x, max_Y)
     * 
* * @param points 离散点集 * @return [(min_x, min_Y), (max_x, max_Y)] */
public static Point[] calMinMaxDots(final List<Point> points) { if (null == points || points.isEmpty()) { return null; } double min_x = points.get(0).getX(), max_x = points.get(0).getX(); double min_y = points.get(0).getY(), max_y = points.get(0).getY(); /* 这里存在优化空间,可以使用并行计算 */ for (Point point : points) { if (min_x > point.getX()) { min_x = point.getX(); } if (max_x < point.getX()) { max_x = point.getX(); } if (min_y > point.getY()) { min_y = point.getY(); } if (max_y < point.getY()) { max_y = point.getY(); } } Point ws = new Point(min_x, min_y); Point en = new Point(max_x, max_y); return new Point[]{ws, en}; } /** *

* 求矩形面积平方根 *

*

     * 以两个点作为矩形的对角线上的两点,计算其面积的平方根
     * 
* * @param ws 西南点 * @param en 东北点 * @return 矩形面积平方根 */
public static double calRectAreaSquare(Point ws, Point en) { if (null == ws || null == en) { return .0; } /* 为防止计算面积时float溢出,先计算各边平方根,再相乘 */ return Math.sqrt(Math.abs(ws.getX() - en.getX())) * Math.sqrt(Math.abs(ws.getY() - en.getY())); } /** *

* 求两点之间的长度 不能用于经纬度 *

*

     * 求两点之间的长度
     * 
* * @param ws 西南点 * @param en 东北点 * @return 两点之间的长度 */
public static double calLineLen(Point ws, Point en) { if (null == ws || null == en) { return .0; } if (ws.equals(en)) { return .0; } double a = Math.abs(ws.getX() - en.getX()); // 直角三角形的直边a double b = Math.abs(ws.getY() - en.getY()); // 直角三角形的直边b double min = Math.min(a, b); // 短直边 double max = Math.max(a, b); // 长直边 /** * 为防止计算平方时float溢出,做如下转换 * √(min²+max²) = √((min/max)²+1) * abs(max) */ double inner = min / max; return Math.sqrt(inner * inner + 1.0) * max; } /** *

* 求两点间的中心点 *

*

     * 求两点间的中心点
     * 
* * @param ws 西南点 * @param en 东北点 * @return 两点间的中心点 */
public static Point calCerter(Point ws, Point en) { if (null == ws || null == en) { return null; } return new Point(ws.getX() + (en.getX() - ws.getX()) / 2.0, ws.getY() + (en.getY() - ws.getY()) / 2.0); } /** *

* 计算向量角 *

*

     * 计算两点组成的向量与x轴正方向的向量角
     * 
* * @param s 向量起点 * @param d 向量终点 * @return 向量角 */
public static double angleOf(Point s, Point d) { double dist = countPointDistance(new Point2D.Double(s.getX(), s.getY()), new Point2D.Double(d.getX(), d.getY())); if (dist <= 0) { return .0; } double x = d.getX() - s.getX(); // 直角三角形的直边a double y = d.getY() - s.getY(); // 直角三角形的直边b if (y >= 0.) { /* 1 2 象限 */ return Math.acos(x / dist); } else { /* 3 4 象限 */ return Math.acos(-x / dist) + Math.PI; } } /** *

* 修正角度 *

*

     * 修正角度到 [0, 2PI]
     * 
* * @param angle 原始角度 * @return 修正后的角度 */
public static double reviseAngle(double angle) { while (angle < 0.) { angle += 2 * Math.PI; } while (angle >= 2 * Math.PI) { angle -= 2 * Math.PI; } return angle; } /** * 计算坐标点之间距离 * WGS84坐标系 * * @param pointA * @param pointB * @return */ public static double countPointDistance(Point2D.Double pointA, Point2D.Double pointB) { return getDistanceMeter(new GlobalCoordinates(pointA.getY(), pointA.getX()), new GlobalCoordinates(pointB.getY(), pointB.getX()), Ellipsoid.WGS84); } public static double getDistanceMeter(GlobalCoordinates gpsFrom, GlobalCoordinates gpsTo, Ellipsoid ellipsoid) { //创建GeodeticCalculator,调用计算方法,传入坐标系、经纬度用于计算距离 return new GeodeticCalculator().calculateGeodeticCurve(ellipsoid, gpsFrom, gpsTo).getEllipsoidalDistance(); } }
package cn.com.gome.shop.analyze.backend.service.assistant;

import cn.com.gome.shop.analyze.backend.data.assistant.Point;
import cn.com.gome.shop.analyze.backend.data.assistant.ShopSalesStreetPoint;

import java.util.*;

/**
 * 

* 最小(凸)包围边界查找 *

*

 * 最小(凸)包围边界查找
 *
 * Minimum Bounding Polygon (Convex Hull; Smallest Enclosing A Set of Points)
 * ©2009 Darel Rex Finley.
 *
 *  y
 *  ↑   ·  ·
 *  │  · ·   ·
 *  │ ·  · ·   ·
 *  │  ·  ·
 * —│————————————→ x
 *
 * 
*/
public class MinimumBoundingPolygon { public static LinkedList<ShopSalesStreetPoint> findSmallestPolygon(List<ShopSalesStreetPoint> ps) { if (null == ps || ps.isEmpty()) { return null; } //查找起始点(保证y最大的情况下、尽量使x最小的点) ShopSalesStreetPoint corner = findStartPoint(ps); if (null == corner) { return null; } double minAngleDif, oldAngle = 2 * Math.PI; LinkedList<ShopSalesStreetPoint> bound = new LinkedList<>(); do { minAngleDif = 2 * Math.PI; bound.add(corner); ShopSalesStreetPoint nextPoint = corner; double nextAngle = oldAngle; for (ShopSalesStreetPoint p : ps) { if (p.isFounded()) { // 已被加入边界链表的点 continue; } if (p.equals(corner)) { // 重合点 /*if (!p.equals(bound.getFirst())) { p.founded = true; }*/ continue; } double currAngle = DiscretePointUtil.angleOf(new Point(corner.getX(), corner.getY()), new Point(p.getX(), p.getY())); /* 当前向量与x轴正方向的夹角 */ double angleDif = DiscretePointUtil.reviseAngle(oldAngle - currAngle); /* 两条向量之间的夹角(顺时针旋转的夹角) */ if (angleDif < minAngleDif) { minAngleDif = angleDif; nextPoint = p; nextAngle = currAngle; } } oldAngle = nextAngle; corner = nextPoint; corner.setFounded(true); } while (!corner.equals(bound.getFirst())); /* 判断边界是否闭合 */ return bound; } /** * 查找起始点(保证y最大的情况下、尽量使x最小的点) */ private static ShopSalesStreetPoint findStartPoint(List<ShopSalesStreetPoint> ps) { if (null == ps || ps.isEmpty()) { return null; } ShopSalesStreetPoint p = ps.get(0); ListIterator<ShopSalesStreetPoint> iter = ps.listIterator(); while (iter.hasNext()) { ShopSalesStreetPoint point = iter.next(); if (point.getY() > p.getY() || (point.getY() == p.getY() && point.getX() < p.getX())) { /* 找到最靠上靠左的点 */ p = point; } } return p; } private static double cal_cross_product(Point A, Point B, Point C) { double AB[] = {B.getX() - A.getX(), B.getY() - A.getY()}; double AC[] = {C.getX() - A.getX(), C.getY() - A.getY()}; return AB[0] * AC[1] - AB[1] * AC[0]; } /** * 验证点位是否为凸多边形 * * @param points * @return */ public static boolean isConvex(List<Point> points) { double flag = 0; int n = points.size(); for (int i = 0; i < n; i++) { //cur > 0 表示points是按逆时针输出的;cur< 0, 顺时针 Double cur = cal_cross_product(points.get(i), points.get((i + 1) % n), points.get((i + 2) % n)); if (cur != 0) { //说明异号, 说明有个角大于180度 if (cur * flag < 0) { return false; } else { flag = cur; } } } return true; } public static void main(String[] args) { //交道口街道 Double lng = 116.400978; Double lat = 39.93576; //东直门 Double lng1 = 116.441826; Double lat1 = 39.930855; //龙潭街道 Double lng2 = 116.436783; Double lat2 = 39.887241; //体育馆路街道 Double lng3 = 116.422462; Double lat3 = 39.88694; //东四街道 Double lng4 = 116.423988; Double lat4 = 39.930332; List<Point> pointList = new ArrayList<>(); pointList.add(new Point(lng, lat)); pointList.add(new Point(lng1, lat1)); pointList.add(new Point(lng2, lat2)); pointList.add(new Point(lng3, lat3)); pointList.add(new Point(lng4, lat4)); long start = new Date().getTime(); boolean convex = isConvex(pointList); long end = new Date().getTime(); System.out.println(convex); System.out.println("执行时间:" + (start - end)); } }

使用的对象类如下
数据源对象

package cn.com.gome.shop.analyze.backend.data.assistant;

import java.io.Serializable;
import lombok.Data;

/**
 * dwr_store_town_sales_info
 * @author 
 */
@Data
public class DwrStoreTownSalesInfo implements Serializable {
    /**
     * 门店编码
     */
    private String storeCode;

    /**
     * 一级编码
     */
    private String firstCode;

    /**
     * 一级区域
     */
    private String firstArea;

    /**
     * 二级编码
     */
    private String secondCode;

    /**
     * 二级区域
     */
    private String secondArea;

    /**
     * 三级编码
     */
    private String thirdCode;

    /**
     * 三级区域
     */
    private String thirdArea;

    /**
     * 四级编码
     */
    private String fouthCode;

    /**
     * 四级区域
     */
    private String fouthArea;

    /**
     * 是否门店内数据:1是,0否
     */
    private Integer isStore;

    /**
     * 订单用户数
     */
    private Double orderUserNum;

    /**
     * 销量
     */
    private Double salesNum;

    /**
     * 销售额
     */
    private Double salesAmt;

    /**
     * 经度
     */
    private Double x;
    /**
     * 纬度
     */
    private Double y;

    /**
     * 数据统计同步时间
     */
    private String dt;

    private static final long serialVersionUID = 1L;
}

计算所需对象

package cn.com.gome.shop.analyze.backend.data.assistant;

import lombok.Data;

import java.math.BigDecimal;
import java.util.Objects;

/**
 * @author hanhao
 */
@Data
public class ShopSalesStreetPoint {
    public ShopSalesStreetPoint() {
    }

    public ShopSalesStreetPoint(double x, double y) {
        this.x = x;
        this.y = y;
    }

    /**
     * x坐标
     */
    private double x;

    /**
     * y坐标
     */
    private double y;

    /**
     * 边界查找算法中 是否被找到
     */
    boolean founded = false;

    public double getX() {
        return x;
    }

    public void setX(double x) {
        this.x = x;
    }

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }

    public boolean isFounded() {
        return founded;
    }

    public void setFounded(boolean founded) {
        this.founded = founded;
    }
    /** Constructor Getters & Setters */

    /**
     * 计算值,具体内容可配置
     */
    private BigDecimal value;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ShopSalesStreetPoint that = (ShopSalesStreetPoint) o;
        return Objects.equals(getX(), that.getX()) &&
                Objects.equals(getY(), that.getY());
    }

}

离散点

package cn.com.gome.shop.analyze.backend.data.assistant;

import lombok.Data;

import java.io.Serializable;

/**
 * 

* 离散点 *

*

 * 离散点
 * 
* */
public class Point implements Serializable { public Point() { } public Point(double x, double y) { this.x = x; this.y = y; } /** * x坐标 */ private double x; /** * y坐标 */ private double y; /** * 边界查找算法中 是否被找到 */ boolean founded = false; public double getX() { return x; } public void setX(double x) { this.x = x; } public double getY() { return y; } public void setY(double y) { this.y = y; } public boolean isFounded() { return founded; } public void setFounded(boolean founded) { this.founded = founded; } /** Constructor Getters & Setters */ }

你可能感兴趣的:(java)