1写出Java的四类八种基本数据类型

1.1背会:

四类:整数型、浮点型、字符型和布尔型
八种:byte、short、int、long、float、double、char、boolean

1.2理解:



类别

整数型

整数型

整数型

数据类型

byte

short

int

占用字节

1

2

4

取值范围

-128 到 127

-32,768 到 32,767

-2^31 到 2^31-1

整数型

long

8

-2^63 到 2^63-1

浮点型

float

4

1.4E-45 到 3.4028235E38

浮点型

double

8

4.9E-324 到 1.7976931348623157E308

字符型

char

2

'u0000'(0)到 '\uffff'(65,535)

布尔型

boolean

特殊

truefalse
需要注意的是,上述表格中的取值范围指的是数据类型的默认取值范围。
对于整数型和浮点型,Java使用补码形式表示负数,而对于布尔型,它只有两个可能的值:truefalse。
此外,char类型用于表示单个的Unicode字符,它的实际存储大小是16位,即2个字节,能够表示的字符范围从'\u0000'到'\uffff'。

1.3boolean类型的特殊说明

1.3.1单独使用时

boolean 类型数据在内存中占用4个字节。这是因为Java虚拟机(JVM)将单独的 boolean 值当作 int 类型来处理,而 int 类型在Java中占用4个字节。

1.3.2放在数组中

boolean类型以数组的形式出现时,每个boolean元素在内存中占用1个字节。
这是因为在JVM规范中,boolean数组被编码为字节数组,每个元素使用8位即1个字节来表示,其中使用1代表 true ,0代表 false 。

2& 和 && 的区别

2.1背会

在进行逻辑运算时,&没有短路效果,&&有短路效果

2.2&运算符可以计算的数据类型

2.2.1整数类型

对于这些类型,&执行按位与操作,即对两个操作数的二进制位逐位进行逻辑与运算。

2.2.2布尔类型

当用于布尔表达式时,&操作符执行逻辑与操作,只有当两边的操作数都为true时结果才为true。注意,这里的&通常用在表达式中而不是直接对boolean变量操作。
此时整个表达式没有短路效果,即使&号左边是false已经能够推断出整体结果,但还是会对后续表达式进行计算

2.2.3字符类型

虽然char本质上是一个整数类型(Unicode值),但是直接对char类型使用按位与操作并不常见,除非特别需要处理字符的二进制位。


2.3应用场景

位运算时使用&这不必多说什么,逻辑运算符通常肯定是使用&&,除非右边表达式必须执行:
boolean result = (a > 0) & ((b = number * 100) > 100); 假设这里必须给b变量赋值,那么右边表达式就一定要执行

2.4优先级

&&比&优先级高,如果出现在同一个表达式中,先执行&&的计算
int a = 10;
boolean b = true;
boolean result = false && true & ((a = 20) == 20);
System.out.println("result = " + result);
System.out.println("a = " + a);
 参照如下代码示例,证明二者优先级:

3switch的参数可以是什么类型

3.1背会

byte,short,int,char,String,枚举

3.2理解

Javaswitch语句的参数类型取决于你使用的JDK版本。具体如下:

3.2.1JDK1.6及以前版本

在这些版本中,switch语句的参数可以是基本数据类型int、byte、shortchar,以及它们的包装类Integer、Byte、ShortCharacter 。由于byte、shortchar可以自动转换为int,因此它们也可以作为switch的参数。此外从JDK1.5开始,switch还支持枚举类型enum。

3.2.2JDK 1.7及以后版本

JDK 1.7开始,switch语句新增了对String类型的支持。
这意味着你可以使用字符串作为switch的参数,这在之前的版本中是不允许的。

3.2.3JDK21版本

JDK21中可以使用Object类型,然后在各个分支中匹配具体类型
public static String formatterSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l  -> String.format("long %d", l);
        case Double d -> String.format("double %f", d);
        case String s -> String.format("String %s", s);
        default    -> o.toString();
    };
}

3.2.4所有版本

在所有版本的Java中,switch语句都不支持long类型作为参数。
这是因为long类型的范围比int大,不能自动转换为int类型。

3.2.5特殊注意

虽然switch语句在某些版本中支持String和枚举类型,但它本质上仍然只接受int类型的参数。
对于String类型,底层实现实际上是使用了字符串的hashCode()方法,该方法返回一个整数值。
对于枚举类型,使用的是ordinal()方法,该方法返回枚举常量的序号,也是一个整数值。


4实例变量和局部变量的区别是什么?

4.1背记

4.1.1代码位置


4.1.2内存位置


4.1.3生命周期


4.1.4有无默认值



类型

基本数据类型中的整数

基本数据类型中的小数

基本数据类型中的字符

默认值

0

0.0

'\u0000'

基本数据类型中的布尔

false

引用数据类型

null

4.2理解

实例变量和局部变量是Java中两种主要的变量类型,它们在作用范围、生命周期和初始化方面存在显著差异。

4.2.1作用范围

实例变量是定义在类中,且不在任何方法体内的变量。
实例变量在对象创建过程中被初始化,在对象使用过程中被读写,它的作用范围在对象内部。
基于封装性考虑,实例变量通常不会被对象外部直接读写,规范的做法是通过getXxx()、setXxx()方法操作。

而局部变量是定义在方法体或代码块内的变量,其作用范围仅限于该方法或代码块内部。
这意味着,一旦方法执行完毕,局部变量就会从内存中消失。

4.2.2生命周期

实例变量的生命周期与类的实例对象相同,即从对象被创建到被销毁的整个过程。
局部变量的生命周期则局限于方法的调用过程,即从方法被执行到方法执行完毕的整个过程。

4.2.3初始化

实例变量会自动初始化为其对应类型的默认值(如int类型为0),即使没有显式赋值也是如此。
而局部变量必须经过显式初始化才能使用,否则编译器会报错。
总的来说,理解实例变量和局部变量的区别对于编写正确的Java代码至关重要。


5static关键字都能修饰什么? 都有什么特点

5.1背记


5.2理解

Javastatic 关键字是一个非常重要的概念,它用于表示一个成员(变量或方法)属于类本身,而不是类的实例对象。
具体来说static关键字具有以下特点:

5.2.1类级属性

static修饰的成员变量被视为类的属性,它们在类加载时被初始化,并且在整个程序运行期间只占用一块内存空间。
这意味着,无论你创建多少个类的实例对象,static成员只有一份拷贝,被所有实例共享。

5.2.2无需实例化

由于static成员属于类,因此你无需创建类的实例对象就可以直接访问它们。
你可以通过类名直接访问static成员,例如ClassName.staticMethod()或ClassName.staticVariable。

5.2.3限制访问非静态成员

由于static方法在类加载时就存在,因此它们不能访问非静态成员(即实例成员),因为此时实例对象可能尚未创建。

5.2.4继承特性

与非静态成员不同,子类不会继承父类的static成员。如果需要访问父类的static成员,可以通过父类的类名直接访问。

5.2.5默认值

static变量有默认值,与实例变量的默认值相同。
例如, static int类型的默认值为0,static boolean类型的默认值为 false。

5.2.6静态代码块

Java中还支持静态代码块,它仅在类加载时执行一次。
静态代码块通常用于对静态成员进行初始化操作。
总的来说,理解static关键字是理解Java面向对象编程的关键之一。
通过合理使用static关键字,可以更好地组织代码,实现类与实例之间的资源共享和交互。


6overloadoverride的区别

6.1背记

在面向对象编程中,“override”和“overload”是两个重要的概念,它们用于描述方法或函数的行为。

6.1.1定义

Override:指子类重写(覆盖)了父类中的方法。子类中的方法名、参数列表与父类中的方法完全相同,返回值类型不超过父类方法返回值类型的范围。
Overload:指在同一个类中或者子类中定义多个具有相同名称但参数列表不同的方法。这些方法可以有不同的参数个数、类型或顺序。

6.1.2参数列表

Override:参数列表必须与父类中被重写的方法完全一致。
Overload:参数列表必须不同,可以是参数个数、类型或顺序的差异。

6.1.3返回类型

Override:返回类型必须与父类中被重写的方法相同,或者是其子类。
Overload:返回类型可以相同也可以不同。

6.1.4作用域

Override:通常发生在继承关系中,子类重写父类的方法。
Overload:可以在同一个类中或者子类中进行方法的重载。

6.1.5编译时决定

Override:动态绑定,即在运行时根据对象的实际类型决定调用哪个版本的方法。
Overload:静态绑定,即根据方法名和参数列表的静态类型在编译期间决定调用哪个方法。
为什么『重写』是动态绑定? 答:有的时候确实要在运行的时候才知道对象的具体类型是什么。比如: walk(Animal animal)这个方法接收Animal类型的参数,这就需要运行时才知道具体传入的是Cat还是Dog

6.1.6口诀


6.2理解

Overload(重载)和Override(重写)是Java中两个基本的概念,它们都是方法多态性的表现,但有一些关键的区别。具体分析如下:

6.2.1Overload(重载)

作用于同一个类中。方法名相同且参数列表不同(参数类型、个数或顺序至少有一项不同)。
不是基于继承的。在编译时根据参数签名确定调用哪个方法。可以改变返回类型(非必需,但改变了参数必须改变返回类型)。

6.2.2Override(重写)

基于继承,子类覆盖父类的方法。方法名及参数列表必须完全相同。访问权限不能比被覆盖的方法更严格。
返回类型必须是相同的或者是协变的。
抛出的异常必须是相同的或者是其子异常。
总的来说,Overload(重载)提供了一种机制,使得同一个方法名可以用于执行不同的功能,只要它们的参数不同即可。
Override(重写)则允许子类提供特定于子类的实现,同时保持与父类相同的方法签名。


7finalfinally的区别

7.1背记

7.1.1总体对比



关键词

final

finally

用法

作为修饰符来使用

后面跟一个代码块

功能

修饰类型、方法或变量

配合try、catch块使用

7.1.2细节:final

final是一个修饰符,可以修饰如下三种语法结构:

7.1.3细节:finally

finally是一个代码块,配合try、catch一起使用,效果是不论前面执行try块成功还是失败,finally块中的代码都必定会执行

7.2理解




8thissuper都能用到哪些地方?

8.1背记:

8.1.1访问成员变量


8.1.2访问成员方法


8.1.3访问构造器


8.2理解:

8.2.1this

当一个方法需要明确指出它正在操作当前对象时,就会用到this关键字。
this可以用来区分局部变量和类的字段之间的名称冲突,也可以用来调用同一个类中的其他构造器。
在构造器中,this通常用于调用同一类中的另一个构造器,这通常发生在构造器的第一行。
这样做的目的是为了代码重用和提高可维护性。
例如,如果一个类有多个构造器,它们可能需要执行一些共同的初始化步骤,那么可以使用this来调用一个共有的构造器,以避免代码重复。

8.2.2super

super关键字用于引用当前对象的父类。
它主要用于在子类的构造器中调用父类的构造器,这是因为子类构造时,需要先初始化从父类继承的成员。
super也可以用来调用父类的方法或访问父类的字段。
如果父类的构造器需要参数,则子类的构造器中必须通过super(参数名)显式调用父类中的某一个构造器。
需要注意的是,在同一个构造器中,this()和super()不能同时出现,因为this()必须放在构造器的第一行,而super() 也是放在第一行,所以它们不能共存。


9接口与抽象类的区别

9.1背记

9.1.1关键字


9.1.2设计


9.1.3构造器和成员变量


9.1.4方法实现


9.1.5继承与实现

一个类只能继承一个抽象类,但可以实现多个接口,这是Java支持多态性的重要方式

9.1.6访问修饰符


9.1.7实例化

抽象类和接口因为包含抽象方法,所以都不能直接用于创建对象,实例化之前都必须给出抽象方法的具体实现
通过匿名内部类方式创建对象时,其实抽象方法在匿名内部类中实现了

9.2理解

抽象类和接口各有其适合使用的应用场景:

9.2.1设计层面


9.2.2功能实现


9.2.3实际应用中的选择

在实际应用中,如果遇到需要同时使用抽象类和接口的情况,一般建议先继承抽象类再实现接口,因为Java不支持多重继承。
抽象类可以定义各种类型的成员变量,而接口中只能有 public static final 修饰的静态常量。
总的来说,在实际开发中选择使用接口还是抽象类取决于你的设计需求和所面临的具体问题。
如果你的设计更侧重于对象的层次结构和它们之间的共性,那么抽象类可能是更好的选择。
如果你的设计需要灵活地为不同类添加或修改行为,而不关心它们的具体类型,那么接口可能更加合适。


10静态变量与实例变量的区别

10.1背记


10.2理解

静态变量和实例变量是Java中的两种不同类型的变量,它们在定义、存储位置和使用方式上都存在差异。具体分析如下:

10.2.1声明

静态变量在声明时需要使用 static 关键字,而实例变量则不需要。

10.2.2存储位置


10.2.3使用方式


10.2.4内存位置


10.2.5生命周期


10.2.6调用方式



11throwthrows 的区别

11.1关键要点


11.2具体细节

11.2.1使用位置


11.2.2使用效果


11.2.3后跟类型


11.3有趣发现:throws为什么加“s”?

并不是因为它后面跟多个异常类型,这不是名词复数形式;
而是因为throws前面是方法名,方法名是主语,throws是谓语动词,那么一般现在时中,主语是第三人称单数时谓语动词加“s”
编程语言语法也是遵循自然语言语法的


12String、StringBuilder 与 StringBuffer 的区别

12.1背记





String

StringBuilder

StringBuffer

可变性

不可变

可变

可变

频繁修改时性能

差(第三名)

较好(第一名)

较好(第二名)

线程安全性



不加锁,线程不安全

加锁,线程安全

12.2细节

12.2.1可变性


12.2.2性能

由于String的不可变性,每次修改都会生成新的对象,这可能会导致性能问题,尤其是在大量修改操作的场景下。
StringBuilderStringBuffer的性能通常比String要好,因为它们避免了频繁的对象创建。
StringBuilder的性能略优于StringBuffer,因为它不是线程安全的,不需要同步操作,所以在单线程环境下速度更快。

12.2.3线程安全

StringBuffer是线程安全的,因为它的关键方法是同步的。这意味着在多线程环境下,使用StringBuffer可以避免并发问题。
StringBuilder不是线程安全的,它的方法没有同步,因此在多线程环境下可能会出现数据不一致的问题,但它在单线程环境下的性能更优。


13==和equals()的区别

13.1背记


13.2使用建议

通常来说:


14包装类拆箱装箱

14.1背记


14.2理解

14.2.1装箱

例如,将int类型的变量赋值给Integer对象时,会自动调用Integer.valueOf()方法。

14.2.2拆箱

14.2.3注意事项

自动拆装箱虽然方便,但也可能导致不必要的性能开销,尤其是在大量数据处理时。
频繁的拆装箱操作可能会导致内存占用增加,因为每次装箱都会创建新的包装类对象。
在编写代码时,应当注意不要过度依赖自动拆装箱,以免影响程序的性能和可读性。
总的来说,理解拆箱和装箱的概念对于Java编程是非常重要的,因为它们涉及到Java中基本类型与对象类型之间的转换机制,这对于编写高效且健壮的Java代码至关重要。


15异常结构图



16请谈一下JavaI/O技术体系中包含哪些种类的流?

16.1背记

Java中的I/O流可以从不同角度来分类:

16.2参考

Java中的IO流是用于数据输入和输出的抽象概念,它代表了输入源和输出目标。


I/O流类型

FileInputStream

FileOutputStream

ObjectInputStream

功能说明

用于从文件中读取字节数据

用于向文件中写入字节数据

用于从输入流中读取对象:对象的反序列化

ObjectOutputStream

用于向输出流中写入对象:对象的序列化

OutputStreamWriter

将字符流转为字节流,用于写入文本数据

BufferedReader

包装其他读取字符的输入流,提供缓冲功能,提高读取效率

InputStreamReader

将字节流转为字符流,用于读取文本数据

BufferedWriter

包装其他写入字符的输出流,提供缓冲功能,提高写入效率

PrintWriter

提供了打印各种数据的便利方法

Scanner

用于解析原始类型和字符串的正则表达式

16.3理解

Java中的输入流和输出流是I/O流体系的核心部分,它们分别负责数据的读取和写入。

16.3.1输入流

输入流主要用于从数据源(如文件、网络连接等)读取数据到程序中。
Java中,输入流主要由InputStreamReader作为基类。

16.3.2输出流

输出流主要用于将程序中的数据写入到目标(如文件、网络连接等)。
Java中,输出流主要由OutputStreamWriter作为基类。
输出流也可以分为节点流和处理流,节点流直接与数据目标相连,而处理流提供了额外的功能,如压缩、序列化等。

16.4思维导图



17请谈谈你对反射的理解

17.1反射要解决的问题


17.2反射的原理

核心就一句话:类的所有信息都在它编译出的*.class字节码文件中,所以反射操作的起点就是读取*.class字节码文件

17.2.1光学中的反射


17.2.2Java 中的反射


17.3反射的具体实现

Java的反射机制实现思路主要包括以下几个关键步骤

17.3.1获取Class对象

首先,需要获取目标类的Class对象,方式很多:

17.3.2分析类信息

有了Class对象后,可以对类进行深入的分析,包括获取构造器、字段(属性)和 方法等信息。这些信息可以通过Class对象提供的一组反射方法来获取。

17.3.3动态操作类

根据获取到的类信息,可以进行动态的操作,如创建对象实例、访问和修改字段值、调用方法等。
这些操作可以通过反射API提供的方法来实现。

17.3.4异常处理

在反射操作过程中,可能会遇到各种异常情况,如类未找到、方法不存在、访问权限不足等。
因此,需要对异常情况进行捕获和处理。

17.3.5性能优化(可选)

反射操作相比直接调用方法通常会有一定的性能开销。
如果性能是一个关键因素,可以考虑使用缓存或其他优化技术来提高反射操作的性能。
总之Java的反射机制实现思路是通过获取目标类的 Class 对象,然后利用反射API提供的方法来分析和操作类及其成员。
这种机制使得Java程序可以在运行时具有更高的灵活性和动态性。

17.4示例

package com.atguigu.demo.reflective;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @Author huzhongkui
 * @Date 2023--09--19:10:30
 * 聪明出于勤奋,天才在于积累
 **/
public class ReflectiveDemo {
    public static void main(String[] args) {
        try {
            // 1.获取任意一个对象所属的类
            Student student = new Student();
            Class<? extends Student> aClass1 = student.getClass();
            System.out.println(aClass1);
            
            // 2.构造任意一个类对象
            Student student1 = aClass1.newInstance();
            System.out.println(student1);
            
            // 3.获取任意一个类中的构造方法
            Class<?> aClass = Class.forName("se.reflective.Student");
            for (Constructor<?> declaredConstructor :
                    aClass.getDeclaredConstructors()) {
                Object instance = declaredConstructor.newInstance();
                System.out.println(instance);
            }
            
            // 4. 获取任意一个类中的方法
            for (Method declaredMethod : aClass.getDeclaredMethods()) {
                System.out.println("Student类中的方法:" + declaredMethod.getName());
            }
            
            // 5. 获取任意一个类中的成员变量
            for (Field declaredField : aClass.getDeclaredFields()) {
                System.out.println("Student类中的成员变量" + declaredField.getName());
            }
            
            // 6.调用任意一个对象的方法
            System.out.println(student1.getAge());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


18深拷贝和浅拷贝

18.1背记


18.2注意

深拷贝涉及到引用类型时,会创建新对象,所以复制之后会得到多个副本,修改任何一个副本都不影响其它副本

18.3应用场景