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;
至此,分析完毕.