Java 进阶总结 - Zhangyao719/java-study GitHub Wiki
- 静态成员属于类的成员,不属于实例对象的成员(非静态的成员属于对象成员);
- 静态成员会随着类的加载而加载;
- 静态成员优先于非静态成员存在在内存中且在内存中只有一份;
- 静态成员可以被类和类的所有实例对象共享。
public class Student {
// 静态变量
static int total;
// 实例变量
String name;
// 静态方法
public static void fn1() {
System.out.println("类和对象都能调用静态方法");
System.out.println("只能访问静态成员(变量和方法)");
System.out.println("不能使用 this");
}
public void fn2() {
System.out.println("只有对象能调用");
System.out.println("可以访问静态变量:" + total);
System.out.println("可以调用静态方法");
fn1();
}
}
- 类方法中可以直接访问类的成员,不可以直接访问实例成员;
- 实例方法中既可以直接访问类的成员,也可以直接访问实例成员;
- 实例方法中可以出现 this 关键字(代表当前对象),类方法中不能出现 this 关键字。
public class Test {
public static ArrayList<String> users = new ArrayList();
// 静态代码块
static {
System.out.println("类加载时执行");
users.add("张三");
users.add("李四");
}
public static void main(String[] args) {
System.out.println(users); // ["张三", "李四"]
}
}
- 类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次;
- 完成类的初始化,对类变量进行初始化赋值。
public class Test {
// 实例代码块
{
System.out.println("创建对象时先执行");
}
// 构造器
public Test() {
System.out.println("构造器后执行");
}
public static void main(String[] args) {
new Test()
}
}
- 每次创建对象时,执行实例代码块,并在构造器之前执行;
- 和构造器一样,都用来完成对象的初始化。
确保一个类只有一个对象
- 把类的构造器私有(不允许被直接拿来实例化);
- 定义一个类变量记住类的一个对象;
- 定义一个类方法,返回对象;
public class SingleA {
// 2. 静态变量保存实例对象
private static SingleA a = new SingleA();
// 1. 私有化构造器
private SingleA() {}
// 3. 封装一个静态方法暴露实例对象(防止被外部篡改)
public static SingleA getSingleInstance() {
return a;
}
}
public class SingleB {
private static SingleB b;
private SingleB() {}
public static SingleB getSingleInstance() {
// 在调用方法时才开始创建单例
if (b == null) {
b = new SingleB();
}
return b;
}
}
- 子类能继承父类的非私有成员(变量和方法);
- 子类创建的对象包含子类和父类的成员,但是能否访问取决于成员的访问权限。
public class Father {
public int i;
private int j;
public void printI() {
System.out.println("i = " + i);
}
private void printJ() {
System.out.println("j = " + j);
}
public int getJ() {
return j
}
}
public class Son extends Father {
public int k;
public void printK() {
System.out.println("k = " + k);
}
}
public class Test {
public static void main(String[] args) {
Son s1 = new Son();
System.out.println(s1.i); // success,父类实例变量
System.out.println(s1.j); // error,私有变量无法访问
System.out.println(s1.k); // success,子类实例变量
s1.printI(); // success
s1.printJ(); // error
System.out.println("使用 get 获取父类私有变量:" + s1.getJ()); // success
}
}
权限修饰符 | 访问范围 |
---|---|
private | 只能本类 |
缺省 | 本类、同一个包中的类 |
protected | 本类,同一个包中的类,子类 |
public | 任意位置 |
- Java 是单继承,一个类只能继承一个直接父类;
- Java 不支持多继承,但支持多层继承;
// Java 不允许一次多继承
class A extends B, C {}
// Java 支持多层继承
class D {}
class E extends D {}
class F extends E {}
- Java 中所有的类都继承于
Object
子类可以重写一个方法名和参数列表一样,但是内部处理逻辑不同的方法来覆盖父类中的同名方法,以满足自己的特定需求,这就是方法重写。
- 使用
@Override
注解,它可以制定 Java 编译器,检查我们方法重写的格式是否正确,代码可读性也会更好; - 访问权限必须大于或等于父类方法的权限(public > protected > 缺省);
- 返回值类型必须与父类的方法返回值类型一致,或者范围更小;
- 私有方法、静态方法不能重写,否则报错。
访问同名成员,默认依据就近原则,也可以使用关键字指定要访问的成员。
-
this
访问自己的成员; -
super
访问父类的成员;
class Father {
String name = "父类名称";
}
class Son extends Father {
String name= "子类名称";
public void showName() {
String name = "局部名称";
System.out.println(name); // 就近原则 "局部名称"
System.out.println(this.name); // "子类名称"
System.out.println(super.name); // "父类名称"
}
}
class Father {
@Override
public void run() {
System.out.println("子类方法");
}
public void go() {
run(); // 执行子类的方法
super.run(); // 执行父类的方法
}
}
class Son extends Father {
public void run() {
System.out.println("父类方法");
}
}
子类的全部构造器(有参或无参)都会先调用父类的无参构造器,再执行自己。
public class Animal {
public Animal() {
System.out.println("父类无参构造器");
}
public Animal(String name) {
System.out.println("父类有参构造器");
}
}
public class Tiger extends Animal {
String name;
public Tiger() {
System.out.println("子类无参构造器");
}
public Tiger(String name) {
System.out.println("子类有参构造器");
this.name = name;
}
}
// 都会先执行父类的无参构造器
Tiger tiger1 = new Tiger();
Tiger tiger2 = new Tiger("Tom");
- 默认情况下,子类全部构造器第一行代码都是
super()
,它会调用父类的无参构造器; - 如果父类没有无参构造器(只写了有参构造器),则必须手动在子类构造器的第一行加上
super(...)
,指定父类的有参构造器。
子类构造器通过 super()
调用父类构造器,把对象中包含父类的部分数据先初始化赋值;
再把对象中包含子类的部分数据也初始化赋值。
public class Animal {
private String kind;
public Animal() {
}
public Animal(String kind) {
this.kind = kind;
}
public String getKind() {
return kind;
}
}
public class Tiger extends Animal {
private String skill;
public Tiger() {
}
public Tiger(String kind, String skill) {
// 调用 super 初始化父类的私有变量
super(kind);
this.skill = skill;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
public class Test {
public static void main(String[] args) {
Tiger tiger = new Tiger("老虎", "捕食");
System.out.println(tiger.getSkill());
System.out.println(tiger.getKind());
}
}
- 使用
this()
,调用兄弟构造器; -
this()
和super()
不能同时出现,因为兄弟构造器内部也会执行super()
,会导致父类构造器执行两次; -
this()
和super()
必须在第一行;
public class Animal {
private String kind;
private String habit;
private String desc;
public Animal() {
}
public Animal(String kind, String habit) {
// 使用 this(),调用兄弟构造器
this(kind, habit, "动物大世界");
}
public Animal(String kind, String habit, String desc) {
this.kind = kind;
this.desc = desc;
this.habit = habit;
}
}
- 一定有继承或实现关系;
- 存在父类引用子类对象;
- 存在方法重写。
public class Animal {
String name = "动物";
public void bark() {
System.out.println("动物的叫声");
}
}
// 1. Dog 继承自 Animal
public class Dog extends Animal {
String name = "狗";
// 2. 存在方法重写
@Override
public void bark() {
System.out.println("汪汪汪");
}
public void guard() {
System.out.println("看门");
}
}
// 3. 存在父类引用子类对象
Animal dog = new Dog(); // 编译看左边,执行看右边
dog.bark();
// 5. 不能调用子类独有的功能
dog.guard(); // error,不能调用 Dog 独有的功能,因为 Animal 没有 Dog 独有的 guard() 方法
// 4. Java 中的属性(成员变量)没有多态
System.out.println(dog.name); // 依旧是 "动物的叫声"
- 多态是对象和行为的多态,Java 中的属性(成员变量)没有多态;
- 不能调用子类独有的功能;
- 只要有继承或实现关系的两个类就可以强转;
- 编译时不会报错,但可能出现强制类型转换异常;
Animal dog = new Dog();
dog.guard(); // error,Animal 没有 guard()
Dog dogCopy = (Dog) dog;
dogCopy.guard(); // success,Animal 强制转换成了 Dog
Cat catCopy = (Cat) dog; // success,没有报错,但其实把 dog 转成 cat 是不对的
catCopy.guard(); // 报错 cat 没有 guard()
建议类型收窄时,使用 instanceof
进行校验
if (dog instanceof Dog) {
Dog dogCopy = (Dog) dog;
dogCopy.guard();
}
可以修饰类、方法、变量
- 修饰类:被
final
修饰的类成为最终类
,不能再被继承了; - 修饰方法:被
final
修饰的方法成为最终方法
,不能被重写了; - 修饰变量:被
final
修饰的变量只能被赋值一次。
public class Test {
public static final String USER_NAME = "张三";
// 或者
public static final int USER_AGE;
static {
USER_AGE = 18;
}
}
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类;
- 类该有的成员(成员变量、方法、构造器)抽象类都可以有;
- 抽象类不能创建对象,仅作为一中特殊的父类,用来给子类继承并实现;
- 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
public abstract class A {
// 只有签名,没有具体实现
public abstract void go()
}
public class B extends A {
// 重写抽象类中定义的抽象方法
@Override
public void go() {
System.out.println("重写抽象类的抽象方法");
}
}
B b = new B();
b.go();
- 解决方法中存在的重复代码的问题;
- 建议使用
final
保护模板方法,避免被重写导致失效;
public abstract class People {
public final void write() {
System.out.println("《标题》");
// 替换中间自定义的内容
writeContent();
System.out.println("结尾");
}
public abstract void writeContent();
}
public class Student extends People {
@Override
public void writeContent() {
System.out.println("我爱上课");
}
}
public class Teacher extends People {
@Override
public void writeContent() {
System.out.println("我爱教课");
}
}
People s = new Student()
s.write()
People t = new Teacher()
t.write()
- 接口不能创建对象;
- 接口用来被类实现
implements
,实现接口的类被称为实现类
; - 接口与接口是多继承,一个接口可以同时继承多个接口;
- 实现类实现多个接口,必须重写完全部接口定义的全部抽象方法,否则这个类必须是抽象类;
public interface A {
// 定义常量,可以省略 public static final
String USER_NAME = "张三";
// 定义抽象方法,可以省略 public abstract
void run();
}
public interface B {
void eat();
}
// 实现类
public class BImpl implements A, B {
@Override
public void run() {
}
@Override
public void eat() {
}
}
BI s1 = new BImpl();
A s2 = new BImpl();
B s3 = new BImpl();
interface A {
void a()
}
interface B {
void b()
}
interface C extends A, B {
void c()
}
-
default
修饰默认方法(普通方法); -
private
修饰私有方法,可以通过接口内部的普通方法调用; -
static
修饰静态方法,只能用接口本身调用;
public interface A {
// 1. default 默认方法
default void go() {
run();
System.out.println("普通的实例方法");
}
// 2. private 私有方法
// 可以通过接口内部的普通方法调用
private void run() {
System.out.println("调用接口内部私有方法");
}
static void hello() {
System.out.println("只能用接口本身调用");
}
}
// 通过实现类调用普通方法
public class AImpl implements A {}
AImpl instance = new AImpl();
instance.go();
// 直接调用接口执行接口的静态方法
A.hello();
- 一个接口继承多个接口,如果多个接口中存在方法签名冲突(比如返回值类型不一样),则此时不支持多继承;
- 一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现;
- 一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先使用父类的;
- 一个类实现了多个接口,多个接口中存在同名默认方法,可以不冲突,这个类重写该方法即可。
类的五大成分:成员变量、方法、构造器、内部类、代码块。
如果一个类定义在另一个类的内部,这个类就是内部类。
- 可以直接访问外部类的实例成员、静态成员;
- 可以使用
外部类名.this
获取当前外部类对象;
public class People {
private String name = "张三";
// 成员内部类
public class User {
private String name = "李四";
public void show() {
String name = "王五";
System.out.println(name); // 王五
System.out.println(this.name); // 李四
System.out.println(People.this.name); // 张三
}
}
}
// 创建成员内部类实例对象
People.User user = new People().new User();
- 可以直接访问外部类的静态成员,不能访问外部类的实例成员
public class People {
private String name = "张三";
private static double height;
// 静态内部类
public static class User {
public void show() {
// 可以访问外部类的静态成员
System.out.println(height); // success
// 不可以访问外部类的实例成员
System.out.println(name); // error
}
}
}
// 创建静态内部类实例对象
People.User user = new People.User();
- 定义在方法、代码块、构造器等执行体中;
public class Test {
public static void main(String[] args) {
class A {
// 鸡肋语法,没啥卵用
}
}
}
- 特殊的局部内部类,匿名指不需要为这个类申明名字;
- 本质是一个子类,并会立即创建出一个子类对象;
- 用于更方便的创建一个子类对象;
public class Test {
public static void main(String[] args) {
// 创建匿名内部类对象
// 匿名内部类的名称:“当前类名$编号”
Animal dog = new Animal() {
@Override
public void eat() {
System.out.println("小狗吃骨头");
}
};
dog.eat();
}
}
abstract class Animal {
public abstract void eat();
}
作为一个对象参数传输给方法使用
public class Test {
public static void main(String[] args) {
// 直接实现 Swimming 接口,并立即创建一个实例对象
Swimming s1 = new Swimming() {
@Override
public void swim() {
System.out.println("蛙泳");
}
};
go(s1);
go(new Swimming() {
@Override
public void swim() {
System.out.println("自由泳");
}
});
}
public static void go(Swimming s) {
s.swim();
}
}
interface Swimming {
void swim();
}
- 枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象;
- 枚举类的构造器是私有的,所以枚举类对外不能创建对象;
- 枚举类是最终类,不可以被继承;
- 枚举中,从第二行开始,可以定义类的其他各种成员;
- 编译器为枚举类新增了几个方法,并且枚举类都是继承:java.lang.Enum 类的,从 enum 类也会继承到一些方法。
public enum A {
X, Y, Z
// 也可以定义其他成员
private int age;
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
public class Test {
public static void main(String[] args) {
A x = A.X;
A y = A.Y;
A z = A.Z;
System.out.println(x + " " + y + " " + z);
// 1. values 方法用来获取枚举类的全部对象,返回一个数组
A[] list = A.values();
for (int i = 0; i < list.length; i++) {
A item = list[i];
System.out.println(item);
}
// 2. valueOf 返回具有指定名称的指定枚举类型的枚举常量
A z2 = A.valueOf("Z");
System.out.println(z2 == z); // true
// 3. ordinal 返回此枚举常量的序数索引
System.out.println(x.ordinal()); // 0
System.out.println(y.ordinal()); // 1
}
}
用作信息标志和信息分类
public enum ActionType {
DOWN, UP, HALF_UP, DELETE_LEFT;
}
public class Test {
public static void main(String[] args) {
handleData(3.9, ActionType.DOWN);
}
public static double handleData(double data, ActionType type) {
switch(type) {
case DOWN:
data = Math.floor(data);
break;
case UP:
data = Math.ceil(data);
break;
case HALF_UP:
data = Math.round(data);
break;
case DELETE_LEFT:
data = (int) data;
break;
}
return data;
}
}
泛型变量建议用大写字母,比如,E、T、K、V 等。
修饰符 class 类名<类型变量,类型变量,......>
修饰符 interface 接口名<类型变量,类型变量,......>
修饰符 <类型变量,类型变量,......> 返回值类型 方法名(形参列表) {}
public static <T> T printArray(T[] a) {}
- 通配符
?
表示任意类型; - 上下限
? extends Car
表示上限必须是 Car 或 Car 的子类; - 上下限
? super Car
表示下限必须是 Car 或 Car 的父类;
public static void go(ArrayList<? extends Car> car) {}
- 泛型工作在编译阶段,一旦程序编译成 class 文件,class 文件中就不存在泛型了,这就是泛型擦除;
- 泛型不能直接支持基本数据类型,只能支持对象类型(引用数据类型);
只能简化函数式接口的匿名内部类代码。
(被重写方法的形参列表) -> {
被重写的方法;
}
- 有且仅有一个抽象方法的接口;
- 在大部分的函数式接口上,会有一个
@FunctionalInterface
的注解,有该注解的接口必定是函数式接口。
public class Test {
public static void main(String[] args) {
Animal cat = new Animal() {
@Override
public void eat() {
System.out.println("小猫吃鱼");
}
};
// 简化函数式接口
Animal dog = () -> {
System.out.println("小狗吃肉");
};
dog.eat();
}
}
@FunctionalInterface
interface Animal {
// 有且仅有一个抽象方法
void eat();
}
- 参数类型可以不写;
- 如果只有一个参数,参数类型可以省略,同时()也可以省略;
- 如果 Lambda 表达式中的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号。此时,如果这行代码是 return 语句,也必须去掉 return 不写。
// 简化前:
Arrays.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getHeight(), o2.getHeight());
}
});
// 简化后:
Arrays.sort(list, (o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));
如果某个 Lambda 表达式里只是调用一个静态方法,并且前后参数的形式一致(传递的参数列表和接受的参数列表),就可以使用静态方法引用。
类名::静态方法
public class Student {
public static int compareByHeight(Student o1, Student o2) {
return Double.compare(o1.getHeight(), o2.getHeight());
}
}
public class Test {
public static void main(String[] args) {
Student[] list = new Student[2];
list[0] = new Student("张三", 18, '男', 1.72);
list[1] = new Student("李四", 19, '男', 1.82);
// 调用静态方法
Arrays.sort(list, (o1, o2) -> Student.compareByHeight(o1, o2));
// 简写:
// 1. 调用的是 compareByHeight 静态方法;
// 2. 前面传递的参数列表和后面接受的参数列表一致;
Arrays.sort(list, Student::compareByHeight);
}
}
与静态方法的引用类似
public class Test {
public static void main(String[] args) {
Test t = new Test();
Arrays.sort(list, t::compareByHeight);
}
public int compareByHeight(Student o1, Student o2) {
return Double.compare(o1.getHeight(), o2.getHeight());
}
}
如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用。
public class Test {
public static void main(String[] args) {
String[] names = {"dlei", "Angela", "baby", "caocao", "coach", "曹操", "deby", "eason", "andy"};
// 排序的正常写法:
Arrays.sort(names, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// 忽略大小写,按首字母排序
return o1.compareToIgnoreCase(o2);
}
});
// 简化:
// 1. 调用的是 o1 实例的方法
// 2. o1 是函数体方法的主调
// 3. o2 是实例方法的入参
Arrays.sort(names, (o1, o2) -> o1.compareToIgnoreCase(o2));
// 终极简化:
Arrays.sort(names, String::compareToIgnoreCase);
}
}
如果 Lambda 表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用。
// Car 实体类
public class Car {
private String name;
public Car() {}
public Car(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 创造 Car 的接口
@FunctionalInterface
interface CreateCar {
Car create(String name);
}
public Test {
public static void main(String[] args) {
// 1. 正常写法:
CreateCar creator = new CreateCar() {
@Override
public Car create(String name) {
return new Car(name);
}
};
// 2. 简写
CreateCar creator1 = name -> new Car(name);
// 3. 终极简写:
CreateCar creator2 = Car::new;
Car han = creator2.create("比亚迪 汉L");
System.out.println(han.getName());
}
}
字符类(只匹配单个字符) | |
---|---|
[abc] | 只能是a,b,或c |
[^abc] | 除了a,b,c之外的任何字符 |
[a-zA-Z] | a到z A到z,包括(范围) |
[a-d[m-p]] | a到d,或m到p |
[a-z&&[def]] | d,e,或f(交集) |
[a-z&&[^bc]] | a到z,除了b和c(等同于[ad-z]) |
[a-z&&[^m-p]]: | a到z,除了m到p(等同于[a-1q-z]) |
预定义字符(只匹配单个字符) | |
. | 任何字符 |
\d | 一个数字: [0-9] |
\D | 非数字:[^0-9] |
\s | 一个空白字符 |
\S | 非空白字符: [^\s] |
\w | [a-zA-Z 0-9] |
\W | [^\w]一个非单词字符 |
数量词 | |
x? | X,一次或0次 |
x* | X,零次或多次 |
x+ | X,一次或多次 |
x {n} | X,正好n次 |
x {n, } | X,至少n次 |
x {n, m} | X,至少n但不超过m次 |
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Test {
public static void main(String[] args) {
// 1. 定义爬取规则对象,定义要爬取的格式
Pattern pattern = Pattern.compile("[A-Z]");
// 2. 通过匹配规则对象与内容建立联系,得到一个匹配器对象
Matcher matcher = pattern.matcher("123Zahahaz" + "ZZZ");
// 3. 使用匹配器对象爬取内容
while (matcher.find()) {
String res1 = matcher.group();
String res2 = matcher.group(1); // 只要爬取出来的邮箱中的第一组括号内容
System.out.println(res1);
System.out.println(res2);
}
}
}
使用小括号()
,创建分组。
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexExample {
public static void main(String[] args) {
// 创建Pattern对象,使用 () 创建分组
Pattern pattern = Pattern.compile("(\\w+)@(\\w+\\.\\w+)");
// 创建Matcher对象
String input = "My email is [email protected]";
Matcher matcher = pattern.matcher(input);
// 查找匹配项并获取分组内容
if (matcher.find()) {
String localPart = matcher.group(1); // 获取本地部分(local part)
String domain = matcher.group(2); // 获取域名部分(domain)
System.out.println("Local Part: " + localPart);
System.out.println("Domain: " + domain);
} else {
System.out.println("No match found.");
}
}
}
紧跟在任何量词 *、 +、? 或 {} 的后面,将会使量词变为非贪婪(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)正好相反。
public class RegexExample {
public static void main(String[] args) {
// 在 + 后添加 ? 开启惰性模式
Pattern pattern = Pattern.compile("欢迎(.+?)光临");
String input = "欢迎张三光临" + "欢迎李四光临" + "欢迎王五光临";
Matcher matcher = pattern.matcher(input);
// 查找匹配项并获取分组内容
while (matcher.find()) {
String user = matcher.group(1);
System.out.println("用户:" + user);
}
}
}
结合 String 提供的以下方法实现
方法名 | 说明 |
---|---|
public String replaceAll(String regex, String newStr) | 按照正则表达式匹配的内容进行替换 |
public String[] split(String regex) | 按照正则表达式匹配的内容进行分割字符串,返回一个字符串数组 |
public class Test {
public static void main(String[] args) {
String str = "古力娜扎asd12迪丽热巴gdf241马尔扎哈88986ui卡尔扎巴";
// 替换
String res = str.replaceAll("\\w+", "-");
System.out.println(res);
System.out.println("\n-----------------\n");
// 分割
String[] names = str.split("\\w+");
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
}
}
示例 | 说明 | 异常 |
---|---|---|
int[] arr = {1}; arr[2]; |
数组索引越界异常 | ArrayIndexOutofBoundsException |
String name = null; name.length; |
空指针异常 | NullPointerException |
10 / 0; | 数学操作异常 | ArithmeticException |
Object o = "哈哈"; Integer i = (Integer) o; |
类型转换异常 | ClassCastException |
String s = "23a"; int i = Integer.valueOf(s); |
数字转换异常 | NumberFormatException |
Error:代表系统级别错误;
Exception:异常,代表程序出现问题,通常会用 Exception 以及子类来封装程序出现的问题。
- 运行时异常:RuntimeException 及其子类,编译阶段不会出现错误提醒,运行时出现的异常(比如数组索引越界异常);
- 编译时异常:编译阶段就会出现错误提醒。(比如日期解析异常);
-
throw new Exception("xxxx")
来抛出异常; -
try...catch() {...}
来捕获异常;
public class Test {
public static void main(String[] args) {
try {
int res = divide(10, 0);
System.out.println("计算结果: " + res);
} catch (RuntimeException e) {
// 2. catch 捕获异常并打印异常信息
System.out.println("捕获到异常");
e.printStackTrace();
}
System.out.println("程序运行结束");
}
public static int divide(int a, int b) {
if (b == 0) {
// 1. 抛出数学计算异常
throw new ArithmeticException("Divide by zero");
} else {
return a / b;
}
}
}
- 定义一个异常类继承
RuntimeException
; - 重写构造器;
- 通过
throw new 异常类(xxx)
来创建异常对象并抛出。
// 自定义运行时异常
public class CustomRuntimeException extends RuntimeException {
public CustomRuntimeException() {}
public CustomRuntimeException(String message) {
super(message);
}
}
public class Test {
public static void main(String[] args) {
try {
setAge(200);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setAge(int age) {
if (age < 1 || age > 100) {
throw new CustomRuntimeException("非法年龄");
}
System.out.println("年龄合法");
}
}
- 定义一个异常类继承
Exception
; - 重写构造器;
- 通过
throw new 异常类(xxx)
来创建异常对象并抛出; - 在方法签名上添加
throws 异常类
,抛出异常(否则代码会报错);
// 自定义运行时异常
public class CustomRuntimeException extends RuntimeException {
public CustomRuntimeException() {}
public CustomRuntimeException(String message) {
super(message);
}
}
public class Test {
public static void main(String[] args) {
try {
setName(null);
} catch (Exception e) {
e.printStackTrace();
}
}
// 方法签名添加 throws CustomCompileException 抛出错误
public static void setName(String name) throws CustomCompileException {
if (name == null) {
throw new CustomCompileException("非法姓名");
}
System.out.println("名字合法");
}
}
建议使用运行时异常,避免频繁和非必要的报错。
方式一:捕获异常,记录并响应合适信息给用户;
方式二:捕获异常,尝试重新修复;
Collection 是单例集合的祖宗,全部单例集合都会继承它规定的方法(功能)。
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
boolean addAll(Collection<? extends E> c) | 将指定集合中的所有元素添加到此集合中 |
Collection<string> list1 = new ArrayList<>();
list1.add("ab");
list1.add("cd");
list1.add("ef");
// 将字符串集合转成字符串数组
String[] arrays = list1.toArray(String[]::new)
// 把 list2 的数据添加进 list1
Collection<string> list2 = new ArrayList<>();
list1.addAll(list2);
迭代器是用来遍历集合的专用方式(数组没有迭代器)
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
// 获取集合的迭代器对象
Iterator<String> iterator = list.iterator();
// next() 方法返回迭代中的下一个元素
// System.out.println(iterator.next()); // a
// System.out.println(iterator.next()); // b
// System.out.println(iterator.next()); // c
// System.out.println(iterator.next()); // 溢出 error NuSuchElementException
// 使用循环迭代
while(iterator.hasNext()) {
String ele = iterator.next();
System.out.println(ele);
}
}
}
用来遍历集合或者数组
for (类型 变量 : 集合)
// 遍历 Collection
Collection<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
for (String s : list) {
System.out.println(s);
}
// 遍历数组
int[] arr = {1, 2, 3, 4};
for (int i : arr) {
System.out.println("i = " + i);
}
使用 Collection 的 forEach
方法来完成
default void forEach(Consumer<? super T> action)
Collection<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
// 简写
list.forEach(s -> System.out.println(s));
// 方法引用简写
list.forEach(System.out::println);
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
注意:
- **Iterator 遍历、增强 for 遍历、forEach 遍历中使用集合自带的 **
**remove()**
方法都会出现并发修改异常错误; - **如果需要在遍历时删除数据,请使用 Iterator 自带的 **
**remove**
方法进行删除。
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Java入门");
list.add("硫磺枸杞");
list.add("黑枸杞");
list.add("Java 进阶");
// 1. 迭代器的并发修改异常
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String ele = iterator.next();
if (ele.contains("枸杞")) {
list.remove(ele); // error
}
}
// 2. 增强 for 的并发修改异常
for (String s : list) {
if (s.contains("枸杞")) {
list.remove(s); // error
}
}
// 3. forEach 的并发修改异常
list.forEach(s -> {
if (s.contains("枸杞")) {
list.remove(s); // error
}
});
}
}
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String ele = iterator.next();
if (ele.contains("枸杞")) {
// 只能使用 Iterator 自带的 remove() 进行删除
iterator.remove();
}
}
如果非要用 for 循环,可以倒叙删除,或者使用 i-- 的方式进行删除。
底层采用的数据结构不用,应用场景不同
- 基于数组实现;
- 查询速度快
特指根据索引寻址速度快,查询数据通过地址值和索引定位,查询任意数据耗时相同。
- 删除效率低
可能需要把后面很多的数据进行迁移
- 添加效率低
可鞥需要把后面很多数据后移,再添加元素,或者也可能需要进行数组的扩容。
- 利用无参构造器创建的集合,会在底层创建一个默认长度为 0 的数组;
- 添加第一个元素时,底层会创建一个新的长度为 10 的数组;
- 存满时,会扩容 1.5 倍;
- 如果一次添加多个元素,1.5 倍还放不下,则新创建数组的长度以实际长度为准;
// 开发中建议使用多态创建 ArrayList 集合
List<String> list = new ArrayList<String>();
- 基于双链表实现;
- 查询慢,增删相对较快,尤其是首尾节点;
方法名 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(Ee) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
LinkedList<String> queue1 = new LinkedList<String>();
// 按队列添加元素(先进)
queue1.addLast("第一个人");
queue1.addLast("第二个人");
queue1.addLast("第三个人");
// 按队列移除元素(先出)
queue1.removeFirst();
queue1.removeFirst();
System.out.println("queue = " + queue1);
LinkedList<String> queue2 = new LinkedList<String>();
// 想象一个右端封口,左端开口的栈:
// 按栈添加元素(进栈)
queue2.push("第一颗子弹"); // 就是 addFirst()
queue2.push("第二颗子弹");
queue2.push("第三颗子弹");
System.out.println("queue2 = " + queue2); // [第三颗子弹, 第二颗子弹, 第一颗子弹]
// 按栈移除元素(出栈)
queue2.pop(); // 就是 removeFirst()
System.out.println("queue2 = " + queue2); // [第二颗子弹, 第一颗子弹]
Set 系列集合特点:
- 无序,添加数据的顺序和获取出的数据顺序不一致;
- 不重复;
- 无索引
Set 要用到的常用方法,基本上都是 Collection 提供的,自己几乎没有额外新增一些功能。
- HashSet:无序、不重复、无索引;
- LinkedHashSet:有序、不重复、无索引;
- TreeSet:默认按元素大小升序排序、不重复、无索引;
- 是一个 int 类型的随机数值,Java 中每一个对象都有一个哈希值;
- Java 中所有的对象,都可以调用 Object 类提供的
hashCode()
方法,返回该对象自己的哈希值;
- 同一个对象多次调用
hashCode()
方法返回的哈希值是相同的; - 不同对象,它们的哈希值一般不同,但也有可能会相同(哈希碰撞);
Set<String> set = new HashSet<>();
- 基于哈希表实现
- 先看元素的哈希值是否一样;
- 再看元素的内容是否一样。
所以,HashSet 集合默认不能对内容一样的两个不同对象去重。
解决:必须重写对象的 hasCode()
和 equals()
方法。
public class Student {
private String name;
private int age;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "{name=" + name + ", age=" + age + "}";
}
// 只要两个对象的内容一样,返回的哈希值就是一样的
@Override
public int hashCode() {
return Objects.hash(name, age);
}
// 比较内容
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
}
public class HashSetDemo {
public static void main(String[] args) {
Set<Student> list = new HashSet<>();
Student s1 = new Student("张三", 18);
Student s2 = new Student("李四", 19);
Student s3 = new Student("李四", 19);
list.add(s1);
list.add(s2);
list.add(s3);
// 并没有去重 [{name=张三, age=18}, {name=李四, age=19}, {name=李四, age=19}]
System.out.println(list);
// 因为 s2 和 s3 的 hashcode 不一样,HashSet 先比较 hashCode,再比较内容
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
// 解决:重写 Student 的 hashCode() 和 equals()
}
}
- 基于哈希表实现;
- 每个元素都额外的多了一个双链表机制记录它前后元素的位置;
- 基于红黑树实现的排序
- 对于数值类型,Integer、Double,默认按照数值本身的大小进行升序排序;
- 对于字符串类型,默认按照首字符的编号生序排序;
- TreeSet 集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定:
- 方式一:让自定义的类实现
Comparable
接口,重写compareTo()
方法来指定规则; - 方式二:通过调用 TreeSet 集合有参构造器,可以设置
Comparator
对象(比较器对象,用于指定比较规则)public TreeSet (Comparator<? super E> comparator)
public class Student implements Comparable<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 String toString() {
return "{name=" + name + ", age=" + age + "}";
}
@Override
public int compareTo(Student o) {
// return this.age - o.age; // 升序
return o.age - this.age; // 降序
}
}
public class TreeSetDemo {
public static void main(String[] args) {
// 自定义排序方式一:类实现 Comparable 接口,重写 compareTo 方法
// Set<Student> list = new TreeSet<>();
// 自定义排序方式二:传递 Comparator 对象
// Set<Student> list = new TreeSet<>(new Comparator<Student>() {
// @Override
// public int compare(Student o1, Student o2) {
// return Double.compare(o2.getAge(), o1.getAge());
// }
// });
Set<Student> list = new TreeSet<>((o1, o2) -> Double.compare(o2.getAge(), o1.getAge()));
list.add(new Student("张三", 18));
list.add(new Student("李四", 19));
list.add(new Student("王五", 20));
System.out.println("list = " + list);
}
}
Set 集合 | 使用场景 |
---|---|
ArrayList ★★★ | 记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据 |
LinkedList | 希望记住元素的添加顺序,且增删首尾数据的情况较多 |
HashSet ★★★ | 不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快 |
LinkedHashSet | 记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快 |
TreeSet | 对元素进行排序,也没有重复元素需要存储且希望增删改查都快? |