一、封装
封装的定义:隐藏对象的具体实现细节,通过公有方法暴露对象的功能。
(比如在setter方法中加值合法判断)
运用场景:直接通过对象.属性的方式给对象的属性赋值时,有可能会出现不满足实际情况(自然规律或特定条件)的意外。那么不通过直接给属性赋值来完成,而是通过方法来完成,将特定的判断条件体现在方法里,以达到满足实际情况的要求。
解决不能直接作用于属性的办法:封装——通过private关键字来实现
提高安全性,提高代码的复用性。可以隐藏内部细节,对外提供公共访问方式。
this关键字:
1.可以用来修饰属性、方法、构造器
2.this理解为当前的对象(或当前正在创建的对象)
比如this.name,this.eat(),表示当前对象的name,当前对象的eat()
3.this可以用来调用构造器,通过“this(形参列表)”显式地调用本类中其他的构造器。
注意:
1)在构造器中通过this来调用其他构造器,则该this必须声明在首行;
2)如果一个类中有n个构造器,则最多只能有n-1个this()构造器调用语句。
Static与this不能用时出现,Static为静态的,在类的初始化时定义的静态代码块,静态属性(类变量),常量等。this为自身本对象的一个引用,为具体实例的引用。
二、继承
子类(次类/派生类/扩展类):
父类(超类/基类):
继承的定义:使用已经存在的类作为基础类(父类)在此基础上建立新类(子类)。
运用场景:当父类的方法不能完全满足子类使用的时候,既可以保留父类的功能(沿袭、传承),还可以有自己特有的功能。
继承的优点:子类拥有父类的所有属性和方法(除了private修饰的属性不能拥有)从而实现了实现代码的复用。
super关键字:
super关键字指代父类,可以用于调用父类中的普通方法和构造方法,前面介绍了关键字this的作用,它是对 调用对象的引用。关键字super是指这个super关键字所在的类的父类。.
1、访问父类的属性:super.age
2、访问父类的方法:super.eat()
3.访问父类的构造方法:super(形参列表);//必须是构造方法的第一题语句,参数列表与父类构造方法参数列表统一。
构造方法链
在任何情况下,构造一个类的实例时,将会调用沿着继承链的所有父类的构造方法。当构造一个子类对象时,子类构造方法会在完成自己任务之前,首先调用它的父类构造方法。如果父类继承自其他类,那么父类构造方法又会在完成自己任务前调用它自己的父类的构造方法。这个过程持续到沿着这个继承体系结构的最后一个构造方法被调用为止。如果没有被显式的调用,编译器将会自动添加super()作为构造方法的第一条语句。
关于继承应该注意的几个关键点:
- 和传统的理解不同,子类并不是父类的一个子集。实际上,一个子类比它的父类包含更多的信息和方法。
- 父类中的私有属性在该类之外是不可访问的。因此,不能在子类中直接使用。但是,如果父类中定义了公共的setter/getter,那么可以通过这些公共的setter/getter来修改和访问它们
- 继承是用来为is-a关系建模的。不要仅仅为了重用方法而盲目扩展一个类。例如:尽管Person类和Tree类可以共享类似高度和重量这样的通用特性,但是从Person类扩展出Tree类是毫无意义的。一个父类和它的子类之间必须存在“是一种”(is-a)关系
- 不是所有的is-a关系都该用继承来建模。例如:正方形是一种矩形,但是不应该定义一个Square类来扩展Rectangle类,因为width和height属性并不适合于正方形。
- java是不允许多重继承的。一个java类只能直接继承自一个父类。多重继承可以通过接口来实现。
方法重写:
前提:有子类继承父类
运用场景:
当子类继承了父类的方法后发现父类的方法不适用于子类,则子类可以通过重写父类的方法。
在调用时,调用的是子类重写后的方法,而不是来自于父类的方法。
Object类及其toString()方法
java中的所有类都继承自java.lang.Object类
toString()方法:
1)当我们打印一个引用数据类型的对象时,默认调用的是这个对象的toString();//Loan@15037e5
2)当我们重写了toString()后再打印该对象,会调用重写后的toString();
3)像String、File、Date等类已经重写了toString方法。
首先需要明确一点,equlas()
方法是Object
类的成员方法。
假设有一个Person类,我们要重写它的equals()
方法,自己重写的代码如下所示:
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
// 增加一个判断,传递的参数obj如果是this本身,直接返回true,提高程序效率
if (obj == this) {
return true;
}
// 增加一个判断,传递的参数obj如果是null,直接返回false,提高程序效率
if (obj == null) {
return false;
}
// 增加一个判断,防止类型转换异常
if (obj instanceof Person) {
// 使用向下转型,把obj转换为Person类型
Person p = (Person) obj;
// 比较两个对象的属性,一个对象是this(person1),一个对象是p(obj->person2)
return this.name.equals(p.name) && this.age == p.age;
}
// 不是Person类型,直接返回false
return false;
}
}
重写的规则:
1)要求子类与父类的方法:返回值类型、方法名、形参列表必须完全相同。
2)返回值类型:一般具有相同的返回值类型,子类重写的方法返回值类型不能大于父类的返回值类型
3)子类重写的方法的访问修饰符权限不能小于父类的被重写的方法的修饰符权限
4)若父类方法抛异常,子类重写的方法不能抛出比父类更大类型的异常
5)仅当实例方法可访问时,才能进行方法重写
6)静态方法也能被继承,静态方法不能被覆盖。
7)子类可以重写父类的静态方法,但是必须是同为静态的(对父类静态方法来说是隐藏,可以使用父类.静态方法的形式访问)
三、多态
多态的定义:多态体现为父类引用变量可以指向子类对象。
多态是同一个行为具有多个不同表现形式或形态的能力。
前提条件:必须有子父类关系。
定义格式:父类类型 变量名=new 子类类型();
运用场景:一个数组存在几种子类同属于一个父类,但是每个子类的同一方法实现细节不一样。
多态的优点:
动态绑定:
在程序运行过程中,根据具体的实例对象才能具体确定是哪个方法。动态绑定是多态性得以实现的重要因素,它通过方法表来实现:每个类被加载到虚拟机时,在方法区保存元数据,其中,包括一个叫做方法表(methodtable)的东西,表中记录了这个类定义的方法的指针,每个表项指向一个具体的方法代码。如果这个类重写了父类中的某个方法,则对应表项指向新的代码实现处。从父类继承来的方法位于子类定义的方法的前面。
pet调用哪个showMe()方法由pet的实际类型决定。这称为动态绑定。
我们知道,向上转型时,用父类引用执行子类对象,并可以用父类引用调用子类中重写了的同名方法。但是不能调用子类中新增的方法。
pet.play()调用的是Dog中的play(),这里就是动态绑定机制的真正体现。
动态绑定和静态绑定
程序在JVM运行过程中,会把类的类型信息、static属性和方法、final常量等元数据加载到方法区,这些在类被加载时就已经知道,不需对象的创建就能访问的,就是静态绑定的内容;需要等对象创建出来,使用时根据堆中的实例对象的类型才进行取用的就是动态绑定的内容。
编译器在每次调用方法时都要进行搜索,时间开销相当大。因此虚拟机会预先为每个类创建一个方发表(method table),其中列出了所有方法的签名和实际调用的方法。
对象转换和instanceof运算符
将一个类型强制转换为另外一个类型的过程称为类型转换,语法格式为:
Pet pet = new Penguin();
Penguin penguin = (Penguin)pet;
在java中,每个对象变量都属于一个类型。将一个值存入变量时,编译器将检查是否允许该操作。将一个子类的引用赋给超类类型(向上转型),编译器是允许的。但将一个超类的引用赋给一个子类变量(向下转型),必须进行类型转换。
如果视图在继承链上进行向下的类型转换,并且谎报有关对象包含的内容,会发生什么情况呢?
Pet pet = new Penguin(); Dog dog = (Dog)pet;
java运行时系统将报告这个错误,产生一个ClassCastException异常。如果没有捕获这个异常,程序就会终止。因此,应该养成一个良好的程序设计习惯,在进行类型转换之前,先查看一下是否能够成功地转换。使用instanceof运算符就可以实现