Effective java6——方法重载

Java方法重载解析
本文详细解释了Java中的方法重载(Overload)概念,包括重载如何选择最合适的方法版本、不同类型转换的情况以及泛型和自动装箱拆箱可能引起的重载混乱。并通过具体实例帮助理解。

很多人分不清java的方法重载(Overload)和方法覆盖(Override),方法重载是方法名相同,而方法的参数不同(参数个数或者类型,注意不能通过参数名称和方法的返回值来进行方法重载),编译器在编译的时候就已经确定了合适的目标方法,也称为早绑定或者静态分派;方法覆盖是子类拥有和父类相同签名的方法(包括参数和返回值),方法覆盖在编译时无法确定要调用的目标方法,只有在运行时由对象本身来确定合适的调用方法,也称为晚绑定或者动态分派,严格意思上来讲,java的多态主要是使用了方法覆盖。

(1)方法重载选择最合适的版本:

方法重载依赖静态类型来定位方法执行版本的分派动作,被称为静态分派,静态分派发生在编译阶段,因此确定静态分派的动作实际上是由编译器来执行而不是由java虚拟机来执行的。另外编译器虽然可以确定出方法的重载版本,但是在很多情况下方法重载的版本可能不是唯一的,往往只能确定一个更加合适的版本,下面的例子演示方法重载中如何选择更加合适的版本:

public class Overload{

     public static void sayHello(Object arg){

         System.out.println("hello Object");

     }

    public static void sayHello(int arg){

           System.out.println("hello int");

    }

    public static void sayHello(long arg){

         System.out.println("hello long");

     }

   public static void sayHello(Character arg){

           System.out.println("hello Character");

   }

   public static void sayHello(char arg){

        System.out.println("hello char");

   }

  public static void sayHello(char ...arg){

         System.out.println("hello char...");

   }

   public static void sayHello(Serializable arg){

         System.out.println("hello Serializable");

    }

   public static void main(String[] args){

         sayHello('a');

   }

}

上面代码运行时会输出:

hello char

这个很容易理解,因为输入参数'a'是一个char类型数据,编译器会选择char类型参数的重载方法。如果注释掉sayHello(char arg)方法,那么输出会变成:

hello int

这次发生了一次类型的自动转换,'a'除了表示一个字符外,还可以表示数组65(a的ascii码),因此编译器选择int类型参数的方法重载也是合适的,如果再次注释掉sayHello(int arg)方法,那么输出会变成:

hello long

这次发生了两次类型的自动转换,首先'a'转换为整数65,然后进一步转换为长整型65L(java中byte->char(short)->int->long->float->double是可以自动转换的),匹配了long类型参数的方法重载。如果再次注释掉sayHello(long arg)方法,那么输出变成:

hello Character

这次发生了一次java基本数据类型自动装箱,'a'被封装为其包装类Character,编译器选择了Character类型参数的重载方法。如果再次注释掉sayHello(Character)方法,那么输出会变成:

hello Serializable

这个结果相信很多人都无法预料,首先'a'被封装为其包装类Character,此时找不到合适的匹配参数,只能继续向上转型为其父类型或者父接口,Character实现了Serializable接口,因此编译器选择了Serializable类型参数重载方法。如果再次注释掉sayHello(Serializable arg)方法,那么输出会变成:

hello Object

这个容易理解,首先'a'被封装为其包装类Character,此时找不到合适的匹配参数,只能继续向上转型为其父类或者父接口,如果找不到任何的父类或者父接口,则java中所有的类都是Object的子类,因此编译器会选择参数类型为Object的重载方法。如果再次注释掉sayHello(Object arg)方法,那么输出会变成:

hello char...

只剩最后一个重载方法了,此时字符'a'被当作字符数组的一个元素,可变参数是优先级最低的。

(2)方法重载时不会自动向下进行类型转换:

下面一个例子演示使用方法重载时不会自动向下进行类型转换:

public class CollectionClassifier{

    public static String classify(Set<?> s){

               return "Set';

    }

    public static String classify(List<?> l){

          return "List";

    }

   public static String classify(Collection<?> c){

         return "Unknown Collection";

    }

   public static void main(String[] args){

          Collection<?>[] collections = {

                new HashSet<String>(),new ArrayList<BigInteger>(),new HashMap<String>()};

           for(Collection<?> c : collections){

                System.out.println(classify(c));

       }

   }

}

本来期望这个程序会打印出"Set" "List” 和"Unknown Collection",但是运行过后打印出来3次"Unknown Collecion"。出现这个问题的原因是在for循环时编译类型都是Collection<?>,因此调用的也是Collection<?>参数类型的重载方法。

(3)泛型和自动装箱拆箱可能引起方法重载混乱:

下面的例子期望把集合中0,1,2三个元素删除:

public class SetList{

    public static void main(String[] args){

          Set<Integer> set = new TreeSet<Integer>();

          List<Integer> list = new ArrayList<Integer>();

          for(int i=-3;i<3;i++){

              set.add(i);

              list.add(i);

       }

       for(int i=0;i<3;i++){

                set.remove(i);

               list.remove(i);

          }

          System.out.println(set + "  " + list);

     }

}

我们期望打印出:

[-3,-2,-1] [-3,-2,-1]

而程序真正运行打印出的结果是:

[-3,-2,-1] [-2,0,2]

为什么List打印结果令人感到混乱且无法理解?

实际情况是:

Set集合是无序的,即元素没有下标索引,因此set.remove(i)调用选择的是重载方法remove(E),这里的E是集合的元素类型,因此程序会将i从int自动装箱为Integer,因此程序符合我们的预期。

List集合是有序的,即元素是有下标索引的,因此List的remove方法有两个:remove(int)根据元素下标删除元素,remove(E)直接删除指定的元素。上述程序中list.remove(i)调用重载的remove(int)方法,即首先删除列表[-3,-2,-2,0,1,2]中的第一个元素-3,接着删除列表[-2,-1,0,1,2]中第一个元素-1,最后删除列表[-2,0,1,2]中第二个元素1,因此得到的结果是[-2,0,2]。

如果想要得到预期的结果,需要将代码重构如下:

for(int i=0;i<3;i++){

        set.remove(i);

        list.remove((Integer)i);//或者list.reomve(Integer.valueOf(i));

  }

在JDK1.5之前,List没有被泛化,List接口的remove方法是remove(Object)和remove(int),由于int和Objec根本不同,并且由于JDK1.5之前没有自动装箱,因此不会出现重载问题。而在JDK1.5之后,List<E>被泛化,List<E>接口的remove变成了remove(E)和remove(int),泛型和自动装箱同时出现之后就有可能引起重载混乱。

由于方法重载这种静态分派的特性,如果使用不当可能不会产生我们预期的结果,同时也很难理解根本原因是什么,因此在编程中请务必保证当传递同样的参数时所用重载的方法行为一致,否则请谨慎使用方法重载。

本课题设计了一种利用Matlab平台开发的植物叶片健康状态识别方案,重点融合了色彩与纹理双重特征以实现对叶片病害的自动化判别。该系统构建了直观的图形操作界面,便于用户提交叶片影像并快速获得分析结论。Matlab作为具备高效数值计算与数据处理能力的工具,在图像分析与模式分类领域应用广泛,本项目正是借助其功能解决农业病害监测的实际问题。 在色彩特征分析方面,叶片影像的颜色分布常与其生理状态密切相关。通常,健康的叶片呈现绿色,而出现黄化、褐变等异常色彩往往指示病害或虫害的发生。Matlab提供了一系列图像处理函数,例如可通过色彩空间转换与直方图统计来量化颜色属性。通过计算各颜色通道的统计参数(如均值、标准差及主成分等),能够提取具有判别力的色彩特征,从而为不同病害类别的区分提供依据。 纹理特征则用于描述叶片表面的微观结构与形态变化,如病斑、皱缩或裂纹等。Matlab中的灰度共生矩阵计算函数可用于提取对比度、均匀性、相关性等纹理指标。此外,局部二值模式与Gabor滤波等方法也能从多尺度刻画纹理细节,进一步增强病害识别的鲁棒性。 系统的人机交互界面基于Matlab的图形用户界面开发环境实现。用户可通过该界面上传待检图像,系统将自动执行图像预处理、特征抽取与分类判断。采用的分类模型包括支持向量机、决策树等机器学习方法,通过对已标注样本的训练,模型能够依据新图像的特征向量预测其所属的病害类别。 此类课题设计有助于深化对Matlab编程、图像处理技术与模式识别原理的理解。通过完整实现从特征提取到分类决策的流程,学生能够将理论知识与实际应用相结合,提升解决复杂工程问题的能力。总体而言,该叶片病害检测系统涵盖了图像分析、特征融合、分类算法及界面开发等多个技术环节,为学习与掌握基于Matlab的智能检测技术提供了综合性实践案例。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值