• 面向对象的三大特征


    说明: 文章根据个人学习《疯狂java讲义》及《疯狂Java:突破程序员基本功的16课》后,学习整理而来,其中部分代码直接使用原文示例:


    说起面向对象的三大特性:封装、继承和多态,知道点面向对象的人可谓是耳熟能详,但其中还是有很多知识点、细节需要仔细的品味一番,才能实的掌握这三大特性,为以后理解程序,编写精致的代码提供基本的保证。 

    一、封装

    封装性就是把类(对象)的属性和行为结合成一个独立的相同单位,并尽可能隐蔽类(对象)的内部细节。

    封装的特性使得类(对象)以外的部分不能随意存取类(对象)的内部数据(属性),保证了程序和数据不受外部干扰且不被误用。

    说的简单点,封装就是使用private修饰符来修饰类中的属性或方法,这样,该属性就不可以被对象直接访问。而如果想访问该属性,程序需提供一个约定好的public方法,如此便可以操作该私有属性,这样保证了数据的安全性。

    已知一个类Person,该类的属性和方法如下表所示(示例源自《疯狂java讲义》):

    public class Person {
    	// 将Field使用private修饰,将这些Field隐藏起来
    	private String name;
    	private int age;
    
    	// 提供方法来操作name Field
    	public void setName(String name) {
    		// 执行合理性校验,要求用户名必须在2~6位之间
    		if (name.length() > 6 || name.length() < 2) {
    			System.out.println("您设置的人名不符合要求");
    			return;
    		} else {
    			this.name = name;
    		}
    	}
    
    	public String getName() {
    		return this.name;
    	}
    
    	// 提供方法来操作age Field
    	public void setAge(int age) {
    		// 执行合理性校验,要求用户年龄必须在0~100之间
    		if (age > 100 || age < 0) {
    			System.out.println("您设置的年龄不合法");
    			return;
    		} else {
    			this.age = age;
    		}
    	}
    
    	public int getAge() {
    		return this.age;
    	}
    }

    测试该Person

    public class PersonTest {
    	public static void main(String[] args) {
    		Person p = new Person();
    		// 因为age Field已被隐藏,所以下面语句将出现编译错误。
    		// p.age = 1000;
    		// 下面语句编译不会出现错误,但运行时将提示"您设置的年龄不合法"
    		// 程序不会修改p的age Field
    		p.setAge(1000);
    		// 访问p的age Field也必须通过其对应的getter方法
    		// 因为上面从未成功设置p的age Field,故此处输出0
    		System.out.println("未能设置age Field时:" + p.getAge());
    		// 成功修改p的age Field
    		p.setAge(30);
    		// 因为上面成功设置了p的age Field,故此处输出30
    		System.out.println("成功设置age Field后:" + p.getAge());
    		// 不能直接操作p的name Field,只能通过其对应的setter方法
    		// 因为"李刚"字符串长度满足2~6,所以可以成功设置
    		p.setName("李刚");
    		System.out.println("成功设置name Field后:" + p.getName());
    	}
    }

    在上述示例中,我们可以看到,通过private修饰符,我们隐藏了nameage属性,程序无法直接操作该属性,必须通过事先约定好的setter方法,对属性进行操作,这就可以保证了数据的检验机制能够得到实现。

    在这里,需要介绍一下访问修饰符的作用域:

    1. private:成员变量和方法只能在类内被访问,具有类可见性

    2. default: 成员变量和方法只能被同一个包里的类访问,具有包可见性。

    3. protected:可以被同一个包中的类访问,被同一个项目中不同包中的子类访问

    4. public:可以被同一个项目中所有的类访问,具有项目可见性,这是最大的访问权限

    程序只能通过类本身定义的方法(settergetter)来对该类所实例化的对象进行数据的访问和处理。如果想对实例化的对象添加其它的一个方法和属性是不可能的,这就体现的类的封装性。

      

    二、继承

    1. 继承是实现代码复用的重要手段,通过继承扩展了父类的功能。Java的继承具有单继承的特点,即只能继承自一个父类,每个子类只有一个直接父类,但是其父类又可以继承于另一个类,从而实现了子类可以间接继承多个父类,但其本质上划分仍然是一个父类和子类的关系。

    2. Java的继承通过extends关键字来实现,实现继承的类被称为子类,被继承的类称为父类,父类和子类的关系,是一种一般和特殊的关系。就像是水果和苹果的关系,苹果继承了水果,苹果是水果的子类,水果是苹果的父类,则苹果是一种特殊的水果。

    3. 创建子类一般形式如下:

    class 类名 extends 父类名{

         子类体

    }

    4. 子类与父类的变量、方法关系

    子类可以继承父类的所有非私有特性(成员变量、方法)。对于被private修饰的类成员变量或方法,其子类是不可见的,也即不可访问

    子类中可以声明与父类同名的成员变量,这时父类的成员变量就被隐藏起来了,在子类中直接访问到的是子类中定义的成员变量。(此处需记得是隐藏了父类的成员变量,在子类中分配内存空间时,会为其分配一个内存空间,存放的是父类的变量)。

    子类中也可以声明与父类相同的成员方法,包括返回值类型、方法名、形式参数都应保持一致,称为方法的覆盖。

    如果在子类中需要访问父类中定义的同名成员变量或方法,需要用的关键字super

    Java中通过super来实现对被隐藏或被覆盖的父类成员的访问。super 的使用有三种情况:

    1) 访问父类被隐藏的成员变量和成员方法;

          super.成员变量名;

    2) 调用父类中被覆盖的方法,如:

          super.成员方法名([参数列]);

    3) 调用父类的构造函数,如:

          super([参数列表]);

    super( )只能在子类的构造函数中出现,并且永远都是位于子类构造函数中的第一条语句。

    举例:


    实例一:

    class BaseClass {
    	public double weight;
    
    	public void info() {
    
    		System.out.println("我的体重是" + weight + "千克");
    	}
    }
    
    public class SubClass extends BaseClass {
    	public static void main(String[] args) {
    
    		// 创建SubClass 对象
    		SubClass sc = new SubClass();
    		// SubClass 本身没有weight属性,但是SubClass 的父类有weight属性,也可以访问SubClass 对象的属性
    		sc.weight = 56;
    		// 调用SubClass 对象的info()方法
    		sc.info();
    	}
    }

    子类SubClass继承自父类BaseClass,则它继承了父类的非私有属性和方法(weight和info()),此时,便可以在子类中直接访问weight和info(),因为它们都已经被SubClass继承过来,属于了SubClass。 


    实例二:

    class  Animal {   
     
      String  name="animal";  
     
      int  age;  
     
      public void move(){
     
        System.out.println("animal move");
     
      }
     
    }
     
    class Dog extends Animal{
     
      String  name="dog";    //隐藏了父类的name属性;
     
      float  weight;        //子类新增成员变量
     
      public void move(){           //覆盖了父类的方法move()
     
        super.move();       //用super调用父类的方法
     
        System.out.println("Dog Move");
     
      }
     
    }
     
    public class InheritDemo{
     
      public static void main(String args[]){
     
        Dog d=new Dog();
     
        d.age=5;
     
        d.weight=6;
     
        System.out.println(d.name+" is"+d.age+" years old");
     
        System.out.println("weight:"+d.weight);
     
        d.move();
     
      }
     
    }
    

    运行结果:

    dog is5 years old
    weight:6.0
    animal move
    Dog Move


    在继承过程中,如果子类拥有和父类同名的属性,则父类的这个属性是被隐藏了起来,在子类中分配内存空间时,会为其分配一个内存空间,存放的是父类的变量;如果子类中声明了与父类相同的成员方法,包括返回值类型、方法名、形式参数都应保持一致,称为方法的覆盖,方法覆盖后,子类的对象将无法访问父类中呗覆盖的方法,但可以在子类中通过super调用父类被覆盖的方法。

    举例三:

    class SuperClass {
    
    	SuperClass() {
    
    		System.out.println("调用父类无参构造函数");
    
    	}
    
    	SuperClass(int n) {
    
    		System.out.println("调用父类有参构造函数:" + n);
    
    	}
    
    }
    
    class SubClass extends SuperClass {
    
    	SubClass(int n) {
    
    		System.out.println("调用子类有参构造函数:" + n);
    
    	}
    
    	SubClass() {
    
    		super(200);
    
    		System.out.println("调用子类无参构造函数");
    
    	}
    
    }
    
    public class InheritDemo2 {
    
    	public static void main(String arg[]) {
    
    		SubClass s1 = new SubClass();
    
    		SubClass s2 = new SubClass(100);
    
    	}
    
    }

    程序运行结果: 


    调用父类有参构造函数:200 

    调用子类无参构造函数 

    调用父类无参构造函数 

    调用子类有参构造函数:100

    由以上程序可以得出结论: 在对象的实例化过程中,无论使用不使用super,程序都会先调用父类的构造器初始化代码; 子类调用父类构造器的几种情况:

    1. 子类通过super显示调用父类构造器,此时根绝super里的参数决定调用哪个构造器;

    2. 子类通过this调用本类中重载的构造器,系统根据this调用里传入的实参决定调用本类中的哪个构造器。

    3. 子类中无super,也无this,将默认调用父类无参构造器。

    三、多态

    Java的引用变量有两种类型:编译时类型和运行时类型;

    编译类型由生命该变量时所用的类型决定,运行时类型由实际赋给该变量的对象决定。

    如果编译类型和运行时类型不一致,则会发生多态。

    举例1

    class Animal {
    	public void eat() {
    		System.out.println("animal eat");
    	}
    }
    
    class Dog extends Animal {
    
    	public void eat() {
    		System.out.println("Dog eat bone");
    	}
    }
    
    class Cat extends Animal {
    
    	public void eat() {
    		System.out.println("Cat eat fish");
    	}
    }
    
    public class PloyDemo {
    
    	public static void main(String args[]) {
    
    		Animal a;
    
    		a = new Animal(); // 编译时类型和运行时类型完全一样,因此不存在多态
    
    		a.eat();
    
    		a = new Dog(); // 下面编译时类型和运行时类型不一样,多态发生
    
    		a.eat();
    
    		a = new Cat(); // 下面编译时类型和运行时类型不一样,多态发生
    
    		a.eat();
    
    	}
    
    }

    程序运行结果:


    animal eat 

    Dog eat bone 

    Cat eat fish

    实例2

    class SuperClass {
    
    	public int book = 6;
    
    	public void base() {
    
    		System.out.println("父类的普通方法base()");
    	}
    
    	public void test() {
    
    		System.out.println("父类中将被子类覆盖的方法");
    	}
    }
    
    public class PloymorphismTest001 extends SuperClass {
    
    	// 重新定义一个book实例属性,覆盖父类的book实例属性
    	public String book = "Java疯狂讲义";
    
    	public void test() {
    		System.out.println("子类中覆盖父类的方法");
    	}
    
    	private void test1() {
    
    		System.out.println("子类中普通的方法");
    
    	}
    
    	// 主方法
    
    	public static void main(String[] args) {
    
    		// 下面编译时类型和运行时类型完全一样,因此不存在多态
    		SuperClass sc = new SuperClass();
    		System.out.println("book1= " + sc.book);// 打印结果为:6
    
    		// 下面两次调用将执行SuperClass的方法
    		sc.base();
    		sc.test();
    
    		// 下面编译时类型和运行时类型完全一样,因此不存在多态
    		PloymorphismTest001 pt = new PloymorphismTest001();
    		System.out.println("book2= " + pt.book); // 打印结果为:Java疯狂讲义
    
    		// 下面调用将执行从父类继承到的base方法
    		pt.base();
    		// 下面调用将执行当前类的test方法
    		pt.test();
    		// 下面编译时类型和运行时类型不一样,多态发生
    		SuperClass sscc = new PloymorphismTest001();
    		// 结果表明访问的是父类属性
    		System.out.println("book3= " + sscc.book);// 打印结果为:6
    		// 下面调用将执行从父类继承到得base方法
    		sscc.base();
    		// 下面调用将执行当前类的test方法
    		sscc.test();
    
    		// 因为sscc的编译类型是SuperClass,SuperClass类没有提供test1()方法
    		// 所以下面代码编译时会出现错误
    		// sscc.test1();
    	}
    }

    程序运行结果为:

    book1= 6

    父类的普通方法base()

    父类中将被子类覆盖的方法

    book2= Java疯狂讲义

    父类的普通方法base()

    子类中覆盖父类的方法

    book3= 6

    父类的普通方法base()

    子类中覆盖父类的方法


    总结上述示例,可发现: 

          1.  当程序的编译时类型和运行时类型完全一样时,不存在多态,系统依然调用本类中的方法。

          2.  当程序的编译时类型和运行时类型不一样,多态发生; 此处,如果对象访问的是属性,则程序根据编译时类型决定要访问的属性; 如果对象访问的是方法,则程序根据运行时类型决定要访问的方法。

          3.  如果程序的编译类型是SuperClass,但SuperClass类没有提供父类中的某个方法,比如(test1()),则编译时会出错。


  • 相关阅读:
    logging模板日志格式
    MariaDB修改默认字符集
    Django之表单验证
    Django之定制属于自己的admin
    sympy-高数可以这么学
    matplotlib01
    mysql---- 用户权限管理
    django----JSONP知识回顾
    django----文件上传
    数据库结构备份
  • 原文地址:https://www.cnblogs.com/hehe520/p/6330048.html
Copyright © 2020-2023  润新知