Java基础重构-数据类型

简介: String是基本数据类型吗?String a=new String(“abc”) ,创建了几个String Object?

什么是强类型语言?

什么是强类型语言:

  • 所有变量必须先声明,后使用
  • 指定类型的变量智能接收类型与之匹配的值。这意味着每个变量和表达式打有一个在编译时就确定的类型。类型限制了一个变量能被赋的值,限制了一个表达式可以产生的值,限制了在这些值上可以进行的操作。

什么是变量?

编程实际上就是对内存中数据的访问和修改,程序员需要一种机制来访问或修改内存中的数据,这种机制就是变量.

每个变量都代表了某一小块内存,而且变量是由名字的,程序对变量赋值,实际上就是吧数据装入该变量所代表的内存区的过程。程序读取变量的值,也就是从该变量所代表的内存区取值的过程。

简单来说,变量相当于一个有名称的容器,该容器用于装不同类型的的数据。

String是基本数据类型吗?String a=new String(“abc”) ,创建了几个String Object?

不是,产生了两个

一个是直接量的“ abc” 字符串对象,该字符串将会被缓存在字符串常量池中,以便以后复用这个字符串;另一个是通过new String() 构造器创建出来的String 对象。这个对象保存在堆内存中。

堆与栈的区别?

栈内存:栈内存首先时一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量。for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后在定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的声明周期很短。

堆内存:存储的是数组和对象,凡是new建立的都是在堆中,栈中只是持有了一个引用而已。堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里面存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java的垃圾回收不定时的会回收。

比如下面的这个Demo

int [] ar=new Int[3],在内存中是怎么定义的:

主函数先进栈,在栈中定义了一个变量arr,接下来为 arr 赋值,但是右边不是一个具体值,是一个实体。实体创建在堆里,在堆里首先通过 new 关键字开辟了一个空间,内存在存储数据的时候都是通过地址来体现的,地址是一块连续的二进制,然后给这个实体分配一个 内存地址 。数组都是有一个索引,数组这个实体在堆内存中产生之后每一个空间都会进行默认的初始化(这是堆内存的特点,未初始化的数据是不能用的,但是在堆里是可以用的,因为初始化过了,凡是在栈里没有),不同的类型初始化的值不一样。所以堆和栈就创建了实体和变量。

image.png

那么堆和栈是怎么联系起来的呢?

我们在上面给堆分配了一个地址,吧堆的地址赋给 arr,arr就通过地址指向了数组,所以arr 想操纵数组时,就通过地址,而不是直接把实体都赋给它。这种我们不再叫它基本数据类型,而是叫引用类型数据。称为 arr 引用了堆内存当中的实体。

image.png

如果当int [] arr=null;

arr不做任何指向,null的作用就是取消引用数据类型的指向。

当一个实体,没有引用数据类型指向的时候,它在堆内存中不会被释放,而被当做一个垃圾,在不定时的时间内自动回收,因为Java有一个自动回收机制,(而c++没有,需要程序员手动回收,如果不回收就越堆越多,直到撑满内存溢出,所以Java在内存管理上优于c++)。自动回收机制(程序)自动监测堆里是否有垃圾,如果有,就会自动的做垃圾回收的动作,但是什么时候收不一定。

所以堆与栈的区别很明显:

1.栈内存存储的是局部变量而堆内存存储的是实体;

2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。

基本操作

outer (标签)

用于在指定的位置退出循环,但是只能放在循环语句前才有效

public class MyClass {
    public static void main(String[] args) {
        //定义标签
        outer:
        for (int j=5;j<10;j++){
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                if (i==5){
                    //退出标签
                    break  outer;
                }
            }
        }
        System.out.println(123);
    }
}

continue(忽略本次循环)

public class MyClass {
    public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                if (i==5){
                  continue;
                }
                System.out.println(i);
            }
    }
}

break(结束本次循环)


return(结束整个方法,无论嵌套多少循环)

public class MyClass {
    public static void main(String[] args) {
        for (int j = 0; j < 10; j++) {
            for (int i = 0; i < 10; i++) {
                if (j == 5) {
                    return;
                }
                System.out.println(i);
            }
        }
    }
}

数组相关

数组也是一种类型。在同一种数组中,数组的类型是唯一的。


因为Java 是面向对象的语言,所以会产生这一种假象,数组里存了父类的类型,但其实数组元素可以是任意一个子类,其实这个数组的元素类型也是唯一,因为都为父类。


Java 的数组即可以存储基本类型的数据,也可以存储引用类型的数据,只要所有的数组元素具有相同的类型即可。


数组也是一种数据类型,它本身是一种引用类型。例如 int 是一个基本类型, 但 int[] (这是定义数组的一种方式) 就是一种引用类型了。 **因此在使用数组来定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指针),这个引用变量还未指向任何有效的内存,因此定义数组时不能和制定数组的长度。而且由于定义数组只是定义了一个应用变量,并未指向任何有效的内存空间,所以还没有内存空间来存储数组元素,**因此这个数组也不能使用,只有对数组进行初始化后才可以使用。


数组的初始化

数组必须先初始化,然后才可以使用,所谓初始化,也就是为数组元素分配内存空间,并为每个数组元素赋初值。

  • 静态初始化 由程序员显示指定每个数组的初始值,由系统决定数组长度。 int [] a={1,2,2};
  • 动态初始化 由系统为数组分配初始值,例如 String[] data=new String[5]

数组元素动态初始化时的默认值:

  • 整数类型(byte,short,int ,long) 默认为 0
  • 浮点类型(float double) ,默认 0.0
  • 字符类型(char) 默认’\u0000’
  • 布尔类型(boolean) 默认 false
  • 引用类型(类,接口和数组) 默认 null

数组在内存中的运行机制

数组时一种引用数据类型,数据引用变量只是一个引用,数组元素和数组变量在内存里是分开存放的。

与所有引用变量相同的是,引用变量时访问真实对象的根本方式,也就是说,如果希望在程序中访问数组对象本身,则只能通过这个数组的引用变量来访问它。

实际的数组对象呗存储在 堆(heap) 内存中;如果引用该数组对象的数组引用变量是一个局部变量,那么他被存储在 栈(stack) 内存中。

[外链图片转存失败(img-WLVehghM-1567944851879)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553134862198.png)]

如果需要访问堆内存的数组元素,则程序只能通过 p[index] 的形式实现。也就是说,数组引用变量时访问堆内存中数组元素的根本方式。


为什么有栈内存和堆内存之分?

当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块内存里,随着方法的执行结束,这个方法内存也将自然销毁。因此,所有在方法中定义的局部变量都是放在栈内存中的;在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成果通常较大),这个运行时数据区就是堆内存。

堆内存中的对象不会随方法的结束而销毁,及时方法结束后,这个对象还可能被另一个引用变量所引用(如方法的参数传递),则这个对象依然不会被销毁。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收才会在合适的时候回收他。

如果堆内存中数组不再有任何引用变量指向自己,则这个数组将成为垃圾,该数组所占的内存将会被系统的垃圾回收机制回收。因此,为了让垃圾回收机制回收一个数组所占的内存空间,可以将该数组变量赋为null, 也就切断了数组引用变量和实际数组之间的引用关系,实际的数组也就成了垃圾。

数组的初始化-图例解释

下面的示意图,解释了数组在定义并初始化后,如果将其指向另一个对象,其在内存中的变化情况

[外链图片转存失败(img-my1h6nii-1567944851880)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553136645114.png)]

定义了并初始化了一个数组后,在内存中分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身。

当程序定义并初始化了 a,b两个数组后,系统内存中实际上产生了4块内存区,其中栈内存中有两块引用变量,a 和 b;堆内存中也有两块内存区,分别用于存储 a 和 b 引用所指向的数组本身。

当执行b=a后,系统会将a的值赋给b,a 和 b 都是引用类型变量,存储的是地址,因此把a 的值 赋给 b后,就是让 b指向 a所指向的地址,此时,如果更改a中一个 postion的值,那么 b此时 postion对应的值也已经被更改。 执行了 b=a,后,堆内存的第一个数组具有了两个引用,a变量和b变量都引用了第一个数组。此时第二个数组失去了引用,变成了垃圾,只有等待垃圾回收机制来回收它,但它的长度不会改变,直到它彻底消失。

基本类型数组的初始化

对于基本类型数组而言,数组元素的值直接存储在对应的数组元素中,因此,初始化数组时,先为该数组分配内存空间,然后直接将数组元素的指存入对应数组元素中。

下面用一个int[] 类型的数组变量,采用了动态初始化的方式初始化了该数组,并显示为每个数组元素赋值

public class MyClass {
    public static void main(String[] args) {
        //定义一个数组变量
        int[] a; 
        //动态初始化
        a= new int[5];
        //为每一个元素赋值
        for (int i = 0; i < a.length; i++) {
            a[i] = i+10;
        }
        //遍历输出
        for (int i : a) {
            System.out.println(i);
        }
    }
}

[外链图片转存失败(img-FjBoMpFU-1567944851881)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553138743064.png)]

执行了第一行代码 int[] a;时,只是定义了一个数组变量,此时内存中的存储如图所示。也就是仅仅只在栈内存中定义了一个空引用(就是 a数组变量),这个引用并未指向任何有效的内存,当然无法指定数组的长度。

[外链图片转存失败(img-eIdfYYdO-1567944851881)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553139042182.png)]

当执行了 a=new int[5];后,系统将负责为该数组分配内存空间,并分配默认的初始值,所有数组元素都被赋为0。

[外链图片转存失败(img-7f8PN8z4-1567944851882)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553139179687.png)]

当执行了循环赋值后,此时每个数组元素的值都变成程序显示指定的值,存储示意图如上所示。

当操作基本类型数组的数组元素时,实际上也就相当于操作基本类型的变量。

引用类型数组的初始化

引用类型数组的数组元素是引用。每个数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了有效数据。

public class MyClass {
    public static void main(String[] args) {
        //1.定义一个Person类型的数组变量
        Person[] person;
        //2.执行动态初始化
        person=new Person[2];
        //3.实例化Person对象,并为它的属性赋值
        Person a1=new Person();
        a1.name="Petterp1";
        a1.pswd="123";
        Person a2=new Person();
        a2.name="Petterp1";
        a2.pswd="123";
        //4.将对象赋给数组对应的下标
        person[0]=a1;
        person[1]=a2;
    }
}
class Person{
    String name;
    String pswd;
}

[外链图片转存失败(img-vFOfIGkw-1567944851882)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553140093651.png)]


[外链图片转存失败(img-iEB6NTYq-1567944851883)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553140239998.png)]


[外链图片转存失败(img-etZJTYKH-1567944851884)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553140619424.png)]


[外链图片转存失败(img-0YhFfTqf-1567944851885)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553140860181.png)]从这里可以看出,a1与person[0]指向的是同一块内存区,如果他们有一方更改了里面的值,那么另一方也会受到影响。这种情况在多线程里,多个线程同时操作一个对象,那么势必要对他们进行安全性方面的改进,比如加锁机制。


没有多维数组

Java 语言里提供了 支持多维数组的语法,但如果从底层的运行机制来说,没有多维数组。

Java 语言里的数组时引用类型,因此数组变量其实是一个引用,这个引用指向指向真实的数组内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像多维数组。

public class MyClass {
    public static void main(String[] args) {
      int [][] a=new int[2][];
      a[0]=new int[2];
      a[0][1]=6;
      for (int i=0;i<a.length;i++){
          System.out.println(a[0][i]);
      }
    }
}

[外链图片转存失败(img-udDs3U1E-1567944851886)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553149917518.png)]

//动态初始化二维数组
int [][] b=new int[3][4];

其再内存中的变化。


[外链图片转存失败(img-gKE1FDmC-1567944851886)(C:\Users\15094\AppData\Roaming\Typora\typora-user-images\1553151550969.png)]

可不可以让二维数组再指向此指向另一个数组,这样不就可以组成三维数组?

不能,至少在上面这个程序中不能。因为 Java 是强类型语言,当定义 a 数组时,已经确定了a 数组的数组元素 是 int[] 类型,则 a[0] 数组的数组元素只能是 int[] 类型,则 a[0] 数组的数组元素只能是 int 类型,所以灰色覆盖的数组元素 只能存储 int 类型的变量。如果想在 java 语言中实现无线扩展的数组,则可以定义一个Object[] 类型的数组,这个数组的元素是 Object 类型,因此可以再次指向一个 object[] 类型的数组,这样就可以从一维数组扩充到二维数组。。。

也可以说:二维数组是一对数组,其数组元素时一维数组,三维数组也是一维数组,其数组元素为二维数组元素。。。从这个角度来看,Java 语言没有多维数组。

Java8增强的工具类:Arrays

  • int binarySearch(type[] a,trype key): 使用二分法查询key元素值在 a 数组中出现的索引,如果a 数组不包含key 元素值,则返回 负数。调用该方法是要求数组中元素已经按升序排列,这样才能得到结果。
  • int binarySerach(type[] a,int fromIndex,int toIndex,type key): 这个方法与前一个类似,但他只搜索 a 数组中 fromIndex 到 toIndex 索引的元素,调用该方法是要求数组中元素已经按升序排列,这样才能得到正确结果。
  • type[] copyOf(type [] original,int from ,int to) 只复制original 数组 from 索引到 to索引的元素。
  • boolean equals(type[] a,type[] a2): 如果a数组和 a2数组长度相等,而且 a数组和 a2数组的数组元素也一一相等,该方法将返回 true.
  • void fill(type []a,type val) 该方法将会把a数组的所有元素度赋值为val
  • void fill(type []a,int fromIndex,int toIndex,type val): 该方法与前一个方法的作用相同,区别只是该方法仅将a数组的 fromIndex 到 toIndex 索引的数组元素赋值为 val
  • void sort(type[] a) 升序
  • void sort(type[] a,int fromIndex,int toIndex): 该方法与前一个方法相似,区别是该方法仅仅对fromIndex到 toIndex 下标所有的元素进行排序
  • System.arraycopey(Object a1,in secpos,Object a2,destPos,int length);

a2数组下标从secpos开始,将a1数组里的元素从destPos开始赋值给a2,长度为length

public class MyClass {
    public static void main(String[] args) {
        int[] a = {1, 2, 3,5};
        //查询key在数组中的索引,前提是数组必须升序,如果不存在,则返回负数
        System.out.println(Arrays.binarySearch(a,3));
        //查询数组中,两个索引之间是否存在要找的元素,前提必须是升序,否则返回负数
        System.out.println(Arrays.binarySearch(a,0,a.length,2));
        //Arrays.conpyOf(a,length)  如果lenth>a的长度,则将a放在新数组元素前面,新数组长度为length,剩余的位置,后面补充0(数值类型),null(对象引用),false(布尔类型)
        for (int i:Arrays.copyOf(a,5)){
            System.out.print(i+" ");
        }
        //Arrays.copyOfRange(a,1,2) 返回1-2索引的值。
        System.out.println(Arrays.toString(Arrays.copyOfRange(a, 1, 2)));
        int []b=a;
        //判断两个数组长度,元素是否相等,返回 boolan类型
        System.out.println(Arrays.equals(a,b));
        //将数组中所有元素全部赋值为 1
//        Arrays.fill(a,1);
        System.out.println(Arrays.toString(a));
        //指定范围内的元素赋值为某个值
        Arrays.fill(a,1,3,100);
        System.out.println(Arrays.toString(a));
        int c[]={1,5,7,6,4,8};
        //对指定位置的元素进行排序
        Arrays.sort(c,2,5);
        System.out.println(Arrays.toString(c));
        int a1[]={1,5,7,6,4,8};
        int a2[]={45,12,13,47,12,16};
        //a2下标为0开始,将a1数组里的元素从0开始赋值给a2,长度为3
        System.arraycopy(a1,0,a2,0,3);
        System.out.println(Arrays.toString(a2));
    }
}
目录
相关文章
|
4月前
|
Java
当Java数据类型遇上“爱情”,会擦出怎样的火花?
当Java数据类型遇上“爱情”,会擦出怎样的火花?
56 1
|
3月前
|
Java
java基础(8)数据类型的分类
Java数据类型分为基本数据类型(8种)和引用数据类型。基本类型包括byte, short, int, long, float, double, boolean, char。每种类型有固定占用空间大小,如int占用4字节。字符编码如ASCII和Unicode用于将文字转换为计算机可识别的二进制形式。
82 2
|
4月前
|
Java 程序员
Java数据类型:为什么程序员都爱它?
Java数据类型:为什么程序员都爱它?
56 1
|
25天前
|
Java
Java基础之数据类型
Java基础之数据类型
19 6
|
26天前
|
Java
在Java中如何将基本数据类型转换为String
在Java中,可使用多种方法将基本数据类型(如int、char等)转换为String:1. 使用String.valueOf()方法;2. 利用+运算符与空字符串连接;3. 对于数字类型,也可使用Integer.toString()等特定类型的方法。这些方法简单高效,适用于不同场景。
54 7
|
1月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
57 4
|
1月前
|
存储 消息中间件 NoSQL
使用Java操作Redis数据类型的详解指南
通过使用Jedis库,可以在Java中方便地操作Redis的各种数据类型。本文详细介绍了字符串、哈希、列表、集合和有序集合的基本操作及其对应的Java实现。这些示例展示了如何使用Java与Redis进行交互,为开发高效的Redis客户端应用程序提供了基础。希望本文的指南能帮助您更好地理解和使用Redis,提升应用程序的性能和可靠性。
38 1
|
2月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
89 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
2月前
|
存储 Java 关系型数据库
[Java]“不同族”基本数据类型间只能“强转”吗?
本文探讨了不同位二进制表示范围的计算方法,重点分析了Java中int和char类型之间的转换规则,以及float与int类型之间的转换特性。通过具体示例说明了显式和隐式转换的条件和限制。
39 0
[Java]“不同族”基本数据类型间只能“强转”吗?
|
3月前
|
存储 Java Windows
java基础(9)数据类型中的char类型以及涉及到的转义字符
Java中的char类型可以存储一个中文字符,因为它占用两个字节。转义字符允许在代码中使用特殊字符,例如`\n`表示换行,`\t`表示制表符,`\\`表示反斜杠,`\'`表示单引号,`\"`表示双引号。可以使用`\u`后跟Unicode编码来表示特定的字符。
72 2
java基础(9)数据类型中的char类型以及涉及到的转义字符