java当中一个类可以被另一个类通过关键字extends继承,继承的类成为子类或派生类,被继承的类成为父类、基类或者超类。
eg:public class UNStudent extends Student{}
子类: UNStudent 父类: Student
现在有一个父类Student
public class Student {
private String name;
//构造器一
public Student(String n)
{
name=n;
System.out.println("父类构造器一被调用,传入形参为: "+name);
}
// 构造器二 方法重载,函数名一样,形参列表不同
public Student()
{
this("通过构造器二调用构造器一");
System.out.println("调用父类构造器二");
}
public void setname(String n)
{
name=n;
}
public String getname()
{
return name;
}
protected void study()
{
System.out.println("student父类study方法:"+name+"正在学习");
}
}
一个子类UNStudent
public class UNStudent extends Student {
public UNStudent()
{
super();//可以省略不写会默认调用,且一定在第一行
System.out.println("子类unstudent构造器被调用");
}
public void printname()
{
System.out.println("名字:"+getname());
}
public void study()
{
//super.study();如果需要用到父类中的方法
System.out.println("子类重写父类study方法");
}
}
java不支持多重继承,即子类只能有一个父类。
子类和父类在同一包中,子类继承了父类中非private的成员变量和方法。
子类和父类不在同一个包中,子类继承了父类中protected和public成员变量和方法,不继承private和友好访问权限内容。
虽然不能直接使用他们private成员变量和方法(被private定义的属性和方法,只能在当前被定义的类中使用),但子类对象可以通过调用父类中的get方法使用他们。
当创建一个子类对象的时候,不仅子类中声明的成员变量被分配了内存,而且父类的成员变量也都分配了内存空间,但是只将其中一部分,即子类继承的那部分成员变量分配给子类对象的变量。
当子类和父类声明了同名的变量,则父类中的变量被隐藏。
子类继承的方法只能操作子类继承和隐藏的成员变量。
子类新定义的方法可以操作子类继承和子类新声明的成员变量,但无法操作子类隐藏的成员变量(需使用super关键字操作)
子类不继承父类的构造方法。
子类的构造器方法一定会使用super来调用父类的构造器方法。
①:无参构造方法在子类构造方法中会被默认调用,子类可以不写构造方法。(构造方法不写,为空,也会被自动调用)
②:父类只有一个构造方法且构造方法是带参数的,则子类必须要写构造方法,且在子类构造方法必须传入形参来调用父类构造方法,不可以省略super();所以在父类中如果定义了多个构造方法,应当包括一个不带参数的构造方法。
public void test2()
{
UNStudent s3=new UNStudent();
s3.setname("guochao");
s3.getname();
s3.printname();
}
方法重载发生在一个类当中,函数名相同但是形参不同。
public void teach(MiddleStudent s){
}
public void teach(UNStudent s){
}
方法重写发生在两个具有继承关系的类中,如果子类可以继承父类的某个方法,那么子类就有权利重写这个方法,并且重写方法的名字、形参需完全相同。
在子类中被重写方法的访问修饰符一定要大于等于父类:
public>protected>default>private
其余返回值类型,方法名,参数类型(个数,顺序)要完全一样,如上所示。
public void test2()
{
s3.setname("guochao");
s3.getname();
s3.printname();
s3.study();
}
重写的方法既可以操作继承的成员变量、调用继承的方法,也可以操作子类新声明的成员变量、调用新定义的其他方法,但无法操作被子类隐藏的成员变量和方法(需使用super关键字)
静态方法不能被重写
重写方法的返回值类型可以是父类方法类型的子类型。
class A
{
Object get(){
return null;
}
}
class B extends A{
Integer get(){
return new Integer(100);
}
}
本例中子类B返回一个Interger对象,父类中返回值为Object对象,因此,父类中的方法必定可以接收一个Interger 对象。
最终的返回值类型需要注意:
A son=new B();
Object ob =son.get();//返回的是父类中原方法的返回值类型
B son2=new B();
Integer in=son.get();//返回值是子类中重写方法的返回值类型
final关键字可以修饰类、成员变量、方法中的局部变量。
final类不可以被继承,即不能有子类。例如String类,java不允许用户程序扩展扩展String类。
final修饰父类中的一个方法,那么这个方法不允许被子类重写。
final修饰成员变量或局部变量,那么它就是常量,运行期间不允许在发生变化,且必须指定该常量的值。
在创建对象的时候,我们知道用: 类 对象名 =new 类()
但一旦有两个继承关系的类,我们会发现有两种表示方式:
子类 对象名=new 子类();
父类 对象名=new 子类();
对于第二种形式,是对象自动得向上转型,通过字面意思我们可以理解为将子类型转成父类型。
当向上转型之后,父类引用变量可以访问子类中属于父类的属性和方法(为子类中的重写方法或继承的方法),但是不能访问子类独有的属性和方法。
注:如果子类重写了父类的静态方法,那么子类对象的向上转型对象不能调用子类重写的静态方法,只能调用父类的静态方法。
和对象向上转型有点相似的还有接口的多态:
接口 对象名 = new 类名();
对象只能调用接口中有的方法,而不能调用类中特有的方法。
类名 对象名 = new 类名();
对象可以调用所有的方法
public void test1()
{
Student s2=new UNStudent();
//当向上转型之后,父类引用变量可以访问子类中属于父类的属性和方法,但是不能访问子类独有的属性和方法。
s2.setname("郭超");
s2.study();
//s2.printname();这里printname为UNStudent中的独有方法,在父类中不存在
}
那么问题来了,明明可以直接通过子类创建对象,既能调用父类的方法,还可以调用子类中的的独有方法,为什么还要用到向上自动转型呢?
想在有一个Teacher类:
通过teach方法调用study
public class Teacher {
//自动转型后只需要一个teach方法就可以了
public void teach(Student s)
{
s.study();
}
//没有自动转型 则要重载多个方法
/*
* public void teach(MiddleStudent s)
* public void teach(UNStudent s)
* 如果子类多的话 还要重载很多
* public void teach(PrimaryStudent s)
* 等等.......
*/
}
现有多个子类,UNStudent,PrimaryStudent,MiddleStudent,他们都继承Student父类。这时如果要传递参数对象进而调用teach方法,通过自动转型,只需一个teach方法(形参列表数据类型为父类Student)即可满足以上三个子类要求,否则需要重载多个teach方法,如上代码注释所示。这样一来就节省了很多代码。
顾名思义,将父类型转成子类型。但并不是所有的对象都可以向下转型,只有当这个对象原本就是子类对象通过向上转型得到的时候才能够成功转型。
public void test3()
{
//向下转型(在向下转型之前,一定要发生了自动转型) 强制转型 强调强制
Student s4=new Student();
//UNStudent s=(UNStudent)s4;
//s.printname();
//这里编译能通过过 但是执行会出错
//正确如下
UNStudent s5=(UNStudent)s2;
s5.printname();
t.teach(s5);
}
abstract类中可以有abstract方法和非abstract方法;非abstract类中一定没有abstract方法。
abstract方法只允许声明,不允许实现(没有方法体),且必须是实例方法,所以不允许使用static修饰abstract方法。
abstract类只能声明对象,但不能使用new运算符创建对象,但该对象可以成为其子类对象的向上转型对象去调用重写方法。
一个非abstract类继承abstract类,它必须重写父类的abstract方法,即去掉abstract修饰,并给出方法体,所以也不允许使用final和abstract修饰同一个方法或类。
一个abstract类继承abstract类,它可以重写父类的abstract方法,或者继承父类的abstract方法。
抽象方法可以抽象出重要的行为标准,该行为用抽象方法来表示;抽象类的子类对象向上转型以调用子类重写的方法,即体现子类根据抽象类里的行为标准给出的具体行为。
使用案例:
计算柱体的体积。柱体的体积计算是通过底面积乘以高来实现的,但是不同底部图形的面积计算公式又是不同的,所以封装的方法体固然不同,因此,可以用一个抽象类来封装这个行为标准,即定义一个抽象方法abstract double getArea()。然后不同的的图形通过继承这个抽象类并重写这个抽象方法即可。
代码如下:
Geometry.java:
将所有计算面积的算法抽象为一个抽象方法:getArea()
public abstract class Geometry{
public abstract double getArea();
}
Circle.java:
重写Geometry类的抽象方法,计算圆的面积
public class Circle extends Geometry{
double r;
Circle(double r){
this.r=r;
}
public double getArea(){
return(3.14*r*r);
}
}
Rectangle.java:
重写Geometry类的抽象方法,计算矩形的面积
public class Rectangle extends Geometry{
double a,b;
Rectangle(double a,double b){
this.a=a;
this.b=b;
}
public double getArea(){
return a*b;
}
}
Pillar.java:
计算机柱体的体积:
public class Pillar(){
Geometry bottom; //bottom是抽象类Geometry声明的变量
double height;
Pillar(Geometry bottom,double height){
this.bottom=bottom;
this.height=height;
}
public double getVolume(){
if(bottom==null){
System.out.println("没有底,无法计算体积");
return -1;
}
return bottom.getArea()*height;
}
}
Application.java:
public class Application{
public static void main(String args[]){
Pillar pillar;
Geometry bottom=null;
pillar=new Pillar(bottom,100);
System.out.println("体积"+pillar.getVolume());//没有底,无法计算,体积-1
bottom=new Rectangle(12,20);
pillar=new Pillar(bottom,58);//长方体
System.out.println("体积"+pillar.getVolume());//体积15312.0
bottom=new Circle(10);
pillar=new Pillar(bottom,58);//圆柱体
System.out.println("体积"+pillar.getVolume());//体积18212.0
}
}