- J3 - 白起
- Java(Java基础 # 对象 # 方法 # 笔记)
1、类和对象
Java 是面向对象的程序设计语言,雷士面向对象的重要内容,我们可以吧类当成一种自定义类型,可以使用类来定义变量,这种类型的变量统称为引用变量。
所有类都是引用变量类型。
1.1 定义类
Java 语言类的简单语法如下:
[修饰符] class [类名] {
零到多个构造器定义...
零到多个Field...
零到多个方法...
}
语法格式中,修饰符可以是 public 、final 、abstract 、或者完全省略这三个修饰符,类名只要是一个合法的标识符即可。
类中可以包含三种成员:
- 构造器:构造器用于构造该类的实例,Java 语言通过 new 关键字来调用构造器,从而返回该类的实例。
- Field:Field 用于定义该类或该类的实例所包含的状态数据。
- 方法:方法用于定义该类过该类的实例的行为特征或者功能实现。
这三种成员可以定义零个或多个,如果三种成员都只定义零个,就是定义了一个空类,这没有太大的实际意义。
类中各成员之间的定义顺序没有任何影响,各成员之间可以相互调用,但是,static 修饰的成员不能访问没有 static 修饰的成员。
构造器是一个类创建对象的根本途径,如果一个类没有构造器,这个类通常无法创建实例。因此 Java 语言提供了一个功能:如果程序员没有为一个类编写构造器,则系统会为该类提供一个默认的构造器。一旦程序员为一个类提供了构造器,系统将不再为该类提供构造器。
定义 Field 的语法格式如下:
[修饰符] Field类型 Field名 [= 默认值];
格式说明:
- 修饰符:修饰符可以省略,也可以是 public、protected、private、static、final,其中 public、protected、private 三个最多只能出现其中之一,可以与 static、final 组合起来修饰 Field。
- Field 类型:Field 类型可以是 Java 语言允许的任何数据类型,包括基本类型和现在介绍的引用类型。
- Field 名:Field 名只要符合标识符即可,但也尽量通俗易懂,便于理解。
- 默认值:定义 Field 可以指定一个可选的默认值。
方法定义格式如下:
[修饰符] 方法返回类型 方法返回名(形参列表) {
// 由零条到多条可执行性语句组成的方法体
}
格式说明:
- 修饰符:修饰符可以省略,也可以是 public、protected、private、static、final、abstract,其中 public、protected、private 三个最多只能出现其中之一;abstract 和 final 最多只能出现其中之一,他们可以与 static 组和起来休息方法。
- 方法返回值类型:返回值类型可以是 Java 语言允许的任何数据类型,包括基本类型和引用类型;如果声明了方法返回值类型,则方法体内必须有一个有效的 return 语句,该语句返回一个变量或一个表达式,这个变量或者表达式的类型必须与此处声明的类型匹配。另外,如果方法没有返回值,则必须使用 void 来声明没有返回值。
- 方法名:符合 Java 标识符即可,但尽量通俗易懂。
- 形参列表:形参列表用于定义该方法可以接受的参数,新参列表由零组到多组 ”参数类型 形参名“ 组合而成,多组参数之间以英文逗号隔开,形参类型和形参名之间以英文空格隔开。
static 是要给特殊的关键字,它可用于修饰方法、Field 等成员。static 修饰的成员表明它属于这个类本身,而不是属于该类的单个实例,反之则属于单个实例,而不属于该类。
构造器是一个特殊的方法,定义构造器语法格式与定义方法的语法格式很像,定义构造器的语法格式如下:
[修饰符] 构造器名(形参列表) {
// 由零条到多条可执行性语句组成的构造体
}
格式说明:
- 修饰符:修饰符可以省略,也可以是 public、protected、private 其中之一。
- 构造器名:构造器名必须和类名相同。
- 形参列表:和定义方法形参列表的格式完全相同。
值得注意的是,构造器不能定义返回类型,也不能使用 void 定义构造器没有返回值。
1.2 对象的产生和使用
创建对象的根本途径是构造器,通过 new 关键字来调用某个类的构造器即可创建这个类的实例。
创建对象示例:
// 定义 girlFriend 变量,同时为该变量赋值
GirlFriend girlFriend = new GirlFriend();
如果访问权限允许,类里定义的方法和 Field 都可以通过类或实例来调用。
类或实例访问方法或 Field 的语法是:类.Field | 方法,或者实例.Field | 方法,这种方式中,类或实例是主调者,用于方法该类或该类实例的指定 Field 或方法。
static 修饰的方法和 Field ,既可以通过类来调用,也可通过实例来调用,反之则只能通过实例来调用。
下面代码通过 GirlFriend 实例来调用 GirlFriend 的 field 和方法。
// 创建对象
GirlFriend girlFriend = new GirlFriend();
// 调用 girlFriend 的 name Field,直接为该 Field 赋值
girlFriend.name = "刘亦菲";
// 输出 girlFriend 的 name Field 的值,将输出 刘亦菲
System.out.println(girlFriend.name);
// 调用 girlFriend 的 toString 方法
System.out.println(girlFriend.toString());
2、方法详解
Java 里面的方法不能独立存在,所有的方法都必须定义在类里。方法在逻辑上要么属于类,要么属于对象。
因此,如果需要定义方法,则只能在类体内定义,不能独立定义一个方法。一旦将一个方法定义在某个类的类体内,如果这个方法使用了 static 修饰,则这个方法属于这个类,否则这个方法属于这个类实例。
因为 Java 里的方法不能独立存在,它必须属于一个类或一个对象,因此方法不能像函数那样被独立执行,执行方法时必须使用类或对象来作为调用者,既所有方法都必须使用功能 “类.方法” 或 “对象.方法” 的形式来调用。至于同一个类中,方法之间的互相调用,如果没有被 static 修饰则默认用 this 调用反之则用类调用。也就是说,表面上看起来某些方法可以被独立执行,但实际上还是使用 this 或者类来作为调用者。
使用 static 修饰的方法既可以使用类作为调用者来调用,也可以使用功能对象作为调用者来调用。但不论使用哪种方式最终的结果是一定相同的,因为其实际还是类作为调用者。
未使用 static 修饰的方法属于类的实例,其只能使用对象作为调用者调用,而且使用不同的对象作为调用者得出的结果可能不相同。
2.1 方法的参数传递机制
如果声明方法时包含了形参声明,则调用方法时必须给这些形参指定参数值,调用方法时实际传给形参的参数值也被称为实参。
Java 中方法中的参数传递只有值传递。所谓值传递,就是将实际参数值得副本(复制品)传入方法内,而参数本省不会受到任何影响。
下面来看看方法参数传递的效果代码。
public class MethodsTest {
public static void main(String[] args) {
int a = 10;
int b = 20;
swap(a, b);
System.out.println("a 、b 交换值之后 a = " + a + ",b = " + b);
}
public static void swap(int a, int b) {
// 交换 a 和 b 的值
int temp = a;
a = b;
b = temp;
System.out.println("swap 方法里 a 、b 交换值之后 a = " + a + ",b = " + b);
}
}
运行结果:
swap 方法里 a 、b 交换值之后 a = 20,b = 10
a 、b 交换值之后 a = 10,b = 20
从结果上来看,main 方法里的变量 a 和 b 并不是 swap 方法里的 a 和 b,而是 main 方法里变量 a 和 b 的一个复制品。
main 方法及 swap 方法在栈中变换的示意图如下:
如图所示,在 main 方法中调用 swap 方法实际就是在栈中开辟一个空间,并重新产生两个变量 a 和 b 并将 main 方法中的 a、b 变量值赋值给 swap 内的变量。此时栈中存在 两个 a 变量、两个 b 变量,只是存在不同的方法区中(栈帧)。
在 swap 中进行值交换时,只是单纯的改变了 swap 方法区的变量,而 main 方法区的变量没有任何改动,所以就有了上面的打印结果。
前面看到的是基本类型参数传递,Java 对于引用类型的参数传递,一样采用的是值传递方式。
public class DataSwap{
public int a;
public int b;
}
public class MethodsTest {
public static void main(String[] args) {
// 创建引用类型对象
DataSwap dataSwap = new DataSwap();
// 赋值
dataSwap.a = 10;
dataSwap.b = 20;
// 调用交换方法
swap(dataSwap);
System.out.println("a Field、b Field交换值之后 a = " + dataSwap.a + ",b = " + dataSwap.b);
}
public static void swap(DataSwap dw) {
// 交换 a 和 b 的值
int temp = dw.a;
dw.a = dw.b;
dw.b = temp;
System.out.println("swap 方法里 a Field、b Field 交换值之后 a = " + dw.a + ",b = " + dw.b);
}
}
运行结果:
swap 方法里 a Field、b Field 交换值之后 a = 20,b = 10
a Field、b Field交换值之后 a = 20,b = 10
从运行结果来看,在 swap 方法里,a、b 两个 Field 值被交换成功。不仅如此,main 方法里 swap 方法执行结束后,a、b 两个 Field 值也被交换了。这会让人造成一种错觉:调用 swap 方法时,传入 swap 方法的就是 DataSwap 对象本身,而不是它的复制品。但这只是一种错觉,因为 dataSwap 和 dw 变量指向同一个对象地址,其中一个变量改变地址所指向的内存值另一个同样跟着改变,内存示意图如下。
2.2 形参个数可变的方法
从 JDK1.5 之后,Java 允许定义形参个数可变的参数,从而允许位方法指定数量不确定的形参。
如果在定义方法时,在最后一个形参的类型后增加三点(…),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。
下面看一个形参可变个数的方法代码。
public class MethodTest {
public static void main(String[] args) {
// 调用方法
variable(1, "疯狂 Java 讲义", "Java 高并发编程", "深入 Java 虚拟机");
}
public static void variable(int a, String... books){
// books 可以当成数组输出
for (String book : books) {
System.out.println(book);
}
// 输出整数值 a
System.out.println(a);
}
}
允许结果:
疯狂 Java 讲义
Java 高并发编程
深入 Java 虚拟机
10
从运行结果可以看出,下面两个方法前面效果完全一样。
// 以可变数形参来定义方法
public static void variable(int a, String... books);
// 下面采用数组形参定义方法
public static void variable(int a, String[] books);
上面两种形式在方法体内都可以把 books 当成数组处理,但区别是调用两个方法时存在差别,对于可变参数调用方法更加简洁,如下面代码所示。
variable(10, "疯狂 Java 讲义", "Java 高并发编程", "深入 Java 虚拟机");
采用数组形参来声明方法,调用共时则必须传给该形参一个数组,如下面代码所示。
// 调用 variable 方法时传入一个数组
variable(10, new String[]{"疯狂 Java 讲义", "Java 高并发编程", "深入 Java 虚拟机"});
对比两种调用方法代码,明显第一种形式更加简洁。
最后,数组形式的形参可以处于形参列表的任意位置,但个数可变的形参只能处于形参列表的最后,也就是所,一个方法中最多只能有一个长度可变的形参。
2.3 递归方法
一个方法体内调用共它自身,被称为方法递归。
方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无循环控制。
例如计算 10!则使用递归方式实现代码如下。
public class FactorialTest {
public static void main(String[] args) {
// 调用递归方法,计算 10!
System.out.println(factorial(10));
}
public static int factorial(int value) {
// 结束条件,一定要有,value 变为 1 就结束
if (value == 1) {
return 1;
}
// 递归,计算
return value * factorial(value - 1);
}
}
对于 factorial(10),即等于 10 * factorial(10 - 1) ,其中 factorial(10 - 1) 又等于 9 * factorial(9 - 1)…依次类推,最终得到 factorial(1) 等于 1 ,即 1 是可计算的,然后一路反计算回去,就可以最终得到 factorial(10) 的值。
仔细看上面递归的过程,当一个方法不断地调用它本身时,必须在某一个时刻方法的返回值是一个确定的,即不再调用它本身,否则这种地柜就变成了无穷递归,类似于死循环。所以定义递归方法时有一条最重要的规定:递归一定要向已知方向递归。
2.4 方法重载
Java 允许同一个类里定义多个同名方法,只要形参列表不同就行。
如果同一个类中包含了两个或两个以上方法的方法名相同,担形参列表不同,则被称为方法重载。
方法重载的要求是方法名相同,形参列表不同。至于方法的其他部分,如方法返回类型,修饰符都和方法重载没有任何关系。
public class OverloadTest {
public static void main(String[] args) {
OverloadTest overloadTest = new OverloadTest();
// 调用 test 方法,没有带参数,所以运行无参方法
overloadTest.test();
// 调用 test 方法,传入一个参数,所以运行带一个参数的方法
overloadTest.test("J3 关注一波不迷路");
}
public void test() {
System.out.println("无参 test 方法");
}
public void test(String str) {
System.out.println("带一个参数的 test 方法,参数 str = " + str);
}
}
运行结果完全正常,虽然 test 方法的方法名相同,但因为它们的形参列表不同,所以系统可以正常区分出这两个方法。
那为什么方法的返回值不能用于区分重载?
因为对于 int f(){} 和 void f(){} 两个方法,如果调用 int result = f();,系统可以识别是调用返回类型是 int 的方法;但Java 调用方法时是可以忽略返回值的,如果采用如下方法来调用 f(); 则不能确定到底调用哪个方法。因此 Java 里不能使用功能方法返回值类型作为方法重载的依据。
好了,今天的内容到这里就结束了,关注我,我们下期见
查阅或参考资料:
《Java核心技术第10卷》
《疯狂 Java 讲义》
联系方式:
QQ:1491989462
微信:13207920596
做个好友,来个点赞之交。
-
由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。
-
如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。
-
感谢您的阅读,十分欢迎并感谢您的关注。
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
个人站点:J3
CSDN:J3 - 白起
掘金:J3-白起
知乎:J3-白起
这是一个技术一般,但热衷于分享;经验尚浅,但脸皮够厚;明明年轻有颜值,但非要靠才华吃饭的程序员。
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^