面向对象三大特性简介
前言
面向对象三大特性简介,继承、封装、多态。
一、继承
1、继承的主要作用
1)代码复用,更加容易实现类的扩展,类之间又有很多共性,避免写更多冗余的代码。
2)方便对现实世界建模,因为现实世界大多数都是继承关系。
2、继承的实现
用extends来实现,class A extends B,A 扩展了 B。
public class Person {
String name;
Person friend;
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
p1.friend = p2;
p2.friend = p1;
p1 = p2 = null;
}
}
class Teacher extends Person{
//Teacher除了有name和朋友,还有所属学校
//String name;
//Person friend;
String universityName;
}
3、继承使用要点
1)为了避免多继承造成复制的继承链,系统难于维护,Java中只能单继承。
2)Java类中没有多继承,但接口有多继承。
3)子类继承父类,能得到父类除构造器的成员变量和成员方法,但不见得可以直接访问(比如父类的私有属性和方法)
4)如果一个Java类没有显示继承,则会隐式继承java.lang.Object类。
5)可通过instanceof来判断类的类型。
p1 instanceof Person
,结果为Boolean变量
4、方法的重写Override
子类重写父类的方法,可以通过自身的行为替代父类的行为。方法的重写是实现多态的必要条件。
三特点
1)“==”,方法名、形参列表相同。
2)“<=”,子类异常要是父类异常的子类或者本异常。这跟动态绑定有关。
3)">=",访问权限,子类大于父类。
之类重写的方法前面给的修饰权限要大于等于父类。public > protected>default>private.
注:属性没有这一说法,子类写了一个同名属性,没有这种权限关系,调属性时,先调子类属性,没有再根据权限来调父类属性。如果是父类对象调同名属性,那只能调父类的属性。没有所谓的属性运行时多态。
public class Person {
String name;
Person friend;
public void sleep(){
System.out.println("睡觉");
}
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
p1.friend = p2;
p2.friend = p1;
p1 = p2 = null;
}
}
class Teacher extends Person{
//Teacher除了有name和朋友,还有所属学校
String universityName;
//不一样的睡觉
@Override
public void sleep(){
System.out.println("去办公室");
super.sleep();
}
}
5、Final关键字
1)FInal修饰变量,该变量变为常量,一经复制,就不能再修改。
2)Final修饰方法,该方法不能被重写,但是可以重载。
3)Final修饰类,该类不能被扩展,即继承,如String、Math类。
final class Teacher extends Person{
//Teacher除了有name和朋友,还有所属学校
String universityName;
//不一样的睡觉
@Override
public void sleep(){
System.out.println("去办公室");
super.sleep();
}
final int MOD = 4;
public final int mod(int n){
return n % MOD;
}
}
6、继承和组合
组合)将父类对象作为子类的属性,通过这个父类属性来调用父类的属性和方法,更加灵活。
class Teacher {
String universityName;
Person p;
//不一样的睡觉
final int MOD = 4;
public final int mod(int n){
return n % MOD;
}
public static void main(String[] args) {
Teacher t = new Teacher();
t.p = new Person();
t.p.sleep();
}
}
总结)组合虽然灵活,但也要分情况,为了更好的建模,当属于is-a的时候,就使用继承;当属于has a 的时候,就使用组合。
如Teacher is a Person更加恰当,所以用继承,能更好的建模。
如Computer has a Chip,此时使用组合更恰当。因为电脑虽然要用芯片里的方法,但是继承它不太合理,逻辑混乱。
二、Object类
1、Object类基本特性
Object类是所有类的根基类,所有Java类的根基类都是Object类,继承了Object类的属性和方法,但因权限问题,不一定能全部访问。
2、toString方法
JDK Object类的源码及注释,大概意思就是返回类名+@+对象地址
/**
* Returns a string representation of the object. In general, the
* {@code toString} method returns a string that
* "textually represents" this object. The result should
* be a concise but informative representation that is easy for a
* person to read.
* It is recommended that all subclasses override this method.
* <p>
* The {@code toString} method for class {@code Object}
* returns a string consisting of the name of the class of which the
* object is an instance, the at-sign character `{@code @}', and
* the unsigned hexadecimal representation of the hash code of the
* object. In other words, this method returns a string equal to the
* value of:
* <blockquote>
* <pre>
* getClass().getName() + '@' + Integer.toHexString(hashCode())
* </pre></blockquote>
*
* @return a string representation of the object.
*/
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
可在类中重写toString方法,具有自己的独特性,而不是只有地址。
public class Person {
String name;
Person friend;
public void sleep(){
System.out.println("睡觉");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", friend=" + friend +
'}';
}
public static void main(String[] args) {
Person p1 = new Person();
//输出p1,会自动调p1.toString()
System.out.println(p1);
}
}
3、== 和equals方法
public boolean equals(Object obj) {
return (this == obj);
}
在Object中,equals的原理就是==,它比较的是两个对象的地址,而如果两个对象的属性相等时,我们认为它俩相等,此时需重写equals方法。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return name.equals(person.name) &&
friend.equals(person.friend);
}
@Override
public int hashCode() {
return Objects.hash(name, friend);
}
name去比较的时候也用了equals,String的equals也是重写了的,注释及源码如下。
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* <p>For finer-grained String comparison, refer to
* {@link java.text.Collator}.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
4、 super关键字
可以看做直接父类的引用。
构造函数)在子类的构造函数中,如果没有显示的调用super(…),会隐式调用super()
public Person(){
System.out.println("调用Person的构造器");
}
package com.xhu.java;
public class Teacher extends Person {
String universityName;
Person p;
//不一样的睡觉
public Teacher(){
System.out.println("调用Teacher构造器");
}
final int MOD = 4;
public final int mod(int n){
return n % MOD;
}
public static void main(String[] args) {
System.out.println("准备new一个对象");
Teacher t = new Teacher();
}
}
5、继承树的追溯
这是继承树的追溯过程。
先构建父类,然后父类执行构造函数,赋值操作,再构建子类对象,然后赋值操。
三、封装
合理的封装让外部调用方便,也便于自己修改代码。
1、封装的作用
“高内聚、低耦合”,高内聚:内部数据操作细节完成,不允许外部干涉;低耦合:只暴露少量方法给外部使用,方便外部调用。
封装的优点:
1)代码的安全性
2)代码的复用性
3)高内聚:封装细节,便于修改内部代码,从而提高可维护性。
4)低耦合:简化外部调用,便于调用者使用,从而便于协作。
2、封装的实现-使用访问控制符
对于protected,若子类和父类在同一包中,子类可以访问父类的protected成员,也可以访问父类任意对象的protected成员;若子类和父类不在同一包中,子类只可以访问父类的protected成员,不能访问任意父类对象的protected成员。
class Person {
protected String name;
}
下面的访问属于访问父类的projected的成员:
public class Teacher extends Person {
String universityName;
public void testProtected(){
System.out.println(super.name);
}
}
下面的访问属于访问父类任意对象的projected的成员:
public class Teacher extends Person {
String universityName;
public static void main(String[] args) {
Teacher t = new Teacher();
System.out.println(t.name);
}
}
3、封装使用的细节
1)对于成员变量,一般是private修饰,然后提供get、set方法来访问。
2)本类提供给它类调用的方法设为public,本类中的辅助方法,用private修饰。
JavaBean的封装演示:
package com.xhu.java;
public class Teacher extends Person {
private String universityName;
private boolean flag;
public String getUniversityName() {
return universityName;
}
public void setUniversityName(String universityName) {
this.universityName = universityName;
}
public boolean isFlag() {
return flag;
}
public Teacher(){
System.out.println("调用Teacher构造器");
}
public Teacher(String universityName) {
this.universityName = universityName;
}
@Override
public String toString() {
return "Teacher{" +
"universityName='" + universityName + '\'' +
", name='" + name + '\'' +
", friend=" + friend +
'}';
}
}
四、多态
1、多态概念和实现
由于继承与方法重写的原因,多态是指调用同名重载方法,对象不同可能会有不同的行为。
比如Person类的睡觉就是简单的睡觉,而继承它的Teacher类和Student类的sleep()方法不同。
Person p1 = new Teacher();
Person p2 = new Student();
p1.sleep();
p2.sleep();
//out
//老师的休息
//学生的休息
多态)多态是方法的多态,不是属性的多态。
多态存在的三个条件)继承、方法重写、父类引用指向之类对象。
多态出现)父类引用指向子类对象后,父类引用调用子类重写的方法,多态出现。
为什么要用父类引用指向子类对象?当一个类被很多类继承时,传入一个子类对象,然后调一个同名函数,此时就要判断这个对象到底是那个子类对象,而此时可以用父类接收对象,通过运行时的多态来决定具体调那个子类的重写函数(通过instanceof来判断的)。此时代码就简介了很多。
需要重载多个函数。
public void test(Teacher t){
t.sleep();
}
public void test(Student s){
s.sleep();
}
public void test(Postgraduate po){
po.sleep();
}
通过多态,简化代码
public void test(Person p){
p.sleep();
}
2、对象的转型
public static void main(String[] args) {
//编译时,只认p为Person对象。
Person p = new Teacher();//向上类型转化,自动完成。
Teacher t = (Teacher) p;//向下类型转化,强制完成。
}
但是为了防止异常,我们需要手动处理一下:
public static void main(String[] args) {
Person p = new Teacher();//向上类型转化,自动完成。
Teacher t = null;
if(p instanceof Teacher)
t = (Teacher) p;//向下类型转化,强制完成。
}
总结
1)封装
2)继承
3)多态
参考文献
[1] Java SE 高淇