Java期末学习总结
每个知识点都是为了解决某些问题提出来的。
202402150599
一、初识Java与面向对象程序设计
JDK和JRE是什么?JDK(Java Development Kit):提供了完整的Java开发工具和JRE,包括编译器、调试器等。支持开发人员从编写代码到编译、调试、运行的全过程。
JRE(Java Runtime Environment):只提供了Java程序运行所需的环境,包含Java虚拟机、Java基础类库。当一台计算机想运行Java编写的程序时,至少需要安装JRE。
第一个Java程序:HelloWorld!
1 public class HelloWorld { 2 public static void main(String[] args) { 3 System.out.println("Hello World!"); 4 } 5 }
编译与执行:
确保安装好Java开发环境。将代码保存为 HelloWorld.java。
打开cmd并定位到 HelloWorld.java 路径,输入javac HelloWorld.java 按回车键进行编译。
编译后会同级目录出现 HelloWorld.class,这个就是 Java 的字节码文件,用于实现跨平台。
1 // 2 // Source code recreated from a .class file by IntelliJ IDEA 3 // (powered by FernFlower decompiler) 4 // 5 6 public class HelloWorld { 7 public HelloWorld() { 8 } 9 10 public static void main(String[] var0) { 11 System.out.println("Hello World!"); 12 } 13 }
如果没有任何输出,说明编译成功。再输入 java HelloWorld,查看程序的运行结果。

二、Java编程基础
2.1 变量和常量
变量:变量是在程序运行过程中其值可以发生改变的量。例如 int a = 1; 这里定义了一个名为a的变量,其初始值为1,之后可以通过 a = 2; 来改变a的值。
常量:常量是在程序运行过程中其值不会发生改变的量。常量通常使用 final 关键字进行修饰,表示这个变量的值一旦被初始化后就不能再被改变。例如 public static final int MAX_VALUE = 0x7fffffff; 这里定义了一个名为 MAX_VALUE 的常量,其值为0x7fffffff,之后就不能再改变这个值。
2.2 数据类型
除了基本数据类型,其他都是引用数据类型。

数据类型占用空间:

2.3 运算符与表达式
运算符分类:算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、三元运算符。
运算符的优先级:不需要特意记,()括号运算符优先级最高,合理使用即可。
表达式是由数字、字母、运算符等通过有限次的加、减、乘、除、乘方和括号等运算得到的数学式子。它用于表示一个量或数学关系,可以包含变量和常量。
2.4 条件结构,循环结构
条件结构:if-else、switch-case
循环结构:for、while、do-while
跳转语句:return、break、continue
2.5 方法
在面向对象编程中,方法是绑定到对象的函数,通常在类的内部定义。方法能够接收输入参数,执行一定的处理,并可选择性地返回结果。方法必须先定义,后使用,否则程序将报错。
方法包含有:无参数方法、带参数方法、带返回值方法。
方法重载:指在同一个类中定义了多个同名但参数不同的方法。(与返回值无关)
方法递归:指的是一个方法在其方法体中直接或间接地调用自身。递归通常用于解决那些可以分解为类似子问题的问题,使得代码更加简洁。
2.6 数组
数组是一种引用数据类型,它提供了一种方式来存储固定大小的同类型元素集合。这些元素在内存中是连续存放的。每个元素都有一个唯一的索引,索引从0开始的,到数组的长度-1结束,通过这个索引可以快速访问对应的元素。
数组的优点:访问速度快。由于数组的元素在内存中是连续存放的,因此可以通过计算偏移量直接访问任何一个元素,这使得数组的访问速度非常快。
数组的缺点:
-
固定大小限制:数组的大小在创建时就已经确定,并且一旦分配内存空间后不能改变。这意味着如果需要存储的元素数量超过数组的初始大小,就必须重新分配更大的内存空间,并将原来的元素复制到新的空间中。这种限制使得数组不适合动态存储需求,如在运行时不确定数据量的情况下使用数组可能会带来不便。
-
插入和删除效率低:由于数组元素是连续存储的,插入或删除操作需要移动大量元素来保持数组的连续性。这会导致插入和删除操作的效率较低,尤其是在数组末尾进行这些操作时更为明显。
-
查找效率低:虽然数组可以通过索引快速访问元素,但如果需要根据内容查找元素,则必须逐个比较每个元素,时间复杂度为O(n)。这在处理大量数据时可能会导致性能瓶颈。
-
内存浪费:如果数组的大小远大于实际存储的元素数量,会造成内存浪费。这是因为数组需要预留足够的空间来容纳所有可能的元素,即使这些元素并未被实际使用。
-
内存要求高:由于数组元素是连续分配的,因此在堆内存中必须找到一块足够大的连续内存空间才能容纳数组的所有数据。这在某些情况下可能导致内存分配失败或程序崩溃。
三、面向对象程序设计(基础)
为什么要有面向对象?
为了解决如何复用代码的问题。要么把代码粘贴到当前代码中,要么把代码独立在一个文件里,然后引用。于是高级语言就诞生“面向对象”这个模型。
不同高级语言对“面向对象模型的理解和具体实现是有差异,这个差异主要源自各自的语言理念不同。
对象和类的关系?
类是用来的定义的,对象是用来的使用的。加载类的过程本身一个动作,会把类拥有的动作执行一遍。如果不是类拥有,而是对象拥有的,在创建对象的时候会执行。
如何理解new一个对象?
是一个动作,会产生一个对象实体,这个实体是保存在堆里面的。通过这个内存区域的地址来唯一定位到这个实体,这个地址是一个十六进制的数字,给变量保存。然后就可以通过这个变量来操作这个实体。至于如何从地址到实体,这个过程,是JDK负责,我们知道原理即可,不用关系具体的操作细节。
关于一个变量在堆还是栈的问题
哪里出生(声明)的就在哪里。
构造方法是什么?
构造,顾名思义,就是创造的意思。但是,java这个设定有点语义不明。Java说的构造方法,指的是对象创建成功后,执行的方法。其实构造的是属性初始值。
类一定要至少有一个构造方法,但是可以在定义类的时候不创建构造方法。这种看起来有点前后矛盾的话语,一定有个中间人在调节矛盾。它就是JDK,JDK在编译我们的.java文件时,如果发现我们没写构造方法,JDK会帮我们写一份。
(原因:希望程序员更多的精力使用Java完成业务上,而不是在纠结语法)
Java类方法定义的变量必须要有初始值吗?
必须要有,因为创建一个变量的时候,其实就是在内存开辟一片区域用于存储数据,这个区域里面的0还是1,是不确定的。因此,如果不初始化的话,可能造成未知。为了避免这个问题,语法强制初始化变量的值。Java类的属性可以不用初始化值。前后矛盾,说明有中间人调停:JDK发现属性没有初始值,会给你加。
static:解决类和对象的关系以及对象和对象的关系:
1)被static修饰的东西,都是类的。对象可以使用,但是不能独立。使得原本没有概念关系的对象有了共同的属性/方法。
2)被static修饰的东西,都是随着类的加载和创建。其他没有被static修饰的,是随着对象的创建而家。所以,被static修饰的东西,加载的顺序是排在非static前面的。
final :中文意思是“最后的”,意味着不在变化的东西
被final关键字修饰的东西,不会再变化。final可以修饰:类,属性/变量,方法
- 类的改变:继承
- 属性的改变:指的是值不能修改。
- 基本数据类型不能改的是值。
- 引用类型不能改的是他的引用地址。
- 字符串类型非常特殊,不可改的是成员属性 value 指向字符串的地址。
String s = "123"; //不可改的是value指向字符串的地址 s="456"; //这个改的是指向另一个value指向字符串的地址,创建了一个新的私有成员属性 char value[]
- 方法的改变:指的是不能被重载,但是可以被重写。重载是新增,重写是覆盖。
四、面向对象程序设计(进阶)
什么是对象?
- 从语言实现层面来看,对象封装了代码和数据。
- 从规格层面讲,对象是一系列可被使用的公共接口。
- 从概念层面讲,对象是某种拥有责任的抽象。
如何理解面向对象?
面向对象三大机制
- 封装,隐藏内部实现
- 继承,复用现有代码
- 多态,改写对象行为
可以看出三大机制都是为了尽可能复用代码。
面向对象机制所带来的抽象意义:变化是复用的天敌,面向对象最大的作用在于抵御变化。
为什么需要面向对象?
- 隔离变化
- 从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小。
- 各司其职
- 从微观层面来看,面向对象的方式更强调各个类的“责任”。
- 由于需求变化导致的新增类型不应该影响原来类型的实现——是所谓各负其责。
4.1 封装
封装解决的问题:有一个功能我已经把用到的变量和表达式都写好了,现在我想执行这个功能,我可以:
- 按顺序把每句代码都复制到需要这个功能的地方。如果后续需要改一下功能的逻辑,每一个复制的地方都要改。
- 把代码封装进一个类里面,然后直接调用类的这个方法。后续需要改的话,直接改这个方法就可以。
看起来明显是方法2更好,这就引出了封装的定义。封装是将数据(属性)和对这些数据的操作(方法)组合在一个单元中(称为“类”),并对外部隐藏实现细节,仅对外公开接口(方法/函数),以控制在程序中属性的读和修改的访问级别。
4.1.1 访问修饰符
- public 哪里都可以访问,private 哪里都不可访问。
- default 只要大家在同一个包里,随便访问。
- protected 专门搞特权的,挎包后还可以访问父类的属性和方法。
4.1.2 get/set方法
通过get/set方法访问私有属性,可以在读写私有属性时有一些逻辑验证,降低程序 bug 出现的可能性。
4.2 继承(扩展)
继承(扩展)解决的问题:需要设计几个类,每个类都有部分相同的代码,我可以:
- 每个类相同的部分都直接复制,再写类的剩下部分。如果后续相同的那部分功能有变化,一样每一个复制的地方都要改。
- 把相同的部分写到父类,使用继承(扩展)出需要的子类直接写不同的部分就好了。后续需要改的话,直接改那个父类就可以。
方法2直接引出了继承(扩展)的定义。继承(扩展)允许一个类(称为子类或派生类)扩展另一个类(称为父类或基类、超类)的属性和方法。子类拥有父类所有的属性和方法,并且还可以拥有自己独有的属性和方法。
4.2.1 方法重写
利用 Java 注解机制 @Override,由编译器检查重写是否成功,检查要点:
- 子类重写的方法必须与父类方法名和参数列表完全一致。
- 子类重写方法的可访问性必须比父类方法一致或更宽松。
- 子类重写方法的返回值类型:
- 父类方法的返回值类型如果是基本数据类型,子类重写的方法必须一致。
- 父类方法返回值类型如果是引用类型,子类重写的方法返回值类型必须小于父类的引用类型。
4.2.2 super、this 关键字
在如下图堆内存模型中,super 关键字指向该对象父类的存储空间,this 关键字指向整个对象。

4.2.3 Object类
java.lang.Object 是 Java 中所有类的父类,所有类都直接或间接地继承自该类。目的是让所有类都拥有一些基本的方法,如:
- toString 方法:返回该对象的字符串表示形式
- equals 方法:判断两个对象的地址是否相同
- hashCode 方法:返回对象的哈希码值
4.3 多态
多态解决的问题:假设有一个特别优秀的方法,我们很想用他,而且不用任何修改。我想直接用这个方法,而且我希望入参的类型能够兼容子类。
在继承后,子类重写了父类的方法,此时可以通过父类去调用子类重新后的方法,这就是多态。可以看出多态有个很明显的特征叫“以父之名”。
4.3.1 引用类型数据转换
引用类型的数据类型转换其实就是对象引用的转换,分为两种:
- 父类变量引用子类对象,属于自动类型转换,隐藏了子类类型,不能调用子类特有的方法。
- 子类变量引用父类对象,属于强制类型转换,存在风险,需要配合 instanceof 操作符避免出现“ClassCastException”异常。
4.4 抽象类(关键字是abstract)
抽象类解决的问题:当编写一个类时,往往会为该类定义一些方法,这些方法用来描述该类功能的具体实现方式,这些方法都有具体的方法体。 但是有的时候,某个父类只是知道子类应该包含的方法,但是无法准确知道子类是可以直接使用父类的方法还是必须要重写一个。此时,对于这种高度抽象的类,就可以定义为抽象类。必须交给子类实现的方法,可以定义为抽象方法。
4.5 接口
接口不提供任何具体实现,接口中所有方法都是抽象方法。接口是完全面向规范的,规定了一批类具有的公共方法规范。
接口解决了 Java 类无法多继承的问题。
default 方法解决的问题:接口投入后,无法升级,新增一个方法,就要全部进行修改。
解决:在 JDK8 版本之后,新增 default 方法。
static 方法:接口方法,必须实现子类,然后通过子类来访问,太麻烦。
解决:在 JDK8 版本之后,新增了一个 static 方法。
1 public interface Student { 2 //属性 3 int a = 2;//默认前缀:public static fianl 4 //方法 5 void study();//默认前缀:public abstract 6 7 //这里的default和访问修饰符中的默认,不一样 8 //访问修饰符的不写:指的是包级访问权限 9 //default指的是该方法有默认实现 10 default void count(){ 11 12 } 13 14 //只能通过接口名访问 15 static void fun(){ 16 System.out.println("static"); 17 } 18 }
4.6 抽象类和接口的区别
抽象类更像一个“模板”,当一个流程中只有个别子流程不确定具体实现时,就使用抽象类。抽象类限定了一个固定的流程,开发者只需要实现这个流程中的一些子流程即可。
接口则用于功能上的扩展,以及规范的制定。在设计一个系统时,可以事先使用接口定义好某些模块的功能,这样开发时只需要用实现类将这些功能一一实现,最终这个系统也就开发完毕了。
4.7 内部类
一个定义在其他类内部的类称为内部类,而这个其他类则称为外部类。内部类分为成员内部类、静态内部类、局部内部类、匿名内部类四种。
问题1:一个类里面方法之间没什么关系
问题2:某些类只给特定的类使用,没必要单独一个文件
解决:使用内部类,即把类定义在类的内部
1 // 视角1:Car和Main,平等,你怎么对他,他就可以怎么对你 2 // 视角2:外部类->内部类:在平等的基础上,有了一点特权,内部类可以访问其他内部类的private 3 // 视角3:内部类->外部类:倒反天罡,内部类可以无视规则,直接访问外部类的属性; 4 // 用法上好像:外部类的也是内部类的 5 // 视角4:外面的无关类->某个类的内部类:除了必须通过这个外部类之外,其他规则和视角1一样 6 public class Car { //外部类 7 private int carId = 3; 8 9 private Engine engine = new Engine(); 10 private Music music = new Music(); 11 private AC ac = new AC(); 12 13 public void onFire() { 14 engine.starEngine(); 15 ac.startAC(); 16 music.playMusic(); 17 System.out.println(engine.engineId); 18 } 19 20 public void offFire() { 21 engine.stopEngine(); 22 ac.stopAC(); 23 music.stopMusic(); 24 } 25 26 // 成员内部类 27 class Engine { 28 29 private int engineId = 2; 30 31 public void starEngine() { 32 System.out.println("statEngine"); 33 System.out.println(carId); 34 } 35 36 public void stopEngine() { 37 System.out.println("stopEngine"); 38 } 39 } 40 41 // 问题:某些类,99%的场景都是给Car服务的 42 // 有1%的场景,确实要给外面的类使用 43 // 44 45 // 静态内部类 46 static class Music { 47 public void playMusic() { 48 System.out.println("playMusic"); 49 } 50 51 public void stopMusic() { 52 System.out.println("stopMusic"); 53 } 54 } 55 56 class AC { 57 public void startAC() { 58 System.out.println("startAC"); 59 } 60 61 public void stopAC() { 62 System.out.println("stopAC"); 63 } 64 } 65 66 // ...其他各种启动和关闭系统 67 }
各种内部类用法示例:
1 public interface People { 2 void eat(); 3 }
1 import org.w3c.dom.ls.LSOutput; 2 3 // 为什么会有类:是为了表示复杂的数据 4 public class Main { 5 // JDK在设计的早期,没设计好,让普通对象可以访问普通类的静态的东西 6 // 到了后面出来的东西,都不允许了 7 public static void main(String[] args) { 8 // 问题:在某个场景下,需要临时表达一下复杂的数据 9 // 局部内部类 10 class Student implements People { 11 @Override 12 public void eat() { 13 System.out.println("student eat"); 14 } 15 } 16 17 // Student student = new Student(); 18 // fun(student); 19 20 //调用非静态内部类 21 Car car = new Car(); 22 Car.AC ac = car.new AC(); 23 24 //调用静态内部类 25 Car.Music music = new Car.Music(); 26 27 28 //匿名内部类 29 // 1、创建了一个类,这个类木有名字,所以叫匿名类 30 // 2、这个类实现了People接口 31 // 3、这个类重写了People的抽象方法 32 // 4、通过new创建了这个类的对象 33 fun(new People() { 34 @Override 35 public void eat() { 36 System.out.println("eeee"); 37 } 38 }); 39 } 40 41 public static void fun(People people) { 42 people.eat(); 43 } 44 45 }
五、异常和异常处理
1 public class TooShuaiException extends Exception { 2 @Override 3 public String getMessage() { 4 return super.getMessage() + "帅出异常"; 5 } 6 7 @Override 8 public String toString() { 9 return super.toString(); 10 } 11 12 @Override 13 public void printStackTrace() { 14 super.printStackTrace(); 15 } 16 }
1 import java.io.FileInputStream; 2 import java.io.FileNotFoundException; 3 4 public class Main { 5 public static void main(String[] args) throws TooShuaiException { 6 // 我们理解的现实世界概念的异常,和Java抽象的异常,是交集关系 7 // 问题:不是所有的代码都能按照预期去执行 8 // 解决:Java语言使用异常处理机制来解决这种问题 9 // 1)当某一行执行出错的时候,JVM会收集错误信息,并把信息存储到一个对象里 10 // 2)这个对象交给所在方法处理,如果该方法不处理,则按调用顺序层层往上甩锅,如果到了main还是没处理,就交给JVM处理了 11 // 3)JVM的处理:调用这个方法的 printStackTrace(),效果就是在输出控制台显示红色文字 12 // int c = divide(1, 0); 13 // int c = 1 / 0; // 说明 ArithmeticException 是运行时异常 14 // FileInputStream fileInputStream = new FileInputStream(""); // FileNotFoundException 是编译期异常 15 16 throw new TooShuaiException(); 17 } 18 19 // Java 把代码没有正常执行的现象称之为异常 20 // 当发生异常时,就会在该行抛出一个异常对象(存储了异常全部信息的对象,而不是有问题的对象) 21 // Throwable 类:异常类的父类,可抛出的类型 22 // 1)子类 Error:用来表示程序员无法在代码层面解决的异常,JVM自动处理 23 // 2)子类 Exception:可以在代码层面解决的异常 24 // 编译期异常:在编译阶段就会被拦截的异常; 25 // 运行时异常:在运行阶段才会报错的异常。 26 // 后面我说的“异常”,狭义的指异常对象 27 // 前面说的这些异常对象都是JVM产生的,要么自动处理,要么逼迫我们在写代码的时候处理 28 // 如果我自己在写代码的时候自己抛出一个异常,用一个关键字: throw 29 // 处理异常两种手段: 30 // 1)try...catch..在当前位置解决; 31 // 2)在方法的入参右括号的右边加上 throws 关键字往上甩锅 32 public static int divide(int a, int b) { 33 int result = -1; 34 try { 35 result = a / b; 36 } catch (Exception e) { 37 e.printStackTrace(); 38 } 39 return result; 40 } 41 }
六、Java 常用的类
6.1 包装类

浙公网安备 33010602011771号