菜鸟学习笔记:Java面向对象篇中
继承
概念
继续上一篇的例子:
生物类是对所有有生命事物的抽象,而生物又可以分为植物、动物和微生物,它们有生物所有的属性和行为,但又有着自己独有的特征,所以它们是生物类的子类。同样的鱼类鸟类也是动物类的子类。大类包含小类的关系就称为继承。在Java中的继承用extends关键字表示。
在代码中使用继承来管理类之间的关系可以很好的提高代码的复用性,假设需要定义所有的动物类,那么如果不用继承,我们会写出这样的代码:
class Fish{
int old;
String environment;
String metabolize;
public void grow(){
System.out.println("我在成长");
}
public void reproduction(){
System.out.println("我在繁殖");
}
public void move(){
System.out.println("我在移动");
}
public void Swimming(){
System.out.println("游泳,游泳");
}
}
class Bird{
int old;
String environment;
String metabolize;
public void grow(){
System.out.println("我在成长");
}
public void reproduction(){
System.out.println("我在繁殖");
}
public void move(){
System.out.println("我在移动");
}
public void Fly(){
System.out.println("飞啊飞");
}
}
像这样的代码,如果类多了那么会非常冗余,如果用继承的方式那代码就可以这样写:
//首先定义动物父类
class Animal{
int old;
String environment;
String metabolize;
public void grow(){
System.out.println("我在成长");
}
public void reproduction(){
System.out.println("我在繁殖");
}
public void move(){
System.out.println("我在移动");
}
}
//鱼类继承动物类
class Fish extends Animal{
public void Swimming(){
System.out.println("游泳,游泳");
}
}
//鸟类继承动物类
class Fish extends Animal{
public void Fly(){
System.out.println("飞啊飞");
}
}
这样父类的属性和方法在子类中就可以使用:
public static void main(String[] args) {
Fish oFish = new Fish();
Bird oBrid = new Bird();
oFish.environment = "大海"; //子类有父类属性
oBrid.environment = "天空"; //子类有父类属性
oFish.move();//子类调用父类方法,打印“我在移动”
oBrid.Fly();//子类调用自己方法,打印"飞啊飞"
}
通过上例可以看到继承的优势在两个实例时已经很明显了。对Java继承有几点可以总结一下:
Java中定义类时如果没有指定父类那么默认继承:java.lang.Object
Java中只有单继承,不能多继承,意思就是一个类只能有一个父类。
方法重写(override)
在之前讲重载时提前说过这个概念,这两个概念特别容易混,所以大家最好放在一块记。
在子类继承父类时,父类方法可能无法完全实现子类的需求,这时子类就可以通过方法重写对父类方法进行一定修改,还是上面那个例子,在鱼类中我们修改父类的move方法:
class Fish extends Animal{
public void move(){
System.out.println("我在游着移动");
}
public void Swimming(){
System.out.println("游泳,游泳");
}
}
这样在main方法中:
public static void main(String[] args) {
Fish oFish = new Fish();
oFish.move();//子类重写父类方法,打印“我在游着移动”
}
注意重写的方法必须和被重写的方法有同样的方法名称、参数列表和返回值类型。父类中:int move(String methods)方法重写也必须是这种形式。
Object类
刚刚提到的Object类是Java所有类型的根类,在没有extends的情况下默认继承这个类,
Object类的概念很简单,重要的是Object上的默认方法,这是面试的重要考点之一,我们可以对这些方法加以运用,同样也可以对其重写,下面介绍几个常用Object方法:
1.equals(Object obj)方法,将对象与传入的obj进行比较,相等返回true,否则返回false。equals方法和“==”的区别面试中的常见问题,个人单独对其做了下简单的总结,准备面试的小伙伴可以简单看一看, 原文链接:
2.toString()方法,普通对象调用会返回代表对象的“类名”+@+对象哈希码的16进制字符串(可以理解为对象唯一标识)。Integer(int类型的包装类,后面讲)等包装类会重写该方法,会将本来的数据转化为字符串显示。
3.clone()方法,将对象复制一份,得到一个新的对象。注意复制后生成新对象,==判断为false。
4.getClass()方法,通过对象获取类。
这里罗列的几个我能想到的较为常用的方法,想了解其他方法就需要自己去阅读源码了,这里啰嗦以下,确实刚开始的时候阅读源码很痛苦,但你不得不承认阅读源码和实践是提升编成能力的最好方式,只有真的知道它怎么写你才能学懂它。
Super关键字
super与this相对应,是我们定义方法时传入的另一个隐式参数,它是直接父类对象的引用(指向父类的引用变量,如果不解可以回顾一下引用变量),可以通过其来访问父类中被子类重写的属性和方法。
到这你可能会有疑问我没有建立父类对象啊,为什么又冒出个父类对象,实际上在每次调用构造方法时会默认在构造函数开始时调用super(),也就是父类的构造方法,举例说明:
class Animal{
int old;
String environment;
String metabolize;
Animal(){
System.out.println("Animal构造方法被调用了");
}
public void grow(){
System.out.println("我在成长");
}
public void reproduction(){
System.out.println("我在繁殖");
}
public void move(){
System.out.println("我在移动");
}
}
class Fish extends Animal{
//没有写构造方法,那么Java会默认给我们添加无参构造方法,而在无参构造方法中又会默认调用super()方法,默认添加的代码如下:
//Fish(){
//super();
//}
public void move(){
System.out.println("我在游着移动");
}
public void Swimming(){
System.out.println("游泳,游泳");
}
}
在main函数中:
public static void main(String[] args) {
Fish oFish = new Fish(); //这时会打印Animal构造方法被调用了
}
这段程序在堆中的对象结构是这样的:
总结来说,在我们new一个对象时,由于非Object类的构造方法中会默认调用super()方法,那么堆中会同时生成一个Object对象和一个Animal对象,所有非Object对象的方法中都会有this和super两个隐式参数,当子类调用方法时会先在自己的方法中寻找,如果找不到回去父类的方法中寻找,如果找不到再去父类的父类中寻找,依次向上直到Object类还未找到的话报出错误。当父类的方法被子类重写时则会在子类中生成新的方法,子类调用时直接调用自身的方法。像上例中,Fish对象调用move()方法时会直接调用自己重写的move()方法,不再去父类中寻找。
组合
解决代码复用问题的除了继承还可以用组合来实现,话不多说直接实例:
class Fish extends Animal{
Animal animal = new Animal();//再这里直接定义Animal类型变量,这样也可以使用Animal中的属性和方法,这种方式就叫组合
public void move(){
animal.move();//这样也可以调用父类的方法
}
public void Swimming(){
System.out.println("游泳,游泳");
}
}
main方法中:
public static void main(String[] args) {
Fish oFish = new fish(); //这时会打印Animal构造方法被调用了
oFish.animal.old//调用父类属性
oFish.move();//打印我在移动
}
在类中包含其他类的成员时一般可以用组合实现(衣服类里有一个扣子类属性),注意他和继承的区别也是面试容易问道的问题。
final关键字补充
在讲完继承之后我们就可以把final关键字扩充到方法和类层面:
- 在修饰变量时,正如第二篇所说的的代表常量。
- 在修饰方法时,被修饰方法不可以被子类重写,但是可以自己重载。
- 在修饰类时,表示该类不能再有子类,也就是不能被继承(比如Math类、String类)。
封装
在我们开开心心的写代码时,我们只需要关系在键盘上按那些键后编辑器中会出现我们想要的代码,而不用去在乎键盘的键是怎么输入到显示屏上的,封装就是这个道理,我们在写程序时要求高内聚、低耦合,高内聚要求用户使用程序时可以根据输入得到正确的输出,而不需要关心内部是怎么实现的。而低耦合则要求供用户使用的方法越少越好。
访问控制符
之前写的例子中方法最前面通常要加一个public,它就是访问控制符,访问控制符用来来控制类、属性和方法的访问权限和其他功能,通常放在语句的最前端。当然访问控制符不止public,以下罗列出所有访问控制符:
访问控 | 同一个类中可访问 | 同一个包中可访问 | 子类可以访问 | 所有类可以访问 |
---|---|---|---|---|
private | 可以 | |||
default | 可以 | 可以 | ||
protected | 可以 | 可以 | 可以 | |
public | 可以 | 可以 | 可以 | 可以 |
其中如果属性或方法之前没有加访问控制符则默认同一个类和包中可以访问(default)。
为实现封装的思想在类中的属性一般采用private来修饰,方法一般采用public来修饰,其中外部可以访问的属性要提供响应的get(获取)/set(设置)方法(Eclipse可以自动生成,注意生成Boolean类型get方法以is开头),通过两个方法对内进行访问。一些辅助性的方法也可以用private修饰。运用封装思想我们可以改造Animal类,此时的类就是Java类的基本样子了。
class Animal{
//属性
private int old;
private String environment;
private String metabolize;
//构造方法
public Animal() {
super();
}
//get和set方法
public int getOld() {
return old;
}
public void setOld(int old) {
this.old = old;
}
public String getEnvironment() {
return environment;
}
public void setEnvironment(String environment) {
this.environment = environment;
}
public String getMetabolize() {
return metabolize;
}
public void setMetabolize(String metabolize) {
this.metabolize = metabolize;
}
//私有方法
private String Name(){
return "我是动物,";
}
//提供给外部调用的方法
public void grow(){
System.out.println(Name()+"我在成长");
}
public void reproduction(){
System.out.println("我在繁殖");
}
public void move(){
System.out.println("我在移动");
}
}
多态
以上面Animal类的Fish类还是如下结构:
class Fish extends Animal{
public void move(){
System.out.println("我在游着移动");
}
public void Swimming(){
System.out.println("游泳,游泳");
}
}
我们在main方法中可以这样做
public test{
public static void move(Animal a){
a.move();//调用Animal类中的set方法设置old属性
}
public static void main(String[] args) {
Fish oFish1 = new Fish(); //用Fish类的引用来指向fish对象
Animal oFish2 = new Fish();//用Animal类的引用指向fish对象,程序不会报错
Object oFish3 = new Fish();//用Object类的引用指向fish对象,程序也不会报错
move(oFish2); //会打印"我在游着移动"
//等价于Animal a = oFish2;a.move();
move(oFish1); //会打印"我在游着移动"
//等价于Animal a = oFish1;a.move();
move(oFish3); //会抛异常
//等价于Animal a = oFish3;a.move();oFish3是Object类型引用,
//虽然对象是Animal的子类,但程序为避免错误不允许进行引用传递,
//解决方法move((Animal)oFish3);
}
}
这段代码就是多态,总结来说就是父类引用指向子类对象。从实际层面上理解就是一条鱼是鱼类,也是动物类,也是生物类,这些类的都可以囊括一条鱼。一般情况下如果有方法重写,用多态可以很好的提高代码复用性。上例中内存结构如图:
上一篇:菜鸟学习笔记:Java基础篇3(面向对象思想、程序执行过程内存分析、面向对象重要概念)