Java基础(一)

Java语言的特点:

  • 简单易学
  • 面向对象(封装、继承、多态)
  • 平台无关性(Java虚拟机实现平台无关性)
  • 支持多线程
  • 可靠性
  • 安全性
  • 支持网络编程并且很方便
  • 编译与解释并存

什么是字节码?采用字节码的好处是什么?

JVM可以理解的代码就是字节码(扩展名.class文件),JVM解释器进行对字节码的解释比较慢、引入了JIT编译器,JIT属于运行时编译,当JIT编译完成第一次编译之后,热点代码和方法的字节码对应机器码会被保存下来,下次可以直接进行使用,而不需要再次被解释器进行解释。

好处:

  • 通过字节码的方式,在一定程度上解决了传统解释性语言执行效率低的问题,同时又保留了解释型语言可移植的特点
  • 字节码不针对某一特定机器,所以Java不需要重新编译就可以在不同的操作系统的计算机运行。

Java源码一次编写,为啥能到处运行?

 JVM(Java虚拟机)是关键原因。

image-20230204194523252

注意:Java程序是跨平台的程序、JVM是C/C++开发的软件,不同平台下的JVM版本不一样

为什么说Java语言“编译与解释并存”?

编译型:编译型语言可以通过编译器将源代码一次性翻译成可被该平台执行的机器码。一般情况下,编译语言的执行速度比较开,开发效率比较低,常见的编译型语言就是C、C++、GO等

解释型:通过解释器将代码一句句解释成机器代码,解释型语言开发快,执行速度慢,常见的解释型语言就是PHP、Python、JavaScript等。

 因为Java要先通过javac编译器将程序编译成字节码(.class文件),然后通过Java解释器来解释执行。

一个Java程序可包含多个类吗?(内部类除外)

 可以包含多个类,但是被public修饰的类只有一个,且被public修饰的这个类必须跟类文件名同名

Java的访问权限:private、protected、default、public

 private < default < protected < public:从左到右权限依次变大

 对成员变量/成员方法而言:

  1、private:仅限该类内部成员访问

  2、default:同包下的其他类成员以及该类内部成员可以访问

  3、protected:同包下的其他类成员、该类内部成员、以及子类可以访问。

  4、public:任意包下的任意类的成员均可访问。

 修饰类时,仅有default、public两种修饰符:

  1、default:同包下的其他类可以访问

  2、public:任意包下的任意类可以访问

注:没有任何修饰符修饰,默认为default

Java数据类型

image-20230204213714958

byte(1 byte = 8 bits)     short(2 byte = 16 bits)     int(4 byte = 32 bits)     long(8 byte = 64 bits)

float(4 byte = 32 bits)    double(8 byte = 64bits)     char(2byte = 16bits)     boolean(根据Java规范规定,不同JVM实现机制不同)

分类 关键字
访问控制 private protected public
类、方法和变量修饰符 abstract class extends final implements interface native
new static strictfp synchronized transient volatile enum
程序控制 break continue return do while if else
for instanceof switch case default assert
错误处理 try catch throw throws finally
包相关 import package
基本类型 boolean byte char double float int long
short
变量引用 super this void
保留字 goto const

default 这个关键字很特殊,既属于程序控制,也属于类,方法和变量修饰符,还属于访问控制。

  • 在程序控制中,当在 switch 中匹配不到任何情况时,可以使用 default 来编写默认匹配的情况。
  • 在类,方法和变量修饰符中,从 JDK8 开始引入了默认方法,可以使用 default 关键字来定义一个方法的默认实现。
  • 在访问控制中,如果一个方法前没有任何修饰符,则默认会有一个修饰符 default,但是这个修饰符加上了就会报错。

静态方法为什么不能调用非静态成员?

这个需要结合 JVM 的相关知识,主要原因如下:

  1. 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
  2. 在类的非静态成员不存在的时候静态方法就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。

静态方法和实例方法有何不同?

1、调用方式

在外部调用静态方法时,可以使用 类名.方法名 的方式,也可以使用 对象.方法名 的方式,而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象

不过,需要注意的是一般不建议使用 对象.方法名 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。

因此,一般建议使用 类名.方法名 的方式来调用静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person {
public void method() {
//......
}

public static void staicMethod(){
//......
}
public static void main(String[] args) {
Person person = new Person();
// 调用实例方法
person.method();
// 调用静态方法
Person.staicMethod()
}
}

2、访问类成员是否存在限制

静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。

基本类型和包装类型的区别?

  • 成员变量包装类型不赋值就null ,而基本类型**有默认值且不是 null**。
  • 包装类型可用于泛型,而基本类型不可以。
  • 基本数据类型的局部变量存放在 Java 虚拟机中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中
  • 相比于对象类型, 基本数据类型占用的空间非常小。

为什么说是几乎所有对象实例呢? 这是因为 HotSpot 虚拟机引入了 JIT 优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存

⚠️ 注意 : 基本数据类型存放在栈中是一个常见的误区! 基本数据类型的成员变量如果没有static 修饰的话(不建议这么使用,应该要使用基本数据类型对应的包装类型),就存放在堆中。

1
2
3
class BasicTypeVar{
private int x;
}

堆区、栈区、方法区的区别:

堆区:

1、存储的全部是对象 ,每个对象都包含一个与之对应的Class的信息。(Class的目的是得到操作指令)

2、JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 ,即实例对象

栈区:

1、每个线程包含一个栈区 ,栈中 只保存基础数据类型的对象和自定义对象的引用(不是对象) ,对象都存放在堆区中

2、每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问 。

3、栈分为3个部分: 基本类型变量区、执行环境上下文、操作指令区(存放操作指令) 。

方法区:

1、又叫 静态区 ,跟堆一样, 被所有的线程共享 。方法区 包含所有的Class和static变量 。

2、方法区中包含的都是在整个程序中永远唯一的元素,如Class,static变量。

包装类型的缓存机制

Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False

Integer 缓存源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static {
// high value may be configured by property
int h = 127;
}
}

Character 缓存源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}

private static class CharacterCache {
private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}

}

Boolean 缓存源码:

1
2
3
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}

两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。

OS:所有整型包装类对象之间值的比较,全部使用 equals 方法比较

自动拆箱和自动装箱,原理是什么?

  • 拆箱:将包装类型转换成基本数据类型
  • 装箱:将基本数据类型用对应的引用类型(包装类型)包装起来

例如:

1
2
Integer i = 10;//装箱
int n = i;//拆箱

对应的字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
L1

LINENUMBER 8 L1

ALOAD 0

BIPUSH 10

INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;

PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;

L2

LINENUMBER 9 L2

ALOAD 0

ALOAD 0

GETFIELD AutoBoxTest.i : Ljava/lang/Integer;

INVOKEVIRTUAL java/lang/Integer.intValue ()I

PUTFIELD AutoBoxTest.n : I

RETURN

装箱其实就是调用了包装类的valueOf()方法,拆箱其实就是调用了**xxxValue()**方法。

1
2
Integer i = 10; =====> Integer  i = Integer.valueOf(10)
int n = i; ======> int n = i.intValue()