目录
1、理解封装
1.1 访问控制符
1.2 JavaBean实现良好的封装
2、继承
2.1 理解继承
2.2 子类重写父类的方法
2.3 调用父类构造器
3、多态
3.1 多态性
3.2 引用变量的强制类型转换
封装(Encapsulation)是面向对象的三大特征之一(另外零个是集成和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内信息,而是通过该类所提供的方法实现对内部信息的操作和访问。提到封装,就设计到访问权限的问题,那么下面就说明一下访问控制符。
Java提供了3个访问控制符:private、protected和private(级别由小到大),分别代表了3个访问控制级别,另外还有一个不加访问控制符的访问级别。提供了4个访问控制级别。Java的访问控制级别由小及大的顺序是:private->default->protected->public。这4个访问控制级别详细介绍如下:
private(当前类访问权限):如果一个类里的成员(包括成员变量、方法和构造器等)使用private访问控制符来修饰,则这个成员只能在该类内部被访问。
default(包访问权限):如果类里的一个成员(包括成员变量、方法和构造器)或者一个外部类,不使用任何访问控制修饰符,就成它为包访问权限的,default访问控制的成员或外部类可以被相同包下的其他类访问。
protected(子类访问权限):如果一个成员(包括成员变量、方法和构造器)使用protected访问控制符修饰,那么这个成员既可以被同一个包下的其他类访问,也可以被不同包下的子类访问。通常情况下,如果使用protected来修饰一个方法,通常是希望其子类来重写这个方法。
public(公共访问权限):如果一个被public修饰的类中的成员或者外部类被public修饰,那么这个成员或者外部类就可以被所有类访问。
下图是访问控制级别表:
private | default | protected | public | |
同一个类中 | ✔ | ✔ | ✔ | ✔ |
同一个包中 | ✔ | ✔ | ✔ | |
子类中 | ✔ | ✔ | ||
全局范围 | ✔ |
如果一个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;
}
}
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;
}
}
子类扩展了父类,子类就是一个特殊的父类。一般情况下,子类是以父类为基础,额外的增加成员变量或方法。但有一种情况例外:当子类包含与父类同名的方法是,子类需要重写父类的方法,这种现象称为方法重写或者方法覆盖。
方法重写要遵循“两同两小一大”的规则,“两同”即方法名相同,形参列表相同;“两小”指的是子类方法返回值类型比父类方法返回值类型小或相等,子类方法抛出的异常类型比父类方法抛出的异常类型小或相等;“一大”是指子类方法的访问权限应比父类方法的访问权限大或相同。特别指出:覆盖方法与被覆盖方法要么都是实例方法,要么都是类方法,不能一个是类方法一个是实例方法。
当子类覆盖了父类方法后,子类的对象将无法访问父类中北覆盖的方法,但是可以在子类方法中调用父类被覆盖的方法。如果需要在子类中调用父类被覆盖的方法,则可以使用super(被覆盖的是实例方法)或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。
如果父类方法的访问权限是private,那么子类是无法访问该方法的,也无法构成重写。
当实例化子类的时候,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有的实例变量分配内存,即使子类定义了与父类同名的实例变量。
子类构造器调用父类构造器分为以下几种情况:
a) 子类构造器执行体的第一行使用super显示的调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器。
b) 子类构造器第一行使用this显示调用本类的重载构造器,系统将根据this调用里传入的实参列表调用本类里对应的构造器。执行本类的另一个构造器是即会调用父类的构造器。
c) 子类构造器中既没有显式的super调用父类的构造器,也没有this显式的调用本类中的重载构造器,系统将在执行子类构造器之前,隐式的调用父类的无参构造器。
综上所述,不管什么情况,当调用子类构造器来初始化子类对象是,父类构造器总是在子类构造器之前执行。由此可见,当初始化子类的实例对象时,总是上溯执行其父类构造器...,以此类推,创建任何Java对象,最先执行的总是java.lang.Object的构造器。
Java的引用变量有两个类型:编译时类型和运行时类型。编译时类型是变量声明是指定的类型,而运行时类型是变量赋值给变量的对象决定的。如果编译时类型和运行时类型不一致,那么就可能出现多态(Polymorphism)。
以下代码是通过继承关系实现多态性,详解如下:
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),向上转型是由系统自动完成的。
与方法不同的是,对象的实例变量不具有多态性,也就是说当调用实例变量的时候,运行时与编译时都是调用变量声明类型的实例对象的实例变量。
编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它所引用的对象却是包含该方法。如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转为运行时类型,强制类型转换需要借助于类型转换运算符。
类型强制转换运算符是小括号,类型转换运算符的用法是:(type)variable,这种用法是将variable变量转换成type类型的变量。类型转换运算符可以将一个基本类型转为另一种基本类型。但是,这种类型强转不是万能的,类型强转的时候需要注意:
a) 基本类型之间的类型强转只能在数值类型之间强转。
b) 引用类型之间的转换只能在具有继承关系的两个类型之间进行。如果两个没有继承关系的类型,则无法进行类型转换,否则编译时出现错误,引发ClassCastExcepton异常。
考虑到类型转换时可能出现异常,因此类型转换前应该先通过instanceof运算符判断是否可以转换成功,保证程序的健壮性。用法如下:
if(variable instanceof Type){
Type type = (Type)variable;
}