再谈方法
Overload 重载
- 定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
两同一不同
- 同一个类、相同方法名
- 参数列表不同:参数个数不同,参数类型不同,参数顺序不同
- 判断是否是重载:
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
- 在通过对象调用方法时,如何确定某一个指定的方法:
- 方法名 —> 参数列表
练习
编写程序,定义三个重载方法并调用。方法名为mOL
。
- 三个方法分别接收一个
int
参数、两个int
参数、一个字符串参数。
分别执行平方运算并输出结果,相乘并输出结果,输出字符串信息。
在主类的main ()
方法中分别用参数区别调用三个方法。
- 定义三个重载方法max(),
第一个方法求两个int
值中的最大值,
第二个方法求两个double
值中的最大值,
第三个方法求三个double
值中的最大值,
并分别调用三个方法。
public class OverloadExer {
public static void main(String[] args) {
OverloadExer test = new OverloadExer();
System.out.println("--------第一题---------");
int a = test.mOL(5);
System.out.println(a);//25
System.out.println(test.mOL(5, 4));//20
test.mOL("我爱你");//我爱你
System.out.println("--------第二题---------");
System.out.println(test.max(3, 4));//4
System.out.println(test.max(3.4, 4.3));//4.3
System.out.println(test.max(5.6, 5.4, 3.4));//5.6
}
//1.
public int mOL(int i) {return i*i;}
public int mOL(int i, int j) {return i*j;}
public void mOL(String s) {System.out.println(s);}
//2.
public int max(int i, int j){return (i > j)? i:j;}
public double max(double i, double j) {return (i > j)? i:j;}
public double max(double i, double j,double k){
double max = (i > j)? i:j;
return (max > k)? max:k;
}
}
可变个数形参
JDK5.0
增加
-
可变个数形参的格式:(用三个点来表示可变)
数据类型 ... 变量名
-
当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个。。。
-
可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
-
可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
关于这点,是java的历史原因。在
JDK5.0
之前,我们想要传入可变参数需要使用数组,像这样:public void show(String[] *strs*){方法体}
其实这和可变参数实际是一样的,只是数组用起来麻烦点,证据就是,你甚至可以使用可变参数的方式声明方法,但是用数组的方式在方法中进行调用。
-
可变个数形参在方法的形参中,必须声明在末尾
The variable argument type String of the method show must be the last parameter
很好理解,如果把可变形参放在前面,编译器如果碰到多个参数
public void show(String ...strs,int i)
,接收参数时,就不知道到底哪个是可变形参,哪个是具体的形参了,别忘了有些参数之间是有类型转换的,万一是double和int就头疼了。 -
可变个数形参在方法的形参中,最多只能声明一个可变形参。
可以用数组声明多个,但是没必要,根据实际情况来。
值传递机制
-
关于变量的赋值:
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
-
区分形参实参
- 形参:方法定义时,声明的小括号内的参数
- 实参:方法调用时,实际传递给形参的数据
重点:
如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。
内存分析
引出一个不是太靠谱的结论:
如果你想在方法中对某对象的属性进行修改,一定要将整个对象传进去。
但是在你明白这句话之前,请一定记住
如果变量是基本数据类型,此时赋值的是变量真实存储的数据值。
如果变量是引用数据类型,此时赋值的是变量存储数据的地址值。
String就是一个特殊的反例。
public class ValueTransferTest {
public static void main(String[] args) {
String s1 = "hello";
ValueTransferTest test = new ValueTransferTest();
test.change(s1);
System.out.println(s1);//hi~~
}
public void change(String s){
s = "hi~~";
}
}
当你在change
方法中修改字符串的值时,你会认为,我传进去的是一个对象,对这个对象进行修改,那方法外再次调用字符串,也应该是修改后的字符串值hi~~
。但并不是这样,String很特殊,它是存储在常量池里的char型数组,数组有什么特性?**长度不可变。**如果我们进行修改,那就只能重新写一个char型数组然后赋值地址给s
,可是s1变量并没有被修改,里面还是之前的地址。
所以本质上,还是传递了地址值,根据情况不同,有不同表现。当你没有完全理解传值的本质时,就不要记这个结论了。
进阶的例子
把握上文的重点是你理解值传递机制的关键。
练习
-
定义一个Circle类, 包含一个double型的radius属性代表圆的半径, 一个
findArea()
方法返回圆的面积。 -
定义一个类
PassObject
, 在类中定义一个方法printAreas()
, 该方法的定义
如下:public void printAreas(Circle c, int time)
在printAreas
方法中打印输出1到time之间的每个整数半径值, 以及对应的面积。
例如, times为5, 则输出半径1, 2, 3, 4, 5, 以及对应的圆面积。 -
在main方法中调用
printAreas()
方法, 调
用完毕后输出当前半径值。 程序运行结果如图
所示。
public class Circle {
double radius;
public double findArea(double radius) {
return Math.PI * radius * radius;
}
}
public class PassObject {
public static void main(String[] args) {
PassObject test = new PassObject();
Circle c = new Circle();
test.printAreas(c, 5);
System.out.println("now radius is " + c.radius);
}
public void printAreas(Circle c, int time){
System.out.println("radius\t\tArea");
int i = 0;
for (; i < time; i++) {
c.radius = i + 1;
System.out.println(c.radius + "\t\t" + c.findArea(i+1));
}
c.radius = i+1;
}
}
递归方法
递归方法:一个方法体内调用它自身。
方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
public static void main(String[] args) {
// 计算1-100之间所有自然数的和
// 方式一(我们最开始使用的方法):
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
System.out.println(sum);
// 方式二:
RecursionTest test = new RecursionTest();
int sum1 = test.getSum(100);
System.out.println(sum1);
}
// 计算1-n之间所有自然数的和
public int getSum(int n) {// 3
if (n == 1) {
return 1;
} else {
return n + getSum(n - 1);
}
}
封装和隐藏
为什么需要封装?封装的作用和含义?
我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
我们程序设计追求**“高内聚,低耦合”**。
- 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合 : 仅对外暴露少量的方法用于使用。
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露
的暴露出来。 这就是封装性的设计思想。
问题的引入
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs()
)同时,我们需要避免用户再使用对象.属性
的方式对属性进行赋值。则需要将属性声明为私有的(private).
此时,针对于属性就体现了封装性。
getter\setter方法
public class Customer {
private int id;//private保证无法通过“对象.属性”方式调用
private String name;
public void setId(int i){//set传参进去,设置属性,无需返回值
id = i;
}
public int getId(){//get无需传参,返回属性值即可
return id;
}
}
封装的体现
我们将类的属性xxx私有化private
,同时,提供公共的public
方法来获取getXxx
和设置setXxx
此属性的值
还体现在: ①如上 ② 不对外暴露的私有的方法 ③ 单例模式 …
权限修饰符
封装性的体现,需要权限修饰符来配合。
Java规定的4种权限(从小到大排列):private、缺省、protected 、public
修饰符 | 类内部 | 同一个包 | 不同包子类 | 同一工程 |
---|---|---|---|---|
private | yes | |||
(缺省) | yes | yes | ||
protected | yes | yes | ||
public | yes | yes | yes | yes |
对于类的权限修饰只能用public和缺省
4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
这四种权限中protected涉及继承,等学过继承之后再来验证。
总结
Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
构造器
构造器(或构造方法、constructor)
构造器的作用
-
创建对象
-
初始化对象的信息
说明
- 如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
- 定义构造器的格式:
权限修饰符 类名(形参列表){}
- 一个类中定义的多个构造器,彼此构成重载
- 一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
- 一个类中,至少会有一个构造器。
练习
编写两个类,TriAngle
和TriAngleTest
,其中TriAngle
类中声明私有的底边长base
和高height
,同时声明公共方法访问私有变量。
此外,提供类必要的构造器。另一个类中使用这些公共方法,计算三角形的面积。
public class TriAngle { //angle:角 angel:天使
private double base;
private double height;
//构造器,无参、有参
public TriAngle(){}
public TriAngle(double b,double h){
base = b;
height = h;
}
//getter、setter方法
public void setBase(double b){
base = b;
}
public double getBase(){
return base;
}
public void setHeight(double h){
height = h;
}
public double getHeight(){
return height;
}
}
public class TriAngleTest {
public static void main(String[] args) {
TriAngle triAngle = new TriAngle(1.0,2.0);
System.out.println("三角形面积为" + (triAngle.getBase() * triAngle.getHeight() / 2));
}
}
属性赋值的先后顺序
简单了解即可,个人觉得作用不是很大,实际开发过程中这几个不会混着用。
JavaBean
是一种Java语言写成的可重用组件
所谓JavaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
public class Customer {
private int id;
private String name;
public Customer(){}
public void setId(int i){
id = i;
}
public int getId(){
return id;
}
public void setName(String n){
name = n;
}
public String getName(){
return name;
}
}
this关键字
是什么
this可以用来修饰、调用:属性、方法、构造器
this可以理解为:当前对象 或 当前正在创建的对象
为什么要用
如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
怎么用
使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。
this调用构造器
- 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
- 构造器中不能通过"this(形参列表)"方式调用自己
- 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
- 规定:"this(形参列表)"必须声明在当前构造器的首行
- 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
class Person{
private String name;
private int age;
public Person(){
String info = "Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)";
System.out.println(info);
}
public Person(String name){
this();
this.name = name;
}
public Person(int age){
this();
this.age = age;
}
public Person(String name,int age){
this(age);
this.name = name;
//this.age = age;//上面的fan
//Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)
}
}
练习
- 按照下面的UML图写两个类,添加必要的构造器,综合应用构造器的重载,this关键字。
答案:
package com.atguigu.exer2;
public class Boy {
private String name;
private int age;
public Boy() {}
public Boy(String name, int age) {
this.name = name;
this.age = age;
}
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 void marry(Girl girl) {
System.out.println("我想娶" + girl.getName() + "。");
}
public void shout() {
if (this.age < 19) {
System.out.println("先谈谈恋爱吧~~~");
} else {
System.out.println("可以合法登记结婚。");
}
}
}
package com.atguigu.exer2;
public class Girl {
private String name;
private int age;
public Girl() {}
public Girl(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public void marry(Boy boy) {
System.out.println("我想嫁给" + boy.getName() + "。");
boy.marry(this);
System.out.println("可以喝喜酒了!");
}
/**
* 对比当前对象和形参对象的年龄
* @param girl
* @return 返回正数说明形参对象大,返回负数说明当前对象大,0一样大
*/
public int compare(Girl girl) {
return girl.age - this.age;
}
}
package com.atguigu.exer2;
public class BoyGirlTest {
public static void main(String[] args) {
Boy boy = new Boy("罗密欧", 21);
boy.shout();
Girl girl = new Girl("朱丽叶", 18);
girl.marry(boy);
Girl girl1 = new Girl("祝英台",19);
int compare = girl.compare(girl1);
if(compare > 0){
System.out.println(girl1.getName() + "大");
}else if(compare < 0){
System.out.println(girl.getName() + "大");
}else{
System.out.println("一样大");
}
}
}
-
写一个名为 Account 的类模拟账户。该类的属性和方法如下图所示。该类包括的属性:
账号id
,余额balance
,年利率annualInterestRate
;包含的方法:访问器方法(getter 和 setter
方法),取款方法withdraw()
,存款方法deposit()
。创建 Customer 类
提示: 在提款方法
withdraw
中,需要判断用户余额是否能够满足提款数额的要求,如果不
能,应给出提示。a. 声明三个私有对象属性: firstName、 lastName 和 account。
b. 声明一个公有构造器,这个构造器带有两个代表对象属性的参数(f 和 l)
c. 声明两个公有存取器来访问该对象属性,方法getFirstName
和getLastName
返回相应的属
性。
d. 声明setAccount
方法来对account
属性赋值。
e. 声明getAccount
方法以获取account
属性。
3.写一个测试程序。
(1) 创建一个 Customer ,名字叫 Jane Smith, 他有一个账号为 1000,余额为 2000 元,
年利率为 1.23% 的账户。
(2) 对 Jane Smith 操作。
存入 100 元,再取出 960 元。 再取出 2000 元。
打印出 Jane Smith 的基本信息成功存入 : 100.0 成功取出: 960.0 余额不足,取款失败 Customer [Smith, Jane] has a account: id is 1000, annualInterestRate is 1.23%, balance is 1140.0
答案
package com.atguigu.yuan;
public class Account {
private int id;
private double balance;
private double annualInterestRate;//年利率
public Account(int id, double balance, double annualInterestRate) {
this.id = id;
this.balance = balance;
this.annualInterestRate = annualInterestRate;
}
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public double getBalance() {return balance;}
public void setBalance(double balance) {this.balance = balance;}
public double getAnnualInterestRate() {return annualInterestRate;}
public void setAnnualInterestRate(double annualInterestRate) {this.annualInterestRate = annualInterestRate;}
/**
* 取钱
* @param amount 数额
*/
public void withdraw(double amount){
if (amount > this.balance) {
System.out.println("余额不足,取款失败。");
} else {
this.balance -= amount;
System.out.println("成功取出:" + amount + "\n现在余额为:" + this.balance);
}
}
/**
* 存钱
* @param amount 数额
*/
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
System.out.println("成功存入:" + amount + "\n现在余额为:" + this.balance);
}else {
System.out.println("存款失败。");
}
}
}
package com.atguigu.yuan;
public class Customer {
private String firstName;
private String lastName;
private Account account;
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {return firstName;}
public String getLastName() {return lastName;}
public Account getAccount() {return account;}
public void setAccount(Account account) {this.account = account;}
}
package com.atguigu.yuan;
public class CustomerTest {
public static void main(String[] args) {
Customer jane = new Customer("Jane", "Smith");
jane.setAccount(new Account(1000, 2000, 1.23));
jane.getAccount().deposit(100);
jane.getAccount().withdraw(960);
jane.getAccount().withdraw(2000);
System.out.println("Customer [" + jane.getFirstName() + ","
+ jane.getLastName() + "] has an account: id is "
+ jane.getAccount().getId() + ", annualInterestRate is "
+ jane.getAccount().getAnnualInterestRate() + "%,balance is "
+ jane.getAccount().getBalance());
}
}
- 按照如下的 UML 类图,创建相应的类,提供必要的结构
在提款方法withdraw()
中,需要判断用户余额是否能够满足提款数额的要求,如果不能,应给出提示。deposit()
方法表示存款。
addCustomer
方法必须依照参数(姓,名)构造一个新的Customer
对象,然后把它放到customer
数组中。还必须把numberOfCustomer
属性的值加 1。
getNumOfCustomers
方法返回numberofCustomers
属性值。getCustomer
方法返回与给出的index
参数相关的客户。
创建BankTest
类,进行测试
答案:
package com.atguigu.yuan2;
public class Account {
private double balance;
public Account(double init_balance){this.balance = init_balance;}
public double getBalance() {return this.balance;}
/**
* 取钱
* @param amount 数额
*/
public void withdraw(double amount){
if (amount > this.balance) {
System.out.println("余额不足,取款失败。");
} else {
this.balance -= amount;
System.out.println("成功取出:" + amount + "\n现在余额为:" + this.balance);
}
}
/**
* 存钱
* @param amount 数额
*/
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
System.out.println("成功存入:" + amount + "\n现在余额为:" + this.balance);
}else {
System.out.println("存款失败。");
}
}
}
package com.atguigu.yuan2;
public class Customer {
private String firstName;
private String lastName;
private Account account;
public Customer(String f, String l) {
this.firstName = f;
this.lastName = l;
}
public String getFirstName() {return firstName;}
public String getLastName() {return lastName;}
public Account getAccount() {return account;}
public void setAccount(Account account) {
this.account = account;
}
}
package com.atguigu.yuan2;
public class Bank {
private Customer[] customers;
private int numberOfCustomer;
public Bank() {customers = new Customer[10];}
public void addCustomer(String f,String l){customers[numberOfCustomer++] = new Customer(f, l);}
public int getNumberOfCustomers() {return numberOfCustomer;}
public Customer getCustomer(int index) {
if (index <= 0 && index > numberOfCustomer) {
System.out.println("没有这么多客户啦。只有" + this.numberOfCustomer + "个客户。");
return null;
} else {return customers[index];}
}
}
package com.atguigu.yuan2;
public class BankTest {
public static void main(String[] args) {
Bank bank = new Bank();
bank.addCustomer("June", "Smith");
bank.getCustomer(0).setAccount(new Account(2000));
Customer customer = bank.getCustomer(0);//获取用户对象
customer.getAccount().deposit(100);//存款
customer.getAccount().withdraw(1234);//取款
System.out.println(bank.getCustomer(0).getAccount().getBalance());;//获取余额
bank.addCustomer("Jim", "Angel");//添加用户
System.out.println("现在有" + bank.getNumberOfCustomers() + "个用户。");//查看用户数
System.out.println(bank.getCustomer(1).getFirstName());
System.out.println(bank.getCustomer(1).getLastName());//获取第二个用户的名字
}
}