面向对象
要理解面向对象思想,我们先要知道什么是对象?
《Java编程思想》中提到“万物皆为对象”的概念。它将对象视为一种奇特的变量,它除了可以存储数据之外还可以对它自身进行操作。它能够直接反映现实生活中的事物,例如人、车、小鸟等,将其表示为程序中的对象。每个对象都具有各自的状态特征(也可以称为属性)及行为特征(方法),java就是通过对象之间行为的交互来解决问题的。
面向对象就是把构成问题的事物分解成一个个对象,建立对象不是为了实现一个步骤,而是为了描述某个事物在解决问题中的行为。
类是面向对象中的一个很重要的概念,因为类是很多个具有相同属性和行为特征的对象所抽象出来的,对象是类的一个实例。
类具有三个特性:封装、继承和多态。
三大特征
封装:核心思想就是“隐藏细节”、“数据安全”,将对象不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。所有的内容对外部不可见。
继承:子类可以继承父类的属性和方法,并对其进行拓展。将其他的功能继承下来继续发展 。
多态:同一种类型的对象执行同一个方法时可以表现出不同的行为特征。通过继承的上下转型、接口的回调以及方法的重写和重载可以实现多态。方法的重载本身就是一个多态性的体现。
三大思想
面向对象思想从概念上讲分为以下三种:OOA、OOD、OOP
OOA:面向对象分析(Object Oriented Analysis)
OOD:面向对象设计(Object Oriented Design)
OOP:面向对象程序(Object Oriented Programming )
类与对象
类表示一个共性的产物,是一个综合的特征,而对象,是一个个性的产物,是一个个体的特征。 (类似生活中的图纸与实物的概念。)
类必须通过对象才可以使用,对象的所有操作都在类中定义。
类由属性和方法组成:
属性:就相当于人的一个个的特征
方法:就相当于人的一个个的行为,例如:说话、吃饭、唱歌、睡觉
一个类要想真正的进行操作,则必须依靠对象,对象的定义格式如下:
类名称 对象名称 = new 类名称() ;
如果要想访问类中的属性或方法(方法的定义),则可以依靠以下的语法形式:
访问类中的属性: 对象.属性 ;
调用类中的方法: 对象.方法(实际参数列表) ;
类必须编写在.java文件中;
一个.java文件中,可以存在N个类,但是只能存在一个public修饰的类;
.java文件的文件名必须与public修饰的类名完全一直;
同一个包中不能有重名的类;
匿名对象
没有对象名称的对象就是匿名对象。 即栈内存中没有名字,而堆内存中有对象。
匿名对象只能使用一次,因为没有任何的对象引用,所以将称为垃圾,等待被GC回收。
只使用一次的对象可以通过匿名对象的方式完成,这一点在以后的开发中将经常使用到。
public static void main(String[] args){
//Math2 m=new Math2();
//int num=m.sum(100,200);
//不通过创建对象名,直接实例对象调用,这就是匿名对象。因为没有对象名指向对象,所以只能调用一次,然后被GC回收。
int num = new Math().sum(100,200);
System.out.println(num);
}
class Math2{
int sum(int x,int y){
return x+y;
}
}
重写
当子类需要父类的功能,而子类有新的内容,可以重写父类中的方法。在实际开发过程中,随着代码量的逐渐增加,维护成了一个很大的问题,如果需要对某个方法进行修改,其本身代码以及其子类代码都会受到影响,而重写则很好的解决了这个问题。方法重写又称为方法覆盖、方法复写。
方法签名 = 方法名 + 参数(顺序+类型+个数)
当父类和子类的方法签名一致时,我们认为子类重写了父类的方法
方法重写特点
在子类和父类中,出现了方法声明相同的情况
子类的方法声明要和父类相同
子类要重写的方法,方法的权限修饰符不能比父类更低(public 、protected 、default 、private 权限依次增加)
父类私有的方法,子类不能进行方法重写
方法重写和方法重载的区别
方法重写:子类和父类中方法相同,两个类之间的关系,函数的返回值类型、函数名、参数列表都一样,当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法
重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
构造方法无法被重写
方法重载:指在同一个类中,多个方法名相同,他们的参数列表不同(个数不同,数据类型不同),同样的一个方法能够根据输入数据的不同,做出不同的处理
区别点 重载方法 重写方法
发生范围 同一个类 子类
参数列表 必须修改 一定不能修改
返回类型 可修改 子类方法返回值类型应比父类方法返回值类型更小或相等
异常 可修改 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
访问修饰符 可修改 一定不能做更严格的限制(可以降低限制)
发生阶段 编译期 运行期
方法的重写要遵循“两同两小一大”
“两同”即方法名相同、形参列表相同;
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
关于 重写的返回值类型 这里需要额外多说明一下,
重载
当一个类具有两个或多个具有相同名称但参数不同的方法时,在基于传递的参数进行调用时,将调用相应的方法(或相应的方法主体将与调用行动态绑定)。这种机制称为方法重载。
方法重载应遵循的规则
两种方法应在同一类中。
方法的名称应相同,但它们应具有不同的数量或参数类型。
如果名称不同,则它们将成为不同的方法,并且如果它们具有相同的名称和参数,则将引发编译时错误,提示“方法已定义”。
但是他们可以有不同的返回类型,修饰符,抛出不同的异常
子类与父类构造方法总结
super关键字
super,只能指代父类对象
super(x)或者super(),指代父类的构造方法,只能放在首行
如何继承父类构造器
某种意义上可以说继承。
子类的构造器必须在第一行调用父类的构造(super),如果无默认的无参构造器,则必须显示调用super指定继承的构造器。
注意:
子类必须通过super关键字调用父类有参数的构造函数
使用super调用父类构造器的语句必须是子类构造器的第一条语句
如果子类构造器没有显式地调用父类的构造器,则将自动调用父类的默认(没有参数)的构造器。如果父类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用父类的构造器,则java编译器将报告错误
情况1:父类有 无参构造方法时
子类可以无参构造方法也可以没有无参构造方法,若无无参调用方法则需要显示的调用super(参数);
情况2:父类中无 无参构造方法时
子类一般不允许有无參构造方法,因为无參构造方法,构造时没有参数传入,而父类中没有无參构造方法,子类中只能显示调用super(参数),但又不存在参数,因此不允许有无參构造方法,除非默认指定的参数。
构造器能不能被重写
构造器就是构造方法,能够被重载(同类中不同参数列表的构造器),不能够被重写(子类使用super方法可以调用)。
构造器根被没有被继承,只是能够调用,所以不能重写。
实例化过程
实例初始化就是执行<init>()方法
<init>()方法可能重载有多个,有几个构造器就有几个<init>方法
<init>()方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成
非静态实例变量显示赋值代码和非静态代码块代码从上到下顺序执行,而对应构造器的代码最后执行
每次创建实例对象,调用对应构造器,执行的就是应对的<init>方法
<init>方法的首行是super()或super(实参列表),即对应父类的<init>方法
调用子类无参构造器
S s1 = new S();
System.out.println(s1.name);
父类的 静态代码块begin
父类的 静态代码块end
子类的 静态代码块begin
子类的 静态代码块begin
父类的 代码块begin
父类的 代码块end
父类的无参数构造方法
子类的 代码块begin
子类的 代码块end
子类的无参数构造方法
null
可以看到,先初始化类,再实例化类。
初始化类,先初始父类,再初始子类(调用静态代码块和静态变量,按照代码中声明的顺序)
实例化类,先实例化父类,再实例化子类。
实例化父类,如果没有显式调用父类构造器,默认调用父类的无参构造器(此处没有显式调用,但是默认隐式调用)
调用构造器前,先执行实例变量和代码块,按照代码中声明的顺序,再执行构造器!
实例化子类,就是先执行实例变量和代码块,按照代码中声明的顺序,再执行构造器
调用子类有参构造器,显式调用父类构造器
S s2 = new S("dddd");
System.out.println(s2.name);
父类的 代码块begin
父类的 代码块end
父类的有参数构造方法str
子类的 代码块begin
子类的 代码块end
子类的有参数构造方法str
dddd
可以看到显式调用父类构造器时,确实调用了父类的有参数构造方法
注意:显式调用父类构造器时,必须把super.xxx写在子类构造器的第一行
调用子类有参构造器,无显示调用父类构造器
S s3 = new S(123);
System.out.println(s3.name);
父类的 代码块begin
父类的 代码块end
父类的无参数构造方法
子类的 代码块begin
子类的 代码块end
子类的有参数构造方法int
null
可以看到如果没有显式调用父类构造器,默认调用父类的无参构造器,即使子类调用有参构造器!!!
调用子类有参构造器,显示调用父类其他有参构造器
S s4 = new S(123L);
System.out.println(s4.name);
内部方法
public S(long str){
super((int)str);
System.out.println("子类的有参数构造方法l
ong");
}
父类的 代码块begin
父类的 代码块end
父类的有参数构造方法int
子类的 代码块begin
子类的 代码块end
子类的有参数构造方法long
123
可以看到如果显式调用父类构造器,就会调用该构造器,即使子类构造器的参数是其他类型!!!
如果父类没有无参构造器
/*public A(){
System.out.println("父类的无参数构造方法");
}*/
将父类无参构造方法注释了,只剩下public A(String str)
调用S s1 = new S();
public S(){
System.out.println("子类的无参数构造方法");
}
会报错
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
Implicit super constructor A() is undefined. Must explicitly invoke another constructor
Implicit super constructor A() is undefined. Must explicitly invoke another constructor at test.t05new.S.<init>(S.java:14)
at test.t05new.S.main(S.java:41)
父类的 静态代码块begin
父类的 静态代码块end
子类的 静态代码块begin
子类的 静态代码块begin
表明子类构造函数内必须调用父类的构造函数,可以是显示的,可以是隐式的(调用无参父类),但如果父类没有无参构造函数,必须显示调用父类构造函数
哪些方法不可以被重写
final方法
静态方法
private等子类中不可见方法
对象的多态性
子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
非静态方法默认的调用对象是this
this对象在构造器或者说<init>方法中就是正在创建的对象
父类的实例化过程里,如果执行了子类重写的方法,那么父类里执行的是子类的方法
构造方法总结
创建一个子类,基本流程是
初始化父类(执行父类静态代码块)> 初始化子类(执行子类静态代码块)>实例化父类(执行父类代码块>隐式,调用无参父类构造器/显式,调用对应父类构造器,注意子类重写方法)>实例化子类(执行子类代码块>执行剩余子类构造函数)
子类调用父类成员、方法
继承的特殊说明
这里说道继承是拥有父类的全部属性和方法,但我们发现父类的私有属性,子类是不可以使用的。这违背了继承的定义吗,拥有父类的全部属性与行为。答案是没有。用数学去描述子类与父类的话,应该是子类大于等于父类。
在一个子类被创建时,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者结合形成子类的对象。所以父类的全部属性和行为子类会拥有,但是对于父类对象中的私有属性和方法,子类是无法访问到的,只是拥有,但也仅仅是拥有(I like you, But just like you!)。这也是关键字private,protected,public的意义所在。
子类可以调用父类的其他方法,访问父类的私有属性。
子类继承是继承父类的所有东西,除了构造函数
调用范围
1、能够访问标为public protected的成员变量和方法;
2、如果子类与父类在同一包内,还能访问默认(无修饰符)的成员变量与方法。
3、不能访问标为private的成员。