浅谈Java中向上造型向下造型和接口回调中的问题

最近回顾了一下java继承中的问题,下面贴代码:

public class Base {

 protected String temp = "base";
 public void fun(){
 System.out.print("BASE fun()");
 }

 public static void main(String[] args) {
 Base b =new Base();//实例化Base对象
 b.fun();   //调用父类中fun()的方法
 System.out.println(b.temp);//访问父类中的成员变量temp 

    /*****************************************************/
 System.out.println("/***************/");
 Son son = new Son();//实例化Son对象
 son.fun();  //调用son的fun方法
 System.out.println(son.temp);//访问son的成员变量temp

 Son S_son=new Son();    //实例化Son对象
 Base B_s =(Base)S_son;   //向上造型-----------相当于Base s =new Son();
 B_s.fun();      //调用子类的fun()方法
 System.out.println(B_s.temp);//访问父类的成员变量temp 

    /******************************************************/
 System.out.println("/***************/");
 Base s =new Son();//向上造型
 s.fun();     //调用子类的方法
 System.out.println(s.temp);//调用父类的成员变量temp

 Base ba =new Son();
 Son so = (Son)ba;      //向下造型
 so.fun();       //调用子类的fun()方法
 System.out.println(so.temp);//访问子类的成员变量temp
 }
}
class Son extends Base{
 protected String temp = "Son";
 public void fun(){
 System.out.print("SON fun()");
 }
}

运行结果:

BASE fun()base
/***************/
SON fun()Son
SON fun()base
/***************/
SON fun()base
SON fun()Son

结论总结:

1、父类的引用变量可以指向子类对象,子类的引用变量不能指向父类对象。

2、向上造型(upcasting):把子类对象直接赋给父类引用,向上转型不用强制转型。如:

Base base = new Son(); 向上造型–隐式

3、向下造型(downcasting):把指向子类对象的父类引用赋给子类引用,要强制转型。

Base b= new Son();

Son s = (Son)base;----------向下造型–必须强制转换,且必须有继承关系

4、upcasting 会丢失子类特有的方法,但是子类overriding 父类的方法,子类对象去 调用方法有效

5、调用方法或成员变量的规律:

前提:子类存在重写方法,父类和子类中同时有相同名称的成员变量。

(1)当进行了向上造型的引用型变量—父类引用变量只能调用子类的重写方法,但父类的引用变量只能访问父类中的成员变量

(2)当进行了向下造型的引用型变量—子类引用变量只调用子类重写方法,子类的引用变量只能访问子类的成员变量。

(3)自己想了个通俗的说法,调用方法,得当前的引用变量指向的对象;调用变量,得看当前引用变量的类型。

即,调方法,看对象,调变量,看类型。(可以试验一下)

接口回调与向上造型

/**
 * 接口回调,和向上造型的对比
 * @author Character_Painter
 *
 */
public class Inter extends Father implements Person,Teacher{
 public void study(){
 System.out.println("学习");
 }
 public void sleep(){
 System.out.println("子类sleep()方法");
 }

public static void main(String[] args) {
 Father f = new Inter();
 f.sleep();//向上转型---调用

 Person p=new Inter();
 p.eat(); //接口回调方法

 Teacher t = new Inter();
 t.teach(); //接口回调方法

}

@Override
public void eat() {
 System.out.println("重写eat()方法");
}

@Override
public void teach() {
 System.out.println("重写teache()方法"); 

}

}

interface Person{
 public void eat();
}
interface Teacher{
 public void teach();
}
class Father{
 public void sleep(){
 System.out.println("父类sleep()方法");
 }
}

运行结果

子类sleep()方法

重写eat()方法

重写teache()方法

总结:

使用接口方式,其目的应该是实现多个父类型的方法实现,强调一个多种实现,而向上造型,针对的是一对一的继承关系,向上造型后,可以调用子类的重写的方法。这就是我认为他们的区别。

补充知识:Java向下转型的意义与塑型的三个方面

一开始学习 Java 时不重视向下转型。一直搞不清楚向下转型的意义和用途,不清楚其实就是不会,那开发的过程肯定也想不到用向下转型。

其实向上转型和向下转型都是很重要的,可能我们平时见向上转型多一点,向上转型也比较好理解。

但是向下转型,会不会觉得很傻,我是要用子类实例对象,先是生成子类实例赋值给父类引用,在将父类引用向下强转给子类引用,这不是多此一举吗?我不向上转型也不向下转型,直接用子类实例就行了。

我开始学习Java时也是这么想的,这误区导致我觉得向下转型就是没用的。

随着技术的提升,我在看开源的项目学习,发现很多地方都用了向下转型的技术,这就让我重视了起来,想要重新来复习(学习)这个知识点。也是搜索了许多博客文章,但都没具体说明向下转型,只是给了例子演示怎么使用,反而是向上转型讲了一堆(可能是我没找到)。

这篇博客就是讲向下转型的,那我们就来学习下向下转型,了解下这种特性的意义和使用场景

新建一个电子产品接口,如下:

public interface Electronics{

}

很简单,什么方法都没有。

新建一个Thinkpad笔记本类,并实现电子产品接口:

public class Thinkpad implements Electronics{

  //Thinkpad引导方法
  public void boot(){
    System.out.println("welcome,I am Thinkpad");
  }

  //使用Thinkpad编程
  public void program(){
    System.out.println("using Thinkpad program");
  }
}

新建一个Mouse鼠标类,并实现电子产品接口:

public class Mouse implements Electronics{

  //鼠标移动
  public void move(){
    System.out.println("move the mouse");
  }

  //鼠标点击
  public void onClick(){
    System.out.println("a click of the mouse");
  }
}

新建一个Keyboard键盘类,并实现电子产品接口:

public class Keyboard implements Electronics{

  //使用键盘输入
  public void input(){
    System.out.println("using Keyboard input");
  }
}

这里子类比较多,是为了更好的理解。每个类的方法的逻辑实现也很简单。打印了一行信息

接下来,我们想象一个情景:我们去商城买电子产品,电子产品很多吧,比如笔记本电脑,鼠标,键盘,步步高点读机哪里不会点哪里,我们用的手机,等等,这些都属于电子产品。电子产品是抽象的。好,那么我们决定买一台Thinkpad,一个鼠标和一个键盘。

这时,我们需要一个购物车来装这些电子产品吧。我们可以添加进购物车,然后通过购物车还能知道存放的电子产品数量,能拿到对应的电子产品。

那么,一个购物车类就出来了,如下:

import java.util.ArrayList;
import java.util.List;
public class ShopCar{

  private List<Electronics> mlist = new ArrayList<Electronics>();
  public void add(Electronics electronics){
    mlist.add(electronics);
  }

  public int getSize(){
    return mlist.size();
  }

  public Electronics getListItem(int position){
    return mlist.get(position);
  }
}

List 集合是用来存放电子产品的,add 方法用来添加电子产品到购物车,getSize 方法用来获取存放的电子产品数量,getListItem 方法用来获取相应的电子产品。

可以看到 List<Electronics> 用了泛型的知识,至于为什么要用泛型?这个不做介绍了,泛型很重要的。

而我觉得比较疑惑的是为什么是放 Electronics 的泛型,而不是放Thinkpad,Mouse,Keyboard,Phone等?

那么如果是List<Thinkpad>,肯定是放不进鼠标Mouse的吧,难道要生成3个集合?这里是定义了3个电子产品类,但是我如果有100种电子产品呢,要定义100个集合?

这太可怕了。所以之前,我们写了一个Electronics接口,提供了一个Electronics的标准,然后让每一个Electronics子类都去实现这个接口。

实际上这里又涉及到了向上转型的知识点,我们虽然在add 方法将子类实例传了进来存放,但子类实例在传进去的过程中也进行了向上转型

所以,此时购物车里存放的子类实例对象,由于向上转型成Electronics,已经丢失了子类独有的方法,以上述例子来分析,Thinkpad实例就是丢失了boot() 和program() 这两个方法,而Mouse实例就是丢失了move()和onClick()这两个方法

但是实际使用Thinkpad或Mouse或Keyboard时,这种情况肯定不是我们想要的

接着我们写一个测试类 Test 去测试购物车里的电子产品。

测试类 Test 如下:

public class Test{

  public static final int THINKPAD = 0;
  public static final int MOUSE = 1;
  public static final int KEYBOARD = 2;

  public static void main(String[] args){

    //添加进购物车
    ShopCar shopcar = new ShopCar();
    shopcar.add(new Thinkpad());
    shopcar.add(new Mouse());
    shopcar.add(new Keyboard());

    //获取大小
    System.out.println("购物车存放的电子产品数量为 ——> "+shopcar.getSize());

    //开始测试thinkpad电脑
    Thinkpad thinkpad = (Thinkpad)shopcar.getListItem(THINKPAD);
    thinkpad.boot();
    thinkpad.program();

    System.out.println("-------------------");

    //开始测试Mouse鼠标
    Mouse mouse = (Mouse)shopcar.getListItem(MOUSE);
    mouse.move();
    mouse.onClick();

    System.out.println("-------------------");

    //开始测试Keyboard键盘
    Keyboard keyboard = (Keyboard)shopcar.getListItem(KEYBOARD);
    keyboard.input();
  }

}

运行截图:

举个例子分析就好

//开始测试thinkpad电脑
Thinkpad thinkpad = (Thinkpad)shopcar.getListItem(THINKPAD);
thinkpad.boot();
thinkpad.program();

shopcar.getListItem(THINKPAD)

这句代码是获取到Electronics类型的实例。不是Thinkpad的实例

通过向下转型,赋值给子类引用

Thinkpad thinkpad = (Thinkpad)shopcar.getListItem(THINKPAD);

这样子类实例又重新获得了因为向上转型而丢失的方法(boot 和program)

总结一下吧,很多时候,我们需要把很多种类的实例对象,全部扔到一个集合。(这句话很重要)

在这个例子里就是把Thinkpad笔记本,Mouse鼠标,KeyBoard键盘等实例对象,全部扔到一个Shopcar购物车集合。

但是肯定不可能给他们每个种类都用一个独立的集合去存放吧,这个时候我们应该寻找到一个标准,接口就是一个标准。这些都是各种电子产品,抽象成电子产品。然后一个Electronics接口就出来了。

在回到刚才,我们把很多种类的实例对象全部扔到一个集合。或许这样比较好理解:把很多种类的子类实例对象全部扔到存放父类实例的集合。

经过了这个过程,子类实例已经赋值给了父类引用(即完成了向上转型),但很遗憾的丢失了子类扩展的方法。

很好的是Java语言有个向下转型的特性,让我们可以重新获得丢失的方法,即强转回子类

所以我们需要用到子类实例的时候,就从那个父类集合里拿出来向下转型就可以了,一样可以使用子类实例对象

……

我在搜索java向下转型的意义时,得到一个比较好的答案是这样的:

最大的用处是java的泛型编程,用处很大,Java的集合类都是这样的。

而在Android开发中,我们在Layout文件夹,用xml写的控件。为什么能在Activity等组件中通过 findViewById() 方法找到呢?为什么 findViewById(R.id.textview) 方法传入TextView的id后,还要转型为TextView呢?这就是 Java 向下转型的一个应用

以上这篇浅谈Java中向上造型向下造型和接口回调中的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • java调用回调机制详解

    调用和回调机制 在一个应用系统中, 无论使用何种语言开发, 必然存在模块之间的调用, 调用的方式分为几种: 1.同步调用 同步调用是最基本并且最简单的一种调用方式, 类A的方法a()调用类B的方法b(), 一直等待b()方法执行完毕, a()方法继续往下走. 这种调用方式适用于方法b()执行时间不长的情况, 因为b()方法执行时间一长或者直接阻塞的话, a()方法的余下代码是无法执行下去的, 这样会造成整个流程的阻塞. 2.异步调用 异步调用是为了解决同步调用可能出现阻塞, 导致整个流程卡住而产

  • Java中的接口回调实例

    定义: /** * @author Administrator * @project: TestOne * @package: PACKAGE_NAME * @date: 2018/11/30 0030 15:42 * @brief: 郭宝 **/ public class Person { /** * 自定义一个接口 **/ public interface OnNameChangeListener{ //接口中的抽象函数,并携带数据 void onNameChange(String name

  • 详解Java的回调机制

    模块之间总是存在这一定的接口,从调用方式上看,可以分为三类:同步调用.回调和异步调用.下面着重详解回调机制. 1. 概述 Java 中的回调机制是一个比较常见的机制,只是有可能在你的程序中使用得比较少,在一些大型的框架中回调机制随处可见.本文就通过一些具体的实例,慢慢走近 Java 的回调机制. 2.回调 所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法.实际在使用的时候,也会有不同的回调形式,比如下面的这几种. 2.1 同步回调 这里我假设

  • 理解Java当中的回调机制(翻译)

    你好,今天我要和大家分享一些东西,举例来说这个在JavaScript中用的很多.我要讲讲回调(callbacks).你知道什么时候用,怎么用这个吗?你真的理解了它在java环境中的用法了吗?当我也问我自己这些问题,这也是我开始研究这些的原因.这个背后的思想是控制反转( PS:维基百科的解释是控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度.)这个范例描述了框架(framework)的工作方式,也以"好莱坞原则:

  • 浅谈Java代码的 微信长链转短链接口使用 post 请求封装Json(实例)

    废话不多说,直接上代码 String longUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + MpUtil.APPID + "&redirect_uri=" + MpUtil.HOMEPAGE + "/nweixinLoginPc.fo%3Frandomcode=" + randomcode + "&response_type=co

  • 浅谈Java中向上造型向下造型和接口回调中的问题

    最近回顾了一下java继承中的问题,下面贴代码: public class Base { protected String temp = "base"; public void fun(){ System.out.print("BASE fun()"); } public static void main(String[] args) { Base b =new Base();//实例化Base对象 b.fun(); //调用父类中fun()的方法 System.o

  • 浅谈java中math类中三种取整函数的区别

    math类中三大取整函数 1.ceil 2.floor 3.round 其实三种取整函数挺简单的.只要记住三个函数名翻译过来的汉语便能轻松理解三大函数,下面一一介绍 1.ceil,意思是天花板,java中叫做向上取整,大于等于该数字的最接近的整数 例: math.ceil(13.2)=14 math.ceil(-13.2)=-13 2.floor,意思是地板,java中叫做向下取整,小于等于该数字的最接近的整数 例: math.floor(13.2)=13 math.floor(-13.2)=-

  • 浅谈Java继承中的转型及其内存分配

    看书的时候被一段代码能凌乱啦,代码是这样的: package 继承; abstract class People { public String tag = "疯狂Java讲义"; //① public String name = "Parent"; String getName(){ return name; } } class Student extends People { //定义一个私有的tag实例变量来隐藏父类的tag实例变量 String tag =

  • 浅谈Java向下转型的意义

    一开始学习 Java 时不重视向下转型.一直搞不清楚向下转型的意义和用途,不清楚其实就是不会,那开发的过程肯定也想不到用向下转型. 其实向上转型和向下转型都是很重要的,可能我们平时见向上转型多一点,向上转型也比较好理解. 但是向下转型,会不会觉得很傻,我是要用子类实例对象,先是生成子类实例赋值给父类引用,在将父类引用向下强转给子类 引用,这不是多此一举吗?我不向上转型也不向下转型,直接用子类实例就行了. 我开始学习Java时也是这么想的,这误区导致我觉得向下转型就是没用的. 随着技术的提升,我在

  • 浅谈Java中Unicode的编码和实现

    Unicode的编码和实现 大概来说,Unicode编码系统可分为编码方式和实现方式两个层次. 编码方式 字符是抽象的最小文本单位.它没有固定的形状(可能是一个字形),而且没有值."A"是一个字符,"€"也是一个字符.字符集是字符的集合.编码字符集是一个字符集,它为每一个字符分配一个唯一数字. Unicode 最初设计是作为一种固定宽度的 16 位字符编码.也就是每个字符占用2个字节.这样理论上一共最多可以表示216(即65536)个字符.上述16位统一码字符构成基

  • 浅谈java里的EL表达式在JSP中不能解析的问题

    今天也碰到了el表达式无法解析的事情,于是在网上查询了下,大多说是因为web.xml中声明的版本问题 于是收集了如下版本: web-app_2_2.xsd <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java

  • 浅谈Java中的四种引用方式的区别

    强引用.软引用.弱引用.虚引用的概念 强引用(StrongReference) 强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用: Object object = new Object(); String str = "hello"; 只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象. 比如下面这段代码: public class Main { publi

  • 浅谈java中BigDecimal的equals与compareTo的区别

    这两天在处理支付金额校验的时候出现了点问题,有个金额比较我用了BigDecimal的equals方法来比较两个金额是否相等,结果导致金额比较出现错误(比如3.0与3.00的比较等). [注:以下所讲都是以sun jdk 1.4.2版本为例,其他版本实现未必一致,请忽略] 首先看一下BigDecimal的equals方法: public boolean equals(Object x){ if (!(x instanceof BigDecimal)) return false; BigDecima

  • 浅谈Java中几种常见的比较器的实现方法

    在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题. 通常对象之间的比较可以从两个方面去看: 第一个方面:对象的地址是否一样,也就是是否引用自同一个对象.这种方式可以直接使用"=="来完成. 第二个方面:以对象的某一个属性的角度去比较. 从最新的JDK8而言,有三种实现对象比较的方法: 一.覆写Object类的equals()方法: 二.继承Comparable接口,并实现compareTo()方法: 三.定义一个单独的对象比较器,继承自Comparator接口

随机推荐