---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
一、继承概述
1、继承的优点:
1)提供了代码的复用性;
2)让类与类之间产生了关系。有了这个关系,才有了多态的特性。
注意:千万不要为了获取其他类的功能,简化代码而继承。必须是类与类之间有所属关系才可以继承。
java只支持单继承,不支持多继承。因为多继承容易带来安全隐患:当多个父类中定义了相同方法,而方法内容不同时,子类对象不确定要运行哪个。
但java保留了这种机制,并用另一种形式来表示:多实现。
java支持多层继承,也就是一个继承体系。
2、如何使用一个继承体系中的方法:
先查阅体系父类的描述,因为父类中定义的是该体系中共性方法。通过了解共性方法,就可以知道该体系的基本功能。然后,具体调用时创建最子类的对象。因为1)父类可能不能创建对象;2)子类对象可以使用更多的方法,包括基本的也包括特有的。
3、继承的原则:
1)子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法。
2)子类中定义的成员变量和父类中定义的成员变量相同时,则父类中的成员变量被隐藏。
3)子类中定义的成员方法,它的名字、返回类型及参数个数和类型都与父类中的某个成员方法完全相同时,则父类的成员方法被覆盖。
二、变量
如果子类中出现非私有的同名成员变量时,子类要访问本类中的变量,用this语句;子类要访问父类中的同名变量,用super语句。
例1:
class Parent
{
int i=1;
}
class Child extends Parent
{
int i=2;
public int getParentI()
{
return super.i; //访问父类同名变量
}
public int getChildI()
{
return this.i; //访问子类同名变量
}
}
class ExtendsDemo
{
public static void main(String[] args)
{
Child c=new Child();
System.out.println(c.getParentI());
System.out.println(c.getChildI());
}
}
输出结果:
1
2
三、方法
如果子类中的成员方法的名字、返回类型及参数个数和类型都与父类中的某个成员方法完全相同时,则父类的成员方法被覆盖(重写)。
覆盖:
当子类继承父类,沿袭了父类的功能到子类中。虽然子类具备了该方法,但想要的方法内容却和父类的不一致。这时,没有必要定义新方法,而是使用覆盖特性,保留父类的方法定义,并重写方法内容。
覆盖原则:
1)子类覆盖父类,必须保证子类的权限大于等于父类的权限,否则编译失败。
2)静态只能覆盖静态。
3)子类覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法只能抛出父类的异常货该异常的子类;如果父类抛出多个异常,那么子类的覆盖方法只能抛出父类异常的子集;如果父类方法中没有异常抛出,那么子类的覆盖方法也不可以抛出异常。如果子类的覆盖方法发生了异常,就必须进行try处理,绝对不能抛出。
覆盖常用于功能扩展:
class Tel
{
void show()
{
System.out.println("number");
}
}
class NewTel extends Tel
{
void show()
{
super.show();
System.out.println("name");
System.out.println("pic");
}
}
四、构造函数
1)在对子类对象进行初始化时,父类的构造函数也会运行。因为子类的所有构造函数默认第一行有一条隐式的语句:super();
2)如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。注意:super语句必须在子类构造函数的第一行。
3)当父类中没有空参数的构造函数时,子类必须手动定义super语句来指定访问父类中的构造函数,否则编译失败。
4)子类的构造函数第一行也可以手动指定this语句来访问本类的其他构造函数。
例2:
class Person
{
private String name;
Person(String name)
{
this.name=name;
}
Person()
{
this.name="AAA";
}
public void getStudent()
{
System.out.println("name="+this.name);
}
}
class Student extends Person
{
private String id;
Student(){} //默认第一行为super();
Student(String name)
{
super(name); //手动指定super语句访问父类构造函数
}
Student(String name,String id)
{
this(name); //手动this语句访问本类其他构造函数
this.id=id;
}
public void getStudent() //覆盖父类getStudent()方法
{
super.getStudent();
System.out.println("id="+this.id);
}
}
class ExtendsDemo
{
public static void main(String[] args)
{
Student s1=new Student();
Student s2=new Student("BBB");
Student s3=new Student("CCC","001");
s1.getStudent();
s2.getStudent();
s3.getStudent();
}
}
输出结果:
name=AAA
id=null
name=BBB
id=null
name=CCC
id=001
五、继承中容易出现的错误
例3:
class Parent
{
int a = 1;
public int getA()
{
return this.a;
}
}
class Child extends Parent
{
int a = 2;
}
public class ExtendsDemo
{
public static void main(String[] args)
{
Child c =new Child();
System.out.println(c.getA());
System.out.println(c.a);
}
}
输出结果:
1
2
此例中,Child继承了Parent,根据继承原则2),父类中的int a变量被隐藏,所以直接打印c.a,结果为2。但因为Child中没有同名的getA()方法,所以c.getA()中调用的getA()方法是继承自Parent类的,Parent类中的getA()方法调用的是Parent类中的a,所以结果为1。
例4:
class Parent
{
int a = 1;
public int getA()
{
return this.a;
}
}
class Child extends Parent
{
int a = 2;
public int getA()
{
return this.a;
}
}
public class ExtendsDemo
{
public static void main(String[] args)
{
Child c =new Child();
System.out.println(c.getA());
System.out.println(c.a);
}
}
输出结果:
2
2
例4与例3 的区别在于,Child类中定义了同名的getA方法,它覆盖了Parent类中的同名方法。所以这里c.getA()中调用的getA()是Child类中的方法,它引用的是Child类中的a,所以结果为2。
例5:
class Parent
{
int a = 1;
public int getA()
{
return this.a;
}
}
class Child extends Parent
{
public Child()
{
a=2;
}
}
public class ExtendsDemo
{
public static void main(String[] args)
{
Child c =new Child();
System.out.println(c.getA());
System.out.println(c.a);
}
}
输出结果:
2
2
此例中,Child类继承Parent类中的变量int a和方法getA()。因此,Child类中的构造函数对a的赋值实际上是对继承自父类的变量a赋值,输出结果显示为2。
例6:
class Parent
{
int a = 1;
public int getA()
{
return this.a;
}
}
class Child extends Parent
{
int a=2;
public Child()
{
a=3;
}
}
public class ExtendsDemo
{
public static void main(String[] args)
{
Child c =new Child();
System.out.println(c.getA());
System.out.println(c.a);
}
}
输出结果:
1
3
此例中,Parent类中的a被Child类中的同名变量a所隐藏。所以Child类中的构造函数初始化的是Child类中的a,所以打印c.a的结果为3。由于未覆盖Parent类中的getA(),所以c.getA()调用的仍是Parent类中的a,所以打印c.getA()的结果为1。
例7:
class Parent
{
int i=1;
Parent()
{
i=i+1;
System.out.println(i);
printI();
}
public void printI()
{
System.out.println("Parent.i="+i);
}
}
class Child extends Parent
{
int i=2;
Child()
{
i=i+2;
System.out.println(i);
printI();
}
}
class ExtendsDemo
{
public static void main(String[] args)
{
Parent p=new Parent();
Child c=new Child();
c.printI();
System.out.println(c.i);
}
}
输出结果:
2
Parent.i=2
2
Parent.i=2
4
Parent.i=2
Parent.i=2
4
此例中,Parent类的构造函数访问的是Parent类中的i,printI()也毫无疑问的调用的是Parent类中的i,因此直接打印i的结果为2,而printI()的结果为2;
Child类的构造函数默认第一行为super();
Child类的构造函数访问的是Child类中的i,而printI()调用的是Parent类中的a,因此直接打印i的结果为4,而printI()的结果为2。
例8:
class Parent
{
int i=1;
Parent()
{
i=i+1;
System.out.println(i);
printI();
}
public void printI()
{
System.out.println("Parent.i="+i);
}
}
class Child extends Parent
{
int i=2;
Child()
{
i=i+2;
System.out.println(i);
printI();
}
public void printI()
{
System.out.println("Parent.i="+i);
}
}
class ExtendsDemo
{
public static void main(String[] args)
{
Parent p=new Parent();
Child c=new Child();
c.printI();
System.out.println(c.i);
}
}
输出结果:
2
Parent.i=2
2
Parent.i=0
4
Parent.i=4
Parent.i=4
4
此例中,Parent类的构造函数访问的是Parent类中的i,printI()也毫无疑问的调用的是Parent类中的i,因此直接打印i的结果为2,而printI()的结果为2;
Child类的构造函数默认第一行为super(); 它调用Parent类的构造函数,此时构造函数访问的仍是Parent类中的i,而printI()因为覆盖所以调用的是Child类中的i,但注意此时Child类中i还未初始化,因此直接打印i的结果为2,而printI()的结果为0;
Child类的构造函数访问的是Child类中的i,而printI()调用的是Parent类中的a,因此直接打印i的结果为4,而printI()的结果为4。
总结:
1)如果调用父类方法,则父类方法中引用的变量一定是父类中的变量(如例3);
2)如果调用子类方法,那么:如果子类中没有同名变量(未实现隐藏),而在子类方法中引用了,则引用的是父类的变量;如果有同名变量(实现隐藏),则引用的是子类本身的变量;
---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------详细请查看:www.itheima.com