小言_互联网的博客

java基础篇-第三章 面向对象---Java三大特性之封装、继承和多态

384人阅读  评论(0)

1、理解封装

封装(Encapsulation)是面向对象的三大特征之一(另外零个是集成和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内信息,而是通过该类所提供的方法实现对内部信息的操作和访问。提到封装,就设计到访问权限的问题,那么下面就说明一下访问控制符。

1.1 访问控制符

Java提供了3个访问控制符:private、protected和private(级别由小到大),分别代表了3个访问控制级别,另外还有一个不加访问控制符的访问级别。提供了4个访问控制级别。Java的访问控制级别由小及大的顺序是:private->default->protected->public。这4个访问控制级别详细介绍如下:

private(当前类访问权限):如果一个类里的成员(包括成员变量、方法和构造器等)使用private访问控制符来修饰,则这个成员只能在该类内部被访问。

default(包访问权限):如果类里的一个成员(包括成员变量、方法和构造器)或者一个外部类,不使用任何访问控制修饰符,就成它为包访问权限的,default访问控制的成员或外部类可以被相同包下的其他类访问。

protected(子类访问权限):如果一个成员(包括成员变量、方法和构造器)使用protected访问控制符修饰,那么这个成员既可以被同一个包下的其他类访问,也可以被不同包下的子类访问。通常情况下,如果使用protected来修饰一个方法,通常是希望其子类来重写这个方法。

public(公共访问权限):如果一个被public修饰的类中的成员或者外部类被public修饰,那么这个成员或者外部类就可以被所有类访问。

下图是访问控制级别表:

Java访问控制级别表
  private default protected public
同一个类中
同一个包中  
子类中    
全局范围      

1.2 JavaBean实现良好的封装

如果一个Java类的每一个实例变量都使用private修饰,并为每个实例变量都提供了getter个setter方法,那么这个类就是一个符合JavaBean规范的类。其中getter方法用于获取该类的变量值,setter方法用于设置该类的变量值,示例代码如下:

public class Person{
    private String name;
    private int age;

    public String getName(){
        return this.name;
    }
    public void setName(name){
        this.name = name;
    }

    public int getAge(){
        returm this.age;
    }
    public void setAge(int age){
        this.age = age;
    }
}

2、继承

2.1 理解继承

Java的继承是通过extends关键字实现的,实现继承的类被称为子类,被继承的类称为父类,有的也叫基类或超类。Java里子类继承父类的语法如下:

修饰符 class SubClass extends SuperClass{
    //定义类部分
}

 Java是单继承的,只能有一个直接父类,但可以有无线多个间接父类。当一个Java类没有显示的定义直接父类的时候,则这个类的默认父类是java.lang.Object类。因此java.lang.Object类是所有类的父类,要么是直接父类,要么是间接父类。下面是一个类继承的示例:

/**
 * @CalssName: Test
 * @Describe:
 * @DATE: 2019/9/27 0027 14:55
 * @Author:Hrzhi
 */
public class Test {
    public static void main(String[] args) {
        Triangle triangle = new Triangle();
        triangle.setName("三角形");
        triangle.setSides(3);
        System.out.println("我是一个" + triangle.getName() + ",我有" + triangle.getSides() + "条边。");
        triangle.intruduce();
    }
}

/**
 * 定义父类图形类
 */
class Graphics {
    private String name;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Object intruduce(){
        System.out.println("我是图形类的鼻祖!");
        return null;
    }
}

/**
 * 定义子类三角形类
 */
class Triangle extends Graphics {
    public int sides;

    public int getSides() { return sides; }
    public void setSides(int sides) { this.sides = sides; }

    public String intruduce(){
        super.intruduce();
        System.out.println("我是图形类的扩展!");
        return null;
    }
}

2.2 子类重写父类的方法

子类扩展了父类,子类就是一个特殊的父类。一般情况下,子类是以父类为基础,额外的增加成员变量或方法。但有一种情况例外:当子类包含与父类同名的方法是,子类需要重写父类的方法,这种现象称为方法重写或者方法覆盖。

方法重写要遵循“两同两小一大”的规则,“两同”即方法名相同,形参列表相同;“两小”指的是子类方法返回值类型比父类方法返回值类型小或相等,子类方法抛出的异常类型比父类方法抛出的异常类型小或相等;“一大”是指子类方法的访问权限应比父类方法的访问权限大或相同。特别指出:覆盖方法与被覆盖方法要么都是实例方法,要么都是类方法,不能一个是类方法一个是实例方法。

当子类覆盖了父类方法后,子类的对象将无法访问父类中北覆盖的方法,但是可以在子类方法中调用父类被覆盖的方法。如果需要在子类中调用父类被覆盖的方法,则可以使用super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。

如果父类方法的访问权限是private,那么子类是无法访问该方法的,也无法构成重写。

当实例化子类的时候,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有的实例变量分配内存,即使子类定义了与父类同名的实例变量。

2.3 调用父类构造器

子类构造器调用父类构造器分为以下几种情况:

a) 子类构造器执行体的第一行使用super显示的调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器。

b) 子类构造器第一行使用this显示调用本类的重载构造器,系统将根据this调用里传入的实参列表调用本类里对应的构造器。执行本类的另一个构造器是即会调用父类的构造器。

c) 子类构造器中既没有显式的super调用父类的构造器,也没有this显式的调用本类中的重载构造器,系统将在执行子类构造器之前,隐式的调用父类的无参构造器。

综上所述,不管什么情况,当调用子类构造器来初始化子类对象是,父类构造器总是在子类构造器之前执行。由此可见,当初始化子类的实例对象时,总是上溯执行其父类构造器...,以此类推,创建任何Java对象,最先执行的总是java.lang.Object的构造器。

3、多态

Java的引用变量有两个类型:编译时类型和运行时类型。编译时类型是变量声明是指定的类型,而运行时类型是变量赋值给变量的对象决定的。如果编译时类型和运行时类型不一致,那么就可能出现多态(Polymorphism)。

3.1 多态性

以下代码是通过继承关系实现多态性,详解如下:

class BaseClass{
    private int base = 3;

    public int getBase() { return base; }
    public void setBase(int base) { this.base = base; }

    public void test(){
        System.out.println("父类被覆盖的方法...");
    }
}

class SubClass extends BaseClass{
    private String subString = "子类变量";

    public String getSubString() { return subString; }

    public void setSubString(String subString) { this.subString = subString; }

    public void sub(){
        System.out.println("子类自定义方法");
    }
    public void test(){
        System.out.println("子类重写父类的test方法...");
    }
}


public class Test3 {
    public static void main(String[] args) {
        //编译时类型和运行时类型不一样,构成多态
        //  编译时类型是BaseClass
        //  运行时类型是SubClass
        BaseClass bs = new SubClass();
        // 执行了子类的方法
        bs.test();
        System.out.println(bs.getBase());
        System.out.println(((SubClass) bs).getSubString());
        System.out.println();
    }
}

main方法中BaseClass bs = new SubClass();这一句,用父类BaseClass声明了一个变bs,但是变量赋值的时候是通过new关键字创建的SubClass实例对象。所以当bs调用test()方法的时候实际上是执行的子类覆盖父类的test()方法,也就是子类的test()方法。编译时变量类型与运行时变量类型显然不是同一个类型,所以构成了多态现象。

因为子类是一种特殊的父类,因此Java允许将一个子类对象直接赋值给父类的引用变量,无需任何的转换,这被称为向上转型(upcasting),向上转型是由系统自动完成的。

与方法不同的是,对象的实例变量不具有多态性,也就是说当调用实例变量的时候,运行时与编译时都是调用变量声明类型的实例对象的实例变量。

3.2 引用变量的强制类型转换

编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它所引用的对象却是包含该方法。如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转为运行时类型,强制类型转换需要借助于类型转换运算符。

类型强制转换运算符是小括号,类型转换运算符的用法是:(type)variable,这种用法是将variable变量转换成type类型的变量。类型转换运算符可以将一个基本类型转为另一种基本类型。但是,这种类型强转不是万能的,类型强转的时候需要注意:

a) 基本类型之间的类型强转只能在数值类型之间强转。

b) 引用类型之间的转换只能在具有继承关系的两个类型之间进行。如果两个没有继承关系的类型,则无法进行类型转换,否则编译时出现错误,引发ClassCastExcepton异常。

考虑到类型转换时可能出现异常,因此类型转换前应该先通过instanceof运算符判断是否可以转换成功,保证程序的健壮性。用法如下:

if(variable instanceof Type){

    Type type = (Type)variable;

}

 


转载:https://blog.csdn.net/weixin_39082031/article/details/101427778
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场