类和方法
类和方法的深入分析
1. 重载方法
- 在一个类中,定义两个或多个共享相同名称、但形参不同的方法
class OverloadedDemo {
void test() {
System.out.println("无形参");
}
void test(int a) {
System.out.println("a: " + a);
}
void test(int a, int b) {
System.out.println("a and b: " + a + " " + b);
}
double test(double a) {
System.out.println("double a: " + a);
return a*a;
}
}
class Overload {
public static void main(String[] args) {
OverloadedDemo ob = new OverloadedDemo();
double result;
ob.test();
ob.test(10);
ob.test(10, 20);
result = ob.test(123.25);
System.out.println("Result of ob.test(123.25): " + result);
}
}
方法重载的规则:
- 方法名称必须相同
- 参数列表必须不同(个数不同、或类型不同、参数排列顺序不同等)
- 方法的返回类型可以相同也可以不相同
- 仅仅返回类型不同不足以称为方法的重载
-
如果类中使用
int
实参调用test()
方法,但没有定义test(int)
,Java 将会使用自动类型转换(比如若定义了test(double)
,则将int
转换为double
) -
方法重载支持多态,Java 实现 “一个接口,多个方法” 机制
重载构造函数
class Box {
double width;
double height;
double depth;
Box(double w, double h, double d) {
width = w;
height = h;
depth = d;
}
// 当使用者不需要知道 Box 的尺寸时
Box() {
width = -1;
height = -1;
depth = -1;
}
// 当使用者的 Box 为正方体时
Box(double len) {
width = height = depth = len;
}
double volume() {
return width * height * depth;
}
}
class OverloadCons {
public static void main(String[] args) {
Box mybox1 = new Box(10, 20, 15);
Box mybox2 = new Box();
Box mycube = new Box(7);
double vol;
vol = mybox1.volume();
System.out.println("mybox1 的体积为: " + vol);
vol = mybox2.volume();
System.out.println("mybox2 的体积为: " + vol);
vol = mycube.volume();
System.out.println("mybox3 的体积为: " + vol);
}
}
2. 将对象用作形参
class Test {
int a, b;
Test(int i, int j) {
a = i;
b = j;
}
boolean equalTo(Test o) {
if (o.a == a && o.b == b) return true;
else return false;
}
}
class PassOb {
public static void main(String[] args) {
Test ob1 = new Test(100, 20);
Test ob2 = new Test(100, 20);
Test ob3 = new Test(-1, -1);
System.out.println("ob1 == ob2: " + ob1.equalTo(ob2));
System.out.println("ob1 == ob3: " + ob1.equalTo(ob3));
}
}
对象形参最常用于调用构造函数,经常需要创建在开始时就与已经存在的某些对象相同的对象
需要定义一个以类的对象作为形参的构造函数
class Box {
double width;
double height;
double depth;
// 将对象用作形参
Box(Box ob) {
width = ob.width;
height = ob.height;
depth = ob.depth;
}
Box(double w, double h, double d) {
width = w;
height = h;
depth = d;
}
double volume() {
return width * height * depth;
}
}
class OverloadCons {
public static void main(String[] args) {
Box mybox = new Box(10, 20, 15);
Box myclone = new Box(mybox);
double vol;
vol = mybox.volume();
System.out.println("mybox 的体积为: " + vol);
vol = myclone.volume();
System.out.println("clone 的体积为: " + vol);
}
}
3. 实参传递的深入分析
-
向子例程传递实参的两种方式:
-
call-by-value
:按值调用- 为方法传递基本类型(
int
,double
等)时,使用按值传递,方法内部对形参的操作不会影响方法外部的实参的值。
- 为方法传递基本类型(
-
call-by-reference
:按引用调用- 为方法传递对象时,使用按引用调用。**当创建类类型变量时,只是创建指向对象的引用。**因此,当将引用传递给方法时,接收引用的形参所引用的对象与实参引用的是同一个对象。在方法内修改对象,将影响到作为实参的对象。
class Test { int a, b; Test(int i, int j) { a = i; b = j; } void meth(Test o) { o.a *= 2; o.b /= 2; } } class PassObjRef { public static void main(String[] args) { Test ob = new Test(15, 20); System.out.println("ob.a, ob.b 调用方法前的值为: " + ob.a + " " + ob.b); ob.meth(ob); System.out.println("ob.a, ob.b 调用方法后的值为: " + ob.a + " " + ob.b); } }
-
当将对象引用传递给方法时,引用本身是使用按值调用的方式传递的。但是,由于传递的值引用一个对象,值的副本仍然引用相应实参指向的同一个对象。
4. 返回对象
class Test {
int a;
Test(int i) {
a = i;
}
Test incrByTen() {
Test temp = new Test(a+10);
return temp;
}
}
class RetOb {
public static void main(String[] args) {
Test ob1 = new Test(2);
Test ob2;
ob2 = ob1.incrByTen();
System.out.println("ob1.a: " + ob1.a);
System.out.println("ob2.a: " + ob2.a);
ob2 = ob2.incrByTen();
System.out.println("ob2.a second time: " + ob2.a);
}
}
以上,所有对象都是使用 new
运算符动态创建的,不必担心对象会由于其创建时所在的方法结束而超出作用域。
5. 访问控制(access control)
-
访问修饰符(access modifier)
public
- 该成员可以被任何代码访问
private
- 该成员只能被所属类的其他成员访问
protected
如果没有使用访问修饰符,该类成员在自己的包中默认时共有的,但在包外不能访问。
class Test {
int a;
public int b;
// 私有
private int c;
void setc(int i) {
c = i;
}
int getc() {
return c;
}
}
class AccessTest {
public static void main(String[] args) {
Test ob = new Test();
ob.a = 10;
ob.b = 20;
// ob.c = 100; // 报错! c为私有, 必须通过共有方法访问
ob.setc(100);
System.out.println("a, b and c: " + ob.a + " " + ob.b + " " + ob.getc());
}
}
堆栈类实例:
class Stack {
// 标识为私有
// 如果不通过 push() 和 pop() 方法,就不能访问和修改它们
private int[] stck = new int[10];
private int tos;
Stack() {
tos = -1;
}
void push(int item) {
if (tos==9)
System.out.println("Stack is full.");
else
stck[++tos] = item;
}
int pop() {
if (tos<0) {
System.out.println("Stack underflow.");
return 0;
}
else
return stck[tos--];
}
}
6. static 关键字
如果成员被声明为静态(static)的,则可以在创建类的任何对象之前访问该成员,不需要使用任何对象引用。
被声明为静态的实例变量在本质上是全局变量。当声明类的对象时,不会生成静态变量的副本。相反,类的所有实例共享相同的静态变量。
- 被声明为静态的方法存在几个限制:
- 只能直接调用其类的其他静态方法
- 只能直接访问其类的静态变量
- 不能以任何方式引用 this 或 super 关键字
class UseStatic {
// 1. a 被设置成 3
static int a = 3;
static int b;
// 4. 调用 meth() 方法
static void meth(int x) {
System.out.println("x = " + x);
System.out.println("a = " + a);
System.out.println("b = " + b);
}
// 2. 执行静态代码块
// 输出一条消息,并将 b 初始化为 a*4
static {
System.out.println("静态块初始化...");
b = a * 4;
}
// 3. 调用 main() 方法
public static void main(String[] args) {
meth(42);
}
}
Output:
静态块初始化...
x = 42
a = 3
b = 12
- 在定义静态方法和静态变量的类的外部,可以独立使用这些静态成员,不必依赖任何对象。如果希望在类的外部访问静态方法 / 静态变量:
类名.方法名()
类名.变量名
- 静态变量和非静态变量对比
public class Student {
private static int age; // 静态的变量
private double score; // 非静态的变量
public static void main(String[] args) {
Student s1 = new Student();
System.out.println(Student.age);
// System.out.println(Student.score); // 编译报错,非静态变量
System.out.println(s1.age);
System.out.println(s1.score);
}
}
- 执行顺序
public class Person {
// 2. 赋初值
{
// 匿名代码块
System.out.println("匿名代码块");
}
// 1. 静态代码块 只执行一次
static {
System.out.println("静态代码块");
}
// 3. 构造方法
public Person() {
System.out.println("构造方法");
}
public static void main(String[] args) {
Person person = new Person();
System.out.println("================");
Person person2 = new Person();
}
}
Output:
静态代码块
匿名代码块
构造方法
================
匿名代码块
构造方法
- 静态导入包
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
System.out.println(random());
System.out.println(PI);
}
}
7. final
-
防止变量的内容被修改,本质上就是将变量变成常量**(final 之后断子绝孙)**
-
final 变量必须在声明时进行初始化
- 在声明式为其提供一个值
final int FILE_NEW = 1;
- 在构造函数中为其赋值
- 在声明式为其提供一个值
-
编码约定:final 变量名全部大写
-
将方法参数和局部变量声明为 final:
- 方法参数:防止在方法中修改参数
- 局部变量:防止多次为其赋值
8. 嵌套类和内部类
嵌套类 (nested class) :在类的内部定义另一个类。作用域被限制在包含它的类中。
如果类 B 是在类 A 中定义的,那么类 B 不能独立于类 A 而存在
嵌套类可以访问包含它的类的成员,包括私有成员;封闭类(包含嵌套类的类)不能访问嵌套类的成员;嵌套类可作为封闭类的成员直接在封闭类中声明,也可以在代码块中声明。可以在任何代码块的作用域内定义内部类:
class Outer {
int outer_x = 100;
void test() {
// 嵌套类可作为封闭类的成员直接在封闭类中声明
Inner inner = new Inner();
inner.display();
// 在 for 循环内定义嵌套类
for (int i = 0; i < 10; i++) {
class Innerinner {
void inDisplay() {
System.out.println("inDisplay: outer_x = " + outer_x);
}
}
Innerinner innerInner = new Innerinner();
innerInner.inDisplay();
}
}
class Inner {
int y = 10; // 嵌套类 Inner 的成员
// 嵌套类访问包含它的类的成员
void display() {
System.out.println("display: outer_x = " + outer_x);
}
}
void showy() {
// 封闭类不能访问嵌套类的成员
// System.out.println(y);
}
}
public class InnerClassDemo {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}
9. String 类
str1.equals(str2); // 测试两个字符串是否相等
str.length(); // 获取字符串长度
str.charAt(1); // 获取字符串中指定索引处的字符
10. 命令行传参
package com.feiye.CommandLine;
class CommandLine {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "]: " + args[i]);
}
}
}
-
在命令行中,该文件的路径下编译 javac 文件:
javac CommandLine.java
-
返回最上一层路径(src),通过命令行向 main() 函数传参:
java com.feiye.CommandLine.CommandLine this is a test 100 -1
-
输出结果:
args[0]: this args[1]: is args[2]: a args[3]: test args[4]: 100 args[5]: -1
所有命令行参数都是作为字符串传递的。必须手动将数值转换成它们的内部形式。
11. varargs: 可变长度实参
-
通过数组传参
class PassArray { static void vaTest(int[] v) { System.out.print("数组长度:" + v.length + " 内容:"); for (int x: v) System.out.print(x + " "); System.out.println(); } public static void main(String[] args) { int[] n1 = {10}; int[] n2 = {1,2,3}; int[] n3 = {}; vaTest(n1); vaTest(n2); vaTest(n3); } }
-
可变长度实参
class VarArgs { static void vaTest(int ... v) { System.out.print("参数长度:" + v.length + " 内容:"); for (int x : v) System.out.print(x + " "); System.out.println(); } public static void main(String[] args) { vaTest(10); vaTest(1,2,3); vaTest(); } }
- 可变长度形参必须是方法最后声明的形参
- 一个方法中只能有一个可变长度形参
11.1 重载 varargs 方法
若一个类中有两个方法:
vaTest(int ... v);
vaTest(int x);
当传递一个 int
类型实参调用 vaTest(int x);
;传递多个 int
类型实参时调用 vaTest(int ... v);
11.2 varargs 方法与模糊性
- 若一个类中存在两个方法:
vaTest(int ... v);
vaTest(boolean ... v);
当程序中调用vaTest();
时,编译不通过 - 若一个类中存在两个方法:
vaTest(int ... v);
vaTest(int n, int ... v);
当程序中调用vaTest(1);
时,编译不通过
综上,在某些情况下,有时需要放弃重载,简单地使用两个不同的方法名。