数据类型,重载,传参,初始化及类的加载

数据类型,重载,传参,初始化及类的加载,第1张

1.数据类型:基本数据类型,引用数据类型 基本数据类型:

Java中基本数据类型有8个,
其中数值类型有6个(整型+浮点型):
字节byte 1个,short 占2个字节,int 占4个字节,long 占8个字节,float单精度浮点型 占4个字节,双精度浮点型double 占8个字节
字符型1个:char 占2个字节
布尔类型1个:boolean

注意:在计算机中,小数是没有精确的数字的

float:保留小数点后六位

double:保留小数点后15位置

Java 中使用 单引号 + 单个字母 的形式表示字符字面值.
计算机中的字符本质上是一个整数. 在 C 语言中使用 ASCII 表示字符, 而 Java 中使用 Unicode 表示字符. 因此一个字符占用两个字节, 表示的字符种类更多, 包括中文.

String是引用类型,不是基本类型

整型提升:
①不同类型的数据混合运算,范围小的会提升成范围大的
②低于int的类型,会将类似于byte,short提升成int,再参与计算

2.重载

什么是重载?
同一个方法名字,提供不同版本的实现,就叫做方法的重载
1).方法名必须相同
2).返回值不做要求
3).参数类型或者参数个数要有一个条件是不一样的.

3.参数传递

Java的参数是以值传递的形式传入方法中,而不是引用传递.

public static void main(String[] args) {
 	int num = 0;
	func(num);
	System.out.println("num = " + num);
} 
public static void func(int x) {
	x = 10;
	System.out.println("x = " + x);
} 
// 执行结果
x = 10
num = 0

这个是直接进行值传递,进行分析一下:

可以看到,这里进行值传递的时候,参数x接收到传过来的值,x = 0,x也是局部变量.

然后x = 10,此时main方法里面的num无变化,因为根本不相关呀

再来看另外一种情况,参数是引用:

public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    func(arr);
    System.out.println("arr[0] = " + arr[0]);
    System.out.println(Arrays.toString(arr))
} 
public static void func(int[] arr) {
    arr[0] = 10;
    System.out.println("arr[0] = " + arr[0]);
    arr = new int[]{1,2,3};
    System.out.println("arr[0] = " + arr[0]);
} 
// 执行结果
上面 arr[0] = 10
    [10,1,2]
下面 arr[0] = 10
    arr[0] = 1; 

若是刚接触怕不是直接被绕晕了.别急,且听我慢慢道来:

这里传递的是一个数组引用,那么数组引用存放的是什么呢,是一个数组对象的地址呀

请看下图,再听我细锁:

调用方法,都会在栈里开辟栈帧,如上我们调用了两个方法,所以开辟了两个栈帧.

首先在main方法里初始化了一个新数组arr(这种初始化也相当于new了一个数组),这个arr指向上面地址为0x888的数组(这里的地址都是随便整出的),然后调用了func方法,在栈里开辟了一块栈帧.

传递一个数组引用,我们这里传递的实际上只是一个地址,传递的并不是原来的引用,也就是说,这个func参数中的arr同main传递过来的arr不能划等号.实际上就是进行了一次赋值.

比如

int a = 10;
int b = a;
b = 11;
System.out.println(a);//a = 10
System.out.println(b);// b = 11

String s = "你好";
String s2 = s;
s2 = "hello";
System.out.println(s);//s = "你好"
System.out.println(s2);//s2 = "hello"

就和这个例子是一个道理可以说,只是赋值,改变原来的引用了吗,没有的.

回到之前的例子,

我们对func-arr进行了 arr[0] = 10,这里改变的是arr指向的对象的内容,而main-arr指向的对象是同一个.所以也发生了变化.然后接下来让func-arr指向一个新new的对象0x999.但这和main-arr又有什么关系呢.

最后,方法调用完毕之后,就都被销毁了.

4.初始化及类的加载

在次之前,先来做一道题目.

public class Base {
    private String baseName = "base";
    public Base() {
        System.out.println(baseName);
        callName();
    }
    public void callName() {
        System.out.println(baseName);
    }
    public static void main(String[] args) {
        Base b = new Sub();
    }
}
class Sub extends Base {
    private String baseName = "sub";
    public void callName() {
        System.out.println(baseName);
    }
}

问,会输出什么?

A. null
   null
B. base
   sub
C. base
   null
D. null
   sub

答案: C

若是不能明确答案,那就得好好看下分析了.

1)触发类加载的时机

在Java中,有两种方式能触发类加载

  • **创建实例之前: **通常,创建类的第一个对象的时候,类的代码在初次使用时才被加载.(要想创建实例,就得先对类进行加载)

  • **对 static 成员进行访问: **访问 static域 或者 static 方法时,也会发生加载

在开始的题目中,当我们调用 main 方法的时候,第一条就执行到 new Sub(); 此时就会开始进行类的加载.如果通过类名访问该类的 static 成员,也会开始进行类的加载.

2)关于类加载的顺序

类加载会从基类开始加载,然后往下逐层进行类加载,到最后一个子类

而加载类,顺序也是由上到下

public class Base {
//    static{
//        System.out.println("这是静态代码块1");
//        System.out.println(baseName); //会报错,因为按照顺序,静态成员变量 baseName 还未被初始化
//    }
    public static String baseName = "base";
    static{
        System.out.println("这是静态代码块2");
        System.out.println(baseName);
    }
    //构造方法,类加载的时候还不会被调用
    public Base() {
        System.out.println("这是构造方法");
        callName();
    }
    {
        System.out.println("这是实例代码块");
    }
    //普通成员方法,类加载的时候还不会被调用
    public void callName() {
        System.out.println("这是普通成员方法");
    }
}
class Main{
    public static void main(String[] args) {
        new Base();
    }
}

运行结果:

这是静态代码块2
base
这是实例代码块
这是构造方法
这是普通成员方法

还可以得知,静态 static 代码块在加载的时候就会被执行.

而在创建对象的时候,也是由上往下的,对普通成员变量进行初始化.以及执行实例代码块的代码.当这两个整完后,才调用构造方法.

3)类的初始化

当我们 new 一个类,即创建一个实例,会发生什么捏

还是以上述题目为例,我们来了解一下类是如何进行初始化的.

为了方便理解,这里修改一下代码:

public class Base {
    private String baseName = "base";
    public Base() {
        System.out.println(baseName);
        callName();
    }
    public void callName() {
        System.out.println(baseName);
    }
}
class Sub extends Base {
    private String baseName = "sub";
    public void callName() {
        System.out.println(baseName);
    }
}
class Main{
    public static void main(String[] args) {
        Base b = new Sub();//测试
    }
}
初始化的顺序:

①无继承关系的类/基类:

  • (如果之前未进行类加载)先进行类加载,加载自身 static 成员变量,以及 static 方法,static 静态代码块

而加载的顺序是从上往下的,如果在 static 方法/static 静态代码块 中调用还未加载的静态成员变量/方法也是会报错的.

在加载的过程中,静态成员变量会赋给初值,静态代码块里面的代码会直接被执行.

  • 在构造对象之前,将分配给对象的存储空间初始化成二进制的零(类加载后)

  • 类加载完成之后,就开始进行初始化.首先会先对普通成员变量赋予初始值.

  • 然后调用自身的构造方法

例子:

class Main{
    public static void main(String[] args) {
        new Base();//测试,main 直接创建 Base 实例
    }
}

运行结果:

base
base

分析:

按上面的初始化顺序,main 方法调用了 new Base(),开始创建 Base 对象,在创建对象前会对类进行加载;加载完毕后,会先对普通成员变量赋予初始值, 此时 baseName = “base”,然后调用自身的构造方法,执行打印了 baseName 的值,调用自身的成员方法 callName().构造函数构造完毕

②有继承关系的类

  • 如果自身不是基类,且有继承关系(子类).在对自己进行类加载之前,会先对上一层类进行类加载,直到帮基类完成类加载为止.然后往下进行加载
  • 在构造对象之前,将分配给对象的存储空间初始化成二进制的零(类加载后)
  • 进行初始化,也会先从基类开始,给基类的成员变量赋予初值,调用基类的构造方法,给基类构造对象.构造完毕后,再往下进行初始化,构造对象.

例子:

class Main{
    public static void main(String[] args) {
        new Sub();//测试
    }
}

运行结果:

base
null

分析:

这里 mian 方法调用了 new Sub(); 创建 Sub 对象.自然,构造对象之前需要进行类加载,但此时这个类是子类,就需要到上一层类 Base,此时这个类就是基类,就对 Base 类进行类加载,加载完后才对 Sub 类进行类加载.
类加载完毕了,就开始构造对象,依旧是从基类开始构造.首先对普通成员变量进行赋予初值(基类的 baseName = “base”),然后调用基类的构造方法,打印自身类的 baseName = “base”.然后调用 callName 成员方法.注意!!! 此时调用的不是基类自身的 callName 方法,而是子类的 callName 方法!!!这里子类 Sub 重写了父类的 baseName 方法,构成了重写.而此时,子类的还未构造,其成员变量都未初始化,默认为对应的零值.所以打印出子类的 baseName = null;

至此,分析完毕.

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/905256.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-15
下一篇 2022-05-15

发表评论

登录后才能评论

评论列表(0条)

保存