湖州做网站推广的公司怎样推广网站
不同于 C ,Java 是一门面向对象的编程语言。C++ 也有面向对象的内容,但是 C++ 和 Java 在方法的具体实现上存在区别。
方法的定义
方法(method)是为执行一个复杂操作组合在一起的语句集合。一个类中可以声明多个方法。其语法是采用 BNF 范式(Backus-Naur Form,巴科斯范式)描述的,用来描述计算机语言语法的符号集。
例如,下面是一个求两个整数中最大值的方法max
:
public static int max(int num1,int num2){int result = 0;if(num1 > num2)result = num1;elseresult = num2;return result;
}
其中,public
和static
是修饰符,int
是返回值,max
是方法名称。
方法签名(Method Signature)是指方法名称+形参列表,如上面方法的签名就是max(int num1,int num2)
。一个类中不能包含方法签名相同的多个方法,因为这样在调用方法时编译器不知道要调用哪个。
方法的调用
Java 类中的成员方法可以分为构造方法、类方法和对象方法。
构造方法的调用
构造方法只能在新建一个对象时,由 Java 虚拟机进行调用。创建对象通常是通过类名 对象名 = new 类名(构造函数的参数)
建立。比如下面的例子:
class TestConstructor{private int value;public TestConstructor(){this.value = 1;}public TestConstructor(int value){this.value = value;}public int getValue(){return this.value;}
}public class CallConstructor{public static void main(String[ ] args) {TestConstructor tc = new TestConstructor();System.out.println("Value: " + tc.getValue());tc = new TestConstructor(5);System.out.println("Value: " + tc.getValue());}
}
运行的结果是:
Value: 1
Value: 5
this 调用
可以通过this
调用该类的其它构造函数,但是必须是构造函数的第一条语句。例如:
class TestConstructor{private int value;public TestConstructor(){this(114);}public TestConstructor(int value){this.value = value;}public int getValue(){return this.value;}
}public class ThisConstructor{public static void main(String[ ] args) {TestConstructor tc = new TestConstructor();System.out.println("Value: " + tc.getValue());}
}
运行的结果为Value: 114
。这是因为在调用无参数构造函数时,无参构造函数通过this(114)
调用了构造函数TestConstructor(int value)
,并给其形参传递值value = 114
,从而使得value
成员初始化为114
。
super 调用
一个类还能够通过super
调用其父类的构造函数。例如
class TestConstructor{private int value;public TestConstructor(){this(114);}public TestConstructor(int value){this.value = value;}public int getValue(){return this.value;}
}public class SuperConstructor extends TestConstructor{public SuperConstructor(){super(514);}public static void main(String[ ] args) {SuperConstructor sc = new SuperConstructor();System.out.println("Value: " + sc.getValue());}
}
运行的结果为Value: 514
。这是因为在调用SuperConstructor
类的午餐构造函数时,通过super(514)
语句调用了父类TestConstructor
中的含参构造函数TestConstructor(int value)
,并给其形参传递值value = 514
,从而使成员value
初始化为514
。
类方法的调用
类方法就是由static
修饰的方法,也称静态方法。静态方法可以通过类名调用,也可以通过对象实例调用。比如下面的代码:
import java.lang.Math;
public class CallStaticMethod{public static void main(String[] args){int a = 114,b = 514;System.out.println(Math.max(a,b));}
}
在这里,调用了 Java 中自带的类Math
中的方法max
,输出的结果为514
。可以看到静态方法max
是通过类名.方法名
的方式调用的max
方法不需要通过创造Math
对象math
,并通过math.max(a,b)
来调用,因为计算一个最大值没有必要依赖对象。
事实上,Java 的开发者也考虑到了这一点,所以将Math
类的构造函数设置为私有的,这就导致我们其实也无法创造一个Math
类的实例对象。
对象方法的调用
对象方法只能通过实例对象来调用。比如下面的代码:
class Wallet{private int money;Wallet(){money = 0;}public void addMoney(int amount){this.money += amount;}public int getMoney(){return this.money;}
}public class CallObjectMethod{public static void main(String[] args){Wallet wallet = new Wallet();System.out.println("Now we have money: " + wallet.getMoney());wallet.addMoney(520);System.out.println("Then we have money: " + wallet.getMoney());}
}
运行结果为:
Now we have money: 0
Then we have money: 520
可以看到实例方法addMoney
和getMoney
都是通过对象名.方法名
调用的。显然不能通过Wallet.addmoney
或者Wallet.getMoney
调用这两个方法,因为我可以有多个Wallet
对象wallet1,wallet2,...
,使用类名.方法名
无法知道调用的是哪个Wallet
对象。
方法的重载
方法重载(Overloading)是指方法名称相同,但形参列表不同的方法。仅返回类型不同的方法不是合法的重载。一个类中可以包含多个重载的方法(同名的方法可以重载多个版本)。
方法重载实例
方法重载,例如
public class InputCheckTest{public static void CheckInput(String s){System.out.println("You entered a string.");}public static void CheckInput(int i){System.out.println("You entered an integer.");}public static void CheckInput(double d){System.out.println("You entered a double.");}public static void main(String[] args){CheckInput(114514);CheckInput("114514");CheckInput(114514.0);}
}
运行结果:
You entered an integer.
You entered a string.
You entered a double.
有歧义的重载
系统根据我们的输入自动判断调用哪个方法。但是有时候,可能会有多个合适的方法。例如
public class AmbiguousOverloading {public static void main(String[ ] args) {// System.out.println(max(1, 2));}public static double max(int num1, double num2) {return (num1 > num2)?num1:num2;}public static double max(double num1, int num2) {return (num1 > num2)?num1:num2;}
}
将注释符号去掉,上面的代码编译时将产生错误,因为编译器不知道max(1,2)
调用的是哪个函数。
包和类的导入
Java 源程序在开头通过import 包名;
语句导入其它包(类),可以使用其它包中的类及其方法。有点类似于 C/C++ 中的#include "头文件名"
语句。Java 程序在编译时会自动导入java.lang.System
类,所以我们在编写源程序时可以直接使用System.out
和System.in
以及它们的方法。
总的来说,import
有两种类型:单类型导入和按需类型导入。
单类型导入
把导入的标识符引入到当前.java文件,因此当前文件里不能定义同名的标识符,类似C++中的 using nm::id
; 把名字空间nm
的名字id
引入到当前代码处。
比如,在包p1
中定义了类A
:
package p1;
public class A{// some statements...
}
那么在包p2
中:
package p2;
import p1.A;//单类型导入,把p1.A引入到当前域
//这个时候当前文件里不能定义A,下面语句编译报错
public class A {//some statements...
}
按需类型导入
不是把包里的标识符都引入到当前.java文件,只是使包里名字都可见,使得我们要使用引入包里的名字时可以不用使用完全限定名,因此在当前.java文件里可以定义与引入包里同名的标识符。但二义性只有当名字被使用时才被检测到。类似于C++里的using nm
。
比如,包p1
还是和上面一样,此时p2
中:
package p2;
import p1.*; //按需导入,没有马上把p1.A引入到当前域
//因此当前文件里可以定义A
public class A {public static void main(String[] args){A a1 = new A(); //这时A是p2.ASystem.out.println(a1 instanceof p2.A); //true//当前域已经定义了A,因此要想使用package p1里的A,只能用完全限定名p1.A a2 = new p1.A();}
}
如果出现了名字冲突,要用完全限定名消除冲突。
类及其方法的可见性修饰符
Java 中的可见性修饰符有private
、public
和protected
,不加可见性修饰符的方法默认访问权限为包级。Java 继承时无继承控制(即都是公有继承,和 C++ 不同),故父类成员继承到派生类时访问权限保持不变(除了私有)。
成员访问控制符的作用:
private
: 只能被当前类定义的函数访问。- 包级:无修饰符的成员,只能被同一包中的类访问。
protected
:子类、同一包中的类的函数可以访问。public
: 所有类的函数都可以访问。
访问权限 | 本类 | 本包 | 子类 | 它包 |
---|---|---|---|---|
public | Yes | Yes | Yes | Yes |
protected | Yes | Yes | Yes | No(非子类时) |
包级(默认) | Yes | Yes | No(非本包时) | No |
private | Yes | No | No | No |
其它的都很好理解,只是需要注意一点:子类类体中可以访问从父类继承来的protected
成员。但如果子类和父类不在同一个包里,子类里不能访问另外父类实例的protected
成员。可以举一个例子说明这一点。我们在p1
包里定义C1
类:
package p1;
public class C1{protected int u = 3;
}
然后在包p2
中,会出现以下情况:
package p2;
import p1.C1
public class C2 extends C1{int u = 5;public static void main(String[] args){System.out.println(u) // OK. u 相当于 this.u,打印结果是 5。System.out.println(super.u) // OK. super.u 指的是父类 C1 中的 u,在子类中能够访问。打印结果是 3。C1 o = new C1();System.out.println(o.u) // ERROR. 虽然这里是 C1 的子类,但是与 C1 不在同一个包中,且 o 是一个另外的父类实例,不能访问 o 的保护成员。}
}
类的继承
Java 中可以用 extends
关键字表示继承,在上面举的例子中也能看到。子类对象是一个父类对象,即子类对象实例 instanceof 父类名
的结果为true
。
方法覆盖与方法隐藏
如果子类重新定义了从父类中继承的实例方法,称为方法覆盖(Method Override)。如果子类重新定义了从父类中继承的类方法,称为方法隐藏(Method Hidden)。它们具有如下性质:
- 仅当父类方法在子类里是可访问的,该实例方法才能被子类覆盖/隐藏;否则只是定义了一个普通方法而已,不叫作覆盖/隐藏。
- 父类的
final
方法不能被子类覆盖/隐藏,否则编译时会报错。 - 在子类函数中可以使用
super
调用被覆盖的父类方法。
实例方法的多态性
之所以会产生实例方法的多态性这个概念,是因为父类引用变量可以指向子类对象。例如,如果A
是B
的父类,那么A o = new B()
是可行的。其中,A
是变量o
的声明类型,编译时对有关变量o
的语句进行检查时,都是将o
视作声明类型;B
是o
的运行时类型,在运行的时候,因为o
实际指向的对象并不是A
类型的,这就会产生多态性。
实例方法的多态性可以通过下面的代码说明:
class A{public void m() {System.out.println("A's m");}public static void s() {System.out.println("A's s");}
}
class B extends A{//覆盖父类实例方法public void m() {System.out.println("B's m");}//隐藏父类静态方法public static void s() {System.out.println("B's s");}
}
public class OverrideDemo {public static void main(String[] args) {A o1 = new B();o1.m(); // B's mo1.s(); // A's s((A)o1).m // B's m,被覆盖的父类实例方法不能再发现B o2 = new B();o2.s // B's s((A)o2).s // A's s,被隐藏的父类静态方法可以再发现}
}
对于上面运行结果的解释是:静态函数没有多态性,函数入口地址在编译时确定,编译时所有的变量都是按照其声明类型;实例方法具有多态性,在运行时时根据实际的运行时类型确定函数入口地址。