第二部分 面向对象OOP(重点)中
类之间的关系:
1.组合(has-a):一个类的方法操纵另一个类的对象 // ,汽车类has-a引擎类
2.继承(is-a):狗 is-a动物
多用组合少用继承,通过判断是否需要从新类向父类进行向上转型来决定是否需要用继承
函数(方法)
1.用来表示做什么事,满足某些功能所提供的一些工具。
2.方法顺序执行,无论是静态还是非静态的方法 。
同类中调用方法:方法名(参数列表)
定义模式:
修饰符 返回值类型 函数名(参数类型 形参...){
执行语句;
return 返回值; //返回值表示的是方法运行后得出的结果,尽量不要返回引用类型,返回它的拷贝
}
注: 1.明确方法结果=明确返回值类型,对于返回值,声明方法时声明了返回值,就一定要return 返回值,不需要返回值用void声明
2.实现功能时是否有未知内容参与运算,其实就是明确参数列表,函数中只能调用函数,不能定义函数。
3.实参是创建对象时传入的具体数字,形参是用于接收实际数字的,创建方法括号中的参数,实参可以是比形参小的类型。
4.当方法1里调用方法2的时候,方法2先执行,方法1的局部变量会在栈上呆许久,直至方法2执行完。
5.判断方法执行后对后续方法是否有影响就是判断方法应不应该有返回值。
6.return可以用来结束一个方法,及时这个函数声明是没有用到返回值,return代表方法结束。
重载:同类中,出现同名函数,若参数列表个数,类型或顺序不同,则为函数重载(可将功能代码封装,便于对该功能的复用) 注:返回值不同不能构成重载,当同类两个方法只有返回值不同是不可能创建成功的,因为调用的时候虚拟机不可能区分出来!
方法参数:方法参数可以是对象,这样这个方法就可以对这个对象的状态进行修改特定的数值
方法会运用形参,调用的一方传入实参,形参有几个参数,实参就必须输入几个参数
java是通过值传递也就是拷贝传递,所以当把实参传给形参只是把实参的字节组合拷贝后传递,两者互不影响
例如有一个涨工资的方法,又有一个涨特定工资的方法,涨特定工资的方法的参数可以是对象,然后再内部中调用涨工资方法
public void addSalary1(Employee e){ e.addsalary(400) ; }
public void addSalary (int x){ double salary+=x;}
java采用的是按值调用,也就是说方法得到的是所有参数值的一个拷贝
(1)方法参数为基本类型(包括字符串),参数不可能修改
double percent
harry.raiseSalary(percent) //方法调用后percent值不变,变得是percent的拷贝,然而方法执行后拷贝不再使用
(2)方法参数为对象引用,参数是引用对象变量的拷贝,但是都指向同一个引用,方法结束后参数不再使用,但是引用的对象发生变化,因此可以改变一个对象参数的状态
(3)一个方法不能让对象参数引用一个新对象
Java采用的不是引用调用而是值传递
public static void swap(Employee e1,Employee e2){
Employee temp=x;
y=temp;}
此时x,y的引用确实发生了交换但是,方法执行后x,y不再使用,所以a,b并没有发生变化
若建立两个对象,调用这两个方法并不会改变两个对象的引用,这个方法交换的是a,b的拷贝,而方法执行后,拷贝不再使用,所以只有直接操作引用对象的才会发生变化,而操作在用于接收引用的变量时,不会发生变化
注:值传递表示的是实参传给形参的只是实参拷贝的值,对值操纵不会影响实参
将p1拷贝一份变成p2,方法里操纵的是p2
引用传递:传递的是实参的地址,可以直接对实参进行改变。
第二部分 面向对象OOP(重点)中
为什么java不是引用传递:虽然在传递一个引用型变量的时候,形参的运算可以改变引用的值,但是,但是其原理并不是直接就把实参的地址传给了形参,而是将实参的引用拷贝了一份传递给了形参,所以当你想交换两个引用的话调用方法的时候,你交换的只会是两个引用的拷贝,而实际上的引用并没有发生交换。也就是说java可以改变引用状态,但是不能让对象参数引用一个新对象。
final
(1)实例域
final修饰的实例域定义后必须被初始化(可以通过声明和直接赋值同时进行或者声明后用构造方法初值化),并且实例域设置为final后,创建对象后也就是构造器执行后,这个域的值被设置表示最终值,并且在后面的操作中,不能对它进行修改,也就是说不可以有set方法改变其状态,final尽量修饰的是不可变的类(String等),或基本类型,对于可变类尽量不要用,final Date hiredate仅仅意为着hiredate中的对象引用不再发生变化,而引用地址里的内容可以发生变化。
final int a; a=4;是不对的 应该使用final int a=4;或者用构造函数
(2)类:不可被继承
(3)方法:不可重写方法
(4)修饰局部变量可以先声明后初始化 final int a; a=4;是可以的 //因为虚拟机没有给它赋初始值
Static静态域,生命周期长
(1)被静态修饰的成员可以被类名直接调用(不和对象关联在一起),类加载是加载进内存
(2)优先于对象调用
(3)位于内存的方法区中,实现同类对象数据共享,也就是说一个对象对它修改后,另一个对象访问的是它修改后的值
(4)静态方法(类方法)只能访问静态成员(因为静态的方法不知道堆上有哪些实例)
(5)少使用静态变量,消耗内存大
(6)静态方法不能重写,因为它已经确定被哪个类所拥有
静态变量是随着类的产生而产生,类只要程序一运行就会产生,所以说只要程序还是运行的,静态变量就会一直存在,而无论你创建了多少个对象访问的的都是同一个静态变量
而对于成员变量来说,在一个对象中访问,不管调用多少次方法,访问的都是同一个成员变量,而当新创建一个对象,它就会将这个成员变量重新初始化,而对于静态变量来说,不管创建多少对象访问的这个变量都是上一个对象操纵后更改后的值,不会因为一个对象的创建而重新初始化,它初始化的方法是重新运行这个程序
注:不管创建多少个对象,一个类只有一个static变量
何时用:1.成员变量:确定该成员变量是否所有对象都一样,一样用static修饰(固定公式,人眼睛瞪)
2.成员函数:是否调用特定数据,(静态方法不能调用实例域(状态),表示一种不依靠对象的行为)
public static void main(String[] args) //main是一个静态方法,执行并创建程序所需要的对象
如果一个方法与其对象及实例对象丝毫没有关系就可以用static修饰,如果有关就不能用它修饰
类常量:希望某个常量在一个类多个方法使用 static final修饰(必须初始化)
3.static代码块
语法: static { 随着类加载被执行 }
应用场景:加载一些文件,图片,配置文件等(只能被执行一次)
所有的静态初始化块都优先执行,其次才是非静态的初始化块和构造函数,它们的执行顺序是:
父类的静态属性初始化 //静态属性初始化和静态初始化块是顺序呢执行的哪个在前执行哪个
父类的静态初始化块
子类的静态属性初始化
子类的静态初始化块
父类属性定义初始化
父类的初始化块
父类构造代码块
父类的构造函数
子类的属性定义初始化
子类的初始化块
子类构造代码块
子类的构造函数
赋值
成员变量(实例变量)和局部变量区别
(1)成员变量声明在类中方法外(随对象产生而产生,随对象销毁而销毁),与方法没有位置的先后之分(方法也叫成员方法)
(2)局部变量声明在方法内 (随方法产生而产生),方法参数相当于局部变量
(3)局部变量使用前必须初始化。
(4)局部变量存在于栈,成员变量存在与堆,所以局部变量和成员变量重名时不会报错,调用时采用就近原则,例:方法内访问的是同名的局部变量,类内访问的是同名的成员变量
(5)局部变量可以不在定义的同时被赋值,可以先定义然后再被赋值
(6)成员变量不能定义初始化完成后再重新赋值。(为什么要这么规定,因为成员变量如果在定义的时候没有初始化,后期却允许其赋值,这样会导致在对象创建的时候创建出的每一个对象的成员变量都相同),对象创建执行顺序,成员变量初始化--》构造函数--》赋值
class a{ int i; i=3;} 这是错的
this关键字
指向自身的引用,当前对象,因此static方法中不可用
1.全局变量与局部变量重名时,指向全局变量
2.成员变量,成员方法只能由对象调用,但是方法中使用成员变量,方法的时候却没有使用对象,其实这时候省略了一个 this. 表示的是当前的对象
3.在函数内部当作当前类的对象,从而调用其它方法(常调用构造方法,且必须放在第一行) 不常用
add(int arg1,int arg2){this.arg1=arg1;this.arg2=arg2}
add(int arg1,int arg2,int arg3){ this(arg1,arg2);this.arg3=arg3 } 优点:减少了公共代码的多次编写
参数少掉参数多,把多出来的直接设置值 add(int arg1,int arg2){this(arg1,arg2,1)}
访问权限
public 被所有类使用(公共类只有一个)
private 只能被定义他们的类所使用(修饰构造方法后该类不能被继承)
包访问权限(Default):未指定默认访问权限 被同一包所访问
protected同包+不同包子类
类设计技巧:
1.保证数据私有,确保封装性
2.对数据初始化
3.不要在类中过多使用基本类型,用其它类代替多个相关的基本类型的使用,比如用Address代替 street和city基本类型
4.不是所有域都需要独立的访问器和更改器
5.将职责过多的类分解
6.类名和方法名要体现出它们的职责
三大特性
封装(保证安全性)将函数方法内容进行封装,把属性隐藏private,提供方法public进行访问
防止通过对象.实例变量访问实例变量和随意修改实例变量的值,对实例变量添加绝对领域,没有其它人可以直接恶搞我的变量,强迫想要使用此实例变量的人必须通过set和get方法访问
比如一个猫的年龄为实例变量,如果不用private修饰,可以直接将它更改为0岁,这样是不合理的,所以将实例变量用private修饰,然后创建set和get方法,set方法内进行判断,如果为0岁不进行赋值,不封装的类是很可怕的,类似于:“老王忘记把他的猫封装了,然后他的猫就被碾平了”
set方法可以执行任何动作,然后直接暴漏public的实例变量不可以。
优势:虽然可能一般set中没有执行任何操作,但是以后改变想法不需要改变其它部分程序,假如说所有人都使用到你的类及共有变量,万一有一天突然发现这个变量需要检查,那不是所有人都要跟着改成set方法么,而已经设置的人,只要直接改变set里的检查内容就可以了,所以为了以后的考虑,直接存储访问的效率虽高,但是却比不上get和set方法的这个好处
访问器get开头,更改器开头set
void setTime(String time){ //setter方法有且只有一个参数
this.time=time; }
String getTime(){ //getter必须有返回值,返回你想要获得的属性
return time }
对于方法:只要对外功能不变,可以任意修改方法内部代码,一开始方法一开始有属性name, return name修改后属性为firstname和lastname ,return firstname+lastname,只是改变了实现方式,结果相同,也就是所说的重构(代码写完后找到了更好的实现方式)
继承(破坏了封装)class (子类名) extends( 父类名):
拥有父类的属性和方法,通过子类对象.父类方法()访问,不可多继承,可多层次继承
父类和子类有相同属性,子类中访问属性时默认访问的是子类的属性,若想访问父类属性,使用super.属性
子类不能直接访问父类私有属性,必须调用访问方法,super.方法 调用父类方法()
当调用子类构造方法的时候,要先调用父类构造方法
super关键字,子类构造方法第一行隐藏super()语句,若父类构造方法有参,则子类必须写super语句 super(参数),必须放在构造方法的第一个(父母先出生,然后孩子再出生)
super与this引用不一样,this标识一个对象引用,可以赋给另一个对象变量
super是一个指示编译器调用超类方法的特殊关键字,不是对象引用,不可赋值给另一对象变量
super和this都想放在第一行,所以它俩不能同时使用,
所以super和this不能共存!!
父类有参,父类无参,子类无参,子类有参,num=4
重写:子类方法覆盖父类方法,重写方法必须遵守规则,重写扩展了子类的功能。
(1)参数必须相同,返回值必须兼容(是其子类)
(2)不可使用更为严格的访问权限
Child child=new Child();
输出的是:父类构造方法 0 4 子类构造方法
为什么是0? 因为子类super()执行后才执行构造代码块,num才被赋值完成
多态:一个Person变量既可以引用Person对象,也可以引用任何一个Person子类对象
一个子类对象创建的时候,在它内部其实还有一个父类对象,这也就可以理解为什么父类引用可以指向子类对象,因为其实子类对象内部包含着父类对象
父类引用指向的是 子类对象中的父类对象
多态意味着很多形式,如果引用是一个遥控器,继承树越往下走,遥控器的按钮越来越多,一个子类的遥控器可以操纵它自己的特有方法和它父类的方法
若不用子类特有方法或属性(父类引用不能访问子类特有方法),最好使用父类引用指向子类对象 Person e=new Student()
写大程序建议用多态,若想从访问Student改成Teacher不用改方法,只改实例就可以,因为student和teacher方法类型相同,只是实现结果不同
Person e=new Teacher()
多态的作用:当你创建父类引用指向子类对象的时候,在后期你多次调用了这个子类对象中的方法,而你突然又不想指向这个子类对象了,这时你可以直接把父类引用指向另一个子类对象,后面的调用都可以不用改,这就可以让你在后期维护的时候少更改代码
动态绑定也就是多态
动态绑定也就是说java是运用的后期绑定机制,在运行的时候父类引用与子类对象就进行了绑定,确定了这个父类引用指向了哪一个
注:1.属性在编译时确认,因此不能调用子类属性
2.可调用子类重写的方法,特有方法不能调用
方法执行过程:1.调用x.f(param),编译器列举出所有同名的方法,参数可不同作为候选
3..进行重载解析,也就是找到符合调用时参数的方法
4..调用方法时,先去子类中找其方法,若子类中没有再去父类中找这个方法
由于每次都要搜索开销大,虚拟机实现为每个类创建方法表
注意:(类的属性不存在多态只有方法存在多态)
1:当父类和子类具有相同的非静态成员变量,那么在多态下访问的是父类的成员变量
2:当父类和子类具有相同的静态成员变量,那么在多态下访问的是父类的静态成员变量
所以:父类和子类有相同的成员变量,多态下访问的是父类的成员变量。
3:当父类和子类具有相同的非静态方法(就是子类重写父类方法),多态下访问的是子类的成员方法。
4:当父类和子类具有相同的静态方法(就是子类重写父类静态方法),多态下访问的是父类的静态方法。
出现的问题,多态时使用父类引用指向子类对象时,想访问属性访问的是子类属性时,却访问到了父类,解决办法:因为方法默认访问的是子类方法,所以将属性私有化,通过方法进行访问属性就可以访问到子类属性。
非静态
1:编译时期,参考引用型变量所属的类是否有调用的方法,如果有编译通过。没有编译失败
2:运行时期,参考对象所属类中是否有调用的方法。
3:总之成员函数在多态调用时,编译看左边,运行看右边。
在多态中,成员变量的特点,无论编译和运行参考左边(引用型变量所属的类)。
在多态中,静态成员函数特点,无论编译和运行都参考右边
上溯造型 Animals a=new Dog
下溯造型:下溯造型必须用instanceof判断是否同类型,若想访问Dog特有属性需要强转
Dog d=(Dog)a,尽量少用强制转换,多考虑父类是否设计合理,是否缺少方法
造型应用,加功能时不用修改主结构,比如获取信息,只写一个方法就可以
public void f(Animals a){ System.out.println(name);}
注:传参时可以把类当参数放在构造函数或定义属性,最好用父类,提高可扩展性,运用动态绑定访问子类方法
数组中的对象如同其它对象一样,唯一差别是如何取得
继承设计的技巧
1.将公共操作和域放在超类,将具有的公共特征和行为放在父类中,特殊的行为放在子类中
2.域尽量用private修饰,少用protected修饰
3.使用继承实现”is-a“关系比如:三角形是一个图形
4.除非所有继承的方法都有意义,否则不要使用继承(父类方法子类都用到使用继承)
5.在覆盖方法时,不要改变预期行为
6.尽量使用多态