Java基础第九讲:面向对象基础(三)
如果您发现本文排版有问题,可以先点击下面的链接切换至老版进行查看!!!
Java基础第九讲:面向对象基础(三)
当我们写好了一个动物类,我们写鸟类的时候就可以继承动物类,自动获得动物类所拥有的属性和方法,提高了代码重用性。
4、Object类
Java中的所有对象(类)都是Object类的子类。我们可以用javap查看一下一个最简单的类的字节码:
1 |
public class Lesson09 { |
2 |
3 |
} |
5、继承的原则:
- 子类能够继承父类中被声明为public和protected的成员变量和成员方法。
- 子类能够继承在同一个包中的默认修饰符修饰的成员变量和成员方法。
- 如果子类声明了一个与父类变量同名的成员变量,则子类不能继承父类的成员变量,这种做法叫做变量的隐藏。
- 如果子类声明了一个与父类方法同名的成员方法,则子类不能继承父类的成员方法,这种做法方法的重写。
这三个部分都是可选的,包声明如果要有必须写在最前面,并且只能写一份。导入声明可以写多个,类声明也可以写多个。
2、包的概念(package)
类名是类之间彼此区分的标示,一个程序中类数量增多是,必然会遇到类名冲突的情形。包提供了类的组织和管理方式。包的用途有以下三种:
- 将功能相近的类放在同一个包里,方便使用和查找
- 类名相同的文件可以放在不同的包里而不会产生冲突
- 可以依据包设定访问权限
4、 包的例子:
01 |
//包的声明 |
02 |
package android.java.basic; |
03 |
04 |
//导入声明 |
05 |
import java.util.Date; |
06 |
07 |
//类声明 |
08 |
class Animal{ |
09 |
long birthTime = new Date().getTime(); |
10 |
11 |
void eat(){ |
12 |
System.out.println("eating"); |
13 |
} |
14 |
} |
15 |
16 |
//类声明 |
17 |
class Fish extends Animal { |
18 |
void swim(){ |
19 |
System.out.println("swimming"); |
20 |
} |
21 |
} |
22 |
23 |
//类声明 |
24 |
public class Lesson09 { |
25 |
public static void main(String[] args){ |
26 |
27 |
//动物类 |
28 |
Animal a = new Animal(); |
29 |
a.eat(); |
30 |
System.out.println(a.birthTime); |
31 |
32 |
//鱼类 |
33 |
Fish f = new Fish(); |
34 |
f.eat(); |
35 |
f.swim(); |
36 |
System.out.println(f.birthTime); |
37 |
} |
38 |
} |
四、访问修饰符 public protected 默认的 private
在Java中可以用访问修饰符来控制类或者类成员对程序的其它部分的可见性,从而在语言级别实现访问控制。当一个类无权访问另一个类或者类的成员时,编译器会提示你试图访问一些可能不存在的内容。
| 看见性 | public | protected | 默认 | private |
| 从同一个类 | 是 | 是 | 是 | 是 |
| 从同一个包中的任何类 | 是 | 是 | 是 | 否 |
| 从同一个包中的子类 | 是 | 是 | 是 | 否 |
| 从包外的子类 | 是 | 是,通过继承 | 否 | 否 |
| 从包外的任何非子类的类 | 是 | 否 | 否 | 否 |
- 对于类的修饰符,只能有2个选择,用public修饰或者不用(不用就是默认修饰符)。
- 如果一个类本身对另一个类不可见,则即使将其成员声明为public,也没有一个成员是可见的,只有当你确类本身对你是可见的时,查看其各个成员的访问级别才有意义。
- 对于类的成员(member, 包括属性和方法),可以用 public protected 默认的和private 4种修饰符。
- 永远不要用访问修饰符修饰局部变量,编译器会毫不留情的报错。(记住:局部变量只有一个修饰符可以用,那就是final)
01 |
public class Lesson09_1 { |
02 |
03 |
int i=1; |
04 |
int j=1; |
05 |
int k=1; |
06 |
07 |
void test(int i){ |
08 |
int j=2; |
09 |
System.out.println("i="+i); |
10 |
System.out.println("j="+j); |
11 |
System.out.println("k="+k); |
12 |
} |
13 |
14 |
public static void main(String[] args){ |
15 |
Lesson09_1 lesson = new Lesson09_1(); |
16 |
lesson.test(2); |
17 |
} |
18 |
} |
我们可以看到,当方法内的局部和成员变量名字相同时,在方法内,局部变量遮蔽住了成员变量,因此打印出来的是2,而不是1。
再看一个子类成员变量遮蔽父类成员变量的例子。
01 |
public class WhiteHorse extends Horse { |
02 |
03 |
private static String color ="白色"; |
04 |
05 |
public static int leg =4; |
06 |
07 |
public static void main(String[] args){ |
08 |
09 |
WhiteHorse xiaobai = new WhiteHorse(); |
10 |
System.out.println(xiaobai.color); |
11 |
System.out.println(xiaobai.leg); |
12 |
13 |
//类变量是遮蔽不住的 |
14 |
System.out.println(Horse.color); |
15 |
16 |
//强制转换后我们看到父类的实体leg变量还在,只是被隐藏了 |
17 |
Horse xiaobai1 = (Horse)xiaobai; |
18 |
System.out.println(xiaobai1.leg); |
19 |
20 |
} |
21 |
22 |
} |
2、方法重写 Override
当子类继承父类时,如果子类方法的签名和父类方法的签名相同时,子类就无法继承父类的方法,此时子类的方法就覆盖了父类的方法,我们称之为重写。重写可以定义子类某个行为的特殊性。
譬如动物会喝水,但是猫喝水和人喝水的具体行为就不同。
重写方法的规则如下:
- 参数列表必须与重写的方法的参数列表完全匹配(方法签名相同)。如果不匹配,你得到的将是方法重载。
- 返回类型必须与超类中被重写方法中原先声明的返回类型或其子类型相同。
- 访问级别的限制性可以比被重写方法弱,但是访问级别的限制性一定不能比被重写方法的更严格。
- 仅当实例方法被子类继承时,它们才能被重写。子类和超类在同一个包内时,子类可以重写未标示为private和final的任何超类方法。不同包的子类只能重写标示为public或protected的非final方法。
- 无论父类的方法是否抛出某种运行时异常,子类的重写方法都可以抛出任意类型的运行时异常。
- 重写方法一定不能抛出比被重写方法声明的检验异常更新或更广的检验异常,可以抛出更少或更有限的异常。
- 不能重写标示为final的方法。
- 不能重写标示为static的方法。
- 如果方法不能被继承,那么方法不能被重写。
1 |
public class Horse { |
2 |
//给马写个摆Pose的方法 |
3 |
public void pose(){ |
4 |
//样子很酷 |
5 |
System.out.println("Cool!"); |
6 |
} |
7 |
} |
01 |
public class WhiteHorse extends Horse { |
02 |
03 |
//白马重写了摆pose的方法 |
04 |
public void pose(){ |
05 |
//白马更酷一点 |
06 |
System.out.println("Cool!!!!"); |
07 |
} |
08 |
09 |
public static void main(String[] args){ |
10 |
WhiteHorse xiaobai = new WhiteHorse(); |
11 |
xiaobai.pose(); |
12 |
} |
13 |
} |
我们再把白马类中pose方法的访问修饰符改成private试试看:
六、this 和 super
1、this.成员变量
当成员变量被局部变量隐藏时想使用成员变量,可以用this关键字来访问成员变量。
01 |
public class Lesson09_1 { |
02 |
03 |
int i=1; |
04 |
int j=1; |
05 |
int k=1; |
06 |
static int l = 1; |
07 |
08 |
void test(int i){ |
09 |
int j=2; |
10 |
int l=2; |
11 |
System.out.println("i="+i); |
12 |
System.out.println("j="+j); |
13 |
System.out.println("k="+k); |
14 |
System.out.println("l="+l); |
15 |
16 |
System.out.println("this.i="+this.i); |
17 |
System.out.println("this.j="+this.j); |
18 |
System.out.println("this.k="+this.k); |
19 |
System.out.println("this.l="+this.l); |
20 |
21 |
} |
22 |
23 |
public static void main(String[] args){ |
24 |
Lesson09_1 lesson = new Lesson09_1(); |
25 |
lesson.test(2); |
26 |
27 |
} |
28 |
} |
2、this() 构造函数
在构造方法中可以使用 this() 来引用另一个构造方法。
01 |
public class Lesson { |
02 |
03 |
private int minute=0; |
04 |
05 |
Lesson(){ |
06 |
this(45); |
07 |
} |
08 |
09 |
Lesson(int minute){ |
10 |
this.minute = minute; |
11 |
} |
12 |
13 |
public static void main(String[] args){ |
14 |
Lesson lesson = new Lesson(); |
15 |
System.out.println(lesson.minute); |
16 |
17 |
Lesson lesson2 = new Lesson(30); |
18 |
System.out.println(lesson2.minute); |
19 |
} |
20 |
} |
我们看到this(45),的确调用了另外一个带参数的构造方法。需要注意的是this()必须写在构造方法的第一行。
3、super.成员
当父类的成员变量被隐藏、成员方法被重写(覆盖),此时想使用父类的这些成员时就要用super关键字。我们改一下上面马和白马的例子:
Horse.java
01 |
public class Horse { |
02 |
03 |
public int height =120; |
04 |
05 |
//给马写个摆Pose的方法 |
06 |
public void pose(){ |
07 |
//样子很酷 |
08 |
System.out.println("Cool!"); |
09 |
} |
10 |
} |
01 |
public class WhiteHorse extends Horse { |
02 |
03 |
public int height =150; |
04 |
05 |
//白马重写了摆pose的方法 |
06 |
public void pose(){ |
07 |
08 |
//先摆一个马的pose |
09 |
super.pose(); |
10 |
11 |
//白马更酷一点 |
12 |
System.out.println("Cool!!!!"); |
13 |
} |
14 |
15 |
public void printHeight(){ |
16 |
17 |
//打印父类被隐藏的变量 |
18 |
System.out.println(super.height); |
19 |
20 |
//打印实例变量 |
21 |
System.out.println(height); |
22 |
} |
23 |
24 |
public static void main(String[] args){ |
25 |
WhiteHorse xiaobai = new WhiteHorse(); |
26 |
xiaobai.pose(); |
27 |
xiaobai.printHeight(); |
28 |
29 |
} |
30 |
} |
我们看到在子类的方法里可以使用super来引用被隐藏的父类变量,被覆盖(重写)的父类方法。
4、super() 父类构造函数
讲super()之前,我们先看一下这个例子:
Horse.java
1 |
public class Horse { |
2 |
3 |
public Horse(){ |
4 |
System.out.println("马类的构造函数"); |
5 |
} |
6 |
7 |
} |
01 |
public class WhiteHorse extends Horse { |
02 |
03 |
public WhiteHorse(){ |
04 |
System.out.println("白马类的构造函数"); |
05 |
06 |
} |
07 |
08 |
public static void main(String[] args){ |
09 |
new WhiteHorse(); |
10 |
} |
11 |
} |
我们看到,构造白马类之前,虚拟机先构造了它的父类马类,由此我们看到了白马类能继承马类的属性和方法的根本原因,原来每一个白马类同时也是一个马类,还是一个Object类。在创建对象时,一个对象的逐级父类自顶向下依次都创建了。
上图是一个白马对象在内存中的示意图,我们看到最外面的是WhiteHorse,内层还有一个Horse对象,更内层还有一个Object对象。
用下面的栈上的示意图可以更清晰的看到对象的创建过程。
首先调用的是main方法,main方法调用 new WhiteHorse() ,WhiteHorse()构造函数调用了一个默认的super(),super()方法就是父类的构造方法,以此类推最后调用了Object()构造方法。
5、带参数的的super()方法
在上面的例子里,我们看到编译器在你没有调用super()方法的时候,插入了一个默认的super()方法。可惜的是编译器并不会自动插入带参数的super(), 因此我们遇到这种情况就只能自己手工插入对super()的调用。
下面我们把上面的例子更改一下:
Horse.java的构造函数添加一个参数:
01 |
public class Horse { |
02 |
03 |
protected int leg = 0; |
04 |
05 |
public Horse(int leg){ |
06 |
07 |
this.leg=4; |
08 |
09 |
System.out.println("马类的构造函数"); |
10 |
} |
11 |
12 |
} |
标准Java编译器的提示有点故作神秘,这个提示几乎什么都没说;我们换个工具,在中文Eclipse上的提示就明显多了:
01 |
public class WhiteHorse extends Horse { |
02 |
03 |
public WhiteHorse(){ |
04 |
super(4); |
05 |
System.out.println("白马类的构造函数"); |
06 |
} |
07 |
08 |
public static void main(String[] args){ |
09 |
new WhiteHorse(); |
10 |
} |
11 |
} |
到这里,我们是不是可以小小总结一下,构造函数只能用new、this() 和 super() 的方式来访问,是不能像方法一样写方法名访问的。 本讲就到这里,下次再见。 分享至上:分享源头
- 分类:
- Java
更新时间:
相关文章
Java基础第十一讲:面向对象基础(五)
本讲内容:接口 一、为什么要有接口 我们已经知道Java中只支持单继承,或者说不允许多重继承的出现,又可以说一个类只能有一个父类。为了提供类似多重继承的功能,Java提供了接口的 阅读更多…
Java基础第十二讲:面向对象基础(六)
本讲内容:内部类 Java语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类。内部类又分为:常规内部类、局部内部类、匿名内部类和静态嵌套类四种。我们内部类的知识在Android手机开发中 阅读更多…
Java基础第八讲:面向对象基础(二)
本讲内容:成员变量、方法、方法的重载、构造函数 一、用程序讲解小白的故事 小白是一条狗,它心情好的时候会恭喜人发财,它心情差的时候会对路人撒野,吓得路人落荒而逃。下面我们用面向对象的方 阅读更多…
Java基础第七讲:面向对象基础(一)
本讲内容:面向对象的概念和发展、面向对象的特征 一、面向对象(Object Oriented)编程语言的历史 1950年有个叫做荷兰德的学生作为程序员进入IBM的时候,这个世界上的程序 阅读更多…
Java基础第十讲:面向对象基础(四)
本讲内容:抽象类、初始化块 一、抽象类 用 abstract 修饰的类定义,我们称之为抽象类,抽象类不能被实例化。 用 abstract 修饰的方法,我们称之为抽象方法,抽象方法 阅读更多…

