博学谷-零基础大数据在线就业班-第一阶段 java基础
博学谷-零基础大数据在线就业班-第一阶段 java基础
智汇君博学谷-零基础大数据在线就业班-第一阶段 java基础
第一章 java基础语法
环境搭建+入门
java语言背景介绍
java语言跨平台原理
JRE和JDK
JDK的下载和安装
常用DOS命令
PATH环境变量的配置
HelloWorld案例
1 | 编译 javac xxx.java |
1 | 类名前有public时,类名必须与文件名一致 |
Notepad软件的安装和使用
1 | 高级的记事本,方便代码编写(有行号) |
注释
1 | 单行 // |
关键字
1 | 关键字全小写 |
数据类型及转换
常量
1 | 字符串常量 |
变量介绍
数据类型
1 | 整数 字节 范围 |
1 | 浮点数 字节 范围 |
1 | 字符 字节 范围 |
1 | 布尔 字节 范围 |
变量的定义和使用
变量的注意事项
1 | 同名变量不可以重复定义 |
运算符
条件控制语句
循环
随机数+开发工具
数组
方法与debug
进制
二维数组
第二章 面向对象基础
第三章 API基础
StringBuilder
1 | 字符串串接,在次数多的情况下(如循环)会很占内存(生成临lin时对象)。StringBuilder可以解决,不会生成临时对象。 |
常用构造方法
public StringBuilder()
1 | 默认创建一个空字符对象 |
public StringBuilder(String str)
1 | 默认创建一个以传入字符串为基础的对象 |
常用成员方法
append
1 | 接受任意数据类型,返回对象本身(可以链式调用) |
reverse
1 | 元素内容反转 返回对象本身 |
length
toString
StringBuilder如何提高的效率
案例
1 | 字符串反转 |
StringBuffer(廖雪峰)
1 | 这是Java早期的一个StringBuilder的线程安全版本,它通过同步来保证多个线程操作StringBuffer也是安全的,但是同步会带来执行速度的下降。 |
StringJoiner(廖雪峰)
1 | 类似用分隔符拼接数组的需求 |
包装类型(廖雪峰)
1 | 引用类型可以赋值为null,表示空,但基本类型不能赋值为null |
1 | public class Main { |
1 | int i = 100; |
进制转换
1 | Integer类本身还提供了大量方法,例如,最常用的静态方法parseInt()可以把字符串解析成一个整数: |
1 | Integer还可以把整数格式化为指定进制的字符串: |
1 | Java的包装类型还定义了一些有用的静态变量 |
1 | 最后,所有的整数和浮点数的包装类型都继承自Number,因此,可以非常方便地直接通过包装类型获取各种基本类型: |
处理无符号整型
1 | 在Java在Java中,并没有无符号整型(Unsigned)的基本数据类型。byte、short、int和long都是带符号整型,最高位是符号位。而C语言则提供了CPU支持的全部数据类型,包括无符号整型。无符号整型和有符号整型的转换在Java中就需要借助包装类型的静态方法完成。 |
1 | 因为byte的-1的二进制表示是11111111,以无符号整型转换后的int就是255。 |
Auto Boxing
1 | Java编译器可以帮助我们自动在int和Integer之间转型: |
1 | 注意:自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。 |
不变类
1 | 所有的包装类型都是不变类。我们查看Integer的源码可知,它的核心代码如下: |
1 | 仔细观察结果的童鞋可以发现,==比较,较小的两个相同的Integer返回true,较大的两个相同的Integer返回false,这是因为Integer是不变类,编译器把Integer x = 127;自动变为Integer x = Integer.valueOf(127);,为了节省内存,Integer.valueOf()对于较小的数,始终返回相同的实例,因此,==比较“恰好”为true,但我们绝不能因为Java标准库的Integer内部有缓存优化就用==比较,必须用equals()方法比较两个Integer。 |
1 | 创建新对象时,优先选用静态工厂方法而不是new操作符。 |
JavaBean(廖雪峰)
1 | 在Java中,有很多class的定义都符合这样的规范: |
1 | 如果读写方法符合以下这种命名规范: |
1 | boolean字段比较特殊,它的读方法一般命名为isXyz(): |
1 | 只有getter的属性称为只读属性(read-only),例如,定义一个age只读属性: |
1 | 属性只需要定义getter和setter方法,不一定需要对应的字段。例如,child只读属性定义如下: |
1 | 可以看出,getter和setter也是一种数据封装的方法。 |
JavaBean的作用
1 | JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。此外,JavaBean可以方便地被IDE工具分析,生成读写属性的代码,主要用在图形界面的可视化设计中。 |
枚举JavaBean属性
1 | 要枚举一个JavaBean的所有属性,可以直接使用Java核心库提供的Introspector |
枚举类(廖雪峰)
enum
1 | 为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类: |
1 | 和int定义的常量相比,使用enum定义枚举有如下好处: |
enum的比较
1 | 使用enum定义的枚举类是一种引用类型。前面我们讲到,引用类型比较,要使用equals()方法,如果使用==比较,它比较的是两个引用类型的变量是否是同一个对象。因此,引用类型比较,要始终使用equals()方法,但enum类型可以例外。 |
enum类型
1 | 通过enum定义的枚举类,和其他的class有什么区别? |
1 | 例如,我们定义的Color枚举类: |
name()
1 | 返回常量名,例如: |
ordinal()
1 | 返回定义的常量的顺序,从0开始计数,例如: |
1 | 但是,如果不小心修改了枚举的顺序,编译器是无法检查出这种逻辑错误的。要编写健壮的代码,就不要依靠ordinal()的返回值。因为enum本身是class,所以我们可以定义private的构造方法,并且,给每个枚举常量添加字段: |
1 | 这样就无需担心顺序的变化,新增枚举常量时,也需要指定一个int值。 |
toString()
1 | 默认情况下,对枚举常量调用toString()会返回和name()一样的字符串。但是,toString()可以被覆写,而name()则不行。我们可以给Weekday添加toString()方法: |
1 | 覆写toString()的目的是在输出时更有可读性。 |
switch
1 | public class Main { |
小结
1 | Java使用enum定义枚举类型,它被编译器编译为final class Xxx extends Enum { … }; |
记录类(廖雪峰)
1 | 假设我们希望定义一个Point类,有x、y两个变量,同时它是一个不变类,可以这么写: |
record
1 | 从Java 14开始,引入了新的Record类。我们定义Record类时,使用关键字record。把上述Point类改写为Record类,代码如下: |
1 | 仔细观察Point的定义: |
1 | 除了用final修饰class以及每个字段外,编译器还自动为我们创建了构造方法,和字段名同名的方法,以及覆写toString()、equals()和hashCode()方法。 |
构造方法
1 | 编译器默认按照record声明的变量顺序自动创建一个构造方法,并在方法内给字段赋值。那么问题来了,如果我们要检查参数,应该怎么办? |
1 | 作为record的Point仍然可以添加静态方法。一种常用的静态方法是of()方法,用来创建Point: |
小结
1 | 从Java 14开始,提供新的record关键字,可以非常方便地定义Data Class: |
常用工具类(廖雪峰)
Math
绝对值
最大或最小值
xy次方
√x
ex次方
e为底的对数
以10为底的对数
三角函数
数学常量
随机数
1 | 生成一个随机数x,x的范围是0 <= x < 1: |
1 | 如果我们要生成一个区间在[MIN, MAX)的随机数,可以借助Math.random()实现,计算如下: |
StrictMath
1 | 有些童鞋可能注意到Java标准库还提供了一个StrictMath,它提供了和Math几乎一模一样的方法。这两个类的区别在于,由于浮点数计算存在误差,不同的平台(例如x86和ARM)计算的结果可能不一致(指误差不同),因此,StrictMath保证所有平台计算结果都是完全相同的,而Math会尽量针对平台优化计算速度,所以,绝大多数情况下,使用Math就足够了。 |
Random
1 | Random用来创建伪随机数。所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的。 |
1 | 有童鞋问,每次运行程序,生成的随机数都是不同的,没看出伪随机数的特性来。 |
1 | 前面我们使用的Math.random()实际上内部调用了Random类,所以它也是伪随机数,只是我们无法指定种子。 |
SecureRandom
1 | 有伪随机数,就有真随机数。实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,SecureRandom就是用来创建安全的随机数的: |
异常处理(廖雪峰)
1 | try: |
java的异常
捕获异常
抛出异常
自定义异常
NullPointerException
使用断言
使用JDK Logging
1 | java核心库的,程序一旦启动无法再修改配置,且使用时需要指定命令 |
使用Commons Logging
1 | 负责充当日志API 第三库;自动挂载其它日志系统,jdk logging或者log4j(负责实现日志底层) |
使用Log4j
1 | 上述结构虽然复杂,但我们在实际使用的时候,并不需要关心Log4j的API,而是通过配置文件来配置它。 |
1 |
|
SLF4J和Logback
1 | 前面介绍了Commons Logging和Log4j这一对好基友,它们一个负责充当日志API,一个负责实现日志底层,搭配使用非常便于开发。 |
1 | 从目前的趋势来看,越来越多的开源项目从Commons Logging加Log4j转向了SLF4J加Logback。 |
反射(廖雪峰)
1 | 反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。 |
Class类
1 | 除了int等基本类型外,Java的其他类型全部都是class(包括interface) |
1 | class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。 |
1 | 以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来: |
获取Class实例
1 | 如何获取一个class的Class实例?有三个方法: |
与instanceof比较
1 | 因为Class实例在JVM中是唯一的,所以,上述方法获取的Class实例是同一个实例。可以用==比较两个Class实例: |
1 | 用instanceof不但匹配指定类型,还匹配指定类型的子类。而用==判断class实例可以精确地判断数据类型,但不能作子类型比较。 |
1 | 因为反射的目的是为了获得某个实例的信息。因此,当我们拿到某个Object实例时,我们可以通过反射获取该Object的class信息: |
Class实例来创建对应类型的实例
1 | 如果获取到了一个Class实例,我们就可以通过该Class实例来创建对应类型的实例: |
动态加载
1 | JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。 |
访问字段
1 | Class类提供了以下几个方法来获取字段: |
1 | public class Main { |
1 | 上述代码首先获取Student的Class实例,然后,分别获取public字段、继承的public字段以及private字段,打印出的Field类似: |
1 | 一个Field对象包含了一个字段的所有信息: |
1 | 以String类的value字段为例,它的定义是: |
获取字段值
1 | 利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。 |
1 | // reflection |
1 | 上述代码先获取Class实例,再获取Field实例,然后,用Field.get(Object)获取指定实例的指定字段的值。 |
1 | 有童鞋会问:如果使用反射可以获取private字段的值,那么类的封装还有什么意义? |
设置字段值
1 | public class Main { |
1 | 运行上述代码,打印的name字段从Xiao Ming变成了Xiao Hong,说明通过反射可以直接修改字段的值。 |
调用方法
1 | 我们已经能通过Class实例获取所有Field对象,同样的,可以通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method: |
1 | public class Main { |
1 | 上述代码首先获取Student的Class实例,然后,分别获取public方法、继承的public方法以及private方法,打印出的Method类似: |
1 | 一个Method对象包含一个方法的所有信息: |
调用方法
1 | 当我们获取到一个Method对象时,就可以对它进行调用。我们以下面的代码为例: |
1 | // reflection |
1 | 注意到substring()有两个重载方法,我们获取的是String substring(int)这个方法。思考一下如何获取String substring(int, int)方法。 |
调用静态方法
1 | 如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。我们以Integer.parseInt(String)为例: |
调用非public方法
1 | 和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用: |
1 | // reflection |
1 | 此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。 |
多态
1 | 我们来考察这样一种情况:一个Person类定义了hello()方法,并且它的子类Student也覆写了hello()方法,那么,从Person.class获取的Method,作用于Student实例时,调用的方法到底是哪个? |
1 | // reflection |
1 | 运行上述代码,发现打印出的是Student:hello,因此,使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。上述的反射代码: |
调用构造方法
1 | 我们通常使用new操作符创建新的实例: |
1 | 为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例: |
1 | 通过Class实例获取Constructor的方法如下: |
获取继承关系
获取父类的Class
1 | 有了Class实例,我们还可以获取它的父类的Class: |
1 | 运行上述代码,可以看到,Integer的父类类型是Number,Number的父类是Object,Object的父类是null。除Object外,其他任何非interface的Class都必定存在一个父类类型。 |
获取interface
1 | 由于一个类可能实现一个或多个接口,通过Class我们就可以查询到实现的接口类型。例如,查询Integer实现的接口: |
1 | 运行上述代码可知,Integer实现的接口有: |
1 | 要特别注意:getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型: |
1 | Integer的父类是Number,Number实现的接口是java.io.Serializable。 |
1 | 如果一个类没有实现任何interface,那么getInterfaces()返回空数组。 |
继承关系
1 | 当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符: |
1 | 如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom(): |
动态代理
1 | 我们来比较Java的class和interface的区别: |
1 | 有没有可能不编写实现类,直接在运行期创建某个interface的实例呢? |
1 | 定义接口: |
1 | 还有一种方式是动态代码,我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。 |
1 | import java.lang.reflect.InvocationHandler; |
1 | 在运行期动态创建一个interface实例的方法如下: |
1 | 动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样: |
1 | import java.lang.reflect.InvocationHandler; |
注解(廖雪峰)
1 | 什么是注解(Annotation)?注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”: |
使用注解
注解的作用
1 | 从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。 |
注解的参数
1 | 定义一个注解时,还可以定义配置参数。配置参数可以包括: |
1 | 举个栗子,对以下代码: |
定义注解
1 | Java语言使用@interface语法来定义注解(Annotation),它的格式如下: |
元注解
1 | 有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。 |
@Target
1 | 最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置: |
1 | 例如,定义注解@Report可用在方法上,我们必须添加一个 |
1 | 定义注解@Report可用在方法或字段上,可以把@Target注解参数变为数组{ ElementType.METHOD, ElementType.FIELD }: |
@Retention
1 | 另一个重要的元注解@Retention定义了Annotation的生命周期: |
@Repeatable
1 | 使用@Repeatable这个元注解可以定义Annotation是否可重复。这个注解应用不是特别广泛。 |
1 | 经过@Repeatable修饰后,在某个类型声明处,就可以添加多个@Report注解: |
@Inherited
1 | 使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效: |
1 | 在使用的时候,如果一个类用到了@Report: |
如何定义Annotation
1 | 我们总结一下定义Annotation的步骤: |
处理注解
1 | Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置: |
1 | 因此,我们只讨论如何读取RUNTIME类型的注解。 |
1 | 例如: |
1 | 使用反射API读取Annotation: |
1 | 使用反射API读取Annotation有两种方法。方法一是先判断Annotation是否存在,如果存在,就直接读取: |
1 | 读取方法、字段和构造方法的Annotation和Class类似。但要读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。例如,对于以下方法定义的注解: |
使用注解
1 | 注解如何使用,完全由程序自己决定。例如,JUnit是一个测试框架,它会自动运行所有标记为@Test的方法。 |
1 | 但是,定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解。这里,我们编写一个Person实例的检查方法,它可以检查Person实例的String字段长度是否满足@Range的定义: |













