Java 基础
IDEA 快捷键
快捷键 | 功能 |
---|---|
Alt+Enter | 导入包,自动修正代码 |
Ctrl+Y | 删除光标所在行 |
Ctrl+D | 复制光标所在行的内容,插入光标位置下面 |
Ctrl+Alt+L | 格式化代码 |
Ctrl+/ | 单行注释 |
Ctrl+Shift+/ | 选中代码注释,多行注释,再按取消注释 |
Alt+Ins | 自动生成代码,toString,get,set等方法(牢记) |
Alt+Shift+上下箭头 | 移动当前代码行 |
Ctrl+Alt+v | 根据右边值生成左边变量(牢记) |
数据类型
数据类型分类
Java的数据类型分为两大类:
- 基本数据类型:包括
整数
、浮点数
、字符
、布尔
。 - 引用数据类型:包括
类
、数组
、接口
。
基本数据类型
四类八种基本数据类型:
数据类型 | 关键字 | 内存占用 | 取值范围 |
---|---|---|---|
字节型 | byte | 1个字节 | -128~127 |
短整型 | short | 2个字节 | -32768~32767 |
整型 | int(默认) | 4个字节 | -231次方~2的31次方-1 |
长整型 | long | 8个字节 | -2的63次方~2的63次方-1 |
单精度浮点数 | float | 4个字节 | 1.4013E-45~3.4028E+38 |
双精度浮点数 | double(默认) | 8个字节 | 4.9E-324~1.7977E+308 |
字符型 | char | 2个字节 | 0-65535 |
布尔类型 | boolean | 1个字节 | true,false |
Java中的默认类型:整数类型是
int
、浮点类型是double
。 long类型:建议数据后加L
表示。 float类型:建议数据后加F
表示。
public class Variable { public static void main(String[] args){ //定义字节型变量 byte b = 100; System.out.println(b); //定义短整型变量 short s = 1000; System.out.println(s); //定义整型变量 int i = 123456; System.out.println(i); //定义长整型变量 long l = 12345678900L; System.out.println(l); //定义单精度浮点型变量 float f = 5.5F; System.out.println(f); //定义双精度浮点型变量 double d = 8.5; System.out.println(d); //定义布尔型变量 boolean bool = false; System.out.println(bool); //定义字符型变量 char c = 'A'; System.out.println(c); } }
类型转换
不同范围的类型进行操作时, 会自动进行类型的提升
public static void main(String[] args) { int i = 1; double d = 2.5; //int类型和double类型运算,结果是double类型 //int类型会提升为double类型 double e = d+i; System.out.println(e); }
范围小的类型向范围大的类型提升,
byte
、short
、char
运算时直接提升为int
。byte、short、char‐‐>int‐‐>long‐‐>float‐‐>double
循环
for
, while
, do while
, 增强for
选择
if
, switch case
,switch case
不推荐, 如果忘写break, 后果很严重, 而且case
后面的类型有限,(python
只有if
, 可以用dict
)
一些常用类
Scanner类
Scanner
类来获取输入信息
public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String s = scanner.nextLine(); System.out.println("s = " + s); }
Random类
Random
生成随机数public int nextInt(int n)
: 返回一个伪随机数, 范围在0
(包括)和指定值n
(不包括)之间的int
值。
public static void main(String[] args) { Random random = new Random(); int i = random.nextInt(); int i1 = random.nextInt(5); System.out.println("i = " + i); System.out.println("i1 = " + i1); }
ArrayList类
数组集合类, 连续的, 区别于LinkedList
String类
字符串类, 类String
中包括用于检查各个字符串的方法,比如用于比较
字符串,搜索
字符串,提取
子字符串以及创建
具有翻译为大写或小写的所有字符的字符串的副本。
Arrays类
java.util.Arrays
此类包含用来操作数组
的各种方法,比如排序
和搜索
等。其所有方法均为静态方法
,调用起来非常简单。
Math类
java.lang.Math
类包含用于执行基本数学运算的方法,如初等指数
、对数
、平方根
和三角函数
。类似这样的工具类,其所有方法均为静态方法,并且不会创建对象,调用起来非常简单。
Object类
java.lang.Object
类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object
。
如果一个类没有特别指定父类, 那么默认则继承自Object
类。例如:
public class MyClass /*extends Object*/ { // ... }
toString方法
返回该对象的字符串表示, 默认是对象的类型+@+内存地址值
, python中的__str__
public class Person { private String name; private int age; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } // 省略构造器与Getter Setter }
equals方法
==
比较的是2个对象的地址,而equals
比较的是2个对象的内容
指示其他某个对象是否与此对象“相等”, 默认比较内存地址, python中的==
import java.util.Objects; public class Person { private String name; private int age; @Override public boolean equals(Object o) { // 如果对象地址一样,则认为相同 if (this == o) return true; // 如果参数为空,或者类型信息不一样,则认为不同 if (o == null || getClass() != o.getClass()) return false; // 转换为当前类型 Person person = (Person) o; // 要求基本类型相等,并且将引用类型交给java.util.Objects类的equals静态方法取用结果 return age == person.age && Objects.equals(name, person.name); } }
Objects类
在刚才重写equals代码中,使用到了java.util.Objects
类,那么这个类是什么呢?
在JDK7添加了一个Objects
工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save
(空指针安全的)或null-tolerant
(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。
在比较两个对象的时候,Object
的equals
方法容易抛出空指针异常,而Objects
类中的equals
方法就优化了这个问题。方法如下:
public static boolean equals(Object a, Object b)
: 判断两个对象是否相等。
源码:
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
Date类
java.util.Date类
表示特定的瞬间,精确到毫秒。
import java.util.Date; public class DateDemo { public static void main(String[] args) { System.out.println(new Date()); System.out.println(new Date(0L)); System.out.println(new Date().getTime()); } } Sun Sep 29 11:31:36 CST 2019 Thu Jan 01 08:00:00 CST 1970 1569727896597
DateFormat类
java.text.DateFormat
是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象
与String对象
之间进行来回转换。
- 格式化:按照指定的格式,从
Date对象
转换为String对象
。 - 解析:按照指定的格式,从
String对象
转换为Date对象
。
构造方法
由于DateFormat
为抽象类,不能直接使用,所以需要常用的子类java.text.SimpleDateFormat
。这个类需要一个模式(格式)来指定格式化或解析的标准。构造方法为:
public SimpleDateFormat(String pattern)
:用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat
。
参数pattern是一个字符串,代表日期时间的自定义格式。
标识字母(区分大小写) | 含义
------------|---
y | 年
M | 月
d | 日
H | 时
m | 分
s | 秒
常用方法
public String format(Date date)
:将Date对象格式化为字符串。public Date parse(String source)
:将字符串解析为Date对象。
import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateDemo { public static void main(String[] args) throws ParseException { System.out.println(new Date()); System.out.println(new Date(0L)); System.out.println(new Date().getTime()); DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(format.format(new Date())); System.out.println(format.parse("2019-09-29 11:46:13")); } } Sun Sep 29 11:47:14 CST 2019 Thu Jan 01 08:00:00 CST 1970 1569728834328 2019-09-29 11:47:14 Sun Sep 29 11:46:13 CST 2019
System类
java.lang.System
类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在System类的API文档中,常用的方法有
public static long currentTimeMillis()
:返回以毫秒为单位的当前时间。public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
:将数组中指定的数据拷贝到另一个数组中。
currentTimeMillis方法
currentTimeMillis
方法就是 获取当前系统时间与1970年01月01日00:00点之间的毫秒差值
System.out.println(System.currentTimeMillis());
arraycopy方法
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
:将数组中指定的数据拷贝到另一个数组中。
数组的拷贝动作是系统级的,性能很高。System.arraycopy
方法具有5个参数,含义分别为:
参数序号 | 参数名称 | 参数类型 | 参数含义 |
---|---|---|---|
1 | src | Object | 源数组 |
2 | srcPos | int | 源数组索引起始位置 |
3 | dest | Object | 目标数组 |
4 | destPos | int | 目标数组索引起始位置 |
5 | length | int | 复制元素个数 |
import java.util.Arrays; public class Demo11SystemArrayCopy { public static void main(String[] args) { int[] src = new int[]{1,2,3,4,5}; int[] dest = new int[]{6,7,8,9,10}; System.arraycopy( src, 0, dest, 0, 3); /*代码运行后:两个数组中的元素发生了变化 src数组元素[1,2,3,4,5] dest数组元素[1,2,3,9,10] */ } }
StringBuilder类
由于String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象, 当进行多次拼接时, 会造成时间和空间上的浪费
在API中对String类有这样的描述:字符串是常量,它们的值在创建后不能被更改
而StringBuilder
被称为可变字符序列,它是一个类似于String
的字符串缓冲区,是一个容器, 可以装很多字符串, 并且能够对其中的字符串进行各种操作。默认开辟16字符空间,超过自动扩充
构造方法
常用的两个
public StringBuilder()
:构造一个空的StringBuilder
容器。public StringBuilder(String str)
:构造一个StringBuilder
容器,并将字符串添加进去。
public StringBuilder() { super(16); } ... public StringBuilder(String str) { super(str.length() + 16); append(str); }
常用方法
public StringBuilder append(...)
:添加任意类型数据的字符串形式,并返回当前对象自身。public String toString()
:将当前StringBuilder对象转换为String对象。
包装类
Java提供了两个类型系统,基本类型
与引用类型
,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:
基本类型 | 对应的包装类(位于java.lang包中) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
面向对象
访问修饰符(私有, 保护, 公开, 默认)
public | protected | default(空的) | private | |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中(子类与无关类) | √ | √ | √ | |
不同包的子类 | √ | √ | ||
不同包中的无关类 | √ |
public
具有最大权限。private
则是最小权限。
编写代码时,如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用
private
,隐藏细节。 - 构造方法使用
public
,方便创建对象。 - 成员方法使用
public
,方便调用方法。
不加权限修饰符,其访问能力与
default
修饰符相同
static关键字
类变量
当static
修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。
类变量:使用
static
关键字修饰的成员变量。 static 数据类型 变量名;
静态方法
当static
修饰成员方法时,该方法称为类方法 。静态方法在声明中有static
,建议使用类名来调用,而不需要创建类的对象。调用方式非常简单。
类方法:使用
static
关键字修饰的成员方法,习惯称为静态方法。
修饰符 static 返回值类型 方法名 (参数列表){ // 执行语句 }
静态方法调用的注意事项:
静态方法可以直接访问类变量和静态方法。
静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。
静态方法中,不能使用this关键字。
静态方法只能访问静态成员。
调用格式
被static
修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。
// 访问类变量 类名.类变量名; // 调用静态方法 类名.静态方法名(参数);
静态原理图解
static
修饰的内容:
- 是随着类的加载而加载的,且只加载一次。
- 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
- 它优先于对象存在,所以,可以被所有对象共享。

静态代码块
静态代码块
:定义在成员位置,使用static修饰的代码块{ }。
- 位置:类中方法外。
- 执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。
public class ClassName{ static { // 执行语句 } }
作用:给类变量进行初始化赋值。用法演示,代码如下:
public class Game { public static int number; public static ArrayList<String> list; static { // 给类变量赋值 number = 2; list = new ArrayList<String>(); // 添加元素到集合中 list.add("张三"); list.add("李四"); } }
小贴士:
static
关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况
下,去调用方法。
super和this
父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private
修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。
super和this的含义
super
:代表父类的存储空间标识(可以理解为父亲的引用)。this
:代表当前对象的引用(谁调用就代表谁)。
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
继承
封装, 继承, 多态是面向对象的三大特征, Java仅支持单继承, 但可实现多接口
使用继承后, 子类中如果想使用父类中的同名变量时, 需要使用super
关键字; 子类使用自身的变量则不需要super
关键字
class Fu{ int age=35; String name="fu"; } class Zi extends Fu{ int age=10; String name = "zi"; void show(){ System.out.println("Zi.age = " + age); System.out.println("Zi.name = " + name); System.out.println("Fu.age = " + super.age); System.out.println("Fu.name = " + super.name); } } public class Extend { public static void main(String[] args) { Zi zi = new Zi(); zi.show(); } } Zi.age = 10 Zi.name = zi Fu.age = 35 Fu.name = fu
仅可访问非私有父类成员变量, 若想访问父类的私有成员变量, 则需要父类提供
get
和set
方法(封装原则)
子类继承父类后, 若要重写父类的方法, 则需要保证权限大于等于父类权限 返回值类型、函数名和参数列表都要一模一样。
多态
同一行为, 具有多个不同表现形式.
- 继承或者实现【二选一】
- 方法的重写【意义体现:不重写,无意义】
- 父类引用指向子类对象【格式体现】
父类类型 变量名 = new 子类对象; 变量名.方法名(); // 父类类型:指子类对象继承的父类类型,或者实现的父接口类型。 Fu f = new Zi(); f.method();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写后方法。
定义父类:
public abstract class Animal { public abstract void eat(); }
定义子类:
class Cat extends Animal { public void eat() { System.out.println("吃鱼"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨头"); } }
定义测试类:
public class Test { public static void main(String[] args) { // 多态形式,创建对象 Animal a1 = new Cat(); // 调用的是 Cat 的 eat a1.eat(); // 多态形式,创建对象 Animal a2 = new Dog(); // 调用的是 Dog 的 eat a2.eat(); } }
多态的好处
实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。
定义父类:
public abstract class Animal { public abstract void eat(); }
定义子类:
class Cat extends Animal { public void eat() { System.out.println("吃鱼"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨头"); } }
定义测试类:
public class Test { public static void main(String[] args) { // 多态形式,创建对象 Cat c = new Cat(); Dog d = new Dog(); // 调用showCatEat showCatEat(c); // 调用showDogEat showDogEat(d); /* 以上两个方法, 均可以被showAnimalEat(Animal a)方法所替代 而执行效果一致 */ showAnimalEat(c); showAnimalEat(d); } public static void showCatEat (Cat c){ c.eat(); } public static void showDogEat (Dog d){ d.eat(); } public static void showAnimalEat (Animal a){ a.eat(); } }
引用类型转换
多态的转型分为向上转型与向下转型两种:
向上转型
向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。
当父类引用指向一个子类对象时,便是向上转型。
父类类型 变量名 = new 子类类型(); 如:Animal a = new Cat();
向下转型
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
子类类型 变量名 = (子类类型) 父类变量名; 如:Cat c =(Cat) a;
为什么要转型
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用
子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
定义类:
abstract class Animal { abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("吃鱼"); } public void catchMouse() { System.out.println("抓老鼠"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨头"); } public void watchHouse() { System.out.println("看家"); } }
定义测试类:
public class Test { public static void main(String[] args) { // 向上转型 Animal a = new Cat(); a.eat(); // 调用的是 Cat 的 eat // 向下转型 Cat c = (Cat)a; c.catchMouse(); // 调用的是 Cat 的 catchMouse } }
转型的异常
public class Test { public static void main(String[] args) { // 向上转型 Animal a = new Cat(); a.eat(); // 调用的是 Cat 的 eat // 向下转型 Dog d = (Dog)a; d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】 } }
这段代码可以通过编译,但是运行时,却报出了 ClassCastException
,类型转换异常!这是因为,明明创建了Cat
类型对象,运行时,当然不能转换成Dog
对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。
为了避免ClassCastException
的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验
变量名 instanceof 数据类型 如果变量属于该数据类型,返回true。 如果变量不属于该数据类型,返回false。
public class Test { public static void main(String[] args) { // 向上转型 Animal a = new Cat(); a.eat(); // 调用的是 Cat 的 eat // 向下转型 if (a instanceof Cat){ Cat c = (Cat)a; c.catchMouse(); // 调用的是 Cat 的 catchMouse } else if (a instanceof Dog){ Dog d = (Dog)a; d.watchHouse(); // 调用的是 Dog 的 watchHouse } } }
接口
接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK7及以前),默认方法和静态方法(JDK 8),私有方法(JDK 9)。
接口的定义,它与定义类方式相似,但是使用interface
关键字。它也会被编译成.class
文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型:
数组
,类
,接口
。
接口的使用,它不能创建对象,但是可以被实现(implements
类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
格式
public interface 接口名称 { // 抽象方法 // 默认方法 // 静态方法 // 私有方法 }
抽象方法:使用 abstract
关键字修饰,可以省略,没有方法体。该方法供子类实现使用。
public interface InterFaceName { public abstract void method(); }
默认方法:使用 default
修饰,不可省略,供子类调用或者子类重写。 静态方法:使用 static
修饰,供接口直接调用。
public interface InterFaceName { public default void method() { // 执行语句 } public static void method2() { // 执行语句 } }
私有方法:使用 private
修饰,供接口中的默认方法或者静态方法调用。
public interface InterFaceName { private void method() { // 执行语句 } }
基本实现
非抽象子类实现接口:
- 必须重写接口中所有抽象方法.
- 继承了接口的默认方法, 即可以直接调用, 也可以重写.
实现格式:
class 类名 implements 接口名 { // 重写接口中抽象方法【必须】 // 重写接口中默认方法【可选】 }
抽象方法的使用: 必须全部实现
默认方法的使用: 可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。
静态方法的使用: 静态与.class
文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用
私有方法的使用 : 私有方法:只有默认方法可以调用。私有静态方法:默认方法和静态方法可以调用。
静态方法的使用
public interface LiveAble { public static void run(){ System.out.println("跑起来~~~"); } } public class Animal implements LiveAble { // 无法重写静态方法 } public class InterfaceDemo { public static void main(String[] args) { // Animal.run(); // 【错误】无法继承方法,也无法调用 LiveAble.run(); // } } 跑起来~~~
接口的多实现
一个类可以实现多个接口
class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... { // 重写接口中抽象方法【必须】 // 重写接口中默认方法【不重名时可选】 }
抽象方法
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
interface A { public abstract void showA(); public abstract void show(); } interface B { public abstract void showB(); public abstract void show(); }
public class C implements A,B{ @Override public void showA() { System.out.println("showA"); } @Override public void showB() { System.out.println("showB"); } @Override public void show() { System.out.println("show"); } }
默认方法
接口中,有多个默认方法时,实现类都可继承使用。如果默认方法有重名的,必须重写一次。
interface A { public default void methodA(){} public default void method(){} } interface B { public default void methodB(){} public default void method(){} }
public class C implements A,B{ @Override public void method() { System.out.println("method"); } }
静态方法
接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。
优先级的问题
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法。
定义接口:
interface A { public default void methodA(){ System.out.println("AAAAAAAAAAAA"); } }
定义父类:
class D { public void methodA(){ System.out.println("DDDDDDDDDDDD"); } }
定义子类:
class C extends D implements A { // 未重写methodA方法 }
定义测试类:
public class Test { public static void main(String[] args) { C c = new C(); c.methodA(); } } DDDDDDDDDDDD
接口的多继承
一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends
关键字,子接口继承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。
定义父接口:
interface A { public default void method(){ System.out.println("AAAAAAAAAAAAAAAAAAA"); } } interface B { public default void method(){ System.out.println("BBBBBBBBBBBBBBBBBBB"); } }
定义子接口:
interface D extends A,B{ @Override public default void method() { System.out.println("DDDDDDDDDDDDDD"); } }
子接口重写默认方法时,default关键字可以保留。 子类重写默认方法时,default关键字不可以保留。
小结
- 接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用
public static final
修饰。- 接口中,没有构造方法,不能创建对象。
- 接口中,没有静态代码块
抽象类
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。
修饰符 abstract 返回值类型 方法名 (参数列表); public abstract void run();
如果一个类中含有抽象方法, 那么该类必须是抽象类
public abstract class Animal { public abstract void run(); }
使用
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
注意事项
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
final
用于修饰不可改变内容。final
: 不可改变。可以用于修饰类、方法和变量。
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,不能被重新赋值。
修饰类
final class 类名 { }
像 public final class String
、 public final class Math
、 public final class Scanner
等,都是被final修饰的,目的就是供我们使用,而不让我们随意改变其内容。
修饰方法
修饰符 final 返回值类型 方法名(参数列表){ //方法体 }
重写被 final
修饰的方法,编译时就会报错。
修饰变量
局部变量 - 基本类型
基本类型的局部变量,被final
修饰后,只能赋值一次,不能再更改。
public class FinalDemo1 { public static void main(String[] args) { // 声明变量,使用final修饰 final int a; // 第一次赋值 a = 10; // 第二次赋值 a = 20; // 报错,不可重新赋值 // 声明变量,直接赋值,使用final修饰 final int b = 10; // 第二次赋值 b = 20; // 报错,不可重新赋值 } }
局部变量 - 引用类型
引用类型的局部变量,被final
修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改
public class FinalDemo2 { public static void main(String[] args) { // 创建 User 对象 final User u = new User(); // 创建 另一个 User对象 u = new User(); // 报错,指向了新的对象,地址值改变。 // 调用setName方法 u.setName("张三"); // 可以修改 } }
成员变量
成员变量涉及到初始化的问题,初始化方式有两种,只能二选一:
- 显示初始化
public class User { final String USERNAME = "张三"; private int age; }
- 构造方法初始化
public class User { final String USERNAME ; private int age; public User(String username, int age) { this.USERNAME = username; this.age = age; } }
被final修饰的常量名称,一般都有书写规范,所有字母都大写。
内部类
class 外部类 { class 内部类{ } }
访问特点
- 内部类可以直接访问外部类的成员,包括私有成员。
- 外部类要访问内部类的成员,必须要建立内部类的对象。
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
定义类:
public class Person { private boolean live = true; class Heart { public void jump() { // 直接访问外部类成员 if (live) { System.out.println("心脏在跳动"); } else { System.out.println("心脏不跳了"); } } } public boolean isLive() { return live; } public void setLive(boolean live) { this.live = live; } }
定义测试类:
public class InnerDemo { public static void main(String[] args) { // 创建外部类对象 Person p = new Person(); // 创建内部类对象 Person.Heart heart = p.new Heart(); // 调用内部类方法 heart.jump(); // 调用外部类方法 p.setLive(false); // 调用内部类方法 heart.jump(); } } 心脏在跳动 心脏不跳了
内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和
$
符号 。
比如,Person$Heart.class
匿名内部类
匿名内部类 :是内部类的简化写法。它的本质是一个带具体实现的
父类或者父接口的
匿名的
子类对象。
匿名内部类必须继承一个父类或者实现一个父接口。
new 父类名或者接口名(){ // 方法重写 @Override public void method() { // 执行语句 } };
定义接口:
public abstract class FlyAble{ public abstract void fly(); }
创建匿名内部类,并调用:
public class InnerDemo { public static void main(String[] args) { /* 1.等号右边:是匿名内部类,定义并创建该接口的子类对象 2.等号左边:是多态赋值,接口类型引用指向子类对象 */ FlyAble f = new FlyAble(){ public void fly() { System.out.println("我飞了~~~"); } }; //调用 fly方法,执行重写后的方法 f.fly(); } }
通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递
public class InnerDemo2 { public static void main(String[] args) { /* 1.等号右边:定义并创建该接口的子类对象 2.等号左边:是多态,接口类型引用指向子类对象 */ FlyAble f = new FlyAble(){ public void fly() { System.out.println("我飞了~~~"); } }; // 将f传递给showFly方法中 showFly(f); } public static void showFly(FlyAble f) { f.fly(); } }
以上两步,也可以简化为一步
public class InnerDemo3 { public static void main(String[] args) { /* 创建匿名内部类,直接传递给showFly(FlyAble f) */ showFly( new FlyAble(){ public void fly() { System.out.println("我飞了~~~"); } }); } public static void showFly(FlyAble f) { f.fly(); } }
引用类型用法
基本类型可以作为成员变量、作为方法的参数、作为方法的返回值,那么当然引用类型也是可以的。
class作为成员变量
interface作为成员变量
interface作为方法参数和返回值类型
接口作为参数时,传递它的子类对象。
接口作为返回值类型时,返回它的子类对象。
集合
集合是java中提供的一种容器,可以用来存储多个数据. 集合的长度是可变的, 可以存储不同的数据类型
单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.List
和java.util.Set
。其中,List
的特点是元素有序
、元素可重复
。Set
的特点是元素无序
,而且不可重复
。List
接口的主要实现类有java.util.ArrayList
和java.util.LinkedList
,Set
接口的主要实现类有java.util.HashSet
和java.util.TreeSet
。
Collection 常用功能
Collection
是所有单列集合的父接口,因此在Collection
中定义了单列集合(List
和Set
)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
public boolean add(E e)
:把给定的对象添加到当前集合中 。public void clear()
: 清空集合中所有的元素。public boolean remove(E e)
: 把给定的对象在当前集合中删除。public boolean contains(E e)
: 判断当前集合中是否包含给定的对象。public boolean isEmpty()
: 判断当前集合是否为空。public int size()
: 返回集合中元素的个数。public Object[] toArray()
: 把集合中的元素,存储到数组中。
Iterator迭代器
JDK提供了一个接口java.util.Iterator
。Iterator
接口也是Java集合
中的一员,但它与Collection
、Map
接口有所不同,Collection
接口与Map
接口主要用于存储元素,而Iterator
主要用于迭代访问(即遍历
)Collection
中的元素,因此Iterator
对象也被称为迭代器
。
想要遍历Collection
集合,那么就要获取该集合迭代器完成迭代操作.
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的。- 迭代:即
Collection
集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
Iterator接口的常用方法:
public E next()
:返回迭代的下一个元素。public boolean hasNext()
:如果仍有元素可以迭代,则返回true
。
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class IteratorDemo { public static void main(String[] args) { // 使用多态方式 创建对象 Collection<String> coll = new ArrayList<String>(); // 添加元素到集合 coll.add("123"); coll.add("456"); coll.add("789"); //遍历 //使用迭代器 遍历 每个集合对象都有自己的迭代器 Iterator<String> it = coll.iterator(); // 泛型指的是 迭代出 元素的数据类型 while(it.hasNext()){ //判断是否有迭代元素 String s = it.next();//获取迭代出的元素 System.out.println(s); } } } 123 456 789
在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的
next
方法,将会发生java.util.NoSuchElementException
没有集合元素的错误。
java/util/ArrayList.java
/** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
增强for
增强for是一个高级for
循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator
迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
for(元素的数据类型 变量 : Collection集合or数组){ //写操作代码 }
泛型
泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>
表示。但是一旦使用泛型的通配符后,只能使用Object
类中的共性方法,集合中元素自身方法无法使用。
public interface Collection<E> extends Iterable<E>{ ... boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean containsAll(Collection<?> c); ... }
通配符高级使用----受限泛型
设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。
泛型的上限:
格式: 类型名称<? extends 类 >
对象名称
意义: 只能接收该类型及其子类泛型的下限:
格式: 类型名称<? super 类 >
对象名称
意义: 只能接收该类型及其父类型
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类
public static void main(String[] args) { Collection<Integer> list1 = new ArrayList<Integer>(); Collection<String> list2 = new ArrayList<String>(); Collection<Number> list3 = new ArrayList<Number>(); Collection<Object> list4 = new ArrayList<Object>(); getElement(list1); getElement(list2);//报错 getElement(list3); getElement(list4);//报错 getElement2(list1);//报错 getElement2(list2);//报错 getElement2(list3); getElement2(list4); } // 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类 public static void getElement1(Collection<? extends Number> coll){} // 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类 public static void getElement2(Collection<? super Number> coll){}
List集合
List接口中常用方法
List
作为Collection
集合的子接口
,不但继承了Collection
接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法:
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。public E get(int index)
:返回集合中指定位置的元素。public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
ArrayList
java.util.ArrayList
集合数据存储的结构是数组结构。元素增删慢
,查找快
LinkedList
java.util.LinkedList
集合数据存储的结构是链表结构, 是一个双向链表
。方便元素添加
、删除
的集合。
对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList
提供了大量首尾操作的方法。
public void addFirst(E e)
: 将指定元素插入此列表的开头。public void addLast(E e)
: 将指定元素添加到此列表的结尾。public E getFirst()
: 返回此列表的第一个元素。public E getLast()
: 返回此列表的最后一个元素。public E removeFirst()
: 移除并返回此列表的第一个元素。public E removeLast()
: 移除并返回此列表的最后一个元素。public E pop()
: 从此列表所表示的堆栈处弹出一个元素。public void push(E e)
: 将元素推入此列表所表示的堆栈。public boolean isEmpty()
:如果列表不包含元素,则返回true。
Set接口
java.util.Set
接口和 java.util.List
接口一样,同样继承自 Collection
接口,它与 Collection
接口中的方法基本一致,并没有对 Collection
接口进行功能上的扩充,只是比 Collection
接口更加严格了。与 List
接口不同的是, Set
接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
HashSet
java.util.HashSet
是 Set
接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。 java.util.HashSet
底层的实现其实是一个java.util.HashMap
支持
HashSet
是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取
和查找
性能。保证元素唯一性的方式依赖于: hashCode
与 equals
方法。

HashSet存储自定义类型元素
给HashSet
中存放自定义类型元素时,需要重写对象中的hashCode
和equals
方法,建立自己的比较方式,才能保证HashSet
集合中的对象唯一
IDEA自动生成的方法
import java.util.Objects; public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && name.equals(student.name); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
import java.util.HashSet; public class HashSetDemo { public static void main(String[] args) { HashSet<Student> hashSet = new HashSet<>(); hashSet.add(new Student("张三", 20)); hashSet.add(new Student("李四", 18)); hashSet.add(new Student("王五", 22)); hashSet.add(new Student("赵六", 25)); for (Student s : hashSet) { System.out.println(s); } } } Student{name='张三', age=20} Student{name='王五', age=22} Student{name='李四', age=18} Student{name='赵六', age=25}
LinkedHashSet
java.util.LinkedHashSet
, 它是链表和哈希表组合的一个有序
数据存储结构。
import java.util.HashSet; import java.util.LinkedHashSet; public class LinkedHashSetDemo { public static void main(String[] args) { LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>(); HashSet<String> hashSet = new HashSet<>(); linkedHashSet.add("张三"); linkedHashSet.add("李四"); linkedHashSet.add("王五"); linkedHashSet.add("赵六"); hashSet.add("张三"); hashSet.add("李四"); hashSet.add("王五"); hashSet.add("赵六"); System.out.println("hashSet = " + hashSet.toString()); System.out.println("linkedHashSet = " + linkedHashSet.toString()); } } hashSet = [李四, 张三, 王五, 赵六] linkedHashSet = [张三, 李四, 王五, 赵六]
Collections
java.utils.Collections
是集合工具类,用来对集合进行操作。
public static <T> boolean addAll(Collection<T> c, T... elements)
:往集合中添加一些元素。public static void shuffle(List<?> list)
:打乱集合顺序。public static <T> void sort(List<T> list)
:将集合中元素按照默认规则排序。public static <T> void sort(List<T> list,Comparator<? super T> )
:将集合中元素按照自定义的规则排序。
import java.util.ArrayList; import java.util.Collections; public class CollectionsDemo { public static void main(String[] args) { ArrayList<String> arrayList = new ArrayList<>(); // arrayList.add("123"); // arrayList.add("789"); // arrayList.add("654"); // arrayList.add("369"); Collections.addAll(arrayList, "123", "789", "654", "369"); System.out.println("arrayList: " + arrayList.toString()); Collections.sort(arrayList); System.out.println("arrayList: " + arrayList.toString()); } }
Comparator比较器
public static <T> void sort(List<T> list)
: 将集合中元素按照默认规则排序。
public int compare(String o1, String o2)
: 比较其两个参数的顺序。
两个对象比较的结果有三种:大于,等于,小于。
如果要按照升序排序, 则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数) 如果要按照降序排序 则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)
Comparable和Comparator两个接口的区别
Comparable
强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()
一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort
(和Arrays.sort
)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。需重写compareTo
方法Comparator
强行对某个对象进行整体排序。可以将Comparator
传递给sort
方法(如Collections.sort
或Arrays.sort
),从而允许在排序顺序上实现精确控制。还可以使用Comparator
来控制某些数据结构(如有序set
或有序映射
)的顺序,或者为那些没有自然顺序的对象collection
提供排序。需重写compare
方法
Comparable
import java.util.Objects; public class Student implements Comparable<Student>{ private String name; private int age; Student(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && name.equals(student.name); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public int compareTo(Student o) { int res = name.compareTo(o.name); if (0==res) return age - o.age; else return res; } }
import java.util.ArrayList; import java.util.Collections; public class ComparableDemo { public static void main(String[] args) { ArrayList<Student> arrayList = new ArrayList<Student>(); arrayList.add(new Student("123", 22)); arrayList.add(new Student("567", 17)); arrayList.add(new Student("567", 15)); arrayList.add(new Student("346", 20)); System.out.println(arrayList.toString()); Collections.sort(arrayList); System.out.println(arrayList.toString()); } } [Student{name='123', age=22}, Student{name='567', age=17}, Student{name='567', age=15}, Student{name='346', age=20}] [Student{name='123', age=22}, Student{name='346', age=20}, Student{name='567', age=15}, Student{name='567', age=17}]
Comparator
import java.util.Objects; public class Student /*implements Comparable<Student>Student*/{ private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } Student(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return age == student.age && name.equals(student.name); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } // @Override // public int compareTo(Student o) { // int res = name.compareTo(o.name); // if (0==res) return age - o.age; // else return res; // } }
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class ComparatorDemo { public static void main(String[] args) { ArrayList<Student> arrayList = new ArrayList<Student>(); arrayList.add(new Student("123", 22)); arrayList.add(new Student("567", 17)); arrayList.add(new Student("567", 15)); arrayList.add(new Student("346", 20)); System.out.println(arrayList.toString()); Collections.sort(arrayList, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { int res = o1.getAge() - o2.getAge(); if (res == 0)return o1.getName().compareTo(o2.getName()); else return res; } }); System.out.println(arrayList.toString()); } } [Student{name='123', age=22}, Student{name='567', age=17}, Student{name='567', age=15}, Student{name='346', age=20}] [Student{name='567', age=15}, Student{name='567', age=17}, Student{name='346', age=20}, Student{name='123', age=22}]
map
HashMap
:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()
方法、equals()
方法。LinkedHashMap
:HashMap
下有个子类LinkedHashMap
,存储数据采用的哈希表结构
+链表结构
。通过链表结构
可以保证元素的存取顺序一致
;通过哈希表结构
可以保证键的唯一
、不重复
,需要重写键的hashCode()
方法、equals()
方法。
Map接口中的集合都有两个泛型变量,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量的数据类型可以相同,也可以不同。
import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; public class MapDemo { private void addElement(Map<String, String> map){ map.put("张三", "aaa"); map.put("李四", "bbb"); map.put("王五", "ccc"); map.put("赵六", "ddd"); } private void showHashMap(){ HashMap<String, String> stringHashMap = new HashMap<>(); addElement(stringHashMap); System.out.println(stringHashMap.toString()); } private void showLinkedHashMap(){ LinkedHashMap<String, String> stringLinkedHashMap = new LinkedHashMap<>(); addElement(stringLinkedHashMap); System.out.println(stringLinkedHashMap.toString()); } public static void main(String[] args) { new MapDemo().showHashMap(); new MapDemo().showLinkedHashMap(); } } {李四=bbb, 张三=aaa, 王五=ccc, 赵六=ddd} {张三=aaa, 李四=bbb, 王五=ccc, 赵六=ddd}
常用方法
public V put(K key, V value)
: 把指定的键与指定的值添加到Map集合中。public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。public V get(Object key)
: 根据指定的键, 在Map集合中获取对应的值, 不存在则返回nullpublic Set<K> keySet()
: 获取Map集合中所有的键,存储到Set集合中。public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
使用
put
方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。
Entry键值对对象
Map
中的一个键值对对象就是一个Entry
public K getKey()
:获取Entry对象中的键。public V getValue()
:获取Entry对象中的值。
// Set<Map.Entry<String, String>> entries = stringHashMap.entrySet(); // // for (Map.Entry<String, String> entry : entries){ // System.out.print(entry.getKey()); // System.out.println(entry.getValue()); // } for (Map.Entry<String, String> entry : stringHashMap.entrySet()) { System.out.println("entry = " + entry); }
Map集合不能直接使用
迭代器
或者foreach
进行遍历。但是转成Set
之后就可以使用了。
HashMap存储自定义类型键值
- 给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的
hashCode
和equals
方法 - 如果要保证map中存放的key和取出的顺序一致,可以使用
java.util.LinkedHashMap
集合来存放
异常
程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
异常机制其实是帮助我们找到程序中的问题,异常的根类是 java.lang.Throwable
,其下有两个子类:java.lang.Error
与java.lang.Exception
, 平常所说的异常指java.lang.Exception
。
Throwable
体系:
Error
: 严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。Exception
: 表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎。
Throwable
中的常用方法:
public void printStackTrace()
:打印异常的详细信息。包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace
���public String getMessage()
:获取发生异常的原因。提示给用户的时候,就提示错误原因。public String toString()
:获取异常的类型和异常描述信息(不用)。

异常的处理
throw
throw new NullPointerException("要访问的arr数组不存在"); throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");
Objects非空判断
java/util/Objects.java:202 public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; }
声明异常throws
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
public class ThrowsDemo { public static void main(String[] args) throws FileNotFoundException { read("a.txt"); } // 如果定义功能时有问题发生需要报告给调用者。可以通过在方法上使用throws关键字进行声明 public static void read(String path) throws FileNotFoundException { if (!path.equals("a.txt")) {//如果不是 a.txt这个文件 // 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw throw new FileNotFoundException("文件不存在"); } } }
捕获异常try…catch
try{ 编写可能会出现异常的代码 }catch(异常类型 e){ 处理异常的代码 //记录日志/打印异常信息/继续抛出异常 }
public class TryCatchDemo { public static void main(String[] args) { try {// 当产生异常时,必须有处理方式。要么捕获,要么声明。 read("b.txt"); } catch (FileNotFoundException e) {// 括号中需要定义什么呢? //try中抛出的是什么异常,在括号中就定义什么异常类型 System.out.println(e); } System.out.println("over"); } /* * * 我们 当前的这个方法中 有异常 有编译期异常 */ public static void read(String path) throws FileNotFoundException { if (!path.equals("a.txt")) {//如果不是 a.txt这个文件 // 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw throw new FileNotFoundException("文件不存在"); } } }
finally代码块
finally
:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally
就是解决这个问题的,在finally
代码块中存放的代码都是一定会被执行的。
try...catch....finally:自身需要处理异常,最终还得关闭资源。
public class TryCatch{ public static void main(String[] args) { try { read("a.txt"); } catch (FileNotFoundException e) { //抓取到的是编译期异常 抛出去的是运行期 throw new RuntimeException(e); } finally { System.out.println("不管程序怎样,这里都将会被执行。"); } System.out.println("over"); } /* * * 我们 当前的这个方法中 有异常 有编译期异常 */ public static void read(String path) throws FileNotFoundException { if (!path.equals("a.txt")) {//如果不是 a.txt这个文件 // 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw throw new FileNotFoundException("文件不存在"); } } }
当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。

自定义异常
1. 自定义一个编译期异常: 自定义类 并继承于 java.lang.Exception 。 2. 自定义一个运行时期的异常类:自定义类 并继承于 java.lang.RuntimeException 。
线程和进程
- 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
- 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
一个程序运行后至少有一个进程,一个进程中可以包含多个线程
线程调度:
- 分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。 - 抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
并发与并行
- 并发:指两个或多个事件在同一个时间段内发生。
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
多线程
Java中通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
通过调用start()
方法来通知JVM来开辟新的线程空间, 并在新的线程空间里面执行对象的run
方法
当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。
Thread类
构造方法:
public Thread()
:分配一个新的线程对象。public Thread(String name)
:分配一个指定名字的新的线程对象。public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。
常用方法:
public String getName()
:获取当前线程名称。public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。public void run()
:此线程要执行的任务在此处定义代码。public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。
创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式
Runnable接口
- 定义
Runnable
接口的实现类,并重写该接口的run()
方法,该run()
方法的方法体同样是该线程的线程执行体。 - 创建
Runnable
实现类的实例,并以此实例作为Thread
的target
来创建Thread
对象,该Thread
对象才是真正的线程对象。 - 调用线程对象的
start()
方法来启动线程。
public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } } public class Demo { public static void main(String[] args) { //创建自定义类对象 线程任务对象 MyRunnable mr = new MyRunnable(); //创建线程对象 Thread t = new Thread(mr, "小强"); t.start(); for (int i = 0; i < 20; i++) { System.out.println("旺财 " + i); } } }
Runnable
对象仅仅作为Thread
对象的target
,Runnable
实现类里包含的run()
方法仅作为线程执行体。而实际的线程对象依然是Thread
实例,只是该Thread
线程负责执行其target
的run()
方法。
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。
匿名内部类方式实现线程的创建
public class NoNameInnerClassThread { public static void main(String[] args) { // new Runnable(){ // public void run(){ // for (int i = 0; i < 20; i++) { // System.out.println("张宇:"+i); // } // } // }; //‐‐‐这个整体 相当于new MyRunnable() Runnable r = new Runnable(){ public void run(){ for (int i = 0; i < 20; i++) { System.out.println("张宇:"+i); } } }; new Thread(r).start(); for (int i = 0; i < 20; i++) { System.out.println("费玉清:"+i); } } }
线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
多线程如果不加约束, 很容易出问题的.
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步
要解决多线程并发访问一个资源的安全性问题,Java中提供了同步机制(synchronized
)来解决。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
- 同步代码块。
- 同步方法。
- 锁机制。
同步代码块
同步代码块: synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){ 需要同步操作的代码 }
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象 可以是任意类型。
- 多个线程对象 要使用同一把锁。
在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着
(BLOCKED)。
public class Ticket implements Runnable{ private int ticket = 100; Object lock = new Object(); /* * 执行卖票操作 */ @Override public void run() { //每个窗口卖票的操作 //窗口 永远开启 while(true){ synchronized (lock) { if(ticket>0){//有票 可以卖 //出票操作 //使用sleep模拟一下出票时间 try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto‐generated catch block e.printStackTrace(); } //获取当前线程对象的名字 String name = Thread.currentThread().getName(); System.out.println(name+"正在卖:"+ticket‐‐); } } } } }
同步方法
同步方法:使用synchronized
修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
public synchronized void method(){ 可能会产生线程安全问题的代码 }
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
public class Ticket implements Runnable{ private int ticket = 100; /* * 执行卖票操作 */ @Override public void run() { //每个窗口卖票的操作 //窗口 永远开启 while(true){ sellTicket(); } } /* * 锁对象 是 谁调用这个方法 就是谁 * 隐含 锁对象 就是 this * */ public synchronized void sellTicket(){ if(ticket>0){//有票 可以卖 //出票操作 //使用sleep模拟一下出票时间 try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto‐generated catch block e.printStackTrace(); } //获取当前线程对象的名字 String name = Thread.currentThread().getName(); System.out.println(name+"正在卖:"+ticket‐‐); } } }
锁机制(Lock锁)
java.util.concurrent.locks.Lock
机制提供了比synchronized
代码块和synchronized
方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock()
: 加同步锁。public void unlock()
: 释放同步锁。
public class Ticket implements Runnable{ private int ticket = 100; Lock lock = new ReentrantLock(); /* * 执行卖票操作 */ @Override public void run() { //每个窗口卖票的操作 //窗口 永远开启 while(true){ lock.lock(); if(ticket>0){//有票 可以卖 //出票操作 //使用sleep模拟一下出票时间 try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto‐generated catch block e.printStackTrace(); } //获取当前线程对象的名字 String name = Thread.currentThread().getName(); System.out.println(name+"正在卖:"+ticket‐‐); } lock.unlock(); } } }
线程状态
API中 java.lang.Thread.State
这个枚举中给出了六种线程状态
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡, 或者因为没有捕获的异常终止了run方法而死亡。 |
Timed Waiting(计时等待)
Timed Waiting
在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。
当调用了sleep
方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting
(计时等待)
实现一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串
public class MyThread extends Thread { public void run() { for (int i = 0; i < 100; i++) { if ((i) % 10 == 0) { System.out.println("‐‐‐‐‐‐‐" + i); } System.out.print(i); try { Thread.sleep(1000); System.out.print(" 线程睡眠1秒!\n"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { new MyThread().start(); } }
- 进入
TIMED_WAITING
状态的一种常见情形是调用的sleep
方法,单独的线程也可以调用,不一定非要有协作关系 - 为了让其他线程有机会执行,可以将
Thread.sleep()
的调用放线程run()
之内。这样才能保证该线程执行过程中会睡眠 sleep
与锁无关,线程睡眠到期自动苏醒,并返回到Runnable
(可运行)状态sleep()
中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行。

BLOCKED(锁阻塞)
Blocked
状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。
线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
这是由Runnable状态进入Blocked状态。除此Waiting以及Time Waiting状态也会在某种情况下进入阻塞状态
