在实际开发过程中,会遇到一个问题:方法的功能实现上及其类似,但却有一些不同,如果每个功能实现都要重新编写一个方法,过于繁琐,也不好想方法名,例子如下所示,相当繁琐
引入方法重载
在同一个类中,方法名相同,参数不同的方法。与返回值无关。
参数不同:个数不同、类型不同、顺序不同(符合其一则是重载)
如果参数相同,则不构成重载关系
如不在同一个类中,则不构成重载关系
基本类型变量中存储的是真实的数据,数值存储在自己的空间里
即便是赋值给其他变量,也是赋的真实的值
引用数据类型中记录的是堆内存的地址值,而不是真实的数据,真实的数据在堆内存new的对象中
数据值是存储在堆空间里,在自己空间里存储的是地址值
赋给其他变量时,赋的是地址值
示例如下
对于基本数据类型,局限于方法的作用域,调用方法并不影响方法外部的基本数据类型的变量。
传递基本数据类型时,传递的是真实的数据,形参的改变,不影响实际参数的值
此时传递给方法的并不是变量真实的数据,而是堆空间的地址值,因此,方法会直接去堆空间对真实值进行修改,方法执行后数据改变
是一个权限修饰符号
可以修饰成员变量
被private修饰的成员变量只有在本类中才能访问
所以,在对象的实例中(也就是箭头指向的部分),不再使用图中简单的 person.age 来进行对象属性的设定和提取,而是 改成了person.setAge() 来设定 / person.getAge() 来提取。这样可以对属性的 age 有更加精细化的规定和设计,而不再局限于 int 属性所规定的范围。
当执行set方法的时候,set内参数名称与成员变量一致的时候,容易发生混淆,导致方法无法正确执行
局部变量定义在方法内部,而成员变量定义在方法外部,类中,在执行set方法时,同名情况下容易发生混淆。如图,最后在控制台输出的age是定义的局部变量age,因为程序执行是遵循就近原则的,所以直接输出局部变量。
此时需要加入this方法,用this指代的变量为成员变量,不用this指代的依旧遵循就近原则。
特点:1、方法名与类名相同,大小写也要一致
2、没有返回值类型
3、没有具体的返回值
4、构造方法不可以被static final等修饰符修饰,可以被public 等权限修饰符修饰
执行时机:1、创建对象时由虚拟机调用,不可手动调用构造方法;
2、每创建一次对象,就会调用一次构造方法
如果我们没有写任何的构造方法,那么虚拟机会给我们加一个空参构造1方法
加载class文件
申明局部变量
在堆内存中开辟一个空间
默认初始化
显示初始化
构造方法初始化
将堆内存中的地址值赋给左边的局部变量
其中4、5、6三步都是对第三步在堆内创建的对象进行赋值
示例讲解
加载class文件
将TestStudent这个类的class文件加载到方法区内部,将main方法进行自动存储并执行
申明局部变量
main方法进入栈内存中,开始执行,而方法区中也会对应的载入Student的class文件
随后,在main方法中开辟出一个空间,存储局部变量s,用于存储地址值
在堆内开辟空间
随着new 关键字的执行,堆内开辟出空间,并在堆内存中存放了成员方法在方法区的地址值
默认初始化
给类对象的成员变量赋予默认的值,如name默认值为null,age的默认值为0
显式初始化
如在类中已赋予了一定的值,在显示初始化的过程中,会将这些值赋予上去
构造方法初始化
即执行构造方法,如无构造方法则默认执行无参构造
将堆内存的地址赋值给左边的局部变量
重点便是class字节码文件无需重复加载
两个变量引用了同一个地址值,即两个变量都能对在堆内存的对象进行操作
可以断开局部变量对堆内存对象的操作,即把地址值改写,如此便无法找到对象,报空指针异常
this的本质:所在方法调用者(堆内存中的某对象)的地址值
this的作用:用于区分方法中的局部变量和成员变量
成员变量和局部变量的区别
创建字符串对象有多种方式
JDK7之前,StringTable是放在方法区中的,JDK7之后,StringTable放在堆内存中
采用直接赋值的方式
示例:
首先执行main方法,对于s1变量,判断串池中是否已经含有这个字符串("abc"),如果没有,则在串池中进行创建,再将串池中创建的地址值传入栈内存中的局部变量。
对于s2变量,此时串池中已有"abc",则不会创建新的字符串,而是会复用串池中已有的字符串对象的地址
此时并不是在串池中创建了,此时是在堆内存开辟新的空间,就像新创建了一个对象一样,将地址值赋给局部变量。局部变量并不会复用地址,因此当字符串相同的多了,相对于直接赋值,会浪费更多的内存空间。
示例所示
拼接的时候没有变量,都是字符串。触发字符串的优化机制。在编译的时候已经是最终的结果了。
JDK8之前
会使用StringBuilder的append方法进行拼接
拼接过程中,会创建一个StringBuilder对象,使用append方法拼接,示例如下所示
在拼接s2时,创建StringBuilder,使用append进行拼接,即两两拼接一下,就需要创建一个StringBuilder对象,非常麻烦,对此JDK8进行了优化
在拼接s3时,又创建了StringBuilder对象,进行了拼接,如此反复创建StringBuilder,非常耗费资源。
JDK8变量拼接的原理
JDK8对于变量的拼接,做了优化,会先预估拼接的次数,将需要拼接的字符串存放到数组里,从而做到只创建一次StringBuilder,便实现所有变量的拼接。
当然预估也是需要耗费时间和资源的,对于频繁拼接字符串,还是耗时耗力的
无需重复创建StringBuilder对象,可以直接进行拼接
即不用重复创建StringBuilder对象
默认创建一个长度为16的字节数组
添加的内容长度小于16,直接存
当超出容量时,会按当前容量*2+2的机制进行扩容,容量最长是Integer.MAX_VALUE,如果扩容之后还不够,以实际长度为准
采用static关键字,对类中的属性赋值后,所有创建的类实例对象都共享这个值。
被static修饰的成员变量,叫做静态变量
特点:1、被该类所有对象共享
2、不属于对象,属于类
3、随着类加载而加载,优先于对象存在
调用方式:
1、类名调用(推荐)
2、对象名调用
被static修饰的成员方法,叫做静态方法
特点:多用于测试类和工具类中
JavaBean类中很少使用
调用方式:
类名调用
对象名调用
可以见得,静态变量是随着类的加载而加载的,优先于对象出现的
堆内存有专门的位置存储静态变量,叫静态区
在new 完对象,并执行方法时,执行过程如图所示
静态方法只能访问静态变量和静态方法
非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法
静态方法中是没有this关键字的(静态变量是共享的,并不针对某一个对象)
无论是非静态方法,非静态成员变量,或者是this,针对的都是某一个对象属性的修改,但是静态方法是针对于类的,对于该类创建的对象,是共享的,并不能在静态方法内部区去处理它。
从内存角度解析 为什么静态方法不能调用非静态成员变量
可知执行method时,并不能输出name,因为静态区根本没有name,静态方法执行时,变量是需要到静态区中获取的。所以静态方法并不能调用实例对象变量
当类与类之间,存在相同(共性)的内容,并满足子类是父类的一种,就可以考虑使用继承,来优化代码
JAVA只支持单继承(一个子类只能继承一个父类),不支持多继承(子类不能同时继承多个父类),但支持多层继承(子类A继承父类B,父类 B可以继承父类C)
Object是所有类的默认父类
JAVA只能单继承,不能多继承,但可以多层继承
JAVA所有的类都直接或间接的继承于Object
子类只能访问父类中非私有的成员
独立完成继承体系的案例
子类继承过来父类的构造方法,会导致构造方法名称和类名不一致的情况
对父类非私有的成员变量:
内存流图
方法区会加载子类和父类的class文件
等执行到new关键字时,在堆中开辟内存,此内存中分为两块,一块是父类的成员变量,另一块是子类的成员变量
对父类私有的成员变量
无法直接调用父类的私有成员变量
对于父类的非私有、非静态、非final的方法,父类会建立一张虚方法表,子类在继承后,也会继承父类的虚方法表,并且在父类的基础上,添加自己的虚方法
只有父类中的虚方法才能被子类继承
内存流图
对于下图的子类和父类,方法区加载class文件如下
其中Object虚方法表中有5个方法,Fu.class文件,虚方法表中包括这5个方法及其非私有方法fuShow1(),而Zi类继承FU类,其虚方法表中加入了ZiShow方法。
fushow2 不在虚方法表中,子类无法调用
在方法中,对于重名的变量,还是使用就近原则
如果想访问本类的成员变量,采用this关键字,会从子类开始找,如果子类没有,会往父类中去找
如果想要访问父类的成员变量,采用super关键字,会从父类中开始找,如果在父类中找不到,会继续回溯到父类的父类中去找
在继承关系中,this调用子类的方法,如果子类没有该方法,会自动往其父类上追踪
super调用父类的方法,如果父类没有该方法,会往父类的父类上去找
this 理解为一个变量,表示当前方法调用者的地址值
super 代表父类存储空间
对父类方法进行重写,对于子类会覆盖掉父类的方法(即子类虚方法表的方法发生改变)
重写注意事项:
重写方法的名称、形参列表必须与父类中的一致
子类重写父类方法时,访问权限子类必须大于等于父类
子类重写父类方法时,返回值类型子类必须小于等于父类
私有方法不能被重写、静态方法不能被重写,重写的只能是虚方法表内的方法
子类不能继承父类的构造方法,但是可以通过父类调用
子类构造方法的第一行,有一个默认的super();
默认先访问父类中无参的构造方法,再执行自己
如果想要调用父类的有参构造,必须手动书写
在子类中调用父类的构造方法
什么是多态:即同类型的对象,表现出不同的形态
多态的表现形式:父类类型 对象名称=new 子类对象()/接口 对象名称=new 实现类名称()
其中新建出的实例对象,只能调用子类重写过父类的方法,父类中未定义的方法,子类中定义过的无法调用
可以由下图显而易见
比如需要注册账号,分为学生、老师、管理员三种类型,如果不使用多态,则要重复写三种注册方法,去接收三个不一样的类。
有了多态就好多了,用一个他们共同的父类去被他们继承,可以集成他们的共有属性,在父类中写一个方法,其他的子类根据具体的需要去重写该方法即可
实现具体例子如下:
一个方法可以接收多类对象,这就是多态的优越性
变量调用:编译看左边,运行也看左边
在编译过程中,编译器会看左边的父类有没有该成员变量,如果有,编译成功,没有则失败
方法调用:编译看左边、运行看右边
即会看左边父类中有没有这个方法,如果有则编译成功;实际执行会调用子类的虚方法表中的方法
用内存图来解释多态的调用特点
优势如上所示,会比较方便,不用重复建方法来接收不同的类,用多态后,可用一个方法接收所有的子类和父类
弊端:不能使用子类的特有功能,如果需要使用,需要强转类,如转换类型与真实对象类型不一致会报错
final修饰方法:表明该方法是最终方法,不能被重写
final修饰类:表明该类是最终类,不能被继承
final修饰变量:叫做常量,只能被赋值一次
权限修饰符:是用来控制一个成员能够被访问的范围的
可以修饰成员变量、方法、构造方法、内部类
浅显的来说 A a=new A(),对他的属性b赋值
在private修饰b的时候,a.b=10这样的赋值方式,或者直接访问a.b,只有在同一个类才做得到
默认情况下,在同一个包下的类都能访问到
protected权限下,不同包下的子类也可以访问到
public权限下,整个项目工程的类都可以直接访问
抽象方法:将共性的行为(方法)抽取到父类之后,由于每一个子类执行的内容是不一样的,所以,在父类中不能确定具体的方法体,该方法就可以定义为抽象方法
抽象方法必须重写
抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类
注意事项:抽象类不能实例化
抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
可以有构造方法
抽象类的子类要么重写抽象类中的所有抽象方法,要么是抽象类