六、面向对象
一、面向过程与面向对象
面向过程(POP) 与面向对象(OOP)
- 面向对象:Object Oriented Programming
- 先整体,再局部先抽象,再具体先想能做什么,在想怎么做什么找对象解决问题
- 面向过程:Procedure Oriented Programming
- 强调的是功能行为,以函数为最小单位,考虑怎么做。
- 程序员从面向过程的执行者转化成了面向对象的指挥者
二、 类和对象
- 类 : 对一类事物的描述,是抽象的、概念上的定义
- 对象 : 是实际存在的该类事物的每个个体,因而也称为实例(instance)
- 类是事物的属性(外在特征)和行为(具备的功能)的集合(对象的模板),对象是类的具体的体现(实例)
- 可以理解为:类= 抽象概念的人;对象= 实实在在的某个人
- 面向对象程序设计的重点是类的设计;
- 设计类,其实就是设计类的成员。
2.2、类与对象的创建及使用
- 创建
- 把用类创建对象的过程称为类的实例化 (创建类 = 类的实例化 = 实例化类)
- 把用类创建的对象称为实例对象(类的实例)
- 使用(思想落地的实现)
- 1.创建类,设计类的成员
- 2.创建类的对象
- 3.通过“对象.属性”或“对象.方法()”调用对象的结构
修饰符 class 类名{
属性声明;
方法声明;
}
2.3、内存解析
- 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java 虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
- 通常所说的栈(堆栈)(Stack),是指虚拟机(VM)栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double).对象引用(reference 类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完,自动释放。
- 方法区(MethodArea),用于存储已被虚拟机加载的类信息 (有多少变量、方法、是什么修饰符修饰的等) 、常量、静态变量、即时编译器编译后的代码等数据。
- 方法区里存放着类的版本,字段,方法,接口和常量池。常量池里存储着字面量和符号引用。符号引用包括:1.类的全限定名,2.字段名和属性,3.方法名和属性。
- 类型信息
方法区需要存储每个加载的类(类,接口,枚举,注解)的以下类型信息:
- 完整名称(包类.类名)
- 这个类的直接父类的完整名称(接口和java.long.object没有父类)
- 这个类型的修饰符(public,abstract,final的某个子集)
- 这个类型直接接口的一个有序列表
- 域(成员)信息
- JVM需要保存类型的域信息和域的声名顺序
- 域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)
- 方法信息
- JVM需要保存所有方法的信息及其声明的顺序
- 方法的名称,返回类型,参数(数量类型,按顺序),修饰符
- 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
- 异常表(abstract和native方法除外)
- 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
- non-final的类变量(static)
- 类变量和类关联在一起,随着类的加载而加载,他们成为类数据在逻辑上的一部分
- 类变量被类的所有实例共享,即使没有类实例时,你也可以访问它
- 补充说明:全局常量(static final)被声明为final的类变量的处理方法则不同,每个全局常量在编译的时候就会被分配了。
- 在运行阶段的时候,进行类加载时classloader类加载器会把class文件中方法对应的代码片段放入内存区中的方法区中,等待调用方法(不调用不执行即使是静态成员方法)
- jvm在运行过程中,会把方法里面的代码块按照方法调用的先后顺序压栈入内存中的栈空间,按照方法里语句的执行顺序(从上到下,从里到外)去进行弹栈与运行操作
2.4、类的加载
当第一次需要使用类信息时会通关classLoader类加载器加载将类信息加载进方法区。
类加载的原则:延迟加载,能不加载就不加载。
**触发类加载的几种情况: **
- (1)、调用静态成员时,会加载静态成员真正所在的类及其父类。
- 通过子类调用父类的静态成员时,只会加载父类而不会加载子类
- (2)、第一次 new 对象的时候 加载(第二次再 new 同一个类时,不需再加载)。
- (3)、加载子类会先加载父类。(覆盖父类方法时所抛出的异常不能超过父类定义的范围)
- 如果静态属性有 final 修饰时,则不会加载,当成常量使用
- 例:public static final int a =123;
- 但是如果上面的等式右值改成表达式(且该表达式在编译时不能确定其值)时则会加载类。
- 例:public static final int a = math.PI
- 如果访问的是类的公开静态常量,那么如果编译器在编译的时候能确定这个常量的值,就不会被加载;
- 如果编译时不能确定其值的话,则运行时加载
三、类的结构
类的成员结构有五种:属性(字段)、方法、构造器、代码块、内部类
在面向对象的编程和思想中,出现了类属性的概念。
注意:在Java中,类的属性并非指的是类中的非static修饰的成员变量(类的成员结构中的属性),属性是一个或两个代码块,表示一个get访问器和/或一个set访问器。当读取属性时,执行 get 访问器的代码块;当向属性分配一个新值时,执行 set 访问器的代码块。不具有 set 访问器的属性被视为只读属性。不具有 get 访问器的属性被视为只写属性。同时具有这两个访问器的属性是读写属性。
成员结构之字段 field(属性,成员变量,实例变量)
声明和默认值
在类中方法外声明的变量,用于描述类的状态信息。
访问权限修饰符 数据类型 变量名 = 变量值 ;(访问权限修饰符和赋值可以省)
数据类型 | 默认值 | |
---|---|---|
基本类型 | 整数(byte,short,int,long) | 0 |
浮点数(float,double) | 0.0 | |
字符(char) | ‘\u0000’ | |
布尔(boolean) | false | |
引用数据类型 | 数组,对象,String | null |
实例变量,局部变量,静态成员变量(类变量)
- 相同点
- 先声明后使用
- 定义变量格式相同 数据类型 变量名 = 变量值
- 都有变量自己对应的作用域
- 不同点
- 在类中的位置不同
- 实例变量,和静态成员变量在类中方法外
- 局部变量在方法中
- 访问权限修饰符不同
- 实例变量,静态成员变量可以用访问权限修饰符修饰
- 局部变量在类中不可以用访问权限修饰符修饰
- 默认初始化值不同
- 实例变量,静态成员静态变量有默认初始值,可以不用进行显示初赋值
- 局部变量没有默认初始值需要进行显示赋值
- 加载到内存位置不同
- 成员静态变量在随着类加载进入方法区中
- 实例变量在堆中(实例变量属于对象,对象在堆中)
- 局部变量在栈中(局部变量属于方法,方法要进栈)
- 作用域不同
- 静态成员变量通过权限修饰符控制其他类通过类名或对象名的访问,在本类随意访问
- 实例变量可以在整个类中使用
- 局部变量只能在方法中使用
- 声明周期不同
- 静态成员变量随着类的加载而存在,随着类的消失而消失
- 实例变量的生命周期随着对象而产生,由垃圾回收机制回收对象结束
- 局部变量随方法进栈而诞生,随方法出栈而死亡
- 在类中的位置不同
对属性赋值的顺序
- 默认初始化
- 显示初始化/代码块中进行赋值(按前后顺序,后面的会覆盖前面的)
- 构造器初始化
- 通过"对象.属性"或"对象.方法"进行赋值
注意:能够使用static修饰的属性方法和代码块中的属性赋值都是拥有最高优先级,以上都是非静态的
成员结构之方法 method(函数)
类中方法的声明和使用
访问权限修饰符 (static) 返回值 方法名(形参){
方法体;
}
public (static) int show(int i){
return i;
}
方法的重载(overload)
- 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。(同类同名不同参)用注解检测@Override
- 注意:与方法的返回值类型、权限修饰符、形参变量名、方法体都无关。
可变个数形参的方法
public void show(int a,String...strs){
}
- jdk 5.0新增的内容
- 可变个数形参的格式:数据类型 ... 变量名
- 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载。
- 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。即二者不可共存。
- 可变个数形参在方法中的形参中,必须声明在末尾。
- 可变个数形参在方法中的形参中,最多只能声明一个可变形参
方法参数
- 形参:方法括号中的参数声明,也是局部变量
- 实参:调用方法时传递的参数
参数传递机制
- 值传递
- 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
- 引用传递
- 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
成员结构之构造器 (构造方法) constructor
作用是用来在创建对象时, 给该实例对象的实例变量进行初始化的
设计类时,都会有一个默认的无参构造器,即使不显示定义也会有,当类中有显示有参构造器是默认的才会消失,多个构造器之间构成重载
访问权限修饰符 类名(形参列表){}
public Student(形参列表 ){}
构造器是创建对象的重要途径,但对象不是构造器创建的,构造器只是负责对 java 对象实例变量执行初始化(也就是赋初始值),当创建任何 java 对象时,程序总会先一次调用每个类非静态初始化块、父类构造器(总是从 Object 开始)执行初始化,最后才调用本类的非静态初始化块、构造器执行初始化。这个过程中对所有的父类进行了类加载,为父类中的属性在堆中开辟了一段内存空间
在执行构造器代码之前,该对象所占的内存已经被系统分配下来,这些内存里值都默认是空值 —— 对于基本类型的变量,默认的空值就是 0 或 false,对于引用类型的变量默认的空值就是 null。
此时还不能被外部程序访问,只能在该构造器中通过this来引用。当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另一个引用变量,从而让程序外部可以访问该对象。
在处理 java 类中的成员变量时,并不是采用运行时绑定,而是一般意义上的静态绑定(通过之前的类加载的各种信息进行绑定)。必须明确,运行时(动态)绑定针对的范畴只是对象的方法。(方法的话只有 static 和 final (所有 private 默认是 final 的) 是静态绑定的.)程序编译期的绑定。Java当中的方法只有final,static,private和构造方法是静态绑定。
在编译时 java 类的成员变量采用静态绑定,即成员变量被关联到了类上。此时虽然父类构造器中的 this 指向的是子类对象的实例,但是虚拟机在如果在子类对象中找不到子类成员属性的时候,就到包含该构造方法中的类去找静态绑定相应的父类成员属性,并拿来为其子类赋值。
为对象分配空间——>实例变量默认初始化——>执行构造器的执行体——>通过this给实例变量赋值——>构造器返回对象给引用变量
JavaBean
- 类是public的。
- 一个无参的public的构造器。和其他构造器
- 属性和对应的set方法/get方法
成员结构之构造器 constructor
1.构造器的作用:
1.1 创建对象
1.2 初始化对象的信息
构造方法是执行对象的初始化操作(初始化:类中成员变量赋值)
构造方法就是给类中属性赋值,类中属性的类型,在定义时就规定好了。
2.说明:
2.1 如果没有显示的定义类的构造器的话,则系统默认提供一个空参的构造器
2.2 定义构造器的格式:权限修饰符 类名(形参列表){}
2.3 一个类中定义的多个构造器,彼此构成重载
2.4 一旦我们显示的定义了类的构造器之后,系统就不再提供默认的空参构造器
2.5 一个类中,至少会有一个构造器。
2.6 构造方法的互相调用:【使用this(相应参数),必须放在该方法的第一行调用,并且调用不能成"环",不能递归调用成环】
扩展
关于构造器的特殊点:(private修饰构造方法)A. 产生对象时,由JVM调用 B. 一旦构造方法被private修饰,这个类就不能通过外部产生对象;举例:对象个数有限,类内部提供方法产生对象,当一个类构造方法被private修饰,表示不希望它能通过外部产生对象,二由类本身向外提供对象,外部只能使用,不能创建。
构造器是创建Java对象的重要途径,通过new关键字调用构造器时,构造器也确实返回了该类的对象,但是这个对象并不是完全由构造器负责创建的。构造器只是负责对 java 对象实例变量执行初始化(也就是赋初始值),在执行构造器代码之前,该对象所占的内存已经被分配下来,这些内存里值都默认是空值 —— 对于基本类型的变量,默认的空值就是 0 或 false,对于引用类型的变量默认的空值就是 null。 实际上,当程序员调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认初始化,这个对象已经产生了——这些操作在构造器执行之前都完成了。也就是说,当系统开始执行构造器的构造体之前,系统已经创建一个对象,只是这个对象还不能被外部程序访问,只能在该构造器中通过this来引用。当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另一个引用变量,从而让程序外部可以访问该对象。
为对象分配空间——>实例变量默认初始化——>执行构造器的执行体——>通过this给实例变量赋值——>构造器返回对象给引用变量
在处理 java 类中的成员变量时,并不是采用运行时绑定,而是一般意义上的静态绑定。必须明确,运行时(动态)绑定针对的范畴只是对象的方法。(方法的话只有 static 和 final (所有 private 默认是 final 的) 是静态绑定的.)https://my.oschina.net/blackgladiolus/blog/691582
当创建任何 java 对象时,程序总会先一次调用每个类非静态初始化块、父类构造器(总是从 Object 开始)执行初始化,最后才调用本类的非静态初始化块、构造器执行初始化。为父类中的属性在堆中开辟了一段内存空间。
在编译时 java 类的成员变量采用静态绑定,即成员变量被关联到了类上。此时虽然父类构造器中的 this 指向的是子类对象的实例,但是虚拟机在子类对象中找不到子类属性的时候,就到包含该构造方法中的父类去找静态绑定相应的属性的,并为其赋值。
成员结构之代码块(初始化块)
静态代码块
用static 修饰的代码块
作用:随着字节码文件对象的加载而对类进行初始化,只会执行一次
注意:静态代码块只能对静态成员变量进行赋值和调用静态方法
构造(实例)代码块
在类中位于字段位置,先于构造方法
作用:创建对象时,先于构造方法给实例变量初始化,多个构造代码块,按顺序执行,执行完才会执行构造方法
每创建一个对象都会执行一次,可以将构造方法中的共性赋值抽取出来,放到构造代码快中,节省代码编写
局部代码块
定义在方法中,用来控制局部变量的生命周期,使其及时从内存中消失
同步代码快
用synchronized修饰的,用于同步线程,被修饰的代码块会被加上内置锁
执行顺序
父类静态代码块->子类成员代码块->main代码块->父类构造代码块->父类构造器->子类构造代码块->子类构造器
成员结构之内部类(实现某个类中单独需要的功能)
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
内部类可以访问外部类的任何成员(包括构造器),不管是公有的还是私有的,静态的还是非静态的。同样,外部类也可以访问到内部类的所有成员.
内部类间接实现了多继承,将一个类当做另一个类的成员,那么这个类拥有另一个类的全部特征
每个内部类都能独立的继承自一个(接口的)实现,无论外部类是否已经继承了某个(接口的)实现
也就是说 单个外部类,可以让多个内部类以不同的方式实现同一个接口或者继承同一个类
一个外部类可以创建多个内部类,就达到了类似"多重继承"的效果
成员内部类
一个类定义在另一个类的成员位置
是成员结构的同时也是类,可以被所有访问权限修饰符,static ,final,abstract修饰
class Outter {
Inner inner = new Inner();//外部类访问内部类需要创建内部类对象引用,通过引用访问
public class Inner{
private int =10;//私有内部类的字段
//成员内部类,因为是类的成员之一所以可以用权限修饰符,作用和其他成员一样,外部类进行访问不受限制
}
class Inner1 {//内部类之间可以互相访问,也不受权限修饰符访问限制
Inner inner = new Inner();//需要创建另一个内部类对象的引用,且访问其成员不受访问权限限制
}
}
//在内部访问外部类字段和方法
外部类名.this.字段()//外部类名.this指代外部类的实例对象,单独的this指代内部类实例对象
外部类名.this.成员方法()
Outter.inner inner = new Outter().new Inner();//通过外部类直接创建内部类
Outter.Inner inner = new Outter().inner;//在外部类中创建好引用,直接调用
**
- 可以自由访问外部类的成员结构,外部类不能直接使用内部类的成员,外部类访问内部类需要创建内部类对象引用,通过引用访问
- 外部类和内部类出现同名的字段和方法时,会出现隐藏,使用时就近原则
- 内部类是依附外部类的存在而存在,所以要创建内部类对象必须要创建外部类对象
- 外部类与内部类和内部类与内部类的成员之间的访问无视权限修饰符,更加体现了权限修饰符是限定本类实例对象在别的类的时候的权限,内部类也是类的成员不受限制,匿名内部类和局部内部类1.7以前只能访问外部类的final变量
- 非静态内部类不能创建类成员,因为在类加载时,java虚拟机要求所有的静态变量必须在对象创建之前加载完成,jvm加载外部类时,不会创建实例对象,而加载内部类,外部类必须实例化,如果成员内部类有静态成员,加载静态成员时,根本没有外部类对象,这样便产生了矛盾。
静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类可以不依赖外部类创建,因此不能使用外部类非静态成员,因为创建内部类实例对象时,外部类可以没有实例, 访问非静态成员必须依赖实例对象,因此出现矛盾
局部内部类(方法内、代码块内、构造器内)
定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
匿名内部类
匿名内部类是唯一一种没有构造器的类。使用的构造函数都是来自父类,构造参数也是用来传给父类,不过可以通过构造带充当构造器,正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
在匿名内部类中可以使用外部类的属性,但是外部类却不能使用匿名内部类中定义的属性,因为是类是匿名,因此在外部类中无法获取这个类的类名,也就无法得到属性信息。
1.7之前局部内部类和匿名内部类只能访问局部final变量
在使用匿名内部类的过程中,我们需要注意如下几点:
- 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
- 匿名内部类中不能存在任何的静态成员变量和静态方法,不能有访问修饰符
- 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
- 匿名内部类不能是抽象的,因为在创建同时就会实例化(new)出一个对象,它必须要实现继承的类或者实现的接口的所有抽象方法。
四、面向对象特征之一:封装与隐藏
通过访问权限修饰符修饰类的结构来指定外部的访问数据的方式,合理隐藏合理暴露
高内聚:类的内部数据操作细节直接完成,外部不允许干涉
低耦合:进对外提供少量的方法使用
结构用private修饰,提供get/set方法访问和设置数据
权限修饰
权限修饰符 public、protected、default(缺省)、private 置于类的成员结构(不能修饰代码块),该代码块是用来初始化的 定义前,用来限定实例对象在别的类中时对本类成员(即外部访问,内部类和外部类之间访问不受限制)的访问权限,本类不管加什么都可以访问.访问一个类的成员结构(非静态)必须有实例对象,因此可以通过修饰符实现封装
对于 class(外部类) 的权限修饰只可以用 public 和 default(缺省)。内部类都可以
- public和private完全相反
- 缺省 同一个包中,不管其他条件
- protected 默认的包访问权限扩展了一下, 子类对象可以访问,不管是不是同一个包,而不是在子类中用父类对象访问
- protected的含义是指子类可以访问,说的是子类直接访问父类的protected方法
- 而不是说子类中,可以调用父类的对象访问父类的protected方法
- 子类可以访问,可以在子类访问不是一个概念
package com.itheima.package1;
class Father{
protected int i = 10;
protected void show(){}
}
package com.itheima.package2;
public class Son extends Father{
public void showw(){
show();//父类的成员,不在一个包,用protected修饰的在子类直接访问,但是不能通过子类实例调用父类的
i = 2;
}
}
同类中 | 同包不同类有继承关系 | 同包不同类没有继承关系 | 不同包有继承关系 | 不同包没有继承关系 | |
---|---|---|---|---|---|
public | true | true | true | true | true |
protected | true | true | true | true | false |
默认 | true | true | true | false | false |
private | true | false | false | false | false |
开发中,一个类的各个组成部分用的修饰符:
类是用public修饰的
属性是用private修饰的
构造方法是用public修饰的
偶尔会用private修饰的
成员方法是用public修饰的
五、面向对象特征之一: 继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。还可以在原有的基础上进行扩展和修改 ,用extends关键字
体现:一旦子类继承父类以后,子类中就获取了父类中声明的结构成员中实例变量和实例方法但是没有权限直接访问父类的实例变量和实例方法,但是可以通过父类的get/set间接访问到,子类的内存中出现了父类的属性
例: 房子中的卧室中的保险箱,卧室被房子全部包含,但是打不开保险箱
子类继承特点
在Java中,类只支持单继承(继承一个),不可以多继承,可以多层继承(a->b->c),不可以循环继承。
- Java 中关于继承性的规定:
- 一个类可以被多个类继承
- Java 中类的单继承性:一个类只能有一个父类
- 子父类是相对的概念。
- 子类直接继承的父类,称为:直接父类。间接继承的父类,称为,间接父类。
- 子类继承父类后,就获取了直接父类以及所有间接父类中声明的变量和方法。
- java中静态成员变量和静态成员方法可以被继承,但是没有被重写(overwrite)而是被隐藏。
- 如果我们没有显式的声明一个类的父类的话,则此类继承于 java.lang.Object 类
- 所有的 java 类(除 java.long.Object 类之外)都直接或间接地继承于 java.lang.Object 类;
- 意味着,所有的 java 类具有 java.lang.Object 类声明的功能。
方法的重写(override/overwrite)
方法重写 :子类中出现与父类一模一样的方法时(返回值类型(基本数据类型)不能高于父类型,或是父类型的子类()引用,访问权限不能比父类被重写的权限低,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
应用:重写以后,当创建子类对象以后,通过子类对象去调用子父类中同名同参数方法时,执行的是子类重写父类的方法。即在程序执行时,子类的方法将覆盖父类的方法。
重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
区分方法的重载与重写(有的书也叫做“覆盖”)
答:方法的重写Overriding和重载Overloading是Java多态性的不同表现。
重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。
如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。
子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。
如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。
构造方法的继承特点
当类之间产生了关系,其中各类中的构造方法,又产生了哪影响呢?
首先我们要回顾两个事情,构造方法的定义格式和作用。
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。
继承后子类构造方法特点:子类所有构造方法都会调用父类的无参构造
子类对象实例化分析
- 从结果看,子类对象在堆空间创建,加载父类声明的的全部字段,方法
- 从过程看,在创建过程中一定会直接或间接调用父类构造器,直到Object的空参构造为止,这个过程中对所有的父类进行了类加载,因此可以看到内存中有父类的所有结构,然后考虑调用
- 注意:加载字节码文件并不代表创建对象,虽然子类调用了父类的构造器,但是从头到尾只创建了一个子类对象为什么只创建一个对象(详见构造器)
- 实例变量是静态绑定,实例方法是动态绑定
静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能够被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态的方法可以被继承和重写,因此可以实现多态。
六、面向对象特征之一: 多态
如果它不是晚绑定(动态绑定), 它就不是多态。
Java中实现多态的机制是靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
简单理解就是适合父类的,必定也适合子类,即父类存在的地方也可以用子类来替换(而这其实对应一种设计原则,就是里氏替换原则。
从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。
编译器根据方法不同的参数表,对同名方法的名称做修饰。
对于编译器而言,这些同名方法就成了不同的方法。
它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,
即子类可以重载父类的同名不同参数的方法。所以:对于重载而言,在方法调用之前,
编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,
这称为“晚绑定”或“动态绑定”。
Java 中多态的定义
多态是指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。继承在为多态的实现做了准备。子类继承父类,我们可以编写一个指向子类的父类类型引用,该引用既可以处理父类对象,也可以处理子类对象,当相同的消息发送给子类或者父类对象时,该对象就会根据自己所属的引用而执行不同的行为,这就是多态。即多态性就是相同的消息使得不同的类做出不同的响应。
Java 实现多态的必要条件
Java实现多态有三个必要条件 : 继承,方法重写,向上转型。
继承: 在多态中必须存在有继承关系的子类和父类。
方法重写(可以没有): 子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型: 在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
简单来说就是: 父类引用指向子类对象(或子类的对象赋给父类的引用)。
多态间子父类之间成员关系
- 父类引用只能使用自身带的成员变量,不能使用子类中成员变量
- 父类引用不能使用子类中特有的成员方法,可以使用子类中重写父类的方法
- 父类引用只能使用父类自身的静态成员方法,成员变量
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类的属性和方法不能调用。但其对象的真实类型是子类对象
提高了代码的扩展性
父类引用作为形式参数,子类对象作为实际参数
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律