1、方法重载
在实际开发过程中,会遇到一个问题:方法的功能实现上及其类似,但却有一些不同,如果每个功能实现都要重新编写一个方法,过于繁琐,也不好想方法名,例子如下所示,相当繁琐
引入方法重载
1.1什么是重载?
在同一个类中,方法名相同,参数不同的方法。与返回值无关。
参数不同:个数不同、类型不同、顺序不同(符合其一则是重载)
如果参数相同,则不构成重载关系
如不在同一个类中,则不构成重载关系
2.基本数据类型和引用数据类型
2.1 基本数据类型和引用数据类型有哪些?
2.2 基本数据类型
基本类型变量中存储的是真实的数据,数值存储在自己的空间里
即便是赋值给其他变量,也是赋的真实的值
2.3 引用数据类型
引用数据类型中记录的是堆内存的地址值,而不是真实的数据,真实的数据在堆内存new的对象中
数据值是存储在堆空间里,在自己空间里存储的是地址值
赋给其他变量时,赋的是地址值
2.4 方法传递基本数据类型的内存原理
示例如下
对于基本数据类型,局限于方法的作用域,调用方法并不影响方法外部的基本数据类型的变量。
传递基本数据类型时,传递的是真实的数据,形参的改变,不影响实际参数的值
2.5 方法传递引用数据类型的内存原理
此时传递给方法的并不是变量真实的数据,而是堆空间的地址值,因此,方法会直接去堆空间对真实值进行修改,方法执行后数据改变
3、类对象
3.1 private关键字
是一个权限修饰符号
可以修饰成员变量
被private修饰的成员变量只有在本类中才能访问
所以,在对象的实例中(也就是箭头指向的部分),不再使用图中简单的 person.age 来进行对象属性的设定和提取,而是 改成了person.setAge() 来设定 / person.getAge() 来提取。这样可以对属性的 age 有更加精细化的规定和设计,而不再局限于 int 属性所规定的范围。
3.2 this关键字
当执行set方法的时候,set内参数名称与成员变量一致的时候,容易发生混淆,导致方法无法正确执行
局部变量定义在方法内部,而成员变量定义在方法外部,类中,在执行set方法时,同名情况下容易发生混淆。如图,最后在控制台输出的age是定义的局部变量age,因为程序执行是遵循就近原则的,所以直接输出局部变量。
此时需要加入this方法,用this指代的变量为成员变量,不用this指代的依旧遵循就近原则。
3.3、构造方法
3.3.1 构造方法的格式
特点:1、方法名与类名相同,大小写也要一致
2、没有返回值类型
3、没有具体的返回值
4、构造方法不可以被static final等修饰符修饰,可以被public 等权限修饰符修饰
执行时机:1、创建对象时由虚拟机调用,不可手动调用构造方法;
2、每创建一次对象,就会调用一次构造方法
如果我们没有写任何的构造方法,那么虚拟机会给我们加一个空参构造1方法
3.3.2 构造方法的注意事项
3.4 标准的JAVABEAN类
3.5 对象内存图
3.5.1 单对象内存图
加载class文件
申明局部变量
在堆内存中开辟一个空间
默认初始化
显示初始化
构造方法初始化
将堆内存中的地址值赋给左边的局部变量
其中4、5、6三步都是对第三步在堆内创建的对象进行赋值
示例讲解
加载class文件
将TestStudent这个类的class文件加载到方法区内部,将main方法进行自动存储并执行
申明局部变量
main方法进入栈内存中,开始执行,而方法区中也会对应的载入Student的class文件
随后,在main方法中开辟出一个空间,存储局部变量s,用于存储地址值
在堆内开辟空间
随着new 关键字的执行,堆内开辟出空间,并在堆内存中存放了成员方法在方法区的地址值
默认初始化
给类对象的成员变量赋予默认的值,如name默认值为null,age的默认值为0
显式初始化
如在类中已赋予了一定的值,在显示初始化的过程中,会将这些值赋予上去
构造方法初始化
即执行构造方法,如无构造方法则默认执行无参构造
将堆内存的地址赋值给左边的局部变量
3.5.2 多对象内存图
重点便是class字节码文件无需重复加载
3.5.3 当引用了已有的对象时
两个变量引用了同一个地址值,即两个变量都能对在堆内存的对象进行操作
可以断开局部变量对堆内存对象的操作,即把地址值改写,如此便无法找到对象,报空指针异常
3.6 this的内存原理
this的本质:所在方法调用者(堆内存中的某对象)的地址值
this的作用:用于区分方法中的局部变量和成员变量
3.7 成员变量和局部变量
成员变量和局部变量的区别
4、String类内存分析
创建字符串对象有多种方式
4.1直接赋值方式
JDK7之前,StringTable是放在方法区中的,JDK7之后,StringTable放在堆内存中
采用直接赋值的方式
示例:
首先执行main方法,对于s1变量,判断串池中是否已经含有这个字符串("abc"),如果没有,则在串池中进行创建,再将串池中创建的地址值传入栈内存中的局部变量。
对于s2变量,此时串池中已有"abc",则不会创建新的字符串,而是会复用串池中已有的字符串对象的地址
4.2 采用new String的方法进行赋值
此时并不是在串池中创建了,此时是在堆内存开辟新的空间,就像新创建了一个对象一样,将地址值赋给局部变量。局部变量并不会复用地址,因此当字符串相同的多了,相对于直接赋值,会浪费更多的内存空间。
4.3 字符串拼接的内存原理
4.3.1 直接拼接字符串
示例所示
拼接的时候没有变量,都是字符串。触发字符串的优化机制。在编译的时候已经是最终的结果了。
4.3.2 采用变量进行拼接
JDK8之前
会使用StringBuilder的append方法进行拼接
拼接过程中,会创建一个StringBuilder对象,使用append方法拼接,示例如下所示
在拼接s2时,创建StringBuilder,使用append进行拼接,即两两拼接一下,就需要创建一个StringBuilder对象,非常麻烦,对此JDK8进行了优化
在拼接s3时,又创建了StringBuilder对象,进行了拼接,如此反复创建StringBuilder,非常耗费资源。
JDK8变量拼接的原理
JDK8对于变量的拼接,做了优化,会先预估拼接的次数,将需要拼接的字符串存放到数组里,从而做到只创建一次StringBuilder,便实现所有变量的拼接。
当然预估也是需要耗费时间和资源的,对于频繁拼接字符串,还是耗时耗力的
4.3.3 采用StringBuilder对字符串进行拼接
无需重复创建StringBuilder对象,可以直接进行拼接
4.4 String小结
4.4.1字符串拼接底层原理:
4.4.2 StringBuilder提高效率原理:
即不用重复创建StringBuilder对象
4.4.3 StringBuilder 扩容机制
默认创建一个长度为16的字节数组
添加的内容长度小于16,直接存
当超出容量时,会按当前容量*2+2的机制进行扩容,容量最长是Integer.MAX_VALUE,如果扩容之后还不够,以实际长度为准
5、Static 静态变量
采用static关键字,对类中的属性赋值后,所有创建的类实例对象都共享这个值。
5.1 静态变量
被static修饰的成员变量,叫做静态变量
特点:1、被该类所有对象共享
2、不属于对象,属于类
3、随着类加载而加载,优先于对象存在
调用方式:
1、类名调用(推荐)
2、对象名调用
5.2 静态方法
被static修饰的成员方法,叫做静态方法
特点:多用于测试类和工具类中
JavaBean类中很少使用
调用方式:
类名调用
对象名调用
5.3 静态变量内存图
可以见得,静态变量是随着类的加载而加载的,优先于对象出现的
堆内存有专门的位置存储静态变量,叫静态区
在new 完对象,并执行方法时,执行过程如图所示
5.4 static注意事项
静态方法只能访问静态变量和静态方法
非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法
静态方法中是没有this关键字的(静态变量是共享的,并不针对某一个对象)
无论是非静态方法,非静态成员变量,或者是this,针对的都是某一个对象属性的修改,但是静态方法是针对于类的,对于该类创建的对象,是共享的,并不能在静态方法内部区去处理它。
从内存角度解析 为什么静态方法不能调用非静态成员变量
可知执行method时,并不能输出name,因为静态区根本没有name,静态方法执行时,变量是需要到静态区中获取的。所以静态方法并不能调用实例对象变量
5.5 重新认识main方法
6、继承
6.1 继承的概念
当类与类之间,存在相同(共性)的内容,并满足子类是父类的一种,就可以考虑使用继承,来优化代码
6.2 继承的特点
JAVA只支持单继承(一个子类只能继承一个父类),不支持多继承(子类不能同时继承多个父类),但支持多层继承(子类A继承父类B,父类 B可以继承父类C)
Object是所有类的默认父类
JAVA只能单继承,不能多继承,但可以多层继承
JAVA所有的类都直接或间接的继承于Object
子类只能访问父类中非私有的成员
独立完成继承体系的案例
6.3 子类到底能继承父类的哪些内容
6.3.1对于父类的构造方法,无论是私有还是非私有都不可继承
子类继承过来父类的构造方法,会导致构造方法名称和类名不一致的情况
6.3.2对于父类的成员变量,私有和非私有的都能继承,不过子类无法调用父类的非私有的成员变量
对父类非私有的成员变量:
内存流图
方法区会加载子类和父类的class文件
等执行到new关键字时,在堆中开辟内存,此内存中分为两块,一块是父类的成员变量,另一块是子类的成员变量
对父类私有的成员变量
无法直接调用父类的私有成员变量
6.3.3对于父类的成员方法,子类能继承父类的非私有成员方法,但是不能继承父类私有的成员方法
对于父类的非私有、非静态、非final的方法,父类会建立一张虚方法表,子类在继承后,也会继承父类的虚方法表,并且在父类的基础上,添加自己的虚方法
只有父类中的虚方法才能被子类继承
内存流图
对于下图的子类和父类,方法区加载class文件如下
其中Object虚方法表中有5个方法,Fu.class文件,虚方法表中包括这5个方法及其非私有方法fuShow1(),而Zi类继承FU类,其虚方法表中加入了ZiShow方法。
fushow2 不在虚方法表中,子类无法调用
6.4 成员变量的访问特点
在方法中,对于重名的变量,还是使用就近原则
如果想访问本类的成员变量,采用this关键字,会从子类开始找,如果子类没有,会往父类中去找
如果想要访问父类的成员变量,采用super关键字,会从父类中开始找,如果在父类中找不到,会继续回溯到父类的父类中去找
6.5 成员方法的访问特点
6.5.1 this super关键字
在继承关系中,this调用子类的方法,如果子类没有该方法,会自动往其父类上追踪
super调用父类的方法,如果父类没有该方法,会往父类的父类上去找
this 理解为一个变量,表示当前方法调用者的地址值
super 代表父类存储空间
6.5.2 子类对父类的方法进行重写
对父类方法进行重写,对于子类会覆盖掉父类的方法(即子类虚方法表的方法发生改变)
重写注意事项:
重写方法的名称、形参列表必须与父类中的一致
子类重写父类方法时,访问权限子类必须大于等于父类
子类重写父类方法时,返回值类型子类必须小于等于父类
私有方法不能被重写、静态方法不能被重写,重写的只能是虚方法表内的方法
6.6 构造方法的访问特点
子类不能继承父类的构造方法,但是可以通过父类调用
子类构造方法的第一行,有一个默认的super();
默认先访问父类中无参的构造方法,再执行自己
如果想要调用父类的有参构造,必须手动书写
在子类中调用父类的构造方法
7 多态
什么是多态:即同类型的对象,表现出不同的形态
多态的表现形式:父类类型 对象名称=new 子类对象()/接口 对象名称=new 实现类名称()
其中新建出的实例对象,只能调用子类重写过父类的方法,父类中未定义的方法,子类中定义过的无法调用
可以由下图显而易见
7.1 多态的应用场景
比如需要注册账号,分为学生、老师、管理员三种类型,如果不使用多态,则要重复写三种注册方法,去接收三个不一样的类。
有了多态就好多了,用一个他们共同的父类去被他们继承,可以集成他们的共有属性,在父类中写一个方法,其他的子类根据具体的需要去重写该方法即可
实现具体例子如下:
一个方法可以接收多类对象,这就是多态的优越性
7.2 多态调用成员的特点
变量调用:编译看左边,运行也看左边
在编译过程中,编译器会看左边的父类有没有该成员变量,如果有,编译成功,没有则失败
方法调用:编译看左边、运行看右边
即会看左边父类中有没有这个方法,如果有则编译成功;实际执行会调用子类的虚方法表中的方法
用内存图来解释多态的调用特点
7.3 多态的优势和弊端
优势如上所示,会比较方便,不用重复建方法来接收不同的类,用多态后,可用一个方法接收所有的子类和父类
弊端:不能使用子类的特有功能,如果需要使用,需要强转类,如转换类型与真实对象类型不一致会报错
8、final关键字
final修饰方法:表明该方法是最终方法,不能被重写
final修饰类:表明该类是最终类,不能被继承
final修饰变量:叫做常量,只能被赋值一次
9、权限修饰符
权限修饰符:是用来控制一个成员能够被访问的范围的
可以修饰成员变量、方法、构造方法、内部类
浅显的来说 A a=new A(),对他的属性b赋值
在private修饰b的时候,a.b=10这样的赋值方式,或者直接访问a.b,只有在同一个类才做得到
默认情况下,在同一个包下的类都能访问到
protected权限下,不同包下的子类也可以访问到
public权限下,整个项目工程的类都可以直接访问
10、抽象类和抽象方法
抽象方法:将共性的行为(方法)抽取到父类之后,由于每一个子类执行的内容是不一样的,所以,在父类中不能确定具体的方法体,该方法就可以定义为抽象方法
抽象方法必须重写
抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类
注意事项:抽象类不能实例化
抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
可以有构造方法
抽象类的子类要么重写抽象类中的所有抽象方法,要么是抽象类
11、接口
12.动态代理及反射
12.1.1什么是动态代理?
如果想在现有类的基础上,增加部分功能代码,如果直接在当前类上手动修改,极容易出错,造成项目的不稳定。此时需要找代理,可以无侵入式的给代码增加额外的功能。
对象如果嫌身上干的事情太多,可以通过代理来转移部分职责
对象有什么方法想被代理,代理就一定要有对应的方法
java通过什么来保证代理的样子:
通过接口保证,后面的对象和代理需要实现同一个接口,接口中就是被代理的所有方法
12.2 动态代理示例
首先定义一个BigStar类,类中有sing与dance方法
定义一个接口,接口中写想要被代理的方法(sing和dance方法)
让star类实现该接口
接下来建立创建代理类
流程如下:
需求:外面的人想要大明星唱一首歌
流程:1、获取代理对象
代理对象=ProxyUtil.createProxy(大明星对象)
2、再调用代理的唱歌方法:代理对象.唱歌的方法(“歌曲的名称”) 即执行invoke函数
具体代码如下所示:其中if else语句中为增强的部分,在执行增强的部分后,再去star实例对象中调用其真正的方法(method.invoke),做到了不侵入改变类,但确确实实给类的方法中增加了部分的内容。
12.3 如何为JAVA创建一个代理对象
java.lang.reflect.Proxy:提供了为对象产生代理对象的方法
12.4 什么是反射
反射允许对成员变量、成员方法和构造方法进行编程访问
12.5 获取class对象的三种方式
Class.forName("全类名")
类名.class
对象.getClass()
12.6反射获取构造方法:
具体见百度