Java SE 是什么,包括哪些内容(九)?
本文内容参考自Java8标准
1、为什么会有方法重载?
我的上一篇博文有关Java中构造方法介绍的内容中其实已经提到了重载的概念:就是下面这个例子:
// 创建一个类代表人类
public class Person{
//创建一个变量代表姓名
private String name;
//创建一个变量代表性别
private String sex;
//创建一个变量代表年龄
private int age;
//希望创建对象的时候不用指定任何内容,所以需要以下形式的构造方法
public Person(){}
//希望创建对象的时候能指定他的姓名、性别、年龄,所以需要以下形式的构造方法
public Person(String name,String sex,int age){
//赋值操作,将传入的实际参数赋值给当前对象的变量。
//this关键字的作用后面博文会详细提到。
this.name = name;
//同上
this.sex = sex;
//同上
this.age = age;
}
//希望创建对象的时候能指定他的姓名、性别,所以需要以下形式的构造方法
public Person(String name,String sex){
//赋值操作,将传入的实际参数赋值给当前对象的变量。
//this关键字的作用后面博文会详细提到。
this.name = name;
//同上
this.sex = sex;
}
//希望创建对象的时候能指定他的性别、年龄,所以需要以下形式的构造方法
public Person(String name,String sex){
//赋值操作,将传入的实际参数赋值给当前对象的变量。
//this关键字的作用后面博文会详细提到。
this.sex = sex;
//同上
this.age = age;
}
//希望创建对象的时候能指定他的姓名、年龄,所以需要以下形式的构造方法
public Person(String name,String sex){
//赋值操作,将传入的实际参数赋值给当前对象的变量。
//this关键字的作用后面博文会详细提到。
this.name = name;
//同上
this.age = age;
}
//希望创建对象的时候仅指定他的姓名,所以需要以下形式的构造方法
public Person(String name,String sex){
//赋值操作,将传入的实际参数赋值给当前对象的变量。
//this关键字的作用后面博文会详细提到。
this.name = name;
}
//希望创建对象的时候仅指定他的性别,所以需要以下形式的构造方法
public Person(String name,String sex){
//赋值操作,将传入的实际参数赋值给当前对象的变量。
//this关键字的作用后面博文会详细提到。
this.sex = sex;
}
//希望创建对象的时候仅指定他的年龄,所以需要以下形式的构造方法
public Person(String name,String sex){
//赋值操作,将传入的实际参数赋值给当前对象的变量。
//this关键字的作用后面博文会详细提到。
this.age = age;
}
//以上共提供了八种构造方法。你可以选择其中的一种创建对象。
public static void main(String[] args){
//直接创建对象,不提供任何实际的参数
Person p = new Person();
//创建一个姓名是小王,性别男,年龄23岁的对象
//根据构造方法里面的形参顺序依次填入实际参数的值。
Person p1 = new Person("小王","男",23);
//创建一个只知道姓名的对象
Person p2 = new Person("小王");
//剩下的例子可以自己尝试。
//需要注意的是,在创建对象的时候,利用哪个构造方法都行
//最重要的是匹配实际使用参数类型、数量的那个构造方法的形式存在!
}
}
提示:上面举例代码中提供了八种方式(八个构造方法)创建/初始化对象!
非常有必要说明一点:个人觉得Java重载的出现就源自构造方法。
⑴、Java中的构造方法名称必须与类名称完全一致(原因在上一篇博文中已经详细描述)。
⑵、Java中允许有多种创建/初始化对象的方法。
推断:
结合以上两点,我们可以推断出以下情况:构造方法的方法名称是唯一的(因为要与类名称完全一致,类名称是唯一的),但是又允许有多种创建/初始化对象的方式(对应有多个构造方法),那么,我们如何来区分这些构造方法呢?方法名称肯定不行,那就只剩下另一个部分了-----形式参数(严格来说应该是形式参数的列表,因为形式参数允许0个以上至无限)。
结论:
⑴、Java的一个类中(注意我这里强调了范围:一个类中!)不允许同时存在两个构造方法的方法名和形式参数列表完全相同,也就是不允许同时存在两个完全一样的方法。
⑵、Java的一个类中(注意我这里强调了范围:一个类中!)允许同时存在两个构造方法仅方法名称相同但是参数列表不同,Java的发明者为这种现象定义了一个名称,就是:方法重载。
方法重载中方法名称唯一源自于类名称唯一,方法形式参数列表不同源自于一个类中不能同时存在两个完全一样的构造方法以及形式参数列表不同正代表了多个创建/初始化对象的方式(实际上,如果两个构造方法的名称以及形式参数列表完全相同根本没有任何意义)。
2、方法重载不仅适用于构造方法,对类中的普通方法也同样适用。
一个类中不但能存在方法名称相同但是形式参数列表不同的构造方法,也能存在方法名称相同但是形式参数列表不相同的普通方法。
你可以这么理解,一个类中存在普通方法的重载是因为这个类的对象允许一种行为有多种不同的实现方式,就好比一道题目有多种解决方式。
举例(账号密码登录验证):
// 账号密码登录验证
//用户类User
public class User{
//代表账号的变量name
private String name;
//代表密码的变量password
private String password;
//构造方法一
public User(){}
//构造方法二
public User(String name){
this.name = name;
}
//构造方法三
public User(String name,String password){
this.name = name;
this.password = password;
}
//变量name和password的Set和Get方法省略...自行脑补。
}
//-------------------------------------------------------------------------------------------------------------------------
//登录验证的类Login
public class Login{
//重载的登录方法login,形式参数列表用来接收客户端输入的账号和密码
public void login(String name,String password){
//创建一个变量接收从数据库搜索出来的账号
String name1;
//创建一个变量接收从数据库搜索出来的密码
String password1;
//从数据库搜索账号密码后进行比对,完全一致就允许登录
//数据库的代码省略.....
//如果账号密码完全一致允许登录
if(name1.equals(name)&&password1.equals(password)){
//允许登录的代码
}
}
//重载的登录方法login,形式参数列表用来接收一个登录用户对象User
public void login(User user){
//创建一个变量接收从数据库搜索出来的账号
String name1;
//创建一个变量接收从数据库搜索出来的密码
String password1;
//从数据库搜索账号密码后进行比对,完全一致就允许登录
//数据库的代码省略......
//如果账号密码完全一致允许登录
//user.getName()和user.getPassword()是获得当前客户端输入的账号和密码
//只不过是用了一个User对象进行存储
if(user.getName().equals(name)&&user.getPassword().equals(password)){
//允许登录的代码
}
}
//登录方式还有指纹登录:
//重载的登录方法login,形式参数列表用来接收一个指纹对象
//比较复杂,就不举例具体的代码了。
//登录方式还有短信验证码登录:
//重载的登录方法login,形式参数列表用来接收一个短信验证码。
//比较复杂,就不举例具体的代码了。
//登录方式还有秘钥登录:
//重载的登录方法login,形式参数列表用来接收一个秘钥。
//比较复杂,就不举例具体的代码了。
//----------------------------------还有其他的验证登录的方式--------------------------------------------------
}
//------------------------------------------------------------------------------------------------------------------------
//登录验证使用:
//登录验证测试类Login
public class TestLogin{
//程序执行的入口main方法
public static void main(String[] args){
//比如最开始使用的是账号密码登录方式
//创建登录的对象
Login login = new Login();
//调用对应的方法
login.login("小王","123456");
//----------------------------------------------
//如果这个时候想换登录方式:
login.login(只要替换这里里面的参数就行了,方法不用换);
}
}
3、实际中方法重载举例
String构造方法重载举例:
StringBuffer构造方法和普通方法重载举例:
通过以上Java文档中的实际例子,我们可以看出,在Java实际编程中,重载是很普遍的情况。
4、Java编程思想第四版
以下内容是"Java编程思想最新版"内对方法重载的解释:
任何程序设计语言都具备一项重要的特性就是对名字的运用,当创建一个对象时,也就给此对象分配到的存储空间取了一个名称,那么方法名称自然就是给动作取的名称,通过名字,你可以引用所有的对象和方法。名称取的好可以使系统更易于理解和修改(通俗理解就是,看到对象名称就知道这个对象是干什么用的,看到方法名称,就知道这是一个什么动作)。
将人类生活方式(原文是"语言",但是我认为这里使用"方式"更合适)中存在的细微差别"映射"到程序设计语言中时,问题随之而生。在日常生活中,相同的词可以表达多种不同的含义,它们被"重载"了,特别是含义之间的差别很小时,这种方式就特别有用(指的就是用一个词表达多种不同含义)。
你可以说"清洗衬衫",“清洗车”,“清洗狗”。但是如果你硬要说"以洗衬衫的方式清洗衬衫",“以洗车的方式清洗车”,“以洗狗的方式清洗狗"就会显得很愚蠢,因为重点在清洗,而不是如何去洗,至于如何洗,人们可以根据自己的生活常识领悟。根本不需要对如何清洗(所执行的动作)在语言中做出明确的区分。
-------相同的是清洗(相同的方法名称),不同的是清洗的方式(不同的形式参数列表),大多数人类语言具有很强的"冗余性”,即使漏掉了几个词,也可以完全推断出所有的含义,不需要对每个概念都使用不同的词汇-----可以从具体的语境中去推断。
大多数程序设计语言(尤其是C语言)要求为每个方法(在这些语言中常被称为函数)都提供一个独一无二的名称(标识符),所以绝不能用一个名为print()的函数显示了整数之后,又用一个名称为print()的函数显示浮点数------每个函数都要有唯一的名称。
在Java(还有C++)里,构造方法是强制重载方法名的另一个原因,既然构造方法的名称由类名决定,就只能有一个构造方法名称(因为类名称唯一),那么,要想用多种方式创建/初始化对象怎么办呢?
假设你要创建一个对象,即可以用标准方式进行初始化,也可以从文件里读取信息来初始化-----这就需要两个构造方法,一个是默认构造方法,另一个用字符串作为形式参数-----该字符创表示初始化对象所需的文件名称。由于都是构造方法,所以他们必须有相同的名称,即类名,为了让方法名称相同而形式参数列表不同的方法存在,必须要用到方法重载。同时,尽管方法重载是构造方法所必须的,但它亦可应用于其他方法,且用法同样方便。
代码举例:
// 方法重载举例
//类Tree
class Tree{
//保存树高度的变量height
int height;
//构造方法一:空构造方法.
//打印字符串"Planting a seeding",给变量
//height赋值0.
Tree(){
System.out.print("Planting a seeding");
height = 0;
}
//构造方法二:形参列表有一个String类型的变量.
//打印字符串Creating new tree that is"+height+"feet tall",给变量
//height赋值initialHeight(创建/初始化对象传入的值是所少,
//initialHeight就是多少,height也就是所少).
Tree(int initialHeight){
//initialHeight用来保存创建/初始化对象传入的值,
//再讲这个值赋值给类变量height.
height = initialHeight;
System.out.print("Creating new tree that is"+height+"feet tall");
}
//重载方法一.形参列表为空
void info(){
System.out.print("Tree is"+height+"feet tall");
}
//重载方法二.形参列表有一个String类型的变量。
void info(String s){
System.out.print("Tree is"+height+"feet tall");
}
}
//----------------------------------------------------------------------
//测试的类Overloading.
public class Overloading{
//程序的执行入口main方法。
public static void main(String[] args){
//for循环,执行5次。
for(int i=0;i<5;i++){
//每次循环创建一个对象,使用的是有一个String类型形参列表的构造方法。
//也就是创建出来的Tree对象都是有初始高度的,这个高度的值就是i.
Tree t = new Tree(i);
//分别调用两个方法。
t.info();
t.info("Overloaded method");
}
//循环执行完毕后,继续调用构造方法。
//这时调用的是空构造方法。
//注意一点,它将height的值变为了0.
new Tree();
}
}
创建Tree对象的时候,既可以不含参数,也可以用树的高度当参数,前者表示一棵树苗,后者表示已有一定高度的树木,要支持这种创建方式,得有一个默认构造方法和一个采用现有高度作为参数的构造方法。
或许你还想通过多种方式调用info()方法,例如你想显示额外信息,你可以调用带参数的info(String s)方法,没有的话就用info()方法,如果对明显相同的概念使用了不同的名称,那一定会让人很纳闷,好在有了方法重载,可以为两者使用相同的名称。
5、重载方法的提示
⑴、参数列表的顺序不同算不算重载?
前面已经提过了,重载方法的关键是方法名称相同,但是参数列表不同,那么问题来了:如果参数列表的类型和数量都完全一样,但是顺序不同行不行?
行!非常可以!但是不建议这么做!
// 参数列表的类型和数量一样,顺序不一样。
public void info(String s,int i){}
public void info(int i,String s){}
//以上两个方法绝对算重载,但是不建议这么做,因为这会造成代码难以维护,
//当然,如果一切都尽在你的掌握之中,也没什么问题。
⑵、方法的返回类型不同算不算重载?
不算!
可能有的人会想,在区分重载方法的时候,为什么只能以类名和方法的形式参数列表作为标准来区分呢?能否考虑用方法的返回值来区分呢?
比如下面两个方法,虽然它们的名称和形式参数列表相同,但是却很容易区分它们:
// 方法的返回值区分重载方法。
//没有返回值
void f(){}
//返回类型为int。
int f(){return 1;}
实际上,只要编译器能在语义上明确区分就行,比如你这么使用:
int x = f();
编译器就知道肯定是有返回值的f()。
但是有的时候我们并不会关心返回值,我们更关心方法使用的其他效果,这个时候你可能会直接调用方法而忽略它的返回值。
所以,一旦你如下面这样调用:
f();
编译器就不知道你调用的是哪一个方法了。
因此,根据方法的返回值来区分重载方法是不行的。
再比如:
// 方法的返回类型不同不算重载
public void info(String s,int i){}
public String info(String s,int i){}
//以上两个方法绝对不算重载,因为调用方法的时候根本区别不了它们。
//因为调用的形式都是info(String,int);
6、继承关系中有关重载方法的说明
在继承关系中,常常会发生子类重新实现父类方法的情况。比如:
// 子类覆写父类的方法
//父类
class boss{
//父类中的方法实现
public void get(int i){
//打印字符串"父类的方法!"
System.out.println(i);
}
}
//子类
class worker extends boss{
//重新实现父类中的方法get()
public void get(double f){
//打印字符串"子类的方法!"
System.out.println(f);
}
}
虽然子类worker重新实现了父类boss的方法get(),但是并不会影响父类中get()方法的使用(C++中不存在这种情况,如果子类重新实现了父类的方法,那么父类的方法将不可用了)。
测试上面的代码:
// 测试父类和子类的重载方法
public class test{
//程序的执行入口main方法。
public static void main(String[] args){
//创建子类worker的对象
worker w = new worker();
//调用子类中覆写的方法。
w.get(5);
//调用父类中的重载方法
//虽然子类将父类的方法进行了重载,但是不影响它的使用。
//C++中的情况则不同,一旦子类重写了父类的重载方法,则父类中的不能再使用了。
w.get(5.5);
}
PS:时间有限,有关Java SE的内容会持续更新!今天就先写这么多,如果有疑问或者有兴趣,可以加QQ:2649160693,并注明优快云,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正!