接口,泛型,可变参数在代码向上抽去中的应用探究

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了接口,泛型,可变参数在代码向上抽去中的应用探究相关的知识,希望对你有一定的参考价值。

  JAVA作为一种面向对象的语言,类和对象可以说是搭建起JAVA体系的基本框架,我们可以将类看做是对众多实体对象共性的向上抽取,而接口则是对类共性的向上抽取,我将接口理解为一种规则,一种规范,同时也是多态的应用中我们作为形式参数传递的最顶层的父类,因此接口的功能非常强大(能装B),我们在sun公司定义的API中可以经常看到它的身影,它也是架构师手中搭建框架的利器,因此以后我们会经常使用到它(能装B).

-------------------------------------------------------------------------------------------------------------------------------------

  程序,功能块的向上抽取,是简化代码和增强扩展性的基本思路,而接口,泛型和可变参数则是我们向上抽取,所要用到的基本工具,话不多说,下面我用三个简单的例子来进行演示:

  案例一:

//创建一个Student对象,对外提供set和get方法
public class Student {
    private String name;
    private int age;
    private int num;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
    
}
public static void main(String[] args) {
        //在这里我们创建一个treeset集合,用来存储Student对象,我们知道牛X的二叉树算法为我们提供了强大的排序功能,但是具体的排序方式需
        //要我们用户自己来定义,所以这里我以匿名内部内的方式传入一个比较器对象(compartor接口的实现子类对象),来定义我所需要的具体的排序方式
        TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int flag = o1.getNum() - o2.getNum();
                return flag == 0? 1 : flag;
            }
        });
    }

  在这个案例中,API的设计者想要将自动排序的功能进行向上抽取,但是又不知道,用户想要以什么样的形式进行排序,是按照学生年龄呢,姓名呢,学号能还是三者结合进行排序,所以设计者就定义出一个comparator接口,在里面定义了一个compare()的抽象方法,再将这个接口以形式参数的形式由构造传入treeset集合中,这样我们用户在创建treeset集合的时候如果想要自定义的它的排序功能就必须,传入一个comparator的实现子类对象,而它的实现子类则必须重写compare方法.在这个案例中,API的设计者以接口和多态相结合的方式完成了自定义排序功能的向上抽取

 

  案例二:这里我以JDBC与数据库连接中,对查找方法的向上抽取为例

 

//自定义一个接口,其中包含具体处理结果集的的抽象方法handle,将结果集以参数形式传入
public interface ResultSetHandler<T> {
    public T handle(ResultSet rs) throws SQLException;
}
//我们想要将查询方法进行向上提取,有三个问题需要解决:
    //1.sql语句由用户传入
    //2.我们对查询参数的设定要根据sql语句而定,我们无法确定参数的具体个数和类型,因此这里我们传入一个object类型的可变参数,可变参数的本质
    //就是一个集合
    //3.这也是最难解决的一点,根据不同的查询方式,会返回不同的结果集,我们对不同的结果集的封装和返回的形式也不相同,所以这里我们传入一个自定义的
    //接口ResultSetHandler,以这种方式将处理结果集的工作交给调用者来完成
    //因为返回值的类型并不确定是对象还是集合,所以这里引入了泛型
    public static <T> T query(String sql, ResultSetHandler<T> rsh,
            Object... args) {
        //提全局变量
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            //这里通过我自定义的一个工具类来获取connection对象
            conn = JDBCUtils.getConnection();
            // 预编译SQL:
            stmt = conn.prepareStatement(sql);
            //通过PreparedStatement对象参数元数据对象
            ParameterMetaData metaData = stmt.getParameterMetaData();
            // 获得参数的个数:
            int count = metaData.getParameterCount();
            for (int i = 1; i <= count; i++) {
                stmt.setObject(i, args[i - 1]);
            }
            // 执行SQL:
            rs = stmt.executeQuery();
            // 数据封装:通过调用接口中交由调用者自己重写的handle方法对获取到的结果集进行封装和返回
            T t = rsh.handle(rs);
            return t;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.release(rs, stmt, conn);
        }
        return null;
    }
// 测试查询一个账户:这里我们以匿名内部类的形式传入ResultSetHandler的实现子类对象,并重写了handle方法,对结果集进行了具体的处理
        Account account = MyDBUtils.query("select * from account where id = ?", new ResultSetHandler<Account>() {
            @Override
            public Account handle(ResultSet rs) throws SQLException {
                Account account = new Account();
                if(rs.next()){
                    account.setId(rs.getInt("id"));
                    account.setName(rs.getString("name"));
                    account.setMoney(rs.getDouble("money"));
                }
                return account;
            }
        }, 2);
        System.out.println(account);

  具体的操作在代码中已经写得很具体,这里就不再赘述

   案例三:

  以前在我大学里面有一个煎饼店,只卖山东杂粮煎饼和台湾手抓饼,但是生意却好得不得了,几乎天天从早上六点多到晚上十点多都有一大群人在排队,想吃不排上个十几分钟队根本吃不上,所以我再举一个煎饼店的例子,希望可以帮助大家理解.

  在这里,我们将摊煎饼看作一个功能块,在将这个功能块向上抽取之前,每次想要吃煎饼都必须要我自己去准备材料,自己摊煎饼非常麻烦,所以我考虑将这个功能块向上抽取(请一个会摊煎饼的阿姨来帮我摊煎饼),这个时候煎饼阿姨就会遇到两个问题,第一个:煎饼有杂粮煎饼和手抓饼,味道有加辣和不加辣,不同的的煎饼还有不同的酱可以选,煎饼阿姨没办法在用户到来之前知道应该摊哪一种煎饼(每个人的煎饼都是DIY的);第二个:在确定具体的煎饼类型的做法之前煎饼阿姨无法确定材料的种类和数量.

public interface Type {
    //在接口中定义一个具体设置煎饼类型的抽象方法
    public void setType();
}

   针对第一个问题我们就可以定义一个接口,来具体设置煎饼的类型,然后将接口以形式参数的形式传入摊煎饼的方法中,当我们来调用摊煎饼方法的时候就需要传入这个接口的实现子类对象,在这个实现子类中必须重写setType()方法,即将设置煎饼类型口味的工作以接口的形式抛给了调用者

 

public class PancakeLady {

    public void madePancake(Type tp ,String...data) {
        //调用接口中的方法
        tp.setType();
        //获取到可变参数中的数据进行遍历打印
        for (String str : data) {
            System.out.print(str + ",");
        }
    }
}

 

 

 

  对于第二个问题,我们采用可变参数的形式来就收任意类型,任意个数的参数(这里为了简便,设置为了String类型)

 

public static void main(String[] args) {
        PancakeLady pl = new PancakeLady();
        pl.madePancake(new Type() {
            @Override
            public void setType() {
                // TODO Auto-generated method stub
                System.out.print("这是一个加辣的山东杂粮煎饼,里面有:");
            }
        }, "鸡蛋","火腿","里脊");
    }
    //这是一个加辣的山东杂粮煎饼,里面有:鸡蛋,火腿,里脊,

 

最后我们在调用的时候我们以匿名内部类的形式传入Type接口的实现子类,来完成了煎饼类型的定义

------------------------------------------------------------------------------------------------------------------------------

  将不能确定的问题抛向调用者,也是最大化调用者权限的体现,JAVA中的异常处理机制采用的就是这种方式

 

以上是关于接口,泛型,可变参数在代码向上抽去中的应用探究的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin泛型 ② ( 可变参数 vararg 关键字与泛型结合使用 | 使用 [] 运算符获取指定可变参数对象 )

使用可变参数泛型进行方法返回特化

13.3.5 接口和委托的泛型可变性限制和说明

JAVA中,关于可变参数和泛型的问题。

java中的泛型的使用与理解

java泛型中可变参数的是使用