详解Java中的封装、继承、多态

封装

在如何理解面向对象这篇文章中,提到所谓的封装就是“功能都给你做好了,你不必去理解它是怎么写出来的,直接使用即可。”。但你得清楚一点,那就是这句话是相对于使用者来说的,而作为开发者,封装就得我们自己来干。

那么作为开发者,我们应该如何去封装呢?其实你应该反过来问,他们应该如何去使用,这样一想会简单很多,作为使用者,自然是希望越简单越好,也就是说,一些复杂的东西,我们不应该让使用者去操作,那也就是说我们应该把复杂的,以及不必要的参数给它封死,不让使用者去操作。

为什么不让使用者去操作?

因为往往使用者是不太专业的,如果暴露太多的接口给他们,就很有可能出现一些稀奇古怪的问题,好比一个不会做水煮鱼的,如果让他去做那肯定是不好的,那怎么办,给他买一包水煮鱼的调料,让他直接放进锅里就好,这样就减少了不必要的麻烦。我们封装程序也是这样,把复杂的代码封死,不让操作者去操作,以免出错。

比如下面这个例子:

class Average{
 private int[] fractions = new int[3]; //分数
 private int average = 0; //平均分
 public void setFraction(int[] fraction){
  fractions = fraction;
 }
 public double getAverage(){
  for(int cell:fractions){
   average += cell;
  }
  return (double) (average / fractions.length);
 }
}
class app{
 public static void main(String[] args){
  int[] a = {50,40,50};
  Average average = new Average();
  average.setFraction(a); //设置分数
  double n = average.getAverage(); //获取平均分
  System.out.println(average.average); //报错
  System.out.println(n); //46.0
 }
}

提示:Java通过private设置私有变量,通过public将变量设置成公开的。

这里我们之所以将分数和平均分设置成私有变量是为了防止使用者误操作,而且也不必让使用者知道有这么一个变量,只需要让使用者知道怎么去设置分数,和获取平均分就好了。

当然这只是一个很基础的封装,如果想封装出一个好的程序,还得多费一些心思。

继承

拿猫和狗来说,它们都是动物,而且它们有一些共同点,比如:名字,年龄,声音,吃等。把这段话写成代码就是下面这个样子。

class Animal{
 private String name;
 private int age;

 public void setName(String name){
  this.name = name;
 }
 public void setAge(int age){
  this.age = age;
 }
 public String getName(){
  return this.name;
 }
 public int getAge(){
  return this.age;
 }
}
class Cat extends Animal{
 public void voice(){
  System.out.println(super.getName() + " 喵");
 }
 public void eat(){
  System.out.println(super.getName() + " fish");
 }
}
class Dog extends Animal{
 public void voice(){
  System.out.println(super.getName() + " 汪");
 }
 public void eat(){
  System.out.println(super.getName() + " Bone");
 }
}
class app{
 public static void main(String[] args){
  Cat cat = new Cat();
  cat.setName("猫大王"); //Cat本身没有setName方法,但是它的基类有,所以java解析器会到Cat的基类那里拿
  cat.voice();
  Dog dog = new Dog();
  dog.setName("大黑");
  dog.setAge(13);
  dog.voice();
  System.out.println(dog.getName() + dog.getAge());
 }
}

------Output------
猫大王 喵
大黑 汪
大黑13

提示:Java通过extends关键字来实现继承,父类中通过private定义的变量和方法不会被继承,也就是你不能在子类中直接操作父类通过private定义的变量以及方法。

在上面代码中,我们可以看到,Cat和Dog并没有定义setName、setAge、getName、getAge方法,但是我们依然可以在Cat和Dog类中使用,这是因为我们通过extends关键字继承了Animal类,因此在Animal中定义的变量和方法,我们可以在子类中直接使用,除private定义的变量和方法。

反过来说,姓名和年龄是猫和狗的基本信息也是它们的共同特性。

重写父类方法或变量

一般重写父类方法,是因为你把猫当成是一个基类,而将狗继承自猫类。看似这很好笑,但如果你去翻翻你的代码,这种情况多如牛毛。当然,如果你不需要继承,那就另说了。那么如果碰到这种情况,我们怎么重写基类呢?很简单,在子类中定义一个和父类中一样的方法,如下面这样:

class Animal{
 private String name;
 private int age;

 public void setName(String name){
  this.name = name;
 }
 public void setAge(int age){
  this.age = age;
 }
 public String getName(){
  return this.name;
 }
 public int getAge(){
  return this.age;
 }
}
class Dog extends Animal{
 public String getName(){
  return super.getName() + "2";
 }
 public void voice(){
  System.out.println(super.getName() + " 汪");
 }
 public void eat(){
  System.out.println(super.getName() + " Bone");
 }
}
class app{
 public static void main(String[] args){
  Dog dog = new Dog();
  dog.setName("大黑");
  System.out.println(dog.getName()); //执行的是Dog中的getName方法
 }
}

提示:通过super可以在子类中直接调用父类的方法以及变量,通过this调用当前类。

我觉得把这叫做重写不太好,因为如果从本质来讲,它不算重写,只是Java寻找变量以及方法的规则罢了。Java会先看一下,自己身上有没有某个变量或方法,如果没有,它会接着到父类中找,如果父类中还是没有,那么它又会到更上一级中找,如果一直找上去都没有,那么才报错。

在重写父类时,需要注意一下,重写时,方法的返回值类型必须和父类中定义的一致,如果是数字类型,只要重写时类型不大于父类中定义的,那么也是可以的。比如下面这样就会报错

class Animal{
 private String name;
 public void setName(String name){
  this.name = name;
 }
 public String getName(){
  return this.name;
 }
}
class Dog extends Animal{
 public int getName(){ //和父类中的getName返回值不同,报错
  return 123;
 }
}
class app{
 public static void main(String[] args){
  Dog dog = new Dog();
  System.out.println(dog.getName());
 }
}

另外还需要注意,如果重写时,和父类中的参数不一致,则会发生意想不到的事,比如下面这个

class Animal{
 private String name;

 public void setName(String name){
  this.name = name;
 }
 public String getName(String hello){
  return this.name + hello;
 }
}
class Dog extends Animal{
 public String getName(){
  return "123";
 }
}
class app{
 public static void main(String[] args){
  Dog dog = new Dog();
  dog.setName("大黑");
  System.out.println(dog.getName("hello"));
 }
}

------Output------
大黑hello

可以看到当我们给getName传达了参数时,执行的是Animal中的方法,而非Dog中的getName方法,也就是说如果参数不一致最后执行的可能就不是重写的那个方法。另外也不可将父类公开的方法或变量改成私有(如将public改成private),否则也会报错,我估计是Java有一套覆盖规则,如果没有达到条件就不会进行覆盖。

总结来,覆盖父类方法或变量时,对其只能更宽松,而反过来则不行。

多态

先来几个例子,再讲理论

class Animal{
 public int age = 5;

 public int getAge(){
  return age;
 }
}
class Dog extends Animal{
 public int age = 8;

 public int getAge(){
  return age + 2;
 }
}
class app{
 public static void main(String[] args){
  Animal dog = new Dog();
  System.out.println(dog.age);
 }
}

------Output------
5

看Animal dog = new Dog();这么一句话,可以发现它们的类型并不一样,但却可以正常运行,之所以可以运行是因为,Dog类是Animal的子类,而父类是包括子类的。我们说动物,那么狗是不是就是动物中的一员呢,这是肯定的,而这里之所以如果运行也正是这个理。

不过需要注意一下,通过这种方式创建的对象,在获取实例变量时,获取到的是父类中的实例变量,如果是方法,则看子类中是否存在和父类中同名的方法,如果存在则使用子类中的方法,但是如果子类中有某个方法,而父类中没有,那么就会报错。如下这段代码就会报错

class Animal{
 public int age = 5;
 public int getAge(){
  return age;
 }
}
class Dog extends Animal{
 public int age = 8;

 public int getAge(){
  return age + 2;
 }
 public setAge(int a){
  this.age = a;
 }
}
class app{
 public static void main(String[] args){
  Animal dog = new Dog();
  System.out.println(dog.setAge(5));
 }
}

因为父类中没有setAge这个方法,因此会报错。

也就是说,通过这种方式来写,只能达到覆盖方法的效果,没有其他的功能。

这里所谓的多态,在程序中你可以理解成,一个方法,它可以有不同的效果,那怎么实现不同的效果呢?在java中通过切换类型来实现(不一定正确)。

多态有什么用?

还是再来看几个例子吧

class Animal{
 public int age = 5;

 public int getAge(){
  return age;
 }
}
class Dog extends Animal{
 public int getAge(){
  return age + 2;
 }
}
class Cat extends Animal{
 public int getAge(){
  return age + 3;
 }
}
class app{
 public static void main(String[] args){
  Animal dog = new Dog();
  Animal cat = new Cat();

  System.out.println(dog.getAge());
  System.out.println(cat.getAge());
 }
}

------Output------
7
8

可以看到,它会根据自身执行不同的方法。不过话说回来,这并不能代表什么,毕竟我们按照正常情况来创建,效果也可以一样,不过还真有它的用武之处,比如下面这段代码

class Animal{
 public int age = 5;

 public int getAge(){
  return age;
 }
}
class Dog extends Animal{
 public int getAge(){
  return age + 2;
 }
}
class Cat extends Animal{
 public int getAge(){
  return age + 3;
 }
}
class app{
 public static void main(String[] args){
  Animal[] animals = new Animal[2];
  animals[0] = new Dog();
  animals[1] = new Cat();

  System.out.println(animals[0].getAge());
  System.out.println(animals[1].getAge());
 }
}

------Output------
7
8

这段代码和上面一段差不多,不过这段代码中用的是一个数组,这种情况就比较适合使用多态了,不然好像没有其他办法来弄了吧(初学java,不太懂)。在这里面多态不仅仅只是指一个方法有不同的效果,在这里还指类型的多样性。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持我们!

(0)

相关推荐

  • C++/java 继承类的多态详解及实例代码

    C++/java 继承类的多态详解 学过C++和Java的人都知道,他们二者由于都可以进行面向对象编程,而面向对象编程的三大特性就是封装.继承.多态,所有今天我们就来简单了解一下C++和Java在多态这方面的不同. 首先我们各看一个案例. C++ //测试继承与多态 class Animal { public: char name[128]; char behavior[128]; void outPut() { cout << "Animal" << endl

  • Java中继承thread类与实现Runnable接口的比较

    Java中线程的创建有两种方式:  1.  通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2.  通过实现Runnable接口,实例化Thread类 在实际应用中,我们经常用到多线程,如车站的售票系统,车站的各个售票口相当于各个线程.当我们做这个系统的时候可能会想到两种方式来实现,继承Thread类或实现Runnable接口,现在看一下这两种方式实现的两种结果. package com.threadtest; class MyThread extends T

  • Java 继承与多态的深入理解

    Java 继承与多态的深入理解 1.  什么是继承,继承的特点? 子类继承父类的特征和行为,使得子类具有父类的各种属性和方法.或子类从父类继承方法,使得子类具有父类相同的行为. 特点:在继承关系中,父类更通用.子类更具体.父类具有更一般的特征和行为,而子类除了具有父类的特征和行为,还具有一些自己特殊的特征和行为. 在继承关系中.父类和子类需要满足is-a的关系.子类是父类. 表示父类和子类的术语:父类和子类.超类和子类.基类和派生类,他们表示的是同一个意思. 2.  为什么需要继承?什么时候应该

  • 详解java中继承关系类加载顺序问题

    详解java中继承关系类加载顺序问题 实例代码: /** * Created by fei on 2017/5/31. */ public class SonClass extends ParentClass{ public SonClass(){ System.out.println("SonClass's constructor"); } { System.out.println("SonClass's block");} static { System.out

  • java 中继承和多态详细介绍

    继承和多态 一.this super关键字 1.this: 可以在构造器中的第一代码中调用本类中的其他构造器.this(参数) 非类方法参数中隐式传入的参数,表示调用当前方法的对象. 2.super: 可以在构造器的第一句代码调用父类的构造器.super(参数). 非静态方法中表示继承的父类对象,可以调用父类方法和属性. 二.方法的覆写: 子类重新实现了和父类一样的方法.访问修饰和异常都必须至少和父类的相同或者更大的范围. 三.方法的重载: 相同的方法的名字不同的参数列表. 四.多态: java

  • Java IO流体系继承结构图_动力节点Java学院整理

    Java IO体系结构看似庞大复杂,其实有规律可循,要弄清楚其结构,需要明白两点: 1. 其对称性质:InputStream 与 OutputStream, Reader 与 Writer,他们分别是一套字节输入-输出,字符输入-输出体系 2. 原始处理器(适配器)与链接流处理器(装饰器) 其结构图如下: Reader-Writer体系 1. 基类 InputStream与OutputStream是所有字节型输入输出流的基抽象类,同时也是适配器(原始流处理器)需要适配的对象,也是装饰器(链接流处

  • java继承中的构造方法实例解析

    本文实例讲述了java继承中的构造方法.分享给大家供大家参考.具体如下: 继承中的构造方法: 1.子类的构造过程中必须调用其基类的构造方法. 2.子类可以在自己的构造方法中使用super(argument_list)调用基类的构造方法. 2.1.使用this(argument_list)调用本类的另外构造方法.   2.2.如果调用super,必须写在子类构造方法的第一行. 3.如果子类的构造方法中没有显示的调用基类的构造方法,则系统默认调用基类的无参数构造方法. 4.如果子类构造方法中既没有显

  • 老生常谈 Java中的继承(必看)

    Java作为一面向对象的语言,具备面向对象的三大特征--继承,多态,封装. 继承顾名思义,继任,承接,传承的意思.面向对象的语言有一个好处,就是可以用生活中的例子来说明面向对象的特性.那么我们先来看看生活中的继承关系有哪些?最常见的:父母子女:汽车,电动车,自行车和车.无论哪种车,都有具备车的特性.再比如说:家里面的电饭锅,电磁炉,电冰箱.他们都属于电器类,都具有名字这个属性,也都需要用电这个方法.如果在程序中我们一个个类去把这些重复的代码都写上去,那不是浪费时间和精力吗?联系之前的知识,我们能

  • 详解 Java继承关系下的构造方法调用

    详解 Java继承关系下的构造方法调用 在Java中创建一个类的对象时,如果该类存在父类,则先调用父类的构造方法,然后再调用子类的构造方法.如果父类没有定义构造方法,则调用编译器自动创建的不带参数的默认构造方法.如果父类定义了public的无参的构造方法,则在调用子类的构造方法前会自动先调用该无参的构造方法.如果父类只有有参的构造方法,没有无参的构造方法,则子类必须在构造方法中必须显式调用super(参数列表)来指定某个有参的构造方法.如果父类定义有无参的构造方法,但无参的构造方法声明为priv

  • 详解Java中的封装、继承、多态

    封装 在如何理解面向对象这篇文章中,提到所谓的封装就是"功能都给你做好了,你不必去理解它是怎么写出来的,直接使用即可.".但你得清楚一点,那就是这句话是相对于使用者来说的,而作为开发者,封装就得我们自己来干. 那么作为开发者,我们应该如何去封装呢?其实你应该反过来问,他们应该如何去使用,这样一想会简单很多,作为使用者,自然是希望越简单越好,也就是说,一些复杂的东西,我们不应该让使用者去操作,那也就是说我们应该把复杂的,以及不必要的参数给它封死,不让使用者去操作. 为什么不让使用者去操作

  • 详解Java Socket通信封装MIna框架

    核心类 IoService :Mina中将服务端和客户端都看成是服务,这里提供统一接口IoService,这个接口的作用就是用来处理套接字机制.也正是IoService来监听消息返回消息这些步骤,可以说IoService就是我们Mina中核心 IoProcessor:这个接口在另一个线程上,负责检查是否有数据在通道上读写,也就是说它也拥有自己的Selector,这是与我们使用JAVA NIO 编码时的一个不同之处,通常在JAVA NIO 编码中,我们都是使用一个Selector,也就是不区分Io

  • 详解Java中的 枚举与泛型

    详解Java中的 枚举与泛型 一:首先从枚举开始说起 枚举类型是JDK5.0的新特征.Sun引进了一个全新的关键字enum来定义一个枚举类.下面就是一个典型枚举类型的定义: public enum Color{ RED,BLUE,BLACK,YELLOW,GREEN } 显然,enum很像特殊的class,实际上enum声明定义的类型就是一个类. 而这些类都是类库中Enum类的子类(Java.lang.Enum).它们继承了这个Enum中的许多有用的方法.我们对代码编译之后发现,编译器将 enu

  • 详解Java 中泛型的实现原理

    泛型是 Java 开发中常用的技术,了解泛型的几种形式和实现泛型的基本原理,有助于写出更优质的代码.本文总结了 Java 泛型的三种形式以及泛型实现原理. 泛型 泛型的本质是对类型进行参数化,在代码逻辑不关注具体的数据类型时使用.例如:实现一个通用的排序算法,此时关注的是算法本身,而非排序的对象的类型. 泛型方法 如下定义了一个泛型方法, 声明了一个类型变量,它可以应用于参数,返回值,和方法内的代码逻辑. class GenericMethod{ public <T> T[] sort(T[]

  • 详解Java中的hashcode

    一.什么是hash Hash,一般翻译做散列.杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值.简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数. 这个说的有点官方,你就可以把它简单的理解为一个key,就像是map的key值一样,是不可重复的. 二.hash有什么用?,

  • 详解Java中的反射机制和动态代理

    一.反射概述 反射机制指的是Java在运行时候有一种自观的能力,能够了解自身的情况为下一步做准备,其想表达的意思就是:在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制.通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以,这是一种动态获取类的信息以及动态调用对象方法的能力. 想要使用反射机制,就必须要先获取到该类

  • 详解Java中方法重写和方法重载的6个区别

    目录 1.方法重写 1.1 基本用法 1.2 使用场景 1.3 注意事项 2.方法重载 2.1 基本使用 2.2 使用场景 2.3 注意事项 3.方法重写 VS 方法重载 总结 方法重写(Override)和方法重载(Overload)都是面向对象编程中,多态特性的不同体现,但二者本身并无关联,它们的区别犹如马德华之于刘德华的区别,除了名字长得像之外,其他的都不像. 接下来咱们就来扒一下二者的具体区别. 1.方法重写 方法重写(Override)是一种语言特性,它是多态的具体表现,它允许子类重新

  • 一文详解Java中的类加载机制

    目录 一.前言 二.类加载的时机 2.1 类加载过程 2.2 什么时候类初始化 2.3 被动引用不会初始化 三.类加载的过程 3.1 加载 3.2 验证 3.3 准备 3.4 解析 3.5 初始化 四.父类和子类初始化过程中的执行顺序 五.类加载器 5.1 类与类加载器 5.2 双亲委派模型 5.3 破坏双亲委派模型 六.Java模块化系统 一.前言 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程

  • 详解Java中static关键字和内部类的使用

    目录 一. static 关键字 1. static修饰成员变量 2. static修饰成员方法 3. static成员变量的初始化 二. 内部类 1. 实例内部类 2. 静态内部类 3. 局部内部类 4. 匿名内部类 一. static 关键字 在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的. 1. static修饰成员变量 static修饰的成员变量,称为静态成员变量 [静态成员变量特性]: 不属于某个具体的对象,是类的属

随机推荐