java 面向对象总结

目录

面向对象和面向过程的区别

面向对象三大特征

继承

重写

方法重写特点

方法重写和方法重载的区别

子类的方法各个修饰符

重载

方法重载应遵循的规则

子类与父类构造方法总结

super关键字

如何继承父类构造器

构造器能不能被重写

实例化过程

父类

子类

调用子类无参构造器

调用子类有参构造器,显式调用父类构造器

调用子类有参构造器,无显示调用父类构造器

调用子类有参构造器,显示调用父类其他有参构造器

如果父类没有无参构造器

父类实例化过程中有子类重写的方法

构造方法总结

子类调用父类成员、方法

继承的特殊说明

调用范围

访问原则

例题

子类重写父类的变量

静态方法和变量的继承和使用

概论

子类访问父类静态变量,并修改

子类重写父类静态变量

静态方法的继承与重写


面向对象和面向过程的区别

面向过程 :面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。

面向对象 :面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低

这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java 性能差的主要原因并不是因为它是面向对象语言,而是 Java 是半编译语言,最终的执行代码并不是可以直接被 CPU 执行的二进制机械码。

而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比 Java 好。

面向对象三大特征

封装

封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法 ,这里只是为了举例子)。

public class Student {
    private int id;//id属性私有化
    private String name;//name属性私有化

    //获取id的方法
    public int getId() {
        return id;
    }

    //设置id的方法
    public void setId(int id) {
        this.id = id;
    }

    //获取name的方法
    public String getName() {
        return name;
    }

    //设置name的方法
    public void setName(String name) {
        this.name = name;
    }
}

继承

不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。

关于继承如下 3 点请记住:

子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有

子类可以拥有自己属性和方法,即子类可以对父类进行扩展。

子类可以用自己的方式实现父类的方法。(以后介绍)。

多态

多态,顾名思义,表示一个对象具有多种的状态。具体表现为父类的引用指向子类的实例。

多态的特点:

对象类型和引用类型之间具有继承(类)/实现(接口)的关系;

引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;

多态不能调用“只在子类存在但在父类不存在”的方法;

如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

继承

简单的说,继承就是在一个现有类型的基础上,通过增加新的方法或者重定义已有方法(下面会讲到,这种方式叫重写)的方式,产生一个新的类型。继承是面向对象的三个基本特征--封装、继承、多态的其中之一,我们在使用JAVA时编写的每一个类都是在继承,因为在JAVA语言中,java.lang.Object类是所有类最根本的基类(或者叫父类、超类),如果我们新定义的一个类没有明确地指定继承自哪个基类,那么JAVA 就会默认为它是继承自Object类的。

我们可以把JAVA中的类分为以下三种: 

类:使用class定义且不含有抽象方法的类。

抽象类:使用abstract class定义的类,它可以含有,也可以不含有抽象方法。

接口:使用interface定义的类。

在这三种类型之间存在下面的继承规律: 

类可以继承(extends)类,可以继承(extends)抽象类,可以继承(implements)接口。

抽象类可以继承(extends)类,可以继承(extends)抽象类,可以继承(implements)接口。

接口只能继承(extends)接口。

请注意上面三条规律中每种继承情况下使用的不同的关键字extends和implements,它们是不可以随意替换的。大家知道,一个普通类继承一个接口后,必须实现这个接口中定义的所有方法,否则就只能被定义为抽象类。我在这里之所以没有对implements关键字使用“实现”这种说法是因为从概念上来说它也是表示一种继承关系,而且对于抽象类implements接口的情况下,它并不是一定要实现这个接口定义的任何方法,因此使用继承的说法更为合理一些。 

以上三条规律同时遵守下面这些约束: 

类和抽象类都只能最多继承一个类,或者最多继承一个抽象类,并且这两种情况是互斥的,也就是说它们要么继承一个类,要么继承一个抽象类。

类、抽象类和接口在继承接口时,不受数量的约束,理论上可以继承无限多个接口。当然,对于类来说,它必须实现它所继承的所有接口中定义的全部方法。

抽象类继承抽象类,或者实现接口时,可以部分、全部或者完全不实现父类抽象类的抽象(abstract)方法,或者父类接口中定义的接口。

类继承抽象类,或者实现接口时,必须全部实现父类抽象类的全部抽象(abstract)方法,或者父类接口中定义的全部接口。

继承给我们的编程带来的好处就是对原有类的复用(重用)。就像模块的复用一样,类的复用可以提高我们的开发效率,实际上,模块的复用是大量类的复用叠加后的效果。

重写

当子类需要父类的功能,而子类有新的内容,可以重写父类中的方法。在实际开发过程中,随着代码量的逐渐增加,维护成了一个很大的问题,如果需要对某个方法进行修改,其本身代码以及其子类代码都会受到影响,而重写则很好的解决了这个问题。方法重写又称为方法覆盖、方法复写。

方法签名 = 方法名 + 参数(顺序+类型+个数)

当父类和子类的方法签名一致时,我们认为子类重写了父类的方法

方法重写特点

在子类和父类中,出现了方法声明相同的情况

子类的方法声明要和父类相同

子类要重写的方法,方法的权限修饰符不能比父类更低(public 、protected  、default  、private 权限依次增加)

父类私有的方法,子类不能进行方法重写

方法重写和方法重载的区别

方法重写:子类和父类中方法相同,两个类之间的关系,函数的返回值类型、函数名、参数列表都一样,当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法

重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。

返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。

如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。

构造方法无法被重写

方法重载:指在同一个类中,多个方法名相同,他们的参数列表不同(个数不同,数据类型不同),同样的一个方法能够根据输入数据的不同,做出不同的处理

区别点 重载方法 重写方法
发生范围 同一个类 子类
参数列表 必须修改 一定不能修改
返回类型 可修改 子类方法返回值类型应比父类方法返回值类型更小或相等
异常 可修改 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
访问修饰符 可修改 一定不能做更严格的限制(可以降低限制)
发生阶段 编译期 运行期

 方法的重写要遵循“两同两小一大”

“两同”即方法名相同、形参列表相同;

“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;

“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。

关于 重写的返回值类型 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。

public class Hero {
    public String name() {
        return "超级英雄";
    }
}
public class SuperMan extends Hero{
    @Override
    public String name() {
        return "超人";
    }
    public Hero hero() {
        return new Hero();
    }
}

public class SuperSuperMan extends SuperMan {
    public String name() {
        return "超级超级英雄";
    }

    @Override
    public SuperMan hero() {
        return new SuperMan();
    }
}

子类的方法各个修饰符

修饰符 子类较父类 备注
访问修饰符 大于等于父类 比如:子类是private,而父类是public,那么就访问权限乱套了,那么当子类强制成父类后,这个方法是否可以调用呢?
抛出异常的范围 小于等于父类 同上,如果调用父类被覆盖的方法抛出异常,发现子类的异常比父类的异常大,就没有try-catch住,那就gameover了,注:try-catch时父类异常可以接收子类异常
static 静态方法可以被继承,但无法被重写,且如果父类含有静态方法,那么子类的成员方法的方法签名不能一致
final 如父类方法含有final,则子类无法覆写。
synchronized synchronized 方法被覆写时,无法继承sychronized

重载

当一个类具有两个或多个具有相同名称但参数不同的方法时,在基于传递的参数进行调用时,将调用相应的方法(或相应的方法主体将与调用行动态绑定)。这种机制称为方法重载

方法重载应遵循的规则

两种方法应在同一类中。

方法的名称应相同,但它们应具有不同的数量或参数类型。

如果名称不同,则它们将成为不同的方法,并且如果它们具有相同的名称和参数,则将引发编译时错误,提示“方法已定义”。

但是他们可以有不同的返回类型,修饰符,抛出不同的异常

子类与父类构造方法总结

super关键字

super,只能指代父类对象

super(x)或者super(),指代父类的构造方法,只能放在首行

如何继承父类构造器

某种意义上可以说继承。

子类的构造器必须在第一行调用父类的构造(super),如果无默认的无参构造器,则必须显示调用super指定继承的构造器。

注意:

子类必须通过super关键字调用父类有参数的构造函数

使用super调用父类构造器的语句必须是子类构造器的第一条语句

如果子类构造器没有显式地调用父类的构造器,则将自动调用父类的默认(没有参数)的构造器。如果父类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用父类的构造器,则java编译器将报告错误

情况1:父类有  无参构造方法时

子类可以无参构造方法也可以没有无参构造方法,若无无参调用方法则需要显示的调用super(参数);

情况2:父类中无   无参构造方法时

子类一般不允许有无參构造方法,因为无參构造方法,构造时没有参数传入,而父类中没有无參构造方法,子类中只能显示调用super(参数),但又不存在参数,因此不允许有无參构造方法,除非默认指定的参数

构造器能不能被重写

构造器就是构造方法,能够被重载(同类中不同参数列表的构造器),不能够被重写(子类使用super方法可以调用)。

构造器根被没有被继承,只是能够调用,所以不能重写。

实例化过程

实例初始化就是执行()方法

()方法可能重载有多个,有几个构造器就有几个方法

()方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成

非静态实例变量显示赋值代码和非静态代码块代码从上到下顺序执行,而对应构造器的代码最后执行

每次创建实例对象,调用对应构造器,执行的就是对应的方法

方法的首行是super()super(实参列表),即对应父类的方法

父类

package test.t05new;


public class A {
	public String name;
	
	{
		System.out.println("父类的 代码块begin");
	}
	
	static{
		System.out.println("父类的 静态代码块begin");
	}
	
	public A(){
		System.out.println("父类的无参数构造方法");
	}
	public A(String str){
		this.name=str;
		System.out.println("父类的有参数构造方法str");
	}
	
	public A(int str){
		this.name=String.valueOf(str);
		System.out.println("父类的有参数构造方法int");
	}
	
	public A(long str){
		this.name=String.valueOf(str);
		System.out.println("父类的有参数构造方法long");
	}
	
	{
		System.out.println("父类的 代码块end");
	}
	static{
		System.out.println("父类的 静态代码块end");
	}
}

子类

package test.t05new;


public class S extends A {
	
	{
		System.out.println("子类的 代码块begin");
	}
	static{
		System.out.println("子类的 静态代码块begin");
	}
	
	
	public S(){
		System.out.println("子类的无参数构造方法");
	}
			
	public S(String str){
		super(str);//想调用父类的有参数构造方法,必须写在第一行
		System.out.println("子类的有参数构造方法str");
	}
	
	public S(int str){
		//super(str);
		System.out.println("子类的有参数构造方法int");
	}
	
	public S(long str){
		super((int)str);
		System.out.println("子类的有参数构造方法long");
	}
	
	{
		System.out.println("子类的 代码块end");
	}
	static{
		System.out.println("子类的 静态代码块begin");
	}
	
	public static void main(String[] args) {
		S s1 = new S();
		System.out.println(s1.name);
		S s2 = new S("dddd");	
		System.out.println(s2.name);
		S s3 = new S(123);
		System.out.println(s3.name);
		S s4 = new S(123L);
		System.out.println(s4.name);
	}
}

调用子类无参构造器

S s1 = new S();
System.out.println(s1.name);

父类的 静态代码块begin
父类的 静态代码块end
子类的 静态代码块begin
子类的 静态代码块begin


父类的 代码块begin
父类的 代码块end
父类的无参数构造方法
子类的 代码块begin
子类的 代码块end
子类的无参数构造方法
null

可以看到,先初始化类,再实例化类。

初始化类,先初始父类,再初始子类(调用静态代码块和静态变量,按照代码中声明的顺序)

实例化类,先实例化父类,再实例化子类。

实例化父类,如果没有显式调用父类构造器,默认调用父类的无参构造器(此处没有显式调用,但是默认隐式调用)

调用构造器前,先执行实例变量和代码块,按照代码中声明的顺序,再执行构造器!

实例化子类,就是先执行实例变量和代码块,按照代码中声明的顺序,再执行构造器

调用子类有参构造器,显式调用父类构造器

S s2 = new S("dddd");   

System.out.println(s2.name);

父类的 代码块begin
父类的 代码块end
父类的有参数构造方法str
子类的 代码块begin
子类的 代码块end
子类的有参数构造方法str
dddd

可以看到显式调用父类构造器时,确实调用了父类的有参数构造方法

注意:显式调用父类构造器时,必须把super.xxx写在子类构造器的第一行

调用子类有参构造器,无显示调用父类构造器

S s3 = new S(123);
System.out.println(s3.name);

父类的 代码块begin
父类的 代码块end
父类的无参数构造方法
子类的 代码块begin
子类的 代码块end
子类的有参数构造方法int
null

可以看到如果没有显式调用父类构造器,默认调用父类的无参构造器,即使子类调用有参构造器!!!

调用子类有参构造器,显示调用父类其他有参构造器

S s4 = new S(123L);
System.out.println(s4.name);

内部方法

    public S(long str){
        super((int)str);
        System.out.println("子类的有参数构造方法long");
    }

父类的 代码块begin
父类的 代码块end
父类的有参数构造方法int
子类的 代码块begin
子类的 代码块end
子类的有参数构造方法long
123

可以看到如果显式调用父类构造器,就会调用该构造器,即使子类构造器的参数是其他类型!!!

如果父类没有无参构造器

    /*public A(){
        System.out.println("父类的无参数构造方法");
    }*/

将父类无参构造方法注释了,只剩下public A(String str)

调用S s1 = new S();

    public S(){
        System.out.println("子类的无参数构造方法");
    }

会报错

Exception in thread "main" java.lang.Error: Unresolved compilation problems:
    Implicit super constructor A() is undefined. Must explicitly invoke another constructor
    Implicit super constructor A() is undefined. Must explicitly invoke another constructor

    at test.t05new.S.(S.java:14)
    at test.t05new.S.main(S.java:41)
父类的 静态代码块begin
父类的 静态代码块end
子类的 静态代码块begin
子类的 静态代码块begin

表明子类构造函数内必须调用父类的构造函数,可以是显示的,可以是隐式的(调用无参父类),但如果父类没有无参构造函数,必须显示调用父类构造函数

父类实例化过程中有子类重写的方法

package construct;

/*
 * 父类的初始化:
 * (1)j = method();
 * (2)父类的静态代码块
 * 
 *  父类的实例化方法:
 * (1)super()(最前)
 * (2)i = test();
 * (3)父类的非静态代码块
 * (4)父类的无参构造(最后)
 * 
 * 非静态方法前面其实有一个默认的对象this
 * this在构造器(或)它表示的是正在创建的对象,因为这里是在创建Son对象,所以
 * test()执行的是子类重写的代码(面向对象多态)
 * 
 * 这里i=test()执行的是子类重写的test()方法
 */
public class Father{
	private int i = test();
	private static int j = method();
	
	static{
		System.out.print("(1)");
	}
	Father(){
		System.out.print("(2)");
	}
	{
		System.out.print("(3)");
	}
	
	
	public int test(){
		System.out.print("(4)");
		return 1;
	}
	public static int method(){
		System.out.print("(5)");
		return 1;
	}
}
package construct;

/*
 * 子类的初始化:
 * (1)j = method();
 * (2)子类的静态代码块
 * 
 * 先初始化父类:(5)(1)
 * 初始化子类:(10)(6)
 * 
 * 子类的实例化方法:
 * (1)super()(最前)      (9)(3)(2)
 * (2)i = test();    (9)
 * (3)子类的非静态代码块    (8)
 * (4)子类的无参构造(最后) (7)
 * 
 * 因为创建了两个Son对象,因此实例化方法执行两次
 * 
 * (9)(3)(2)(9)(8)(7)
 */
public class Son extends Father{
	private int i = test();
	private static int j = method();
	static{
		System.out.print("(6)");
	}
	Son(){
		//		super();//写或不写都在,在子类构造器中一定会调用父类的构造器
		System.out.print("(7)");
	}
	{
		System.out.print("(8)");
	}
	public int test(){
		System.out.print("(9)");
		return 1;
	}
	public static int method(){
		System.out.print("(10)");
		return 1;
	}
	public static void main(String[] args) {
		Son s1 = new Son();
		System.out.println();
		Son s2 = new Son();
	}
}

(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)

注意:这里 实例化过程是9开头,是子类重写的test方法! 

哪些方法不可以被重写

final方法

静态方法

private等子类中不可见方法

对象的多态性

子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码

非静态方法默认的调用对象是this

this对象在构造器或者说<init>方法中就是正在创建的对象

父类的实例化过程里,如果执行了子类重写的方法,那么父类里执行的是子类的方法

构造方法总结

创建一个子类,基本流程是

初始化父类(执行父类静态代码块)> 初始化子类(执行子类静态代码块)>实例化父类(执行父类代码块>隐式,调用无参父类构造器/显式,调用对应父类构造器,注意子类重写方法)>实例化子类(执行子类代码块>执行剩余子类构造函数)

子类调用父类成员、方法

继承的特殊说明

这里说道继承是拥有父类的全部属性和方法,但我们发现父类的私有属性,子类是不可以使用的。这违背了继承的定义吗,拥有父类的全部属性与行为。答案是没有。用数学去描述子类与父类的话,应该是子类大于等于父类。

在一个子类被创建时,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者结合形成子类的对象。所以父类的全部属性和行为子类会拥有,但是对于父类对象中的私有属性和方法,子类是无法访问到的,只是拥有,但也仅仅是拥有(I like you, But just like you!)。这也是关键字private,protected,public的意义所在。

子类可以调用父类的其他方法,访问父类的私有属性。

子类继承是继承父类的所有东西,除了构造函数

调用范围

1、能够访问标为public protected的成员变量和方法;
2、如果子类与父类在同一包内,还能访问默认(无修饰符)的成员变量与方法。
3、不能访问标为private的成员。

父类修饰符 子类是否会继承 备注
private 如果父子类含有相同的A方法,但父类是private修饰,那么他们互不影响,不属于被覆盖的情况
default 未知 在同一个包下可以继承父类,在不同包下无法继承
protected
public

访问原则

/*
    看程序写结果:
        A:访问成员变量的原则:就近原则。
        B:this和super的问题:
            this     访问本类的成员
            super    访问父类的成员(可以理解为的)
        C:子类的所有构造方法执行前默认先执行父类的无参构造方法。
        D:一个类的初始化过程:
            成员变量进行初始化过程如下:
                默认初始化
                显示初始化
                构造方法初始化

    输出的结果是:
            fu
            zi
            30
            20
            10
*/
class Fu {
    public int num = 10;
    public Fu() {
        System.out.println("fu");
    }
}

class Zi extends Fu {
    public int num = 20;
    public Zi() {
        System.out.println("zi");
    }
    public void show() {
        int num = 30;
        System.out.println(num); //30
        System.out.println(this.num); //20
        System.out.println(super.num); //10
    }
}
class ExtendsTest {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show();
    }
}

可以看到在能够访问的前提下,访问同一个名字的变量的原则是就近原则:

先寻找方法中的局部变量num,没找到再找本类中的num,还没找到就找父类中的num。

当就近原则访问不到想要的变量时,例如父类子类都有num,可以使用this.num或者super.num

例题

public class Test {

//    则下面测试类中的输出结果分别是什么?
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();

        B b = new B();
        C c = new C();
        D d = new D();
        System.out.println(a1.show(b));
        System.out.println(a1.show(c));
        System.out.println(a1.show(d));
        System.out.println("============");
        System.out.println(a2.show(b));
        System.out.println(a2.show(c));
        System.out.println(a2.show(d));
        System.out.println("============");
        System.out.println(b.show(b));
        System.out.println(b.show(c));
        System.out.println(b.show(d));
        
    }

}


class A {
    public String show(D d) {
        return "A and D";
    }

    public String show(A a) {
        return "A and A";
    }
}

class B extends A {
    public String show(B b) {
        return "B and B";
    }

    @Override
    public String show(A a) {
        return "B and A";
    }
}

class C extends B {}
class D extends B {}
A and A
A and A
A and D
============ //优先调用最短的继承路径
B and A (有挑战哦)
B and A (有挑战哦)
A and D
============ //对外类型会影响调用哦
B and B
B and B
A and D

D继承C,C继承B,B继承A

可以看到A类型的A实例,调用的参数为B,C,D,对应的参数为A,A,D

这个是重载,匹配最符合参数类型的方法。

A类型的B实例,调用的参数为B,C,D,这个较难!

首先A类型的方法,只有参数为A和D,A类型的B实例,实际上能调用的也只有参数为A和D的方法!

同时B重写了参数A,相当于A类型的B实例,能调用的是B的参数A和A的参数D!

所以A类型的B实例,调用的参数为B,C,实际对应的参数是A,方法是B里重写的参数A,调用参数D,方法是A的参数D

B类型的B实例,总共有3个可用方法,A的参数D,B的重新参数A,B的新增的参数B

调用参数为B,C,D,分别为B的新增的参数B,B的新增的参数B,A的参数D

可以看到,如果实例为子类型,类型为父类,有的方法是父类的方法和子类重写的方法,子类新增的方法不算!

实例为子类型,类型为子类,有父类的方法,子类重写的方法,子类新增的方法,三者都有,优先子类重写的方法(对于父类的相同标签的方法)

子类重写父类的变量

package test;


public class Father{
	int a = 10;
	public int getA(){
		return a;
	}

	Father(){
		System.out.print(a);
	}
}
package test;


public class Son extends Father{

	int a = 20;
	public int getA(){
		return a;
	}

	Son(){
		System.out.print(a);
	}

	public static void main(String[] args) {
		Father father = new Father();
		System.out.println();
		Father son = new Son();
		System.out.println();
		Son son2 = new Son();
		System.out.println();
		System.out.println(father.a);
		System.out.println(father.getA());
		System.out.println(son.a);
		System.out.println(son.getA());
		System.out.println(son2.a);
		System.out.println(son2.getA());
	}
}
10
1020
1020
10
10
10
20
20
20

关注点:

new Son,不管是调用类型是父类型还是子类型,getA方法都是重写后的,也就是返回子类的20

但是父类的Son,的a,是父类的10,只有子类的Son,的a,是子类的20

构造函数里面打印的a,因为被重写了,都是自个的

静态方法和变量的继承和使用

概论

结论:java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏.

原因:

1). 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。

如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。

2). 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。

3). 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态

子类访问父类静态变量,并修改

package test.t05new;

/**
 * Created by wangbin10 on 2018/7/9.
 * 我们知道静态变量属于类级别变量,对应每个类只有一份,类的所有实例共有一份,而成员变量则分别属于每个对象;
 * 那么在类的继承中,父类的静态变量和成员变量,子类都会继承,继承的原则是什么?通过下面的测试,我们的结论是:
 * 1.不仅类的对象共享类的静态变量,父类的所有子类对象也都共享父类的静态变量,有且仅有一份,对其进行修改,会影响到所有类及对象;
 * 2.子类复制父类的成员变量,每个子类持有一份,互不影响。
 */
public class Parent {
    public static String STATIC_VAR = "parent";
    public String MEMBER_VAR = "member";

    public static void main(String[] args) {
        Child1 c1 = new Child1();
        Child2 c2 = new Child2();
        System.out.println(c1.STATIC_VAR);
        System.out.println(Child1.STATIC_VAR);
        System.out.println(Parent.STATIC_VAR);
        System.out.println("------------");
        c1.setVar("child1");
        System.out.println(c1.getVar());
        System.out.println(c2.getVar());//输出:child1,说明子类共享父类的静态变量
        System.out.println(Child1.STATIC_VAR);
        System.out.println(c1.STATIC_VAR);
        System.out.println("------------");
        c1.setMember("mem change");
        System.out.println(c1.getMember());
        System.out.println(c2.getMember());//输出:member,说明子类分别复制并持有一份父类的成员变量,互不影响
    }
}

class Child1 extends Parent {
    void setVar(String var) {
        STATIC_VAR = var;
    }

    String getVar() {
        return STATIC_VAR;
    }

    void setMember(String mem) {
        MEMBER_VAR = mem;
    }

    String getMember() {
        return MEMBER_VAR;
    }
}

class Child2 extends Parent {
    String getVar() {
        return STATIC_VAR;
    }

    String getMember() {
        return MEMBER_VAR;
    }
}

parent
parent
------------
child1
child1
child1
------------
mem change
member

可以看到 当子类不重写父类静态变量时,    子类实例.父类的静态变量=子类.class.父类的静态变量=父类.class.父类的静态变量

而且它们三个引用的变量是同一个!!!

父类的所有子类对象也都共享父类的静态变量,有且仅有一份,对其进行修改,会影响到所有类及对象
子类复制父类的成员变量,每个子类持有一份,互不影响。

子类重写父类静态变量

package test.t05new;



public class Parent {
    public static String STATIC_VAR = "parent_static";
    
    static void setVar(String var) {
        STATIC_VAR = var;
    }
    
    static void setVar2(String var) {
        STATIC_VAR = var;
    }

    public static void main(String[] args) {
        Child1 c1 = new Child1();
        System.out.println(c1.STATIC_VAR);
        System.out.println(Parent.STATIC_VAR);
        System.out.println("------------");
        
        
        c1.setVar("child");
        c1.setVar2("parent");
        System.out.println(c1.getVar());
        System.out.println(Parent.STATIC_VAR);

    }
}

class Child1 extends Parent {
	public static String STATIC_VAR = "son_static";
    
    static void setVar(String var) {
        STATIC_VAR = var;
        //super.STATIC_VAR=var;
    }

    static String getVar() {
        return STATIC_VAR;
    }


}



son_static
parent_static
------------
child
parent

可以看到,子类如果有父类同名的静态变量, 子类实例.父类的静态变量=子类.class.父类的静态变量 此时不是  父类.class.父类的静态变量!!!而是子类自己的静态变量,如果想访问必须用父类.class.父类的静态变量

可以看到此处仍然是 就近访问原则的体现。

子类的静态变量覆盖了父类的静态变量,可以认为父类的静态变量被隐藏了

静态方法的继承与重写

//可以继承,例子:
class A{
 public static void a(){
  System.out.println("a");
 }
}
class B extends A{}
 
public class Test9 {
   public static void main(String[] args) {
         B.a();//输出a,证明静态方法可以被继承
   }
}

可以看到子类型.父类静态方法=父类静态方法

//不能被覆写,例子:
class A{
 public static void a(){
  System.out.println("a");
 }
}
class B extends A{
 public static void a(){
  System.out.println("b");
 }
}
 
public class Test9 {
   public static void main(String[] args) {
    A a = new B();
    a.a();//输出结果是a
   }
}

可以看到 父类型的子实例.子类重写静态方法 = 父类静态方法  而不是子类重写静态方法!!!

java不推荐用对象调用static方法,这会使人混淆,请大家注意。

可以重写静态方法,但重写后的静态方法不支持多态。

本质是因为对静态方法在运行期只查找显式声明的类的内存,这就意味着不可能是多态。

你可能感兴趣的:(java,redis,数据结构,数据库)