首页 > 编程语言 >Java中的类型转换

Java中的类型转换

时间:2023-04-14 19:44:43浏览次数:45  
标签:类型转换 Java 函数 子类 数据类型 类型 父类

强类型语言和弱类型语言

按照数据类型要求,编程语言可以分为强类型语言和弱类型语言。

  • 强类型语言是要求变量在使用之前必须声明数据类型,并且不能随意改变类型的语言,如 Java、C/C++、Golang 等。在编译时就进行类型检查,优点是可以提前发现类型错误,提高程序的效率和安全性,但缺点是需要显式声明变量的类型,增加了编程的复杂度和不灵活性。
  • 弱类型语言是不要求变量声明数据类型,并且可以根据赋值改变类型的语言,如 Python、PHP、JavaScript 等。在运行时才进行类型检查,优点是不需要显式声明变量的类型,简化了编程的过程和提高了灵活性,但缺点是可能在运行时出现类型错误,降低了程序的效率和安全性。

对于强类型语言,为了实现编程的灵活性,必然要涉及到类型转换。

类型转换

Java 类型转换的原理可以分为以下几种情况:

  • 基本数据类型之间的转换,可以分为自动类型转换和强制类型转换。自动类型转换是从存储范围小的类型转换为存储范围大的类型,如 byte、short、char → int → long → float → double。强制类型转换是从存储范围大的类型转换为存储范围小的类型,如 double → float → long → int → byte、short、char。强制类型转换需要在等号后面的类型前加上强制()转换符,并且可能会造成数据精度的降低或溢出。基本数据类型之间的转换,是将一个数据类型的值的位模式解释为另一个数据类型的值的位模式。在这个过程中,java 使用的规则是将数据类型的值的位模式按照目标数据类型的格式进行解释和存储。如果目标数据类型的容量小于原数据类型的容量,那么可能会发生溢出或精度损失。如果目标数据类型的容量大于原数据类型的容量,那么可能会发生补位或扩展。

  • 基本数据类型和 String 类型之间的转换,可以使用 String 类的静态方法 valueOf()或 toString()将基本数据类型转换为 String 类型,也可以使用基本数据类型对应的包装类的静态方法 parseInt()或 parseDouble()等将 String 类型转换为基本数据类型。

  • 基本数据类型和对应的包装类型之间的转换,可以使用自动装箱和拆箱完成。自动装箱是将基本数据类型的值赋给对应的包装类对象,拆箱是将包装类对象赋给对应的基本数据类型变量。

  • 引用数据类型之间的转换,可以分为向上转型和向下转型。在这个过程中,java使用的规则是检查两个引用类型之间是否存在继承关系,如果存在,那么可以进行向上转型或向下转型。 向上转型是将子类类型转换为父类类型,这是一种自动或隐式的转换。 向下转型是将父类类型转换为子类类型,这是一种强制或显式的转换。 在进行向下转型时,java会在运行时检查实际指向的对象是否是目标子类的实例,如果不是,那么会抛出ClassCastException异常。

对于强类型语言,编译器在编译程序时会进行类型检查,防止出现类型转换的错误。

多态

Java 多态是指当父类中的一个方法在不同的子类对象中被重载时,父类引用指向子类对象可以调用子类中重载的方法。

Java 多态的实现有三个必要条件:继承、重写和向上转型。

  • 继承:子类继承父类或实现接口,从而具有父类或接口中定义的方法。
  • 重写:子类重写父类或接口中的方法,根据自己的特性提供不同的实现逻辑。
  • 向上转型:父类或接口的引用变量指向子类的实例对象,从而可以调用子类重写的方法。

Java 多态的实现机制是动态绑定,也就是在运行时根据引用变量所指向的具体实现对象来调用相应的方法,而不是引用变量的类型中定义的方法。

Java 多态的实现方式有三种:重载、接口和继承。

  • 重载:在同一个类中定义多个同名但参数不同的方法,根据传入参数的类型和个数来调用不同的方法。
  • 接口:定义一个接口,包含一些抽象方法,让不同的类实现该接口,并重写其中的抽象方法,从而实现不同的行为。
  • 继承:定义一个父类,包含一些普通或抽象方法,让不同的子类继承该父类,并重写其中的方法,从而实现不同的行为。

动态绑定

动态绑定是一种调用对象方法的机制,它是指在运行时根据引用变量所指向的对象的实际类型来选择调用相应的方法,而不是引用变量的类型中定义的方法。

C++ 动态绑定机制

对于 C++来说,动态绑定的原理是利用虚函数表和虚函数指针来实现的。

  • 虚函数表:每一个定义了虚函数的类都会有一个虚函数表,它是一个存储虚函数入口地址的数组,每个虚函数占用一个数组元素。
  • 虚函数指针:每一个定义了虚函数的类的对象都会有一个虚函数指针,它指向该对象所属类的虚函数表的首地址。

动态绑定的过程如下:

  • 当使用基类或接口的引用变量调用虚函数时,编译器会根据引用变量的类型找到对应的虚函数表。
  • 然后根据虚函数在虚函数表中的位置(偏移量)找到相应的虚函数入口地址。
  • 最后通过虚函数指针调用该地址所指向的方法,这个方法可能是基类或派生类中重写或覆盖的方法,取决于引用变量所指向的对象的实际类型。

动态绑定的好处是可以实现多态性,即同一个方法在不同的子类对象中有不同的实现方式,从而提高了代码的可扩展性和可维护性。

方法重载不会改变虚函数表的结构,只会增加虚函数表中的条目。虚函数表是一个存储虚函数入口地址的数组,每个虚函数占用一个数组元素。如果一个类中有多个重载的虚函数,那么它们都会被放入虚函数表中,按照声明顺序和参数类型来区分。

例如,如果有一个类 A,它有两个重载的虚函数 vfunc1 (),如下所示:

class A { 
    public: 
	    virtual void vfunc1 (); 
	    virtual void vfunc1 (int); 
};

那么它的虚函数表大致如下:

A_vtbl  -->  A::vfunc1 () 
             A::vfunc1 (int)

当使用基类或接口的引用变量调用虚函数时,编译器会根据引用变量的类型找到对应的虚函数表,然后根据虚函数在虚函数表中的位置(偏移量)找到相应的虚函数入口地址,最后通过虚函数指针调用该地址所指向的方法。
例如,如果有一个类 B 继承自类 A,并重写了 vfunc1 () 方法,如下所示:

class B: public A { 
	public: 
		virtual void vfunc1 (); 
};

那么它的虚函数表大致如下:

B_vtbl  -->  B::vfunc1 ()
             A::vfunc1 (int)

如果有一个基类指针 p 指向一个 B 类对象,那么当调用 p->vfunc1 () 时,编译器会先找到 B 类的虚函数表,然后在虚表中查找所调用的函数对应的条目。由于虚表在编译阶段就可以构造出来了,所以可以根据所调用的函数定位到虚表中的对应条目。对于 p->vfunc1 () 的调用,B vtbl 的第一项即是 vfunc1 () 对应的条目。

最后,根据虚表中找到的函数指针,调用函数。在这个例子中,就是调用 B 类重写的 vfunc1 () 方法。

子类的虚函数表与父类的虚函数表有以下不同点:

  • 子类和父类不会公用一个虚函数表,每个类都会有一个虚函数表。
  • 子类的虚函数表会继承父类的虚函数表中的指针,如果子类没有重写父类的虚函数,那么子类虚函数表中仍然会有该函数的地址,只不过这个地址指向的是父类的虚函数实现。
  • 子类的虚函数表中如果重写了父类的虚函数,那么子类虚函数表中对应的指针会被修改为指向子类的虚函数实现。
  • 子类的虚函数表中如果新增了虚函数,那么子类虚函数表中会增加新的指针,指向子类新增的虚函数实现,并且按照声明顺序存放在父类虚函数表指针之后。

Java 动态绑定机制

Java 动态绑定的机制是指当调用对象方法时,该方法会和该对象的内存地址或运行类型绑定,而不是和对象变量的声明类型绑定。Java 动态绑定的机制和 C++有一些相似之处,也有一些不同之处。相似之处在于:

  • 两种语言都是通过虚函数表和虚函数指针来实现动态绑定的,即在运行时根据对象的实际类型来调用相应的虚函数。
  • 两种语言都是只有虚函数才能实现动态绑定,而非虚函数则是静态绑定,即在编译时就确定调用哪个函数。
  • 两种语言都需要通过基类指针或引用来调用子类对象的虚函数,才能体现出多态性。

不同之处在于:

  • Java 中所有的非静态方法都是默认为虚函数的,而 C++中需要用 virtual 关键字来显式声明虚函数。
  • Java 中对象的属性没有动态绑定机制,即总是访问对象变量的声明类型所定义的属性,而不是对象的实际类型所定义的属性。而 C++中对象的属性也可以实现动态绑定,只要将属性声明为虚函数,并在子类中重写该虚函数。
  • Java 中没有多重继承,只有单一继承和接口实现,因此不存在菱形继承问题,也不需要虚基类表来解决共享基类问题。而 C++中有多重继承,因此需要处理菱形继承问题,并使用虚基类表来记录虚基类子对象相对于虚基类表指针的偏移量。

因此,java 动态绑定的机制和 C++动态绑定的机制在原理上是类似的,但在具体实现上有一些差异,主要体现在虚函数的声明、属性的访问和多重继承方面。

对于 Java 的多态来说,当把子类对象赋值给父类对象时,引用的地址并没有发生改变,如:

class Example {
    public void fun1(){
        System.out.println("Example fun1");
    }
    public void fun2(){
        System.out.println("Example fun2");
    }
}

class Example1 extends Example{
    @Override
    public void fun1(){
        System.out.println("Example1 fun1");
    }
    public void fun3(){
        System.out.println("Example1 fun3");
    }
}

public class Test{
    public static void main(String[] args){
        Example e = new Example1();
        Example1 e1 = (Example1)e;
        e.fun1();
        System.out.println(e);          // Example1@15db9742
        System.out.println(e1);         // Example1@15db9742
    }
}

对于上述程序,当我们调用 e.fun3() 时,会编译不通过,并不是说将 Example1 的实例赋值给 e 时,e 所指向的地址块中不包含 fun3() 的代码,只是编译器在执行类型检查时不允许我们这么做。

标签:类型转换,Java,函数,子类,数据类型,类型,父类
From: https://www.cnblogs.com/fhhw/p/17319658.html

相关文章

  • Java接收到MySQL数据库查询出的date类型的数据输出格式不对
    问题查询某条数据,里面有个effective_time字段,数据库里保存的该条数据的effective_time的值是2023-04-13,但是使用postman调用接口,返回的确是2023-04-12T16:00:00.000+00:00,不仅格式不对,而且时间还慢了一天。但是在application.yml中配置数据库连接的时候,确实指定了时区......
  • Linux java生成图片文字乱码问题
     场景:用java生成图片文字,在windows运行没问题,发布到linux中发现文字乱码了原因:图片是在Linux下的系统里生成的,用到java.awt.Font这个类。实例化的时候它会到JRE里去找字体,如果找不到对应字体,就会显示方框解决方法:把需要的字体文件从windows系统中粘贴到Linux一份,重启服务器......
  • JavaSE06方法
    1.方法概述1.1方法的概念​ 方法(method)是程序中最小的执行单元注意:方法必须先创建才可以使用,该过程成为方法定义方法创建后并不是直接可以运行的,需要手动使用后,才执行,该过程成为方法调用2.方法的定义和调用2.1无参数方法定义和调用定义格式:publicstaticvoid......
  • javaweb实验二
    实验项目名称:实验二  服务器端简单程序设计 一、实验目的通过一个小型网站的开发,加深对session,request,response,cookie等对象的理解,掌握其使用方法,进一步深入掌握HTML、CSS和JavaScript等知识。二、实验内容和基本要求1)编写index.jsp文件,展示某一类物品或知识的介绍,可以......
  • 【Java】初学Vert.x(1)
    今天开始将分享自己初学Vert.x的一些经验和看法。里面有不足或想得不周到的地方,还请各位看官多多包涵。为了更贴合实际,本次分享将结合实例进行叙述。实例将提供一个简单的RESTful接口用以收集埋点数据,希望能帮助各位理解。1.前言本人学习Vert.x的初衷是为了找一个Springboot的平替......
  • Java基础语法
    Java基础语法注释、标识符、关键字注释:注释并不会被执行,是给我们写代码的人看的。分为单行注释,多行注释,文档注释。标识符:Java所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。关键字:标识符注意点:所有的标识符都应该以字母,美元符($),下划线(_)开始。首字母......
  • Java中的long与double的区别
    1.long与double在java中本身都是用64位存储的,但是他们的存储方式不同,导致double可储存的范围比long大很多2.long可以准确存储19位数字,而double只能准备存储16位数字(实际测试,是17位,)。double由于有exp位,可以存16位以上的数字,但是需要以低位的不精确作为代价。如果一个大于17位的lo......
  • JavaScript 中 new Date().getTime() 方法在 iOS 中的兼容性问题
    JavaScript中newDate(time).getTime()获取时间戳方法在iOS中的兼容性问题在iOS系统的H5页面中获取时间戳方法newDate(time).getTime()存在返回NaN或结果不准确的情况在iPhone8中iOS11.03系统下的H5页面测试newDate(time).getTime()方法测试代码:测试结......
  • java文件复制,文件下载
    1.下载文件  publicvoidexportOpenFile(HttpServletResponseresponse){//通过工具类创建writerFileInputStreamfis=null;ServletOutputStreamsos=null;try{//设置响应头response.setContentTy......
  • JavaScript 使用 reduce 方法实现简单的 i18n 功能
    JavaScript使用reduce方法实现简单的i18n功能i18n:国际化(Internationalization)的缩写使用Array.prototype.reduce()方法实现简单的i18n功能reduce()方法对数组中的每个元素按序执行一个由您提供的reducer函数,每一次运行reducer会将先前元素的计算结果作为参......