Java中的封装、继承、多态

面向对象语言中都有三大属性:封装、继承、多态。
此处记录一下我对三者的理解,以后会不断修正!

Java中的封装、继承、多态_第1张图片
编程就是登山!

封装

封装是面向对象语言的基础属性。从包、类、方法的创建中:我们一直在做一件事,将某一个功能的具体实现尽量隐藏,只提供少量的外部接口来给用户使用。现实生活中,我们使用的很多东西都是如此,比如说手机。普通用户不需要知道手机内部电路板是如何走线,芯片是如何排布,软件是如何运行的;我们只需要使用它的打电话软件、上网用的浏览器就够了。实现过程交给工程师已然足够!

包(package)

我们在引入类时,有时会出现类名相同的情况,我们需要更精确地界定类的出处。因此,包的存在必不可少,它将向我们区分相同名称的类的区别!

包的使用

首先看如下代码:

public class Demo {
    public static void main(String[] args) {
        java.util.ArrayList list = new java.util.ArrayList();
        java.util.ArrayList list1 = new java.util.ArrayList();
    }
}

我们实例化了java.util包中的ArrayList类,如果我们要是创建了自己的ArrayList,包名的介入会将两者区分。但如果我们总是带上包名引用该类的话就显得太过繁杂;因此,我们可以使用导入命令(import)来简化代码,当我们在导入包后就可以直接使用类名了。(如果真的存在同时调用不同包中的同名类,还是得加上包名,但这种情况比较少见):

import java.util.ArrayList;
public class Demo {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
    }
}

当我们要导入包中多个类时,可以使用包名.*

import java.util.*;
public class Demo {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
    }
}

这样我们就能够直接使用java.util中的所有类了。

包的创建

程序员一般将自己创建的轮子保存在本公司的包中以待以后使用,避免重复工作。为了防止包名也重复,大家默认包名一般以公司域名倒写+项目名来命名,如果没有域名,那就起一个非常难见到的包名。比如说我要创建一个包,可以命名为online.suiyu.test要注意包名一般都是小写的。
那我们如何在代码中实现呢?看下方代码:

package online.suiyu.test;
public class Demo {
    public static void main(String[] args) {
        System.out.println("hello");
    }
}

这样,我们就将Demo.java放入到了online.suiyu.test包中。但这还没结束,我们需要使用如下方法编译源文件:

javac -d . Demo.java

这样,我们就编译了包与类文件。此时我们查看文件将会发现当前目录下存在如下目录:

D:.
├─online
│  └─suiyu
│      └─test

这是包的表现形式,Demo.java就存在于test文件夹中!当然,如果想保存到其他路径可以直接指定目录即可:

//将包保存到D:\java目录
javac -d D:\java DemoClass.java

如果使用eclipse的话,直接在创建类的时候也能指定包,不指定的话就是default包。

JAR包的创建与使用

jar包其实也是压缩包的一种,可以使用压缩软件打开。

在命令行中,输入jar,可以看到以下提示

$ jar
用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] fi                                                                     les ...
选项:
    -c  创建新档案
    -t  列出档案目录
    -x  从档案中提取指定的 (或所有) 文件
    -u  更新现有档案
    -v  在标准输出中生成详细输出
    -f  指定档案文件名
    -m  包含指定清单文件中的清单信息
    -n  创建新档案后执行 Pack200 规范化
    -e  为捆绑到可执行 jar 文件的独立应用程序
        指定应用程序入口点
    -0  仅存储; 不使用任何 ZIP 压缩
    -P  保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件
    -M  不创建条目的清单文件
    -i  为指定的 jar 文件生成索引信息
    -C  更改为指定的目录并包含以下文件
如果任何文件为目录, 则对其进行递归处理。
清单文件名, 档案文件名和入口点名称的指定顺序
与 'm', 'f' 和 'e' 标记的指定顺序相同。

示例 1: 将两个类文件归档到一个名为 classes.jar 的档案中:
       jar cvf classes.jar Foo.class Bar.class
示例 2: 使用现有的清单文件 'mymanifest' 并
           将 foo/ 目录中的所有文件归档到 'classes.jar' 中:
       jar cvfm classes.jar mymanifest -C foo/ .

根据提示:

压缩指定文件(多个文件或文件夹):

jar -cf Demo.jar online

生成了名为Demo.jar的jar包。

查看jar包中的文件列表

$ jar -tf Demo.jar
META-INF/
META-INF/MANIFEST.MF
online/
online/suiyu/
online/suiyu/test/
online/suiyu/test/Demo.class

可以看到已经压缩的文件夹与文件(其中添加了一些特殊文件及文件夹)

运行jar中的class文件

$ java online.suiyu.test.Demo
hello

和没压缩一样,只要指定包名+类名直接使用就行了。

类的存在隐藏了具体实现,防止别人更改代码,更加安全!

类中使用以下控制符控制访问权限:

private default protected public
同一类中
同一包中
子类中
全局范围内

继承

简单介绍

当我们创建了类时,就已经完成继承了,因为所有类的标准根类都是Object类,编译器会自动完成继承的过程。我们要实现继承的话一般使用extends关键字。这里记录一般类的特点:

  • 就像是人都有一个亲生父亲,而一个父亲可以有很多儿子一样,类可以有很多子类,但只能有一个父类
  • 子类继承父类后,可以拥有父类的方法和属性,并且可以添加、覆盖、重写父类的方法
  • 子父类的继承受类的控制符约束
  • 父类有非默认构造器时,子类也必须写出构造器,并在子类构造器第一行调用super来加载父类构造器
  • 子类可以在重写父类方法后依然调用父类方法,需要加入super区分;为了区分调用的是自己的方法可以加上this(也可省略)
  • 父类与父类的父类等都是子类的基类型

举例介绍

水果的重量、颜色、种类不同。其中苹果就是其中一个种类。

代码实现

class Fruit {
    public double weight;
    String color ;
    Fruit(double weight, String color) {
        this.weight = weight;
        this.color = color;
    }
    protected void info() {
        System.out.println("这个水果是" + color + "的,有" + weight + "kg");
    }
}

class Apple extends Fruit {

    Apple(double weight, String color) {
        super(weight, color);
    }

    //重写
    public void info() {
        System.out.println("这个苹果是" + color + "的,有" + weight + "kg");
    }
    // 重载
    void info(String addInfo) {
        System.out.println("这个苹果是" + color + "的,有" + weight + "kg," + addInfo);
    }
    void anotherInfo() {
        this.info();
        System.out.print("对于上面这句话我们也可以说成");
        super.info();
    }
}


public class Test {
    public static void main(String[] args) {
        new Fruit(10.5, "紫色").info();
        new Apple(0.5, "红色").info();
        new Apple(0.6, "红色").info("我最喜欢吃苹果!");
        new Apple(0.3, "青色").anotherInfo();
    }
}

运行结果:

这个水果是紫色的,有10.5kg
这个苹果是红色的,有0.5kg
这个苹果是红色的,有0.6kg,我最喜欢吃苹果!
这个苹果是青色的,有0.3kg
对于上面这句话我们也可以说成这个水果是青色的,有0.3kg

多态

简单说明

继承允许将对象视为它自己本身的类型或其基类型来加以处理。比如上方代码中苹果可以被当做苹果同时也可以被当做水果来处理!而多态的存在能够消除类型之间的耦合性,并且能够改善代码的组织结构和可读性,同时它还能使得后续的代码随着后续的需求不断"生长"!

多态的建立要满足三个条件:继承、重写、向上转型!
向上转型就是把某个对象的引用视为对其基类型的引用!

举例说明

我们还拿学生的例子来说。小明是一个光荣的小学生,他喜欢和他们班的同学一起去读书。他的邻居大明是一名大学生,很喜欢旅游。

代码实现

//Test.java
abstract class Student {
  public abstract void learnMath();
}

class Pupil extends Student {
  public void learnMath() {
    System.out.println("我是一个小学生,我们学小学数学");
  }
  //休闲方式
  void reading() {
    System.out.println("我喜欢和同学一起去读书");
  }
}

class Undergraduate extends Student {
  public void learnMath() {
    System.out.println("我是一个大学生,我们学高等数学");
  }
  //休闲方式
  void tour() {
    System.out.println("我喜欢和同学一起去旅游");
  }
}

public class Test {

  public static void main(String[] args) {

    aboutMe(new Pupil());

    System.out.println();

    Undergraduate daHua = new Undergraduate();
    aboutMe(daHua);

  }
  public static void aboutMe(Student stu) {
    stu.learnMath();

    if (stu instanceof Pupil) {
      Pupil pu = (Pupil)stu;
      pu.reading();
    } else if (stu instanceof Undergraduate) {
      ((Undergraduate)stu).tour();
    }
  }
}


运行结果

我是一个小学生,我们学小学数学
我喜欢和同学一起去读书

我是一个大学生,我们学高等数学
我喜欢和同学一起去旅游

代码解析

使用了aboutMe函数来对象的相关行为。形参Student是两个实参PupilUndergraduate的基类。因此,当两个子类对象分别传入进来后就实现了向上转型,此时就相当于执行了如下代码:

Student stu = new Pupil();
Student stu = new Undergraduate();

Student stu = new Pupil();Student stu会在栈中创建一个基类的基本变量,它指向右边子类new Pupil()创建的对象的内容!
看如下图,子类和父类都有learnMath方法,我们调用stu.learnMath时,其实也是调用xiaoming.learnMath①。但当我们调用stu.reading时,由于父类中没有该方法,因此,他也不会有索引指向子类的reading方法,就会出错。如果我们想要调用该方法,应该将父类强制向下转型成子类,此时就有了②③。这就是Test.java中的aboutMe方法实现原理。一个方可可以处理不同的类,这就是多态的有点。为什么说多态能够使代码不断"成长"呢?因为现在即使我们再加入其它类(但必须是Student的子类),比如说研究生也可以直接使用aboutMe方法!

Java中的封装、继承、多态_第2张图片
演示

此处的代码解析可能有些欠妥,不过便于理解,以后会逐步修正!

小例题

class A {
  public String show(D obj) {
    return ("A and D");
  }
  public String show(A obj) {
    return ("A and A");
  }

}

class B extends A {
  public String show(B obj) {
    return ("B and B");
  }
  public String show(A obj) {
    return ("B and A");
  }
}

class C extends B {

}

class D extends B {

}

public class Test {
  public static void main(String[] args) {
    A a1 = new A();
    A a2 = new B();
    B b = new B();
    C c = new C();
    D d = new D();
     
    System.out.println("1--" + a1.show(b)); 
    System.out.println("2--" + a1.show(c)); 
    System.out.println("3--" + a1.show(d)); 
    System.out.println("4--" + a2.show(b)); 
    System.out.println("5--" + a2.show(c)); 
    System.out.println("6--" + a2.show(d)); 
    System.out.println("7--" + b.show(b));  
    System.out.println("8--" + b.show(c));  
    System.out.println("9--" + b.show(d));  
  }
}

以上会输出什么?
答案:

1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D

此题引用于该博客,该博主讲的够全面了,可以跳过去看看。在这说一些我的解题思路:拿4来说吧,a2.show(b)

1、我们需要找到子父类(A,B)都已经存在的方法,要知道,多态中父类没有的方法,即使子类有,我们也不会调用,此处找到show(A obj)
2、此处子类的方法覆盖了父类的方法。我们传递的参数是b(class B),b的基类是A。多态中子类可以当做基类使用,所以会调用子类中的show(A obj)方法!

总结起来就是:父子都有才可调用,优先调用子类方法,形参子类可当父类!

本人小白一个,欢迎访问我的个人博客,同时也欢迎来相互交流学习!

你可能感兴趣的:(Java中的封装、继承、多态)