类与对象
概念贴士:
1. 类必须先定义了才能使用。类是创建对象的模板,创建对象也叫类的实例化。
2. 在Java中,使用new关键字来创建对象,一般有一下3个步骤:
1)声 明:声明一个对象,包括对象名称和对象类型。
2)实例化:使用关键字new创建一个对象。
3)初始化:使用new创建对象时,会调用构造方法初始化对象。
3. 在类实例化的过程中自动执行的方法叫做构造方法,它不需要手动调用。构造方法可以在类实例化的过程中完成一些初始化的工作。构造方法的名称必须与类的名称相同,并且没有返回值。每个类都有构造方法,如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认的构造方法。
4. Java通过修饰符来控制类、属性和方法的访问权限和其他功能,通常放在语句的最前端。
5. Java的修饰符很多,分为访问修饰符和非访问修饰符。其中访问修饰符也叫访问控制符,是指能够控制类、成员变量、方法的使用权限的关键字。 在面向对象编程中,访问控制符是一个很重要的概念,可以使用它来保护对类、变量、方法和控制方法的访问。
6. Java支持4种不同的访问权限:
1)public 共有的,对所有类可见
2)protected 受保护的,对同一包内的类和所有子类可见
3)private 私有的,在同一类内可见
4)默认的 在同一包内可见,默认不使用任何修饰符
6.1 public:公有的
被声明为public的类、方法、构造方法行业接口能够被任何其他类访问。如果几个相互访问的public类分布在不同的包中,则需要导入相应public类所在的包。由于类的继承性,类所有的公有方法和变量都能被子类继承。
例子:public static void main(String[] arguments){}
PS:Java程序main()方法必须是设置成公有的。
6.2 protected:受保护的
被声明为protected的变量、方法和构造方法能够被同一包中的任意其他类访问,也能够被不同包中的子类访问。protected访问修饰符不能修饰类和接口,方法和成员变量能够声明为protected,但是接口的成员变量和成员方法不能声明为protected。子类能访问protected修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。
例子:
1 public class Dog{
2 protected void bark(){
3 System.out.println("汪汪,不要过来!");
4 }
5 }
6
7 class Teddy extends Dog{ //泰迪
8 protected void bark(){
9 System.out.println("汪汪,我好怕,不要跟着我!");
10 }
11 }
PS:如果把bark()方法声明为private,那么除了Dog之外的类将不能访问该方法。如果把bark()方法声明为public,那么所有的类都能够访问该方法。如果只想让该方法对其所在类的子类可见,则将该方法声明为protected即可。
6.3 private:私有的
私有访问修饰符是最严格的访问控制级别,所以被声明为private的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为private。 声明为私有访问类型的变量只能通过类中公共的getter/setter方法被外部类访问。private访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。
例子:
1 public class Dog{
2 private String name;
3 private int age;
4 public String getName(){
5 return name;
6 }
7 public void setName(String name){
8 this.name=name;
9 }
10 public int getAge(){
11 return age;
12 }
13 public void setAge(int age){
14 this.age=age;
15 }
16 }
PS:程序中,Dog类中的name、age变量为私有变量,所以其他类不能直接得到和设置该变量的值。为了使其他类能够操作该变量,程序中定义了两对public方法,getName()/setName()和getAge()/setAge(),用来获取和设置私有变量的值。
PS:this是Java中的一个关键字,在类中定义访问私有变量的方法,习惯上是这样命名的:在变量名称前面添加get或者set,并将变量的首字母大写。上述两对方法由于经常使用,也有特定的称呼,称为getter和setter方法。
6.4 默认的:不使用任何关键字
不适用任何修饰符声明的属性和方法,对同一个包内的类是常见的。接口的变量都隐式声明为public static final(final为非访问修饰符之一),而接口里的方法默认情况下访问权限为public。
6.5 访问控制符使用小总结:
- 访问控制符可以令我们便于控制代码的权限。
1)当需要让自己编写的类被所有的其他类访问时,就可以将类的访问控制符声明为public。
2)当需要让自己的类只能被自己的包中的类访问时,就可以省略访问控制符。
3)当需要控制一个类中的成员数据时,可以将这个类中的成员数据访问控制符设置为public、protected或者省略。
2. 方法继承的规则。
1)父类中声明为public的方法在子类中也必须为public。
2)父类中声明为protected的方法在子类中要么声明为protected,要么声明为public,不能声明为private。
3)父类中默认修饰符声明的方法,能够在子类中声明为private。
4)父类中声明为private的方法,不能被继承。
7. 在Java中,变量的作用域分为4个级别,即类级、对象实例级、方法级和块级。(详见实例3-4)
1)类级变量又称为全局级变量或者静态变量,需要使用static关键字修饰,类级变量在类定义后就已经存在,占用内存空间,可以通过类名来访问,不需要实例化。
2)对象实例级变量就是成员变量,实例化后才会分配内存空间,才能访问。
3)方法级变量是在方法内部定义的变量,是局部变量。
4)块级变量是定义在一个块内部的变量,变量的生存周期就是这个块,出了这个块则变量消失,如if、for语句的块。块是指由大括号(花括号)包围的代码。
8. 在Java中,this可以在类里引用这个类的属性和方法。this关键字用来表示当前对象本身,或当前类的一个实例,通过this关键字可以调用本对象的所有方法和属性。(详见实例3-5)
9. 成员变量与方法内部的变量重名时,希望在方法内部调用成员变量,可以通过使用this关键字区分同名变量。(详见实例3-6)
10. 在构造函数中通过this引用来调用另一个构造函数,也就是相当于调用本类的其他构造方法,它必须作为构造方法的第一句(即作为方法名初始化对象)。(详见实例3-7)
11. 需要在某些完全分离的类中调用一个方法,并将当前对象的一个引用作为参数传递时,可以使用this关键字,作为参数传递。(详见实例3-8)
12. 在Java中
,同一个类中的多个方法可以有相同的名字,但参数列表不同,这被称为方法重载(method overloading)。重载是面向对象的一个基本特性。 参数列表又叫参数签名,包括参数的类型,参数的个数与参数的顺序,只要有一个不同就叫做参数列表不同。(详见实例3-9)
13. 1.重载说明:
1)参数列表不同包括:个数不同、类型不同和顺序不同。
2)仅参数变量名称不同是不可以的(毕竟那只是形参)。
3)与成员方法一样,构造方法也可以重载。
4)声明为final的方法不能被重载(感兴趣可以查询final这个非访问修饰符)。
5)声明为static的方法不能被重载,但是能够被再次声明。
2.方法的重载规则:
1)方法名称必须相同。
2)参数列表必须不同。
3)方法的返回类型可以不同,也可以相同。
4)仅仅返回类型不同不足以成为方法的重载。
3.方法重载的实现:方法名称相同时,编译器会根据调用方法的参数个数、参数类型等逐个去匹配,以选择对应的方法,如果匹配失败,则编译器报错,这叫重载分辨。
14. Java为每种基本数据类型分别设计了对应的类,实现数据间类型转化,这些类被称之为包装类(Wrapper Classes)。
15. 封装就是将属性私有化,提供公有的方法访问私有的属性。其实现步骤如下:
1)修改属性的可见性限制对属性的访问。
2)为每个属性创建一对赋值方法和取值方法,用于对这些属性的访问。
3)在赋值和取值方法中,加入对属性的存取限制。
16. 为了实现良好的封装性,通常将类的成员变量声明为private,再通过public的方法对这个变量进行访问。对一个变量的操作,一般是读取和赋值操作,下面分别定义两个方法来实现这两种操作,一个是getXxx()方法(Xxx表示要访问的成员变量的名字),用来读取这个成员变量操作;另外一个是setXxx()方法,用来对这个成员变量赋值。(详见实例3-10)
代码解释:
实例3-1
1 package duke.example.ch3;
2
3 public class Dog{
4 String name; //名字
5 int age; //年龄
6
7 void bark(){ //汪汪叫
8 System.out println("汪汪,不要过来");
9 }
10
11 void hungry(){ //饥饿
12 System.out.println("主任,我饿了");
13 }
14 }
代码说明: 代码行.3中 public是类的修饰符,表明该类是公共类,可以被其他类访问。class是定义类的关键字,Dog是类名称。
代码行.4-5中 name、age是类的成员变量,也叫属性。
代码行.7-9中 bark()是类中的函数,也叫方法,即定义犬吠这个方法。
代码行.11-13中 hungry()是类中的函数,也叫方法。
PS:一个类的变量可以包含以下类型变量。
- 局部变量:在方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在这个方法中,方法结束后,变量就会自动销毁。
- 成员变量:在类中、方法体之外定义的变量。这种变量在创建变量的时候实例化(分配内存)。成员变量可以被类中的方法和特定类的语句访问。
- 类 变 量 :也是在类中、方法体之外定义的变量,但必须声明为static类型。static也是修饰符的一种。
实例3-2
1 package duke.example.ch3;
2
3 public class Dog{
4 String name; //名字
5 int age; //年龄
6
7 Dog(String name,int age){ //构造方法,没有返回值
8 this.name=name;
9 this.age=age;
10 System.out.println(name+“感谢主人天天带我玩耍!”);
11 }
12
13 void bark(){ //汪汪叫
14 System.out println("汪汪,不要过来");
15 }
16
17 void hungry(){ //饥饿
18 System.out.println("主任,我饿了");
19 }
20
21 public static void main(String[] args){
22 //创建对象时传递的参数要与构造方法参数列表对应
23 Dog myDog=new Dog("卡拉",3);
24 System.out.println("我是一只小狗,我的名字叫"+name+",我"+age+"岁了!");
25 }
26 }
输出结果:
卡拉感谢主人天天带我玩耍!
我是一只小狗,我的名字叫卡拉,我3岁了!
代码说明: 代码行.7-9中 定义包含两个参数name和age的构造函数,构造方法的名称必须与类的名称Dog相同。构造方法不能有返回值,因为没有变量来接收返回值。
代码行.23中 构造方法不能被显示调用,在类Dog实例化的过程中自动执行了。
代码执行顺序:
1)先运行到代码行.21。这是程序的入口。
2)然后运行代码行.23。这里要创建一个Dog,就要调用Dog的构造方法。
3)运行到代码行.7。注意接下来并不是运行代码行.8,初始化一个类,必须先初始化他的属性。
4)因此运行到代码行.4。然后是代码行.5。
5)属性初始化后,才回到构造方法,执行里面的代码,也就是代码行.8-10。
6)然后是代码行.11。表示创建一个Dog实例完成。
7)然后回到main()方法中执行代码行.23-24。
8)最后是代码行.25。main()方法执行完毕。
书上说作为程序员,应该知道程序的基本运行过程,否则不利于编写代码与进步,其实这是有道理的。在一个程序中,你看到的深度决定了你编程水平的上限。
实例3-3
1 package duke.example.ch3;
2
3 public class Dog{
4 String name; //名字
5 int age; //年龄
6
7 Dog(String name,int age){ //构造方法,没有返回值
8 this.name=name;
9 this.age=age;
10 System.out.println(name+“感谢主人天天带我玩耍!”);
11 }
12
13 void bark(){ //汪汪叫
14 System.out println("汪汪,不要过来");
15 }
16
17 void hungry(){ //饥饿
18 System.out.println("主任,我饿了");
19 }
20
21 public static void main(String[] args){
22 //创建对象时传递的参数要与构造方法参数列表对应
23 Dog myDog=new Dog("卡拉",3);
24 //访问成员变量
25 String name=myDog.name;
26 int age=myDog.age;
27 System.out.println("我是一只小狗,我的名字叫"+name+",我"+age+"岁了!");
28 //访问方法
29 myDog.bark();
30 myDog.hungry();
31 }
32 }
输出结果:
卡拉感谢主人天天带我玩耍!
我是一只小狗,我的名字叫卡拉,我3岁了!
汪汪,不要过来!
主人,我饿了!
代码说明: 代码行.26-27中 通过点号(.)访问变量name和age。
代码行.29-30中 通过点号(.)访问成员方法bark()和hungry()。
实例3-4
1 package duke.example.ch3;
2
3 public class Scope{
4 public static String name="爪哇岛旅游攻略"; //类级变量
5 public int i; //对象实例级变量
6
7 //属性块,在类初始化属性时候运行
8 {
9 int j=2; //块级变量
10 }
11
12 public void test(){
13 int j=3; //方法级变量
14 if(j==3){
15 int k=5;
16 }
17 //这里不能访问块级变量,块级变量只能在块内部访问
18 System.out.println("name="+name+",i="+i+",j="+j);
19 }
20
21 public static void main(String[] args){
22 //不创建对象,直接通过类名访问类级变量
23 System.out.println(Scope.name);
24 //创建对象并访问它的方法
25 Scope.scope=new Scope();
26 scope.test();
27 }
28 }
输出结果:
爪哇岛旅游攻略
name=爪哇岛旅游攻略,i=0,j=3
代码说明: 代码行.4中 使用static关键字定义类级字符串变量name。
代码行.12-19中 定义方法test(),方法内部定义了方法级变量和块级变量。
PS: 1)方法内部除了能访问方法级的变量,还可以访问类级和实例级的变量。
2)块内部能够访问类级、实例级变量,如果块被包含在方法内部,它还可以访问方法级的变量。
3)方法级和块级的变量必须被显式初始化,否则不能访问。
实例3-5
1 package duke.example.ch3
2
3 public class ThisDemo1 {
4 public int x=10;
5 public int y=15;
6
7 public void sum() {
8 int z=this.x+this.y;
9 System.out.println("x+y="+z);
10 }
11
12 public static void main(String[] args) {
13 ThisDemo1 thisdemol=new ThisDemo1();
14 thisdemo1.sum();
15 }
16 }
输出结果:
x+y=25
代码说明: 代码行.4-5中 定义成员变量x和y。
代码行.8中 通过this点取成员变量。
PS:上面程序中,thisdemol时ThisDemol类的一个实例,this和thisdemol等价,执行int z=this.x+this.y;,就相当于执行int z=thisdemol.x+thisdemol.y;。
PS:this只有在类实例化后才有意义。
实例3-6
1 package duke.example.ch3;
2
3 public class ThisDemo2 {
4 public String name;
5 public int age;
6
7 public ThisDemo2(String name,int age) {
8 this.name=name;
9 this.age=age;
10 }
11
12 public void say() {
13 System.out.println("网站的名字是"+name+",已经成立了"+age+"年");
14 }
15
16 public static void main(String[] args) {
17 ThisDemo2 thisdemo2=new ThisDemo2("www.baidu.com",15);
18 thisdemo2.say();
19 }
20 }
输出结果:
网站的名字是www.baidu.com,已经成立了15年
代码说明: 代码行.7-10中 成员变量与方法内部的变量重名时,通过this引用调用成员变量。 形参的作用域是整个方法体,是局部变量。在ThisDemo2(String name,int age)中,形参与成员变量重名,如果不使用this,访问到的就是局部变量name和age,而不是成员变量。
代码行.12-14中 在say()中没有使用this,因为成员变量的作用域是整个实例,当然也可以加上this。
eg:System.out.println("网站的名字是"+this.name+",已经成立了"+this.age+"年"):
PS:Java默认将所有成员变量和成员方法与this关联在一起,因此使用this在某些情况下是多余的。
实例3-7
1 package duke.example.ch3;
2
3 public class ThisDemo3 {
4 public String name;
5 public int age;
6
7 public ThisDemo3() {
8 this("www.baidu.com",15);
9 }
10
11 public ThisDemo3(String name,int age) {
12 this.name=name;
13 this.age=age;
14 }
15
16 public void say() {
17 System.out.println("网站的名字是"+name+",已经成立了"+age+"年");
18 }
19
20 public static void main(String[] args) {
21 ThisDemo3 thisdemo3=new ThisDemo3();
22 thisdemo3.say();
23 }
24 }
输出结果:
网站的名字是www.baidu.com,已经成立了5年
代码说明: 代码行.7-9中 在构造方法中,this调用另一个构造方法,调用动作必须置于最起始的位置。注意,不能在构造方法以外的任何方法内调用构造方法,在一个构造方法内只能调用一个构造方法。
PS:上述代码设计方法重载,即Java允许出现多个同名方法,只要参数不同即可。
实例3-8
1 package duke.example.ch3;
2
3 public class ThisDemo4 {
4
5 public static void main(String[] args) {
6 B b=new B(new A());
7 }
8 }
9
10 class A {
11 public A() {
12 new B(this).print();
13 }
14
15 public void print() {
16 System.out.println("Hello from A!");
17 }
18 }
19
20 class B {
21 A a;
22
23 public B(A a) {
24 this.a=a;
25 }
26
27 public void print() {
28 a.print();
29 System.out.println("Hello from B!");
30 }
31 }
输出结果:
Hello from A!
Hello from B!
代码说明: 代码行.12中 匿名对象就是没有名字的对象。如果对象只是用一次,就可以作为匿名对象,代码中new B(this).print();等价于(new B(this)).print();,先通过new B (this)创建一个没有名字的对象,再调用它的方法。
实例3-9
1 package duke.example.ch3;
2
3 public class Overload {
4 //一个普通的方法,不带参数的
5 void test() {
6 System.out.println("No parameters");
7 }
8
9 //重载上面的方法,并且带了一个整型参数
10 void test(int a) {
11 System.out.println("a:"+a);
12 }
13
14 //重载上面的方法,并且带了两个整型参数
15 void test(int a,int b) {
16 System.out.println("a and b:"+a+" "+b);
17 }
18
19 //重载上面的方法,并且带了一个双精度参数
20 double test(double a) {
21 System.out.println("double a:"+a);
22 return a*a;
23 }
24
25 public static void main(String[] args) {
26 Overload overload=new Overload();
27 overload.test;
28 overload.test(2);
29 overload.test(2,3);
30 overload.test(2,0);
31 }
32 }
输出结果:
No parameters
a:2
a and b:2 3
double a:2.0
PS:重载就是在一个类中,有相同的函数名称,但形参不同的函数。重载的结果,可以让一个程序段减少代码和方法的种类。
实例3-10
1 package duke.example.ch3;
2
3 public class Person {
4 //封装属性:将属性设置为私有
5 private String name;
6 private int age;
7
8 public String getName() { //外部通过此方法访问name属性
9 return name;
10 }
11
12 public void setName(String name) { //该方法是外部赋值私有属性name
13 this.name=name;
14 }
15
16 public int getAge() { //外部通过此方法访问age属性
17 return age;
18 }
19
20 public void setAge(int age) { //该方法使外部赋值私有属性age
21 shis.age=age;
22 }
23
24 void say() { //此方法可以被外部直接调用使用
25 System.out.println("我叫"+name+",今年"+age+"岁了!");
26 }
27
28 public static void main(String[] args) {
29 Person person=new Person(); //实例化person类
30 person.setName("小芳"); //通过开放方法给实例化对象的name属性赋值
31 person.setAge(22); //通过开放方法给实例化对象的age属性赋值
32 person.say(); //通过实例化对象的方法
33 }
34 }
输出结果:
我叫小芳,今年22岁了!
代码说明: 代码行.5-6中 private是修饰符表示私有化,这就封装了类的属性,封装后外部不能直接使用该属性。
代码行.7-22中 对外用标准的set/get方法修改/读取属性name和age的值。
代码行.24中 定义方法say(),该方法可以被外部直接调用使用。
PS:封装的优点:
1)隐藏类的实现细节。
2)让使用者只能通过实现制定好的方法访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作。
3)便于修改,增强了代码的可维护性。
总结:这一部分主要谈到了Java的类及其相关基础应用。另外,类的继承和多态是其高阶应用。