java基础知识总结

前言

本文主要是跟大家一起复习一下我之前学习的 java 基础中的知识点的总结。java 的知识点非常的多,它更像是一个成熟稳重的中年大叔。有些我们自以为理解了的知识点,其实只是停留在了他的表面之上,并没有深入了解到其实现原理。

纸上得来终觉浅,绝知此事要躬行。java 学习之路很长,我们要不断的学习实践才能深入理解代码的具体实现方式,这样才能造出更加稳固的轮子。

java 概述

Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling 和同事们共同研发,并在 1995 年正式推出。

Java 分为三个体系:

  • JavaSE(J2SE)(Java2 Platform Standard Edition,java 平台标准版,完成桌面应用程序的开发,是其它两者的基础;)

  • JavaEE(J2EE)(Java 2 Platform,Enterprise Edition,java 平台企业版,开发企业环境下的应用程序,主要针对 web 程序开发;)

  • JavaME(J2ME)(Java 2 Platform Micro Edition,java 平台微型版,开发电子消费产品和嵌入式设备,如手机中的程序;)。

2005 年 6 月,JavaOne 大会召开,SUN 公司公开 Java SE 6。此时,Java 的各种版本已经更名以取消其中的数字 “2”:J2EE 更名为 Java EE, J2SE 更名为 Java SE,J2ME 更名为 Java ME。

基础知识总结

每部分内容会重点写一些常见知识点,方便复习和记忆,但并不是全部内容。

  • 环境变量配置

本文以 Windows10 为例进行环境变量配置(Windows11适用此方法),Windows7 配置环境变量 参考这里

首先请确认你的电脑已经安装了 JDK,如果没有安装请点击 这里下载 你需要的 JDK 版本,安装时建议默认路径就可以,如果有特殊需求可自定义路径。

1.在桌面右键此电脑 -->选属性 -->高级系统设置 -->环境变量设置 -->系统变量中点新建如图:QQ截图20200504163931.png

变量名:JAVA_HOME

变量值:C:\Program Files\Java\jdk1.8.0_202(你JDK的安装路径)

2.再次点击新建输入如图:

QQ截图20200504165155.png

变量名:CLASSPATH

变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar(需要注意前面有个点)

3.找到 Path 点编辑,进去之后点新建如图:QQ截图20200504165632.png

在后面添加 %JAVA_HOME%\bin%JAVA_HOME%\jre\bin(注意不需要添加; 号,直接另起一行即点击新建添加即可)最后点击确定!

  • 验证

win+R 键 输入 cmd 回车 进入 dos,分别输入 java javac 查看安装是否完成,出现如图即安装成QQ截图20200504170403.png

java

QQ截图20200504170428.png

javac

  • 面向对象的三大特性

继承:一般类只能单继承,内部类实现多继承,接口可以多继承

封装:将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。访问权限控制 public > protected > 包 > private 内部类也是一种封装

多态:编译时多态,体现在向上转型和向下转型,通过引用类型判断调用哪个方法(静态分派)。运行时多态,体现在同名函数通过不同参数实现多种方法(动态分派)。

  • 基本数据类型

Java 语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

级别从低到高为:byte,char,short(这三个平级)–>int–>float–>long–>double

自动类型转换:从低级别到高级别,系统自动转的

强制类型转换:什么情况下使用?把一个高级别的数赋给一个别该数的级别低的变量

  • String 及包装类

  1. String 类型是 final 类型,在堆中分配空间后内存地址不可变。
  2. 底层是 final 修饰的 char[] 数组,数组的内存地址同样不可变。

但实际上可以通过修改 char[n] = ‘a’ 来进行修改,不会改变 String 实例的内存值,不过在 jdk 中,用户无法直接获取 char[],也没有方法能操作该数组。所以 String 类型的不可变实际上也是理论上的不可变。所以我们在分配 String 对象以后,如果将其 = “abc”,那也只是改变了引用的指向,实际上没有改变原来的对象。

  1. StringBuffer 和 StringBuilder 底层是可变的 char[] 数组,继承父类 AbstractStringBuilder 的各种成员和方法,实际上的操作都是由父类方法来完成的。
  • final 关键字

  1. final 修饰基本数据类型保证不可变
  2. final 修饰引用保证引用不能指向别的对象,否则会报错。
  3. final 修饰类,类的实例分配空间后地址不可变,子类不能重写所有父类方法。因此在 cglib 动态代理中,不能为一个类的 final 修饰的函数做代理,因为 cglib 要将被代理的类设置为父类,然后再生成字节码。

final 修饰的方法,子类不能重写该方法。

  • 抽象类和接口

  1. 抽象类可以有方法实现。抽象类可以有非 final 成员变量。抽象方法要用 abstract 修饰。抽象类可以有构造方法,但是只能由子类进行实例化。
  2. 接口可以用 extends 加多个接口实现多继承。接口只能有 public final 类型的成员变量。接口只能有抽象方法,不能有方法体,接口不能实例化,但是可以作为引用类型。
  • 代码块和加载顺序

由 { } 包起来的代码,称为代码块;由 static { } 包起来的代码,称为静态代码块。

  1. 由 static 关键字修饰的,如类变量和静态代码块,将在类创建实例之前被初始化,而且是按顺序从上到下依次被执行。(类变量、静态代码块)属于类本身,不依赖于类的实例。
  2. 没有 static 关键字修饰的(如:实例变量 (非静态变量)、非静态代码块)初始化实际上是会被提取到类的构造器中被执行的,但是会比类构造器中的代码块优先执行。实例变量、非静态代码块的地位是相等的,它们将按顺序被执行。
  3. 静态方法只允许直接访问静态成员,而实例方法中可以访问静态成员和实例成员,原因是类还没有实例化,所以实例成员也没有被创建,静态方法中因此也不能用 this。
  • 包、内部类、外部类

  1. Java 项目一般从 src 目录开始有 com...example.java 这样的目录结构。这就是包结构。所以一般编译后的结构是跟包结构一模一样的,这样的结构保证了 import 时能找到正确的 class 引用包访问权限就是指同包下的类可见。

import 一般加上全路径,并且使用.* 时只包含当前目录的所有类文件,不包括子目录。

  1. 外部类只有 public 和 default 两种修饰,要么全局可访问,要么包内可访问。
  2. 内部类可以有全部访问权限,因为它的概念就是一个成员变量,所以访问权限设置与一般的成员变量相同。

非静态内部类是外部类的一个成员变量,只跟外部类的实例有关。静态内部类是独立于外部类存在的一个类,与外部类实例无关,可以通过外部类.内部类直接获取 Class 类型。

  • 异常

  1. Execption 可以分为 java 标准定义的异常和程序员自定义异常 2 种
  2. java 对异常进行了分类,不同类型的异常使用了不同的 java 类,所有异常的根类为 java.lang.Throwable.Throwable 派生了 2 个子类:Error 和 Exception。
  3. Error 代表了 JVM 本身的错误,不能被程序员通过代码处理,如内存溢出。
  4. Exception 分为 IoException 和 RuntimeException 。

Error 和 RuntimeException 以及他们的子类。Javac 在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常称之为非检查异常,比如下标越界。编译器强制必须 try.catch 处理或 throws 声明继续抛给上层调用方法处理的异常称之为检查异常,比如使用 jdbc 连接数据库的 SQLException。try 块中放可能发生异常的代码。每一个 catch 块用于捕获并处理一个特定的异常,或者这异常类型的子类,顺序为从小到大。finally 无论异常是否发生,异常是否匹配被处理,finally 都会执行。

  • 泛型

  1. Java 中的泛型是伪泛型,只在编译期生效,运行期自动进行泛型擦除,将泛型替换为实际上传入的类型。

泛型类:ExpClass<T>{}

  1. 这样的形式表示,里面的方法和成员变量都可以用 T 来表示类型。泛型接口也是类似的,不过泛型类实现泛型接口时可以选择注入实际类型或者是继续使用泛型。
  2. 泛型方法可以自带泛型比如 void exp();

泛型可以使用?通配符进行泛化 Object 可以接受任何类型,也可以使用 这种方式进行上下边界的限制。

  • Class 类和 Object 类

  1. Java 反射的基础是 Class 类,该类封装所有其他类的类型信息,并且在每个类加载后在堆区生成每个类的一个 Class<类名>实例,用于该类的实例化。
  2. Java 中可以通过多种方式获取 Class 类型,比如 A.class,new A().getClass() 方法以及 Class.forName(“com.?.?.A”) 方法。
  3. Object 是所有类的父类,有着自己的一些私有方法,以及被所有类继承的 9 大方法。
  • javac 和 java

  1. javac 是编译一个 java 文件的基本命令,通过不同参数可以完成各种配置,比如导入其他类,指定编译路径等。
  2. java 是执行一个 java 文件的基本命令,通过参数配置可以以不同方式执行一个 java 程序或者是一个 jar 包。
  3. javap 是一个 class 文件的反编译程序,可以获取 class 文件的反编译结果,甚至是 jvm 执行程序的每一步代码实现。
  • 反射

  1. Java 反射包 reflection 提供对 Class,Method,field,constructor1 等信息的封装类型。
  2. 通过这些 api 可以轻易获得一个类的各种信息并且可以进行实例化,方法调用等。

类中的 private 参数可以通过 setaccessible 方法强制获取。

  1. 反射的作用可谓是博大精深,JDK 动态代理生成代理类的字节码后,首先把这个类通过 defineclass 定义成一个类,然后用 class.for(name) 会把该类加载到 jvm,之后我们就可以通过,A.class.GetMethod() 获取其方法,然后通过 invoke 调用其方法,在调用这个方法时,实际上会通过被代理类的引用再去调用原方法。
  • 枚举类

  1. 枚举类继承 Enum 并且每个枚举类的实例都是唯一的。
  2. 枚举类可以用于封装一组常量,取值从这组常量中取,比如一周的七天,一年的十二个月。
  3. 枚举类的底层实现其实是语法糖,每个实例可以被转化成内部类。并且使用静态代码块进行初始化,同时保证内部成员变量不可变。
  • 序列化

  1. 所有需要网络传输的对象都需要实现序列化接口,建议所有的 javaBean 都实现 Serializable 接口。
  2. 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient 实例变量都不会被序列化。
  3. 如果想让某个变量不被序列化,使用 transient 修饰。
  4. readObject 和 writeOject 来实现实例的写入和读取。
  5. 事实上,一些拥有数组变量的类都会把数组设为 transient 修饰,这样的话不会对整个数组进行序列化,而是利用专门的方法将有数据的数组范围进行序列化,以便节省空间。
  6. 反序列化时必须有序列化对象的 class 文件。
  7. 单例类序列化,需要重写 readResolve() 方法;否则会破坏单例原则。
  • 动态代理

  1. jdk 自带的动态代理可以代理一个已经实现接口的类。
  2. cglib 代理可以代理一个普通的类。
  3. 动态代理的基本实现原理都是通过字节码框架动态生成字节码,并且在用 defineclass 加载类后,获取代理类的实例。
  4. 一般需要实现一个代理处理器,用来处理被代理类的前置操作和后置操作。在 JDK 动态代理中,这个类叫做 invocationHandler。
  5. JDK 动态代理首先获取被代理类的方法,并且只获取在接口中声明的方法,生成代理类的字节码后,首先把这个类通过 defineclass 定义成一个类,然后把该类加载到 jvm,之后我们就可以通过,A.class.GetMethod() 获取其方法,然后通过 invoke 调用其方法,在调用这个方法时,实际上会通过被代理类的引用再去调用原方法。
  6. 而对于 cglib 动态代理,一般会把被代理类设为代理类的父类,然后获取被代理类中所有非 final 的方法,通过 asm 字节码框架生成代理类的字节码,这个代理类很神奇,他会保留原来的方法以及代理后的方法,通过方法数组的形式保存。

cglib 的动态代理需要实现一个 enhancer 和一个 interceptor,在 interceptor 中配置我们需要的代理内容。如果没有配置 interceptor,那么代理类会调用被代理类自己的方法,如果配置了 interceptor,则会使用代理类修饰过的方法。

  • 多线程

Java 线程是一个庞大的话题,以后会考虑专门开一篇进行线程讲解

创建多线程 —— 继承 Thread

创建多线程 —— 实现 Runnable

创建多线程 —— 实现 Callable

callable 配合 future 可以实现线程中的数据获取

  1. Java 中的线程有 7 种状态,new runable running blocked waiting timewaiting terminate

blocked 是线程等待其他线程锁释放。waiting 是 wait 以后线程无限等待其他线程使用 notify 唤醒
timewating 是有限时间地等待被唤醒,也可能是 sleep 固定时间。

  1. 多线程操作共享变量,会产生三个问题,可见性、有序性和原子性。
  2. Thread 的 join 是实例方法,比如 a.join(b),则说明 a 线程要等 b 线程运行完才会运行。
  3. 在 Java 中,每次程序运行至少启动 2 个线程:一个是 main 线程,一个是垃圾收集线程。因为每当使用 java 命令执行一个类的时候,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个进程。
  4. o.wait 方法会让持有该对象 o 的线程释放锁并且进入阻塞状态,notify 则是持有 o 锁对象的线程通知其他等待锁的线程获取锁。notify 方法并不会释放锁。注意这两个方法都只能在 synchronized 同步方法或同步块里使用。
  5. synchronized 方法底层使用系统调用的 mutex 锁,开销较大,jvm 会为每个锁对象维护一个等待队列,让等待该对象锁的线程在这个队列中等待。当线程获取不到锁时则让线程阻塞,而其他检查 notify 以后则会通知任意一个线程,所以这个锁时非公平锁。
  6. Thread.sleep(),Thread.interrupt() 等方法都是类方法,表示当前调用该方法的线程的操作。
  7. 一个线程实例连续 start 两次会抛异常,这是因为线程 start 后会设置标识,如果再次 start 则判断为错误。
  • IO 流

  1. IO 流也是 Java 中比较重要的一块,Java 中主要有字节流,字符流,文件等。其中文件也是通过流的方式打开,读取和写入的。
  2. IO 流的很多接口都使用了装饰者模式,即将原类型通过传入装饰类构造函数的方式,增强原类型,以此获得像带有缓冲区的字节流,或者将字节流封装成字符流等等,其中需要注意的是编码问题,后者打印出来的结果可能是乱码哦。
  3. IO 流与网络编程息息相关,一个 socket 接入后,我们可以获取它的输入流和输出流,以获取 TCP 数据包的内容,并且可以往数据报里写入内容,因为 TCP 协议也是按照流的方式进行传输的,实际上 TCP 会将这些数据进行分包处理,并且通过差错检验,超时重传,滑动窗口协议等方式,保证了 TCP 数据包的高效和可靠传输。
  • 网络编程

承接 IO 流的内容

  1. IO 流与网络编程息息相关,一个 socket 接入后,我们可以获取它的输入流和输出流,以获取 TCP 数据包的内容,并且可以往数据报里写入内容,因为 TCP 协议也是按照流的方式进行传输的,实际上 TCP 会将这些数据进行分包处理,并且通过差错检验,超时重传,滑动窗口协议等方式,保证了 TCP 数据包的高效和可靠传输。
  2. 除了使用 socket 来获取 TCP 数据包外,还可以使用 UDP 的 DatagramPacket 来封装 UDP 数据包,因为 UDP 数据包的大小是确定的,所以不是使用流方式处理,而是需要事先定义他的长度,源端口和目标端口等信息。
  3. 为了方便网络编程,Java 提供了一系列类型来支持网络编程的 api,比如 URL 类,InetAddress 类等。

后记

简单总结了一下 java 中的一些基础知识点,有错误的地方还恳请各位进行指正!Java 的知识点其实非常多,并且有些知识点比较难以理解,有时候我们自以为理解了某些内容,其实可能只是停留在表面上,没有理解其底层实现原理。学无止境!我们工作中也应该学会总结!