精华内容
下载资源
问答
  • Java中继承详解

    万次阅读 2019-10-20 00:52:45
    Java中继承详解继承的基本知识成员访问与继承构造函数与继承super关键字使用super调用超类构造函数使用super访问超类成员创建多级层次结构怎样调用构造函数超类引用与子类对象方法重写抽象类final关键字Object类 ...

    继承的基本知识

    继承是面向对象程序设计中的三个基本原则1之一。

    在Java语言中,被继承的类被称为超类,继承类被称为子类2

    比如狗类是动物类,牧羊犬类又是狗类。于是我们可以说狗类继承了动物类,而牧羊犬类就继承了狗类。于是狗类就是动物类的子类(或派生类),动物类就是狗类的父类(或基类)。

    实现继承将需要使用extends关键字。

    如下:

    // A simple class hierarchy
    class TwoDShape{
    	double length;
    	double width;
    		
    	void show(){
    		System.out.println("length:" + length + " width:" + width);
    	}
    }
    	
    class Triangle extends TwoDShape{
    	String style;
    	
    	double area(){
    		return length * width;
    	}
    	
    	void showStyle() {
    		System.out.println("style:" + style);
    	}
    }
    
    class Shapes {
    	public static void main(String[] args) {
    		Triangle triangle = new Triangle();
    		triangle.length = 2;
    		triangle.width = 1;
    		triangle.style = "Rectangle";
    		
    		triangle.showStyle();
    		triangle.show();
    	}
    }
    

    输出结果如下:

    style:Rectangle
    length:2.0 width:1.0

    需要注意的是:在Java中不存在多继承,与C++等支持多继承语言不同,所以在转换代码的过程中需要注意,而Java想要实现多继承可以使用implements(实现)接口,在后续博客中将会解释接口知识。

    继承的优点:如果有两个类相似,那么它们会有许多重复的代码,导致后果就是代码量大,后期的维护性不高。通过继承就可以解决这个问题,将两段代码中相同的部分提取出来组成一个父类,实现代码的复用。

    继承的特点:

    1. 子类拥有父类除 private 以外的所有属性和方法
    2. 子类可以拥有自己的属性和方法
    3. 子类可以重写实现父类的方法
    4. Java 中的继承是单继承,一个类只有一个父类

    成员访问与继承

    由类的成员变量具有访问限制可知,private可以防止未经过授权而进行修改,在继承中,也不会超越private的访问权限,如果将上述程序中的length,width声明为private,则会存在错误,Triangle类将会无法直接使用length和width。对此,程序员通常使用访问器方法来进行访问私有成员。

    // A simple class hierarchy
    class TwoDShape{
    	private double length;
    	private double width;
    		
    	void show(){
    		System.out.println("length:" + length + " width:" + width);
    	}
    
    	public double getLength() {
    		return length;
    	}
    
    	public void setLength(double length) {
    		this.length = length;
    	}
    
    	public double getWidth() {
    		return width;
    	}
    
    	public void setWidth(double width) {
    		this.width = width;
    	}
    }
    	
    class Triangle extends TwoDShape{
    	private String style;
    	
    	double area(){
    		return getLength() * getWidth();
    	}
    	
    	public String getStyle() {
    		return style;
    	}
    
    	public void setStyle(String style) {
    		this.style = style;
    	}
    
    	void showStyle() {
    		System.out.println("style:" + style);
    	}
    }
    
    class Shapes {
    	public static void main(String[] args) {
    		Triangle triangle = new Triangle();
    		triangle.setLength(2);
    		triangle.setWidth(1);
    		triangle.setStyle("Rectangle");
    		
    		triangle.showStyle();
    		triangle.show();
    	}
    }
    

    输出结果如下:

    style:Rectangle
    length:2.0 width:1.0

    构造函数与继承

    如果超类与子类都存在构造函数,那么是由哪个构造函数来构造子类对象呢?答案是超类构造函数构造超类部分,子类构造函数构造子类部分。

    // A simple class hierarchy
    class TwoDShape{
    	private double length;
    	private double width;
    		
    	void show(){
    		System.out.println("length:" + length + " width:" + width);
    	}
    
    	public double getLength() {
    		return length;
    	}
    
    	public void setLength(double length) {
    		this.length = length;
    	}
    
    	public double getWidth() {
    		return width;
    	}
    
    	public void setWidth(double width) {
    		this.width = width;
    	}
    }
    	
    class Triangle extends TwoDShape{
    	private String style;
    	
    	Triangle(String style, double length, double width){
    		setLength(length);
    		setWidth(width);
    		this.style = style; 
    	}
    	
    	double area(){
    		return getLength() * getWidth();
    	}
    	
    	public String getStyle() {
    		return style;
    	}
    
    	public void setStyle(String style) {
    		this.style = style;
    	}
    
    	void showStyle() {
    		System.out.println("style:" + style);
    	}
    }
    
    class Shapes {
    	public static void main(String[] args) {
    		Triangle triangle = new Triangle("Rectangle", 2, 1);
    		
    		triangle.showStyle();
    		triangle.show();
    	}
    }
    

    输出结果如下:

    style:Rectangle
    length:2.0 width:1.0

    super关键字

    super关键字在子类内部使用,代表父类对象。

    使用super调用超类构造函数

    // A simple class hierarchy
    class TwoDShape{
    	private double length;
    	private double width;
    		
    	TwoDShape(double length, double width) {
    		this.length = length;
    		this.width = width;
    	}
    	void show(){
    		System.out.println("length:" + length + " width:" + width);
    	}
    
    	public double getLength() {
    		return length;
    	}
    
    	public void setLength(double length) {
    		this.length = length;
    	}
    
    	public double getWidth() {
    		return width;
    	}
    
    	public void setWidth(double width) {
    		this.width = width;
    	}
    }
    	
    class Triangle extends TwoDShape{
    	private String style;
    	
    	Triangle(String style, double length, double width){
    		super(length, width);
    		this.style = style; 
    	}
    	
    	double area(){
    		return getLength() * getWidth();
    	}
    	
    	public String getStyle() {
    		return style;
    	}
    
    	public void setStyle(String style) {
    		this.style = style;
    	}
    
    	void showStyle() {
    		System.out.println("style:" + style);
    	}
    }
    
    class Shapes {
    	public static void main(String[] args) {
    		Triangle triangle = new Triangle("Rectangle", 2, 1);
    		
    		triangle.showStyle();
    		triangle.show();
    	}
    }
    

    输出结果如下:

    style:Rectangle
    length:2.0 width:1.0

    子类构造方法需要调用父类的构造方法时,在子类的构造方法体里最前面的位置:super()

    使用super访问超类成员

    super访问超类成员的用法与this相似,只不过super引用的是子类的超类。

    这种用法多用于被子类覆盖的同名成员和同名方法。

    创建多级层次结构

    如果存在三个类,分别为A,B,C,而它们的关系为A是B的超类,B是C的超类,这样就形成了一个简单的多级层次结构,此时,C将继承A和B所有的成员。

    // A simple class hierarchy
    class TwoDShape{
    	private double length;
    	private double width;
    		
    	TwoDShape(double length, double width) {
    		this.length = length;
    		this.width = width;
    	}
    	void show(){
    		System.out.println("length:" + length + " width:" + width);
    	}
    
    	public double getLength() {
    		return length;
    	}
    
    	public void setLength(double length) {
    		this.length = length;
    	}
    
    	public double getWidth() {
    		return width;
    	}
    
    	public void setWidth(double width) {
    		this.width = width;
    	}
    }
    	
    class Triangle extends TwoDShape{
    	private String style;
    	
    	Triangle(String style, double length, double width){
    		super(length, width);
    		this.style = style; 
    	}
    	
    	double area(){
    		return getLength() * getWidth();
    	}
    	
    	public String getStyle() {
    		return style;
    	}
    
    	public void setStyle(String style) {
    		this.style = style;
    	}
    
    	void showStyle() {
    		System.out.println("style:" + style);
    	}
    }
    
    class ColorTriangle extends Triangle{
    	private String color;
    	
    	ColorTriangle(String color, String style, double length, double width) {
    		super(style, length, width);
    		
    		this.color = color;
    	}
    
    	public String getColor() {
    		return color;
    	}
    
    	public void setColor(String color) {
    		this.color = color;
    	}
    	
    	void showColor() {
    		System.out.println("Color:" + color);
    	}
    }
    
    class Shapes {
    	public static void main(String[] args) {
    		ColorTriangle triangle = new ColorTriangle("Red", "Rectangle", 2, 1);
    		
    		triangle.showColor();
    		triangle.showStyle();
    		triangle.show();
    	}
    }
    

    输出结果如下:

    Color:Red
    style:Rectangle
    length:2.0 width:1.0

    怎样调用构造函数

    对于多级层级结构来说,就会存在有构造函数使用的先后顺序的问题。

    因为super()必须是构造函数中的第一条语句,所以根据递归的思想,将会先调用第一个超类的构造函数,依次下来,最后构造最后一个子类构造函数。

    超类引用与子类对象

    Java是强类型语言,在类型方面十分严格,因此在超类对象与子类对象中如果出现需要转换时,该是如何操作的呢?

    以上面的Shapes.java程序为例,ColorTriangle是Triangle类的子类。

    ColorTriangle t1 = new ColorTriangle("Red", "Rectangle", 2, 1);
    Triangle t2 = new Triangle("Rectangle", 2, 1);
    
    t1 = t2;			//ERROR
    t2 = t1;			//OK
    

    对于这一小段代码可以简单的这样理解,子类的成员中包含了超类的所有成员,因此超类引用可以引用子类对象。

    所以,在构造中也可以直接使用子类对象来进行super()构造。

    Triangle(Triangle t1){
    	super(t1);
    	style = t1.style;
    }
    

    方法重写

    在类层次结构中,当子类中的方法与超类中方法有相同的签名和返回类型,就成称子类中的方法重写(override)了超类的方法。此时在子类中调用被重写的方法时,将会使用子类定义的方法,而超类的方法被覆盖。

    class Animal {
        public void bark() {
            System.out.println("动物叫!");
        }
    }
    
    class Dog extends Animal {
        public void bark() {
            System.out.println("汪!汪!汪!");
        }
    }
    
    public class test{
        public static void main(String args[]){
            Animal a = new Animal(); 
            Dog d = new Dog();   
    
             Animal b = new Dog(); 
    
            a.bark();
            d.bark();
            b.bark();
        }
    }
    

    输出结果如下:

    动物叫!
    汪!汪!汪!
    汪!汪!汪!

    方法重写也使得Java能够实现动态方法分配,而动态方法分配这个机制是Java实现运行时多态性的机制。

    抽象类

    在定义类时,前面加上abstract关键字修饰的类叫抽象类。 抽象类中有抽象方法,这种方法是不完整的,仅有声明而没有方法体。抽象方法声明语法如下:

    abstract void f();  //f()方法是抽象方法
    

    使用抽象类的主要情况:

    1. 在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法。也就是说抽象类是约束子类必须要实现哪些方法,而并不关注方法如何去实现。
    2. 从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免了子类设计的随意性。

    所以由上可知,抽象类是限制规定子类必须实现某些方法,但不关注实现细节。

    抽象类的规则:

    1. 用 abstract 修饰符定义抽象类
    2. 用 abstract 修饰符定义抽象方法,只用声明,不需要实现
    3. 包含抽象方法的类就是抽象类
    4. 抽象类中可以包含普通的方法,也可以没有抽象方法
    5. 抽象类的对象不能直接创建,通常是定义引用变量指向子类对象。
    abstract class TwoDShape{
    	private double length;
    	private double width;
    		
    	TwoDShape(double length, double width) {
    		this.length = length;
    		this.width = width;
    	}
    	void show(){
    		System.out.println("length:" + length + " width:" + width);
    	}
    
    	public double getLength() {
    		return length;
    	}
    
    	public void setLength(double length) {
    		this.length = length;
    	}
    
    	public double getWidth() {
    		return width;
    	}
    
    	public void setWidth(double width) {
    		this.width = width;
    	}
    	
    	abstract double area();
    }
    	
    class Triangle extends TwoDShape{
    	private String style;
    	
    	Triangle(String style, double length, double width){
    		super(length, width);
    		this.style = style; 
    	}
    	
    	double area(){
    		return getLength() * getWidth();
    	}
    	
    	public String getStyle() {
    		return style;
    	}
    
    	public void setStyle(String style) {
    		this.style = style;
    	}
    
    	void showStyle() {
    		System.out.println("style:" + style);
    	}
    }
    
    class Shapes {
    	public static void main(String[] args) {
    		Triangle triangle = new Triangle("Rectangle", 2, 1);
    		triangle.showStyle();
    		triangle.show();
    	}
    }
    

    输出结果如下:

    style:Rectangle
    length:2.0 width:1.0

    final关键字

    final关键字可以修饰类、方法、属性和变量

    1. final 修饰类,则该类不允许被继承,为最终类
    2. final 修饰方法,则该方法不允许被覆盖(重写)
    3. final 修饰属性:则该类的属性不会进行隐式的初始化(类的初始化属性必须有值)或在构造方法中赋值(但只能选其一)
    4. final 修饰变量,则该变量的值只能赋一次值,即常量

    Object类

    Java中定义了一个特殊的类Object类,它是所有类的隐式超类,换而言之,其他的类都是Object类的子类。

    方法目的
    clone()创建并返回此对象的一个副本。
    toString()返回该对象的字符串表示。
    notify()唤醒在此对象监视器上等待的单个线程。
    notifyAll()唤醒在此对象监视器上等待的所有线程。
    registerNatives()私有方法
    getClass()返回此 Object 的运行类。
    hashCode()用于获取对象的哈希值。
    equals(Object obj)用于确认两个对象是否“相同”。
    wait(long timeout)在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或 者超过指定的时间量前,导致当前线程等待。
    wait(long timeout, int nanos)在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。
    wait()用于让当前线程失去操作权限,当前线程进入等待序列
    finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

    1. 面向对象程序设计中的三个基本原则:封装、继承和多态。 ↩︎

    2. 在不同的地方,超类也可称为父类或基类,子类可被称为派生类。
      一般超类和父类对应子类,基类对应派生类。 ↩︎

    展开全文
  • java中继承详解

    万次阅读 多人点赞 2020-12-19 08:43:00
    继承的描述在现实生活继承一般指的是子女继承父辈的财产。在程序继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如猫和狗都属于动物,程序便可以描述为猫...

    继承的描述

    在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物,同理,波斯猫和巴厘猫继承自猫,而沙皮狗和斑点狗继承自狗。这些动物之间会形成一个继承体系,具体如下图所示。

    1500704359335

    在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类,子类会自动拥有父类所有可继承的属性和方法。在程序中,如果想声明一个类继承另一个类,需要使用extends关键字。

    通过 extends 关键字让类与类之间产生继承关系。

    多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。多个类可以称为子类,单独这个类称为父类或者超类。

    注意事项:

    • 子类可以直接访问父类中的非私有的属性和行为。

    • 子类无法继承父类中私有的内容。

    • 父类怎么来的?共性不断向上抽取而来的。

    示例:

    class Person{
        String name;
        int age ;
    }
    class Student extends Person{
        void study(){
            System.out.println("student study..." + age);
        }
    }
    class Worker extends Person{
        void work(){
            System.out.println("worker work..." + age);
        }
    }
    class ExtendDemo{
        public static void main(String[] args){
            Student s = new Student();
            s. name = "zhangsan" ;
            s. age = 20;
            s.study();
            Worker w = new Worker();
            w. name = "lisi" ;
            w. age = 30;
            w.work();
        }
    }
    

    运行结果:

    1491308047866

    好处:

    • 继承的出现提高了代码的复用性。

    • 继承的出现让类与类之间产生了关系,提供了多态的前提。

    继承的特点

    在类的继承中,需要注意一些问题,具体如下:

    1.在Java中,类只支持单继承,不允许多重继承,也就是说一个类只能有一个直接父类,例如下面这种情况是不合法的。

    1500704447701

    2.多个类可以继承一个父类,例如下面这种情况是允许的。

    1500704467099

    3.在Java中,多层继承是可以的,即一个类的父类可以再去继承另外的父类,例如C类继承自B类,而B类又可以去继承A类,这时,C类也可称作A类的子类。例如下面这种情况是允许的。

    1500704487847

    4.在Java中,子类和父类是一种相对概念,也就是说一个类是某个类父类的同时,也可以是另一个类的子类。例如上面的示例中,B类是A类的子类,同时又是C类的父类。

    Java只支持单继承,不支持多继承。一个类只能有一个父类,不可以有多个父类。
    原因:因为多继承容易出现问题。两个父类中有相同的方法,子类到底要执行哪一个是不确定的。

    示例:

    class A{
        void show(){
            System.out.println("a" );
        }
    }
    class B{
        void show(){
            System.out.println("b" );
        }
    }
    class C extends B,A{
    }
    

    那么创建类C的对象,调用show方法就不知道调用类A中的show方法还是类B中的show方法。所以java不支持多继承,但将这种机制换了另一个安全的方式来体现,也就是多实现(后面会详细说明)。

    Java支持多层继承(继承体系):

    C继承B,B继承A,就会出现继承体系。多层继承出现的继承体系中,通常看父类中的功能,了解该体系的基本功能,建立子类对象,即可使用该体系功能。

    定义继承需要注意:不要仅为了获取其他类中某个功能而去继承,类与类之间要有所属( "is a")关系。

    super关键字&函数覆盖

    在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。需要注意的是,在子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型。

    当子类重写父类的方法后,子类对象将无法访问父类被重写的方法,为了解决这个问题,在Java中专门提供了一个super关键字用于访问父类的成员。例如访问父类的成员变量、成员方法和构造方法。

    在子父类中,成员的特点体现:

    成员变量

    • this和super的用法很相似

    • this代表本类对象的引用

    • super代表父类的内存空间的标识

    • 当本类的成员和局部变量同名用this区分

    • 当子父类中的成员变量同名用super区分父类

    示例:

    class Fu{
       private int num = 4;
    
       public int getNum(){
           return num ;
       }
    }
    
    class Zi extends Fu{
       private int num = 5;
    
       void show(){
           System.out.println(this.num + "..." + super.getNum());
       }
    }
    
    class ExtendDemo{
       public static void main(String[] args){
           Zi z = new Zi();
           z.show();
       }
    }
    

    运行结果

    1491308096802

    成员函数

    当子父类中出现成员函数一模一样的情况,会运行子类的函数。这种现象,称为覆盖操作,这是函数在子父类中的特性。

    在子类覆盖方法中,继续使用被覆盖的方法可以通过super.函数名获取。

    函数两个特性:

    1. 重载,同一个类中。

    2. 覆盖,子类中,覆盖也称为重写,覆写,override。

    示例:

    class Fu{
       public void show(){
           System.out.println("fu show run" );
       }
    }
    
    class Zi extends Fu{
       public void show(){
           System.out.println("zi show run" );
       }
    }
    
    class ExtendDemo{
       public static void main(String[] args){
           Zi z = new Zi();
           z.show();
       }
    }
    

    运行结果:

    1491308113418

    什么时候使用覆盖操作?

    当子类需要父类的功能,而功能主体子类有自己特有内容时,可以复写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容。

    示例:

    class Phone{
       void call(){}
       void show(){
           System.out.println("number" );
       }
    }
    
    class NewPhone extends Phone{
       void show(){
           System.out.println("name" );
           System.out.println("pic" );
           super.show();
       }
    }
    
    class ExtendDemo{
       public static void main(String[] args){
           NewPhone p = new NewPhone();
           p.show();
       }
    }
    

    运行结果:

    1491308129771

    注意事项:

    • 父类中的私有方法不可以被覆盖

    • 父类为static的方法无法覆盖

    • 覆盖时,子类方法权限一定要大于等于父类方法权限

    示例:

    class Fu{
        public void show(){
            System.out.println("fu show run" );
        }
    }
    
    class Zi extends Fu{
        private void show(){
            System.out.println("zi show run" );
        }
    }
    
    class ExtendDemo{
        public static void main(String[] args){
            Zi z = new Zi();
            z.show();
        }
    }
    

    运行结果:

    1491308142717

    构造函数

    子父类中构造函数的特点:在子类构造函数执行时,发现父类构造函数也运行了。
    原因:在子类的构造函数中,第一行有一个默认的隐式语句:super();。
    注意:如果使用super(4);语句调用父类的其他构造函数,那么默认的父类构造函数将不会再被调用。

    示例:

    class Fu{
        int num ;
        Fu(){
            num = 10;
            System.out.println("A fu run" );
        }
        Fu(int x){
            System.out.println("B fu run..." + x);
        }
    }
    
    class Zi extends Fu{
        Zi(){
            //super();//默认调用的就是父类中的空参数的构造函数
            System.out.println("C zi run " + num);
        }
        Zi(int x){
            super(4);
            System.out.println("D zi run " + x);
        }
    }
    
    class ExtendDemo{
        public static void main(String[] args){
            new Zi();
            System.out.println("-------------------" );
            new Zi(6);
        }
    }
    

    运行结果:

    1491308168245

    子类的实例化过程

    子类中所有的构造函数默认都会访问父类中空参数的构造函数。因为每一个构造函数的第一行都有一条默认的语句super();。

    为什么子类实例化的时候要访问父类中的构造函数呢?

    那是因为子类继承了父类,获取到了父类中内容(属性),所以在使用父类内容之前,要先看父类是如何对自己的内容进行初始化的。

    注意事项:

    • 当父类中没有空参数的构造函数时,子类的构造函数必须通过this或者super语句指定要访问的构造函数。

    • 子类构造函数中如果使用this调用了本类构造函数,那么默认的super();就没有了,因为super和this都只能定义在第一行,所以只能有一个。但是可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数。

    • super语句必须要定义在子类构造函数的第一行!因为父类的初始化动作要先完成。

    示例:

    class Fu{
        Fu(){
            super();
            //调用的是子类的show方法,此时其成员变量num还未进行显示初始化
            show();
            return;
        }
        void show(){
            System.out.println("fu show" );
        }
    }
    class Zi extends Fu{
        int num = 8;
        Zi(){
            super();
            //通过super初始化父类内容时,子类的成员变量并未显示初始化,等super()父类初始化完毕后,才进行子类的成员变量显示初始化
            return;
        }
        void show(){
            System.out.println("zi show..." + num);
        }
    }
    class ExtendDemo{
        public static void main(String[] args){
            Zi z = new Zi();
            z.show();
        }
    }
    

    运行结果:

    1491308191041

    总结:一个对象实例化过程,以Person p = new Person();为例

    1. JVM会读取指定的路径下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有直接的父类的情况下)

    2. 在内存中开辟空间,并分配地址

    3. 并在对象空间中,对对象的属性进行默认初始化

    4. 调用对应的构造函数进行初始化

    5. 在构造函数中,第一行会先到调用父类中构造函数进行初始化

    6. 父类初始化完毕后,再对子类的属性进行显示初始化

    7. 再进行子类构造函数的特定初始化

    8. 初始化完毕后,将地址值赋值给引用变量

    final关键字

    final关键字可用于修饰类、变量和方法,它有“无法改变”或者“最终”的含义,因此被final修饰的类、变量和方法将具有以下特性:

    • final可以修饰类,方法,变量

    • final修饰的类不可以被继承

    • final修饰的方法不可以被覆盖

    • final修饰的变量是一个常量,只能被赋值一次

    • 为什么要用final修饰变量,其实,在程序中如果一个数据是固定的。那么直接使用这个数据就可以了,但是这种阅读性差,所以应该给数据起个名称。而且这个变量名称的值不能变化,所以加上final固定

    • 写法规范:常量所有字母都大写,多个单词,中间用_连接

    示例1:

     //继承弊端:打破了封装性
     class Fu{
            void method(){
            }
     }
    
     class Zi extends Fu{
            public static final double PI = 3.14;
            void method(){
                 System.out.println(PI);
            }
     }
    
     class FinalDemo{
            public static void main(String[] args){
                 Zi zi = new Zi();
                 zi.method();
            }
     }
    

    运行结果:

    1491308214198

    示例2:

    class FinalDemo{
        public static void main(String[] args){
            final int x = 4;
            x = 5;
        }
    }
    

    运行结果:

    1491308228930

    内存结构

    静态绑定,当方法被 static private final三个关键字其中一个修饰,执行的静态绑定

    动态绑定,方法执行的动态绑定

    属性看变量的类型,Person.this.salary

    public class TestChunApp
    {
    
        public static void main(String[] args) {
            Person p = new Manager();
              p.sing();
            //System.out.println(p);    //Person.this.salary
        }
    }
    
    class Person{
        private int salary = 10000;
    
        public static void sing(){
            System.out.println("忘情水");
        }
        int getSalary(){
            return salary;
        }
        public void printSalary(){
            System.out.println(salary);
        }
    }
    
    class Manager extends Person{
        int salary = 30000;
    
        public static void sing(){
            System.out.println("中国人");
        }
        public void printSalary(){
            System.out.println(getSalary());
        }
    }
    

    - EOF -

    推荐阅读  点击标题可跳转

    java精品入门-0基础第一篇

    JDK、JRE、JVM的区别与联系

    变量与数据类型 java基础03

    运算符-小学生都能学会的java基础

    java循环经典案例-学会它入行java

    java基础之方法重载

    java数组精讲-多案例-够详细

    java中基本类型包装类

    引用、指针和句柄的区别
    面向对象-搞定她-搞定程序

    面向对象(二)-搞定她-搞定程序

    面向对象第一步封装

    都来到这了,我需要你的点赞支持 

    展开全文
  • Java继承详解

    2021-01-20 03:35:36
    什么是继承?  多个类存在相同属性和行为时,将这些内容抽取到单独一个类,那么多个类无需再定义这些属性和行为,只要继承那个类即可。...  1.Java只支持单继承,不支持多继承。  //一个类只能有
  • 主要介绍了java继承示例详解,需要的朋友可以参考下
  • java中继承 (extends) 详解

    万次阅读 多人点赞 2019-08-18 10:45:21
    继承的而关系,“子类就是一个父类“。也就是说,子类可以被当做父类来看待。例如父类是员工,子类是老师,那么我们可以说**”老师就是一个员工“**。 关系是“is - a”,例如“老师 is a 员工” 2. ...

    继承(extends)

    1. 父类与子类

    • 父类也叫基类、超类

    • 子类也叫派生类

    • 在继承的而关系中,“子类就是一个父类“。也就是说,子类可以被当做父类来看待。例如父类是员工,子类是老师,那么我们可以说**”老师就是一个员工“**。

    2. 继承的格式

    2.1 定义父类的格式:(一个普通的类定义)

    public class 父类名称{
        //.....
    }
    

    2.2 定义子类的格式

    public class 子类名称 extends 父类名称{
        //.....
    }
    

    2.3 代码演示(老师类继承员工类)

    • 2.3.1 Employee类
    public class Employee(){
        public void method(){
            System.out.println("方法执行")
        }
    }
    
    • 2.3.2 Teacher类,继承自Employee类
    public class Teacher extends Employee{
    	//我们先不在里面添加任何东西
    }
    
    
    • 2.3.3 在main中,创建一个Teacher类的对象
    public class Main {
    
        public static void main(String[] args) {
    	    Teacher one = new Teacher();
            //Teacher类继承了Empolyee类的方法
    	    one.method();
        }
    }
    /*输出结果:
        执行方法
     */
    

    2.3.4 小结论

    Teacher类继承了Employee类,也就继承了它的 public void method() 方法,达到了代码复用的效果,当父类有我们子类需要用的方法时,我们就不用再去重新打一次,直接可以拿来用。

    3. 继承中成员变量的访问特点

    3.1 成员变量之间的访问 (变量不重名的情况)

    • 3.1.1 先创立一个父类Fu
    public class Fu {
        public int numFu = 10;  //关键字为public,可以直接通过(对象.变量名)访问,方便说明问题
    }
    
    • 3.1.2 再创立一个子类Zi
    public class Zi extends Fu{
        public int numZi = 20;
    }
    
    • 3.1.3 在main中分别建立父类和子类的对象
    public class Demo02ExtendsField {
        public static void main(String[] args) {
            //创建父类对象
            Fu fu = new Fu();
            //父类只能找到自己的成员numFu,并没有找到子类的成员numZi
            System.out.println(fu.numFu);
    
            //创立一个子类对象
            Zi zi = new Zi();
            //子类对象既可以打印父类的成员numFu,也可以打印自己的成员numZi
            //还是那句"先人不知道后人的事情,而后人知道先人的事情"
            System.out.println(zi.numFu);
            System.out.println(zi.numZi);
        }
    }
    

    3.2 成员变量之间的访问 (变量重名的情况)

    有两种情况:

    1. 直接通过对象访问成员变量:

      等号左边是谁,就优先用谁,没有则向上找

    2. 间接通过成员方法访问成员变量

      该方法属于谁,就优先用谁,没有则向上找

    • 假设现在父类 Fu 和子类 Zi 都有一个变量名叫num

    • Fu 类

      public class Fu {
          public int num = 10;
      
          public void methodFu(){
              //这里打印的num,一定是本类的,不会再往下找子类的
              System.out.println(num);
          }
      }
      
    • Zi类

      public class Zi extends Fu{
          public int num = 20;
      
          public void methodZi(){
              //这里打印的num,如果本类有,就优先打印本类的,如果没有再往上找
              System.out.println(num);
          }
      }
      

    第一种情况:直接通过对象访问成员变量

    等号左边是谁,就优先用谁,没有则向上找。Fu fu = new Zi();等号的左边是父类

    public class Demo02ExtendsField {
        public static void main(String[] args) {
         // Zi zi = new Fu(); 不能通过父类来构造子类,先人(父类)根本不知道后人(子类)长什么样子
            Fu fu = new Zi(); //可以通过子类来构造父类,这时等号左边是父类
            System.out.println(fu.num);  //10,打印的是父类的num
        }
    }
    

    第二种情况:间接通过成员方法访问成员变量

    public class Demo02ExtendsField {
        public static void main(String[] args) {
    
            Fu fu = new Fu();
            Zi zi = new Zi();
    
            //打印的是父类的num,因为该类没有继承其它类,他自己肯定有一个num,才能写出这个方法
            fu.methodFu();  //父类的num 10,补充:没有fu.methodZi(), 先人不知道后人的方法
    
            //如果子类有一个num,那就优先打印本类的,没有的话再往父类那里找
            zi.methodZi();  //子类的num 20
    
            //重点!子类用的是父类的方法打印num,这就要看这个方法属于谁,是谁定义的这个方法
            //因为methodFu()这个方法是属于父类的,打印的当然就是父类的num
            zi.methodFu();  //父类的num 10
        }
    }
    

    4. 区分子类方法中的重名

    假如有好多个num,父类有一个,子类有两个,怎么才能正确地打印想要的那个num呢?

    • 4.1 父类

      public class Fu {
          public int num = 10;
      }
      
    • 4.2 子类

      public class Zi extends Fu {
          public int num = 20;
      
          public void methodZi(){
              int num = 30;
              System.out.println(num);      //30, 局部变量
              System.out.println(this.num); //20, 本类的成员变量
              System.out.println(super.num);//10, 父类的成员变量
          }
      }
      
    • 4.3 看看子类方法 methodZi() 能不能正确区分三个num

      public class Demo03Main {
          public static void main(String[] args) {
              Zi zi = new Zi();
              zi.methodZi();  //30, 20, 10
          }
      }
      
    • 4.4 总结:要想正确地打印想要的num,可以这样打

      • 局部变量,上面的那个num = 30,就可以直接写
      • 本类的成员变量,上面的num = 20, 用this.成员变量名
      • 父类的成员变量,上面的num = 10, 用super.成员变量名

    5. 继承中成员方法重名的问题

    假如子类和父类都有一个方法叫 method() , 那怎么知道用的是哪一个呢?

    • 5.1 父类

      public class Fu {
          public void method(){
              System.out.println("父类重名方法执行");
          }
      }
      
    • 5.2 子类

      public class Zi extends Fu {
          public void method(){
              System.out.println("子类重名方法执行");
          }
      }
      
    • 5.3 在main中调用 method() 方法

      public class Demo04Main {
          public static void main(String[] args) {
              Fu fu1 = new Fu();
              Fu fu2 = new Zi(); //通过子类来构造fu2
              Zi zi = new Zi();
      
              fu1.method(); //父类重名方法执行, 用的是父类方法
              fu2.method(); //子类重名方法执行,用的是子类方法
              zi.method();  //子类重名方法执行, 用的是子类方法
          }
      }
      
    • 结论 :

    1. 创建的对象是谁,用谁的类来构造对象的,就优先用谁,如果没有就向上找。比如Fu fu2 = new Zi();,fu2是用子类来构造的,那fu2.method()就是用的子类的方法
    2. 注意!无论是成员变量还是成员方法,如果没有都是向上找父类,绝对不会向下找子类的。

    6. 继承方法中的覆盖重写

    5. 继承中成员方法重名的问题,我们可以引出重写(Override)的概念

    重写:在继承关系中,方法的名称一样,参数列表也一样

    • 6.1 重写 (Override) 和 重载 (Overload) 的区别

      重写:方法的名称一样,参数列表【也一样】。也叫覆盖、覆写

      重载:方法的名称一样,参数列表【不一样】。

    • 6.2 覆盖重写的注意事项 (了解)

      • 6.2.1 必须保证父子类之间方法的名称相同,参数列表也相同,否则无法进行覆盖重写

      • 6.2.2 注解:@Override,这个注解写在方法的前面,用来检验是不是有效的覆盖重写,例如当方法名写错了,@Override底下就会出现红色的波浪线,提示你这不是有效的覆盖重写。

        public class Zi extends Fu {
            @Override
            public void method(){
                System.out.println("子类重名方法执行");
            }
        }
        

        这个注解可写可不写,但是强烈推荐把它写上去。

      • 6.2.3 子类方法的返回值必须【小于等于】父类方法的返回值范围。java.lang.Object是所有类的公共最高父类(祖宗类),每个类都默认继承了它,例如String类就是Object的子类。下面代码会报错,是因为返回值的范围问题

        • 6.2.3.1 父类

          public class Fu {
              public String method(){
                  System.out.println("父类重名方法执行");
              }
          }
          
        • 6.2.3.2 子类

          public class Zi extends Fu {
              @Override
              public Object method(){  //范围:Object > String 报错
                  System.out.println("子类重名方法执行");
              }
          }
          
      • 6.2.4 子类方法的权限必须【大于等于】父类方法的权限修饰符。权限的排名:public > protected > (default) > private。备注:(default)不是关键字default,而是什么都不写,留空,例如:

        public class Fu{
            int num; //num的访问权限是default
        }
        

        下面的代码会因为重写方法的权限问题而报错:

        • 6.2.4.1 父类

          public class Fu {
              public void method(){
                  System.out.println("父类重名方法执行");
              }
          }
          
        • 6.2.4.2 子类

          public class Zi extends Fu {
              @Override
              protected void method(){  //protected < public 报错
                  System.out.println("子类重名方法执行");
              }
          }
          
          
    • 6.3 覆盖重写实战演练

      假如旧手机的功能有打电话、发短信、来电显示(显示号码)

      新手机的功能有来电显示、发短信、来电显示(显示号码、显示头像、显示姓名)

      可见新旧手机的打电话和发短信功能是一样的,但是新手机的来电显示功能比旧手机多了显示头像、显示姓名。

      我们可以把旧手机当做父类,把新手机当做子类,新手机只需要重写旧手机的来电显示功能即可

      • 6.3.1 旧手机是父类,名为Oldphone类

        public class Oldphone {  
            //打电话功能
            public void call(){
                System.out.println("打电话");
            }
            //发短信功能
            public void send(){
                System.out.println("发短信");
            }
            //来电显示功能
            public void show(){
                System.out.println("显示号码");
            }
        }
        
      • 6.3.2 新手机是子类,名为Newphone类

        public class Newphone extends Oldphone{
        
            @Override
            public void show() {
                super.show();  //不要修改原来的代码,直接通过super调用它,后面再添加新的内容
                System.out.println("显示头像");
                System.out.println("显示姓名");
            }
        }
        
      • 6.3.3 在main中实验一下

        public class Demo05Main {
            public static void main(String[] args) {
                Newphone newphone = new Newphone();
                newphone.call();
                newphone.send();
                newphone.show();
            }
        }
        /*输出结果:
            打电话
            发短信
            显示号码
            显示头像
            显示姓名
         */
        

    7. 继承中构造方法的访问特点

    7.1 概述

    子类的构造方法启动时,一定会先跑去启动父类的构造方法,等父类的构造方法执行完后,再去执行子类(本类)的构造方法。

    7.2 代码说明一下7.1

    • 7.2.1 父类(只有无参构造方法)

      public class Fu {
          //父类的无参构造方法
          public Fu(){
              System.out.println("父类构造方法执行");
          }
      }
      
      
    • 7.2.2 子类

      public class Zi extends Fu {
          //子类的无参构造方法
          public Zi(){
              System.out.println("子类构造方法执行");
          }
      }
      
      
    • 7.2.3 在main中构造一个子类

      public class Demo06Main {
          public static void main(String[] args) {
              Zi zi = new Zi();
          }
      }
      
      /*输出结果:
          父类构造方法执行
          子类构造方法执行
       */
      
    • 7.2.4 小总结

      其实子类的构造方法中隐含了super()调用,如果子类的构造方法没有写super(),编译器会帮我们默认加上去。子类就变成

      public class Zi extends Fu {
          //子类的无参构造方法
          public Zi(){
              super();  //注意!这句必须写在第一行的位置,如果父类构造函数有参数,就是super(参数),有                   参数的调用,必须要自己写上去,不然会默认调用无参构造
              System.out.println("子类构造方法执行");
          }
      }
      

    8. super关键字的三种用法总结

    8.1 用法1

    在子类的成员方法中,访问父类的成员变量,比如:

    • 8.1.1 父类

      public class Fu {
          public int num = 10;
      }
      
    • 8.1.2 子类

      public class Zi extends Fu {
          public int num = 20;
      
          public void methodZi(){
             // System.out.println(num);  这样打印的一定是本类的num
              System.out.println(super.num); //打印的是父类的num
          }
      }
      

    8.2 用法2

    在子类的成员方法中,访问父类的成员方法,比如:

    • 8.2.1 父类

      public class Fu {
          public void methodFu(){
              System.out.println("父类的成员方法执行");
          }
      }
      
      
    • 8.2.2 子类

      public class Zi extends Fu{
          public void methodZi(){
              super.methodFu(); //访问父类的methodFu()方法
              System.out.println("子类的成员方法执行");
          }
      }
      
    • 在main中执行子类的方法

      public class Demo07Main {
          public static void main(String[] args) {
              Zi zi = new Zi();
              zi.methodZi();
          }
      }
      /*输出结果:
          父类的成员方法执行
          子类的成员方法执行
       */
      

    8.3 用法3

    在子类的构造方法中,访问父类的构造方法。就在 7. 继承中构造方法的访问特点 中,请自行查阅。

    9. this关键字的三种用法

    9.1 概述

    9.1.1 在本类的成员方法中,访问本类的成员变量

    9.1.2 在本类的成员方法中,访问本类的另一个成员方法

    9.1.3 在本类的构造方法中,访问本类的另一个构造方法

    9.2 代码说明一下

    public class Zi extends Fu {
        private int num = 10;
    
        public Zi(){
            this(123);  //9.1.3 在本类的无参构造中调用有参构造
        }
    
        public Zi(int num){
            this.num = num;
        }
    
        public void methodZi(){
            System.out.println(this.num); //9.1.1 在本类的成员方法中,访问本类的成员变量
        }
    
        public void methodA(){
            System.out.println("A方法");
        }
    
        public void methodB(){
            this.methodA();  //9.1.2 在本类的成员方法中,访问本类的另一个成员方法
            System.out.println("B方法"); 
    
    

    9.3 注意事项

    • 在构造方法中调用this,那这个this调用必须是该函数中的第一个语句,也是唯一的一个
    • super和this两种在构造调用中,不能同时使用。两个都要排在第一行,我哪知道该怎么排。

    10. java继承的三个特点

    10.1 java语言是单继承

    一个类的直接父类只能有唯一的一个。

    class A{}
    class B extends A{}  正确写法
    class C{}
    class D extends A,C{} 错误写法,不能同时继承A和C
    

    试想假如class A{}有一个 method() 方法,

    Class C{}也有一个 method() 方法,

    子类D同时继承类A和类C,那当我新建一个D类对象d后,

    对象d想要调用父类的method方法,那它应该用A的 method() 还是C的 method() 呢?这就乱套了!

    在这里插入图片描述

    10.2 java语言是可以多级继承的

    虽然,java语言是单继承的,一个类的直接父类只有一个,类D不能同时继承类A和类C

    但是,可以让类A继承类C之后,类D再继承类A,C就是爷爷,A就是爸爸,D就是儿子

    类D --> 类A --> 类C,这就叫多级继承。
    在这里插入图片描述

    10.3 java语言中,一个父类可以有多个子类

    这就好比二胎政策,一个爸爸可以有多个儿子

    在这里插入图片描述

    展开全文
  • java中类的继承详解

    万次阅读 多人点赞 2018-07-11 21:42:48
     Java继承具有单继承的特点, 每个子类只有一个直接父类.继承的特点Java的继承通过extends关键字实现. 实现继承的类被称为子类. 被继承的类被称为父类. 父类和子类的关系, 是一种一般和特殊的关系. 例如水果和...

    前言

    继承是面向对象的三大特征之一. 
    也是实现软件复用的重要手段. 
    Java继承具有单继承的特点, 每个子类只有一个直接父类.

    继承的特点

    Java的继承通过extends关键字实现. 
    实现继承的类被称为子类
    被继承的类被称为父类
    父类和子类的关系, 是一种一般和特殊的关系. 
    例如水果和苹果的关系, 苹果继承了水果, 苹果是水果的子类, 水果是苹果的父类.

    Java里子类继承父类的语法格式如下:

    修饰符 class SubClass extends SuperClass
    {
        //类定义部分
    }
    • 1
    • 2
    • 3
    • 4

    从上面的语法格式来看, 定义子类的语法非常简单, 只需要在原来的类定义上增加 extends SuperClass 即可. 
    即表明该类是 SuperClass 的子类.

    为什么国内把 extends 翻译为 继承 而不是 扩展呢? 
    除了历史原因, 还有一点. 
    子类继承了父类, 也将获得父类的全部成员变量和方法
    这与我们现实中子辈从父辈那里获得一笔财富的继承关系很像. 
    但是, Java的子类不能继承父类的构造器.

    下面写个程序示范子类继承父类.

    public class Fruit
    {
        public double weight;
        public void info()
        {
            System.out.println("我是一个水果! 重:" + weight + "g!");
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接下来定义该Fruit类的子类Apple

    public class Apple extends Fruit
    {
        public static void main(String[] args)
        {
            //创建Apple对象
            Apple a = new Apple();
            //Apple 对象本身没有 weight 成员变量
            //因为Apple 父类有 weight 成员变量, 所以也可以访问 Apple 对象的 weight 成员变量.
            a.weight = 56;
            //调用 Apple 对象的 info() 方法
            a.info();
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面的 Apple 类只是一个空类, 它只包含了一个 main() 方法. 
    但程序中创建了 Apple 对象之后, 可以访问该 Apple 对象的 weight 实例变量和info()方法, 这表明 Apple 对象也具有了 weight 实例变量和 info() 方法, 这就是继承的作用.

    Java类虽然只能有一个直接父类, 但它可以有无限多个间接父类
    例如:

    class Fruit extends Plant{...}
    class Apple extends Fruit{...}
    ......
    • 1
    • 2
    • 3

    上面类定义中, Fruit 是 Apple 类的父类. 
    Plant 类也是 Apple 类的父类. 
    区别是 Fruit 是 Apple 的直接父类, 而 Plant 则是 Apple 类的间接父类.

    如果定义一个 Java类时, 并未显式指定这个类的直接父类
    则这个类默认继承 java.lang.Object 类. 
    因此可以得出, java.lang.Object 类时所有类的父类. 
    要么是直接父类, 要么是其间接父类. 
    因此, 所有的Java对象都可以调用 java.lang.Object 类所定义的实例方法. 
    关于这个很牛逼的类我们以后会有介绍.

    重写父类的方法

    子类继承了父类, 所以说子类是一个特殊的父类
    大部分时候, 子类总是以父类为基础
    额外增加新的成员变量和方法. 
    但有一种情况例外: 子类需要重写父类的方法. 
    例如鸟类都包含了飞翔的方法, 但其中的鸵鸟并不会飞, 因为鸵鸟是鸟的子类, 因此它将从鸟类中获得飞翔的方法, 但这个飞翔的方法显然不适合鸵鸟, 所以鸵鸟这个子类需要重写鸟类(父类)的方法.

    下面先定义一个 Bird 类

    public class Bird
    {
        //Bird 类的 fly() 方法
        public void fly()
        {
            System.out.println("我在天空自由自在的飞翔...啦啦啦");
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    下面定义一个 Ostrich 类, 这个类继承了 Bird 类, 同时重写 Bird 类的 fly() 方法.

    public class Ostrich
    {
        //重写 Bird 类的  fly() 方法
        public void fly()
        {
            System.out.println("NND, 我可飞不了, 虽然我有双翅膀, 啦啦啦");
        }
        public static void main(String[] args)
        {
            //创建 Ostrich 对象
            Ostrich os = new Ostrich();
            //执行 Ostrich 对象的 fly() 方法, 将会输出 "...飞不了..."
            os.fly();
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    执行上面的程序, 将看到执行 os.fly() 时执行的不是 Bird 类的 fly() 方法. 
    而是执行 Ostrich 类的 fly() 方法.

    这种子类包含与父类同名方法的现象称为方法重写(Override). 也被称为方法覆盖. 
    可以说子类重写了父类的方法, 也可以说子类覆盖了父类的方法, 都行.

    方法的重写要遵循两同两小一大规则.

    • 两同: 方法名相同 / 形参列表相同
    • 两小: 子类方法返回值类型应比父类方法返回值类型小或相等. / 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等.
    • 一大: 子类方法的访问权限应比父类方法的访问权限大或相等.

    尤其需要指出, 覆盖方法和被覆盖方法要么都是类方法, 要么都是实例方法
    不能一个是类方法, 一个是实例方法, 例如下面代码就会报错.

    class BaseClass
    {
        public static void test(){...}
    }
    class SubClass extends BaseClass
    {
        public void test(){...}
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当子类覆盖了父类方法后, 子类的对象将无法访问父类中被覆盖的方法. 
    但可以在子类方法中调用父类中被覆盖的方法
    如果需要在子类方法中调用父类中被覆盖的方法, 则可以使用super(被覆盖的是实例方法) 或者 父类类名(被覆盖的是类方法) 来作为调用者, 调用父类中被覆盖的方法.

    如果父类方法具有 private 访问权限, 则该方法对其子类是隐藏的
    因此子类无法访问该方法, 也就无法重写该方法. 
    如果子类中定义了一个与父类 private 方法具有相同的方法名 / 相同的形参列表 / 相同的返回值类型的方法, 依然不是重写
    这只是在子类中重新定义了一个新的方法
    例如下面代码时完全正确的.

    class BaseClass
    {
        //test() 方法是 private 访问权限, 子类不可访问该方法
        private void test(){...}
    }
    class SubClass extends BaseClass
    {
        //此处并不是方法重写, 所以可以增加 static 关键字
        public static void test(){...}
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    涨姿势: 
    方法重载和方法重写在英文中分别是 overload 和 override 
    重载和重写 并不是同一种东西, 虽然二者都是发生在方法之间, 并要求方法名相同之外, 并没有太大相似之处. 
    因为重载主要发生在同一个类的多个同名方法之间. 
    而重写发生在子类和父类的同名方法之间. 
    当然, 父类方法和子类方法之间也有可能发生重载, 因为子类会获得父类方法. 
    如果子类定义了一个与父类方法有相同方法名, 但参数列表不同的方法, 就会形成父类方法和子类方法的重载.

    super 限定

    如果需要在子类方法中调用父类被覆盖的实例方法. 
    则可以使用 super 限定来调用父类被覆盖的实例方法
    为上面的 Ostrich 类添加一个方法, 在这个方法中调用 Bird 类中被覆盖的 fly 方法.

    public void callOverrideMethod()
    {
        //在子类方法中通过 super 显式调用父类被覆盖的实例方法
        super.fly();
    }
    • 1
    • 2
    • 3
    • 4
    • 5

    super 是 Java提供的一个关键字, super 用于限定该对象调用它从父类继承得到的实例变量或方法
    正如 this 不能出现在 static 修饰的方法中一样, super 也不能出现在 static 修饰的方法中. 
    static 修饰的方法是属于类的
    该方法的调用者可能是一个类, 而不是对象, 因而 super 限定也就失去了意义.

    如果在构造器中使用 super 
    则 super 用于限定该构造器初始化的是该对象从父类继承得到的实例变量, 而不是该类自己定义的实例变量.

    如果子类定义了和父类同名的实例变量
    则会发生子类实例变量隐藏父类实例变量的情形. 
    在正常情况下, 子类里定义的方法直接访问该实例变量默认会访问到子类中定义的实例变量. 
    无法访问到父类中被隐藏的实例变量. 
    在子类定义的实例方法中可以通过 super 来访问父类中被隐藏的实例变量
    如下代码所示:

    class BaseClass
    {
        public int a = 5;
    }
    public class SubClass extends BaseClass
    {
        public int a = 7;
        public void accessOwner()
        {
            System.out.println(a);
        }
        public void accessBase()
        {
            //通过使用 super 来限定访问从父类继承得到的 a 的实例变量
            System.out.println(super.a);
        }
        public static void main(String[] args)
        {
            SubClass sc = new SubClass();
            sc.accessOwner(); //输出 7
            sc.accessBase(); //输出 5
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    上面程序的 BaseClass 和 SubClass 中都定义了名为 a 的实例变量
    则 SubClass 的 a 实例变量将会隐藏BaseClass 的 a 实例变量. 
    当系统创建了 SubClass 对象时, 实际上会为 SubClass 对象分配两块内存.

    • 一块用于存储在 SubClass 类中定义的 a 实例变量.
    • 一块用于存储从 BaseClass 类继承得到的 a 实例变量.

    如果子类里没有包含和父类同名的成员变量. 
    那么在子类实例方法中访问该成员变量时, 则无需显式使用 super 或 父类名作为调用者. 
    如果在某个方法中访问名为 a 的成员变量, 但没有显式指定调用者, 则系统查找 a 的顺序为:

    • 查找该方法中是否有名为 a 的局部变量.
    • 查找当前类中是否包含名为 a 的成员变量.
    • 查找 a 的直接父类中是否包含名为 a 的成员变量, 依次上溯 a 的所有父类. 直到 java.lang.Object 类.
    • 如果最终不能找到名为 a 的成员变量, 则系统出现编译错误.

    如果被覆盖的是类变量, 在子类的方法中则可以通过父类名作为调用者来访问被覆盖的类变量.

    涨姿势: 
    当程序创建一个子类对象时. 
    系统不仅会为该类中定义的实例变量分配内存. 
    也会为它从父类继承得到的所有实例变量分配内存. 
    即使子类定义了与父类中同名的实例变量. 
    也就是说, 当系统创建一个 java 对象时. 
    如果该 java 类有两个父类(一个直接父类 A / 一个间接父类 B) 
    假设 A 类中定义了 2 个实例变量, B 类中定义了 3 个实例变量. 
    当前类中定义了 2 个实例变量, 那么这个 java 对象会保存 2 + 3 + 2 个实例变量.

    因为子类中定义与父类中同名的实例变量并不会完全覆盖父类中定义的实例变量, 它只是简单的隐藏了父类中实例变量, 所以会出现如下特殊情况:

    class Parent
    {
        public String tag = "孙悟空";
    }
    class Derived extends Parent
    {
        //定义一个私有的 tag 实例变量来隐藏父类的 tag 实例变量
        private String tag = "猪八戒";
    }
    public class HideTest
    {
        public static void main(String[] args)
        {
            Derived d = new Derived();
            //程序不可访问 d 的私有变量 tag , 所以下面语句将引起编译错误
            //System.out.println(d.tag);
            //将 d 变量显式的向上转型为 Parent 后, 即可访问 tag 实例变量
            //程序将输出 孙悟空
            System.out.println(((Parent)d).tag);
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上面程序父类 Parent 定义了一个 tag 实例变量. 
    其子类 Derived 定义了一个 private 的 tag 实例变量. 
    子类中定义的这个实例变量将会隐藏父类中定义的 tag 实例变量.

    程序的入口 main() 方法中先创建了一个 Derived 对象. 
    这个 Derived 对象将会保存两个 tag 实例变量. 
    一个是在 Parent 中定义的 tag 实例变量. 
    一个是在 Derived 类中定义的 tag 实例变量. 
    此时程序中包括了一个 d 变量. 
    它引用一个 Derived 对象, 内存中的存储示意图如下:

    这里写图片描述

    接着, 程序将 Derived 对象赋给 d 变量. 
    接着, 程序试图通过 d 来访问 tag 实例变量, 程序将提示访问权限不允许. 
    接着, 将 d 变量强制向上转型为 Parent 类型. 
    再通过它来访问 tag 实例变量是允许的.

    调用父类构造器

    子类不会获得父类的构造器. 
    但子类构造器里可以调用父类构造器的初始化代码. 
    类似于前面所介绍的一个构造器调用另一个重载的构造器.

    在一个构造器中调用另一个重载的构造器使用 this 调用来完成. 
    在子类构造器中调用父类构造器使用 super 调用来完成.

    看下面程序定义了 Base 类 和 Sub 类, 其中 Sub 类是 Base 类的子类. 
    程序在 Sub 类的构造器中使用 super 来调用 Base 类的构造器初始化代码.

    class Base
    {
        public double size;
        public String name;
        public Base(double size, String name)
        {
            this.size = size;
            this.name = name;
        }
    }
    public class Sub extends Base
    {
        public String color;
        public Sub(double size, String name, String color)
        {
            //通过 super 调用来调用父类构造器的初始化过程
            super(size, name);
            this.color = color;
        }
        public static void main(String[] args)
        {
            Sub s = new Sub(5.6, "皮卡丘", "黄色");
            //输出 Sub 对象的 三个实例变量
            System.out.println(s.size + "--" + s.name + "--" + s.color);
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    从上面程序中不难看出, 使用 super 调用和使用 this 调用也很像. 
    区别在于 super 调用的是其父类的构造器, 而 this 调用的是同一个类中重载的构造器. 
    因此, 使用 super 调用父类构造器也必需出现在子类构造器执行体的第一行
    所以 this 调用 和 super 调用不会同时出现.

    不管是否使用 super 调用来执行父类构造器的初始化代码. 
    子类构造器总会调用父类构造器一次. 
    子类构造器调用父类构造器分如下几种情况:

    • 子类构造器执行体的第一行使用 super 显式调用父类构造器.系统将根据 super 调用里传入的实参列表调用父类对应的构造器.
    • 子类构造器执行体的第一行代码使用 this 显式调用本类中重载的构造器,系统将根据 this 调用里传入的实参列表调用本类中的另一个构造器.执行本类中另一个构造器时即会调用父类构造器.
    • 子类构造器执行体中既没有 super 调用, 也没有 this 调用, 系统将会在执行子类构造器之前, 隐式调用父类无参数的构造器.

    不管上面哪种情况, 当调用子类构造器来初始化子类对象时. 
    父类构造器总会在子类构造器之前执行: 
    不仅如此, 执行父类构造器时, 系统会再次上溯执行其父类构造器……以此类推. 
    创建任何 Java对象, 最先执行的总是 java.lang.Object 类的构造器.

    对于如下图所示的继承树. 
    如果创建 ClassB 的对象, 系统将先执行 java.lang.Object 类的构造器. 
    再执行 ClassA 类的构造器. 
    然后才执行 ClassB 类的构造器. 
    这个执行过程还是最基本的情况. 
    如果 ClassB 显式调用 ClassA 的构造器, 而该构造器又调用了 ClassA 类中重载的构造器, 则会看到 ClassA 两个构造器先后执行的情形.

    这里写图片描述

    下面程序定义了三个类, 它们之间有严格的继承关系. 
    通过这种继承关系来让你看看构造器之间的调用关系.

    class Creature
    {
        public Creature()
        {
            System.out.println("Creature 无参数的构造器");
        }
    }
    
    class Animal extends Creature
    {
        public Animal(String name)
        {
            System.out.println("Animal 带一个参数的构造器," + "该动物的 name 为:" + name);
        }
        public Animal(String name, int age)
        {
            //使用 this 调用同一个重载的构造器
            this(name);
            System.out.println("Animal 带两个参数的构造器," + "其 age 为:" + age);
        }
    }
    
    public class Wolf extends Animal
    {
        public Wolf()
        {
            //显式调用父类有两个参数的构造器
            super("大灰狼", 3);
            System.out.println("Wolf 无参数的构造器");
        }
        public static void main(String[] args)
        {
            new Wolf();
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    上面程序的 main 方法只创建了一个 Wolf 对象. 
    但系统在底层完成了复杂的操作. 
    运行上面的程序, 看到如下运行结果:

    Creature 无参数的构造器
    Animal 带一个参数的构造器, 该动物的 name 为大灰狼
    Animal 带两个参数的构造器, 其 age 为 3
    Wolf 无参数的构造器
    • 1
    • 2
    • 3
    • 4

    从上面的运行过程来看. 
    创建任何对象总是从该类所在继承树最顶层的类的构造器开始执行. 
    然后依次向下执行. 
    最后才执行本类的构造器. 
    如果某个父类通过 this 调用了 同类中重载的构造器. 
    就会依次执行此父类的多个构造器.

    展开全文
  • Java集合继承体系详解

    2020-08-28 23:08:29
    主要为大家详细介绍了Java集合继承体系,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Java继承是面向对象的最显著的一个特征,然而下面这篇文章主要给大家介绍了关于为何说JAVA要慎重使用继承的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
  • Java 继承详解

    万次阅读 多人点赞 2017-02-02 21:14:45
    多个类存在相同属性和行为时,将这些内容抽取到单独一个类,那么多个类无需再定义这些属性和行为,只要继承那个类即可。 多个类可以称为子类,单独这个类称为父类、超类或者基类。 子类可以直接访问父类...
  • 主要介绍了Java基础教程之继承详解,继承是除组合(composition)之外,提高代码重复可用性(reusibility)的另一种重要方式,本文对继承做了详细讲解,需要的朋友可以参考下
  • Java继承机制详解

    2011-04-10 13:41:06
    讲述了Java继承机制,说明了Java继承的特点,文档是在网上下载后整理的
  • java集合超详解

    万次阅读 多人点赞 2018-08-03 21:28:15
    Object类的hashCode()的方法是所有子类都会继承这个方法,这个方法会用Hash算法算出一个Hash(哈希)码值返回,HashSet会用Hash码值去和数组长度取模, 模(这个模就是对象要存放在数组的位置)相同时才会判断...
  • 详解java封装继承多态

    2020-08-26 04:46:57
    主要介绍了java封装继承多态,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 主要介绍了Java继承中方法实例,非常的实用,这里推荐给大家,有需要的小伙伴可以参考下
  • 主要介绍了Java继承关系的初始化顺序,结合实例形式详细对比分析了Java继承关系的初始化与继承关系的初始化相关原理与操作技巧,需要的朋友可以参考下
  • 主要介绍了Java泛型继承原理与用法,结合实例形式分析了java泛型继承的相关原理与实现技巧,需要的朋友可以参考下
  • java中toString详解

    2017-12-13 09:03:51
    java中toString详解
  • JAVA中继承、封装和多态详解

    千次阅读 2015-05-22 10:35:24
     Java继承用extends关键字来实现,被继承的类成为父类,实现继承的类被称为子类。子类和父类的关系就比如现实生活儿子与父亲的关系。子类继承父类所有的“特点”,子类是父类的扩展,子类是一种特殊的父类,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 87,179
精华内容 34,871
关键字:

java中的继承详解

java 订阅