面向对象
《疯狂Java讲义》学习笔记
构造器
-
修饰符:
public
protected
private
的其中一个修饰符 -
构造器名:必须和类名相同
注 :构造器既不能定义返回值类型,也不能使用void
声明构造器没有返回值。如果给构造器定义返回值类型,或者使用void
声明构造器没有返回值,编译不会报错。但是 Java 会把这个所谓的构造器当成方法来处理。 -
构造器返回值:返回该类的实例
-
如果没有定义构造器,系统会提供一个默认的构造器
-
static
修饰的变量或者方法即可以使用类名.变量名 | 方法名
调用,也可以使用对象.变量名 | 方法名
Person p = new Person();
- 对象放在堆内存中,变量p放在栈内存中
- 对象的
this
引用:this
关键字总是指向调用该方法的对象 this
作为对象的默认引用有两种情形
① 构造器中引用该构造器正在初始化的对象
② 在方法中调用该方法的对象- 静态成员不能访问非静态成员
- 可以把
this
当做返回值:如果在某种方法中把this
作为返回值,则可以连续多次调用该方法
public class TestThis {
int count;
public TestThis calculate() {
count++;
return this;
}
public static void main(String[] args) {
TestThis testThis = new TestThis();
//连续3次调用calculate()方法
testThis.calculate().calculate().calculate();
//输出count的值
System.out.println("count = " + testThis.count);//3
}
}
方法
- 方法的参数传递机制
- 值传递:将实际参数的副本(复制品)传入方法内,参数本身不会受到影响
- 形参个数可变的方法(JDK1.5之后):在最后一个参数的类型后面加三个点(…),多个参数值当成数组
- 方法的重载:方法名相同,参数列表不同
- 方法重载的要求:两同一不同,同一个类,方法名相同,参数列表不同
- 至于其他部分,方法的返回值类型,修饰符等,与方法的重载没有任何关系
成员变量和局部变量
- 成员变量分为类变量(
static
修饰)和实例变量 - 如果通过
对象 . 类变量
修改类变量的值,那么导致该类的所有实例访问改类变量都会得到修改之后的值 - 成员变量无需显示初始化,系统会进行默认初始化
- 局部变量分为:
形参
,方法内局部变量
,代码块局部变量
- 局部变量除了形参之外都需要显示进行初始化
- Java允许局部变量和成员变量同名,局部变量会覆盖成员变量
封装
private | default | protected | public | |
---|---|---|---|---|
同一个类中 | √ | √ | √ | √ |
同一个包中 | √ | √ | √ | |
子类中 | √ | √ | ||
全局范围内 | √ |
注 :对于外部类而言,只有 public
和 default
两种访问控制级别
package
import
和 import static
package
位于Java源程序第一行非注释行,一旦源文件使用了package
,那么这个文件的所有类都属于这个包import package.subpackage …*; (*)
星号只能代表类,不能代表包。package.subpackage
的子包中的类不会被导入import static
静态导入:JDK1.5以后增加了静态导入语法,用于导入指定类的某个静态成员变量、方法、或全部的静态成员变量、方法- 静态导入的两种语法:
//导入指定类的单个静态成员变量、方法
//导入ClassName类中字段名为fieldName变量,或者方法名为methodName的方法
import static 包名.子包名.类名.字段名 | 方法名;
//导入指定类的全部静态成员变量、方法:星号只能代表静态变量和方法名
import static 包名.子包名.类名.*;
构造器重载
使用 this
调用另一个构造器:使用 this
调用另一个构造器时只能在构造器中使用,而且必须位于构造器执行体的第一行代码
封装
对于外部类而言,只有 public
和 default
两种访问控制级别
类的继承
-
Java的子类不能获得父类的构造器
-
重写父类的方法:子类包含父类同名方法的现象被称为方法的重写
-
方法重写要遵循:
两同两小一大的原则
两同
:方法名相同,形参列表相同
两小
:- 子类的返回值类型比父类的返回值类型更小或者相等
- 子类方法抛出的异常应该比父类抛出的异常更小或者相等
一大
:子类的访问权限应该比父类的访问权限更大或者相等 -
覆盖方法和被覆盖的方法要么都是类方法,要么都是实例方法
-
子类覆盖父类的方法之后,子类就无法访问父类被覆盖的方法,但是可以在子类中使用
super
(被覆盖的方法是实例方法)或者父类类名
调用(被覆盖的方法是类方法)来调用父类被覆盖的方法
注 :如果父类的某个方法为 private
修饰,则该方法对于子类是隐藏的,子类就不能覆盖该方法,如果子类定义了一个与父类具有相同方法名,相同的形参列表,相同的返回值类型的方法,那么依然不是重写,而是在子类中重新定义了一个新的方法
super限定
- 如果需要在子类中调用父类被覆盖的实例方法,可以使用
super
限定来调super
也不能出现在static
修饰的方法中 - 当程序创建一个子类对象时,系统不仅会为该类定义的实例变量分配内存空间,也会为它从父类继承的所有实例变量分配内存空间,即使子类定义了与父类同名的实例变量
调用父类构造器
-
子类使用
super
构造器调用父类构造器必须出现在子类构造器的第一行
所以this
调用和super
调用不会同时出现 -
不管是否使用
super
调用父类构造器,子类总会调用一次父类构造器
① 子类构造器使用super
显示调用父类构造器,体统会根据super
传入的参数调用父类对应的构造器
② 子类构造器使用this
调用本类构造器,另一个构造器会调用父类对应的构造器
③ 子类既没有使用super
显示调用父类构造器,也没有使用this
调用本类构造器,则隐式调用父类无参构造器 -
不管哪种情况,调用子类构造器初始化子类对象时,父类构造器总会在子类构造器之前执行。
-
执行父类构造器时,系统会再次上溯执行其父类构造器,创建任何对象时,总是最先执行
java.lang.Object
类的构造器
多态
- Java引用变量有两个类型:
① 编译时类型:由声明该变量时使用的类型决定
② 运行时类型:由实际赋给该变量的对象决定 - 多态:编译时类型和运行时类型不一样
- 向上转型:把一个子类对象直接赋值给父类对象时,无需任何转换或被称为向上转型,向上转型由系统自动完成
- 当编译时类型是父类类型时,运行时类型为子类类型时,当运行时调用该引用变量的方法时:方法行为总是表现为子类的行为特征,
- 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
- 相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态
- 与方法不同的是:对象的实例变量不具备多态性,程序访问的依然是父类的实例变量
- 引用类型变量在编译阶段只能调用其编译类型所具有的方法,在运行时则执行它运行时所具有的方法。通过引用变量访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的变量
引用变量的强制转换
- 基本类型的转换只能在数值类型之间转换:包括整型、浮点型、
char
(字符型);数值和Boolean
类型之间不能转换 - 引用类型之间转换只能在具有继承关系的两个类型之间进行转换;如果没有继承关系,强制转换则发生编译报错
instanceof
:判断两个类型是否可以进行类型转换instanceof
运算符的前一个操作符一般是引用类型的变量,后一个操作符是一个类,也可以是一个借口,用于判断前面的对象是否是后面的类,或是其子类、实现类的实例- 使用
instanceof
运算符时需要注意:instanceof
前面操作数的编译时类型要么与要么与后面类型相同,要么与后面的类具有父子类的继承关系,否则会编译出错
继承和组合
继承会破坏封装
- 为了保证父类具有良好的封装性,不被子类随意改变,父类的设计尽量遵循如下规则:
① 尽量隐藏父类的内部数据,不要让子类直接访问父类的成员变量。
② 不要让子类可以随意访问、修改父类的方法。如果父类中的某些仅为辅助其他的工具方法,应该使用private
修饰。 - 如果父类的某些方法需要被外部调用,但又不希望子类重写该方法,可以使用
final
修饰。
① 如果希望被子类重写,不希望被其他类自由访问,可以使用protected修饰
② 尽量不要在父类构造器中调用将要被子类重写的方法
利用组合实现复用
- 复用一个类的两种方法:
继承
:子类继承父类,子类可以直接获得父类的public
方法,程序使用子类时,可以直接访问子类从父类那里继承到的方法。
组合
:把旧类对象作为新类的成员变量组合进来,用以实现新类的功能。
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
Bird b = new Bird(a);
b.breath();
b.fly();
}
}
// 要复用的类
class Animal {
private void beat() {
System.out.println("心脏跳动...");
}
public void breath() {
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
}
class Bird {
// 把要复用的类组合到当前类中
private Animal a;
public Bird(Animal a) {
this.a = a;
}
// 由于当前类不能直接调用Animal的breath()方法
// 所以要重新定义一个自己的breath()方法
public void breath() {
// 通过Animal的对象调用Animal的breath()方法
a.breath();
}
public void fly() {
System.out.println("飞...");
}
}
class Wolf {
private Animal a;
public Wolf(Animal a) {
this.a = a;
}
public void breath() {
a.breath();
}
public void run() {
System.out.println("我在地上跑...");
}
}
到底该用继承,还是组合:如果两个类有明确的整体、部分,如 Person
类需要 Arm
对象的方法( Person
对象由 Arm
对象组合),此时需要使用组合来完成复用
继承是用来表达( is-a
)的关系,组合是( has-a
)的关系
初始化块:(初始化块是Java类中可以出先的第四种成员)
[修饰符] {
//修饰符只能是 static 或者没有,使用 static 修饰的初始化块成为静态初始化块。
//初始化块里的点吗可以包含任何可执行的语句:
包括定义局部变量、调用其他对象的方法、使用分支、循环语句
}
- 初始化块总是先于构造器执行之前执行,先定义的初始化块先执行,后定义的初始化块后执行
- 初始化块没有标识符,无法通过类、对象调用初始化块
- Java创建对象的过程(前提是该类已经被加载进JVM中):
① 先为该对象的所有实例变量分配内存,
② 然后对这些变量进行初始化:
a . 先执行初始化代码块或声明实例变量时指定的初始值(顺序与他们在源代码中的位置相同)
b . 在执行构造器中指定的初始值 - 静态初始化块:使用
static
修饰,也被称为类初始化块,通常用于对整个类进行初始化操作,先于普通初始化代码块执行 - 系统执行静态代码块时会上溯到
java.lang.Object
类,先执行java.lang.Object
的静态初始化块,在执行其父类的静态初始化块….最后在执行该类的静态初始化块